kaizenai 0.1.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/LICENSE +22 -0
  2. package/README.md +246 -0
  3. package/bin/kaizen +15 -0
  4. package/dist/client/apple-touch-icon.png +0 -0
  5. package/dist/client/assets/index-D-ORCGrq.js +603 -0
  6. package/dist/client/assets/index-r28mcHqz.css +32 -0
  7. package/dist/client/favicon.png +0 -0
  8. package/dist/client/fonts/body-medium.woff2 +0 -0
  9. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  10. package/dist/client/fonts/body-regular.woff2 +0 -0
  11. package/dist/client/fonts/body-semibold.woff2 +0 -0
  12. package/dist/client/index.html +22 -0
  13. package/dist/client/manifest-dark.webmanifest +24 -0
  14. package/dist/client/manifest.webmanifest +24 -0
  15. package/dist/client/pwa-192.png +0 -0
  16. package/dist/client/pwa-512.png +0 -0
  17. package/dist/client/pwa-icon.svg +15 -0
  18. package/dist/client/pwa-splash.png +0 -0
  19. package/dist/client/pwa-splash.svg +15 -0
  20. package/package.json +103 -0
  21. package/src/server/acp-shared.ts +315 -0
  22. package/src/server/agent.ts +1120 -0
  23. package/src/server/attachments.ts +133 -0
  24. package/src/server/backgrounds.ts +74 -0
  25. package/src/server/cli-runtime.ts +333 -0
  26. package/src/server/cli-supervisor.ts +81 -0
  27. package/src/server/cli.ts +68 -0
  28. package/src/server/codex-app-server-protocol.ts +453 -0
  29. package/src/server/codex-app-server.ts +1350 -0
  30. package/src/server/cursor-acp.ts +819 -0
  31. package/src/server/discovery.ts +322 -0
  32. package/src/server/event-store.ts +1369 -0
  33. package/src/server/events.ts +244 -0
  34. package/src/server/external-open.ts +272 -0
  35. package/src/server/gemini-acp.ts +844 -0
  36. package/src/server/gemini-cli.ts +525 -0
  37. package/src/server/generate-title.ts +36 -0
  38. package/src/server/git-manager.ts +79 -0
  39. package/src/server/git-repository.ts +101 -0
  40. package/src/server/harness-types.ts +20 -0
  41. package/src/server/keybindings.ts +177 -0
  42. package/src/server/machine-name.ts +22 -0
  43. package/src/server/paths.ts +112 -0
  44. package/src/server/process-utils.ts +22 -0
  45. package/src/server/project-icon.ts +344 -0
  46. package/src/server/project-metadata.ts +10 -0
  47. package/src/server/provider-catalog.ts +85 -0
  48. package/src/server/provider-settings.ts +155 -0
  49. package/src/server/quick-response.ts +153 -0
  50. package/src/server/read-models.ts +275 -0
  51. package/src/server/recovery.ts +507 -0
  52. package/src/server/restart.ts +30 -0
  53. package/src/server/server.ts +244 -0
  54. package/src/server/terminal-manager.ts +350 -0
  55. package/src/server/theme-settings.ts +179 -0
  56. package/src/server/update-manager.ts +230 -0
  57. package/src/server/usage/base-provider-usage.ts +57 -0
  58. package/src/server/usage/claude-usage.ts +558 -0
  59. package/src/server/usage/codex-usage.ts +144 -0
  60. package/src/server/usage/cursor-browser.ts +120 -0
  61. package/src/server/usage/cursor-cookies.ts +390 -0
  62. package/src/server/usage/cursor-usage.ts +490 -0
  63. package/src/server/usage/gemini-usage.ts +24 -0
  64. package/src/server/usage/provider-usage.ts +61 -0
  65. package/src/server/usage/test-helpers.ts +9 -0
  66. package/src/server/usage/types.ts +54 -0
  67. package/src/server/usage/utils.ts +325 -0
  68. package/src/server/ws-router.ts +717 -0
  69. package/src/shared/branding.ts +83 -0
  70. package/src/shared/dev-ports.ts +43 -0
  71. package/src/shared/ports.ts +2 -0
  72. package/src/shared/protocol.ts +152 -0
  73. package/src/shared/tools.ts +251 -0
  74. package/src/shared/types.ts +1028 -0
@@ -0,0 +1,244 @@
1
+ import type { AgentProvider, FeatureBrowserState, FeatureStage, FeatureSummary, ProjectSummary, TranscriptEntry } from "../shared/types"
2
+
3
+ export interface ProjectRecord extends ProjectSummary {
4
+ deletedAt?: number
5
+ }
6
+
7
+ export interface FeatureRecord extends FeatureSummary {
8
+ deletedAt?: number
9
+ }
10
+
11
+ export interface ChatRecord {
12
+ id: string
13
+ projectId: string
14
+ title: string
15
+ createdAt: number
16
+ updatedAt: number
17
+ deletedAt?: number
18
+ featureId?: string | null
19
+ provider: AgentProvider | null
20
+ planMode: boolean
21
+ sessionToken: string | null
22
+ lastMessageAt?: number
23
+ lastTurnOutcome: "success" | "failed" | "cancelled" | null
24
+ }
25
+
26
+ export interface StoreState {
27
+ projectsById: Map<string, ProjectRecord>
28
+ projectIdsByRepoKey: Map<string, string>
29
+ projectIdsByPath: Map<string, string>
30
+ featuresById: Map<string, FeatureRecord>
31
+ chatsById: Map<string, ChatRecord>
32
+ hiddenProjectKeys: Set<string>
33
+ }
34
+
35
+ export interface SnapshotFile {
36
+ v: 3
37
+ generatedAt: number
38
+ projects: ProjectRecord[]
39
+ features: FeatureRecord[]
40
+ chats: ChatRecord[]
41
+ messages?: Array<{ chatId: string; entries: TranscriptEntry[] }>
42
+ hiddenProjectKeys?: string[]
43
+ }
44
+
45
+ export type ProjectEvent = {
46
+ v: 3
47
+ type: "project_opened"
48
+ timestamp: number
49
+ projectId: string
50
+ repoKey: string
51
+ localPath: string
52
+ worktreePaths: string[]
53
+ title: string
54
+ browserState?: FeatureBrowserState
55
+ generalChatsBrowserState?: FeatureBrowserState
56
+ } | {
57
+ v: 3
58
+ type: "project_worktree_added"
59
+ timestamp: number
60
+ projectId: string
61
+ localPath: string
62
+ } | {
63
+ v: 3
64
+ type: "project_browser_state_set"
65
+ timestamp: number
66
+ projectId: string
67
+ browserState: FeatureBrowserState
68
+ } | {
69
+ v: 3
70
+ type: "project_general_chats_browser_state_set"
71
+ timestamp: number
72
+ projectId: string
73
+ browserState: FeatureBrowserState
74
+ } | {
75
+ v: 3
76
+ type: "project_removed"
77
+ timestamp: number
78
+ projectId: string
79
+ } | {
80
+ v: 3
81
+ type: "project_hidden"
82
+ timestamp: number
83
+ repoKey: string
84
+ } | {
85
+ v: 3
86
+ type: "project_unhidden"
87
+ timestamp: number
88
+ repoKey: string
89
+ }
90
+
91
+ export type FeatureEvent =
92
+ | {
93
+ v: 3
94
+ type: "feature_created"
95
+ timestamp: number
96
+ featureId: string
97
+ projectId: string
98
+ title: string
99
+ description: string
100
+ browserState?: FeatureBrowserState
101
+ stage: FeatureStage
102
+ sortOrder: number
103
+ directoryRelativePath: string
104
+ overviewRelativePath: string
105
+ }
106
+ | {
107
+ v: 3
108
+ type: "feature_renamed"
109
+ timestamp: number
110
+ featureId: string
111
+ title: string
112
+ }
113
+ | {
114
+ v: 3
115
+ type: "feature_browser_state_set"
116
+ timestamp: number
117
+ featureId: string
118
+ browserState: FeatureBrowserState
119
+ }
120
+ | {
121
+ v: 3
122
+ type: "feature_stage_set"
123
+ timestamp: number
124
+ featureId: string
125
+ stage: FeatureStage
126
+ sortOrder?: number
127
+ }
128
+ | {
129
+ v: 3
130
+ type: "feature_reordered"
131
+ timestamp: number
132
+ projectId: string
133
+ orderedFeatureIds: string[]
134
+ }
135
+ | {
136
+ v: 3
137
+ type: "feature_deleted"
138
+ timestamp: number
139
+ featureId: string
140
+ }
141
+
142
+ export type ChatEvent =
143
+ | {
144
+ v: 3
145
+ type: "chat_created"
146
+ timestamp: number
147
+ chatId: string
148
+ projectId: string
149
+ title: string
150
+ featureId?: string
151
+ }
152
+ | {
153
+ v: 3
154
+ type: "chat_renamed"
155
+ timestamp: number
156
+ chatId: string
157
+ title: string
158
+ }
159
+ | {
160
+ v: 3
161
+ type: "chat_deleted"
162
+ timestamp: number
163
+ chatId: string
164
+ }
165
+ | {
166
+ v: 3
167
+ type: "chat_provider_set"
168
+ timestamp: number
169
+ chatId: string
170
+ provider: AgentProvider
171
+ }
172
+ | {
173
+ v: 3
174
+ type: "chat_plan_mode_set"
175
+ timestamp: number
176
+ chatId: string
177
+ planMode: boolean
178
+ }
179
+ | {
180
+ v: 3
181
+ type: "chat_feature_set"
182
+ timestamp: number
183
+ chatId: string
184
+ featureId: string | null
185
+ }
186
+
187
+ export type MessageEvent = {
188
+ v: 3
189
+ type: "message_appended"
190
+ timestamp: number
191
+ chatId: string
192
+ entry: TranscriptEntry
193
+ }
194
+
195
+ export type TurnEvent =
196
+ | {
197
+ v: 3
198
+ type: "turn_started"
199
+ timestamp: number
200
+ chatId: string
201
+ }
202
+ | {
203
+ v: 3
204
+ type: "turn_finished"
205
+ timestamp: number
206
+ chatId: string
207
+ }
208
+ | {
209
+ v: 3
210
+ type: "turn_failed"
211
+ timestamp: number
212
+ chatId: string
213
+ error: string
214
+ }
215
+ | {
216
+ v: 3
217
+ type: "turn_cancelled"
218
+ timestamp: number
219
+ chatId: string
220
+ }
221
+ | {
222
+ v: 3
223
+ type: "session_token_set"
224
+ timestamp: number
225
+ chatId: string
226
+ sessionToken: string | null
227
+ }
228
+
229
+ export type StoreEvent = ProjectEvent | FeatureEvent | ChatEvent | MessageEvent | TurnEvent
230
+
231
+ export function createEmptyState(): StoreState {
232
+ return {
233
+ projectsById: new Map(),
234
+ projectIdsByRepoKey: new Map(),
235
+ projectIdsByPath: new Map(),
236
+ featuresById: new Map(),
237
+ chatsById: new Map(),
238
+ hiddenProjectKeys: new Set(),
239
+ }
240
+ }
241
+
242
+ export function cloneTranscriptEntries(entries: TranscriptEntry[]): TranscriptEntry[] {
243
+ return entries.map((entry) => ({ ...entry }))
244
+ }
@@ -0,0 +1,272 @@
1
+ import { stat } from "node:fs/promises"
2
+ import path from "node:path"
3
+ import process from "node:process"
4
+ import type { ClientCommand, EditorOpenSettings, EditorPreset } from "../shared/protocol"
5
+ import { resolveLocalPath } from "./paths"
6
+ import { canOpenMacApp, hasCommand, spawnDetached } from "./process-utils"
7
+
8
+ type OpenExternalCommand = Extract<ClientCommand, { type: "system.openExternal" }>
9
+ type OpenUrlCommand = Extract<ClientCommand, { type: "system.openUrl" }>
10
+
11
+ interface CommandSpec {
12
+ command: string
13
+ args: string[]
14
+ }
15
+
16
+ const DEFAULT_EDITOR_SETTINGS: EditorOpenSettings = {
17
+ preset: "cursor",
18
+ commandTemplate: "cursor {path}",
19
+ }
20
+
21
+ export async function openExternal(command: OpenExternalCommand) {
22
+ const resolvedPath = resolveLocalPath(command.localPath)
23
+ const platform = process.platform
24
+ const info = command.action === "open_editor" || command.action === "open_finder"
25
+ ? await stat(resolvedPath).catch(() => null)
26
+ : null
27
+
28
+ if (command.action === "open_editor") {
29
+ if (!info) {
30
+ throw new Error(`Path not found: ${resolvedPath}`)
31
+ }
32
+ const editorCommand = buildEditorCommand({
33
+ localPath: resolvedPath,
34
+ isDirectory: info.isDirectory(),
35
+ line: command.line,
36
+ column: command.column,
37
+ editor: command.editor ?? DEFAULT_EDITOR_SETTINGS,
38
+ platform,
39
+ })
40
+ spawnDetached(editorCommand.command, editorCommand.args)
41
+ return
42
+ }
43
+
44
+ if (platform === "darwin") {
45
+ if (command.action === "open_finder") {
46
+ if (info?.isDirectory()) {
47
+ spawnDetached("open", [resolvedPath])
48
+ } else {
49
+ spawnDetached("open", ["-R", resolvedPath])
50
+ }
51
+ return
52
+ }
53
+ if (command.action === "open_terminal") {
54
+ spawnDetached("open", ["-a", "Terminal", resolvedPath])
55
+ return
56
+ }
57
+ }
58
+
59
+ if (platform === "win32") {
60
+ if (command.action === "open_finder") {
61
+ if (info?.isDirectory()) {
62
+ spawnDetached("explorer", [resolvedPath])
63
+ } else {
64
+ spawnDetached("explorer", ["/select,", resolvedPath])
65
+ }
66
+ return
67
+ }
68
+ if (command.action === "open_terminal") {
69
+ if (hasCommand("wt")) {
70
+ spawnDetached("wt", ["-d", resolvedPath])
71
+ return
72
+ }
73
+ spawnDetached("cmd", ["/c", "start", "", "cmd", "/K", `cd /d ${resolvedPath}`])
74
+ return
75
+ }
76
+ }
77
+
78
+ if (command.action === "open_finder") {
79
+ spawnDetached("xdg-open", [info?.isDirectory() ? resolvedPath : path.dirname(resolvedPath)])
80
+ return
81
+ }
82
+ if (command.action === "open_terminal") {
83
+ for (const terminalCommand of ["x-terminal-emulator", "gnome-terminal", "konsole"]) {
84
+ if (!hasCommand(terminalCommand)) continue
85
+ if (terminalCommand === "gnome-terminal") {
86
+ spawnDetached(terminalCommand, ["--working-directory", resolvedPath])
87
+ } else if (terminalCommand === "konsole") {
88
+ spawnDetached(terminalCommand, ["--workdir", resolvedPath])
89
+ } else {
90
+ spawnDetached(terminalCommand, ["--working-directory", resolvedPath])
91
+ }
92
+ return
93
+ }
94
+ spawnDetached("xdg-open", [resolvedPath])
95
+ }
96
+ }
97
+
98
+ export async function openUrl(command: OpenUrlCommand) {
99
+ const platform = process.platform
100
+
101
+ if (platform === "darwin") {
102
+ spawnDetached("open", [command.url])
103
+ return
104
+ }
105
+
106
+ if (platform === "win32") {
107
+ spawnDetached("cmd", ["/c", "start", "", command.url])
108
+ return
109
+ }
110
+
111
+ spawnDetached("xdg-open", [command.url])
112
+ }
113
+
114
+ export function buildEditorCommand(args: {
115
+ localPath: string
116
+ isDirectory: boolean
117
+ line?: number
118
+ column?: number
119
+ editor: EditorOpenSettings
120
+ platform: NodeJS.Platform
121
+ }): CommandSpec {
122
+ const editor = normalizeEditorSettings(args.editor)
123
+ if (editor.preset === "custom") {
124
+ return buildCustomEditorCommand({
125
+ commandTemplate: editor.commandTemplate,
126
+ localPath: args.localPath,
127
+ line: args.line,
128
+ column: args.column,
129
+ })
130
+ }
131
+ return buildPresetEditorCommand(args, editor.preset)
132
+ }
133
+
134
+ function buildPresetEditorCommand(
135
+ args: {
136
+ localPath: string
137
+ isDirectory: boolean
138
+ line?: number
139
+ column?: number
140
+ platform: NodeJS.Platform
141
+ },
142
+ preset: Exclude<EditorPreset, "custom">
143
+ ): CommandSpec {
144
+ const gotoTarget = `${args.localPath}:${args.line ?? 1}:${args.column ?? 1}`
145
+ const opener = resolveEditorExecutable(preset, args.platform)
146
+ if (args.isDirectory || !args.line) {
147
+ return { command: opener.command, args: [...opener.args, args.localPath] }
148
+ }
149
+ return { command: opener.command, args: [...opener.args, "--goto", gotoTarget] }
150
+ }
151
+
152
+ function resolveEditorExecutable(preset: Exclude<EditorPreset, "custom">, platform: NodeJS.Platform) {
153
+ if (preset === "cursor") {
154
+ if (hasCommand("cursor")) return { command: "cursor", args: [] }
155
+ if (platform === "darwin" && canOpenMacApp("Cursor")) return { command: "open", args: ["-a", "Cursor"] }
156
+ }
157
+ if (preset === "vscode") {
158
+ if (hasCommand("code")) return { command: "code", args: [] }
159
+ if (platform === "darwin" && canOpenMacApp("Visual Studio Code")) return { command: "open", args: ["-a", "Visual Studio Code"] }
160
+ }
161
+ if (preset === "windsurf") {
162
+ if (hasCommand("windsurf")) return { command: "windsurf", args: [] }
163
+ if (platform === "darwin" && canOpenMacApp("Windsurf")) return { command: "open", args: ["-a", "Windsurf"] }
164
+ }
165
+
166
+ if (platform === "darwin") {
167
+ switch (preset) {
168
+ case "cursor":
169
+ return { command: "open", args: ["-a", "Cursor"] }
170
+ case "vscode":
171
+ return { command: "open", args: ["-a", "Visual Studio Code"] }
172
+ case "windsurf":
173
+ return { command: "open", args: ["-a", "Windsurf"] }
174
+ }
175
+ }
176
+
177
+ return { command: preset === "vscode" ? "code" : preset, args: [] }
178
+ }
179
+
180
+ function buildCustomEditorCommand(args: {
181
+ commandTemplate: string
182
+ localPath: string
183
+ line?: number
184
+ column?: number
185
+ }): CommandSpec {
186
+ const template = args.commandTemplate.trim()
187
+ if (!template.includes("{path}")) {
188
+ throw new Error("Custom editor command must include {path}")
189
+ }
190
+
191
+ const line = String(args.line ?? 1)
192
+ const column = String(args.column ?? 1)
193
+ const replaced = template
194
+ .replaceAll("{path}", args.localPath)
195
+ .replaceAll("{line}", line)
196
+ .replaceAll("{column}", column)
197
+
198
+ const tokens = tokenizeCommandTemplate(replaced)
199
+ const [command, ...commandArgs] = tokens
200
+ if (!command) {
201
+ throw new Error("Custom editor command is empty")
202
+ }
203
+ return { command, args: commandArgs }
204
+ }
205
+
206
+ export function tokenizeCommandTemplate(template: string) {
207
+ const tokens: string[] = []
208
+ let current = ""
209
+ let quote: "'" | "\"" | null = null
210
+
211
+ for (let index = 0; index < template.length; index += 1) {
212
+ const char = template[index]
213
+
214
+ if (char === "\\" && index + 1 < template.length) {
215
+ current += template[index + 1]
216
+ index += 1
217
+ continue
218
+ }
219
+
220
+ if (quote) {
221
+ if (char === quote) {
222
+ quote = null
223
+ } else {
224
+ current += char
225
+ }
226
+ continue
227
+ }
228
+
229
+ if (char === "'" || char === "\"") {
230
+ quote = char
231
+ continue
232
+ }
233
+
234
+ if (/\s/.test(char)) {
235
+ if (current.length > 0) {
236
+ tokens.push(current)
237
+ current = ""
238
+ }
239
+ continue
240
+ }
241
+
242
+ current += char
243
+ }
244
+
245
+ if (quote) {
246
+ throw new Error("Custom editor command has an unclosed quote")
247
+ }
248
+ if (current.length > 0) {
249
+ tokens.push(current)
250
+ }
251
+ return tokens
252
+ }
253
+
254
+ function normalizeEditorSettings(editor: EditorOpenSettings): EditorOpenSettings {
255
+ const preset = normalizeEditorPreset(editor.preset)
256
+ return {
257
+ preset,
258
+ commandTemplate: editor.commandTemplate.trim() || DEFAULT_EDITOR_SETTINGS.commandTemplate,
259
+ }
260
+ }
261
+
262
+ function normalizeEditorPreset(preset: EditorPreset): EditorPreset {
263
+ switch (preset) {
264
+ case "vscode":
265
+ case "windsurf":
266
+ case "custom":
267
+ case "cursor":
268
+ return preset
269
+ default:
270
+ return DEFAULT_EDITOR_SETTINGS.preset
271
+ }
272
+ }