mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
docs: add Homebrew installation (#531)
* docs: add Homebrew installation instructions Add Homebrew as an installation option for macOS/Linux users in both English and Chinese READMEs. Closes #130 * feat(cli): detect Homebrew installs and use brew upgrade for updates When kimi-code is installed via Homebrew, the update system now detects the installation source and uses 'brew upgrade kimi-code' instead of falling back to 'npm install -g'. This prevents duplicate installations when Homebrew users receive update prompts. * chore: add changeset for Homebrew update detection * fix(cli): tighten Homebrew detection and disable auto-update - Only match /cellar/ path segment (not /homebrew/) to avoid false positives on Apple Silicon where npm global installs live under /opt/homebrew/lib/node_modules/ - Disable background auto-update for Homebrew: brew upgrade may mutate dependents silently and the formula can lag behind CDN releases * fix(cli): add homebrew to InstallSource Zod schema Keeps the persistence schema in sync with the TypeScript type. Currently harmless since Homebrew auto-install is disabled, but prevents a silent state reset if it is ever enabled later. --------- Co-authored-by: liruifengv <liruifeng1024@gmail.com>
This commit is contained in:
parent
b785e2698a
commit
b47734ca0b
9 changed files with 76 additions and 0 deletions
5
.changeset/homebrew-update-detection.md
Normal file
5
.changeset/homebrew-update-detection.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": minor
|
||||
---
|
||||
|
||||
Detect Homebrew installations and use `brew upgrade kimi-code` for updates instead of falling back to npm.
|
||||
|
|
@ -19,6 +19,12 @@ Install with the official script. No Node.js required.
|
|||
curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
|
||||
```
|
||||
|
||||
- **Homebrew (macOS/Linux)**:
|
||||
|
||||
```sh
|
||||
brew install kimi-code
|
||||
```
|
||||
|
||||
- **Windows (PowerShell)**:
|
||||
|
||||
```powershell
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ Kimi Code CLI 是一个运行在终端里的 AI 编程 agent,可以帮你读
|
|||
curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
|
||||
```
|
||||
|
||||
- **Homebrew(macOS / Linux)**:
|
||||
|
||||
```sh
|
||||
brew install kimi-code
|
||||
```
|
||||
|
||||
- **Windows(PowerShell)**:
|
||||
|
||||
```powershell
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const InstallSourceSchema: z.ZodType<InstallSource> = z.enum([
|
|||
'pnpm-global',
|
||||
'yarn-global',
|
||||
'bun-global',
|
||||
'homebrew',
|
||||
'native',
|
||||
'unsupported',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ export function installCommandFor(
|
|||
return `yarn global add ${NPM_PACKAGE_NAME}@${version}`;
|
||||
case 'bun-global':
|
||||
return `bun add -g ${NPM_PACKAGE_NAME}@${version}`;
|
||||
case 'homebrew':
|
||||
return 'brew upgrade kimi-code';
|
||||
case 'native':
|
||||
return platform === 'win32' ? NATIVE_INSTALL_COMMAND_WIN : NATIVE_INSTALL_COMMAND_UNIX;
|
||||
case 'unsupported':
|
||||
|
|
@ -82,6 +84,10 @@ export function canAutoInstall(source: InstallSource, platform: NodeJS.Platform)
|
|||
case 'yarn-global':
|
||||
case 'bun-global':
|
||||
return true;
|
||||
case 'homebrew':
|
||||
// Homebrew upgrade may mutate other dependents and the formula can lag
|
||||
// behind the CDN release — prompt the user to run `brew upgrade` manually.
|
||||
return false;
|
||||
case 'native':
|
||||
return platform !== 'win32';
|
||||
case 'unsupported':
|
||||
|
|
@ -108,6 +114,8 @@ export function spawnForSource(
|
|||
return { cmd: withCmdSuffix('yarn', platform), args: ['global', 'add', `${NPM_PACKAGE_NAME}@${version}`] };
|
||||
case 'bun-global':
|
||||
return { cmd: bunCommand(platform), args: ['add', '-g', `${NPM_PACKAGE_NAME}@${version}`] };
|
||||
case 'homebrew':
|
||||
return { cmd: 'brew', args: ['upgrade', 'kimi-code'] };
|
||||
case 'native':
|
||||
// `curl … | bash` reports only the trailing bash's exit status, so a
|
||||
// failed download (curl can't connect → empty stdin → bash exits 0)
|
||||
|
|
@ -138,6 +146,9 @@ export function renderManualUpdateMessage(
|
|||
case 'bun-global':
|
||||
sourceDesc = source;
|
||||
break;
|
||||
case 'homebrew':
|
||||
sourceDesc = 'homebrew';
|
||||
break;
|
||||
case 'native':
|
||||
sourceDesc = 'native (windows). Auto-update is not supported on this platform.';
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export function detectNativeInstall(): boolean {
|
|||
const PNPM_PATH_SEGMENT = 'pnpm/global/';
|
||||
const YARN_PATH_SEGMENTS = ['.config/yarn/global/', '/.yarn/global/'];
|
||||
const BUN_PATH_SEGMENT = '.bun/install/global/';
|
||||
// Homebrew installs formulae under its Cellar directory. Avoid matching the
|
||||
// broader /homebrew/ prefix — on Apple Silicon, npm itself lives under
|
||||
// /opt/homebrew/, so `npm install -g` paths also contain /homebrew/.
|
||||
const HOMEBREW_PATH_SEGMENT = '/cellar/';
|
||||
|
||||
function normalizeForHeuristic(filePath: string): string {
|
||||
return filePath.replaceAll('\\', '/').toLowerCase();
|
||||
|
|
@ -57,6 +61,7 @@ export function classifyByPathHeuristic(packageRoot: string): InstallSource | nu
|
|||
if (normalized.includes(seg)) return 'yarn-global';
|
||||
}
|
||||
if (normalized.includes(BUN_PATH_SEGMENT)) return 'bun-global';
|
||||
if (normalized.includes(HOMEBREW_PATH_SEGMENT)) return 'homebrew';
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export type InstallSource =
|
|||
| 'pnpm-global'
|
||||
| 'yarn-global'
|
||||
| 'bun-global'
|
||||
| 'homebrew'
|
||||
| 'native'
|
||||
| 'unsupported';
|
||||
|
||||
|
|
|
|||
|
|
@ -397,6 +397,17 @@ describe('runUpdatePreflight', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('homebrew: prints manual brew upgrade command, does not spawn', async () => {
|
||||
mocks.readUpdateCache.mockResolvedValue(cacheWith('0.5.0'));
|
||||
mocks.refreshUpdateCache.mockResolvedValue(cacheWith('0.5.0'));
|
||||
mocks.detectInstallSource.mockResolvedValue('homebrew');
|
||||
const { stdout, options } = captureOutput();
|
||||
await expect(runUpdatePreflight('0.4.0', options)).resolves.toBe('continue');
|
||||
expect(stdout.join('')).toContain('brew upgrade kimi-code');
|
||||
expect(promptForInstallChoice).not.toHaveBeenCalled();
|
||||
expect(mocks.spawn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('native on darwin: spawns bash -c with pipefail-guarded curl|bash', async () => {
|
||||
disableAutoInstall();
|
||||
mocks.readUpdateCache.mockResolvedValue(cacheWith('0.5.0'));
|
||||
|
|
|
|||
|
|
@ -47,6 +47,24 @@ describe('classifyByPathHeuristic', () => {
|
|||
).toBe('bun-global');
|
||||
});
|
||||
|
||||
it('detects homebrew on macOS (Cellar path)', () => {
|
||||
expect(
|
||||
classifyByPathHeuristic('/opt/homebrew/Cellar/kimi-code/0.5.0/libexec/lib/node_modules/@moonshot-ai/kimi-code'),
|
||||
).toBe('homebrew');
|
||||
});
|
||||
|
||||
it('detects homebrew on Linux (Linuxbrew)', () => {
|
||||
expect(
|
||||
classifyByPathHeuristic('/home/linuxbrew/.linuxbrew/Cellar/kimi-code/0.5.0/libexec/lib/node_modules/@moonshot-ai/kimi-code'),
|
||||
).toBe('homebrew');
|
||||
});
|
||||
|
||||
it('does not treat npm-global under Homebrew prefix as homebrew', () => {
|
||||
expect(
|
||||
classifyByPathHeuristic('/opt/homebrew/lib/node_modules/@moonshot-ai/kimi-code'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for an unknown layout', () => {
|
||||
expect(classifyByPathHeuristic('/Users/me/dev/@moonshot-ai/kimi-code')).toBeNull();
|
||||
});
|
||||
|
|
@ -112,6 +130,18 @@ describe('detectInstallSource', () => {
|
|||
).resolves.toBe('npm-global');
|
||||
});
|
||||
|
||||
it('returns homebrew when packageRoot matches Cellar heuristic', async () => {
|
||||
await expect(
|
||||
detectInstallSource({
|
||||
getPackageRoot: () =>
|
||||
'/opt/homebrew/Cellar/kimi-code/0.5.0/libexec/lib/node_modules/@moonshot-ai/kimi-code',
|
||||
getGlobalPrefix: async () => '/usr/local',
|
||||
detectNative: () => false,
|
||||
platform: 'darwin',
|
||||
}),
|
||||
).resolves.toBe('homebrew');
|
||||
});
|
||||
|
||||
it('returns native when SEA isSea() is true (highest priority)', async () => {
|
||||
await expect(
|
||||
detectInstallSource({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue