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,101 +0,0 @@
1
- import path from "node:path"
2
- import { resolveLocalPath } from "./paths"
3
-
4
- export interface ProjectRepositoryIdentity {
5
- isGitRepo: boolean
6
- localPath: string
7
- repoKey: string
8
- repoRootPath: string | null
9
- title: string
10
- worktreePath: string
11
- }
12
-
13
- function normalizeAbsolutePath(value: string, cwd: string) {
14
- const resolved = path.isAbsolute(value) ? value : path.resolve(cwd, value)
15
- return path.normalize(resolved)
16
- }
17
-
18
- export function resolveProjectWorktreePaths(localPath: string): string[] {
19
- const normalizedLocalPath = resolveLocalPath(localPath)
20
- const result = Bun.spawnSync(
21
- ["git", "-C", normalizedLocalPath, "worktree", "list", "--porcelain"],
22
- {
23
- stdout: "pipe",
24
- stderr: "pipe",
25
- }
26
- )
27
-
28
- if (result.exitCode !== 0) {
29
- return [normalizedLocalPath]
30
- }
31
-
32
- const worktreePaths: string[] = []
33
- for (const line of new TextDecoder().decode(result.stdout).split("\n")) {
34
- const match = line.match(/^worktree\s+(.+)$/)
35
- if (!match?.[1]) continue
36
- worktreePaths.push(normalizeAbsolutePath(match[1], normalizedLocalPath))
37
- }
38
-
39
- return worktreePaths.length > 0 ? [...new Set(worktreePaths)] : [normalizedLocalPath]
40
- }
41
-
42
- export function resolveProjectRepositoryIdentity(localPath: string): ProjectRepositoryIdentity {
43
- const normalizedLocalPath = resolveLocalPath(localPath)
44
- const fallbackTitle = path.basename(normalizedLocalPath) || normalizedLocalPath
45
- const fallbackRepoKey = `path:${normalizedLocalPath}`
46
-
47
- const result = Bun.spawnSync(
48
- [
49
- "git",
50
- "-C",
51
- normalizedLocalPath,
52
- "rev-parse",
53
- "--path-format=absolute",
54
- "--show-toplevel",
55
- "--git-common-dir",
56
- ],
57
- {
58
- stdout: "pipe",
59
- stderr: "pipe",
60
- }
61
- )
62
-
63
- if (result.exitCode !== 0) {
64
- return {
65
- isGitRepo: false,
66
- localPath: normalizedLocalPath,
67
- repoKey: fallbackRepoKey,
68
- repoRootPath: null,
69
- title: fallbackTitle,
70
- worktreePath: normalizedLocalPath,
71
- }
72
- }
73
-
74
- const stdout = new TextDecoder().decode(result.stdout).trim()
75
- const [repoRootPathRaw, gitCommonDirRaw] = stdout.split("\n").map((line) => line.trim())
76
- if (!repoRootPathRaw || !gitCommonDirRaw) {
77
- return {
78
- isGitRepo: false,
79
- localPath: normalizedLocalPath,
80
- repoKey: fallbackRepoKey,
81
- repoRootPath: null,
82
- title: fallbackTitle,
83
- worktreePath: normalizedLocalPath,
84
- }
85
- }
86
-
87
- const gitCommonDir = normalizeAbsolutePath(gitCommonDirRaw, normalizedLocalPath)
88
- const repoRootPath = normalizeAbsolutePath(repoRootPathRaw, normalizedLocalPath)
89
- const repositoryDisplayPath = path.basename(gitCommonDir) === ".git"
90
- ? path.dirname(gitCommonDir)
91
- : repoRootPath
92
-
93
- return {
94
- isGitRepo: true,
95
- localPath: normalizedLocalPath,
96
- repoKey: `git:${gitCommonDir}`,
97
- repoRootPath: repositoryDisplayPath,
98
- title: path.basename(repositoryDisplayPath) || fallbackTitle,
99
- worktreePath: repoRootPath,
100
- }
101
- }
@@ -1,20 +0,0 @@
1
- import type { AccountInfo, AgentProvider, ChatUsageSnapshot, NormalizedToolCall, TranscriptEntry } from "../shared/types"
2
-
3
- export interface HarnessEvent {
4
- type: "transcript" | "session_token" | "usage"
5
- entry?: TranscriptEntry
6
- sessionToken?: string
7
- usage?: ChatUsageSnapshot
8
- }
9
-
10
- export interface HarnessToolRequest {
11
- tool: NormalizedToolCall & { toolKind: "ask_user_question" | "exit_plan_mode" }
12
- }
13
-
14
- export interface HarnessTurn {
15
- provider: AgentProvider
16
- stream: AsyncIterable<HarnessEvent>
17
- getAccountInfo?: () => Promise<AccountInfo | null>
18
- interrupt: () => Promise<void>
19
- close: () => void
20
- }
@@ -1,177 +0,0 @@
1
- import { watch, type FSWatcher } from "node:fs"
2
- import { mkdir, readFile, writeFile } from "node:fs/promises"
3
- import { homedir } from "node:os"
4
- import path from "node:path"
5
- import { getKeybindingsFilePath, LOG_PREFIX } from "../shared/branding"
6
- import { DEFAULT_KEYBINDINGS, type KeybindingAction, type KeybindingsSnapshot } from "../shared/types"
7
-
8
- const KEYBINDING_ACTIONS = Object.keys(DEFAULT_KEYBINDINGS) as KeybindingAction[]
9
-
10
- type KeybindingsFile = Partial<Record<KeybindingAction, unknown>>
11
-
12
- export class KeybindingsManager {
13
- readonly filePath: string
14
- private watcher: FSWatcher | null = null
15
- private snapshot: KeybindingsSnapshot
16
- private readonly listeners = new Set<(snapshot: KeybindingsSnapshot) => void>()
17
-
18
- constructor(filePath = getKeybindingsFilePath(homedir())) {
19
- this.filePath = filePath
20
- this.snapshot = createDefaultSnapshot(this.filePath)
21
- }
22
-
23
- async initialize() {
24
- await mkdir(path.dirname(this.filePath), { recursive: true })
25
- const file = Bun.file(this.filePath)
26
- if (!(await file.exists())) {
27
- await writeFile(this.filePath, `${JSON.stringify(DEFAULT_KEYBINDINGS, null, 2)}\n`, "utf8")
28
- }
29
- await this.reload()
30
- this.startWatching()
31
- }
32
-
33
- dispose() {
34
- this.watcher?.close()
35
- this.watcher = null
36
- this.listeners.clear()
37
- }
38
-
39
- getSnapshot() {
40
- return this.snapshot
41
- }
42
-
43
- onChange(listener: (snapshot: KeybindingsSnapshot) => void) {
44
- this.listeners.add(listener)
45
- return () => {
46
- this.listeners.delete(listener)
47
- }
48
- }
49
-
50
- async reload() {
51
- const nextSnapshot = await readKeybindingsSnapshot(this.filePath)
52
- this.setSnapshot(nextSnapshot)
53
- }
54
-
55
- async write(bindings: Partial<Record<KeybindingAction, string[]>>) {
56
- const nextSnapshot = normalizeKeybindings(bindings, this.filePath)
57
- await mkdir(path.dirname(this.filePath), { recursive: true })
58
- await writeFile(this.filePath, `${JSON.stringify(nextSnapshot.bindings, null, 2)}\n`, "utf8")
59
- this.setSnapshot(nextSnapshot)
60
- return nextSnapshot
61
- }
62
-
63
- private setSnapshot(snapshot: KeybindingsSnapshot) {
64
- this.snapshot = snapshot
65
- for (const listener of this.listeners) {
66
- listener(snapshot)
67
- }
68
- }
69
-
70
- private startWatching() {
71
- this.watcher?.close()
72
- try {
73
- this.watcher = watch(path.dirname(this.filePath), { persistent: false }, (_eventType, filename) => {
74
- if (filename && filename !== path.basename(this.filePath)) {
75
- return
76
- }
77
- void this.reload().catch((error: unknown) => {
78
- console.warn(`${LOG_PREFIX} Failed to reload keybindings:`, error)
79
- })
80
- })
81
- } catch (error) {
82
- console.warn(`${LOG_PREFIX} Failed to watch keybindings file:`, error)
83
- this.watcher = null
84
- }
85
- }
86
- }
87
-
88
- export async function readKeybindingsSnapshot(filePath: string) {
89
- try {
90
- const text = await readFile(filePath, "utf8")
91
- if (!text.trim()) {
92
- return createDefaultSnapshot(filePath, "Keybindings file was empty. Using defaults.")
93
- }
94
- const parsed = JSON.parse(text) as KeybindingsFile
95
- return normalizeKeybindings(parsed, filePath)
96
- } catch (error) {
97
- if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
98
- return createDefaultSnapshot(filePath)
99
- }
100
-
101
- if (error instanceof SyntaxError) {
102
- return createDefaultSnapshot(filePath, "Keybindings file is invalid JSON. Using defaults.")
103
- }
104
-
105
- throw error
106
- }
107
- }
108
-
109
- export function normalizeKeybindings(value: KeybindingsFile | null | undefined, filePath = getKeybindingsFilePath(homedir())): KeybindingsSnapshot {
110
- const warnings: string[] = []
111
- const source = value && typeof value === "object" && !Array.isArray(value)
112
- ? value
113
- : null
114
-
115
- if (!source) {
116
- return createDefaultSnapshot(filePath, "Keybindings file must contain a JSON object. Using defaults.")
117
- }
118
-
119
- const bindings = {} as Record<KeybindingAction, string[]>
120
- for (const action of KEYBINDING_ACTIONS) {
121
- const rawValue = source[action]
122
- if (!Array.isArray(rawValue)) {
123
- bindings[action] = [...DEFAULT_KEYBINDINGS[action]]
124
- if (rawValue !== undefined) {
125
- warnings.push(`${action} must be an array of shortcut strings`)
126
- }
127
- continue
128
- }
129
-
130
- const normalized = rawValue
131
- .filter((entry): entry is string => typeof entry === "string")
132
- .map((entry) => entry.trim())
133
- .map((entry) => entry.toLowerCase())
134
- .filter(Boolean)
135
-
136
- if (normalized.length === 0) {
137
- bindings[action] = [...DEFAULT_KEYBINDINGS[action]]
138
- if (rawValue.length > 0 || source[action] !== undefined) {
139
- warnings.push(`${action} did not contain any valid shortcut strings`)
140
- }
141
- continue
142
- }
143
-
144
- bindings[action] = normalized
145
- }
146
-
147
- return {
148
- bindings,
149
- warning: warnings.length > 0 ? `Some keybindings were reset to defaults: ${warnings.join("; ")}` : null,
150
- filePathDisplay: formatDisplayPath(filePath),
151
- }
152
- }
153
-
154
- function createDefaultSnapshot(filePath: string, warning: string | null = null): KeybindingsSnapshot {
155
- return {
156
- bindings: {
157
- submitChatMessage: [...DEFAULT_KEYBINDINGS.submitChatMessage],
158
- toggleProjectsSidebar: [...DEFAULT_KEYBINDINGS.toggleProjectsSidebar],
159
- toggleEmbeddedTerminal: [...DEFAULT_KEYBINDINGS.toggleEmbeddedTerminal],
160
- toggleRightSidebar: [...DEFAULT_KEYBINDINGS.toggleRightSidebar],
161
- openInFinder: [...DEFAULT_KEYBINDINGS.openInFinder],
162
- openInEditor: [...DEFAULT_KEYBINDINGS.openInEditor],
163
- addSplitTerminal: [...DEFAULT_KEYBINDINGS.addSplitTerminal],
164
- },
165
- warning,
166
- filePathDisplay: formatDisplayPath(filePath),
167
- }
168
- }
169
-
170
- function formatDisplayPath(filePath: string) {
171
- const homePath = homedir()
172
- if (filePath === homePath) return "~"
173
- if (filePath.startsWith(`${homePath}${path.sep}`)) {
174
- return `~${filePath.slice(homePath.length)}`
175
- }
176
- return filePath
177
- }
@@ -1,22 +0,0 @@
1
- import { hostname } from "node:os"
2
- import process from "node:process"
3
- import { spawnSync } from "node:child_process"
4
-
5
- function runAndRead(command: string, args: string[]) {
6
- const result = spawnSync(command, args, { encoding: "utf8" })
7
- if (result.status !== 0) return null
8
- const value = result.stdout.trim()
9
- return value || null
10
- }
11
-
12
- export function getMachineDisplayName() {
13
- if (process.platform === "darwin") {
14
- const computerName = runAndRead("scutil", ["--get", "ComputerName"])
15
- if (computerName) {
16
- return computerName
17
- }
18
- }
19
-
20
- const rawHostname = hostname().trim()
21
- return rawHostname.replace(/\.local$|\.lan$/i, "") || "This Machine"
22
- }
@@ -1,112 +0,0 @@
1
- import { statSync } from "node:fs"
2
- import { mkdir, readdir, stat } from "node:fs/promises"
3
- import { homedir } from "node:os"
4
- import path from "node:path"
5
- import type { DirectoryBrowserSnapshot } from "../shared/types"
6
-
7
- export function getDefaultDirectoryRoot() {
8
- const home = homedir()
9
- const candidates = [
10
- path.join(home, "Documents"),
11
- path.join(home, "projects"),
12
- path.join(home, "Projects"),
13
- home,
14
- ]
15
-
16
- for (const candidate of candidates) {
17
- try {
18
- const info = statSync(candidate)
19
- if (info.isDirectory()) {
20
- return candidate
21
- }
22
- } catch {
23
- continue
24
- }
25
- }
26
-
27
- if (process.platform === "win32") {
28
- return path.parse(home).root || "C:\\"
29
- }
30
- return "/"
31
- }
32
-
33
- export function resolveLocalPath(localPath: string) {
34
- const trimmed = localPath.trim()
35
- if (!trimmed) {
36
- throw new Error("Project path is required")
37
- }
38
- if (trimmed === "~") {
39
- return homedir()
40
- }
41
- if (trimmed.startsWith("~/")) {
42
- return path.join(homedir(), trimmed.slice(2))
43
- }
44
- return path.resolve(trimmed)
45
- }
46
-
47
- export async function ensureProjectDirectory(localPath: string) {
48
- const resolvedPath = resolveLocalPath(localPath)
49
-
50
- await mkdir(resolvedPath, { recursive: true })
51
- const info = await stat(resolvedPath)
52
- if (!info.isDirectory()) {
53
- throw new Error("Project path must be a directory")
54
- }
55
- }
56
-
57
- export async function requireProjectDirectory(localPath: string) {
58
- const resolvedPath = resolveLocalPath(localPath)
59
- const info = await stat(resolvedPath).catch(() => null)
60
- if (!info) {
61
- throw new Error(`Project folder not found: ${resolvedPath}`)
62
- }
63
- if (!info.isDirectory()) {
64
- throw new Error("Project path must be a directory")
65
- }
66
- }
67
-
68
- function getDirectoryRoots() {
69
- const home = homedir()
70
- if (process.platform === "win32") {
71
- const drive = path.parse(home).root || "C:\\"
72
- return [
73
- { name: "Home", localPath: home },
74
- { name: drive, localPath: drive },
75
- ]
76
- }
77
-
78
- return [
79
- { name: "Home", localPath: home },
80
- { name: "/", localPath: "/" },
81
- ]
82
- }
83
-
84
- export async function listProjectDirectories(localPath?: string): Promise<DirectoryBrowserSnapshot> {
85
- const currentPath = localPath ? resolveLocalPath(localPath) : getDefaultDirectoryRoot()
86
- const info = await stat(currentPath).catch(() => null)
87
-
88
- if (!info) {
89
- throw new Error(`Folder not found: ${currentPath}`)
90
- }
91
- if (!info.isDirectory()) {
92
- throw new Error("Project path must be a directory")
93
- }
94
-
95
- const directoryEntries = await readdir(currentPath, { withFileTypes: true })
96
- const entries = directoryEntries
97
- .filter((entry) => entry.isDirectory())
98
- .map((entry) => ({
99
- name: entry.name,
100
- localPath: path.join(currentPath, entry.name),
101
- }))
102
- .sort((a, b) => a.name.localeCompare(b.name))
103
-
104
- const parentPath = path.dirname(currentPath) === currentPath ? null : path.dirname(currentPath)
105
-
106
- return {
107
- currentPath,
108
- parentPath,
109
- roots: getDirectoryRoots(),
110
- entries,
111
- }
112
- }
@@ -1,22 +0,0 @@
1
- import { spawn, spawnSync } from "node:child_process"
2
-
3
- export function spawnDetached(command: string, args: string[]) {
4
- spawn(command, args, { stdio: "ignore", detached: true }).unref()
5
- }
6
-
7
- export function hasCommand(command: string) {
8
- const result = spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" })
9
- return result.status === 0
10
- }
11
-
12
- export function resolveCommandPath(command: string) {
13
- const result = spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: ["ignore", "pipe", "ignore"] })
14
- if (result.status !== 0) return null
15
- const output = result.stdout?.toString().trim()
16
- return output || null
17
- }
18
-
19
- export function canOpenMacApp(appName: string) {
20
- const result = spawnSync("open", ["-Ra", appName], { stdio: "ignore" })
21
- return result.status === 0
22
- }