@jonaspauleta/cursor-bridge 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/classify.d.ts +8 -0
- package/dist/classify.js +62 -0
- package/dist/classify.js.map +1 -0
- package/dist/codex.classify.d.ts +15 -0
- package/dist/codex.classify.js +34 -0
- package/dist/codex.classify.js.map +1 -0
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +158 -0
- package/dist/codex.js.map +1 -0
- package/dist/codex.preflight.d.ts +14 -0
- package/dist/codex.preflight.js +27 -0
- package/dist/codex.preflight.js.map +1 -0
- package/dist/cursor.d.ts +17 -0
- package/dist/cursor.js +88 -0
- package/dist/cursor.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -0
- package/dist/imagegen.tool.d.ts +57 -0
- package/dist/imagegen.tool.js +79 -0
- package/dist/imagegen.tool.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +27 -0
- package/dist/logger.js.map +1 -0
- package/dist/preflight.d.ts +11 -0
- package/dist/preflight.js +35 -0
- package/dist/preflight.js.map +1 -0
- package/dist/runner.d.ts +25 -0
- package/dist/runner.js +35 -0
- package/dist/runner.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +19 -0
- package/dist/server.js.map +1 -0
- package/dist/tool.d.ts +38 -0
- package/dist/tool.js +41 -0
- package/dist/tool.js.map +1 -0
- package/dist/validate.d.ts +23 -0
- package/dist/validate.js +112 -0
- package/dist/validate.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 João Paulo Santos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# cursor-bridge
|
|
2
|
+
|
|
3
|
+
A thin MCP server bridging coding-agent CLIs to Claude Code (Opus). It exposes two
|
|
4
|
+
tools: `run_cursor_agent`, which offloads implementation/review tasks to Cursor's
|
|
5
|
+
`cursor-agent` CLI (Composer 2.5 Fast by default), and `generate_image`, which
|
|
6
|
+
generates/edits images with `gpt-image-2` by driving the Codex CLI. The final review
|
|
7
|
+
gate stays on Opus.
|
|
8
|
+
|
|
9
|
+
## Use via npx (recommended)
|
|
10
|
+
|
|
11
|
+
Published as [`@jonaspauleta/cursor-bridge`](https://www.npmjs.com/package/@jonaspauleta/cursor-bridge). No clone or build needed:
|
|
12
|
+
|
|
13
|
+
# one-off
|
|
14
|
+
CURSOR_BRIDGE_ALLOWED_ROOT="$(pwd)" npx -y @jonaspauleta/cursor-bridge
|
|
15
|
+
|
|
16
|
+
Register with Claude Code:
|
|
17
|
+
|
|
18
|
+
claude mcp add --transport stdio --scope project \
|
|
19
|
+
--env CURSOR_BRIDGE_ALLOWED_ROOT="$(pwd)" \
|
|
20
|
+
cursor-bridge -- npx -y @jonaspauleta/cursor-bridge
|
|
21
|
+
|
|
22
|
+
Or as project-scoped `.mcp.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"cursor-bridge": {
|
|
28
|
+
"type": "stdio",
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "@jonaspauleta/cursor-bridge"],
|
|
31
|
+
"env": {
|
|
32
|
+
"CURSOR_API_KEY": "${CURSOR_API_KEY:-}",
|
|
33
|
+
"CURSOR_BRIDGE_ALLOWED_ROOT": "${CLAUDE_PROJECT_DIR}"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
You can also run straight from GitHub without npm: `npx github:jonaspauleta/cursor-bridge`
|
|
41
|
+
(npx clones and builds via the `prepare` hook).
|
|
42
|
+
|
|
43
|
+
**Prerequisites (not bundled):** the `cursor-agent` CLI (for `run_cursor_agent`) and/or
|
|
44
|
+
the `codex` CLI v0.134.0+ (for `generate_image`) must be installed on PATH and logged in
|
|
45
|
+
(`cursor-agent login` / `codex login`). `CURSOR_BRIDGE_ALLOWED_ROOT` is required and must
|
|
46
|
+
be an absolute path. (This repo's own committed `.mcp.json` intentionally keeps pointing
|
|
47
|
+
at local `dist/` for development.)
|
|
48
|
+
|
|
49
|
+
## Build & run
|
|
50
|
+
|
|
51
|
+
npm install
|
|
52
|
+
npm run build # -> dist/index.js
|
|
53
|
+
node dist/index.js # stdio MCP server (Claude Code launches this for you)
|
|
54
|
+
|
|
55
|
+
## Register with Claude Code
|
|
56
|
+
|
|
57
|
+
Either commit `.mcp.json` (project scope) or run:
|
|
58
|
+
|
|
59
|
+
# Export CURSOR_API_KEY in your shell profile — do NOT put it on argv (ps/history leak).
|
|
60
|
+
claude mcp add --transport stdio --scope project \
|
|
61
|
+
--env CURSOR_BRIDGE_ALLOWED_ROOT="$(pwd)" \
|
|
62
|
+
cursor-bridge -- node dist/index.js
|
|
63
|
+
|
|
64
|
+
`CURSOR_API_KEY` is read from the ambient environment via `.mcp.json`'s
|
|
65
|
+
`${CURSOR_API_KEY}` and never hardcoded or passed on a command line. If unset,
|
|
66
|
+
`cursor-agent` falls back to your interactive `cursor-agent login` session.
|
|
67
|
+
`CURSOR_BRIDGE_ALLOWED_ROOT` is required and must be an absolute path.
|
|
68
|
+
|
|
69
|
+
## The tool: `run_cursor_agent`
|
|
70
|
+
|
|
71
|
+
| input | type | default | notes |
|
|
72
|
+
|-------|------|---------|-------|
|
|
73
|
+
| `prompt` | string | — | task/prompt sent to the worker |
|
|
74
|
+
| `cwd` | string | — | working dir; validated against `CURSOR_BRIDGE_ALLOWED_ROOT` |
|
|
75
|
+
| `mode` | `default`/`plan`/`ask` | `default` | default = read-write (`--force`); plan/ask = read-only |
|
|
76
|
+
| `model` | string | `composer-2.5-fast` | allowlisted (`composer-2.5-fast`, `composer-2.5`, `claude-4.6-sonnet-medium`) |
|
|
77
|
+
| `timeoutMs` | number | `270000` | wall-clock; wrapper SIGKILLs cursor-agent past this |
|
|
78
|
+
|
|
79
|
+
Returns the worker's final text on `SUCCESS` (plus `structuredContent` with usage).
|
|
80
|
+
Every other outcome returns `isError: true` with a taxonomy-tagged message.
|
|
81
|
+
|
|
82
|
+
## The tool: `generate_image`
|
|
83
|
+
|
|
84
|
+
Generates or edits images with `gpt-image-2` by driving `codex exec` with the
|
|
85
|
+
`$imagegen` skill. **Subscription-only:** it uses your `codex login` (ChatGPT/Codex
|
|
86
|
+
plan) auth and counts toward your Codex usage limits — no API key. `OPENAI_API_KEY` is
|
|
87
|
+
scrubbed from the child env so an ambient key can't silently switch you to API billing.
|
|
88
|
+
|
|
89
|
+
| input | type | default | notes |
|
|
90
|
+
|-------|------|---------|-------|
|
|
91
|
+
| `prompt` | string | — | image task; free-form, may include edit/iterate instructions |
|
|
92
|
+
| `outputDir` | string | `<allowedRoot>/generated-images` | absolute, validated inside the allowed root |
|
|
93
|
+
| `referenceImages` | string[] | — | paths attached via Codex `-i` (edit/iterate); png/jpg/jpeg/webp, inside root, max 8 |
|
|
94
|
+
| `count` | number | `1` | 1–10 (gpt-image-2 ceiling) |
|
|
95
|
+
| `size` | enum | `auto` | `auto`/`1024x1024`/`1536x1024`/`1024x1536` (best-effort directive) |
|
|
96
|
+
| `quality` | enum | `auto` | `auto`/`low`/`medium`/`high` (best-effort directive) |
|
|
97
|
+
| `inlineImages` | boolean | `false` | also return generated images as base64 blocks (first 4) |
|
|
98
|
+
| `timeoutMs` | number | `300000` | wall-clock; wrapper SIGKILLs codex past this |
|
|
99
|
+
|
|
100
|
+
Each call writes into a fresh `<outputDir>/<runId>/` subdir. On `SUCCESS` it returns the
|
|
101
|
+
generated file paths (and Codex's summary); every other outcome returns `isError: true`.
|
|
102
|
+
`size`/`quality`/`count` are natural-language directives templated into the prompt (the
|
|
103
|
+
built-in `image_gen` tool is invoked by the model, not via CLI flags), so they are
|
|
104
|
+
best-effort rather than hard API parameters.
|
|
105
|
+
|
|
106
|
+
**Status taxonomy:** `SUCCESS | NO_IMAGE | AUTH_REQUIRED | TIMEOUT | TOOL_ERROR`. Success
|
|
107
|
+
means ≥1 image file actually landed on disk — the exit code is not trusted.
|
|
108
|
+
|
|
109
|
+
**Requirements:** the `codex` CLI (v0.134.0+) must be installed and on the server's PATH,
|
|
110
|
+
and you must be logged in (`codex login`; check with `codex login status`). Override the
|
|
111
|
+
binary with `CODEX_AGENT_BIN`. Unlike `cursor-agent`, Codex's `-s workspace-write`
|
|
112
|
+
sandbox is genuinely enforced, so the image worker can only write within the workspace
|
|
113
|
+
root and cannot run destructive shell outside it.
|
|
114
|
+
|
|
115
|
+
Smoke test: `npm run test:smoke:codex` (live gpt-image-2 call; requires `codex login`).
|
|
116
|
+
|
|
117
|
+
## Status taxonomy
|
|
118
|
+
|
|
119
|
+
`SUCCESS | AUTH_REQUIRED | TIMEOUT | TOOL_ERROR | MALFORMED`. Success requires a
|
|
120
|
+
parsed `{"type":"result"}` envelope with `is_error:false`; the exit code is NOT
|
|
121
|
+
trusted (the auth-failure path is exit-code-unstable).
|
|
122
|
+
|
|
123
|
+
## Auth pre-flight
|
|
124
|
+
|
|
125
|
+
cursor-agent status --format json # {"isAuthenticated":true,...}
|
|
126
|
+
|
|
127
|
+
The server exposes `preflightAuth()` for a fail-fast check before a real call.
|
|
128
|
+
|
|
129
|
+
## Security notes
|
|
130
|
+
|
|
131
|
+
- argv is always an array; `spawn` runs with no shell — no command injection via the wrapper.
|
|
132
|
+
- All MCP-tool inputs pass `src/validate.ts` (mode/model allowlist, cwd allowlist + symlink-safe) before reaching argv. The git-diff SHAs used by the *final review gate* are NOT validated here — they live in the orchestrator's shell (Part B) and are resolved mechanically with `git rev-parse`, never taken from task text.
|
|
133
|
+
- Secrets come from env only; the logger redacts secret-looking values and never logs prompt bodies or raw output (stderr only). Never pass `CURSOR_API_KEY` on a command line (`ps`/history leak) — export it in your shell profile so `.mcp.json`'s `${CURSOR_API_KEY}` picks it up.
|
|
134
|
+
- `CURSOR_BRIDGE_ALLOWED_ROOT` is REQUIRED and must be absolute; the server refuses to start otherwise and logs the resolved root. Set it as narrowly as possible.
|
|
135
|
+
- **Blast radius — VERIFIED on `cursor-agent` v2026.05.28: `--sandbox enabled` does NOT contain a `--force` worker on this build.** A probe worker wrote a file outside `--workspace` (into `$HOME`) despite `--sandbox enabled`. `--mode plan`/`ask` are genuinely read-only and safe. But `mode:default` passes `--force` (auto-approves arbitrary shell + writes) and `--workspace` is only a cwd, not a jail — so a default-mode worker is effectively **full user-level shell**: it can read `~/.ssh`, exfiltrate over the network, or `git push`. The wrapper still passes `--sandbox enabled` (harmless future-proofing should a later build enforce it — re-run the Task A20 Step 5 probe to recheck), but **today you MUST treat `mode:default` as unsandboxed** and run workers ONLY in disposable/throwaway git worktrees with NO access to real secrets. The `assertWorkspacePath` allow-root and the Opus security gate are the real controls; cwd validation alone does not contain `--force`.
|
|
136
|
+
|
|
137
|
+
## Tests
|
|
138
|
+
|
|
139
|
+
npm test # unit + in-memory round-trip (no network)
|
|
140
|
+
npm run test:smoke # RUN_CURSOR_SMOKE=1; live call to Composer 2.5 Fast
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CursorResult } from "./errors.js";
|
|
2
|
+
export declare const stripAnsi: (s: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Pure classification of captured streams into a CursorResult.
|
|
5
|
+
* Never spawns, never times. The exit code is intentionally ignored: the
|
|
6
|
+
* auth-failure path is exit-code-unstable and emits no result envelope.
|
|
7
|
+
*/
|
|
8
|
+
export declare function classify(stdout: string, stderr: string): CursorResult;
|
package/dist/classify.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
2
|
+
export const stripAnsi = (s) => s.replace(ANSI_RE, "");
|
|
3
|
+
const AUTH_MARKERS = [
|
|
4
|
+
/API key is invalid/i,
|
|
5
|
+
/not logged in/i,
|
|
6
|
+
/\bauthenticate\b/i,
|
|
7
|
+
/unauthorized/i,
|
|
8
|
+
/session expired/i,
|
|
9
|
+
/provided API key/i,
|
|
10
|
+
];
|
|
11
|
+
const RESULT_HINT = /"type"\s*:\s*"result"/;
|
|
12
|
+
/**
|
|
13
|
+
* Pure classification of captured streams into a CursorResult.
|
|
14
|
+
* Never spawns, never times. The exit code is intentionally ignored: the
|
|
15
|
+
* auth-failure path is exit-code-unstable and emits no result envelope.
|
|
16
|
+
*/
|
|
17
|
+
export function classify(stdout, stderr) {
|
|
18
|
+
let envelope = null;
|
|
19
|
+
let sawUnparseableResultLine = false;
|
|
20
|
+
for (const rawLine of stripAnsi(stdout).split("\n")) {
|
|
21
|
+
const line = rawLine.trim();
|
|
22
|
+
if (!line || line[0] !== "{")
|
|
23
|
+
continue;
|
|
24
|
+
let obj;
|
|
25
|
+
try {
|
|
26
|
+
obj = JSON.parse(line);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Looks like JSON, mentions a result envelope, but won't parse -> malformed candidate.
|
|
30
|
+
if (RESULT_HINT.test(line))
|
|
31
|
+
sawUnparseableResultLine = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (obj && typeof obj === "object" && obj.type === "result") {
|
|
35
|
+
envelope = obj;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (envelope) {
|
|
40
|
+
if (envelope.is_error === false && typeof envelope.result === "string") {
|
|
41
|
+
return {
|
|
42
|
+
status: "SUCCESS",
|
|
43
|
+
result: envelope.result,
|
|
44
|
+
usage: envelope.usage,
|
|
45
|
+
sessionId: envelope.session_id,
|
|
46
|
+
requestId: envelope.request_id,
|
|
47
|
+
raw: stdout,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Parsed a result envelope, but it's an error / has no string result.
|
|
51
|
+
return { status: "TOOL_ERROR", error: "cursor-agent returned an error result", raw: stdout };
|
|
52
|
+
}
|
|
53
|
+
if (sawUnparseableResultLine) {
|
|
54
|
+
return { status: "MALFORMED", error: "result line present but unparseable", raw: stdout };
|
|
55
|
+
}
|
|
56
|
+
const scan = stripAnsi(`${stdout}\n${stderr}`);
|
|
57
|
+
if (AUTH_MARKERS.some((re) => re.test(scan))) {
|
|
58
|
+
return { status: "AUTH_REQUIRED", error: "cursor-agent reported an auth problem", raw: scan };
|
|
59
|
+
}
|
|
60
|
+
return { status: "TOOL_ERROR", error: "no result envelope emitted", raw: scan };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../src/classify.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAClC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAEvE,MAAM,YAAY,GAAsB;IACtC,qBAAqB;IACrB,gBAAgB;IAChB,mBAAmB;IACnB,eAAe;IACf,kBAAkB;IAClB,mBAAmB;CACpB,CAAC;AAEF,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAW5C;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,MAAc;IACrD,IAAI,QAAQ,GAA0B,IAAI,CAAC;IAC3C,IAAI,wBAAwB,GAAG,KAAK,CAAC;IAErC,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,SAAS;QACvC,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,uFAAuF;YACvF,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,wBAAwB,GAAG,IAAI,CAAC;YAC5D,SAAS;QACX,CAAC;QACD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAK,GAA0B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACpF,QAAQ,GAAG,GAAqB,CAAC;YACjC,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,QAAQ,CAAC,QAAQ,KAAK,KAAK,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACvE,OAAO;gBACL,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,SAAS,EAAE,QAAQ,CAAC,UAAU;gBAC9B,SAAS,EAAE,QAAQ,CAAC,UAAU;gBAC9B,GAAG,EAAE,MAAM;aACZ,CAAC;QACJ,CAAC;QACD,sEAAsE;QACtE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,uCAAuC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAC/F,CAAC;IAED,IAAI,wBAAwB,EAAE,CAAC;QAC7B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,qCAAqC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAC5F,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;IAC/C,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,uCAAuC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAChG,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,4BAA4B,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ImageResult } from "./errors.js";
|
|
2
|
+
export interface ClassifyImageInput {
|
|
3
|
+
/** Image files found in the run subdir (absolute paths). */
|
|
4
|
+
files: string[];
|
|
5
|
+
/** Contents of --output-last-message (may be empty). */
|
|
6
|
+
summary: string;
|
|
7
|
+
stdout: string;
|
|
8
|
+
stderr: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Pure classification of an image-generation run. Success is determined by the
|
|
12
|
+
* FILESYSTEM (image files on disk), never the exit code. The caller (codex.ts)
|
|
13
|
+
* handles TIMEOUT and spawn/validation TOOL_ERROR before calling this.
|
|
14
|
+
*/
|
|
15
|
+
export declare function classifyImage(input: ClassifyImageInput): ImageResult;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { stripAnsi } from "./classify.js";
|
|
2
|
+
const AUTH_MARKERS = [
|
|
3
|
+
/not logged in/i,
|
|
4
|
+
/codex login/i,
|
|
5
|
+
/unauthorized/i,
|
|
6
|
+
/\b401\b/,
|
|
7
|
+
/please (?:run )?login/i,
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* Pure classification of an image-generation run. Success is determined by the
|
|
11
|
+
* FILESYSTEM (image files on disk), never the exit code. The caller (codex.ts)
|
|
12
|
+
* handles TIMEOUT and spawn/validation TOOL_ERROR before calling this.
|
|
13
|
+
*/
|
|
14
|
+
export function classifyImage(input) {
|
|
15
|
+
if (input.files.length > 0) {
|
|
16
|
+
return {
|
|
17
|
+
status: "SUCCESS",
|
|
18
|
+
images: input.files,
|
|
19
|
+
summary: input.summary.trim() || undefined,
|
|
20
|
+
count: input.files.length,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const scan = stripAnsi(`${input.stdout}\n${input.stderr}`);
|
|
24
|
+
if (AUTH_MARKERS.some((re) => re.test(scan))) {
|
|
25
|
+
return { status: "AUTH_REQUIRED", error: "codex reported an auth problem", raw: scan };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
status: "NO_IMAGE",
|
|
29
|
+
error: "codex produced no image file",
|
|
30
|
+
summary: input.summary.trim() || undefined,
|
|
31
|
+
raw: scan,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=codex.classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.classify.js","sourceRoot":"","sources":["../src/codex.classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,MAAM,YAAY,GAAsB;IACtC,gBAAgB;IAChB,cAAc;IACd,eAAe;IACf,SAAS;IACT,wBAAwB;CACzB,CAAC;AAWF;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,KAAK,CAAC,KAAK;YACnB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,SAAS;YAC1C,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;SAC1B,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,gCAAgC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACzF,CAAC;IACD,OAAO;QACL,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,8BAA8B;QACrC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,SAAS;QAC1C,GAAG,EAAE,IAAI;KACV,CAAC;AACJ,CAAC"}
|
package/dist/codex.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Runner } from "./runner.js";
|
|
2
|
+
import { type ImageSize, type ImageQuality } from "./validate.js";
|
|
3
|
+
import { type LogFields } from "./logger.js";
|
|
4
|
+
import type { ImageResult } from "./errors.js";
|
|
5
|
+
export interface GenerateImageOptions {
|
|
6
|
+
outputDir?: string;
|
|
7
|
+
referenceImages?: string[];
|
|
8
|
+
count?: number;
|
|
9
|
+
size?: ImageSize;
|
|
10
|
+
quality?: ImageQuality;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
allowedRoot?: string;
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
runner?: Runner;
|
|
15
|
+
log?: (fields: LogFields) => void;
|
|
16
|
+
bin?: string;
|
|
17
|
+
/** Injectable for deterministic test output subdirs; defaults to timestamp+random. */
|
|
18
|
+
runId?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function generateImage(prompt: string, opts?: GenerateImageOptions): Promise<ImageResult>;
|
package/dist/codex.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, copyFileSync } from "node:fs";
|
|
2
|
+
import { join, isAbsolute } from "node:path";
|
|
3
|
+
import { tmpdir, homedir } from "node:os";
|
|
4
|
+
import { realRunner, collectStream } from "./runner.js";
|
|
5
|
+
import { classifyImage } from "./codex.classify.js";
|
|
6
|
+
import { assertOutputDir, assertReferenceImages, assertCount, assertSize, assertQuality, } from "./validate.js";
|
|
7
|
+
import { createLogger } from "./logger.js";
|
|
8
|
+
import { ValidationError } from "./errors.js";
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
10
|
+
const IMAGE_EXT_RE = /\.(png|jpe?g|webp)$/i;
|
|
11
|
+
/** Builds the natural-language directive sent to codex (size/quality/count are best-effort hints). */
|
|
12
|
+
function buildDirective(prompt, count, size, quality, runSubdir) {
|
|
13
|
+
const sizeClause = size === "auto" ? "at the most appropriate size" : `at ${size}`;
|
|
14
|
+
const qualityClause = quality === "auto" ? "" : ` Use ${quality} quality.`;
|
|
15
|
+
return (`$imagegen\n${prompt}\n\n` +
|
|
16
|
+
`Generate exactly ${count} image${count === 1 ? "" : "s"} ${sizeClause}.${qualityClause} ` +
|
|
17
|
+
`Save every generated image as a PNG file into the directory ${runSubdir}. ` +
|
|
18
|
+
`Do not write any other files.`);
|
|
19
|
+
}
|
|
20
|
+
function safeReaddir(dir) {
|
|
21
|
+
try {
|
|
22
|
+
return readdirSync(dir);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function scanImages(dir) {
|
|
29
|
+
return safeReaddir(dir)
|
|
30
|
+
.filter((f) => IMAGE_EXT_RE.test(f))
|
|
31
|
+
.map((f) => join(dir, f))
|
|
32
|
+
.sort();
|
|
33
|
+
}
|
|
34
|
+
export async function generateImage(prompt, opts = {}) {
|
|
35
|
+
const runner = opts.runner ?? realRunner;
|
|
36
|
+
const log = opts.log ?? createLogger();
|
|
37
|
+
const bin = opts.bin ?? process.env.CODEX_AGENT_BIN ?? "codex";
|
|
38
|
+
const env = opts.env ?? process.env;
|
|
39
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
40
|
+
let refs;
|
|
41
|
+
let count;
|
|
42
|
+
let size;
|
|
43
|
+
let quality;
|
|
44
|
+
let runSubdir;
|
|
45
|
+
let allowedRoot;
|
|
46
|
+
try {
|
|
47
|
+
allowedRoot = opts.allowedRoot ?? process.env.CURSOR_BRIDGE_ALLOWED_ROOT ?? "";
|
|
48
|
+
if (!allowedRoot || !isAbsolute(allowedRoot)) {
|
|
49
|
+
throw new ValidationError("CURSOR_BRIDGE_ALLOWED_ROOT must be set to an absolute path (refusing to default to cwd)");
|
|
50
|
+
}
|
|
51
|
+
const outputDir = assertOutputDir(opts.outputDir ?? join(allowedRoot, "generated-images"), allowedRoot);
|
|
52
|
+
count = assertCount(opts.count ?? 1);
|
|
53
|
+
size = assertSize(opts.size ?? "auto");
|
|
54
|
+
quality = assertQuality(opts.quality ?? "auto");
|
|
55
|
+
refs = assertReferenceImages(opts.referenceImages ?? [], allowedRoot);
|
|
56
|
+
const runId = opts.runId ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
57
|
+
runSubdir = join(outputDir, runId);
|
|
58
|
+
mkdirSync(runSubdir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
const r = { status: "TOOL_ERROR", error: e instanceof ValidationError ? e.message : String(e) };
|
|
62
|
+
log({ event: "codex_imagegen", level: "error", status: r.status });
|
|
63
|
+
return r;
|
|
64
|
+
}
|
|
65
|
+
const promptText = buildDirective(prompt, count, size, quality, runSubdir);
|
|
66
|
+
const lastMsgFile = join(tmpdir(), `codex-lastmsg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`);
|
|
67
|
+
const args = [
|
|
68
|
+
"exec",
|
|
69
|
+
promptText,
|
|
70
|
+
"-C", allowedRoot,
|
|
71
|
+
"-s", "workspace-write",
|
|
72
|
+
"--skip-git-repo-check",
|
|
73
|
+
"--add-dir", runSubdir,
|
|
74
|
+
"--output-last-message", lastMsgFile,
|
|
75
|
+
"--color", "never",
|
|
76
|
+
];
|
|
77
|
+
for (const ref of refs) {
|
|
78
|
+
args.push("--image", ref);
|
|
79
|
+
}
|
|
80
|
+
// Subscription-only: scrub OPENAI_API_KEY so an ambient key can't switch codex to API billing.
|
|
81
|
+
const childEnv = { ...env };
|
|
82
|
+
delete childEnv.OPENAI_API_KEY;
|
|
83
|
+
// Snapshot codex's default image dir so we can recover images if it ignores the save-path directive.
|
|
84
|
+
const homeImagesDir = join(env.CODEX_HOME ?? join(homedir(), ".codex"), "generated_images");
|
|
85
|
+
const beforeHome = new Set(safeReaddir(homeImagesDir));
|
|
86
|
+
const startedAt = Date.now();
|
|
87
|
+
const child = runner(bin, args, { env: childEnv, cwd: allowedRoot });
|
|
88
|
+
const outP = collectStream(child.stdout);
|
|
89
|
+
const errP = collectStream(child.stderr);
|
|
90
|
+
try {
|
|
91
|
+
let timedOut = false;
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
timedOut = true;
|
|
94
|
+
child.kill("SIGKILL");
|
|
95
|
+
}, timeoutMs);
|
|
96
|
+
let spawnError;
|
|
97
|
+
try {
|
|
98
|
+
await child.done;
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
spawnError = e;
|
|
102
|
+
}
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
const durationMs = Date.now() - startedAt;
|
|
105
|
+
if (timedOut) {
|
|
106
|
+
const r = { status: "TIMEOUT", error: `killed after ${timeoutMs}ms`, raw: outP.current() };
|
|
107
|
+
log({ event: "codex_imagegen", level: "warn", status: r.status, durationMs });
|
|
108
|
+
return r;
|
|
109
|
+
}
|
|
110
|
+
if (spawnError) {
|
|
111
|
+
const r = { status: "TOOL_ERROR", error: spawnError.message };
|
|
112
|
+
log({ event: "codex_imagegen", level: "error", status: r.status, durationMs });
|
|
113
|
+
return r;
|
|
114
|
+
}
|
|
115
|
+
const [out, err] = await Promise.all([outP.done, errP.done]);
|
|
116
|
+
let summary = "";
|
|
117
|
+
try {
|
|
118
|
+
summary = readFileSync(lastMsgFile, "utf8");
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* no last-message file — summary stays empty */
|
|
122
|
+
}
|
|
123
|
+
let files = scanImages(runSubdir);
|
|
124
|
+
if (files.length === 0) {
|
|
125
|
+
// Fallback: codex may have saved to its default ~/.codex/generated_images instead.
|
|
126
|
+
// Copy any NEW images (by filename) into our run subdir. Defensive; covered by smoke.
|
|
127
|
+
for (const f of safeReaddir(homeImagesDir)) {
|
|
128
|
+
if (IMAGE_EXT_RE.test(f) && !beforeHome.has(f)) {
|
|
129
|
+
try {
|
|
130
|
+
copyFileSync(join(homeImagesDir, f), join(runSubdir, f));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* ignore */
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
files = scanImages(runSubdir);
|
|
138
|
+
}
|
|
139
|
+
const r = classifyImage({ files, summary, stdout: out, stderr: err });
|
|
140
|
+
log({
|
|
141
|
+
event: "codex_imagegen",
|
|
142
|
+
level: r.status === "SUCCESS" ? "info" : "warn",
|
|
143
|
+
status: r.status,
|
|
144
|
+
durationMs,
|
|
145
|
+
count: r.count,
|
|
146
|
+
});
|
|
147
|
+
return r;
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
try {
|
|
151
|
+
rmSync(lastMsgFile, { force: true });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* ignore */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../src/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Runner } from "./runner.js";
|
|
2
|
+
export interface CodexPreflightResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
reason?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Fail-fast auth check via `codex login status`. "Not logged in" is checked BEFORE
|
|
8
|
+
* "logged in" because the negative string contains the positive substring.
|
|
9
|
+
*/
|
|
10
|
+
export declare function preflightCodexAuth(opts?: {
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
runner?: Runner;
|
|
13
|
+
bin?: string;
|
|
14
|
+
}): Promise<CodexPreflightResult>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { realRunner, collectStream } from "./runner.js";
|
|
2
|
+
import { stripAnsi } from "./classify.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fail-fast auth check via `codex login status`. "Not logged in" is checked BEFORE
|
|
5
|
+
* "logged in" because the negative string contains the positive substring.
|
|
6
|
+
*/
|
|
7
|
+
export async function preflightCodexAuth(opts = {}) {
|
|
8
|
+
const runner = opts.runner ?? realRunner;
|
|
9
|
+
const bin = opts.bin ?? process.env.CODEX_AGENT_BIN ?? "codex";
|
|
10
|
+
const child = runner(bin, ["login", "status"], { env: opts.env ?? process.env });
|
|
11
|
+
const outP = collectStream(child.stdout);
|
|
12
|
+
const errP = collectStream(child.stderr);
|
|
13
|
+
try {
|
|
14
|
+
await child.done;
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return { ok: false, reason: e.message };
|
|
18
|
+
}
|
|
19
|
+
const [out, err] = await Promise.all([outP.done, errP.done]);
|
|
20
|
+
const clean = stripAnsi(`${out}\n${err}`);
|
|
21
|
+
if (/not logged in/i.test(clean))
|
|
22
|
+
return { ok: false, reason: "codex reports not logged in" };
|
|
23
|
+
if (/logged in/i.test(clean))
|
|
24
|
+
return { ok: true };
|
|
25
|
+
return { ok: false, reason: "could not determine codex login status" };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=codex.preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.preflight.js","sourceRoot":"","sources":["../src/codex.preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAO1C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAmE,EAAE;IAErE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,OAAO,CAAC;IAC/D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,IAAI,CAAC;IACnB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;IAC1C,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,6BAA6B,EAAE,CAAC;IAC9F,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAClD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;AACzE,CAAC"}
|
package/dist/cursor.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Runner } from "./runner.js";
|
|
2
|
+
import { type CursorMode } from "./validate.js";
|
|
3
|
+
import { type LogFields } from "./logger.js";
|
|
4
|
+
import type { CursorResult } from "./errors.js";
|
|
5
|
+
export interface RunOptions {
|
|
6
|
+
cwd: string;
|
|
7
|
+
mode?: CursorMode;
|
|
8
|
+
model?: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
allowedRoot?: string;
|
|
11
|
+
sandbox?: "enabled" | "disabled";
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
runner?: Runner;
|
|
14
|
+
log?: (fields: LogFields) => void;
|
|
15
|
+
bin?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function runCursorAgent(prompt: string, opts: RunOptions): Promise<CursorResult>;
|
package/dist/cursor.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { realRunner, collectStream } from "./runner.js";
|
|
3
|
+
import { classify } from "./classify.js";
|
|
4
|
+
import { assertMode, assertModel, assertWorkspacePath } from "./validate.js";
|
|
5
|
+
import { createLogger } from "./logger.js";
|
|
6
|
+
import { ValidationError } from "./errors.js";
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 270_000; // under cursor-agent's ~5min ceiling
|
|
8
|
+
export async function runCursorAgent(prompt, opts) {
|
|
9
|
+
const runner = opts.runner ?? realRunner;
|
|
10
|
+
const log = opts.log ?? createLogger();
|
|
11
|
+
const bin = opts.bin ?? process.env.CURSOR_AGENT_BIN ?? "cursor-agent";
|
|
12
|
+
const env = opts.env ?? process.env;
|
|
13
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
14
|
+
let mode;
|
|
15
|
+
let model;
|
|
16
|
+
let cwd;
|
|
17
|
+
try {
|
|
18
|
+
mode = assertMode(opts.mode ?? "default");
|
|
19
|
+
model = assertModel(opts.model ?? "composer-2.5-fast");
|
|
20
|
+
// No cwd fallback: an unset root must fail loudly, never silently widen to the launch cwd (often $HOME).
|
|
21
|
+
const allowedRoot = opts.allowedRoot ?? process.env.CURSOR_BRIDGE_ALLOWED_ROOT;
|
|
22
|
+
if (!allowedRoot || !isAbsolute(allowedRoot)) {
|
|
23
|
+
throw new ValidationError("CURSOR_BRIDGE_ALLOWED_ROOT must be set to an absolute path (refusing to default to cwd)");
|
|
24
|
+
}
|
|
25
|
+
cwd = assertWorkspacePath(opts.cwd, allowedRoot);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
const r = {
|
|
29
|
+
status: "TOOL_ERROR",
|
|
30
|
+
error: e instanceof ValidationError ? e.message : String(e),
|
|
31
|
+
};
|
|
32
|
+
log({ event: "cursor_agent", level: "error", status: r.status });
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
// --sandbox enabled bounds filesystem/network for the --force write path (defense in depth).
|
|
36
|
+
// It must be VERIFIED on your cursor-agent build (see Task A20); --workspace alone is only a cwd, not a jail.
|
|
37
|
+
const sandbox = opts.sandbox ?? (process.env.CURSOR_BRIDGE_SANDBOX === "disabled" ? "disabled" : "enabled");
|
|
38
|
+
const args = ["-p", prompt, "--model", model, "--output-format", "json", "--workspace", cwd, "--trust", "--sandbox", sandbox];
|
|
39
|
+
if (mode === "default") {
|
|
40
|
+
args.push("--force"); // auto-approve writes/shell (headless cannot prompt)
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
args.push("--mode", mode); // plan|ask -> read-only enforcement
|
|
44
|
+
}
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
const child = runner(bin, args, { env, cwd });
|
|
47
|
+
const outP = collectStream(child.stdout);
|
|
48
|
+
const errP = collectStream(child.stderr);
|
|
49
|
+
let timedOut = false;
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
timedOut = true;
|
|
52
|
+
child.kill("SIGKILL");
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
let spawnError;
|
|
55
|
+
try {
|
|
56
|
+
await child.done;
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
spawnError = e;
|
|
60
|
+
}
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
const durationMs = Date.now() - startedAt;
|
|
63
|
+
if (timedOut) {
|
|
64
|
+
const r = { status: "TIMEOUT", error: `killed after ${timeoutMs}ms`, raw: outP.current() };
|
|
65
|
+
log({ event: "cursor_agent", level: "warn", status: r.status, durationMs, model, mode });
|
|
66
|
+
return r;
|
|
67
|
+
}
|
|
68
|
+
if (spawnError) {
|
|
69
|
+
const r = { status: "TOOL_ERROR", error: spawnError.message };
|
|
70
|
+
log({ event: "cursor_agent", level: "error", status: r.status, durationMs, model, mode });
|
|
71
|
+
return r;
|
|
72
|
+
}
|
|
73
|
+
const [out, err] = await Promise.all([outP.done, errP.done]);
|
|
74
|
+
const r = classify(out, err);
|
|
75
|
+
log({
|
|
76
|
+
event: "cursor_agent",
|
|
77
|
+
level: r.status === "SUCCESS" ? "info" : "warn",
|
|
78
|
+
status: r.status,
|
|
79
|
+
durationMs,
|
|
80
|
+
model,
|
|
81
|
+
mode,
|
|
82
|
+
sessionId: r.sessionId,
|
|
83
|
+
requestId: r.requestId,
|
|
84
|
+
tokens: r.usage,
|
|
85
|
+
});
|
|
86
|
+
return r;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAmB,MAAM,eAAe,CAAC;AAC9F,OAAO,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,kBAAkB,GAAG,OAAO,CAAC,CAAC,qCAAqC;AAezE,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,IAAgB;IACnE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,YAAY,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,cAAc,CAAC;IACvE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAEvD,IAAI,IAAgB,CAAC;IACrB,IAAI,KAAa,CAAC;IAClB,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC;QAC1C,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,mBAAmB,CAAC,CAAC;QACvD,yGAAyG;QACzG,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAC/E,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,eAAe,CAAC,yFAAyF,CAAC,CAAC;QACvH,CAAC;QACD,GAAG,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,GAAiB;YACtB,MAAM,EAAE,YAAY;YACpB,KAAK,EAAE,CAAC,YAAY,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;SAC5D,CAAC;QACF,GAAG,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,CAAC;IACX,CAAC;IAED,6FAA6F;IAC7F,8GAA8G;IAC9G,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5G,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAC9H,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,qDAAqD;IAC7E,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,oCAAoC;IACjE,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEzC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,QAAQ,GAAG,IAAI,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACxB,CAAC,EAAE,SAAS,CAAC,CAAC;IAEd,IAAI,UAA6B,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,IAAI,CAAC;IACnB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,UAAU,GAAG,CAAU,CAAC;IAC1B,CAAC;IACD,YAAY,CAAC,KAAK,CAAC,CAAC;IAEpB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAE1C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,GAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,SAAS,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACzG,GAAG,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,GAAiB,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC;QAC5E,GAAG,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1F,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC7B,GAAG,CAAC;QACF,KAAK,EAAE,cAAc;QACrB,KAAK,EAAE,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC/C,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,UAAU;QACV,KAAK;QACL,IAAI;QACJ,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,MAAM,EAAE,CAAC,CAAC,KAA2C;KACtD,CAAC,CAAC;IACH,OAAO,CAAC,CAAC;AACX,CAAC"}
|