kaizenai 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +0 -4
  2. package/bin/kaizen +16 -5
  3. package/dist/client/app-icon-180.png +0 -0
  4. package/dist/client/app-icon-192.png +0 -0
  5. package/dist/client/app-icon-512.png +0 -0
  6. package/dist/client/app-icon-96.png +0 -0
  7. package/dist/client/apple-touch-icon.png +0 -0
  8. package/dist/client/assets/index-C4Zm475L.js +638 -0
  9. package/dist/client/assets/index-CxKis6wK.css +32 -0
  10. package/dist/client/favicon.png +0 -0
  11. package/dist/client/index.html +17 -10
  12. package/dist/client/manifest-dark.webmanifest +3 -3
  13. package/dist/client/manifest.webmanifest +3 -3
  14. package/dist/client/pwa-192.png +0 -0
  15. package/dist/client/pwa-512.png +0 -0
  16. package/dist/server/cli-supervisor.js +306 -0
  17. package/dist/server/cli.js +12636 -0
  18. package/package.json +7 -10
  19. package/dist/client/assets/index-BBs80KD-.js +0 -623
  20. package/dist/client/assets/index-CkCgyLNq.css +0 -32
  21. package/src/server/acp-shared.ts +0 -315
  22. package/src/server/agent.ts +0 -1159
  23. package/src/server/attachments.ts +0 -133
  24. package/src/server/backgrounds.ts +0 -74
  25. package/src/server/cli-runtime.ts +0 -375
  26. package/src/server/cli-supervisor.ts +0 -97
  27. package/src/server/cli.ts +0 -68
  28. package/src/server/codex-app-server-protocol.ts +0 -453
  29. package/src/server/codex-app-server.ts +0 -1350
  30. package/src/server/cursor-acp.ts +0 -819
  31. package/src/server/discovery.ts +0 -322
  32. package/src/server/event-store.ts +0 -1470
  33. package/src/server/events.ts +0 -252
  34. package/src/server/external-open.ts +0 -272
  35. package/src/server/gemini-acp.ts +0 -844
  36. package/src/server/gemini-cli.ts +0 -525
  37. package/src/server/generate-title.ts +0 -36
  38. package/src/server/git-manager.ts +0 -79
  39. package/src/server/git-repository.ts +0 -101
  40. package/src/server/harness-types.ts +0 -20
  41. package/src/server/keybindings.ts +0 -177
  42. package/src/server/machine-name.ts +0 -22
  43. package/src/server/paths.ts +0 -112
  44. package/src/server/process-utils.ts +0 -22
  45. package/src/server/project-icon.ts +0 -352
  46. package/src/server/project-metadata.ts +0 -10
  47. package/src/server/provider-catalog.ts +0 -85
  48. package/src/server/provider-settings.ts +0 -155
  49. package/src/server/quick-response.ts +0 -153
  50. package/src/server/read-models.ts +0 -275
  51. package/src/server/recovery.ts +0 -507
  52. package/src/server/restart.ts +0 -56
  53. package/src/server/server.ts +0 -244
  54. package/src/server/terminal-manager.ts +0 -350
  55. package/src/server/theme-settings.ts +0 -179
  56. package/src/server/update-manager.ts +0 -230
  57. package/src/server/usage/base-provider-usage.ts +0 -57
  58. package/src/server/usage/claude-usage.ts +0 -558
  59. package/src/server/usage/codex-usage.ts +0 -144
  60. package/src/server/usage/cursor-browser.ts +0 -120
  61. package/src/server/usage/cursor-cookies.ts +0 -390
  62. package/src/server/usage/cursor-usage.ts +0 -490
  63. package/src/server/usage/gemini-usage.ts +0 -24
  64. package/src/server/usage/provider-usage.ts +0 -61
  65. package/src/server/usage/test-helpers.ts +0 -9
  66. package/src/server/usage/types.ts +0 -54
  67. package/src/server/usage/utils.ts +0 -325
  68. package/src/server/ws-router.ts +0 -742
  69. package/src/shared/branding.ts +0 -83
  70. package/src/shared/dev-ports.ts +0 -43
  71. package/src/shared/ports.ts +0 -2
  72. package/src/shared/protocol.ts +0 -156
  73. package/src/shared/tools.ts +0 -251
  74. package/src/shared/types.ts +0 -1040
@@ -1,133 +0,0 @@
1
- import path from "node:path"
2
- import { mkdir, writeFile } from "node:fs/promises"
3
- import {
4
- MAX_CHAT_ATTACHMENTS,
5
- MAX_CHAT_IMAGE_BYTES,
6
- SUPPORTED_CHAT_IMAGE_MIME_TYPES,
7
- type ChatAttachment,
8
- type ChatAttachmentUpload,
9
- type ChatImageAttachment,
10
- type UserPromptEntry,
11
- } from "../shared/types"
12
-
13
- export const ATTACHMENTS_ROUTE_PREFIX = "/attachments"
14
- export const MAX_CHAT_IMAGE_DATA_URL_CHARS = 14_000_000
15
- export const SUPPORTED_CHAT_IMAGE_MIME_TYPES_SET = new Set(SUPPORTED_CHAT_IMAGE_MIME_TYPES)
16
-
17
- const EXTENSIONS_BY_MIME_TYPE: Record<string, string> = {
18
- "image/gif": ".gif",
19
- "image/jpeg": ".jpg",
20
- "image/png": ".png",
21
- "image/webp": ".webp",
22
- }
23
-
24
- export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null {
25
- const normalized = path.normalize(rawRelativePath).replace(/^[/\\]+/, "")
26
- if (!normalized || normalized.startsWith("..") || normalized.includes("\0")) {
27
- return null
28
- }
29
- return normalized.replace(/\\/g, "/")
30
- }
31
-
32
- export function resolveAttachmentPath(attachmentsDir: string, relativePath: string): string | null {
33
- const normalizedRelativePath = normalizeAttachmentRelativePath(relativePath)
34
- if (!normalizedRelativePath) return null
35
-
36
- const attachmentsRoot = path.resolve(attachmentsDir)
37
- const filePath = path.resolve(path.join(attachmentsRoot, normalizedRelativePath))
38
- if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) {
39
- return null
40
- }
41
-
42
- return filePath
43
- }
44
-
45
- export function buildAttachmentPreviewUrl(relativePath: string): string {
46
- return `${ATTACHMENTS_ROUTE_PREFIX}/${relativePath.split("/").map(encodeURIComponent).join("/")}`
47
- }
48
-
49
- function parseBase64DataUrl(dataUrl: string): { mimeType: string; bytes: Buffer } | null {
50
- const match = /^data:([^,;]+)(?:;charset=[^,;]+)?;base64,([a-z0-9+/=\r\n ]+)$/i.exec(dataUrl.trim())
51
- if (!match) return null
52
-
53
- const mimeType = match[1].toLowerCase()
54
- const base64 = match[2].replace(/\s+/g, "")
55
- try {
56
- return {
57
- mimeType,
58
- bytes: Buffer.from(base64, "base64"),
59
- }
60
- } catch {
61
- return null
62
- }
63
- }
64
-
65
- function extensionForMimeType(mimeType: string): string | null {
66
- return EXTENSIONS_BY_MIME_TYPE[mimeType] ?? null
67
- }
68
-
69
- export async function persistChatAttachments(input: {
70
- attachmentsDir: string
71
- chatId: string
72
- messageEntry: UserPromptEntry
73
- uploads: ChatAttachmentUpload[] | undefined
74
- }): Promise<ChatAttachment[] | undefined> {
75
- const uploads = input.uploads ?? []
76
- if (uploads.length === 0) return undefined
77
- if (uploads.length > MAX_CHAT_ATTACHMENTS) {
78
- throw new Error(`Too many image attachments. Maximum is ${MAX_CHAT_ATTACHMENTS}.`)
79
- }
80
-
81
- const persisted: ChatImageAttachment[] = []
82
-
83
- for (const [index, attachment] of uploads.entries()) {
84
- if (attachment.type !== "image") {
85
- throw new Error("Unsupported attachment type.")
86
- }
87
- if (!attachment.name.trim()) {
88
- throw new Error("Attachment name is required.")
89
- }
90
- if (!attachment.mimeType.trim() || !SUPPORTED_CHAT_IMAGE_MIME_TYPES_SET.has(attachment.mimeType.toLowerCase() as typeof SUPPORTED_CHAT_IMAGE_MIME_TYPES[number])) {
91
- throw new Error(`Unsupported image type: ${attachment.mimeType}`)
92
- }
93
- if (attachment.sizeBytes <= 0 || attachment.sizeBytes > MAX_CHAT_IMAGE_BYTES) {
94
- throw new Error(`Image attachment '${attachment.name}' is empty or too large.`)
95
- }
96
- if (!attachment.dataUrl.trim() || attachment.dataUrl.length > MAX_CHAT_IMAGE_DATA_URL_CHARS) {
97
- throw new Error(`Image attachment '${attachment.name}' payload is invalid or too large.`)
98
- }
99
-
100
- const parsed = parseBase64DataUrl(attachment.dataUrl)
101
- if (!parsed || parsed.mimeType !== attachment.mimeType.toLowerCase()) {
102
- throw new Error(`Invalid image attachment payload for '${attachment.name}'.`)
103
- }
104
- if (parsed.bytes.byteLength !== attachment.sizeBytes) {
105
- throw new Error(`Image attachment '${attachment.name}' size did not match payload.`)
106
- }
107
-
108
- const extension = extensionForMimeType(parsed.mimeType)
109
- if (!extension) {
110
- throw new Error(`Unsupported image type: ${attachment.mimeType}`)
111
- }
112
-
113
- const relativePath = `${input.chatId}/${input.messageEntry._id}/${index}${extension}`
114
- const filePath = resolveAttachmentPath(input.attachmentsDir, relativePath)
115
- if (!filePath) {
116
- throw new Error(`Failed to resolve persisted path for '${attachment.name}'.`)
117
- }
118
-
119
- await mkdir(path.dirname(filePath), { recursive: true })
120
- await writeFile(filePath, parsed.bytes)
121
-
122
- persisted.push({
123
- type: "image",
124
- id: `${input.messageEntry._id}:${index}`,
125
- name: attachment.name.trim(),
126
- mimeType: parsed.mimeType,
127
- sizeBytes: parsed.bytes.byteLength,
128
- relativePath,
129
- })
130
- }
131
-
132
- return persisted
133
- }
@@ -1,74 +0,0 @@
1
- import fs from "node:fs/promises"
2
- import path from "node:path"
3
- import os from "node:os"
4
-
5
- const BACKGROUND_DIRS = [
6
- path.join(os.homedir(), ".local/share/backgrounds"),
7
- "/usr/share/backgrounds",
8
- "/Library/Desktop Pictures",
9
- path.join(os.homedir(), "Pictures"),
10
- process.platform === "win32" ? "C:\\Windows\\Web\\Wallpaper" : "",
11
- ].filter(Boolean)
12
-
13
- export interface SystemBackground {
14
- id: string
15
- name: string
16
- url: string
17
- }
18
-
19
- export async function getSystemBackgrounds(): Promise<SystemBackground[]> {
20
- const backgrounds: SystemBackground[] = []
21
- const seen = new Set<string>()
22
-
23
- async function scanDir(dir: string, depth = 0) {
24
- if (depth > 3) return // Prevent excessive recursion depth
25
-
26
- try {
27
- const entries = await fs.readdir(dir, { withFileTypes: true })
28
-
29
- for (const entry of entries) {
30
- if (entry.isDirectory()) {
31
- await scanDir(path.join(dir, entry.name), depth + 1)
32
- } else if (entry.isFile()) {
33
- const ext = path.extname(entry.name).toLowerCase()
34
- if (ext === ".jpg" || ext === ".jpeg" || ext === ".png" || ext === ".webp") {
35
- const fullPath = path.join(dir, entry.name)
36
- if (seen.has(fullPath)) continue
37
- seen.add(fullPath)
38
-
39
- const id = Buffer.from(fullPath).toString('base64url')
40
- backgrounds.push({
41
- id,
42
- name: entry.name,
43
- url: `/api/backgrounds/${id}`
44
- })
45
- }
46
- }
47
- }
48
- } catch (err) {
49
- // Ignore if directory doesn't exist or is inaccessible
50
- }
51
- }
52
-
53
- for (const dir of BACKGROUND_DIRS) {
54
- if (!dir) continue
55
- await scanDir(dir)
56
- }
57
-
58
- return backgrounds
59
- }
60
-
61
- export async function resolveBackgroundPath(id: string): Promise<string | null> {
62
- try {
63
- const fullPath = Buffer.from(id, 'base64url').toString('utf-8')
64
- const isAllowed = BACKGROUND_DIRS.some(dir => dir && fullPath.startsWith(dir))
65
- if (!isAllowed) return null
66
-
67
- const stats = await fs.stat(fullPath)
68
- if (!stats.isFile()) return null
69
-
70
- return fullPath
71
- } catch {
72
- return null
73
- }
74
- }
@@ -1,375 +0,0 @@
1
- import process from 'node:process';
2
- import { spawnSync } from 'node:child_process';
3
- import { hasCommand, spawnDetached } from './process-utils';
4
- import {
5
- APP_NAME,
6
- CLI_COMMAND,
7
- DISABLE_SELF_UPDATE_ENV_VAR,
8
- getDataDirDisplay,
9
- LOG_PREFIX,
10
- PACKAGE_NAME,
11
- } from '../shared/branding';
12
- import type { UpdateInstallErrorCode } from '../shared/types';
13
- import { PROD_SERVER_PORT } from '../shared/ports';
14
- import {
15
- CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR,
16
- CLI_SUPPRESS_OPEN_ONCE_ENV_VAR,
17
- shouldSkipStartupUpdateOnce,
18
- } from './restart';
19
-
20
- export interface CliOptions {
21
- port: number;
22
- host: string;
23
- openBrowser: boolean;
24
- strictPort: boolean;
25
- }
26
-
27
- export interface CliUpdateOptions {
28
- version: string;
29
- fetchLatestVersion: (packageName: string) => Promise<string>;
30
- installVersion: (
31
- packageName: string,
32
- version: string
33
- ) => UpdateInstallAttemptResult;
34
- argv: string[];
35
- command: string;
36
- }
37
-
38
- export interface StartedCli {
39
- kind: 'started';
40
- stop: () => Promise<void>;
41
- }
42
-
43
- export interface RestartingCli {
44
- kind: 'restarting';
45
- reason: 'startup_update' | 'ui_update';
46
- }
47
-
48
- export interface ExitedCli {
49
- kind: 'exited';
50
- code: number;
51
- }
52
-
53
- export type CliRunResult = StartedCli | RestartingCli | ExitedCli;
54
-
55
- export interface CliRuntimeDeps {
56
- version: string;
57
- bunVersion: string;
58
- allowSelfUpdate?: boolean;
59
- startServer: (
60
- options: CliOptions & {
61
- update: CliUpdateOptions;
62
- onMigrationProgress?: (message: string) => void;
63
- }
64
- ) => Promise<{ port: number; stop: () => Promise<void> }>;
65
- fetchLatestVersion: (packageName: string) => Promise<string>;
66
- installVersion: (
67
- packageName: string,
68
- version: string
69
- ) => UpdateInstallAttemptResult;
70
- openUrl: (url: string) => void;
71
- log: (message: string) => void;
72
- warn: (message: string) => void;
73
- }
74
-
75
- export interface UpdateInstallAttemptResult {
76
- ok: boolean;
77
- errorCode: UpdateInstallErrorCode | null;
78
- userTitle: string | null;
79
- userMessage: string | null;
80
- }
81
-
82
- type ParsedArgs =
83
- | { kind: 'run'; options: CliOptions }
84
- | { kind: 'help' }
85
- | { kind: 'version' };
86
-
87
- const MINIMUM_BUN_VERSION = '1.3.5';
88
-
89
- function printHelp() {
90
- console.log(`${APP_NAME} — local-only project chat UI
91
-
92
- Usage:
93
- ${CLI_COMMAND} [options]
94
-
95
- Options:
96
- --port <number> Port to listen on (default: ${PROD_SERVER_PORT})
97
- --host <host> Bind to a specific host or IP
98
- --remote Shortcut for --host 0.0.0.0
99
- --strict-port Fail instead of trying another port
100
- --no-open Don't open browser automatically
101
- --version Print version and exit
102
- --help Show this help message`);
103
- }
104
-
105
- export function parseArgs(argv: string[]): ParsedArgs {
106
- let port = PROD_SERVER_PORT;
107
- let host = '127.0.0.1';
108
- let openBrowser = true;
109
- let strictPort = false;
110
-
111
- for (let index = 0; index < argv.length; index += 1) {
112
- const arg = argv[index];
113
- if (arg === '--version' || arg === '-v') {
114
- return { kind: 'version' };
115
- }
116
- if (arg === '--help' || arg === '-h') {
117
- return { kind: 'help' };
118
- }
119
- if (arg === '--port') {
120
- const next = argv[index + 1];
121
- if (!next) throw new Error('Missing value for --port');
122
- port = Number(next);
123
- index += 1;
124
- continue;
125
- }
126
- if (arg === '--host') {
127
- const next = argv[index + 1];
128
- if (!next || next.startsWith('-'))
129
- throw new Error('Missing value for --host');
130
- host = next;
131
- index += 1;
132
- continue;
133
- }
134
- if (arg === '--remote') {
135
- host = '0.0.0.0';
136
- continue;
137
- }
138
- if (arg === '--no-open') {
139
- openBrowser = false;
140
- continue;
141
- }
142
- if (arg === '--strict-port') {
143
- strictPort = true;
144
- continue;
145
- }
146
- if (!arg.startsWith('-'))
147
- throw new Error(`Unexpected positional argument: ${arg}`);
148
- }
149
-
150
- return {
151
- kind: 'run',
152
- options: {
153
- port,
154
- host,
155
- openBrowser,
156
- strictPort,
157
- },
158
- };
159
- }
160
-
161
- export function compareVersions(currentVersion: string, latestVersion: string) {
162
- const currentParts = normalizeVersion(currentVersion);
163
- const latestParts = normalizeVersion(latestVersion);
164
- const length = Math.max(currentParts.length, latestParts.length);
165
-
166
- for (let index = 0; index < length; index += 1) {
167
- const current = currentParts[index] ?? 0;
168
- const latest = latestParts[index] ?? 0;
169
- if (current === latest) continue;
170
- return current < latest ? -1 : 1;
171
- }
172
-
173
- return 0;
174
- }
175
-
176
- function normalizeVersion(version: string) {
177
- return version
178
- .trim()
179
- .replace(/^v/i, '')
180
- .split('-')[0]
181
- .split('.')
182
- .map((part) => Number.parseInt(part, 10))
183
- .filter((part) => Number.isFinite(part));
184
- }
185
-
186
- async function maybeSelfUpdate(_argv: string[], deps: CliRuntimeDeps) {
187
- if (process.env[DISABLE_SELF_UPDATE_ENV_VAR] === '1') {
188
- return null;
189
- }
190
-
191
- if (
192
- shouldSkipStartupUpdateOnce(
193
- process.env[CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR]
194
- )
195
- ) {
196
- deps.warn(
197
- `${LOG_PREFIX} skipping startup update after restart to avoid an update loop`
198
- );
199
- return null;
200
- }
201
-
202
- deps.log(`${LOG_PREFIX} checking for updates`);
203
-
204
- let latestVersion: string;
205
- try {
206
- latestVersion = await deps.fetchLatestVersion(PACKAGE_NAME);
207
- } catch (error) {
208
- deps.warn(`${LOG_PREFIX} update check failed, continuing current version`);
209
- if (error instanceof Error && error.message) {
210
- deps.warn(`${LOG_PREFIX} ${error.message}`);
211
- }
212
- return null;
213
- }
214
-
215
- if (!latestVersion || compareVersions(deps.version, latestVersion) >= 0) {
216
- return null;
217
- }
218
-
219
- deps.log(`${LOG_PREFIX} installing ${PACKAGE_NAME}@${latestVersion}`);
220
- const installResult = deps.installVersion(PACKAGE_NAME, latestVersion);
221
- if (!installResult.ok) {
222
- deps.warn(`${LOG_PREFIX} update failed, continuing current version`);
223
- if (installResult.userMessage) {
224
- deps.warn(`${LOG_PREFIX} ${installResult.userMessage}`);
225
- }
226
- return null;
227
- }
228
-
229
- deps.log(`${LOG_PREFIX} restarting into updated version`);
230
- return 'startup_update';
231
- }
232
-
233
- export async function runCli(
234
- argv: string[],
235
- deps: CliRuntimeDeps
236
- ): Promise<CliRunResult> {
237
- const parsedArgs = parseArgs(argv);
238
- if (parsedArgs.kind === 'version') {
239
- deps.log(deps.version);
240
- return { kind: 'exited', code: 0 };
241
- }
242
- if (parsedArgs.kind === 'help') {
243
- printHelp();
244
- return { kind: 'exited', code: 0 };
245
- }
246
- if (compareVersions(deps.bunVersion, MINIMUM_BUN_VERSION) < 0) {
247
- deps.warn(
248
- `${LOG_PREFIX} Bun ${MINIMUM_BUN_VERSION}+ is required for the embedded terminal. Current Bun: ${deps.bunVersion}`
249
- );
250
- return { kind: 'exited', code: 1 };
251
- }
252
-
253
- if (deps.allowSelfUpdate !== false) {
254
- const shouldRestart = await maybeSelfUpdate(argv, deps);
255
- if (shouldRestart !== null) {
256
- return { kind: 'restarting', reason: shouldRestart };
257
- }
258
- }
259
-
260
- const { port, stop } = await deps.startServer({
261
- ...parsedArgs.options,
262
- onMigrationProgress: deps.log,
263
- update: {
264
- version: deps.version,
265
- fetchLatestVersion: deps.fetchLatestVersion,
266
- installVersion: deps.installVersion,
267
- argv,
268
- command: CLI_COMMAND,
269
- },
270
- });
271
- const bindHost = parsedArgs.options.host;
272
- const displayHost =
273
- bindHost === '127.0.0.1' || bindHost === '0.0.0.0' ? 'localhost' : bindHost;
274
- const launchUrl = `http://${displayHost}:${port}`;
275
-
276
- deps.log(`${LOG_PREFIX} listening on http://${bindHost}:${port}`);
277
- deps.log(`${LOG_PREFIX} data dir: ${getDataDirDisplay()}`);
278
-
279
- const suppressOpenBrowser =
280
- process.env[CLI_SUPPRESS_OPEN_ONCE_ENV_VAR] === '1';
281
- if (parsedArgs.options.openBrowser && !suppressOpenBrowser) {
282
- deps.openUrl(launchUrl);
283
- }
284
-
285
- return {
286
- kind: 'started',
287
- stop,
288
- };
289
- }
290
-
291
- export function openUrl(url: string) {
292
- const platform = process.platform;
293
- if (platform === 'darwin') {
294
- spawnDetached('open', [url]);
295
- } else if (platform === 'win32') {
296
- spawnDetached('cmd', ['/c', 'start', '', url]);
297
- } else {
298
- spawnDetached('xdg-open', [url]);
299
- }
300
- console.log(`${LOG_PREFIX} opened in default browser`);
301
- }
302
-
303
- export async function fetchLatestPackageVersion(packageName: string) {
304
- const response = await fetch(
305
- `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`
306
- );
307
- if (!response.ok) {
308
- throw new Error(`registry returned ${response.status}`);
309
- }
310
-
311
- const payload = (await response.json()) as { version?: unknown };
312
- if (typeof payload.version !== 'string' || !payload.version.trim()) {
313
- throw new Error('registry response did not include a version');
314
- }
315
-
316
- return payload.version;
317
- }
318
-
319
- export function classifyInstallVersionFailure(
320
- output: string
321
- ): UpdateInstallAttemptResult {
322
- const normalizedOutput = output.trim();
323
- if (
324
- /No version matching .* found|failed to resolve/i.test(normalizedOutput)
325
- ) {
326
- return {
327
- ok: false,
328
- errorCode: 'version_not_live_yet',
329
- userTitle: 'Update not live yet',
330
- userMessage:
331
- 'This update is still propagating. Try again in a few minutes.',
332
- };
333
- }
334
-
335
- return {
336
- ok: false,
337
- errorCode: 'install_failed',
338
- userTitle: 'Update failed',
339
- userMessage: 'Kaizen could not install the update. Try again later.',
340
- };
341
- }
342
-
343
- export function installPackageVersion(packageName: string, version: string) {
344
- if (!hasCommand('bun')) {
345
- return {
346
- ok: false,
347
- errorCode: 'command_missing',
348
- userTitle: 'Bun not found',
349
- userMessage: 'Kaizen could not find Bun to install the update.',
350
- } satisfies UpdateInstallAttemptResult;
351
- }
352
-
353
- const result = spawnSync(
354
- 'bun',
355
- ['install', '-g', `${packageName}@${version}`],
356
- {
357
- stdio: ['ignore', 'pipe', 'pipe'],
358
- encoding: 'utf8',
359
- }
360
- );
361
- const stdout = result.stdout ?? '';
362
- const stderr = result.stderr ?? '';
363
- if (stdout) process.stdout.write(stdout);
364
- if (stderr) process.stderr.write(stderr);
365
- if (result.status === 0) {
366
- return {
367
- ok: true,
368
- errorCode: null,
369
- userTitle: null,
370
- userMessage: null,
371
- } satisfies UpdateInstallAttemptResult;
372
- }
373
-
374
- return classifyInstallVersionFailure(`${stdout}\n${stderr}`);
375
- }
@@ -1,97 +0,0 @@
1
- import process from 'node:process';
2
- import { spawn } from 'node:child_process';
3
- import { CLI_COMMAND, LOG_PREFIX } from '../shared/branding';
4
- import {
5
- CLI_CHILD_ARGS_ENV_VAR,
6
- CLI_CHILD_COMMAND_ENV_VAR,
7
- CLI_CHILD_MODE,
8
- CLI_CHILD_MODE_ENV_VAR,
9
- CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR,
10
- CLI_SUPPRESS_OPEN_ONCE_ENV_VAR,
11
- isStartupUpdateRestart,
12
- isUiUpdateRestart,
13
- parseChildArgsEnv,
14
- shouldRestartCliProcess,
15
- } from './restart';
16
-
17
- interface ChildExit {
18
- code: number | null;
19
- signal: NodeJS.Signals | null;
20
- }
21
-
22
- function getChildProcessSpec() {
23
- const command = process.env[CLI_CHILD_COMMAND_ENV_VAR] || CLI_COMMAND;
24
- const args = parseChildArgsEnv(process.env[CLI_CHILD_ARGS_ENV_VAR]);
25
- return { command, args };
26
- }
27
-
28
- function spawnChild(argv: string[]) {
29
- const childProcess = getChildProcessSpec();
30
- const suppressOpenThisChild = suppressOpenOnNextChild;
31
- const skipStartupUpdateThisChild = skipStartupUpdateOnNextChild;
32
- suppressOpenOnNextChild = false;
33
- skipStartupUpdateOnNextChild = false;
34
- return new Promise<ChildExit>((resolve, reject) => {
35
- const child = spawn(childProcess.command, [...childProcess.args, ...argv], {
36
- stdio: 'inherit',
37
- env: {
38
- ...process.env,
39
- [CLI_CHILD_MODE_ENV_VAR]: CLI_CHILD_MODE,
40
- ...(suppressOpenThisChild
41
- ? { [CLI_SUPPRESS_OPEN_ONCE_ENV_VAR]: '1' }
42
- : {}),
43
- ...(skipStartupUpdateThisChild
44
- ? { [CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR]: '1' }
45
- : {}),
46
- },
47
- });
48
-
49
- const forwardSignal = (signal: NodeJS.Signals) => {
50
- if (child.exitCode !== null) return;
51
- child.kill(signal);
52
- };
53
-
54
- const onSigint = () => {
55
- forwardSignal('SIGINT');
56
- };
57
- const onSigterm = () => {
58
- forwardSignal('SIGTERM');
59
- };
60
-
61
- process.on('SIGINT', onSigint);
62
- process.on('SIGTERM', onSigterm);
63
-
64
- child.once('error', (error) => {
65
- process.off('SIGINT', onSigint);
66
- process.off('SIGTERM', onSigterm);
67
- reject(error);
68
- });
69
-
70
- child.once('exit', (code, signal) => {
71
- process.off('SIGINT', onSigint);
72
- process.off('SIGTERM', onSigterm);
73
- resolve({ code, signal });
74
- });
75
- });
76
- }
77
-
78
- const argv = process.argv.slice(2);
79
- let suppressOpenOnNextChild = false;
80
- let skipStartupUpdateOnNextChild = false;
81
-
82
- while (true) {
83
- const result = await spawnChild(argv);
84
- if (shouldRestartCliProcess(result.code, result.signal)) {
85
- suppressOpenOnNextChild = isUiUpdateRestart(result.code, result.signal);
86
- skipStartupUpdateOnNextChild = isStartupUpdateRestart(
87
- result.code,
88
- result.signal
89
- );
90
- console.log(
91
- `${LOG_PREFIX} supervisor restarting ${CLI_COMMAND} in the same terminal session`
92
- );
93
- continue;
94
- }
95
-
96
- process.exit(result.code ?? (result.signal ? 1 : 0));
97
- }