@illuminis/comprism 0.1.3 → 0.1.5
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 +61 -2
- package/out/agent/command.d.ts +75 -4
- package/out/agent/command.js +221 -26
- package/out/agent/render.d.ts +56 -8
- package/out/agent/render.js +441 -39
- package/out/agent/session.d.ts +156 -2
- package/out/agent/session.js +795 -154
- package/out/commands/agents.d.ts +2 -0
- package/out/commands/agents.js +79 -0
- package/out/commands/ask.d.ts +1 -1
- package/out/commands/ask.js +88 -12
- package/out/commands/commands-thin.js +79 -17
- package/out/commands/config.d.ts +1 -0
- package/out/commands/config.js +138 -0
- package/out/commands/cost.d.ts +1 -0
- package/out/commands/cost.js +167 -0
- package/out/commands/hooks.d.ts +1 -0
- package/out/commands/hooks.js +83 -0
- package/out/commands/install.d.ts +44 -1
- package/out/commands/install.js +198 -4
- package/out/commands/instructions.d.ts +1 -0
- package/out/commands/instructions.js +113 -0
- package/out/commands/integrations.d.ts +3 -0
- package/out/commands/integrations.js +215 -0
- package/out/commands/jobs.d.ts +5 -0
- package/out/commands/jobs.js +157 -0
- package/out/commands/login.js +249 -43
- package/out/commands/memory.d.ts +3 -0
- package/out/commands/memory.js +113 -0
- package/out/commands/permissions.d.ts +1 -0
- package/out/commands/permissions.js +94 -0
- package/out/commands/plugins.d.ts +4 -0
- package/out/commands/plugins.js +192 -0
- package/out/commands/privacy.d.ts +1 -0
- package/out/commands/privacy.js +57 -0
- package/out/commands/providerKey.d.ts +32 -0
- package/out/commands/providerKey.js +108 -0
- package/out/commands/repl.d.ts +12 -1
- package/out/commands/repl.js +1649 -122
- package/out/commands/report.d.ts +39 -0
- package/out/commands/report.js +115 -0
- package/out/commands/review.d.ts +5 -0
- package/out/commands/review.js +223 -0
- package/out/commands/sessions.d.ts +23 -0
- package/out/commands/sessions.js +115 -0
- package/out/commands/settings.d.ts +3 -1
- package/out/commands/settings.js +18 -16
- package/out/commands/skills.d.ts +21 -0
- package/out/commands/skills.js +207 -0
- package/out/commands/unattended.d.ts +7 -0
- package/out/commands/unattended.js +351 -0
- package/out/commands/update.d.ts +1 -0
- package/out/commands/update.js +123 -0
- package/out/commands/welcome.d.ts +25 -1
- package/out/commands/welcome.js +71 -35
- package/out/commands/worktrees.d.ts +5 -0
- package/out/commands/worktrees.js +186 -0
- package/out/executor/browser.d.ts +14 -0
- package/out/executor/browser.js +270 -0
- package/out/executor/diagnostics.d.ts +2 -0
- package/out/executor/diagnostics.js +181 -0
- package/out/executor/files.js +270 -40
- package/out/executor/git.js +42 -29
- package/out/executor/hooks.d.ts +42 -58
- package/out/executor/hooks.js +89 -182
- package/out/executor/index.d.ts +21 -5
- package/out/executor/index.js +160 -14
- package/out/executor/paths.d.ts +6 -1
- package/out/executor/paths.js +34 -6
- package/out/executor/sandbox.d.ts +40 -0
- package/out/executor/sandbox.js +299 -0
- package/out/executor/shell.d.ts +49 -4
- package/out/executor/shell.js +302 -56
- package/out/executor/toolservers.d.ts +20 -0
- package/out/executor/toolservers.js +189 -0
- package/out/executor/worktree.d.ts +9 -0
- package/out/executor/worktree.js +119 -0
- package/out/graph/read-python.js +2 -1
- package/out/lib/attach.d.ts +56 -12
- package/out/lib/attach.js +230 -63
- package/out/lib/clipboard.d.ts +23 -0
- package/out/lib/clipboard.js +182 -0
- package/out/lib/commandlist.d.ts +20 -0
- package/out/lib/commandlist.js +58 -0
- package/out/lib/decision.d.ts +22 -0
- package/out/lib/decision.js +50 -0
- package/out/lib/fingerprint.d.ts +25 -0
- package/out/lib/fingerprint.js +58 -0
- package/out/lib/gateway.d.ts +190 -2
- package/out/lib/gateway.js +59 -4
- package/out/lib/history.d.ts +24 -0
- package/out/lib/history.js +137 -0
- package/out/lib/ide.d.ts +19 -0
- package/out/lib/ide.js +131 -0
- package/out/lib/keyboard.d.ts +98 -0
- package/out/lib/keyboard.js +390 -0
- package/out/lib/lastlogin.d.ts +14 -0
- package/out/lib/lastlogin.js +105 -0
- package/out/lib/machine.d.ts +21 -0
- package/out/lib/machine.js +91 -0
- package/out/lib/notify.d.ts +4 -0
- package/out/lib/notify.js +52 -0
- package/out/lib/output.d.ts +48 -0
- package/out/lib/output.js +108 -0
- package/out/lib/picker.d.ts +1 -0
- package/out/lib/picker.js +158 -0
- package/out/lib/project-ops.d.ts +19 -0
- package/out/lib/project-ops.js +146 -0
- package/out/lib/project.d.ts +28 -0
- package/out/lib/project.js +114 -0
- package/out/lib/prompt.d.ts +3 -0
- package/out/lib/prompt.js +36 -2
- package/out/lib/queue.d.ts +13 -0
- package/out/lib/queue.js +116 -0
- package/out/lib/readiness.d.ts +19 -0
- package/out/lib/readiness.js +170 -1
- package/out/lib/self.d.ts +23 -0
- package/out/lib/self.js +124 -0
- package/out/lib/sessions.d.ts +19 -0
- package/out/lib/sessions.js +221 -0
- package/out/lib/startcache.d.ts +13 -0
- package/out/lib/startcache.js +78 -0
- package/out/lib/stdin.d.ts +32 -0
- package/out/lib/stdin.js +117 -0
- package/out/lib/store.d.ts +40 -0
- package/out/lib/store.js +138 -0
- package/out/lib/sync.d.ts +18 -0
- package/out/lib/sync.js +81 -0
- package/out/lib/ui.d.ts +10 -4
- package/out/lib/ui.js +65 -25
- package/out/lib/voice.d.ts +36 -0
- package/out/lib/voice.js +160 -1
- package/out/native/dictate +0 -0
- package/out/postinstall.js +42 -17
- package/out/providers/anthropic.d.ts +22 -0
- package/out/providers/anthropic.js +80 -0
- package/out/providers/index.d.ts +8 -0
- package/out/providers/index.js +83 -0
- package/out/providers/openai.d.ts +11 -0
- package/out/providers/openai.js +57 -0
- package/out/thin.js +595 -33
- package/package.json +11 -50
package/README.md
CHANGED
|
@@ -6,23 +6,81 @@ cost, and changes nothing about what your tool does.
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
+
Three ways, one tool. Pick the one that suits the machine; each gives you the
|
|
10
|
+
same `comprism` command, sign in and settings.
|
|
11
|
+
|
|
12
|
+
| Option | Pick it when | Install |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| 1. Install script (recommended) | Nothing needs to be on the machine first: no Node, no Python | `curl -fsSL https://yourco.completionprism.com/install.sh \| sh` |
|
|
15
|
+
| 2. npm | You already use Node 22 or newer | `npm install -g @illuminis/comprism` |
|
|
16
|
+
| 3. Python | You have Python 3.9 or newer and no Node | `pip install comprism-cli` (or `pipx install comprism-cli`) |
|
|
17
|
+
|
|
18
|
+
On Windows, the install script is one line in PowerShell:
|
|
19
|
+
`irm https://yourco.completionprism.com/install.ps1 | iex`. The address is your
|
|
20
|
+
own workspace, the one you sign in to. A company that allows only its own
|
|
21
|
+
package sources installs the Python option from the workspace:
|
|
22
|
+
`pip install --index-url https://yourco.completionprism.com/pypi/simple/ comprism-cli`.
|
|
23
|
+
|
|
24
|
+
Then sign in:
|
|
25
|
+
|
|
9
26
|
```bash
|
|
10
|
-
npm install -g @illuminis/comprism
|
|
11
27
|
comprism login
|
|
12
28
|
```
|
|
13
29
|
|
|
14
30
|
That is the whole install. It configures itself, including the one line it adds
|
|
15
31
|
to your shell profile, and it says what it touched. npm hides that message by
|
|
16
32
|
default, so add `--foreground-scripts` to the install if you want to read it.
|
|
33
|
+
A Python install finishes its setup on the first `comprism login`.
|
|
17
34
|
|
|
18
35
|
No API key to export. The keys live in your workspace, and every decision is
|
|
19
36
|
made on our servers.
|
|
20
37
|
|
|
38
|
+
Sign in from an interactive terminal. `comprism login` and its `connect` alias
|
|
39
|
+
exit with status 1 when input is piped or redirected, before asking for credentials
|
|
40
|
+
or contacting the service. Sign in first, then run commands from your script.
|
|
41
|
+
|
|
21
42
|
```bash
|
|
43
|
+
comprism status # which account and company this machine is signed in to
|
|
22
44
|
comprism check # what this machine still needs, and how to add it
|
|
45
|
+
comprism install # redo the shell setup, if the install could not
|
|
46
|
+
comprism cost # what a session cost, and which factors drove it
|
|
23
47
|
comprism uninstall # takes the line back out
|
|
24
48
|
```
|
|
25
49
|
|
|
50
|
+
`comprism check` exits with status 1 while setup is unfinished, so a script can
|
|
51
|
+
test for it. `comprism --version` prints the installed version. `comprism
|
|
52
|
+
update` installs a newer version the same way this one was installed; your sign
|
|
53
|
+
in and the single profile line are kept.
|
|
54
|
+
|
|
55
|
+
`comprism uninstall` removes the profile line, the generated shell script and
|
|
56
|
+
any old Claude Desktop entry of ours; for an install script install it also
|
|
57
|
+
removes the program. It leaves your sign in and settings in `~/.comprism` and
|
|
58
|
+
says so. Run `comprism logout` first to revoke this machine's key. Then remove
|
|
59
|
+
the tool the way it was installed: nothing more for the install script,
|
|
60
|
+
`npm uninstall -g @illuminis/comprism` for npm, `pip uninstall comprism-cli`
|
|
61
|
+
for Python. Delete `~/.comprism` to remove every trace.
|
|
62
|
+
|
|
63
|
+
Automatic shell setup covers zsh and bash on macOS and Linux, and PowerShell on
|
|
64
|
+
Windows. The Windows install script also puts the tool on your user PATH, so
|
|
65
|
+
Command Prompt finds it too.
|
|
66
|
+
|
|
67
|
+
## Where the money went
|
|
68
|
+
|
|
69
|
+
`comprism cost` breaks down a session without leaving the terminal: what it
|
|
70
|
+
cost, what it would have cost, and the five factors behind the difference, then
|
|
71
|
+
every provider call with its tokens, its price and the decision that chose that
|
|
72
|
+
model.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
comprism cost # the newest session this workspace recorded
|
|
76
|
+
comprism cost srv_69e322469de33b90 # a particular one
|
|
77
|
+
cost # inside a session, meaning this one
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The figures come from the same service the portal reads, so the two can never
|
|
81
|
+
quote different numbers for one piece of work. A factor nothing measured prints
|
|
82
|
+
blank, never zero: a zero in a cost column reads as a measurement.
|
|
83
|
+
|
|
26
84
|
## The coding agent
|
|
27
85
|
|
|
28
86
|
`comprism work` reads your project, changes files, runs commands, runs your
|
|
@@ -124,7 +182,8 @@ reach depends on the keys on your account.
|
|
|
124
182
|
|
|
125
183
|
### Requirements
|
|
126
184
|
|
|
127
|
-
|
|
185
|
+
A workspace you have signed in to with `comprism login`. Node 22 or newer for
|
|
186
|
+
the npm option only; the install script and Python options carry their own.
|
|
128
187
|
|
|
129
188
|
---
|
|
130
189
|
|
package/out/agent/command.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { type JobResult } from "./session";
|
|
2
|
+
import { type OutputChoice } from "../lib/output";
|
|
2
3
|
export interface AgentArgs {
|
|
3
4
|
request: string[];
|
|
5
|
+
/** Inside a session: background processes outlive this job and are stopped
|
|
6
|
+
* when the session ends (manual 5.13). */
|
|
7
|
+
keepProcesses?: boolean;
|
|
8
|
+
/** Seconds each command may run before it is stopped (manual 5.12). */
|
|
9
|
+
timeout?: number;
|
|
10
|
+
/** Start from this GitHub issue (manual 5.20). */
|
|
11
|
+
issue?: number;
|
|
12
|
+
/** Lead the team defined under this name (manual 5.27). */
|
|
13
|
+
team?: string;
|
|
14
|
+
/** A review, not a request: the instruction is the service's (manual 5.19). */
|
|
15
|
+
review?: {
|
|
16
|
+
pr?: number;
|
|
17
|
+
post?: boolean;
|
|
18
|
+
};
|
|
4
19
|
/** Start it and hand the terminal back. See `runDetached` for what this can
|
|
5
20
|
* and cannot do. */
|
|
6
21
|
background?: boolean;
|
|
@@ -14,6 +29,14 @@ export interface AgentArgs {
|
|
|
14
29
|
/** Run `model` and nothing else, whatever the router would prefer. */
|
|
15
30
|
pin?: boolean;
|
|
16
31
|
maxSpend?: number;
|
|
32
|
+
/** Stop after this many steps (manual 4.13). */
|
|
33
|
+
maxSteps?: number;
|
|
34
|
+
/** No new step after this many minutes (manual 10.7). */
|
|
35
|
+
maxMinutes?: number;
|
|
36
|
+
/** Reasoning effort: low, medium or high (manual 4.18). */
|
|
37
|
+
effort?: string;
|
|
38
|
+
/** Run commands in the operating system's sandbox (manual 4.12). */
|
|
39
|
+
sandbox?: boolean;
|
|
17
40
|
/** `--allow "python3 grade.py"`, repeatable. Exact commands, pre approved. */
|
|
18
41
|
allow?: string[];
|
|
19
42
|
yes?: boolean;
|
|
@@ -31,7 +54,13 @@ export interface AgentArgs {
|
|
|
31
54
|
* already said. Not the same as `resume`, which re-enters a job that stopped
|
|
32
55
|
* part way; this is the ordinary next question in a conversation. */
|
|
33
56
|
continuesJob?: string;
|
|
57
|
+
/** Which conversation this job belongs to, so the server files it under the
|
|
58
|
+
* session the person is actually in rather than under their whole day. */
|
|
59
|
+
conversationId?: string;
|
|
34
60
|
cwd?: string;
|
|
61
|
+
/** Other folders the person named with `--add-folder` or `folders add`
|
|
62
|
+
* (manual 2.3). Nothing else ever becomes an allowed folder. */
|
|
63
|
+
addFolders?: string[];
|
|
35
64
|
/**
|
|
36
65
|
* Ask the person a yes or no question.
|
|
37
66
|
*
|
|
@@ -44,6 +73,9 @@ export interface AgentArgs {
|
|
|
44
73
|
* what the standalone command wants.
|
|
45
74
|
*/
|
|
46
75
|
confirm?: (question: string) => Promise<boolean>;
|
|
76
|
+
/** A line of text from the same lent keyboard, for the plan question and the
|
|
77
|
+
* agent's questions. Null means Ctrl+C. */
|
|
78
|
+
prompt?: (question: string) => Promise<string | null>;
|
|
47
79
|
/**
|
|
48
80
|
* Files already uploaded, to be put in front of this question.
|
|
49
81
|
*
|
|
@@ -52,6 +84,12 @@ export interface AgentArgs {
|
|
|
52
84
|
* here would be sending them twice and storing them nowhere useful.
|
|
53
85
|
*/
|
|
54
86
|
attachments?: string[];
|
|
87
|
+
/** Names of files the person named that could not be attached, so the
|
|
88
|
+
* agent is told rather than left to guess (manual 3.10). */
|
|
89
|
+
leftOut?: string[];
|
|
90
|
+
/** The terminal to ask approval questions at when standard input is a
|
|
91
|
+
* pipe (manual 3.9). Never the pipe itself. */
|
|
92
|
+
keyboard?: NodeJS.ReadableStream | null;
|
|
55
93
|
/**
|
|
56
94
|
* Handed a way to stop the job, as soon as there is one to stop.
|
|
57
95
|
*
|
|
@@ -65,10 +103,21 @@ export interface AgentArgs {
|
|
|
65
103
|
* to it while it runs. A caller that owns the keyboard needs both, because
|
|
66
104
|
* a person watching a job go the wrong way at step three should be able to
|
|
67
105
|
* correct it rather than stop it and start again. */
|
|
68
|
-
onStarted?: (controls:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
106
|
+
onStarted?: (controls: JobControls) => void;
|
|
107
|
+
/** `--output` and `--quiet`, and whether standard output is a terminal
|
|
108
|
+
* (manual 10.2, 10.3). Absent inside a session, which always draws. */
|
|
109
|
+
output?: OutputChoice;
|
|
110
|
+
/** `--schema`: the answer must be JSON of this shape (manual 10.5). */
|
|
111
|
+
answerSchema?: Record<string, unknown>;
|
|
112
|
+
/** A queued job this runner claimed (10.17 to 10.19); the service holds
|
|
113
|
+
* its request, mode and limits. */
|
|
114
|
+
queuedJob?: string;
|
|
115
|
+
/** `--config`: settings read as if they were the project file (10.8). */
|
|
116
|
+
settingsConfig?: Record<string, unknown>;
|
|
117
|
+
/** `--no-personal`: leave out personal settings and instructions (10.8). */
|
|
118
|
+
noPersonal?: boolean;
|
|
119
|
+
/** Start in the full view (manual 6.9), because Ctrl+O was pressed earlier. */
|
|
120
|
+
fullView?: boolean;
|
|
72
121
|
/**
|
|
73
122
|
* Told what the job cost, once it has finished.
|
|
74
123
|
*
|
|
@@ -81,6 +130,28 @@ export interface AgentArgs {
|
|
|
81
130
|
*/
|
|
82
131
|
onFinished?: (result: JobResult) => void;
|
|
83
132
|
}
|
|
133
|
+
export declare const MODES: string[];
|
|
134
|
+
/** What a caller holding the keyboard can do to a running job (manual 6.7 to
|
|
135
|
+
* 6.9). Every one of them is a message to the service, which decides. */
|
|
136
|
+
export interface JobControls {
|
|
137
|
+
cancel: () => void;
|
|
138
|
+
steer: (text: string) => void;
|
|
139
|
+
pause: () => void;
|
|
140
|
+
carryOn: (text: string) => void;
|
|
141
|
+
isPaused: () => boolean;
|
|
142
|
+
queue: (clear: boolean) => void;
|
|
143
|
+
setFull: (on: boolean) => void;
|
|
144
|
+
/** Helpers running now, as the service reported them (manual 5.25). */
|
|
145
|
+
helpers: () => Array<{
|
|
146
|
+
id: string;
|
|
147
|
+
question: string;
|
|
148
|
+
agent?: string | null;
|
|
149
|
+
}>;
|
|
150
|
+
/** `stop h2`: the service stops that helper, the rest carry on. */
|
|
151
|
+
stopHelper: (id: string) => void;
|
|
152
|
+
/** The job's id, once the service has issued it. */
|
|
153
|
+
jobId: () => string | undefined;
|
|
154
|
+
}
|
|
84
155
|
export declare function runAgent(args: AgentArgs): Promise<number>;
|
|
85
156
|
/** The help text. Written to be read by somebody who has not read anything else. */
|
|
86
157
|
export declare const AGENT_HELP: string;
|
package/out/agent/command.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AGENT_HELP = void 0;
|
|
36
|
+
exports.AGENT_HELP = exports.MODES = void 0;
|
|
37
37
|
exports.runAgent = runAgent;
|
|
38
38
|
/**
|
|
39
39
|
* `comprism work` — the coding agent, from a terminal.
|
|
@@ -56,8 +56,11 @@ exports.runAgent = runAgent;
|
|
|
56
56
|
const fs = __importStar(require("fs"));
|
|
57
57
|
const readline = __importStar(require("readline"));
|
|
58
58
|
const connection_1 = require("../lib/connection");
|
|
59
|
+
const project_1 = require("../lib/project");
|
|
59
60
|
const session_1 = require("./session");
|
|
60
61
|
const ui = __importStar(require("./render"));
|
|
62
|
+
const output_1 = require("../lib/output");
|
|
63
|
+
const self_1 = require("../lib/self");
|
|
61
64
|
/** The WebSocket the session needs is a global from Node 22.
|
|
62
65
|
*
|
|
63
66
|
* Checked here with a message somebody can act on. Without it the failure is
|
|
@@ -70,7 +73,7 @@ function requireWebSocket() {
|
|
|
70
73
|
ui.dim(` You are on ${process.version}. Upgrade Node and try again.\n`));
|
|
71
74
|
return false;
|
|
72
75
|
}
|
|
73
|
-
|
|
76
|
+
exports.MODES = ["read_only", "approve_writes", "auto_edit", "full_auto"];
|
|
74
77
|
/**
|
|
75
78
|
* Start a job, hand the terminal back, and keep working.
|
|
76
79
|
*
|
|
@@ -105,28 +108,83 @@ async function runDetached(args) {
|
|
|
105
108
|
const log = fs.openSync(logPath, "a");
|
|
106
109
|
// Detached, with its own process group, so closing the terminal does not take
|
|
107
110
|
// the job with it. `unref` is what actually lets this process exit.
|
|
108
|
-
const child = spawn(
|
|
109
|
-
|
|
111
|
+
const child = spawn(...(0, self_1.selfLaunch)([
|
|
112
|
+
"agent",
|
|
110
113
|
...args.request,
|
|
111
114
|
"--mode", mode, "--yes",
|
|
112
115
|
...(args.model ? ["--model", args.model] : []),
|
|
113
116
|
...(args.pin ? ["--pin"] : []),
|
|
114
117
|
...(args.maxSpend ? ["--max-spend", String(args.maxSpend)] : []),
|
|
118
|
+
...(args.timeout ? ["--timeout", String(args.timeout)] : []),
|
|
115
119
|
...(args.allow ?? []).flatMap((c) => ["--allow", c]),
|
|
116
120
|
...(args.cwd ? ["--cwd", args.cwd] : []),
|
|
117
|
-
|
|
121
|
+
...(args.addFolders ?? []).flatMap((f) => ["--add-folder", f]),
|
|
122
|
+
]), {
|
|
123
|
+
detached: true, stdio: ["ignore", log, log],
|
|
124
|
+
// Marked, so the job is recorded as a background job and its log can be
|
|
125
|
+
// found again by `comprism jobs attach` (manual 5.14).
|
|
126
|
+
env: { ...process.env, NO_COLOR: "1", COMPRISM_BACKGROUND: "1", COMPRISM_JOB_LOG: logPath },
|
|
127
|
+
});
|
|
118
128
|
child.unref();
|
|
119
129
|
process.stdout.write(ui.green(` Started in the background as process ${child.pid}.\n`)
|
|
130
|
+
+ ui.dim(" comprism jobs lists it; comprism jobs attach <job> watches it.\n")
|
|
120
131
|
+ ui.dim(` Follow it: tail -f ${logPath}\n`)
|
|
121
132
|
+ ui.dim(" It keeps working while you do something else. This machine has "
|
|
122
133
|
+ "to stay awake: the files are here, so the work is too.\n"));
|
|
123
134
|
return 0;
|
|
124
135
|
}
|
|
125
136
|
async function runAgent(args) {
|
|
137
|
+
const output = args.output;
|
|
138
|
+
const json = output?.format === "json";
|
|
139
|
+
const streaming = output?.format === "stream";
|
|
140
|
+
// A stream is lines the service built, printed as they come (manual 10.4).
|
|
141
|
+
// If the program reading it goes away, the job is stopped and the exit is
|
|
142
|
+
// 141, with nothing said about it.
|
|
143
|
+
let pipeClosed = false;
|
|
144
|
+
let finishedLine = false;
|
|
145
|
+
let stopJob = null;
|
|
146
|
+
const emit = (items) => {
|
|
147
|
+
for (const item of items) {
|
|
148
|
+
if (pipeClosed)
|
|
149
|
+
return;
|
|
150
|
+
if (item.event === "job_finished")
|
|
151
|
+
finishedLine = true;
|
|
152
|
+
process.stdout.write(JSON.stringify(item) + "\n");
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
if (streaming) {
|
|
156
|
+
process.stdout.on("error", (e) => {
|
|
157
|
+
if (e.code !== "EPIPE")
|
|
158
|
+
return;
|
|
159
|
+
pipeClosed = true;
|
|
160
|
+
stopJob?.();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** An ending the service never saw: its number, and with --output json the
|
|
164
|
+
* result object in its published shape (manual 10.3, Appendix B). */
|
|
165
|
+
const ended = (reason, why) => {
|
|
166
|
+
if (json)
|
|
167
|
+
(0, output_1.printJson)((0, output_1.localResult)(reason, why));
|
|
168
|
+
if (streaming)
|
|
169
|
+
emit([{ event: "job_finished", ...(0, output_1.localResult)(reason, why) }]);
|
|
170
|
+
return (0, output_1.exitFor)(reason);
|
|
171
|
+
};
|
|
172
|
+
// A folder that does not exist is refused before anything runs or is charged
|
|
173
|
+
// (manual 2.1, exit 2 in Appendix B).
|
|
174
|
+
const folders = [];
|
|
175
|
+
for (const raw of [args.cwd ?? process.cwd(), ...(args.addFolders ?? [])]) {
|
|
176
|
+
const found = (0, project_1.checkFolder)(raw);
|
|
177
|
+
if (!found.ok) {
|
|
178
|
+
process.stderr.write(ui.red(` ${found.why}`) + ui.dim(" Nothing ran and nothing was charged.\n"));
|
|
179
|
+
return 2;
|
|
180
|
+
}
|
|
181
|
+
folders.push(found.path);
|
|
182
|
+
}
|
|
183
|
+
const [startFolder, ...added] = folders;
|
|
126
184
|
if (!requireWebSocket())
|
|
127
185
|
return 1;
|
|
128
186
|
if (args.background)
|
|
129
|
-
return runDetached(args);
|
|
187
|
+
return runDetached({ ...args, cwd: startFolder, addFolders: added });
|
|
130
188
|
const connection = (0, connection_1.readConnection)();
|
|
131
189
|
if (!connection?.url) {
|
|
132
190
|
process.stderr.write(
|
|
@@ -135,7 +193,7 @@ async function runAgent(args) {
|
|
|
135
193
|
// brand new user is given was a dead end.
|
|
136
194
|
ui.red(" Not signed in.") +
|
|
137
195
|
ui.dim(" Run `comprism login` first.\n"));
|
|
138
|
-
return
|
|
196
|
+
return ended("signed_out", "Not signed in. Run comprism login first.");
|
|
139
197
|
}
|
|
140
198
|
// The machine credential, not the session token. The session token belongs to
|
|
141
199
|
// a browser sign-in and is short lived; this is the long-lived credential
|
|
@@ -146,14 +204,28 @@ async function runAgent(args) {
|
|
|
146
204
|
if (!credential) {
|
|
147
205
|
process.stderr.write(ui.red(" This machine has no workspace key.") +
|
|
148
206
|
ui.dim(" Run `comprism connect` again to mint one.\n"));
|
|
149
|
-
return
|
|
207
|
+
return ended("signed_out", "This machine has no workspace key. Run comprism connect again.");
|
|
150
208
|
}
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
209
|
+
const project = (0, project_1.resolveProject)(startFolder, added);
|
|
210
|
+
const root = project.root;
|
|
211
|
+
// Absent means the settings decide (manual 2.10): the service resolves the
|
|
212
|
+
// mode from the local and project settings files and your account.
|
|
213
|
+
const mode = args.mode;
|
|
214
|
+
if (mode !== undefined && !exports.MODES.includes(mode)) {
|
|
154
215
|
process.stderr.write(ui.red(` ${mode} is not a permission mode.`) +
|
|
155
|
-
ui.dim(` One of: ${MODES.join(", ")}\n`));
|
|
156
|
-
return
|
|
216
|
+
ui.dim(` One of: ${exports.MODES.join(", ")}\n`));
|
|
217
|
+
return ended("usage", `${mode} is not a permission mode.`);
|
|
218
|
+
}
|
|
219
|
+
// `--sandbox` never runs without one (manual 4.12): refused before anything
|
|
220
|
+
// runs or is charged.
|
|
221
|
+
if (args.sandbox) {
|
|
222
|
+
const { availability } = await Promise.resolve().then(() => __importStar(require("../executor/sandbox")));
|
|
223
|
+
const found = availability();
|
|
224
|
+
if (!found.system) {
|
|
225
|
+
process.stderr.write(ui.red(` --sandbox cannot start: ${found.why}.`)
|
|
226
|
+
+ ui.dim(" Nothing ran and nothing was charged.\n"));
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
157
229
|
}
|
|
158
230
|
const request = args.request.join(" ").trim();
|
|
159
231
|
if (!request && !args.resume) {
|
|
@@ -163,33 +235,99 @@ async function runAgent(args) {
|
|
|
163
235
|
// Headless when asked for, and also when there is no terminal at all: piped
|
|
164
236
|
// into a script, nobody is there to answer an approval, and pretending
|
|
165
237
|
// otherwise would hang the pipeline until it timed out.
|
|
166
|
-
|
|
238
|
+
//
|
|
239
|
+
// Standard input that is a pipe is material, never answers (manual 3.9):
|
|
240
|
+
// questions then go to the terminal the caller opened, and with none the
|
|
241
|
+
// job is unattended.
|
|
242
|
+
const questions = process.stdin.isTTY
|
|
243
|
+
? process.stdin : args.keyboard ?? null;
|
|
244
|
+
const headless = Boolean(args.yes) || !questions;
|
|
245
|
+
// ONE reader for the whole job when nobody lent a keyboard: it carries what
|
|
246
|
+
// is typed while the agent works, and it answers questions and approvals.
|
|
247
|
+
// Two readers on one keyboard divide the typing, so an answer also arrived
|
|
248
|
+
// as a correction to the job.
|
|
249
|
+
let steering = null;
|
|
250
|
+
let waiting = null;
|
|
251
|
+
const ownPrompt = (question) => new Promise((resolve) => {
|
|
252
|
+
process.stdout.write(` ${question} `);
|
|
253
|
+
waiting = resolve;
|
|
254
|
+
});
|
|
255
|
+
const lent = args.confirm || headless ? {} : {
|
|
256
|
+
prompt: ownPrompt,
|
|
257
|
+
confirm: async (question) => /^y(es)?$/i.test(((await ownPrompt(question)) ?? "").trim()),
|
|
258
|
+
};
|
|
259
|
+
// With standard output going to a script, everything the job says on the
|
|
260
|
+
// way goes to standard error, and only the result goes to standard output
|
|
261
|
+
// (manual 10.2). `--quiet` drops the progress altogether.
|
|
262
|
+
const toErr = output ? (0, output_1.progressToErr)(output) : false;
|
|
263
|
+
const write = output?.quiet || streaming ? () => undefined
|
|
264
|
+
: toErr ? (s) => { process.stderr.write(s); } : (s) => { process.stdout.write(s); };
|
|
167
265
|
const session = new session_1.TerminalSession({
|
|
168
266
|
baseUrl: connection.url,
|
|
169
267
|
credential,
|
|
170
268
|
root,
|
|
269
|
+
project,
|
|
171
270
|
permissionMode: mode,
|
|
172
271
|
planOnly: Boolean(args.plan),
|
|
173
272
|
planApproved: Boolean(args.approvePlan),
|
|
174
273
|
model: args.model,
|
|
175
274
|
pinModel: Boolean(args.pin),
|
|
176
275
|
maxSpendUsd: args.maxSpend,
|
|
276
|
+
maxSteps: args.maxSteps,
|
|
277
|
+
maxMinutes: args.maxMinutes,
|
|
278
|
+
effort: args.effort,
|
|
279
|
+
sandbox: Boolean(args.sandbox),
|
|
280
|
+
commandTimeoutS: args.timeout,
|
|
281
|
+
issue: args.issue,
|
|
282
|
+
team: args.team,
|
|
283
|
+
review: args.review,
|
|
284
|
+
keepProcesses: Boolean(args.keepProcesses),
|
|
177
285
|
approvedCommands: args.allow ?? [],
|
|
178
286
|
attachmentIds: args.attachments ?? [],
|
|
287
|
+
leftOut: args.leftOut ?? [],
|
|
179
288
|
headless,
|
|
180
289
|
confirm: args.confirm,
|
|
290
|
+
prompt: args.prompt,
|
|
291
|
+
...lent,
|
|
181
292
|
resumeJobId: args.resume,
|
|
182
293
|
continuesJob: args.continuesJob,
|
|
294
|
+
conversationId: args.conversationId,
|
|
183
295
|
withoutMap: Boolean(args.noMap),
|
|
184
|
-
|
|
296
|
+
...(args.answerSchema ? { answerSchema: args.answerSchema } : {}),
|
|
297
|
+
...(args.queuedJob ? { queuedJob: args.queuedJob } : {}),
|
|
298
|
+
...(args.settingsConfig ? { settingsConfig: args.settingsConfig } : {}),
|
|
299
|
+
noPersonal: Boolean(args.noPersonal),
|
|
300
|
+
...(streaming ? { onStream: emit } : {}),
|
|
301
|
+
}, write);
|
|
302
|
+
stopJob = () => session.cancel();
|
|
185
303
|
// Ctrl+C stops the JOB, not the process. Killing the process would leave the
|
|
186
304
|
// job running on the server with nothing watching it, and whatever the agent
|
|
187
305
|
// had started on this machine would carry on unsupervised.
|
|
188
|
-
|
|
306
|
+
let interrupted = false;
|
|
307
|
+
const onInterrupt = () => {
|
|
308
|
+
interrupted = true;
|
|
309
|
+
// Ctrl+C at a question answers it as stopped, then stops the job.
|
|
310
|
+
const w = waiting;
|
|
311
|
+
waiting = null;
|
|
312
|
+
if (w) {
|
|
313
|
+
process.stdout.write("\n");
|
|
314
|
+
w(null);
|
|
315
|
+
}
|
|
316
|
+
session.cancel();
|
|
317
|
+
};
|
|
189
318
|
process.on("SIGINT", onInterrupt);
|
|
319
|
+
session.full = Boolean(args.fullView);
|
|
190
320
|
args.onStarted?.({
|
|
191
321
|
cancel: () => session.cancel(),
|
|
192
322
|
steer: (text) => session.steer(text),
|
|
323
|
+
pause: () => session.pause(),
|
|
324
|
+
carryOn: (text) => session.carryOn(text),
|
|
325
|
+
isPaused: () => session.paused,
|
|
326
|
+
queue: (clear) => session.queue(clear),
|
|
327
|
+
setFull: (on) => { session.full = on; },
|
|
328
|
+
jobId: () => session.jobId,
|
|
329
|
+
helpers: () => [...session.helpers.values()],
|
|
330
|
+
stopHelper: (id) => session.stopHelper(id),
|
|
193
331
|
});
|
|
194
332
|
// Anything typed while it works. Not a prompt, deliberately: the run is
|
|
195
333
|
// printing, and a prompt competing with it for the same lines is unreadable.
|
|
@@ -198,10 +336,15 @@ async function runAgent(args) {
|
|
|
198
336
|
// `confirm` is that one component owns the terminal; opening a reader here as
|
|
199
337
|
// well would put the same two owners back on it by another door, and the
|
|
200
338
|
// caller is the one already reading what gets typed.
|
|
201
|
-
let steering = null;
|
|
202
339
|
if (!headless && !args.confirm) {
|
|
203
|
-
steering = readline.createInterface({ input:
|
|
340
|
+
steering = readline.createInterface({ input: questions, terminal: false });
|
|
204
341
|
steering.on("line", (line) => {
|
|
342
|
+
const w = waiting;
|
|
343
|
+
if (w) {
|
|
344
|
+
waiting = null;
|
|
345
|
+
w(line);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
205
348
|
const text = line.trim();
|
|
206
349
|
if (text)
|
|
207
350
|
session.steer(text);
|
|
@@ -210,14 +353,54 @@ async function runAgent(args) {
|
|
|
210
353
|
try {
|
|
211
354
|
const result = await session.run(request);
|
|
212
355
|
args.onFinished?.(result);
|
|
213
|
-
// A
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
356
|
+
// A job on its own is its session, and it has ended (manual 7.3).
|
|
357
|
+
if (!args.keepProcesses && args.conversationId) {
|
|
358
|
+
const { review } = await Promise.resolve().then(() => __importStar(require("../commands/review")));
|
|
359
|
+
await review("session_ended", { session_id: args.conversationId }).catch(() => undefined);
|
|
360
|
+
}
|
|
361
|
+
// How it ended is the service's reason word; the number comes from the
|
|
362
|
+
// table the service sent with it (manual 10.6, Appendix B). The only
|
|
363
|
+
// endings decided here are the ones the service could not see: a dropped
|
|
364
|
+
// connection with an action in doubt (8.9), Ctrl+C, and never reaching it.
|
|
365
|
+
const reason = result.outcome === "unknown" ? "unknown"
|
|
366
|
+
: interrupted ? "interrupted"
|
|
367
|
+
: result.ending?.reason ?? result.refusalReason
|
|
368
|
+
?? (result.outcome === "finished" ? "done" : "incomplete");
|
|
369
|
+
if (pipeClosed)
|
|
370
|
+
return (0, output_1.exitFor)("output_closed", result.exitStatus);
|
|
371
|
+
const code = (0, output_1.exitFor)(reason, result.exitStatus);
|
|
372
|
+
if (streaming) {
|
|
373
|
+
// A job refused before it started has sent no stream, so its end is
|
|
374
|
+
// the service's result for the refusal.
|
|
375
|
+
if (!finishedLine) {
|
|
376
|
+
emit([{ event: "job_finished", ...(result.result ?? (0, output_1.localResult)(reason, result.detail ?? reason)) }]);
|
|
377
|
+
}
|
|
378
|
+
if (code !== 0)
|
|
379
|
+
process.stderr.write(` ${result.ending?.line ?? result.detail ?? reason}\n`);
|
|
380
|
+
}
|
|
381
|
+
else if (json) {
|
|
382
|
+
(0, output_1.printJson)(result.result && reason !== "unknown" && !interrupted
|
|
383
|
+
? result.result : (0, output_1.localResult)(reason, result.detail ?? reason));
|
|
384
|
+
}
|
|
385
|
+
else if (toErr) {
|
|
386
|
+
// The final summary, and nothing else, on standard output (10.2).
|
|
387
|
+
const summary = result.ending?.line ?? result.detail ?? "";
|
|
388
|
+
if (summary)
|
|
389
|
+
process.stdout.write(`${summary}\n`);
|
|
390
|
+
if (result.text.trim() && result.text.trim() !== summary)
|
|
391
|
+
process.stdout.write(`${result.text.trim()}\n`);
|
|
392
|
+
}
|
|
393
|
+
if (code !== 0 && output?.quiet) {
|
|
394
|
+
// A number other than 0 comes with a one line reason on standard error.
|
|
395
|
+
process.stderr.write(` ${result.ending?.line ?? result.detail ?? reason}\n`);
|
|
396
|
+
}
|
|
397
|
+
return code;
|
|
217
398
|
}
|
|
218
399
|
catch (err) {
|
|
400
|
+
// The live connection never opened: the service could not be reached
|
|
401
|
+
// (manual 10.14, exit 6).
|
|
219
402
|
process.stderr.write(ui.red(` ${err.message}\n`));
|
|
220
|
-
return
|
|
403
|
+
return ended("unreachable", err.message);
|
|
221
404
|
}
|
|
222
405
|
finally {
|
|
223
406
|
process.off("SIGINT", onInterrupt);
|
|
@@ -231,26 +414,38 @@ ${ui.bold("comprism work")} — a coding agent in your terminal
|
|
|
231
414
|
comprism work "add a test for the parser and make it pass"
|
|
232
415
|
|
|
233
416
|
${ui.bold("What it does")}
|
|
234
|
-
Reads your project, changes files, runs commands and runs your tests
|
|
235
|
-
shows you every change
|
|
417
|
+
Reads your project, changes files, runs commands and runs your tests. It
|
|
418
|
+
shows you every change as it makes it, asks before deleting a file or
|
|
419
|
+
running a command, and can undo the whole job. Each step is routed to whichever
|
|
236
420
|
model finishes the work for least, and the receipt at the end says what it
|
|
237
421
|
cost.
|
|
238
422
|
|
|
239
423
|
${ui.bold("Options")}
|
|
240
424
|
--mode <mode> read_only, approve_writes (default), auto_edit, full_auto
|
|
425
|
+
--worktree <name> work in a separate copy of the repository on its own branch
|
|
241
426
|
--plan work out what it would do and change nothing
|
|
242
427
|
--approve-plan you read the plan and want it built. Files change without
|
|
243
428
|
being asked about one at a time; commands still ask
|
|
244
429
|
--model <id> tell it which model you are running, so the receipt can
|
|
245
430
|
price the saving against it. It may still move you up
|
|
246
|
-
--
|
|
431
|
+
--no-optimize Optimize off: run --model and nothing else (also --pin)
|
|
247
432
|
--max-spend <usd> stop at this much rather than running on
|
|
433
|
+
--max-steps <n> stop after this many steps
|
|
434
|
+
--max-minutes <n> start no new step after this many minutes
|
|
435
|
+
--effort <level> low, medium or high: how carefully the model thinks
|
|
436
|
+
--sandbox run commands in the operating system's sandbox
|
|
437
|
+
--timeout <secs> time limit for each command, two minutes unless set
|
|
438
|
+
--issue <n> start from GitHub issue n; its text is information only
|
|
439
|
+
--team <name> lead the team defined in .completionprism/teams/<name>.md
|
|
248
440
|
--allow "<cmd>" approve one exact command up front, repeatable. The only
|
|
249
441
|
way an unattended run can verify its own work
|
|
250
442
|
--yes never ask; anything needing approval is refused instead
|
|
251
443
|
--background start it and get your terminal back; it keeps working
|
|
252
444
|
--resume <job> carry on from a job you already started
|
|
253
445
|
--cwd <path> work in this folder rather than the current one
|
|
446
|
+
--cloud run it on a CompletionPrism worker with a copy of the
|
|
447
|
+
repository; comprism --resume <session> brings it back
|
|
448
|
+
--add-folder <path> also allow this folder, repeatable
|
|
254
449
|
--no-map do not use the code map for this job; read the files
|
|
255
450
|
|
|
256
451
|
${ui.bold("While it runs")}
|