@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.
- package/README.md +77 -4
- package/dist/agent-service.d.ts +42 -0
- package/dist/agent-service.d.ts.map +1 -0
- package/dist/agent-service.js +116 -0
- package/dist/agent-service.js.map +1 -0
- package/dist/claude.d.ts +50 -0
- package/dist/claude.d.ts.map +1 -0
- package/dist/claude.js +119 -0
- package/dist/claude.js.map +1 -0
- package/dist/cursor.d.ts +30 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +57 -0
- package/dist/cursor.js.map +1 -0
- package/dist/deployment-provider.d.ts +32 -0
- package/dist/deployment-provider.d.ts.map +1 -0
- package/dist/deployment-provider.js +167 -0
- package/dist/deployment-provider.js.map +1 -0
- package/dist/duplicate.d.ts +3 -2
- package/dist/duplicate.d.ts.map +1 -1
- package/dist/duplicate.js +18 -18
- package/dist/duplicate.js.map +1 -1
- package/dist/executable.d.ts +16 -0
- package/dist/executable.d.ts.map +1 -0
- package/dist/executable.js +65 -0
- package/dist/executable.js.map +1 -0
- package/dist/git-repository.d.ts.map +1 -1
- package/dist/git-repository.js.map +1 -1
- package/dist/github-cli.d.ts.map +1 -1
- package/dist/github-cli.js +23 -3
- package/dist/github-cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/owned-destination.d.ts +2 -2
- package/dist/internal/owned-destination.d.ts.map +1 -1
- package/dist/internal/owned-destination.js +2 -2
- package/dist/internal/owned-destination.js.map +1 -1
- package/dist/internal/prototype-templates.d.ts +3 -2
- package/dist/internal/prototype-templates.d.ts.map +1 -1
- package/dist/internal/prototype-templates.js +13 -8
- package/dist/internal/prototype-templates.js.map +1 -1
- package/dist/internal/utils.d.ts +2 -0
- package/dist/internal/utils.d.ts.map +1 -1
- package/dist/internal/utils.js +6 -0
- package/dist/internal/utils.js.map +1 -1
- package/dist/lifecycle/dirty-state.d.ts.map +1 -1
- package/dist/lifecycle/dirty-state.js.map +1 -1
- package/dist/lifecycle/local-prototype-lifecycle.d.ts +22 -10
- package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
- package/dist/lifecycle/local-prototype-lifecycle.js +255 -6
- package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
- package/dist/lifecycle/operation-gate.d.ts.map +1 -1
- package/dist/lifecycle/operation-gate.js.map +1 -1
- package/dist/lifecycle/runtime-context.d.ts.map +1 -1
- package/dist/lifecycle/runtime-context.js.map +1 -1
- package/dist/preferences.d.ts +19 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +182 -0
- package/dist/preferences.js.map +1 -0
- package/dist/prepare-prototype-files.d.ts.map +1 -1
- package/dist/prepare-prototype-files.js.map +1 -1
- package/dist/process-runner.d.ts.map +1 -1
- package/dist/process-runner.js.map +1 -1
- package/dist/prototype-catalog.d.ts.map +1 -1
- package/dist/prototype-catalog.js +18 -7
- package/dist/prototype-catalog.js.map +1 -1
- package/dist/prototype-paths.d.ts +2 -0
- package/dist/prototype-paths.d.ts.map +1 -1
- package/dist/prototype-paths.js.map +1 -1
- package/dist/scaffold.d.ts +4 -2
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +7 -12
- package/dist/scaffold.js.map +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js.map +1 -1
- 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 `.
|
|
73
|
-
one process-local operation gate.
|
|
74
|
-
|
|
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"]}
|
package/dist/claude.d.ts
ADDED
|
@@ -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"]}
|
package/dist/cursor.d.ts
ADDED
|
@@ -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"]}
|