@ebay-dt/bonfire-node 0.1.0-alpha.0 → 0.1.0-alpha.2

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 (77) hide show
  1. package/README.md +77 -4
  2. package/dist/agent-service.d.ts +42 -0
  3. package/dist/agent-service.d.ts.map +1 -0
  4. package/dist/agent-service.js +116 -0
  5. package/dist/agent-service.js.map +1 -0
  6. package/dist/claude.d.ts +50 -0
  7. package/dist/claude.d.ts.map +1 -0
  8. package/dist/claude.js +119 -0
  9. package/dist/claude.js.map +1 -0
  10. package/dist/cursor.d.ts +30 -0
  11. package/dist/cursor.d.ts.map +1 -0
  12. package/dist/cursor.js +57 -0
  13. package/dist/cursor.js.map +1 -0
  14. package/dist/deployment-provider.d.ts +32 -0
  15. package/dist/deployment-provider.d.ts.map +1 -0
  16. package/dist/deployment-provider.js +167 -0
  17. package/dist/deployment-provider.js.map +1 -0
  18. package/dist/duplicate.d.ts +3 -2
  19. package/dist/duplicate.d.ts.map +1 -1
  20. package/dist/duplicate.js +18 -18
  21. package/dist/duplicate.js.map +1 -1
  22. package/dist/executable.d.ts +16 -0
  23. package/dist/executable.d.ts.map +1 -0
  24. package/dist/executable.js +65 -0
  25. package/dist/executable.js.map +1 -0
  26. package/dist/git-repository.d.ts.map +1 -1
  27. package/dist/git-repository.js.map +1 -1
  28. package/dist/github-cli.d.ts.map +1 -1
  29. package/dist/github-cli.js +23 -3
  30. package/dist/github-cli.js.map +1 -1
  31. package/dist/index.d.ts +7 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +7 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/internal/owned-destination.d.ts +2 -2
  36. package/dist/internal/owned-destination.d.ts.map +1 -1
  37. package/dist/internal/owned-destination.js +2 -2
  38. package/dist/internal/owned-destination.js.map +1 -1
  39. package/dist/internal/prototype-templates.d.ts +3 -2
  40. package/dist/internal/prototype-templates.d.ts.map +1 -1
  41. package/dist/internal/prototype-templates.js +13 -8
  42. package/dist/internal/prototype-templates.js.map +1 -1
  43. package/dist/internal/utils.d.ts +2 -0
  44. package/dist/internal/utils.d.ts.map +1 -1
  45. package/dist/internal/utils.js +6 -0
  46. package/dist/internal/utils.js.map +1 -1
  47. package/dist/lifecycle/dirty-state.d.ts.map +1 -1
  48. package/dist/lifecycle/dirty-state.js.map +1 -1
  49. package/dist/lifecycle/local-prototype-lifecycle.d.ts +22 -10
  50. package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
  51. package/dist/lifecycle/local-prototype-lifecycle.js +255 -6
  52. package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
  53. package/dist/lifecycle/operation-gate.d.ts.map +1 -1
  54. package/dist/lifecycle/operation-gate.js.map +1 -1
  55. package/dist/lifecycle/runtime-context.d.ts.map +1 -1
  56. package/dist/lifecycle/runtime-context.js.map +1 -1
  57. package/dist/preferences.d.ts +19 -0
  58. package/dist/preferences.d.ts.map +1 -0
  59. package/dist/preferences.js +182 -0
  60. package/dist/preferences.js.map +1 -0
  61. package/dist/prepare-prototype-files.d.ts.map +1 -1
  62. package/dist/prepare-prototype-files.js.map +1 -1
  63. package/dist/process-runner.d.ts.map +1 -1
  64. package/dist/process-runner.js.map +1 -1
  65. package/dist/prototype-catalog.d.ts.map +1 -1
  66. package/dist/prototype-catalog.js +18 -7
  67. package/dist/prototype-catalog.js.map +1 -1
  68. package/dist/prototype-paths.d.ts +2 -0
  69. package/dist/prototype-paths.d.ts.map +1 -1
  70. package/dist/prototype-paths.js.map +1 -1
  71. package/dist/scaffold.d.ts +4 -2
  72. package/dist/scaffold.d.ts.map +1 -1
  73. package/dist/scaffold.js +7 -12
  74. package/dist/scaffold.js.map +1 -1
  75. package/dist/watcher.d.ts.map +1 -1
  76. package/dist/watcher.js.map +1 -1
  77. package/package.json +3 -3
package/README.md CHANGED
@@ -28,6 +28,48 @@ A competing mutation returns `repository-busy` immediately rather than waiting.
28
28
  The gate covers the supported local Node process only; coordination across
29
29
  multiple processes or machines is not provided.
30
30
 
31
+ ## Deployment provider
32
+
33
+ Deployment is an optional development capability. Configure the generic provider
34
+ only in a server-only host module with a complete HTTP endpoint; never expose the
35
+ endpoint or provider to browser code:
36
+
37
+ ```ts
38
+ import { createDeploymentProvider } from "@ebay-dt/bonfire-node";
39
+
40
+ const deploymentProvider = createDeploymentProvider({
41
+ endpoint: process.env.BONFIRE_DEPLOYMENT_ENDPOINT!,
42
+ timeoutMs: 120_000,
43
+ });
44
+ ```
45
+
46
+ The provider sends exactly `{ applicationId, login }`. Bonfire supplies the
47
+ application ID from trusted application configuration and the login from the
48
+ verified authenticated identity; instances never provide owner/branch values.
49
+ The application ID must exactly match the application's DT deployment-service
50
+ registration; Bonfire does not validate that registration locally. A mismatch
51
+ is returned as a safe deployment failure rather than exposing provider details.
52
+ The documented success response is returned with HTTP 201 and contains
53
+ `repository`, `ref`, `commitSha`, `sourceFileCount`, `project`, `deployed`,
54
+ `deploymentId`, `url`, and `readyState`. `readyState` is one of `BLOCKED`,
55
+ `BUILDING`, `CANCELED`, `ERROR`, `INITIALIZING`, `QUEUED`, or `READY`. HTTP 201
56
+ means deployment creation succeeded, not that the preview is ready. A failure
57
+ response contains `repository`, optional `ref`, `commitSha`, `project`, and
58
+ `stage`, plus `deployed: false`, `statusCode`, and `details`; those diagnostics
59
+ are validated and then intentionally hidden behind a safe provider error.
60
+
61
+ The provider preserves the deployment ID and reported ready state, but Bonfire
62
+ does not poll for later status. Provider failures are reduced to safe typed
63
+ lifecycle errors; response diagnostics, credentials, Vercel credentials, and
64
+ deployment-target selection remain outside Bonfire in the deployment service.
65
+ If the deployment service exposes a registration-specific status, hosts may
66
+ map it to a generic message such as “Deployment is not configured for this
67
+ application.” They should not infer that classification from an arbitrary
68
+ failure or forward provider response bodies, endpoint details, repository or
69
+ branch data, or filesystem diagnostics to the browser. The provider validates the endpoint,
70
+ response, HTTPS preview URL, and timeout without exposing service details to the
71
+ browser.
72
+
31
73
  ## Prototype shape and duplication
32
74
 
33
75
  Duplicate sources must contain valid `metadata.json` plus regular,
@@ -68,10 +110,13 @@ completed destination. The default callback is a no-op.
68
110
  prototype reference, then returns a `clean` or `dirty` observation based only on
69
111
  Git status within that prototype path. `LocalPrototypeLifecycle.save` creates a
70
112
  local, path-scoped checkpoint without pushing; `.discard` restores tracked
71
- files and removes untracked files within that path. `LocalPrototypeLifecycle.create`
72
- and `.duplicate` return typed results and serialize repository mutations through
73
- one process-local operation gate. They
74
- prepare the authenticated identity branch when automatic switching is enabled;
113
+ files and removes untracked files within that path. `LocalPrototypeLifecycle.create`,
114
+ `.duplicate`, `.edit`, and `.delete` return typed results and serialize repository
115
+ mutations through one process-local operation gate. Edit updates only title,
116
+ description, and `updatedAt` while preserving the existing ref/URL. Delete
117
+ removes the complete owned prototype directory, including uncommitted files,
118
+ after ownership and path validation. These operations prepare the authenticated
119
+ identity branch when automatic switching is enabled;
75
120
  `BF_DISABLE_AUTO_BRANCH_SWITCH=true` disables every automatic switch and leaves
76
121
  the current branch untouched. Git remains behind the Node-owned repository
77
122
  adapter, and `simple-git` is not part of the public API.
@@ -87,6 +132,34 @@ debounces filesystem events, and reports only clean/dirty transitions. The
87
132
  watcher subpath is intentionally separate so importing `@ebay-dt/bonfire-node` does
88
133
  not load Chokidar.
89
134
 
135
+ ## Agent launch preferences
136
+
137
+ `PrototypeAgentService` stores a user's selected Claude Code terminal at
138
+ `<prototypeRoot>/<login>/preferences.json`, using the explicit runtime context
139
+ provided by the host. This is a local preference file, not prototype source:
140
+ consuming applications must add their prototype-root preference pattern to
141
+ `.gitignore`, for example:
142
+
143
+ ```gitignore
144
+ /src/app/(prototypes)/*/preferences.json
145
+ ```
146
+
147
+ The service writes the file atomically and only after authenticated ownership
148
+ and command preflight have succeeded. Cursor launching requires `cursor` on the
149
+ Bonfire server process's inherited `PATH`. Claude Code launching requires
150
+ `claude` and, on macOS, `osascript` on that `PATH`; the service checks these
151
+ commands without executing them. GUI-launched Node processes may have a
152
+ different `PATH` from an interactive shell, so restart Bonfire after changing
153
+ shell profiles.
154
+
155
+ Claude Code supports the explicit terminal values `terminal` (Terminal.app),
156
+ `iterm` (iTerm2), and `warp` (Warp). Unsupported platforms, missing commands,
157
+ missing preferences, ownership failures, and launch failures are returned as
158
+ safe typed results; raw process output and absolute paths are never public
159
+ results. No skill, clipboard fallback, or copy/paste setup flow is involved.
160
+ Production hosts must not construct the service or expose its local preference
161
+ store.
162
+
90
163
  From the repository root, use Turbo filters so package dependencies are
91
164
  prepared in dependency order:
92
165
 
@@ -0,0 +1,42 @@
1
+ import { type AgentActionError, type AgentActionResult } from "@ebay-dt/bonfire-core";
2
+ import { type ClaudeCodeLauncherLike } from "./claude.js";
3
+ import { type CursorLauncherLike } from "./cursor.js";
4
+ import { type ExecutableResolverLike } from "./executable.js";
5
+ import { type PrototypePreferencesLike } from "./preferences.js";
6
+ import type { PrototypeCatalogLike } from "./prototype-catalog.js";
7
+ import type { PrototypeLifecycleRuntimeContext } from "./lifecycle/runtime-context.js";
8
+ export type PrototypeAgentServiceOptions = {
9
+ runtime: Pick<PrototypeLifecycleRuntimeContext, "prototypeRoot">;
10
+ catalog: PrototypeCatalogLike;
11
+ executableResolver?: ExecutableResolverLike;
12
+ preferences?: PrototypePreferencesLike;
13
+ cursor?: CursorLauncherLike;
14
+ claudeCode?: ClaudeCodeLauncherLike;
15
+ };
16
+ export interface PrototypeAgentServiceLike {
17
+ openInCursor(identity: unknown, input: unknown): Promise<AgentActionResult>;
18
+ openInClaudeCode(identity: unknown, input: unknown): Promise<AgentActionResult>;
19
+ }
20
+ export declare class PrototypeAgentService implements PrototypeAgentServiceLike {
21
+ #private;
22
+ constructor(options: PrototypeAgentServiceOptions);
23
+ openInCursor(identity: unknown, input: unknown): Promise<{
24
+ ok: true;
25
+ value: import("@ebay-dt/bonfire-core").AgentLaunchValue;
26
+ } | {
27
+ ok: false;
28
+ error: AgentActionError;
29
+ }>;
30
+ openInClaudeCode(identity: unknown, input: unknown): Promise<{
31
+ ok: true;
32
+ value: import("@ebay-dt/bonfire-core").AgentLaunchValue;
33
+ } | {
34
+ ok: false;
35
+ error: {
36
+ agent?: import("@ebay-dt/bonfire-core").Agent;
37
+ terminal?: import("@ebay-dt/bonfire-core").TerminalApp;
38
+ code: import("@ebay-dt/bonfire-core").AgentActionErrorCode;
39
+ };
40
+ }>;
41
+ }
42
+ //# sourceMappingURL=agent-service.d.ts.map
@@ -0,0 +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"]}
@@ -0,0 +1,116 @@
1
+ import { join } from "node:path";
2
+ import { definePrototypeRef, parseBonfireIdentity, parseOpenInAgentInput, parseOpenInClaudeCodeInput, } from "@ebay-dt/bonfire-core";
3
+ import { ClaudeCodeLauncher } from "./claude.js";
4
+ import { CursorLauncher } from "./cursor.js";
5
+ import { PathExecutableResolver, } from "./executable.js";
6
+ import { PrototypePreferences, } from "./preferences.js";
7
+ function failure(code, extras = {}) {
8
+ return {
9
+ ok: false,
10
+ error: { code, ...extras },
11
+ };
12
+ }
13
+ function authenticate(identity) {
14
+ if (identity === null || identity === undefined)
15
+ return null;
16
+ try {
17
+ const parsed = parseBonfireIdentity(identity);
18
+ definePrototypeRef({ login: parsed.login, slug: "prototype" });
19
+ return parsed;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export class PrototypeAgentService {
26
+ #runtime;
27
+ #catalog;
28
+ #preferences;
29
+ #cursor;
30
+ #claudeCode;
31
+ constructor(options) {
32
+ const executableResolver = options.executableResolver ?? new PathExecutableResolver();
33
+ this.#runtime = options.runtime;
34
+ this.#catalog = options.catalog;
35
+ this.#preferences =
36
+ options.preferences ?? new PrototypePreferences(options.runtime);
37
+ this.#cursor = options.cursor ?? new CursorLauncher({ executableResolver });
38
+ this.#claudeCode =
39
+ options.claudeCode ?? new ClaudeCodeLauncher({ executableResolver });
40
+ }
41
+ async #authorize(identity, input, parse) {
42
+ const authenticated = authenticate(identity);
43
+ if (authenticated === null) {
44
+ return { ok: false, result: failure("unauthenticated") };
45
+ }
46
+ let normalizedInput;
47
+ try {
48
+ normalizedInput = parse(input);
49
+ }
50
+ catch {
51
+ return { ok: false, result: failure("invalid-input") };
52
+ }
53
+ let summary;
54
+ try {
55
+ summary = await this.#catalog.getSummary(normalizedInput.ref);
56
+ }
57
+ catch {
58
+ summary = null;
59
+ }
60
+ if (summary === null) {
61
+ return { ok: false, result: failure("not-found") };
62
+ }
63
+ if (summary.authorLogin !== authenticated.login) {
64
+ return { ok: false, result: failure("forbidden") };
65
+ }
66
+ return {
67
+ ok: true,
68
+ identity: authenticated,
69
+ input: normalizedInput,
70
+ prototypePath: join(this.#runtime.prototypeRoot, normalizedInput.ref.login, normalizedInput.ref.slug),
71
+ };
72
+ }
73
+ async openInCursor(identity, input) {
74
+ const authorized = await this.#authorize(identity, input, parseOpenInAgentInput);
75
+ if (!authorized.ok)
76
+ return authorized.result;
77
+ return this.#cursor.launch(authorized.prototypePath);
78
+ }
79
+ async openInClaudeCode(identity, input) {
80
+ const authorized = await this.#authorize(identity, input, parseOpenInClaudeCodeInput);
81
+ if (!authorized.ok)
82
+ return authorized.result;
83
+ const preflight = await this.#claudeCode.preflight(authorized.input.terminal);
84
+ if (!preflight.ok)
85
+ return preflight;
86
+ let terminal = authorized.input.terminal;
87
+ if (terminal === undefined) {
88
+ let preferences;
89
+ try {
90
+ preferences = await this.#preferences.read(authorized.identity.login);
91
+ }
92
+ catch {
93
+ return failure("launch-failed", { agent: "claude-code" });
94
+ }
95
+ terminal = preferences.preferredTerminal;
96
+ if (terminal === undefined) {
97
+ return failure("needs-terminal", { agent: "claude-code" });
98
+ }
99
+ }
100
+ else {
101
+ try {
102
+ await this.#preferences.update(authorized.identity.login, {
103
+ preferredTerminal: terminal,
104
+ });
105
+ }
106
+ catch {
107
+ return failure("launch-failed", {
108
+ agent: "claude-code",
109
+ terminal,
110
+ });
111
+ }
112
+ }
113
+ return this.#claudeCode.launch(terminal, authorized.prototypePath, preflight);
114
+ }
115
+ }
116
+ //# sourceMappingURL=agent-service.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,50 @@
1
+ import { type AgentActionError, type AgentActionResult, type AgentLaunchValue, type TerminalApp } from "@ebay-dt/bonfire-core";
2
+ import { type ExecutableResolverLike } from "./executable.js";
3
+ export type ClaudeCodeExecOptions = {
4
+ timeout: number;
5
+ maxBuffer: number;
6
+ };
7
+ export type ClaudeCodeExecResult = {
8
+ stdout: string;
9
+ stderr: string;
10
+ };
11
+ export type ClaudeCodeProcessRunner = {
12
+ execFile(executable: string, args: readonly string[], options: ClaudeCodeExecOptions): Promise<ClaudeCodeExecResult>;
13
+ };
14
+ export type ClaudeCodePreflightValue = {
15
+ osascript: string;
16
+ };
17
+ export type ClaudeCodePreflightResult = AgentActionResult<ClaudeCodePreflightValue>;
18
+ export type ClaudeCodeLauncherOptions = {
19
+ executableResolver?: ExecutableResolverLike;
20
+ platform?: NodeJS.Platform;
21
+ process?: ClaudeCodeProcessRunner;
22
+ };
23
+ export interface ClaudeCodeLauncherLike {
24
+ preflight(terminal?: TerminalApp): Promise<ClaudeCodePreflightResult>;
25
+ launch(terminal: TerminalApp, prototypePath: string, preflight?: ClaudeCodePreflightResult): Promise<AgentActionResult<AgentLaunchValue>>;
26
+ }
27
+ export declare function buildClaudeCodeAppleScript(terminal: TerminalApp, prototypePath: string): string;
28
+ export declare class ClaudeCodeLauncher implements ClaudeCodeLauncherLike {
29
+ #private;
30
+ 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
+ launch(terminal: TerminalApp, prototypePath: string, preflight?: ClaudeCodePreflightResult): Promise<{
41
+ ok: false;
42
+ error: AgentActionError;
43
+ } | {
44
+ ok: true;
45
+ value: {
46
+ agent: "claude-code";
47
+ };
48
+ }>;
49
+ }
50
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +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"]}
package/dist/claude.js ADDED
@@ -0,0 +1,119 @@
1
+ import { execFile as nodeExecFile } from "node:child_process";
2
+ import { PathExecutableResolver, } from "./executable.js";
3
+ const CLAUDE_COMMAND = "claude";
4
+ const OSASCRIPT_COMMAND = "osascript";
5
+ const APPLESCRIPT_TIMEOUT = 10_000;
6
+ const APPLESCRIPT_MAX_BUFFER = 1024 * 1024;
7
+ const defaultProcess = {
8
+ execFile(executable, args, options) {
9
+ return new Promise((resolve, reject) => {
10
+ nodeExecFile(executable, [...args], { ...options, encoding: "utf8" }, (error, stdout, stderr) => {
11
+ if (error) {
12
+ Object.assign(error, { stdout, stderr });
13
+ reject(error);
14
+ return;
15
+ }
16
+ resolve({ stdout, stderr });
17
+ });
18
+ });
19
+ },
20
+ };
21
+ function appleScriptString(value) {
22
+ return `"${value
23
+ .replaceAll("\\", "\\\\")
24
+ .replaceAll('"', '\\"')
25
+ .replaceAll("\r", "\\r")
26
+ .replaceAll("\n", "\\n")}"`;
27
+ }
28
+ function shellString(value) {
29
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
30
+ }
31
+ function commandForPath(prototypePath) {
32
+ return `cd ${shellString(prototypePath)} && ${CLAUDE_COMMAND}`;
33
+ }
34
+ export function buildClaudeCodeAppleScript(terminal, prototypePath) {
35
+ const command = commandForPath(prototypePath);
36
+ switch (terminal) {
37
+ case "terminal":
38
+ return `tell application "Terminal" to do script ${appleScriptString(command)}`;
39
+ case "iterm":
40
+ return [
41
+ 'tell application "iTerm"',
42
+ " create window with default profile",
43
+ " tell current session of current window",
44
+ ` write text ${appleScriptString(command)}`,
45
+ " end tell",
46
+ "end tell",
47
+ ].join("\n");
48
+ case "warp": {
49
+ const uri = `warp://action/new_tab?path=${encodeURIComponent(prototypePath)}`;
50
+ return [
51
+ `do shell script ${appleScriptString(`open ${shellString(uri)}`)}`,
52
+ "delay 1",
53
+ 'tell application "Warp" to activate',
54
+ `tell application "System Events" to keystroke ${appleScriptString(command)}`,
55
+ 'tell application "System Events" to key code 36',
56
+ ].join("\n");
57
+ }
58
+ }
59
+ }
60
+ function actionFailure(code, terminal, command) {
61
+ const error = {
62
+ code,
63
+ agent: "claude-code",
64
+ ...(command ? { command } : {}),
65
+ ...(terminal ? { terminal } : {}),
66
+ };
67
+ return {
68
+ ok: false,
69
+ error,
70
+ };
71
+ }
72
+ export class ClaudeCodeLauncher {
73
+ #executableResolver;
74
+ #platform;
75
+ #process;
76
+ constructor(options = {}) {
77
+ this.#executableResolver =
78
+ options.executableResolver ?? new PathExecutableResolver();
79
+ this.#platform = options.platform ?? process.platform;
80
+ this.#process = options.process ?? defaultProcess;
81
+ }
82
+ async preflight(terminal) {
83
+ if (this.#platform !== "darwin") {
84
+ return actionFailure("unsupported-platform", terminal);
85
+ }
86
+ const claude = await this.#executableResolver.resolve(CLAUDE_COMMAND);
87
+ if (claude === null) {
88
+ return actionFailure("missing-command", terminal, CLAUDE_COMMAND);
89
+ }
90
+ const osascript = await this.#executableResolver.resolve(OSASCRIPT_COMMAND);
91
+ if (osascript === null) {
92
+ return actionFailure("missing-command", terminal, OSASCRIPT_COMMAND);
93
+ }
94
+ return {
95
+ ok: true,
96
+ value: { osascript },
97
+ };
98
+ }
99
+ async launch(terminal, prototypePath, preflight) {
100
+ const checked = preflight ?? (await this.preflight(terminal));
101
+ if (!checked.ok)
102
+ return checked;
103
+ const script = buildClaudeCodeAppleScript(terminal, prototypePath);
104
+ try {
105
+ await this.#process.execFile(checked.value.osascript, ["-e", script], {
106
+ timeout: APPLESCRIPT_TIMEOUT,
107
+ maxBuffer: APPLESCRIPT_MAX_BUFFER,
108
+ });
109
+ }
110
+ catch {
111
+ return actionFailure("launch-failed", terminal);
112
+ }
113
+ return {
114
+ ok: true,
115
+ value: { agent: "claude-code" },
116
+ };
117
+ }
118
+ }
119
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
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 {\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"]}
@@ -0,0 +1,30 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import { type AgentActionResult, type AgentLaunchValue } from "@ebay-dt/bonfire-core";
3
+ import { type ExecutableResolverLike } from "./executable.js";
4
+ export type CursorSpawnOptions = {
5
+ detached: true;
6
+ shell: false;
7
+ stdio: "ignore";
8
+ };
9
+ export type CursorChildProcess = Pick<ChildProcess, "once" | "unref">;
10
+ export type CursorSpawn = (executable: string, args: readonly string[], options: CursorSpawnOptions) => CursorChildProcess;
11
+ export type CursorLauncherOptions = {
12
+ executableResolver?: ExecutableResolverLike;
13
+ spawn?: CursorSpawn;
14
+ };
15
+ export interface CursorLauncherLike {
16
+ launch(prototypePath: string): Promise<AgentActionResult<AgentLaunchValue>>;
17
+ }
18
+ export declare class CursorLauncher implements CursorLauncherLike {
19
+ #private;
20
+ constructor(options?: CursorLauncherOptions);
21
+ launch(prototypePath: string): Promise<{
22
+ ok: false;
23
+ error: {
24
+ code: "launch-failed";
25
+ agent: "cursor";
26
+ command: "cursor";
27
+ };
28
+ } | AgentActionResult>;
29
+ }
30
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,iBAAiB,CAAC;AAIzB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,IAAI,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,QAAQ,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAEtE,MAAM,MAAM,WAAW,GAAG,CACxB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,kBAAkB,KACxB,kBAAkB,CAAC;AAExB,MAAM,MAAM,qBAAqB,GAAG;IAClC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAC;CAC7E;AAgBD,qBAAa,cAAe,YAAW,kBAAkB;;IAIvD,YAAY,OAAO,GAAE,qBAA0B,EAI9C;IAEK,MAAM,CAAC,aAAa,EAAE,MAAM;;;;;;;2BAiCjC;CACF","sourcesContent":["import { spawn as nodeSpawn, type ChildProcess } from \"node:child_process\";\nimport {\n type AgentActionResult,\n type AgentLaunchValue,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\n\nconst CURSOR_COMMAND = \"cursor\";\n\nexport type CursorSpawnOptions = {\n detached: true;\n shell: false;\n stdio: \"ignore\";\n};\n\nexport type CursorChildProcess = Pick<ChildProcess, \"once\" | \"unref\">;\n\nexport type CursorSpawn = (\n executable: string,\n args: readonly string[],\n options: CursorSpawnOptions,\n) => CursorChildProcess;\n\nexport type CursorLauncherOptions = {\n executableResolver?: ExecutableResolverLike;\n spawn?: CursorSpawn;\n};\n\nexport interface CursorLauncherLike {\n launch(prototypePath: string): Promise<AgentActionResult<AgentLaunchValue>>;\n}\n\nconst defaultSpawn: CursorSpawn = (executable, args, options) =>\n nodeSpawn(executable, [...args], options);\n\nfunction launchFailure() {\n return {\n ok: false,\n error: {\n code: \"launch-failed\",\n agent: \"cursor\",\n command: CURSOR_COMMAND,\n },\n } satisfies AgentActionResult;\n}\n\nexport class CursorLauncher implements CursorLauncherLike {\n readonly #executableResolver: ExecutableResolverLike;\n readonly #spawn: CursorSpawn;\n\n constructor(options: CursorLauncherOptions = {}) {\n this.#executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#spawn = options.spawn ?? defaultSpawn;\n }\n\n async launch(prototypePath: string) {\n const executable = await this.#executableResolver.resolve(CURSOR_COMMAND);\n if (executable === null) {\n return {\n ok: false,\n error: {\n code: \"missing-command\",\n agent: \"cursor\",\n command: CURSOR_COMMAND,\n },\n } satisfies AgentActionResult;\n }\n\n let child: CursorChildProcess;\n try {\n child = this.#spawn(executable, [prototypePath], {\n detached: true,\n shell: false,\n stdio: \"ignore\",\n });\n child.unref();\n } catch {\n return launchFailure();\n }\n\n return new Promise<AgentActionResult>((resolve) => {\n child.once(\"spawn\", () => {\n resolve({ ok: true, value: { agent: \"cursor\" } });\n });\n child.once(\"error\", () => {\n resolve(launchFailure());\n });\n });\n }\n}\n"]}
package/dist/cursor.js ADDED
@@ -0,0 +1,57 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ import { PathExecutableResolver, } from "./executable.js";
3
+ const CURSOR_COMMAND = "cursor";
4
+ const defaultSpawn = (executable, args, options) => nodeSpawn(executable, [...args], options);
5
+ function launchFailure() {
6
+ return {
7
+ ok: false,
8
+ error: {
9
+ code: "launch-failed",
10
+ agent: "cursor",
11
+ command: CURSOR_COMMAND,
12
+ },
13
+ };
14
+ }
15
+ export class CursorLauncher {
16
+ #executableResolver;
17
+ #spawn;
18
+ constructor(options = {}) {
19
+ this.#executableResolver =
20
+ options.executableResolver ?? new PathExecutableResolver();
21
+ this.#spawn = options.spawn ?? defaultSpawn;
22
+ }
23
+ async launch(prototypePath) {
24
+ const executable = await this.#executableResolver.resolve(CURSOR_COMMAND);
25
+ if (executable === null) {
26
+ return {
27
+ ok: false,
28
+ error: {
29
+ code: "missing-command",
30
+ agent: "cursor",
31
+ command: CURSOR_COMMAND,
32
+ },
33
+ };
34
+ }
35
+ let child;
36
+ try {
37
+ child = this.#spawn(executable, [prototypePath], {
38
+ detached: true,
39
+ shell: false,
40
+ stdio: "ignore",
41
+ });
42
+ child.unref();
43
+ }
44
+ catch {
45
+ return launchFailure();
46
+ }
47
+ return new Promise((resolve) => {
48
+ child.once("spawn", () => {
49
+ resolve({ ok: true, value: { agent: "cursor" } });
50
+ });
51
+ child.once("error", () => {
52
+ resolve(launchFailure());
53
+ });
54
+ });
55
+ }
56
+ }
57
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAqB,MAAM,oBAAoB,CAAC;AAK3E,OAAO,EACL,sBAAsB,GAEvB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC;AAyBhC,MAAM,YAAY,GAAgB,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAC9D,SAAS,CAAC,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAE5C,SAAS,aAAa;IACpB,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE;YACL,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,QAAQ;YACf,OAAO,EAAE,cAAc;SACxB;KAC0B,CAAC;AAChC,CAAC;AAED,MAAM,OAAO,cAAc;IAChB,mBAAmB,CAAyB;IAC5C,MAAM,CAAc;IAE7B,YAAY,OAAO,GAA0B,EAAE;QAC7C,IAAI,CAAC,mBAAmB;YACtB,OAAO,CAAC,kBAAkB,IAAI,IAAI,sBAAsB,EAAE,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,aAAqB;QAChC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC1E,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE;oBACL,IAAI,EAAE,iBAAiB;oBACvB,KAAK,EAAE,QAAQ;oBACf,OAAO,EAAE,cAAc;iBACxB;aAC0B,CAAC;QAChC,CAAC;QAED,IAAI,KAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,aAAa,CAAC,EAAE;gBAC/C,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,QAAQ;aAChB,CAAC,CAAC;YACH,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,aAAa,EAAE,CAAC;QACzB,CAAC;QAED,OAAO,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,EAAE;YAChD,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { spawn as nodeSpawn, type ChildProcess } from \"node:child_process\";\nimport {\n type AgentActionResult,\n type AgentLaunchValue,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n PathExecutableResolver,\n type ExecutableResolverLike,\n} from \"./executable.js\";\n\nconst CURSOR_COMMAND = \"cursor\";\n\nexport type CursorSpawnOptions = {\n detached: true;\n shell: false;\n stdio: \"ignore\";\n};\n\nexport type CursorChildProcess = Pick<ChildProcess, \"once\" | \"unref\">;\n\nexport type CursorSpawn = (\n executable: string,\n args: readonly string[],\n options: CursorSpawnOptions,\n) => CursorChildProcess;\n\nexport type CursorLauncherOptions = {\n executableResolver?: ExecutableResolverLike;\n spawn?: CursorSpawn;\n};\n\nexport interface CursorLauncherLike {\n launch(prototypePath: string): Promise<AgentActionResult<AgentLaunchValue>>;\n}\n\nconst defaultSpawn: CursorSpawn = (executable, args, options) =>\n nodeSpawn(executable, [...args], options);\n\nfunction launchFailure() {\n return {\n ok: false,\n error: {\n code: \"launch-failed\",\n agent: \"cursor\",\n command: CURSOR_COMMAND,\n },\n } satisfies AgentActionResult;\n}\n\nexport class CursorLauncher implements CursorLauncherLike {\n readonly #executableResolver: ExecutableResolverLike;\n readonly #spawn: CursorSpawn;\n\n constructor(options: CursorLauncherOptions = {}) {\n this.#executableResolver =\n options.executableResolver ?? new PathExecutableResolver();\n this.#spawn = options.spawn ?? defaultSpawn;\n }\n\n async launch(prototypePath: string) {\n const executable = await this.#executableResolver.resolve(CURSOR_COMMAND);\n if (executable === null) {\n return {\n ok: false,\n error: {\n code: \"missing-command\",\n agent: \"cursor\",\n command: CURSOR_COMMAND,\n },\n } satisfies AgentActionResult;\n }\n\n let child: CursorChildProcess;\n try {\n child = this.#spawn(executable, [prototypePath], {\n detached: true,\n shell: false,\n stdio: \"ignore\",\n });\n child.unref();\n } catch {\n return launchFailure();\n }\n\n return new Promise<AgentActionResult>((resolve) => {\n child.once(\"spawn\", () => {\n resolve({ ok: true, value: { agent: \"cursor\" } });\n });\n child.once(\"error\", () => {\n resolve(launchFailure());\n });\n });\n }\n}\n"]}