@illuminis/comprism 0.1.2 → 0.1.4
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 +98 -36
- package/out/agent/command.d.ts +75 -4
- package/out/agent/command.js +220 -25
- package/out/agent/render.d.ts +17 -2
- package/out/agent/render.js +157 -14
- package/out/agent/session.d.ts +141 -2
- package/out/agent/session.js +735 -149
- 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 +78 -11
- 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 +188 -36
- 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 +8 -1
- package/out/commands/repl.js +1207 -118
- 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/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 +186 -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 +95 -0
- package/out/lib/keyboard.js +383 -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/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.js +15 -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/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 +2 -4
- package/out/lib/ui.js +31 -25
- package/out/lib/voice.js +24 -0
- package/out/postinstall.js +42 -14
- 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 +609 -21
- package/package.json +9 -49
package/out/thin.js
CHANGED
|
@@ -56,7 +56,46 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
56
56
|
*/
|
|
57
57
|
const ui = __importStar(require("./lib/ui"));
|
|
58
58
|
const version_1 = require("./lib/version");
|
|
59
|
+
const self_1 = require("./lib/self");
|
|
59
60
|
const out = (s = '') => process.stdout.write(s + '\n');
|
|
61
|
+
/** A refusal a script reads: always on standard error (Appendix B). */
|
|
62
|
+
const err = (s = '') => process.stderr.write(s + '\n');
|
|
63
|
+
/**
|
|
64
|
+
* Piped text, attached as `standard input`, and the terminal to ask approval
|
|
65
|
+
* questions at (manual 3.9). The attachment line goes to standard error when
|
|
66
|
+
* standard output is not a terminal, so a script reads only the result.
|
|
67
|
+
* Null when the text was piped and could not be attached: the request is not
|
|
68
|
+
* sent without the material it was about.
|
|
69
|
+
*/
|
|
70
|
+
async function attachStdin(piped) {
|
|
71
|
+
const { attachPiped, openTerminal } = await Promise.resolve().then(() => __importStar(require('./lib/stdin')));
|
|
72
|
+
const keyboard = process.stdin.isTTY ? null : openTerminal();
|
|
73
|
+
if (!piped?.trim())
|
|
74
|
+
return { ids: [], keyboard };
|
|
75
|
+
const say = process.stdout.isTTY ? out : err;
|
|
76
|
+
const { readConnection } = await Promise.resolve().then(() => __importStar(require('./lib/connection')));
|
|
77
|
+
const conn = readConnection();
|
|
78
|
+
if (!conn?.url || !conn.workspaceKey)
|
|
79
|
+
return { ids: [], keyboard };
|
|
80
|
+
const attach = await Promise.resolve().then(() => __importStar(require('./lib/attach')));
|
|
81
|
+
const { attached, refused } = await attachPiped(piped, conn.url, conn.workspaceKey);
|
|
82
|
+
for (const line of attach.describe(attached))
|
|
83
|
+
say(ui.c.dim(` ${line}`));
|
|
84
|
+
for (const r of refused)
|
|
85
|
+
err(ui.c.amber(` ${r.why}`));
|
|
86
|
+
if (refused.length > 0) {
|
|
87
|
+
keyboard?.destroy();
|
|
88
|
+
err(ui.c.dim(' Nothing was sent.'));
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return { ids: attached.map((a) => a.id), keyboard };
|
|
92
|
+
}
|
|
93
|
+
/** Flags `work` takes with a value, and flags it takes on their own. */
|
|
94
|
+
const WORK_VALUES = ['--mode', '--model', '--max-spend', '--max-steps', '--effort', '--resume',
|
|
95
|
+
'--cwd', '--allow', '--add-folder', '--timeout', '--issue', '--team', '--output', '--worktree',
|
|
96
|
+
'--schema', '--config', '--max-minutes', '--queued'];
|
|
97
|
+
const WORK_SWITCHES = ['--plan', '--approve-plan', '--pin', '--sandbox', '--yes', '--background',
|
|
98
|
+
'--no-map', '--no-graph', '--quiet', '--no-personal', '--cloud'];
|
|
60
99
|
/**
|
|
61
100
|
* The value after a flag, or undefined when the flag was not given.
|
|
62
101
|
*
|
|
@@ -79,24 +118,296 @@ function usage() {
|
|
|
79
118
|
['comprism work', 'have it do the work: reads your project, edits, runs your tests'],
|
|
80
119
|
['comprism login', 'sign in with your email and password'],
|
|
81
120
|
['comprism', 'start a session and just type your questions'],
|
|
121
|
+
['comprism --continue', 'carry on the most recent session in this project'],
|
|
122
|
+
['comprism --resume [number]', 'carry on a session; with no number, choose from a list'],
|
|
123
|
+
['comprism sessions', 'your recent sessions, newest first; --search to find one'],
|
|
82
124
|
['comprism keys', 'add a provider key, or point at a file of them'],
|
|
83
125
|
['comprism model', 'the model your savings are measured against'],
|
|
84
126
|
['comprism providers', 'every AI vendor and model, and what you can reach'],
|
|
85
|
-
['comprism
|
|
127
|
+
['comprism project', 'the folder to work in, default is where you are'],
|
|
128
|
+
['comprism config', 'settings in force and where each came from'],
|
|
129
|
+
['comprism instructions', 'the instructions the next job receives'],
|
|
130
|
+
['comprism memory', 'this project\'s notes'],
|
|
131
|
+
['comprism skills', 'the skills here and where each came from; /name runs one'],
|
|
132
|
+
['comprism integrations', 'outside tools the agent can call, and their sign in'],
|
|
133
|
+
['comprism plugins', 'packs of skills, helpers, hooks and connections'],
|
|
134
|
+
['comprism worktrees', 'separate copies of the repository'],
|
|
135
|
+
['comprism privacy', 'what your company keeps about your work'],
|
|
136
|
+
['comprism permissions', 'the rules in force here; permissions why <action> "<target>"'],
|
|
86
137
|
['comprism status', 'is this machine signed in, and to what'],
|
|
87
138
|
['comprism check', 'what this machine still needs, and how to add it'],
|
|
88
139
|
['comprism logout', 'sign out, and revoke this machine\'s key'],
|
|
140
|
+
['comprism cost', 'what a session cost, and which factors drove it'],
|
|
141
|
+
['comprism schema output', 'the shape of --output json, for scripts'],
|
|
142
|
+
['comprism usage', 'your coding allowance left this month, and provider limits'],
|
|
143
|
+
['comprism hooks', 'the hooks in force here; hooks test <when> <action> <file> runs them'],
|
|
144
|
+
['comprism update', 'check for a newer version and install it; --check only says'],
|
|
145
|
+
['comprism changes', 'what the agent changed in a job, apart from your own changes'],
|
|
146
|
+
['comprism diff', 'the agent\'s changes, line by line'],
|
|
147
|
+
['comprism rewind <job>', 'put the files back; --to-step N or one file'],
|
|
148
|
+
['comprism schedule', 'requests that run by themselves; schedule add "<when>" "<request>"'],
|
|
149
|
+
['comprism triggers', 'outside events that start a job; triggers add "<request>"'],
|
|
150
|
+
['comprism worker', 'run the jobs queued for this machine, or --cloud for any worker'],
|
|
151
|
+
['comprism install', 'set up this machine again, if the install could not'],
|
|
152
|
+
['comprism uninstall', 'remove comprism from this machine, everything it set up included'],
|
|
89
153
|
]));
|
|
90
154
|
out();
|
|
91
155
|
out(ui.c.dim(` v${version_1.COMPRISM_VERSION} · every decision is made on our servers`));
|
|
92
156
|
out();
|
|
93
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Behind a company proxy (manual 1.11): Node uses HTTPS_PROXY and NO_PROXY for
|
|
160
|
+
* its own requests only when NODE_USE_ENV_PROXY is set as it starts, so the
|
|
161
|
+
* tool starts itself once more with it set. Nothing changes without a proxy.
|
|
162
|
+
*/
|
|
163
|
+
function throughProxy() {
|
|
164
|
+
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
165
|
+
if (!proxy || process.env.NODE_USE_ENV_PROXY)
|
|
166
|
+
return false;
|
|
167
|
+
const { spawnSync } = require('child_process');
|
|
168
|
+
// Node 22 calls its proxy support experimental and says so on every start;
|
|
169
|
+
// that one notice is not the person's concern.
|
|
170
|
+
const [cmd, argv] = (0, self_1.selfLaunch)(process.argv.slice(2), ['--disable-warning=UNDICI-EHPA']);
|
|
171
|
+
const r = spawnSync(cmd, argv, {
|
|
172
|
+
stdio: 'inherit', env: { ...process.env, NODE_USE_ENV_PROXY: '1', ...((0, self_1.isStandalone)() ? { NODE_NO_WARNINGS: '1' } : {}) },
|
|
173
|
+
});
|
|
174
|
+
process.exitCode = r.status ?? (r.signal === 'SIGINT' ? 130 : 1);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
94
177
|
async function main() {
|
|
178
|
+
// The sandbox's network relay (manual 4.12). Under Node it runs with `-e`;
|
|
179
|
+
// the ready to run program has no `-e`, so it asks for it by name.
|
|
180
|
+
if (process.argv[2] === '__relay' && (0, self_1.isStandalone)() && process.env.COMPRISM_RELAY) {
|
|
181
|
+
new Function('require', 'process', process.env.COMPRISM_RELAY)(require, process);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (throughProxy())
|
|
185
|
+
return;
|
|
186
|
+
// `--plain` anywhere means no color and no redrawn lines (manual 6.12). Taken
|
|
187
|
+
// off the line so no command reads it as a word of its request.
|
|
188
|
+
const plainAt = process.argv.indexOf('--plain', 2);
|
|
189
|
+
if (plainAt !== -1)
|
|
190
|
+
process.argv.splice(plainAt, 1);
|
|
191
|
+
const { applyLook } = await Promise.resolve().then(() => __importStar(require('./lib/machine')));
|
|
192
|
+
applyLook(plainAt !== -1);
|
|
95
193
|
const [, , command, ...args] = process.argv;
|
|
194
|
+
// Carrying on a session (manual 8.4, 8.5). Flags rather than words, so
|
|
195
|
+
// `comprism --resume` reads as the same product starting, not a command.
|
|
196
|
+
if (command === '--continue' || command === '-c') {
|
|
197
|
+
const { runRepl } = await Promise.resolve().then(() => __importStar(require('./commands/repl')));
|
|
198
|
+
return runRepl({ latest: true });
|
|
199
|
+
}
|
|
200
|
+
if (command === '--resume' || command === '-r') {
|
|
201
|
+
const { runRepl } = await Promise.resolve().then(() => __importStar(require('./commands/repl')));
|
|
202
|
+
// `--resume --pr 42`: the latest session that worked on that pull request (8.10).
|
|
203
|
+
const prAt = args.indexOf('--pr');
|
|
204
|
+
if (prAt !== -1) {
|
|
205
|
+
const { sessions: ask8, failure } = await Promise.resolve().then(() => __importStar(require('./lib/sessions')));
|
|
206
|
+
const found = await ask8('by_pr', { pr: args[prAt + 1] ?? '' });
|
|
207
|
+
const first = found.sessions?.sessions?.[0]?.number;
|
|
208
|
+
if (!first) {
|
|
209
|
+
out(found.ok ? (found.sessions?.lines?.[0] ?? '') : ui.fail(failure(found)));
|
|
210
|
+
process.exitCode = 1;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
return runRepl({ resume: first });
|
|
214
|
+
}
|
|
215
|
+
const id = args.find((a) => a && !a.startsWith('-'));
|
|
216
|
+
if (id) {
|
|
217
|
+
// A session a cloud job worked on: its changes come here first (10.18).
|
|
218
|
+
const { sessions: ask8 } = await Promise.resolve().then(() => __importStar(require('./lib/sessions')));
|
|
219
|
+
const found = await ask8('resume', { key: id });
|
|
220
|
+
if (found.ok && found.sessions?.session_id) {
|
|
221
|
+
const { handBack } = await Promise.resolve().then(() => __importStar(require('./commands/unattended')));
|
|
222
|
+
await handBack(found.sessions.session_id, process.cwd());
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return runRepl(id ? { resume: id } : { pick: true });
|
|
226
|
+
}
|
|
96
227
|
switch (command) {
|
|
228
|
+
case 'sessions': {
|
|
229
|
+
const { cmdSessions } = await Promise.resolve().then(() => __importStar(require('./lib/sessions')));
|
|
230
|
+
process.exitCode = await cmdSessions(args);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
case 'review': {
|
|
234
|
+
// A read only job whose instruction is held on the service (manual 5.19).
|
|
235
|
+
const pr = flag(args, '--pr');
|
|
236
|
+
if (args.includes('--pr') && !/^\d+$/.test(pr ?? '')) {
|
|
237
|
+
out(ui.fail('--pr takes a pull request number, for example --pr 42.'));
|
|
238
|
+
process.exitCode = 2;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (args.includes('--post') && !pr) {
|
|
242
|
+
out(ui.fail('--post needs --pr: findings are posted to a pull request.'));
|
|
243
|
+
process.exitCode = 2;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const { runAgent } = await Promise.resolve().then(() => __importStar(require('./agent/command')));
|
|
247
|
+
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('node:crypto')));
|
|
248
|
+
process.exitCode = await runAgent({
|
|
249
|
+
request: [pr ? `review pull request #${pr}` : 'review my uncommitted changes'],
|
|
250
|
+
mode: 'read_only',
|
|
251
|
+
review: { ...(pr ? { pr: Number(pr) } : {}), post: args.includes('--post') },
|
|
252
|
+
conversationId: randomUUID(),
|
|
253
|
+
cwd: flag(args, '--cwd') ?? (await Promise.resolve().then(() => __importStar(require('./commands/settings')))).currentWorkspace().path,
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
case 'integrations': {
|
|
258
|
+
const { cmdIntegrations } = await Promise.resolve().then(() => __importStar(require('./commands/integrations')));
|
|
259
|
+
process.exitCode = await cmdIntegrations(args);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
case 'plugins': {
|
|
263
|
+
const { cmdPlugins } = await Promise.resolve().then(() => __importStar(require('./commands/plugins')));
|
|
264
|
+
process.exitCode = await cmdPlugins(args);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
case 'skills': {
|
|
268
|
+
const { cmdSkills } = await Promise.resolve().then(() => __importStar(require('./commands/skills')));
|
|
269
|
+
process.exitCode = await cmdSkills(args);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
case 'agents': {
|
|
273
|
+
const { cmdAgents } = await Promise.resolve().then(() => __importStar(require('./commands/agents')));
|
|
274
|
+
process.exitCode = await cmdAgents(args);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
case 'jobs': {
|
|
278
|
+
const { cmdJobs } = await Promise.resolve().then(() => __importStar(require('./commands/jobs')));
|
|
279
|
+
process.exitCode = await cmdJobs(args);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
case 'permissions': {
|
|
283
|
+
const { cmdPermissions } = await Promise.resolve().then(() => __importStar(require('./commands/permissions')));
|
|
284
|
+
process.exitCode = await cmdPermissions(args);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
case 'config': {
|
|
288
|
+
const { cmdConfig } = await Promise.resolve().then(() => __importStar(require('./commands/config')));
|
|
289
|
+
process.exitCode = await cmdConfig(args);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Every instruction source in force, and your personal ones (manual 2.5 to 2.8).
|
|
293
|
+
case 'instructions': {
|
|
294
|
+
const { cmdInstructions } = await Promise.resolve().then(() => __importStar(require('./commands/instructions')));
|
|
295
|
+
process.exitCode = await cmdInstructions(args);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
// This project's notes (manual 2.9).
|
|
299
|
+
case 'memory': {
|
|
300
|
+
const { cmdMemory } = await Promise.resolve().then(() => __importStar(require('./commands/memory')));
|
|
301
|
+
process.exitCode = await cmdMemory(args);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// What your company keeps about your work (manual 2.13).
|
|
305
|
+
case 'privacy': {
|
|
306
|
+
const { cmdPrivacy } = await Promise.resolve().then(() => __importStar(require('./commands/privacy')));
|
|
307
|
+
process.exitCode = await cmdPrivacy();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// Separate copies of the repository (manual 2.11).
|
|
311
|
+
case 'worktrees': {
|
|
312
|
+
const { cmdWorktrees } = await Promise.resolve().then(() => __importStar(require('./commands/worktrees')));
|
|
313
|
+
process.exitCode = await cmdWorktrees(args);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
97
316
|
case 'ask': {
|
|
98
317
|
const { cmdAsk } = await Promise.resolve().then(() => __importStar(require('./commands/ask')));
|
|
99
|
-
|
|
318
|
+
process.exitCode = await cmdAsk(args);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// The newest version, and installing it when asked (manual 1.7).
|
|
322
|
+
case 'update': {
|
|
323
|
+
const { cmdUpdate } = await Promise.resolve().then(() => __importStar(require('./commands/update')));
|
|
324
|
+
process.exitCode = await cmdUpdate(args);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
// What the agent changed, the diff, and putting files back (manual 7).
|
|
328
|
+
case 'changes': {
|
|
329
|
+
const { cmdChanges } = await Promise.resolve().then(() => __importStar(require('./commands/review')));
|
|
330
|
+
process.exitCode = await cmdChanges(args);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
case 'diff': {
|
|
334
|
+
const { cmdDiff } = await Promise.resolve().then(() => __importStar(require('./commands/review')));
|
|
335
|
+
process.exitCode = await cmdDiff(args);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
case 'rewind': {
|
|
339
|
+
const { cmdRewind } = await Promise.resolve().then(() => __importStar(require('./commands/review')));
|
|
340
|
+
process.exitCode = await cmdRewind(args);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Jobs nobody is sitting at (manual 10.17 to 10.19).
|
|
344
|
+
case 'schedule': {
|
|
345
|
+
const { cmdSchedule } = await Promise.resolve().then(() => __importStar(require('./commands/unattended')));
|
|
346
|
+
process.exitCode = await cmdSchedule(args);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
case 'triggers': {
|
|
350
|
+
const { cmdTriggers } = await Promise.resolve().then(() => __importStar(require('./commands/unattended')));
|
|
351
|
+
process.exitCode = await cmdTriggers(args);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
case 'worker': {
|
|
355
|
+
const { cmdWorker } = await Promise.resolve().then(() => __importStar(require('./commands/unattended')));
|
|
356
|
+
process.exitCode = await cmdWorker(args);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// Hooks in force here, and running them now to see what they do (10.10).
|
|
360
|
+
case 'hooks': {
|
|
361
|
+
const { cmdHooks } = await Promise.resolve().then(() => __importStar(require('./commands/hooks')));
|
|
362
|
+
process.exitCode = await cmdHooks(args);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// The coding allowance and provider limits, from the service (manual 10.12).
|
|
366
|
+
case 'ide': {
|
|
367
|
+
// Which VS Code window this folder is joined to (VS Code manual 9.12).
|
|
368
|
+
const { findEditor } = await Promise.resolve().then(() => __importStar(require('./lib/ide')));
|
|
369
|
+
const link = findEditor(process.cwd());
|
|
370
|
+
out(link
|
|
371
|
+
? ` Connected to VS Code for ${link.folder}. Proposed changes open in its diff view.`
|
|
372
|
+
: ' No VS Code window with CompletionPrism has this folder open. Open the folder in VS Code and run this again.');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
case 'usage': {
|
|
376
|
+
const { ask: askGateway } = await Promise.resolve().then(() => __importStar(require('./lib/gateway')));
|
|
377
|
+
const { exitFor } = await Promise.resolve().then(() => __importStar(require('./lib/output')));
|
|
378
|
+
const reply = await askGateway({ intent: 'usage', prompt: '', tzMinutes: -new Date().getTimezoneOffset() });
|
|
379
|
+
const found = reply.usage;
|
|
380
|
+
if (!reply.ok || reply.served === false || !found?.lines) {
|
|
381
|
+
err(ui.fail(reply.message || 'Usage could not be read from your workspace.'));
|
|
382
|
+
process.exitCode = exitFor(reply.httpStatus === 401 ? 'signed_out'
|
|
383
|
+
: reply.ok || reply.httpStatus ? 'incomplete' : 'unreachable');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
out();
|
|
387
|
+
for (const line of found.lines)
|
|
388
|
+
out(` ${line}`);
|
|
389
|
+
out();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// The published shape of `--output json`, from the service (manual 10.3).
|
|
393
|
+
case 'schema': {
|
|
394
|
+
if (args[0] !== 'output') {
|
|
395
|
+
err(ui.fail('comprism schema output prints the shape of --output json.'));
|
|
396
|
+
process.exitCode = 2;
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const { ask: askGateway } = await Promise.resolve().then(() => __importStar(require('./lib/gateway')));
|
|
400
|
+
const reply = await askGateway({ intent: 'schema', prompt: '' });
|
|
401
|
+
const doc = reply.schema_doc;
|
|
402
|
+
if (!reply.ok || reply.served === false || !doc?.output) {
|
|
403
|
+
err(ui.fail(reply.message || 'The schema could not be read from your workspace.'));
|
|
404
|
+
const { exitFor } = await Promise.resolve().then(() => __importStar(require('./lib/output')));
|
|
405
|
+
process.exitCode = exitFor(reply.httpStatus === 401 ? 'signed_out'
|
|
406
|
+
: reply.ok || reply.httpStatus ? 'incomplete' : 'unreachable');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
out(JSON.stringify(doc.output, null, 2));
|
|
410
|
+
return;
|
|
100
411
|
}
|
|
101
412
|
// `work` is the word. `agent` still answers to it, because anybody who
|
|
102
413
|
// learned the old name should not meet "unknown command".
|
|
@@ -107,17 +418,167 @@ async function main() {
|
|
|
107
418
|
out(AGENT_HELP);
|
|
108
419
|
return;
|
|
109
420
|
}
|
|
421
|
+
// Every typing mistake is refused here, before anything is sent, asked
|
|
422
|
+
// or charged (manual 10.6, exit 2 in Appendix B). A reason goes to
|
|
423
|
+
// standard error, so a script reading standard output reads nothing.
|
|
424
|
+
const { outputChoice, readJsonFile, unknownFlag } = await Promise.resolve().then(() => __importStar(require('./lib/output')));
|
|
425
|
+
const { MODES } = await Promise.resolve().then(() => __importStar(require('./agent/command')));
|
|
426
|
+
const typo = (why) => {
|
|
427
|
+
err(ui.fail(why) + ui.c.dim(' Nothing ran and nothing was charged.'));
|
|
428
|
+
process.exitCode = 2;
|
|
429
|
+
};
|
|
430
|
+
const output = outputChoice(args);
|
|
431
|
+
if ('bad' in output)
|
|
432
|
+
return typo(output.bad);
|
|
433
|
+
const stray = unknownFlag(args, WORK_VALUES, WORK_SWITCHES);
|
|
434
|
+
if (stray)
|
|
435
|
+
return typo(`${stray} is not an option of comprism work. comprism work --help lists them.`);
|
|
436
|
+
// `--schema` and `--config` name files read here and judged by the
|
|
437
|
+
// service (manual 10.5, 10.8). A missing or unreadable file is a typing
|
|
438
|
+
// mistake.
|
|
439
|
+
let answerSchema;
|
|
440
|
+
if (args.includes('--schema')) {
|
|
441
|
+
const got = readJsonFile(String(flag(args, '--schema') ?? ''), '--schema');
|
|
442
|
+
if ('bad' in got)
|
|
443
|
+
return typo(got.bad);
|
|
444
|
+
answerSchema = got.value;
|
|
445
|
+
}
|
|
446
|
+
let settingsConfig;
|
|
447
|
+
if (args.includes('--config')) {
|
|
448
|
+
const got = readJsonFile(String(flag(args, '--config') ?? ''), '--config');
|
|
449
|
+
if ('bad' in got)
|
|
450
|
+
return typo(got.bad);
|
|
451
|
+
settingsConfig = got.value;
|
|
452
|
+
}
|
|
453
|
+
const wantedMode = flag(args, '--mode');
|
|
454
|
+
if (args.includes('--mode') && !MODES.includes(String(wantedMode))) {
|
|
455
|
+
return typo(`${wantedMode ?? '(nothing)'} is not a permission mode. One of: ${MODES.join(', ')}.`);
|
|
456
|
+
}
|
|
457
|
+
const spend = flag(args, '--max-spend');
|
|
458
|
+
if (args.includes('--max-spend') && !(Number(spend) > 0)) {
|
|
459
|
+
return typo('--max-spend takes an amount in dollars, for example --max-spend 2.00.');
|
|
460
|
+
}
|
|
461
|
+
// A step limit that is not a whole number is a typing mistake (4.13).
|
|
462
|
+
if (args.includes('--max-steps')) {
|
|
463
|
+
const steps = Number(flag(args, '--max-steps'));
|
|
464
|
+
if (!Number.isInteger(steps) || steps <= 0) {
|
|
465
|
+
return typo('--max-steps takes a whole number, for example --max-steps 40.');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// A time limit is a number of minutes (manual 10.7).
|
|
469
|
+
if (args.includes('--max-minutes') && !(Number(flag(args, '--max-minutes')) > 0)) {
|
|
470
|
+
return typo('--max-minutes takes a number of minutes, for example --max-minutes 15.');
|
|
471
|
+
}
|
|
472
|
+
// Effort is one of three words (manual 4.18).
|
|
473
|
+
if (args.includes('--effort') && !['low', 'medium', 'high'].includes(String(flag(args, '--effort')))) {
|
|
474
|
+
return typo('--effort takes low, medium or high.');
|
|
475
|
+
}
|
|
476
|
+
// A time limit that is not a number of seconds is a typing mistake:
|
|
477
|
+
// nothing runs and nothing is charged (manual 5.12, exit 2).
|
|
478
|
+
if (args.includes('--issue') && !/^\d+$/.test(flag(args, '--issue') ?? '')) {
|
|
479
|
+
return typo('--issue takes an issue number, for example --issue 42.');
|
|
480
|
+
}
|
|
481
|
+
if (args.includes('--timeout')) {
|
|
482
|
+
const secs = Number(flag(args, '--timeout'));
|
|
483
|
+
if (!Number.isInteger(secs) || secs <= 0) {
|
|
484
|
+
return typo('--timeout takes a whole number of seconds, for example --timeout 600.');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Text piped in is read to its end first: it is material for the job,
|
|
488
|
+
// never the request and never an answer (manual 3.9).
|
|
489
|
+
const { readPiped } = await Promise.resolve().then(() => __importStar(require('./lib/stdin')));
|
|
490
|
+
const piped = await readPiped();
|
|
110
491
|
const { subject, closePrompt } = await Promise.resolve().then(() => __importStar(require('./lib/prompt')));
|
|
111
492
|
// Asked for when it was not given, same as every other command.
|
|
112
|
-
const request = await subject(args, ' what would you like done? ',
|
|
493
|
+
const request = await subject(args, ' what would you like done? ', WORK_VALUES);
|
|
113
494
|
closePrompt();
|
|
114
495
|
if (!request) {
|
|
115
|
-
|
|
116
|
-
|
|
496
|
+
if (piped?.trim()) {
|
|
497
|
+
return typo('Piped text needs a request with it, for example: '
|
|
498
|
+
+ 'git diff | comprism work "write a commit message for this"');
|
|
499
|
+
}
|
|
500
|
+
// Standard input closed with nothing on it is a missing request.
|
|
501
|
+
return typo('Nothing was asked for.');
|
|
502
|
+
}
|
|
503
|
+
// On a CompletionPrism worker with a copy of the repository (10.18).
|
|
504
|
+
if (args.includes('--cloud')) {
|
|
505
|
+
const { submitCloud } = await Promise.resolve().then(() => __importStar(require('./commands/unattended')));
|
|
506
|
+
process.exitCode = await submitCloud(request, flag(args, '--cwd') ?? process.cwd(), args);
|
|
117
507
|
return;
|
|
118
508
|
}
|
|
119
|
-
|
|
509
|
+
// `comprism work /name the rest` runs that skill (manual 9.2). A slash
|
|
510
|
+
// word the service lists as neither a skill nor a saved command is a
|
|
511
|
+
// typo, refused before anything is sent or charged, as in a session.
|
|
512
|
+
const slashWord = /^\/([a-z][a-z0-9-]*)(?:\s|$)/i.exec(request.trim());
|
|
513
|
+
if (slashWord && !(await Promise.resolve().then(() => __importStar(require('./lib/attach')))).startsWithPath(request.trim())) {
|
|
514
|
+
const { fetchCommandList } = await Promise.resolve().then(() => __importStar(require('./lib/commandlist')));
|
|
515
|
+
const { resolveProject } = await Promise.resolve().then(() => __importStar(require('./lib/project')));
|
|
516
|
+
const where = flag(args, '--cwd') ?? (await Promise.resolve().then(() => __importStar(require('./commands/settings')))).currentWorkspace().path;
|
|
517
|
+
const list = await fetchCommandList(resolveProject(where).root, []);
|
|
518
|
+
const name = slashWord[1].toLowerCase();
|
|
519
|
+
if (list.ok && !list.items.some((i) => i.source !== 'session' && i.name === name)) {
|
|
520
|
+
return typo(`\`/${name}\` is neither a skill nor a saved command here. comprism skills lists the skills.`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const fromPipe = await attachStdin(piped);
|
|
524
|
+
if (fromPipe === null) {
|
|
525
|
+
process.exitCode = 1;
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
// `--resume` takes a session number or a job number and carries on that
|
|
529
|
+
// session's conversation (manual 8.4). The service resolves it; a job
|
|
530
|
+
// that belongs to no session is rejoined as before.
|
|
531
|
+
let resume = flag(args, '--resume');
|
|
532
|
+
let carry = {};
|
|
533
|
+
if (resume) {
|
|
534
|
+
const { sessions } = await Promise.resolve().then(() => __importStar(require('./lib/sessions')));
|
|
535
|
+
const found = await sessions('resume', { key: resume });
|
|
536
|
+
if (found.ok && found.sessions?.session_id) {
|
|
537
|
+
for (const line of found.sessions.lines)
|
|
538
|
+
out(line);
|
|
539
|
+
out();
|
|
540
|
+
carry = {
|
|
541
|
+
conversationId: found.sessions.session_id,
|
|
542
|
+
...(found.sessions.continues_job ? { continuesJob: found.sessions.continues_job } : {}),
|
|
543
|
+
};
|
|
544
|
+
resume = undefined;
|
|
545
|
+
}
|
|
546
|
+
else if (resume.startsWith('s_')) {
|
|
547
|
+
out(ui.fail(found.message || 'There is no session with that number you can open.'));
|
|
548
|
+
out();
|
|
549
|
+
process.exitCode = 1;
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// Every job belongs to a session, so it has a number, is listed and can
|
|
554
|
+
// be resumed (manual 8.1). A job on its own is a session of one job.
|
|
555
|
+
if (!carry.conversationId && !resume) {
|
|
556
|
+
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('node:crypto')));
|
|
557
|
+
carry.conversationId = randomUUID();
|
|
558
|
+
}
|
|
559
|
+
// A separate copy of the repository (manual 2.11): made the first time,
|
|
560
|
+
// and the job runs in it.
|
|
561
|
+
let workFolder = flag(args, '--cwd')
|
|
562
|
+
?? (await Promise.resolve().then(() => __importStar(require('./commands/settings')))).currentWorkspace().path;
|
|
563
|
+
const worktree = flag(args, '--worktree');
|
|
564
|
+
if (worktree) {
|
|
565
|
+
const { checkFolder, resolveProject } = await Promise.resolve().then(() => __importStar(require('./lib/project')));
|
|
566
|
+
const start = checkFolder(workFolder);
|
|
567
|
+
if (!start.ok) {
|
|
568
|
+
out(ui.fail(start.why));
|
|
569
|
+
process.exitCode = 2;
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const { ensureWorktree } = await Promise.resolve().then(() => __importStar(require('./commands/worktrees')));
|
|
573
|
+
const made = await ensureWorktree(resolveProject(start.path), worktree, carry.conversationId ?? 'cli');
|
|
574
|
+
if (!made) {
|
|
575
|
+
process.exitCode = 1;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
workFolder = made;
|
|
579
|
+
}
|
|
120
580
|
process.exitCode = await runAgent({
|
|
581
|
+
...carry,
|
|
121
582
|
request: [request],
|
|
122
583
|
mode: flag(args, '--mode'),
|
|
123
584
|
plan: args.includes('--plan'),
|
|
@@ -125,21 +586,37 @@ async function main() {
|
|
|
125
586
|
model: flag(args, '--model'),
|
|
126
587
|
pin: args.includes('--pin'),
|
|
127
588
|
...(args.includes('--max-spend') ? { maxSpend: Number(spend) } : {}),
|
|
589
|
+
...(args.includes('--max-steps') ? { maxSteps: Number(flag(args, '--max-steps')) } : {}),
|
|
590
|
+
...(args.includes('--max-minutes') ? { maxMinutes: Number(flag(args, '--max-minutes')) } : {}),
|
|
591
|
+
...(args.includes('--effort') ? { effort: flag(args, '--effort') } : {}),
|
|
592
|
+
sandbox: args.includes('--sandbox'),
|
|
593
|
+
...(args.includes('--timeout') ? { timeout: Number(flag(args, '--timeout')) } : {}),
|
|
594
|
+
...(args.includes('--issue') ? { issue: Number(flag(args, '--issue')) } : {}),
|
|
595
|
+
...(args.includes('--team') ? { team: flag(args, '--team') } : {}),
|
|
128
596
|
allow: args.reduce((acc, a, i) => (args[i - 1] === '--allow' ? [...acc, a] : acc), []),
|
|
597
|
+
// Repeatable, and the only way a second folder is allowed (manual 2.3).
|
|
598
|
+
addFolders: args.reduce((acc, a, i) => (args[i - 1] === '--add-folder' ? [...acc, a] : acc), []),
|
|
129
599
|
yes: args.includes('--yes'),
|
|
130
600
|
background: args.includes('--background'),
|
|
131
|
-
|
|
601
|
+
output,
|
|
602
|
+
...(args.includes('--queued') ? { queuedJob: flag(args, '--queued') } : {}),
|
|
603
|
+
...(answerSchema ? { answerSchema } : {}),
|
|
604
|
+
...(settingsConfig ? { settingsConfig } : {}),
|
|
605
|
+
noPersonal: args.includes('--no-personal'),
|
|
606
|
+
resume,
|
|
132
607
|
// Go without the code map for this one job. Two reasons somebody uses
|
|
133
608
|
// it: they are measuring, and a run with a map and a run without one
|
|
134
609
|
// are two different quantities; or they suspect the map and want the
|
|
135
610
|
// files read directly. Both are legitimate and neither should require
|
|
136
611
|
// an administrator.
|
|
137
612
|
noMap: args.includes('--no-map') || args.includes('--no-graph'),
|
|
613
|
+
attachments: fromPipe.ids,
|
|
614
|
+
keyboard: fromPipe.keyboard,
|
|
138
615
|
// The saved working folder when there is one, so `comprism workspace`
|
|
139
616
|
// is a setting the agent honors rather than a stored string nothing reads.
|
|
140
|
-
cwd:
|
|
141
|
-
?? (await Promise.resolve().then(() => __importStar(require('./commands/settings')))).currentWorkspace().path,
|
|
617
|
+
cwd: workFolder,
|
|
142
618
|
});
|
|
619
|
+
fromPipe.keyboard?.destroy();
|
|
143
620
|
return;
|
|
144
621
|
}
|
|
145
622
|
// The map of the project you are standing in: what it holds, and a way to
|
|
@@ -192,9 +669,12 @@ async function main() {
|
|
|
192
669
|
const { cmdProviders } = await Promise.resolve().then(() => __importStar(require('./commands/settings')));
|
|
193
670
|
return cmdProviders();
|
|
194
671
|
}
|
|
672
|
+
// `project` is the word (manual 2.1). `workspace` stays as another name,
|
|
673
|
+
// because it also means the company account and people learned it first.
|
|
674
|
+
case 'project':
|
|
195
675
|
case 'workspace': {
|
|
196
|
-
const {
|
|
197
|
-
return
|
|
676
|
+
const { cmdProject } = await Promise.resolve().then(() => __importStar(require('./commands/settings')));
|
|
677
|
+
return cmdProject(args);
|
|
198
678
|
}
|
|
199
679
|
case 'keys': {
|
|
200
680
|
const { cmdKeys } = await Promise.resolve().then(() => __importStar(require('./commands/keys')));
|
|
@@ -207,25 +687,118 @@ async function main() {
|
|
|
207
687
|
// What this machine still needs. Its own word because somebody whose PDF
|
|
208
688
|
// would not read has a question about THIS machine, and the answer should
|
|
209
689
|
// not be buried inside a setup command they have already run.
|
|
690
|
+
// What a session cost, and what drove it. Its own word because "where did
|
|
691
|
+
// the money go" is a different question from "what did this answer cost",
|
|
692
|
+
// and the receipt under an answer only ever answers the second.
|
|
693
|
+
case 'cost':
|
|
694
|
+
case 'spend': {
|
|
695
|
+
const { cmdCost } = await Promise.resolve().then(() => __importStar(require('./commands/cost')));
|
|
696
|
+
process.exitCode = await cmdCost(args);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
210
699
|
case 'check':
|
|
211
700
|
case 'doctor': {
|
|
212
|
-
const {
|
|
213
|
-
const
|
|
701
|
+
const { diagnose } = await Promise.resolve().then(() => __importStar(require('./lib/readiness')));
|
|
702
|
+
const found = await diagnose();
|
|
214
703
|
out();
|
|
215
704
|
out(ui.banner('This machine'));
|
|
216
705
|
out();
|
|
217
|
-
for (const
|
|
218
|
-
|
|
706
|
+
for (const f of found) {
|
|
707
|
+
const mark = f.ok ? ui.c.dim('ok ') : f.required ? 'no ' : ui.c.dim('-- ');
|
|
708
|
+
out(` ${mark} ${f.line}${!f.ok && f.fix ? `. ${f.fix}` : ''}`);
|
|
709
|
+
}
|
|
710
|
+
out();
|
|
711
|
+
const fixes = [...new Set(found.filter((f) => !f.ok && f.fix).map((f) => f.fix))];
|
|
712
|
+
if (fixes.length)
|
|
713
|
+
out(` ${ui.c.dim('to add what is missing:')} ${ui.c.bold(fixes.join(', then '))}`);
|
|
714
|
+
out(` ${ui.c.dim('to check the account and the service:')} ${ui.c.bold('comprism status')}`);
|
|
715
|
+
out();
|
|
716
|
+
// Only a required item decides the exit status (manual 1.6).
|
|
717
|
+
if (found.some((f) => f.required && !f.ok))
|
|
718
|
+
process.exitCode = 1;
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
// The install's own message, the postinstall fallback and the generated
|
|
722
|
+
// shell script all tell a person to run this, so it has to exist as a word.
|
|
723
|
+
case 'install': {
|
|
724
|
+
const { install } = await Promise.resolve().then(() => __importStar(require('./commands/install')));
|
|
725
|
+
const report = install();
|
|
726
|
+
// `--quiet` is the install scripts' form (manual 1.1, Option 1): only the
|
|
727
|
+
// lines that say what changed, under the script's own heading.
|
|
728
|
+
const quiet = args.includes('--quiet');
|
|
729
|
+
if (!quiet) {
|
|
730
|
+
out();
|
|
731
|
+
out(ui.banner('Set up this machine'));
|
|
732
|
+
out();
|
|
733
|
+
}
|
|
734
|
+
for (const profile of report.profilesTouched)
|
|
735
|
+
out(` added one line to ${profile}`);
|
|
736
|
+
for (const profile of report.profilesAlreadyHad)
|
|
737
|
+
out(` already set up in ${profile}`);
|
|
738
|
+
if (quiet)
|
|
739
|
+
return;
|
|
740
|
+
out(report.enrolled && report.baseUrl
|
|
741
|
+
? ` AI tools in a new terminal are measured against ${report.baseUrl}`
|
|
742
|
+
: ` nothing is measured until this machine signs in: ${ui.c.bold('comprism login')}`);
|
|
743
|
+
out();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
// The undo. The install edits a shell profile, and the message it prints
|
|
747
|
+
// has always promised this command could take the line back out. The
|
|
748
|
+
// function that does it was written and exported and never wired to a
|
|
749
|
+
// word, so a customer following our own instruction got "there is no
|
|
750
|
+
// command called uninstall" and was left to edit their profile by hand.
|
|
751
|
+
case 'uninstall': {
|
|
752
|
+
// One command takes everything back out, as one command put it in
|
|
753
|
+
// (owner, 27 September 2026): the machine's key is revoked, the shell
|
|
754
|
+
// line and generated script go, the saved settings go, and the program
|
|
755
|
+
// removes itself. `--keep-settings` keeps the sign in for a reinstall.
|
|
756
|
+
const keep = args.includes('--keep-settings');
|
|
757
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs')));
|
|
758
|
+
const { spawnSync, spawn } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
|
|
759
|
+
const { uninstall } = await Promise.resolve().then(() => __importStar(require('./commands/install')));
|
|
760
|
+
if (!keep) {
|
|
761
|
+
const { cmdDisconnect } = await Promise.resolve().then(() => __importStar(require('./commands/commands-thin')));
|
|
762
|
+
await cmdDisconnect().catch(() => undefined);
|
|
763
|
+
}
|
|
764
|
+
const report = uninstall();
|
|
765
|
+
out(ui.banner('Removed from this machine'));
|
|
219
766
|
out();
|
|
220
|
-
|
|
221
|
-
out(
|
|
767
|
+
for (const profile of report.profilesCleaned)
|
|
768
|
+
out(` removed our line from ${profile}`);
|
|
769
|
+
if (report.scriptRemoved)
|
|
770
|
+
out(' removed the generated shell script');
|
|
771
|
+
if (report.programRemoved)
|
|
772
|
+
out(' removed the program from ~/.comprism/bin');
|
|
773
|
+
if (!keep) {
|
|
774
|
+
fs.rmSync(report.kept, { recursive: true, force: true });
|
|
775
|
+
out(` removed the saved settings in ${report.kept}`);
|
|
776
|
+
}
|
|
777
|
+
// The program itself, the way it was installed. On Windows a running
|
|
778
|
+
// program cannot delete itself, so the removal runs just after we exit.
|
|
779
|
+
const remover = report.method === 'npm'
|
|
780
|
+
? [process.platform === 'win32' ? 'npm.cmd' : 'npm', ['uninstall', '-g', '@illuminis/comprism']]
|
|
781
|
+
: report.method === 'python'
|
|
782
|
+
? (spawnSync('pipx', ['--version'], { stdio: 'ignore' }).status === 0
|
|
783
|
+
? ['pipx', ['uninstall', 'comprism-cli']]
|
|
784
|
+
: [process.platform === 'win32' ? 'python' : 'python3', ['-m', 'pip', 'uninstall', '-y', 'comprism-cli']])
|
|
785
|
+
: null;
|
|
786
|
+
if (remover && process.platform === 'win32') {
|
|
787
|
+
// Through cmd.exe, which is how npm.cmd and pip run on Windows, with
|
|
788
|
+
// every value its own argument and no shell string built from them.
|
|
789
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process -- a fixed removal command, no shell
|
|
790
|
+
spawn('cmd.exe', ['/d', '/c', remover[0], ...remover[1]], { detached: true, stdio: 'ignore' }).unref();
|
|
791
|
+
out(' removing the comprism command');
|
|
222
792
|
}
|
|
223
|
-
else {
|
|
224
|
-
|
|
793
|
+
else if (remover) {
|
|
794
|
+
const done = spawnSync(remover[0], remover[1], { stdio: 'ignore' });
|
|
795
|
+
out(done.status === 0
|
|
796
|
+
? ' removed the comprism command'
|
|
797
|
+
: ui.warn(` could not remove the command itself; run: ${remover[0]} ${remover[1].join(' ')}`));
|
|
225
798
|
}
|
|
226
799
|
out();
|
|
227
|
-
out(ui.c.dim('
|
|
228
|
-
out(ui.c.dim('
|
|
800
|
+
out(ui.c.dim(' Every AI tool on this machine now talks straight to its provider.'));
|
|
801
|
+
out(ui.c.dim(' Open a new terminal window to finish.'));
|
|
229
802
|
out();
|
|
230
803
|
return;
|
|
231
804
|
}
|
|
@@ -245,8 +818,23 @@ async function main() {
|
|
|
245
818
|
if (command) {
|
|
246
819
|
out(ui.fail(`There is no command called "${command}".`));
|
|
247
820
|
out();
|
|
821
|
+
// A script that mistypes a command must not read it as success. Two is
|
|
822
|
+
// the manual's number for a wrongly typed command (Appendix B).
|
|
823
|
+
process.exitCode = 2;
|
|
248
824
|
return usage();
|
|
249
825
|
}
|
|
826
|
+
// Text piped into the bare command has nothing to go with (manual 3.9).
|
|
827
|
+
if (!process.stdin.isTTY) {
|
|
828
|
+
const { readPiped } = await Promise.resolve().then(() => __importStar(require('./lib/stdin')));
|
|
829
|
+
const piped = await readPiped();
|
|
830
|
+
if (piped?.trim()) {
|
|
831
|
+
err(ui.fail('Piped text needs a request with it, for example: '
|
|
832
|
+
+ '... | comprism ask "why is this failing"'));
|
|
833
|
+
err(ui.c.dim(' Nothing ran and nothing was charged.'));
|
|
834
|
+
process.exitCode = 2;
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
250
838
|
// The bare command STARTS the product rather than describing it.
|
|
251
839
|
const { runRepl } = await Promise.resolve().then(() => __importStar(require('./commands/repl')));
|
|
252
840
|
return runRepl();
|