@ebay-dt/bonfire-node 0.1.0 → 0.2.0-alpha.3

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 (34) hide show
  1. package/README.md +10 -4
  2. package/dist/agent-service.d.ts +4 -1
  3. package/dist/agent-service.d.ts.map +1 -1
  4. package/dist/agent-service.js +18 -2
  5. package/dist/agent-service.js.map +1 -1
  6. package/dist/claude.d.ts +12 -11
  7. package/dist/claude.d.ts.map +1 -1
  8. package/dist/claude.js +166 -11
  9. package/dist/claude.js.map +1 -1
  10. package/dist/git-repository.d.ts +1 -1
  11. package/dist/git-repository.d.ts.map +1 -1
  12. package/dist/git-repository.js +5 -2
  13. package/dist/git-repository.js.map +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/lifecycle/local-prototype-lifecycle.d.ts +9 -1
  19. package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
  20. package/dist/lifecycle/local-prototype-lifecycle.js +104 -25
  21. package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
  22. package/dist/lifecycle/repository-summary.d.ts +17 -0
  23. package/dist/lifecycle/repository-summary.d.ts.map +1 -0
  24. package/dist/lifecycle/repository-summary.js +182 -0
  25. package/dist/lifecycle/repository-summary.js.map +1 -0
  26. package/dist/lifecycle/repository-synchronization.d.ts +35 -0
  27. package/dist/lifecycle/repository-synchronization.d.ts.map +1 -0
  28. package/dist/lifecycle/repository-synchronization.js +270 -0
  29. package/dist/lifecycle/repository-synchronization.js.map +1 -0
  30. package/dist/lifecycle/runtime-context.d.ts +3 -1
  31. package/dist/lifecycle/runtime-context.d.ts.map +1 -1
  32. package/dist/lifecycle/runtime-context.js +3 -0
  33. package/dist/lifecycle/runtime-context.js.map +1 -1
  34. package/package.json +2 -2
package/README.md CHANGED
@@ -108,9 +108,11 @@ completed destination. The default callback is a no-op.
108
108
 
109
109
  `LocalPrototypeLifecycle.getDirtyState` validates the authenticated owner and
110
110
  prototype reference, then returns a `clean` or `dirty` observation based only on
111
- Git status within that prototype path. `LocalPrototypeLifecycle.save` creates a
112
- local, path-scoped checkpoint without pushing; `.discard` restores tracked
113
- files and removes untracked files within that path. `LocalPrototypeLifecycle.create`,
111
+ Git status within that prototype path. In managed mode,
112
+ `LocalPrototypeLifecycle.save` creates a path-scoped checkpoint and pushes the
113
+ identity branch; a push failure preserves the local commit. `.discard` restores
114
+ tracked files and removes untracked files within that path.
115
+ `LocalPrototypeLifecycle.create`,
114
116
  `.duplicate`, `.edit`, and `.delete` return typed results and serialize repository
115
117
  mutations through one process-local operation gate. Edit updates only title,
116
118
  description, and `updatedAt` while preserving the existing ref/URL. Delete
@@ -153,7 +155,11 @@ different `PATH` from an interactive shell, so restart Bonfire after changing
153
155
  shell profiles.
154
156
 
155
157
  Claude Code supports the explicit terminal values `terminal` (Terminal.app),
156
- `iterm` (iTerm2), and `warp` (Warp). Unsupported platforms, missing commands,
158
+ `iterm` (iTerm2), `warp` (Warp), `ghostty` (Ghostty), and `vscode` (Visual
159
+ Studio Code). Ghostty is opened through macOS AppleScript and requires a
160
+ Ghostty version with AppleScript support. The VS Code option uses the `code`
161
+ CLI when it is on `PATH`; on macOS it also recognizes the CLI bundled inside a standard VS Code or VS Code Insiders app
162
+ installation. It opens the prototype as a new window. Unsupported platforms, missing commands,
157
163
  missing preferences, ownership failures, and launch failures are returned as
158
164
  safe typed results; raw process output and absolute paths are never public
159
165
  results. No skill, clipboard fallback, or copy/paste setup flow is involved.
@@ -1,4 +1,4 @@
1
- import { type AgentActionError, type AgentActionResult } from "@ebay-dt/bonfire-core";
1
+ import { type AgentActionError, type AgentActionResult, type PrototypePathValue } from "@ebay-dt/bonfire-core";
2
2
  import { type ClaudeCodeLauncherLike } from "./claude.js";
3
3
  import { type CursorLauncherLike } from "./cursor.js";
4
4
  import { type ExecutableResolverLike } from "./executable.js";
@@ -16,6 +16,7 @@ export type PrototypeAgentServiceOptions = {
16
16
  export interface PrototypeAgentServiceLike {
17
17
  openInCursor(identity: unknown, input: unknown): Promise<AgentActionResult>;
18
18
  openInClaudeCode(identity: unknown, input: unknown): Promise<AgentActionResult>;
19
+ copyPrototypePath?(identity: unknown, input: unknown): Promise<AgentActionResult<PrototypePathValue>>;
19
20
  }
20
21
  export declare class PrototypeAgentService implements PrototypeAgentServiceLike {
21
22
  #private;
@@ -27,6 +28,7 @@ export declare class PrototypeAgentService implements PrototypeAgentServiceLike
27
28
  ok: false;
28
29
  error: AgentActionError;
29
30
  }>;
31
+ copyPrototypePath(identity: unknown, input: unknown): Promise<AgentActionResult<PrototypePathValue>>;
30
32
  openInClaudeCode(identity: unknown, input: unknown): Promise<{
31
33
  ok: true;
32
34
  value: import("@ebay-dt/bonfire-core").AgentLaunchValue;
@@ -35,6 +37,7 @@ export declare class PrototypeAgentService implements PrototypeAgentServiceLike
35
37
  error: {
36
38
  agent?: import("@ebay-dt/bonfire-core").Agent;
37
39
  terminal?: import("@ebay-dt/bonfire-core").TerminalApp;
40
+ availableTerminals?: readonly import("@ebay-dt/bonfire-core").TerminalApp[];
38
41
  code: import("@ebay-dt/bonfire-core").AgentActionErrorCode;
39
42
  };
40
43
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-service.d.ts","sourceRoot":"","sources":["../src/agent-service.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAGvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAEvF,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,IAAI,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAC;IACjE,OAAO,EAAE,oBAAoB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACrC,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC5E,gBAAgB,CACd,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/B;AAiCD,qBAAa,qBAAsB,YAAW,yBAAyB;;IAOrE,YAAY,OAAO,EAAE,4BAA4B,EAUhD;IA4CK,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO;;;;;;OASnD;IAEK,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO;;;;;;;;;;OA2CvD;CACF","sourcesContent":["import { join } from \"node:path\";\nimport {\n definePrototypeRef,\n parseBonfireIdentity,\n parseOpenInAgentInput,\n parseOpenInClaudeCodeInput,\n type AgentActionError,\n type AgentActionResult,\n type BonfireIdentity,\n type OpenInAgentInput,\n} from \"@ebay-dt/bonfire-core\";\nimport { ClaudeCodeLauncher, type ClaudeCodeLauncherLike } from \"./claude.js\";\nimport { CursorLauncher, type CursorLauncherLike } from \"./cursor.js\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\nimport {\n PrototypePreferences,\n type PrototypePreferencesLike,\n} from \"./preferences.js\";\nimport type { PrototypeCatalogLike } from \"./prototype-catalog.js\";\nimport type { PrototypeLifecycleRuntimeContext } from \"./lifecycle/runtime-context.js\";\n\nexport type PrototypeAgentServiceOptions = {\n runtime: Pick<PrototypeLifecycleRuntimeContext, \"prototypeRoot\">;\n catalog: PrototypeCatalogLike;\n executableResolver?: ExecutableResolverLike;\n preferences?: PrototypePreferencesLike;\n cursor?: CursorLauncherLike;\n claudeCode?: ClaudeCodeLauncherLike;\n};\n\nexport interface PrototypeAgentServiceLike {\n openInCursor(identity: unknown, input: unknown): Promise<AgentActionResult>;\n openInClaudeCode(\n identity: unknown,\n input: unknown,\n ): Promise<AgentActionResult>;\n}\n\ntype AuthorizedInput<Value> =\n | {\n ok: true;\n identity: BonfireIdentity;\n input: Value;\n prototypePath: string;\n }\n | { ok: false; result: AgentActionResult };\n\nfunction failure(\n code: AgentActionError[\"code\"],\n extras: Pick<AgentActionError, \"agent\" | \"terminal\"> = {},\n) {\n return {\n ok: false,\n error: { code, ...extras },\n } satisfies AgentActionResult;\n}\n\nfunction authenticate(identity: unknown) {\n if (identity === null || identity === undefined) return null;\n\n try {\n const parsed = parseBonfireIdentity(identity);\n definePrototypeRef({ login: parsed.login, slug: \"prototype\" });\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport class PrototypeAgentService implements PrototypeAgentServiceLike {\n readonly #runtime: PrototypeAgentServiceOptions[\"runtime\"];\n readonly #catalog: PrototypeCatalogLike;\n readonly #preferences: PrototypePreferencesLike;\n readonly #cursor: CursorLauncherLike;\n readonly #claudeCode: ClaudeCodeLauncherLike;\n\n constructor(options: PrototypeAgentServiceOptions) {\n const executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#runtime = options.runtime;\n this.#catalog = options.catalog;\n this.#preferences =\n options.preferences ?? new PrototypePreferences(options.runtime);\n this.#cursor = options.cursor ?? new CursorLauncher({ executableResolver });\n this.#claudeCode =\n options.claudeCode ?? new ClaudeCodeLauncher({ executableResolver });\n }\n\n async #authorize<Value extends { ref: OpenInAgentInput[\"ref\"] }>(\n identity: unknown,\n input: unknown,\n parse: (value: unknown) => Value,\n ): Promise<AuthorizedInput<Value>> {\n const authenticated = authenticate(identity);\n if (authenticated === null) {\n return { ok: false, result: failure(\"unauthenticated\") };\n }\n\n let normalizedInput: Value;\n try {\n normalizedInput = parse(input);\n } catch {\n return { ok: false, result: failure(\"invalid-input\") };\n }\n\n let summary;\n try {\n summary = await this.#catalog.getSummary(normalizedInput.ref);\n } catch {\n summary = null;\n }\n if (summary === null) {\n return { ok: false, result: failure(\"not-found\") };\n }\n if (summary.authorLogin !== authenticated.login) {\n return { ok: false, result: failure(\"forbidden\") };\n }\n\n return {\n ok: true,\n identity: authenticated,\n input: normalizedInput,\n prototypePath: join(\n this.#runtime.prototypeRoot,\n normalizedInput.ref.login,\n normalizedInput.ref.slug,\n ),\n };\n }\n\n async openInCursor(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInAgentInput,\n );\n if (!authorized.ok) return authorized.result;\n\n return this.#cursor.launch(authorized.prototypePath);\n }\n\n async openInClaudeCode(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInClaudeCodeInput,\n );\n if (!authorized.ok) return authorized.result;\n\n const preflight = await this.#claudeCode.preflight(\n authorized.input.terminal,\n );\n if (!preflight.ok) return preflight;\n\n let terminal = authorized.input.terminal;\n if (terminal === undefined) {\n let preferences;\n try {\n preferences = await this.#preferences.read(authorized.identity.login);\n } catch {\n return failure(\"launch-failed\", { agent: \"claude-code\" });\n }\n terminal = preferences.preferredTerminal;\n if (terminal === undefined) {\n return failure(\"needs-terminal\", { agent: \"claude-code\" });\n }\n } else {\n try {\n await this.#preferences.update(authorized.identity.login, {\n preferredTerminal: terminal,\n });\n } catch {\n return failure(\"launch-failed\", {\n agent: \"claude-code\",\n terminal,\n });\n }\n }\n\n return this.#claudeCode.launch(\n terminal,\n authorized.prototypePath,\n preflight,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"agent-service.d.ts","sourceRoot":"","sources":["../src/agent-service.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAGtB,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAEvF,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,IAAI,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAC;IACjE,OAAO,EAAE,oBAAoB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACrC,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC5E,gBAAgB,CACd,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,iBAAiB,CAAC,CAChB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,CAAC;CACnD;AAoCD,qBAAa,qBAAsB,YAAW,yBAAyB;;IAOrE,YAAY,OAAO,EAAE,4BAA4B,EAUhD;IA4CK,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO;;;;;;OASnD;IAEK,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,kDAcxD;IAEK,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO;;;;;;;;;;;OAmDvD;CACF","sourcesContent":["import { join } from \"node:path\";\nimport {\n definePrototypeRef,\n parseBonfireIdentity,\n parseOpenInAgentInput,\n parseOpenInClaudeCodeInput,\n type AgentActionError,\n type AgentActionResult,\n type BonfireIdentity,\n type OpenInAgentInput,\n type PrototypePathValue,\n} from \"@ebay-dt/bonfire-core\";\nimport { ClaudeCodeLauncher, type ClaudeCodeLauncherLike } from \"./claude.js\";\nimport { CursorLauncher, type CursorLauncherLike } from \"./cursor.js\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\nimport {\n PrototypePreferences,\n type PrototypePreferencesLike,\n} from \"./preferences.js\";\nimport type { PrototypeCatalogLike } from \"./prototype-catalog.js\";\nimport type { PrototypeLifecycleRuntimeContext } from \"./lifecycle/runtime-context.js\";\n\nexport type PrototypeAgentServiceOptions = {\n runtime: Pick<PrototypeLifecycleRuntimeContext, \"prototypeRoot\">;\n catalog: PrototypeCatalogLike;\n executableResolver?: ExecutableResolverLike;\n preferences?: PrototypePreferencesLike;\n cursor?: CursorLauncherLike;\n claudeCode?: ClaudeCodeLauncherLike;\n};\n\nexport interface PrototypeAgentServiceLike {\n openInCursor(identity: unknown, input: unknown): Promise<AgentActionResult>;\n openInClaudeCode(\n identity: unknown,\n input: unknown,\n ): Promise<AgentActionResult>;\n copyPrototypePath?(\n identity: unknown,\n input: unknown,\n ): Promise<AgentActionResult<PrototypePathValue>>;\n}\n\ntype AuthorizedInput<Value> =\n | {\n ok: true;\n identity: BonfireIdentity;\n input: Value;\n prototypePath: string;\n }\n | { ok: false; result: AgentActionResult };\n\nfunction failure(\n code: AgentActionError[\"code\"],\n extras: Pick<\n AgentActionError,\n \"agent\" | \"terminal\" | \"availableTerminals\"\n > = {},\n) {\n return {\n ok: false,\n error: { code, ...extras },\n } satisfies AgentActionResult;\n}\n\nfunction authenticate(identity: unknown) {\n if (identity === null || identity === undefined) return null;\n\n try {\n const parsed = parseBonfireIdentity(identity);\n definePrototypeRef({ login: parsed.login, slug: \"prototype\" });\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport class PrototypeAgentService implements PrototypeAgentServiceLike {\n readonly #runtime: PrototypeAgentServiceOptions[\"runtime\"];\n readonly #catalog: PrototypeCatalogLike;\n readonly #preferences: PrototypePreferencesLike;\n readonly #cursor: CursorLauncherLike;\n readonly #claudeCode: ClaudeCodeLauncherLike;\n\n constructor(options: PrototypeAgentServiceOptions) {\n const executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#runtime = options.runtime;\n this.#catalog = options.catalog;\n this.#preferences =\n options.preferences ?? new PrototypePreferences(options.runtime);\n this.#cursor = options.cursor ?? new CursorLauncher({ executableResolver });\n this.#claudeCode =\n options.claudeCode ?? new ClaudeCodeLauncher({ executableResolver });\n }\n\n async #authorize<Value extends { ref: OpenInAgentInput[\"ref\"] }>(\n identity: unknown,\n input: unknown,\n parse: (value: unknown) => Value,\n ): Promise<AuthorizedInput<Value>> {\n const authenticated = authenticate(identity);\n if (authenticated === null) {\n return { ok: false, result: failure(\"unauthenticated\") };\n }\n\n let normalizedInput: Value;\n try {\n normalizedInput = parse(input);\n } catch {\n return { ok: false, result: failure(\"invalid-input\") };\n }\n\n let summary;\n try {\n summary = await this.#catalog.getSummary(normalizedInput.ref);\n } catch {\n summary = null;\n }\n if (summary === null) {\n return { ok: false, result: failure(\"not-found\") };\n }\n if (summary.authorLogin !== authenticated.login) {\n return { ok: false, result: failure(\"forbidden\") };\n }\n\n return {\n ok: true,\n identity: authenticated,\n input: normalizedInput,\n prototypePath: join(\n this.#runtime.prototypeRoot,\n normalizedInput.ref.login,\n normalizedInput.ref.slug,\n ),\n };\n }\n\n async openInCursor(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInAgentInput,\n );\n if (!authorized.ok) return authorized.result;\n\n return this.#cursor.launch(authorized.prototypePath);\n }\n\n async copyPrototypePath(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInAgentInput,\n );\n if (!authorized.ok) {\n return authorized.result as AgentActionResult<PrototypePathValue>;\n }\n\n return {\n ok: true,\n value: { path: authorized.prototypePath },\n } satisfies AgentActionResult<PrototypePathValue>;\n }\n\n async openInClaudeCode(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInClaudeCodeInput,\n );\n if (!authorized.ok) return authorized.result;\n\n const preflight = await this.#claudeCode.preflight(\n authorized.input.terminal,\n );\n if (!preflight.ok) return preflight;\n\n let terminal = authorized.input.terminal;\n if (terminal === undefined) {\n let preferences;\n try {\n preferences = await this.#preferences.read(authorized.identity.login);\n } catch {\n return failure(\"launch-failed\", { agent: \"claude-code\" });\n }\n terminal = preferences.preferredTerminal;\n const availableTerminals = preflight.value.availableTerminals;\n if (\n terminal === undefined ||\n (availableTerminals !== undefined &&\n !availableTerminals.includes(terminal))\n ) {\n return failure(\"needs-terminal\", {\n agent: \"claude-code\",\n ...(availableTerminals ? { availableTerminals } : {}),\n });\n }\n } else {\n try {\n await this.#preferences.update(authorized.identity.login, {\n preferredTerminal: terminal,\n });\n } catch {\n return failure(\"launch-failed\", {\n agent: \"claude-code\",\n terminal,\n });\n }\n }\n\n return this.#claudeCode.launch(\n terminal,\n authorized.prototypePath,\n preflight,\n );\n }\n}\n"]}
@@ -76,6 +76,16 @@ export class PrototypeAgentService {
76
76
  return authorized.result;
77
77
  return this.#cursor.launch(authorized.prototypePath);
78
78
  }
79
+ async copyPrototypePath(identity, input) {
80
+ const authorized = await this.#authorize(identity, input, parseOpenInAgentInput);
81
+ if (!authorized.ok) {
82
+ return authorized.result;
83
+ }
84
+ return {
85
+ ok: true,
86
+ value: { path: authorized.prototypePath },
87
+ };
88
+ }
79
89
  async openInClaudeCode(identity, input) {
80
90
  const authorized = await this.#authorize(identity, input, parseOpenInClaudeCodeInput);
81
91
  if (!authorized.ok)
@@ -93,8 +103,14 @@ export class PrototypeAgentService {
93
103
  return failure("launch-failed", { agent: "claude-code" });
94
104
  }
95
105
  terminal = preferences.preferredTerminal;
96
- if (terminal === undefined) {
97
- return failure("needs-terminal", { agent: "claude-code" });
106
+ const availableTerminals = preflight.value.availableTerminals;
107
+ if (terminal === undefined ||
108
+ (availableTerminals !== undefined &&
109
+ !availableTerminals.includes(terminal))) {
110
+ return failure("needs-terminal", {
111
+ agent: "claude-code",
112
+ ...(availableTerminals ? { availableTerminals } : {}),
113
+ });
98
114
  }
99
115
  }
100
116
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-service.js","sourceRoot":"","sources":["../src/agent-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport {\n definePrototypeRef,\n parseBonfireIdentity,\n parseOpenInAgentInput,\n parseOpenInClaudeCodeInput,\n type AgentActionError,\n type AgentActionResult,\n type BonfireIdentity,\n type OpenInAgentInput,\n} from \"@ebay-dt/bonfire-core\";\nimport { ClaudeCodeLauncher, type ClaudeCodeLauncherLike } from \"./claude.js\";\nimport { CursorLauncher, type CursorLauncherLike } from \"./cursor.js\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\nimport {\n PrototypePreferences,\n type PrototypePreferencesLike,\n} from \"./preferences.js\";\nimport type { PrototypeCatalogLike } from \"./prototype-catalog.js\";\nimport type { PrototypeLifecycleRuntimeContext } from \"./lifecycle/runtime-context.js\";\n\nexport type PrototypeAgentServiceOptions = {\n runtime: Pick<PrototypeLifecycleRuntimeContext, \"prototypeRoot\">;\n catalog: PrototypeCatalogLike;\n executableResolver?: ExecutableResolverLike;\n preferences?: PrototypePreferencesLike;\n cursor?: CursorLauncherLike;\n claudeCode?: ClaudeCodeLauncherLike;\n};\n\nexport interface PrototypeAgentServiceLike {\n openInCursor(identity: unknown, input: unknown): Promise<AgentActionResult>;\n openInClaudeCode(\n identity: unknown,\n input: unknown,\n ): Promise<AgentActionResult>;\n}\n\ntype AuthorizedInput<Value> =\n | {\n ok: true;\n identity: BonfireIdentity;\n input: Value;\n prototypePath: string;\n }\n | { ok: false; result: AgentActionResult };\n\nfunction failure(\n code: AgentActionError[\"code\"],\n extras: Pick<AgentActionError, \"agent\" | \"terminal\"> = {},\n) {\n return {\n ok: false,\n error: { code, ...extras },\n } satisfies AgentActionResult;\n}\n\nfunction authenticate(identity: unknown) {\n if (identity === null || identity === undefined) return null;\n\n try {\n const parsed = parseBonfireIdentity(identity);\n definePrototypeRef({ login: parsed.login, slug: \"prototype\" });\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport class PrototypeAgentService implements PrototypeAgentServiceLike {\n readonly #runtime: PrototypeAgentServiceOptions[\"runtime\"];\n readonly #catalog: PrototypeCatalogLike;\n readonly #preferences: PrototypePreferencesLike;\n readonly #cursor: CursorLauncherLike;\n readonly #claudeCode: ClaudeCodeLauncherLike;\n\n constructor(options: PrototypeAgentServiceOptions) {\n const executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#runtime = options.runtime;\n this.#catalog = options.catalog;\n this.#preferences =\n options.preferences ?? new PrototypePreferences(options.runtime);\n this.#cursor = options.cursor ?? new CursorLauncher({ executableResolver });\n this.#claudeCode =\n options.claudeCode ?? new ClaudeCodeLauncher({ executableResolver });\n }\n\n async #authorize<Value extends { ref: OpenInAgentInput[\"ref\"] }>(\n identity: unknown,\n input: unknown,\n parse: (value: unknown) => Value,\n ): Promise<AuthorizedInput<Value>> {\n const authenticated = authenticate(identity);\n if (authenticated === null) {\n return { ok: false, result: failure(\"unauthenticated\") };\n }\n\n let normalizedInput: Value;\n try {\n normalizedInput = parse(input);\n } catch {\n return { ok: false, result: failure(\"invalid-input\") };\n }\n\n let summary;\n try {\n summary = await this.#catalog.getSummary(normalizedInput.ref);\n } catch {\n summary = null;\n }\n if (summary === null) {\n return { ok: false, result: failure(\"not-found\") };\n }\n if (summary.authorLogin !== authenticated.login) {\n return { ok: false, result: failure(\"forbidden\") };\n }\n\n return {\n ok: true,\n identity: authenticated,\n input: normalizedInput,\n prototypePath: join(\n this.#runtime.prototypeRoot,\n normalizedInput.ref.login,\n normalizedInput.ref.slug,\n ),\n };\n }\n\n async openInCursor(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInAgentInput,\n );\n if (!authorized.ok) return authorized.result;\n\n return this.#cursor.launch(authorized.prototypePath);\n }\n\n async openInClaudeCode(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInClaudeCodeInput,\n );\n if (!authorized.ok) return authorized.result;\n\n const preflight = await this.#claudeCode.preflight(\n authorized.input.terminal,\n );\n if (!preflight.ok) return preflight;\n\n let terminal = authorized.input.terminal;\n if (terminal === undefined) {\n let preferences;\n try {\n preferences = await this.#preferences.read(authorized.identity.login);\n } catch {\n return failure(\"launch-failed\", { agent: \"claude-code\" });\n }\n terminal = preferences.preferredTerminal;\n if (terminal === undefined) {\n return failure(\"needs-terminal\", { agent: \"claude-code\" });\n }\n } else {\n try {\n await this.#preferences.update(authorized.identity.login, {\n preferredTerminal: terminal,\n });\n } catch {\n return failure(\"launch-failed\", {\n agent: \"claude-code\",\n terminal,\n });\n }\n }\n\n return this.#claudeCode.launch(\n terminal,\n authorized.prototypePath,\n preflight,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"agent-service.js","sourceRoot":"","sources":["../src/agent-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport {\n definePrototypeRef,\n parseBonfireIdentity,\n parseOpenInAgentInput,\n parseOpenInClaudeCodeInput,\n type AgentActionError,\n type AgentActionResult,\n type BonfireIdentity,\n type OpenInAgentInput,\n type PrototypePathValue,\n} from \"@ebay-dt/bonfire-core\";\nimport { ClaudeCodeLauncher, type ClaudeCodeLauncherLike } from \"./claude.js\";\nimport { CursorLauncher, type CursorLauncherLike } from \"./cursor.js\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\nimport {\n PrototypePreferences,\n type PrototypePreferencesLike,\n} from \"./preferences.js\";\nimport type { PrototypeCatalogLike } from \"./prototype-catalog.js\";\nimport type { PrototypeLifecycleRuntimeContext } from \"./lifecycle/runtime-context.js\";\n\nexport type PrototypeAgentServiceOptions = {\n runtime: Pick<PrototypeLifecycleRuntimeContext, \"prototypeRoot\">;\n catalog: PrototypeCatalogLike;\n executableResolver?: ExecutableResolverLike;\n preferences?: PrototypePreferencesLike;\n cursor?: CursorLauncherLike;\n claudeCode?: ClaudeCodeLauncherLike;\n};\n\nexport interface PrototypeAgentServiceLike {\n openInCursor(identity: unknown, input: unknown): Promise<AgentActionResult>;\n openInClaudeCode(\n identity: unknown,\n input: unknown,\n ): Promise<AgentActionResult>;\n copyPrototypePath?(\n identity: unknown,\n input: unknown,\n ): Promise<AgentActionResult<PrototypePathValue>>;\n}\n\ntype AuthorizedInput<Value> =\n | {\n ok: true;\n identity: BonfireIdentity;\n input: Value;\n prototypePath: string;\n }\n | { ok: false; result: AgentActionResult };\n\nfunction failure(\n code: AgentActionError[\"code\"],\n extras: Pick<\n AgentActionError,\n \"agent\" | \"terminal\" | \"availableTerminals\"\n > = {},\n) {\n return {\n ok: false,\n error: { code, ...extras },\n } satisfies AgentActionResult;\n}\n\nfunction authenticate(identity: unknown) {\n if (identity === null || identity === undefined) return null;\n\n try {\n const parsed = parseBonfireIdentity(identity);\n definePrototypeRef({ login: parsed.login, slug: \"prototype\" });\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport class PrototypeAgentService implements PrototypeAgentServiceLike {\n readonly #runtime: PrototypeAgentServiceOptions[\"runtime\"];\n readonly #catalog: PrototypeCatalogLike;\n readonly #preferences: PrototypePreferencesLike;\n readonly #cursor: CursorLauncherLike;\n readonly #claudeCode: ClaudeCodeLauncherLike;\n\n constructor(options: PrototypeAgentServiceOptions) {\n const executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#runtime = options.runtime;\n this.#catalog = options.catalog;\n this.#preferences =\n options.preferences ?? new PrototypePreferences(options.runtime);\n this.#cursor = options.cursor ?? new CursorLauncher({ executableResolver });\n this.#claudeCode =\n options.claudeCode ?? new ClaudeCodeLauncher({ executableResolver });\n }\n\n async #authorize<Value extends { ref: OpenInAgentInput[\"ref\"] }>(\n identity: unknown,\n input: unknown,\n parse: (value: unknown) => Value,\n ): Promise<AuthorizedInput<Value>> {\n const authenticated = authenticate(identity);\n if (authenticated === null) {\n return { ok: false, result: failure(\"unauthenticated\") };\n }\n\n let normalizedInput: Value;\n try {\n normalizedInput = parse(input);\n } catch {\n return { ok: false, result: failure(\"invalid-input\") };\n }\n\n let summary;\n try {\n summary = await this.#catalog.getSummary(normalizedInput.ref);\n } catch {\n summary = null;\n }\n if (summary === null) {\n return { ok: false, result: failure(\"not-found\") };\n }\n if (summary.authorLogin !== authenticated.login) {\n return { ok: false, result: failure(\"forbidden\") };\n }\n\n return {\n ok: true,\n identity: authenticated,\n input: normalizedInput,\n prototypePath: join(\n this.#runtime.prototypeRoot,\n normalizedInput.ref.login,\n normalizedInput.ref.slug,\n ),\n };\n }\n\n async openInCursor(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInAgentInput,\n );\n if (!authorized.ok) return authorized.result;\n\n return this.#cursor.launch(authorized.prototypePath);\n }\n\n async copyPrototypePath(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInAgentInput,\n );\n if (!authorized.ok) {\n return authorized.result as AgentActionResult<PrototypePathValue>;\n }\n\n return {\n ok: true,\n value: { path: authorized.prototypePath },\n } satisfies AgentActionResult<PrototypePathValue>;\n }\n\n async openInClaudeCode(identity: unknown, input: unknown) {\n const authorized = await this.#authorize(\n identity,\n input,\n parseOpenInClaudeCodeInput,\n );\n if (!authorized.ok) return authorized.result;\n\n const preflight = await this.#claudeCode.preflight(\n authorized.input.terminal,\n );\n if (!preflight.ok) return preflight;\n\n let terminal = authorized.input.terminal;\n if (terminal === undefined) {\n let preferences;\n try {\n preferences = await this.#preferences.read(authorized.identity.login);\n } catch {\n return failure(\"launch-failed\", { agent: \"claude-code\" });\n }\n terminal = preferences.preferredTerminal;\n const availableTerminals = preflight.value.availableTerminals;\n if (\n terminal === undefined ||\n (availableTerminals !== undefined &&\n !availableTerminals.includes(terminal))\n ) {\n return failure(\"needs-terminal\", {\n agent: \"claude-code\",\n ...(availableTerminals ? { availableTerminals } : {}),\n });\n }\n } else {\n try {\n await this.#preferences.update(authorized.identity.login, {\n preferredTerminal: terminal,\n });\n } catch {\n return failure(\"launch-failed\", {\n agent: \"claude-code\",\n terminal,\n });\n }\n }\n\n return this.#claudeCode.launch(\n terminal,\n authorized.prototypePath,\n preflight,\n );\n }\n}\n"]}
package/dist/claude.d.ts CHANGED
@@ -12,31 +12,32 @@ export type ClaudeCodeProcessRunner = {
12
12
  execFile(executable: string, args: readonly string[], options: ClaudeCodeExecOptions): Promise<ClaudeCodeExecResult>;
13
13
  };
14
14
  export type ClaudeCodePreflightValue = {
15
- osascript: string;
15
+ /** The destinations detected during an unselected preflight. */
16
+ availableTerminals?: readonly TerminalApp[];
17
+ /** The macOS AppleScript runner, when a terminal app is selected. */
18
+ osascript?: string;
19
+ /** The VS Code CLI, when VS Code is available. */
20
+ code?: string;
16
21
  };
17
22
  export type ClaudeCodePreflightResult = AgentActionResult<ClaudeCodePreflightValue>;
18
23
  export type ClaudeCodeLauncherOptions = {
19
24
  executableResolver?: ExecutableResolverLike;
20
25
  platform?: NodeJS.Platform;
26
+ /** Home directory used when checking a user-installed VS Code app bundle. */
27
+ home?: string;
28
+ /** Application directories used when checking macOS terminal bundles. */
29
+ macApplicationDirectories?: readonly string[];
21
30
  process?: ClaudeCodeProcessRunner;
22
31
  };
23
32
  export interface ClaudeCodeLauncherLike {
24
33
  preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;
25
34
  launch(terminal: TerminalApp, prototypePath: string, preflight?: ClaudeCodePreflightResult): Promise<AgentActionResult<AgentLaunchValue>>;
26
35
  }
27
- export declare function buildClaudeCodeAppleScript(terminal: TerminalApp, prototypePath: string): string;
36
+ export declare function buildClaudeCodeAppleScript(terminal: Exclude<TerminalApp, "vscode">, prototypePath: string): string;
28
37
  export declare class ClaudeCodeLauncher implements ClaudeCodeLauncherLike {
29
38
  #private;
30
39
  constructor(options?: ClaudeCodeLauncherOptions);
31
- preflight(terminal?: TerminalApp): Promise<{
32
- ok: false;
33
- error: AgentActionError;
34
- } | {
35
- ok: true;
36
- value: {
37
- osascript: string;
38
- };
39
- }>;
40
+ preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;
40
41
  launch(terminal: TerminalApp, prototypePath: string, preflight?: ClaudeCodePreflightResult): Promise<{
41
42
  ok: false;
42
43
  error: AgentActionError;
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAEtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,iBAAiB,CAAC;AAOzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GACnC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACtE,MAAM,CACJ,QAAQ,EAAE,WAAW,EACrB,aAAa,EAAE,MAAM,EACrB,SAAS,CAAC,EAAE,yBAAyB,GACpC,OAAO,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAC;CACjD;AAuCD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,WAAW,EACrB,aAAa,EAAE,MAAM,UA2BtB;AAoBD,qBAAa,kBAAmB,YAAW,sBAAsB;;IAK/D,YAAY,OAAO,GAAE,yBAA8B,EAKlD;IAEK,SAAS,CAAC,QAAQ,CAAC,EAAE,WAAW;;;;;;;;OAmBrC;IAEK,MAAM,CACV,QAAQ,EAAE,WAAW,EACrB,aAAa,EAAE,MAAM,EACrB,SAAS,CAAC,EAAE,yBAAyB;;;;;;;;OAmBtC;CACF","sourcesContent":["import { execFile as nodeExecFile } from \"node:child_process\";\nimport {\n type AgentActionError,\n type AgentActionResult,\n type AgentCommand,\n type AgentLaunchValue,\n type TerminalApp,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\n\nconst CLAUDE_COMMAND = \"claude\";\nconst OSASCRIPT_COMMAND = \"osascript\";\nconst APPLESCRIPT_TIMEOUT = 10_000;\nconst APPLESCRIPT_MAX_BUFFER = 1024 * 1024;\n\nexport type ClaudeCodeExecOptions = {\n timeout: number;\n maxBuffer: number;\n};\n\nexport type ClaudeCodeExecResult = {\n stdout: string;\n stderr: string;\n};\n\nexport type ClaudeCodeProcessRunner = {\n execFile(\n executable: string,\n args: readonly string[],\n options: ClaudeCodeExecOptions,\n ): Promise<ClaudeCodeExecResult>;\n};\n\nexport type ClaudeCodePreflightValue = {\n osascript: string;\n};\n\nexport type ClaudeCodePreflightResult =\n AgentActionResult<ClaudeCodePreflightValue>;\n\nexport type ClaudeCodeLauncherOptions = {\n executableResolver?: ExecutableResolverLike;\n platform?: NodeJS.Platform;\n process?: ClaudeCodeProcessRunner;\n};\n\nexport interface ClaudeCodeLauncherLike {\n preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;\n launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ): Promise<AgentActionResult<AgentLaunchValue>>;\n}\n\nconst defaultProcess: ClaudeCodeProcessRunner = {\n execFile(executable, args, options) {\n return new Promise((resolve, reject) => {\n nodeExecFile(\n executable,\n [...args],\n { ...options, encoding: \"utf8\" },\n (error, stdout, stderr) => {\n if (error) {\n Object.assign(error, { stdout, stderr });\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n },\n );\n });\n },\n};\n\nfunction appleScriptString(value: string) {\n return `\"${value\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll('\"', '\\\\\"')\n .replaceAll(\"\\r\", \"\\\\r\")\n .replaceAll(\"\\n\", \"\\\\n\")}\"`;\n}\n\nfunction shellString(value: string) {\n return `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n\nfunction commandForPath(prototypePath: string) {\n return `cd ${shellString(prototypePath)} && ${CLAUDE_COMMAND}`;\n}\n\nexport function buildClaudeCodeAppleScript(\n terminal: TerminalApp,\n prototypePath: string,\n) {\n const command = commandForPath(prototypePath);\n\n switch (terminal) {\n case \"terminal\":\n return `tell application \"Terminal\" to do script ${appleScriptString(command)}`;\n case \"iterm\":\n return [\n 'tell application \"iTerm\"',\n \" create window with default profile\",\n \" tell current session of current window\",\n ` write text ${appleScriptString(command)}`,\n \" end tell\",\n \"end tell\",\n ].join(\"\\n\");\n case \"warp\": {\n const uri = `warp://action/new_tab?path=${encodeURIComponent(prototypePath)}`;\n return [\n `do shell script ${appleScriptString(`open ${shellString(uri)}`)}`,\n \"delay 1\",\n 'tell application \"Warp\" to activate',\n `tell application \"System Events\" to keystroke ${appleScriptString(command)}`,\n 'tell application \"System Events\" to key code 36',\n ].join(\"\\n\");\n }\n }\n}\n\nfunction actionFailure(\n code: \"missing-command\" | \"launch-failed\" | \"unsupported-platform\",\n terminal?: TerminalApp,\n command?: AgentCommand,\n) {\n const error: AgentActionError = {\n code,\n agent: \"claude-code\",\n ...(command ? { command } : {}),\n ...(terminal ? { terminal } : {}),\n };\n\n return {\n ok: false,\n error,\n } satisfies AgentActionResult;\n}\n\nexport class ClaudeCodeLauncher implements ClaudeCodeLauncherLike {\n readonly #executableResolver: ExecutableResolverLike;\n readonly #platform: NodeJS.Platform;\n readonly #process: ClaudeCodeProcessRunner;\n\n constructor(options: ClaudeCodeLauncherOptions = {}) {\n this.#executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#platform = options.platform ?? process.platform;\n this.#process = options.process ?? defaultProcess;\n }\n\n async preflight(terminal?: TerminalApp) {\n if (this.#platform !== \"darwin\") {\n return actionFailure(\"unsupported-platform\", terminal);\n }\n\n const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);\n if (claude === null) {\n return actionFailure(\"missing-command\", terminal, CLAUDE_COMMAND);\n }\n\n const osascript = await this.#executableResolver.resolve(OSASCRIPT_COMMAND);\n if (osascript === null) {\n return actionFailure(\"missing-command\", terminal, OSASCRIPT_COMMAND);\n }\n\n return {\n ok: true,\n value: { osascript },\n } satisfies ClaudeCodePreflightResult;\n }\n\n async launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ) {\n const checked = preflight ?? (await this.preflight(terminal));\n if (!checked.ok) return checked;\n\n const script = buildClaudeCodeAppleScript(terminal, prototypePath);\n try {\n await this.#process.execFile(checked.value.osascript, [\"-e\", script], {\n timeout: APPLESCRIPT_TIMEOUT,\n maxBuffer: APPLESCRIPT_MAX_BUFFER,\n });\n } catch {\n return actionFailure(\"launch-failed\", terminal);\n }\n\n return {\n ok: true,\n value: { agent: \"claude-code\" },\n } satisfies AgentActionResult;\n }\n}\n"]}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAEtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,iBAAiB,CAAC;AAQzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,gEAAgE;IAChE,kBAAkB,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC5C,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,yBAAyB,GACnC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACtE,MAAM,CACJ,QAAQ,EAAE,WAAW,EACrB,aAAa,EAAE,MAAM,EACrB,SAAS,CAAC,EAAE,yBAAyB,GACpC,OAAO,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAC;CACjD;AA+FD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,EACxC,aAAa,EAAE,MAAM,UAqCtB;AAoBD,qBAAa,kBAAmB,YAAW,sBAAsB;;IAQ/D,YAAY,OAAO,GAAE,yBAA8B,EAUlD;IAyCK,SAAS,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAkF1E;IAEK,MAAM,CACV,QAAQ,EAAE,WAAW,EACrB,aAAa,EAAE,MAAM,EACrB,SAAS,CAAC,EAAE,yBAAyB;;;;;;;;OA+CtC;CACF","sourcesContent":["import { execFile as nodeExecFile } from \"node:child_process\";\nimport { access } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n type AgentActionError,\n type AgentActionResult,\n type AgentCommand,\n type AgentLaunchValue,\n type TerminalApp,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\n\nconst CLAUDE_COMMAND = \"claude\";\nconst CODE_COMMAND = \"code\";\nconst OSASCRIPT_COMMAND = \"osascript\";\nconst APPLESCRIPT_TIMEOUT = 10_000;\nconst APPLESCRIPT_MAX_BUFFER = 1024 * 1024;\n\nexport type ClaudeCodeExecOptions = {\n timeout: number;\n maxBuffer: number;\n};\n\nexport type ClaudeCodeExecResult = {\n stdout: string;\n stderr: string;\n};\n\nexport type ClaudeCodeProcessRunner = {\n execFile(\n executable: string,\n args: readonly string[],\n options: ClaudeCodeExecOptions,\n ): Promise<ClaudeCodeExecResult>;\n};\n\nexport type ClaudeCodePreflightValue = {\n /** The destinations detected during an unselected preflight. */\n availableTerminals?: readonly TerminalApp[];\n /** The macOS AppleScript runner, when a terminal app is selected. */\n osascript?: string;\n /** The VS Code CLI, when VS Code is available. */\n code?: string;\n};\n\nexport type ClaudeCodePreflightResult =\n AgentActionResult<ClaudeCodePreflightValue>;\n\nexport type ClaudeCodeLauncherOptions = {\n executableResolver?: ExecutableResolverLike;\n platform?: NodeJS.Platform;\n /** Home directory used when checking a user-installed VS Code app bundle. */\n home?: string;\n /** Application directories used when checking macOS terminal bundles. */\n macApplicationDirectories?: readonly string[];\n process?: ClaudeCodeProcessRunner;\n};\n\nexport interface ClaudeCodeLauncherLike {\n preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;\n launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ): Promise<AgentActionResult<AgentLaunchValue>>;\n}\n\nfunction bundledVscodeCliCandidates(home: string) {\n return [\n join(\n \"/Applications\",\n \"Visual Studio Code.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code\",\n ),\n join(\n home,\n \"Applications\",\n \"Visual Studio Code.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code\",\n ),\n join(\n \"/Applications\",\n \"Visual Studio Code - Insiders.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code-insiders\",\n ),\n join(\n home,\n \"Applications\",\n \"Visual Studio Code - Insiders.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code-insiders\",\n ),\n ];\n}\n\nconst defaultProcess: ClaudeCodeProcessRunner = {\n execFile(executable, args, options) {\n return new Promise((resolve, reject) => {\n nodeExecFile(\n executable,\n [...args],\n { ...options, encoding: \"utf8\" },\n (error, stdout, stderr) => {\n if (error) {\n Object.assign(error, { stdout, stderr });\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n },\n );\n });\n },\n};\n\nfunction appleScriptString(value: string) {\n return `\"${value\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll('\"', '\\\\\"')\n .replaceAll(\"\\r\", \"\\\\r\")\n .replaceAll(\"\\n\", \"\\\\n\")}\"`;\n}\n\nfunction shellString(value: string) {\n return `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n\nfunction macApplicationBundleForTerminal(terminal: TerminalApp) {\n switch (terminal) {\n case \"iterm\":\n return \"iTerm.app\";\n case \"warp\":\n return \"Warp.app\";\n case \"ghostty\":\n return \"Ghostty.app\";\n default:\n return undefined;\n }\n}\n\nfunction commandForPath(prototypePath: string) {\n return `cd ${shellString(prototypePath)} && ${CLAUDE_COMMAND}`;\n}\n\nexport function buildClaudeCodeAppleScript(\n terminal: Exclude<TerminalApp, \"vscode\">,\n prototypePath: string,\n) {\n const command = commandForPath(prototypePath);\n\n switch (terminal) {\n case \"terminal\":\n return `tell application \"Terminal\" to do script ${appleScriptString(command)}`;\n case \"iterm\":\n return [\n 'tell application \"iTerm\"',\n \" create window with default profile\",\n \" tell current session of current window\",\n ` write text ${appleScriptString(command)}`,\n \" end tell\",\n \"end tell\",\n ].join(\"\\n\");\n case \"warp\": {\n const uri = `warp://action/new_tab?path=${encodeURIComponent(prototypePath)}`;\n return [\n `do shell script ${appleScriptString(`open ${shellString(uri)}`)}`,\n \"delay 1\",\n 'tell application \"Warp\" to activate',\n `tell application \"System Events\" to keystroke ${appleScriptString(command)}`,\n 'tell application \"System Events\" to key code 36',\n ].join(\"\\n\");\n }\n case \"ghostty\":\n return [\n 'tell application \"Ghostty\"',\n \" activate\",\n \" set config to new surface configuration\",\n ` set initial working directory of config to ${appleScriptString(prototypePath)}`,\n ` set initial input of config to ${appleScriptString(`${CLAUDE_COMMAND}\\n`)}`,\n \" new window with configuration config\",\n \"end tell\",\n ].join(\"\\n\");\n }\n}\n\nfunction actionFailure(\n code: \"missing-command\" | \"launch-failed\" | \"unsupported-platform\",\n terminal?: TerminalApp,\n command?: AgentCommand,\n) {\n const error: AgentActionError = {\n code,\n agent: \"claude-code\",\n ...(command ? { command } : {}),\n ...(terminal ? { terminal } : {}),\n };\n\n return {\n ok: false,\n error,\n } satisfies AgentActionResult;\n}\n\nexport class ClaudeCodeLauncher implements ClaudeCodeLauncherLike {\n readonly #executableResolver: ExecutableResolverLike;\n readonly #platform: NodeJS.Platform;\n readonly #home: string;\n readonly #macApplicationDirectories: readonly string[];\n readonly #process: ClaudeCodeProcessRunner;\n readonly #applicationAvailability = new Map<TerminalApp, Promise<boolean>>();\n\n constructor(options: ClaudeCodeLauncherOptions = {}) {\n this.#executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#platform = options.platform ?? process.platform;\n this.#home = options.home ?? homedir();\n this.#macApplicationDirectories = options.macApplicationDirectories ?? [\n \"/Applications\",\n join(this.#home, \"Applications\"),\n ];\n this.#process = options.process ?? defaultProcess;\n }\n\n async #resolveVscodeCli() {\n const onPath = await this.#executableResolver.resolve(CODE_COMMAND);\n if (onPath !== null) return onPath;\n if (this.#platform !== \"darwin\") return null;\n\n for (const candidate of bundledVscodeCliCandidates(this.#home)) {\n const bundled = await this.#executableResolver.resolve(candidate);\n if (bundled !== null) return bundled;\n }\n return null;\n }\n\n #isMacApplicationAvailable(terminal: TerminalApp) {\n const cached = this.#applicationAvailability.get(terminal);\n if (cached !== undefined) return cached;\n\n const application = macApplicationBundleForTerminal(terminal);\n const availability = (async () => {\n if (this.#platform !== \"darwin\" || application === undefined) {\n return false;\n }\n\n const candidates = this.#macApplicationDirectories.map((directory) =>\n join(directory, application),\n );\n for (const candidate of candidates) {\n try {\n await access(candidate);\n return true;\n } catch {\n // Continue with the next standard application location.\n }\n }\n return false;\n })();\n this.#applicationAvailability.set(terminal, availability);\n return availability;\n }\n\n async preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult> {\n if (terminal === \"vscode\") {\n const code = await this.#resolveVscodeCli();\n if (code === null) {\n return actionFailure(\"missing-command\", terminal, CODE_COMMAND);\n }\n return {\n ok: true,\n value: { code },\n } satisfies ClaudeCodePreflightResult;\n }\n\n if (terminal !== undefined && this.#platform !== \"darwin\") {\n return actionFailure(\"unsupported-platform\", terminal);\n }\n\n if (terminal !== undefined) {\n const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);\n if (claude === null) {\n return actionFailure(\"missing-command\", terminal, CLAUDE_COMMAND);\n }\n\n const osascript =\n await this.#executableResolver.resolve(OSASCRIPT_COMMAND);\n if (osascript === null) {\n return actionFailure(\"missing-command\", terminal, OSASCRIPT_COMMAND);\n }\n\n return {\n ok: true,\n value: { osascript },\n } satisfies ClaudeCodePreflightResult;\n }\n\n // An omitted terminal means that the UI still needs to offer a choice.\n // Keep the preflight successful when either the macOS terminal flow or the\n // VS Code flow is usable; the selected option is checked again below.\n if (this.#platform === \"darwin\") {\n const [claude, osascript, code] = await Promise.all([\n this.#executableResolver.resolve(CLAUDE_COMMAND),\n this.#executableResolver.resolve(OSASCRIPT_COMMAND),\n this.#resolveVscodeCli(),\n ]);\n const terminalReady = claude !== null && osascript !== null;\n const [iterm, warp, ghostty] = terminalReady\n ? await Promise.all(\n ([\"iterm\", \"warp\", \"ghostty\"] as const).map((app) =>\n this.#isMacApplicationAvailable(app),\n ),\n )\n : [false, false, false];\n const availableTerminals = [\n ...(terminalReady ? ([\"terminal\"] as const) : []),\n ...(iterm ? ([\"iterm\"] as const) : []),\n ...(warp ? ([\"warp\"] as const) : []),\n ...(ghostty ? ([\"ghostty\"] as const) : []),\n ...(code !== null ? ([\"vscode\"] as const) : []),\n ];\n if (availableTerminals.length > 0) {\n return {\n ok: true,\n value: {\n availableTerminals,\n ...(code !== null ? { code } : {}),\n ...(osascript !== null && claude !== null ? { osascript } : {}),\n },\n } satisfies ClaudeCodePreflightResult;\n }\n if (claude === null) {\n return actionFailure(\"missing-command\", undefined, CLAUDE_COMMAND);\n }\n return actionFailure(\"missing-command\", undefined, OSASCRIPT_COMMAND);\n }\n\n const code = await this.#resolveVscodeCli();\n if (code !== null) {\n return {\n ok: true,\n value: { code, availableTerminals: [\"vscode\"] },\n } satisfies ClaudeCodePreflightResult;\n }\n return actionFailure(\"unsupported-platform\");\n }\n\n async launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ) {\n const checked = preflight ?? (await this.preflight(terminal));\n if (!checked.ok) return checked;\n\n if (terminal === \"vscode\") {\n if (checked.value.code === undefined) {\n return actionFailure(\"missing-command\", terminal, CODE_COMMAND);\n }\n\n try {\n await this.#process.execFile(\n checked.value.code,\n [\"--new-window\", prototypePath],\n {\n timeout: APPLESCRIPT_TIMEOUT,\n maxBuffer: APPLESCRIPT_MAX_BUFFER,\n },\n );\n } catch {\n return actionFailure(\"launch-failed\", terminal);\n }\n\n return {\n ok: true,\n value: { agent: \"claude-code\" },\n } satisfies AgentActionResult;\n }\n\n if (checked.value.osascript === undefined) {\n return actionFailure(\"missing-command\", terminal, OSASCRIPT_COMMAND);\n }\n\n const script = buildClaudeCodeAppleScript(terminal, prototypePath);\n try {\n await this.#process.execFile(checked.value.osascript, [\"-e\", script], {\n timeout: APPLESCRIPT_TIMEOUT,\n maxBuffer: APPLESCRIPT_MAX_BUFFER,\n });\n } catch {\n return actionFailure(\"launch-failed\", terminal);\n }\n\n return {\n ok: true,\n value: { agent: \"claude-code\" },\n } satisfies AgentActionResult;\n }\n}\n"]}
package/dist/claude.js CHANGED
@@ -1,9 +1,21 @@
1
1
  import { execFile as nodeExecFile } from "node:child_process";
2
+ import { access } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
2
5
  import { PathExecutableResolver, } from "./executable.js";
3
6
  const CLAUDE_COMMAND = "claude";
7
+ const CODE_COMMAND = "code";
4
8
  const OSASCRIPT_COMMAND = "osascript";
5
9
  const APPLESCRIPT_TIMEOUT = 10_000;
6
10
  const APPLESCRIPT_MAX_BUFFER = 1024 * 1024;
11
+ function bundledVscodeCliCandidates(home) {
12
+ return [
13
+ join("/Applications", "Visual Studio Code.app", "Contents", "Resources", "app", "bin", "code"),
14
+ join(home, "Applications", "Visual Studio Code.app", "Contents", "Resources", "app", "bin", "code"),
15
+ join("/Applications", "Visual Studio Code - Insiders.app", "Contents", "Resources", "app", "bin", "code-insiders"),
16
+ join(home, "Applications", "Visual Studio Code - Insiders.app", "Contents", "Resources", "app", "bin", "code-insiders"),
17
+ ];
18
+ }
7
19
  const defaultProcess = {
8
20
  execFile(executable, args, options) {
9
21
  return new Promise((resolve, reject) => {
@@ -28,6 +40,18 @@ function appleScriptString(value) {
28
40
  function shellString(value) {
29
41
  return `'${value.replaceAll("'", `'"'"'`)}'`;
30
42
  }
43
+ function macApplicationBundleForTerminal(terminal) {
44
+ switch (terminal) {
45
+ case "iterm":
46
+ return "iTerm.app";
47
+ case "warp":
48
+ return "Warp.app";
49
+ case "ghostty":
50
+ return "Ghostty.app";
51
+ default:
52
+ return undefined;
53
+ }
54
+ }
31
55
  function commandForPath(prototypePath) {
32
56
  return `cd ${shellString(prototypePath)} && ${CLAUDE_COMMAND}`;
33
57
  }
@@ -55,6 +79,16 @@ export function buildClaudeCodeAppleScript(terminal, prototypePath) {
55
79
  'tell application "System Events" to key code 36',
56
80
  ].join("\n");
57
81
  }
82
+ case "ghostty":
83
+ return [
84
+ 'tell application "Ghostty"',
85
+ " activate",
86
+ " set config to new surface configuration",
87
+ ` set initial working directory of config to ${appleScriptString(prototypePath)}`,
88
+ ` set initial input of config to ${appleScriptString(`${CLAUDE_COMMAND}\n`)}`,
89
+ " new window with configuration config",
90
+ "end tell",
91
+ ].join("\n");
58
92
  }
59
93
  }
60
94
  function actionFailure(code, terminal, command) {
@@ -72,34 +106,155 @@ function actionFailure(code, terminal, command) {
72
106
  export class ClaudeCodeLauncher {
73
107
  #executableResolver;
74
108
  #platform;
109
+ #home;
110
+ #macApplicationDirectories;
75
111
  #process;
112
+ #applicationAvailability = new Map();
76
113
  constructor(options = {}) {
77
114
  this.#executableResolver =
78
115
  options.executableResolver ?? new PathExecutableResolver();
79
116
  this.#platform = options.platform ?? process.platform;
117
+ this.#home = options.home ?? homedir();
118
+ this.#macApplicationDirectories = options.macApplicationDirectories ?? [
119
+ "/Applications",
120
+ join(this.#home, "Applications"),
121
+ ];
80
122
  this.#process = options.process ?? defaultProcess;
81
123
  }
124
+ async #resolveVscodeCli() {
125
+ const onPath = await this.#executableResolver.resolve(CODE_COMMAND);
126
+ if (onPath !== null)
127
+ return onPath;
128
+ if (this.#platform !== "darwin")
129
+ return null;
130
+ for (const candidate of bundledVscodeCliCandidates(this.#home)) {
131
+ const bundled = await this.#executableResolver.resolve(candidate);
132
+ if (bundled !== null)
133
+ return bundled;
134
+ }
135
+ return null;
136
+ }
137
+ #isMacApplicationAvailable(terminal) {
138
+ const cached = this.#applicationAvailability.get(terminal);
139
+ if (cached !== undefined)
140
+ return cached;
141
+ const application = macApplicationBundleForTerminal(terminal);
142
+ const availability = (async () => {
143
+ if (this.#platform !== "darwin" || application === undefined) {
144
+ return false;
145
+ }
146
+ const candidates = this.#macApplicationDirectories.map((directory) => join(directory, application));
147
+ for (const candidate of candidates) {
148
+ try {
149
+ await access(candidate);
150
+ return true;
151
+ }
152
+ catch {
153
+ // Continue with the next standard application location.
154
+ }
155
+ }
156
+ return false;
157
+ })();
158
+ this.#applicationAvailability.set(terminal, availability);
159
+ return availability;
160
+ }
82
161
  async preflight(terminal) {
83
- if (this.#platform !== "darwin") {
162
+ if (terminal === "vscode") {
163
+ const code = await this.#resolveVscodeCli();
164
+ if (code === null) {
165
+ return actionFailure("missing-command", terminal, CODE_COMMAND);
166
+ }
167
+ return {
168
+ ok: true,
169
+ value: { code },
170
+ };
171
+ }
172
+ if (terminal !== undefined && this.#platform !== "darwin") {
84
173
  return actionFailure("unsupported-platform", terminal);
85
174
  }
86
- const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);
87
- if (claude === null) {
88
- return actionFailure("missing-command", terminal, CLAUDE_COMMAND);
175
+ if (terminal !== undefined) {
176
+ const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);
177
+ if (claude === null) {
178
+ return actionFailure("missing-command", terminal, CLAUDE_COMMAND);
179
+ }
180
+ const osascript = await this.#executableResolver.resolve(OSASCRIPT_COMMAND);
181
+ if (osascript === null) {
182
+ return actionFailure("missing-command", terminal, OSASCRIPT_COMMAND);
183
+ }
184
+ return {
185
+ ok: true,
186
+ value: { osascript },
187
+ };
89
188
  }
90
- const osascript = await this.#executableResolver.resolve(OSASCRIPT_COMMAND);
91
- if (osascript === null) {
92
- return actionFailure("missing-command", terminal, OSASCRIPT_COMMAND);
189
+ // An omitted terminal means that the UI still needs to offer a choice.
190
+ // Keep the preflight successful when either the macOS terminal flow or the
191
+ // VS Code flow is usable; the selected option is checked again below.
192
+ if (this.#platform === "darwin") {
193
+ const [claude, osascript, code] = await Promise.all([
194
+ this.#executableResolver.resolve(CLAUDE_COMMAND),
195
+ this.#executableResolver.resolve(OSASCRIPT_COMMAND),
196
+ this.#resolveVscodeCli(),
197
+ ]);
198
+ const terminalReady = claude !== null && osascript !== null;
199
+ const [iterm, warp, ghostty] = terminalReady
200
+ ? await Promise.all(["iterm", "warp", "ghostty"].map((app) => this.#isMacApplicationAvailable(app)))
201
+ : [false, false, false];
202
+ const availableTerminals = [
203
+ ...(terminalReady ? ["terminal"] : []),
204
+ ...(iterm ? ["iterm"] : []),
205
+ ...(warp ? ["warp"] : []),
206
+ ...(ghostty ? ["ghostty"] : []),
207
+ ...(code !== null ? ["vscode"] : []),
208
+ ];
209
+ if (availableTerminals.length > 0) {
210
+ return {
211
+ ok: true,
212
+ value: {
213
+ availableTerminals,
214
+ ...(code !== null ? { code } : {}),
215
+ ...(osascript !== null && claude !== null ? { osascript } : {}),
216
+ },
217
+ };
218
+ }
219
+ if (claude === null) {
220
+ return actionFailure("missing-command", undefined, CLAUDE_COMMAND);
221
+ }
222
+ return actionFailure("missing-command", undefined, OSASCRIPT_COMMAND);
93
223
  }
94
- return {
95
- ok: true,
96
- value: { osascript },
97
- };
224
+ const code = await this.#resolveVscodeCli();
225
+ if (code !== null) {
226
+ return {
227
+ ok: true,
228
+ value: { code, availableTerminals: ["vscode"] },
229
+ };
230
+ }
231
+ return actionFailure("unsupported-platform");
98
232
  }
99
233
  async launch(terminal, prototypePath, preflight) {
100
234
  const checked = preflight ?? (await this.preflight(terminal));
101
235
  if (!checked.ok)
102
236
  return checked;
237
+ if (terminal === "vscode") {
238
+ if (checked.value.code === undefined) {
239
+ return actionFailure("missing-command", terminal, CODE_COMMAND);
240
+ }
241
+ try {
242
+ await this.#process.execFile(checked.value.code, ["--new-window", prototypePath], {
243
+ timeout: APPLESCRIPT_TIMEOUT,
244
+ maxBuffer: APPLESCRIPT_MAX_BUFFER,
245
+ });
246
+ }
247
+ catch {
248
+ return actionFailure("launch-failed", terminal);
249
+ }
250
+ return {
251
+ ok: true,
252
+ value: { agent: "claude-code" },
253
+ };
254
+ }
255
+ if (checked.value.osascript === undefined) {
256
+ return actionFailure("missing-command", terminal, OSASCRIPT_COMMAND);
257
+ }
103
258
  const script = buildClaudeCodeAppleScript(terminal, prototypePath);
104
259
  try {
105
260
  await this.#process.execFile(checked.value.osascript, ["-e", script], {
@@ -1 +1 @@
1
- {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQ9D,OAAO,EACL,sBAAsB,GAEvB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC;AAChC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AACtC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,MAAM,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAAC;AA0C3C,MAAM,cAAc,GAA4B;IAC9C,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO;QAChC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,YAAY,CACV,UAAU,EACV,CAAC,GAAG,IAAI,CAAC,EACT,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAChC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;gBACxB,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;oBACzC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;gBACT,CAAC;gBAED,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9B,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC;AAEF,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,IAAI,KAAK;SACb,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC;SACtB,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC;SACvB,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;AAChC,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC;AAED,SAAS,cAAc,CAAC,aAAqB;IAC3C,OAAO,MAAM,WAAW,CAAC,aAAa,CAAC,OAAO,cAAc,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,QAAqB,EACrB,aAAqB;IAErB,MAAM,OAAO,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IAE9C,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,4CAA4C,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;QAClF,KAAK,OAAO;YACV,OAAO;gBACL,0BAA0B;gBAC1B,sCAAsC;gBACtC,0CAA0C;gBAC1C,kBAAkB,iBAAiB,CAAC,OAAO,CAAC,EAAE;gBAC9C,YAAY;gBACZ,UAAU;aACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,8BAA8B,kBAAkB,CAAC,aAAa,CAAC,EAAE,CAAC;YAC9E,OAAO;gBACL,mBAAmB,iBAAiB,CAAC,QAAQ,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;gBAClE,SAAS;gBACT,qCAAqC;gBACrC,iDAAiD,iBAAiB,CAAC,OAAO,CAAC,EAAE;gBAC7E,iDAAiD;aAClD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CACpB,IAAkE,EAClE,QAAsB,EACtB,OAAsB;IAEtB,MAAM,KAAK,GAAqB;QAC9B,IAAI;QACJ,KAAK,EAAE,aAAa;QACpB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClC,CAAC;IAEF,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK;KACsB,CAAC;AAChC,CAAC;AAED,MAAM,OAAO,kBAAkB;IACpB,mBAAmB,CAAyB;IAC5C,SAAS,CAAkB;IAC3B,QAAQ,CAA0B;IAE3C,YAAY,OAAO,GAA8B,EAAE;QACjD,IAAI,CAAC,mBAAmB;YACtB,OAAO,CAAC,kBAAkB,IAAI,IAAI,sBAAsB,EAAE,CAAC;QAC7D,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;QACtD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,IAAI,cAAc,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,QAAsB;QACpC,IAAI,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,aAAa,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACtE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,aAAa,CAAC,iBAAiB,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC5E,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,aAAa,CAAC,iBAAiB,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QACvE,CAAC;QAED,OAAO;YACL,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,EAAE,SAAS,EAAE;SACe,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,MAAM,CACV,QAAqB,EACrB,aAAqB,EACrB,SAAqC;QAErC,MAAM,OAAO,GAAG,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,OAAO,CAAC;QAEhC,MAAM,MAAM,GAAG,0BAA0B,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;gBACpE,OAAO,EAAE,mBAAmB;gBAC5B,SAAS,EAAE,sBAAsB;aAClC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,aAAa,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;QAClD,CAAC;QAED,OAAO;YACL,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE;SACJ,CAAC;IAChC,CAAC;CACF","sourcesContent":["import { execFile as nodeExecFile } from \"node:child_process\";\nimport {\n type AgentActionError,\n type AgentActionResult,\n type AgentCommand,\n type AgentLaunchValue,\n type TerminalApp,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\n\nconst CLAUDE_COMMAND = \"claude\";\nconst OSASCRIPT_COMMAND = \"osascript\";\nconst APPLESCRIPT_TIMEOUT = 10_000;\nconst APPLESCRIPT_MAX_BUFFER = 1024 * 1024;\n\nexport type ClaudeCodeExecOptions = {\n timeout: number;\n maxBuffer: number;\n};\n\nexport type ClaudeCodeExecResult = {\n stdout: string;\n stderr: string;\n};\n\nexport type ClaudeCodeProcessRunner = {\n execFile(\n executable: string,\n args: readonly string[],\n options: ClaudeCodeExecOptions,\n ): Promise<ClaudeCodeExecResult>;\n};\n\nexport type ClaudeCodePreflightValue = {\n osascript: string;\n};\n\nexport type ClaudeCodePreflightResult =\n AgentActionResult<ClaudeCodePreflightValue>;\n\nexport type ClaudeCodeLauncherOptions = {\n executableResolver?: ExecutableResolverLike;\n platform?: NodeJS.Platform;\n process?: ClaudeCodeProcessRunner;\n};\n\nexport interface ClaudeCodeLauncherLike {\n preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;\n launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ): Promise<AgentActionResult<AgentLaunchValue>>;\n}\n\nconst defaultProcess: ClaudeCodeProcessRunner = {\n execFile(executable, args, options) {\n return new Promise((resolve, reject) => {\n nodeExecFile(\n executable,\n [...args],\n { ...options, encoding: \"utf8\" },\n (error, stdout, stderr) => {\n if (error) {\n Object.assign(error, { stdout, stderr });\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n },\n );\n });\n },\n};\n\nfunction appleScriptString(value: string) {\n return `\"${value\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll('\"', '\\\\\"')\n .replaceAll(\"\\r\", \"\\\\r\")\n .replaceAll(\"\\n\", \"\\\\n\")}\"`;\n}\n\nfunction shellString(value: string) {\n return `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n\nfunction commandForPath(prototypePath: string) {\n return `cd ${shellString(prototypePath)} && ${CLAUDE_COMMAND}`;\n}\n\nexport function buildClaudeCodeAppleScript(\n terminal: TerminalApp,\n prototypePath: string,\n) {\n const command = commandForPath(prototypePath);\n\n switch (terminal) {\n case \"terminal\":\n return `tell application \"Terminal\" to do script ${appleScriptString(command)}`;\n case \"iterm\":\n return [\n 'tell application \"iTerm\"',\n \" create window with default profile\",\n \" tell current session of current window\",\n ` write text ${appleScriptString(command)}`,\n \" end tell\",\n \"end tell\",\n ].join(\"\\n\");\n case \"warp\": {\n const uri = `warp://action/new_tab?path=${encodeURIComponent(prototypePath)}`;\n return [\n `do shell script ${appleScriptString(`open ${shellString(uri)}`)}`,\n \"delay 1\",\n 'tell application \"Warp\" to activate',\n `tell application \"System Events\" to keystroke ${appleScriptString(command)}`,\n 'tell application \"System Events\" to key code 36',\n ].join(\"\\n\");\n }\n }\n}\n\nfunction actionFailure(\n code: \"missing-command\" | \"launch-failed\" | \"unsupported-platform\",\n terminal?: TerminalApp,\n command?: AgentCommand,\n) {\n const error: AgentActionError = {\n code,\n agent: \"claude-code\",\n ...(command ? { command } : {}),\n ...(terminal ? { terminal } : {}),\n };\n\n return {\n ok: false,\n error,\n } satisfies AgentActionResult;\n}\n\nexport class ClaudeCodeLauncher implements ClaudeCodeLauncherLike {\n readonly #executableResolver: ExecutableResolverLike;\n readonly #platform: NodeJS.Platform;\n readonly #process: ClaudeCodeProcessRunner;\n\n constructor(options: ClaudeCodeLauncherOptions = {}) {\n this.#executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#platform = options.platform ?? process.platform;\n this.#process = options.process ?? defaultProcess;\n }\n\n async preflight(terminal?: TerminalApp) {\n if (this.#platform !== \"darwin\") {\n return actionFailure(\"unsupported-platform\", terminal);\n }\n\n const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);\n if (claude === null) {\n return actionFailure(\"missing-command\", terminal, CLAUDE_COMMAND);\n }\n\n const osascript = await this.#executableResolver.resolve(OSASCRIPT_COMMAND);\n if (osascript === null) {\n return actionFailure(\"missing-command\", terminal, OSASCRIPT_COMMAND);\n }\n\n return {\n ok: true,\n value: { osascript },\n } satisfies ClaudeCodePreflightResult;\n }\n\n async launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ) {\n const checked = preflight ?? (await this.preflight(terminal));\n if (!checked.ok) return checked;\n\n const script = buildClaudeCodeAppleScript(terminal, prototypePath);\n try {\n await this.#process.execFile(checked.value.osascript, [\"-e\", script], {\n timeout: APPLESCRIPT_TIMEOUT,\n maxBuffer: APPLESCRIPT_MAX_BUFFER,\n });\n } catch {\n return actionFailure(\"launch-failed\", terminal);\n }\n\n return {\n ok: true,\n value: { agent: \"claude-code\" },\n } satisfies AgentActionResult;\n }\n}\n"]}
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as nodeExecFile } from \"node:child_process\";\nimport { access } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n type AgentActionError,\n type AgentActionResult,\n type AgentCommand,\n type AgentLaunchValue,\n type TerminalApp,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\n\nconst CLAUDE_COMMAND = \"claude\";\nconst CODE_COMMAND = \"code\";\nconst OSASCRIPT_COMMAND = \"osascript\";\nconst APPLESCRIPT_TIMEOUT = 10_000;\nconst APPLESCRIPT_MAX_BUFFER = 1024 * 1024;\n\nexport type ClaudeCodeExecOptions = {\n timeout: number;\n maxBuffer: number;\n};\n\nexport type ClaudeCodeExecResult = {\n stdout: string;\n stderr: string;\n};\n\nexport type ClaudeCodeProcessRunner = {\n execFile(\n executable: string,\n args: readonly string[],\n options: ClaudeCodeExecOptions,\n ): Promise<ClaudeCodeExecResult>;\n};\n\nexport type ClaudeCodePreflightValue = {\n /** The destinations detected during an unselected preflight. */\n availableTerminals?: readonly TerminalApp[];\n /** The macOS AppleScript runner, when a terminal app is selected. */\n osascript?: string;\n /** The VS Code CLI, when VS Code is available. */\n code?: string;\n};\n\nexport type ClaudeCodePreflightResult =\n AgentActionResult<ClaudeCodePreflightValue>;\n\nexport type ClaudeCodeLauncherOptions = {\n executableResolver?: ExecutableResolverLike;\n platform?: NodeJS.Platform;\n /** Home directory used when checking a user-installed VS Code app bundle. */\n home?: string;\n /** Application directories used when checking macOS terminal bundles. */\n macApplicationDirectories?: readonly string[];\n process?: ClaudeCodeProcessRunner;\n};\n\nexport interface ClaudeCodeLauncherLike {\n preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;\n launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ): Promise<AgentActionResult<AgentLaunchValue>>;\n}\n\nfunction bundledVscodeCliCandidates(home: string) {\n return [\n join(\n \"/Applications\",\n \"Visual Studio Code.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code\",\n ),\n join(\n home,\n \"Applications\",\n \"Visual Studio Code.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code\",\n ),\n join(\n \"/Applications\",\n \"Visual Studio Code - Insiders.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code-insiders\",\n ),\n join(\n home,\n \"Applications\",\n \"Visual Studio Code - Insiders.app\",\n \"Contents\",\n \"Resources\",\n \"app\",\n \"bin\",\n \"code-insiders\",\n ),\n ];\n}\n\nconst defaultProcess: ClaudeCodeProcessRunner = {\n execFile(executable, args, options) {\n return new Promise((resolve, reject) => {\n nodeExecFile(\n executable,\n [...args],\n { ...options, encoding: \"utf8\" },\n (error, stdout, stderr) => {\n if (error) {\n Object.assign(error, { stdout, stderr });\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n },\n );\n });\n },\n};\n\nfunction appleScriptString(value: string) {\n return `\"${value\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll('\"', '\\\\\"')\n .replaceAll(\"\\r\", \"\\\\r\")\n .replaceAll(\"\\n\", \"\\\\n\")}\"`;\n}\n\nfunction shellString(value: string) {\n return `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n\nfunction macApplicationBundleForTerminal(terminal: TerminalApp) {\n switch (terminal) {\n case \"iterm\":\n return \"iTerm.app\";\n case \"warp\":\n return \"Warp.app\";\n case \"ghostty\":\n return \"Ghostty.app\";\n default:\n return undefined;\n }\n}\n\nfunction commandForPath(prototypePath: string) {\n return `cd ${shellString(prototypePath)} && ${CLAUDE_COMMAND}`;\n}\n\nexport function buildClaudeCodeAppleScript(\n terminal: Exclude<TerminalApp, \"vscode\">,\n prototypePath: string,\n) {\n const command = commandForPath(prototypePath);\n\n switch (terminal) {\n case \"terminal\":\n return `tell application \"Terminal\" to do script ${appleScriptString(command)}`;\n case \"iterm\":\n return [\n 'tell application \"iTerm\"',\n \" create window with default profile\",\n \" tell current session of current window\",\n ` write text ${appleScriptString(command)}`,\n \" end tell\",\n \"end tell\",\n ].join(\"\\n\");\n case \"warp\": {\n const uri = `warp://action/new_tab?path=${encodeURIComponent(prototypePath)}`;\n return [\n `do shell script ${appleScriptString(`open ${shellString(uri)}`)}`,\n \"delay 1\",\n 'tell application \"Warp\" to activate',\n `tell application \"System Events\" to keystroke ${appleScriptString(command)}`,\n 'tell application \"System Events\" to key code 36',\n ].join(\"\\n\");\n }\n case \"ghostty\":\n return [\n 'tell application \"Ghostty\"',\n \" activate\",\n \" set config to new surface configuration\",\n ` set initial working directory of config to ${appleScriptString(prototypePath)}`,\n ` set initial input of config to ${appleScriptString(`${CLAUDE_COMMAND}\\n`)}`,\n \" new window with configuration config\",\n \"end tell\",\n ].join(\"\\n\");\n }\n}\n\nfunction actionFailure(\n code: \"missing-command\" | \"launch-failed\" | \"unsupported-platform\",\n terminal?: TerminalApp,\n command?: AgentCommand,\n) {\n const error: AgentActionError = {\n code,\n agent: \"claude-code\",\n ...(command ? { command } : {}),\n ...(terminal ? { terminal } : {}),\n };\n\n return {\n ok: false,\n error,\n } satisfies AgentActionResult;\n}\n\nexport class ClaudeCodeLauncher implements ClaudeCodeLauncherLike {\n readonly #executableResolver: ExecutableResolverLike;\n readonly #platform: NodeJS.Platform;\n readonly #home: string;\n readonly #macApplicationDirectories: readonly string[];\n readonly #process: ClaudeCodeProcessRunner;\n readonly #applicationAvailability = new Map<TerminalApp, Promise<boolean>>();\n\n constructor(options: ClaudeCodeLauncherOptions = {}) {\n this.#executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#platform = options.platform ?? process.platform;\n this.#home = options.home ?? homedir();\n this.#macApplicationDirectories = options.macApplicationDirectories ?? [\n \"/Applications\",\n join(this.#home, \"Applications\"),\n ];\n this.#process = options.process ?? defaultProcess;\n }\n\n async #resolveVscodeCli() {\n const onPath = await this.#executableResolver.resolve(CODE_COMMAND);\n if (onPath !== null) return onPath;\n if (this.#platform !== \"darwin\") return null;\n\n for (const candidate of bundledVscodeCliCandidates(this.#home)) {\n const bundled = await this.#executableResolver.resolve(candidate);\n if (bundled !== null) return bundled;\n }\n return null;\n }\n\n #isMacApplicationAvailable(terminal: TerminalApp) {\n const cached = this.#applicationAvailability.get(terminal);\n if (cached !== undefined) return cached;\n\n const application = macApplicationBundleForTerminal(terminal);\n const availability = (async () => {\n if (this.#platform !== \"darwin\" || application === undefined) {\n return false;\n }\n\n const candidates = this.#macApplicationDirectories.map((directory) =>\n join(directory, application),\n );\n for (const candidate of candidates) {\n try {\n await access(candidate);\n return true;\n } catch {\n // Continue with the next standard application location.\n }\n }\n return false;\n })();\n this.#applicationAvailability.set(terminal, availability);\n return availability;\n }\n\n async preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult> {\n if (terminal === \"vscode\") {\n const code = await this.#resolveVscodeCli();\n if (code === null) {\n return actionFailure(\"missing-command\", terminal, CODE_COMMAND);\n }\n return {\n ok: true,\n value: { code },\n } satisfies ClaudeCodePreflightResult;\n }\n\n if (terminal !== undefined && this.#platform !== \"darwin\") {\n return actionFailure(\"unsupported-platform\", terminal);\n }\n\n if (terminal !== undefined) {\n const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);\n if (claude === null) {\n return actionFailure(\"missing-command\", terminal, CLAUDE_COMMAND);\n }\n\n const osascript =\n await this.#executableResolver.resolve(OSASCRIPT_COMMAND);\n if (osascript === null) {\n return actionFailure(\"missing-command\", terminal, OSASCRIPT_COMMAND);\n }\n\n return {\n ok: true,\n value: { osascript },\n } satisfies ClaudeCodePreflightResult;\n }\n\n // An omitted terminal means that the UI still needs to offer a choice.\n // Keep the preflight successful when either the macOS terminal flow or the\n // VS Code flow is usable; the selected option is checked again below.\n if (this.#platform === \"darwin\") {\n const [claude, osascript, code] = await Promise.all([\n this.#executableResolver.resolve(CLAUDE_COMMAND),\n this.#executableResolver.resolve(OSASCRIPT_COMMAND),\n this.#resolveVscodeCli(),\n ]);\n const terminalReady = claude !== null && osascript !== null;\n const [iterm, warp, ghostty] = terminalReady\n ? await Promise.all(\n ([\"iterm\", \"warp\", \"ghostty\"] as const).map((app) =>\n this.#isMacApplicationAvailable(app),\n ),\n )\n : [false, false, false];\n const availableTerminals = [\n ...(terminalReady ? ([\"terminal\"] as const) : []),\n ...(iterm ? ([\"iterm\"] as const) : []),\n ...(warp ? ([\"warp\"] as const) : []),\n ...(ghostty ? ([\"ghostty\"] as const) : []),\n ...(code !== null ? ([\"vscode\"] as const) : []),\n ];\n if (availableTerminals.length > 0) {\n return {\n ok: true,\n value: {\n availableTerminals,\n ...(code !== null ? { code } : {}),\n ...(osascript !== null && claude !== null ? { osascript } : {}),\n },\n } satisfies ClaudeCodePreflightResult;\n }\n if (claude === null) {\n return actionFailure(\"missing-command\", undefined, CLAUDE_COMMAND);\n }\n return actionFailure(\"missing-command\", undefined, OSASCRIPT_COMMAND);\n }\n\n const code = await this.#resolveVscodeCli();\n if (code !== null) {\n return {\n ok: true,\n value: { code, availableTerminals: [\"vscode\"] },\n } satisfies ClaudeCodePreflightResult;\n }\n return actionFailure(\"unsupported-platform\");\n }\n\n async launch(\n terminal: TerminalApp,\n prototypePath: string,\n preflight?: ClaudeCodePreflightResult,\n ) {\n const checked = preflight ?? (await this.preflight(terminal));\n if (!checked.ok) return checked;\n\n if (terminal === \"vscode\") {\n if (checked.value.code === undefined) {\n return actionFailure(\"missing-command\", terminal, CODE_COMMAND);\n }\n\n try {\n await this.#process.execFile(\n checked.value.code,\n [\"--new-window\", prototypePath],\n {\n timeout: APPLESCRIPT_TIMEOUT,\n maxBuffer: APPLESCRIPT_MAX_BUFFER,\n },\n );\n } catch {\n return actionFailure(\"launch-failed\", terminal);\n }\n\n return {\n ok: true,\n value: { agent: \"claude-code\" },\n } satisfies AgentActionResult;\n }\n\n if (checked.value.osascript === undefined) {\n return actionFailure(\"missing-command\", terminal, OSASCRIPT_COMMAND);\n }\n\n const script = buildClaudeCodeAppleScript(terminal, prototypePath);\n try {\n await this.#process.execFile(checked.value.osascript, [\"-e\", script], {\n timeout: APPLESCRIPT_TIMEOUT,\n maxBuffer: APPLESCRIPT_MAX_BUFFER,\n });\n } catch {\n return actionFailure(\"launch-failed\", terminal);\n }\n\n return {\n ok: true,\n value: { agent: \"claude-code\" },\n } satisfies AgentActionResult;\n }\n}\n"]}
@@ -49,7 +49,7 @@ export interface GitRepository {
49
49
  status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;
50
50
  remotes(): Promise<readonly GitRemoteSnapshot[]>;
51
51
  branches(): Promise<GitBranchSnapshot>;
52
- fetch(remote: string, branch: string): Promise<void>;
52
+ fetch(remote: string, branch?: string): Promise<void>;
53
53
  checkout(args: readonly string[]): Promise<void>;
54
54
  checkoutLocalBranch(branch: string): Promise<void>;
55
55
  merge(args: readonly string[]): Promise<void>;