mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
The desktop app already shipped a bundled sidecar, but only desktop-managed sessions could see it. This change installs a `claude-haha` launcher into the user bin directory, wires PATH setup so new terminals can resolve it, and keeps desktop installer sessions aligned on the same bundled sidecar resolution path. The desktop install surface now also reports whether the launcher is ready or still waiting on a terminal restart. Constraint: The worktree already contains unrelated icon, docs, and UI changes, so this commit stages only the bundled CLI launcher slice Rejected: Tell users to install the official Claude CLI separately | it breaks the desktop out-of-box install story Rejected: Keep the bundled CLI reachable only inside desktop-managed shells | system terminals would still be unable to call the packaged runtime Rejected: Symlink directly into the app bundle instead of copying to user bin | moving or replacing the app bundle would leave a stale launcher behind Confidence: high Scope-risk: moderate Reversibility: clean Directive: Proxy-backed non-Anthropic providers still depend on the desktop server; do not assume this launcher makes every provider fully standalone Tested: bun test src/server/__tests__/desktop-cli-launcher.test.ts src/server/__tests__/settings.test.ts; bun test src/server/__tests__/conversation-service.test.ts; bun test src/utils/shell/bashProvider.test.ts; cd desktop && bun x vitest run sidecars/launcherRouting.test.ts src/components/settings/InstallCenter.test.tsx; cd desktop && bun run lint; cd desktop && bun run build; cargo check --manifest-path desktop/src-tauri/Cargo.toml Not-tested: Manual packaged desktop app install plus real Terminal/iTerm/PowerShell invocation on fresh macOS and Windows machines
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
import { tmpdir } from 'node:os'
|
|
|
|
import { ensureDesktopCliLauncherInstalled } from '../services/desktopCliLauncherService.js'
|
|
|
|
const isWindows = process.platform === 'win32'
|
|
const unixOnly = isWindows ? it.skip : it
|
|
|
|
const ORIGINAL_HOME = process.env.HOME
|
|
const ORIGINAL_USERPROFILE = process.env.USERPROFILE
|
|
const ORIGINAL_SHELL = process.env.SHELL
|
|
const ORIGINAL_PATH = process.env.PATH
|
|
const ORIGINAL_CLAUDE_CLI_PATH = process.env.CLAUDE_CLI_PATH
|
|
|
|
describe('ensureDesktopCliLauncherInstalled', () => {
|
|
let tempHome = ''
|
|
let tempSourceDir = ''
|
|
|
|
beforeEach(async () => {
|
|
tempHome = await mkdtemp(join(tmpdir(), 'desktop-cli-home-'))
|
|
tempSourceDir = await mkdtemp(join(tmpdir(), 'desktop-cli-source-'))
|
|
process.env.HOME = tempHome
|
|
process.env.USERPROFILE = tempHome
|
|
process.env.SHELL = '/bin/zsh'
|
|
process.env.PATH = ''
|
|
})
|
|
|
|
afterEach(async () => {
|
|
if (ORIGINAL_HOME === undefined) {
|
|
delete process.env.HOME
|
|
} else {
|
|
process.env.HOME = ORIGINAL_HOME
|
|
}
|
|
|
|
if (ORIGINAL_USERPROFILE === undefined) {
|
|
delete process.env.USERPROFILE
|
|
} else {
|
|
process.env.USERPROFILE = ORIGINAL_USERPROFILE
|
|
}
|
|
|
|
if (ORIGINAL_SHELL === undefined) {
|
|
delete process.env.SHELL
|
|
} else {
|
|
process.env.SHELL = ORIGINAL_SHELL
|
|
}
|
|
|
|
if (ORIGINAL_PATH === undefined) {
|
|
delete process.env.PATH
|
|
} else {
|
|
process.env.PATH = ORIGINAL_PATH
|
|
}
|
|
|
|
if (ORIGINAL_CLAUDE_CLI_PATH === undefined) {
|
|
delete process.env.CLAUDE_CLI_PATH
|
|
} else {
|
|
process.env.CLAUDE_CLI_PATH = ORIGINAL_CLAUDE_CLI_PATH
|
|
}
|
|
|
|
await rm(tempHome, { recursive: true, force: true })
|
|
await rm(tempSourceDir, { recursive: true, force: true })
|
|
})
|
|
|
|
unixOnly('copies the bundled sidecar into the user bin dir and configures PATH', async () => {
|
|
const sourcePath = join(tempSourceDir, 'claude-sidecar')
|
|
await writeFile(sourcePath, '#!/bin/sh\necho desktop-sidecar\n', 'utf8')
|
|
await chmod(sourcePath, 0o755)
|
|
process.env.CLAUDE_CLI_PATH = sourcePath
|
|
|
|
const status = await ensureDesktopCliLauncherInstalled()
|
|
const launcherPath = join(tempHome, '.local', 'bin', 'claude-haha')
|
|
const shellConfigPath = join(tempHome, '.zshrc')
|
|
|
|
expect(status.supported).toBe(true)
|
|
expect(status.installed).toBe(true)
|
|
expect(status.command).toBe('claude-haha')
|
|
expect(status.launcherPath).toBe(launcherPath)
|
|
expect(status.availableInNewTerminals).toBe(true)
|
|
expect(status.needsTerminalRestart).toBe(true)
|
|
expect(status.configTarget).toBe(shellConfigPath)
|
|
|
|
expect(await readFile(launcherPath, 'utf8')).toContain('desktop-sidecar')
|
|
expect(await readFile(shellConfigPath, 'utf8')).toContain(
|
|
'export PATH="$HOME/.local/bin:$PATH"',
|
|
)
|
|
})
|
|
|
|
it('reports unsupported status when the current launcher is not a bundled sidecar', async () => {
|
|
const sourcePath = join(tempSourceDir, 'claude')
|
|
await writeFile(sourcePath, '#!/bin/sh\necho plain-cli\n', 'utf8')
|
|
process.env.CLAUDE_CLI_PATH = sourcePath
|
|
|
|
const status = await ensureDesktopCliLauncherInstalled()
|
|
|
|
expect(status.supported).toBe(false)
|
|
expect(status.installed).toBe(false)
|
|
expect(status.command).toBe('claude-haha')
|
|
})
|
|
})
|