@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.
Files changed (142) hide show
  1. package/README.md +61 -2
  2. package/out/agent/command.d.ts +75 -4
  3. package/out/agent/command.js +221 -26
  4. package/out/agent/render.d.ts +56 -8
  5. package/out/agent/render.js +441 -39
  6. package/out/agent/session.d.ts +156 -2
  7. package/out/agent/session.js +795 -154
  8. package/out/commands/agents.d.ts +2 -0
  9. package/out/commands/agents.js +79 -0
  10. package/out/commands/ask.d.ts +1 -1
  11. package/out/commands/ask.js +88 -12
  12. package/out/commands/commands-thin.js +79 -17
  13. package/out/commands/config.d.ts +1 -0
  14. package/out/commands/config.js +138 -0
  15. package/out/commands/cost.d.ts +1 -0
  16. package/out/commands/cost.js +167 -0
  17. package/out/commands/hooks.d.ts +1 -0
  18. package/out/commands/hooks.js +83 -0
  19. package/out/commands/install.d.ts +44 -1
  20. package/out/commands/install.js +198 -4
  21. package/out/commands/instructions.d.ts +1 -0
  22. package/out/commands/instructions.js +113 -0
  23. package/out/commands/integrations.d.ts +3 -0
  24. package/out/commands/integrations.js +215 -0
  25. package/out/commands/jobs.d.ts +5 -0
  26. package/out/commands/jobs.js +157 -0
  27. package/out/commands/login.js +249 -43
  28. package/out/commands/memory.d.ts +3 -0
  29. package/out/commands/memory.js +113 -0
  30. package/out/commands/permissions.d.ts +1 -0
  31. package/out/commands/permissions.js +94 -0
  32. package/out/commands/plugins.d.ts +4 -0
  33. package/out/commands/plugins.js +192 -0
  34. package/out/commands/privacy.d.ts +1 -0
  35. package/out/commands/privacy.js +57 -0
  36. package/out/commands/providerKey.d.ts +32 -0
  37. package/out/commands/providerKey.js +108 -0
  38. package/out/commands/repl.d.ts +12 -1
  39. package/out/commands/repl.js +1649 -122
  40. package/out/commands/report.d.ts +39 -0
  41. package/out/commands/report.js +115 -0
  42. package/out/commands/review.d.ts +5 -0
  43. package/out/commands/review.js +223 -0
  44. package/out/commands/sessions.d.ts +23 -0
  45. package/out/commands/sessions.js +115 -0
  46. package/out/commands/settings.d.ts +3 -1
  47. package/out/commands/settings.js +18 -16
  48. package/out/commands/skills.d.ts +21 -0
  49. package/out/commands/skills.js +207 -0
  50. package/out/commands/unattended.d.ts +7 -0
  51. package/out/commands/unattended.js +351 -0
  52. package/out/commands/update.d.ts +1 -0
  53. package/out/commands/update.js +123 -0
  54. package/out/commands/welcome.d.ts +25 -1
  55. package/out/commands/welcome.js +71 -35
  56. package/out/commands/worktrees.d.ts +5 -0
  57. package/out/commands/worktrees.js +186 -0
  58. package/out/executor/browser.d.ts +14 -0
  59. package/out/executor/browser.js +270 -0
  60. package/out/executor/diagnostics.d.ts +2 -0
  61. package/out/executor/diagnostics.js +181 -0
  62. package/out/executor/files.js +270 -40
  63. package/out/executor/git.js +42 -29
  64. package/out/executor/hooks.d.ts +42 -58
  65. package/out/executor/hooks.js +89 -182
  66. package/out/executor/index.d.ts +21 -5
  67. package/out/executor/index.js +160 -14
  68. package/out/executor/paths.d.ts +6 -1
  69. package/out/executor/paths.js +34 -6
  70. package/out/executor/sandbox.d.ts +40 -0
  71. package/out/executor/sandbox.js +299 -0
  72. package/out/executor/shell.d.ts +49 -4
  73. package/out/executor/shell.js +302 -56
  74. package/out/executor/toolservers.d.ts +20 -0
  75. package/out/executor/toolservers.js +189 -0
  76. package/out/executor/worktree.d.ts +9 -0
  77. package/out/executor/worktree.js +119 -0
  78. package/out/graph/read-python.js +2 -1
  79. package/out/lib/attach.d.ts +56 -12
  80. package/out/lib/attach.js +230 -63
  81. package/out/lib/clipboard.d.ts +23 -0
  82. package/out/lib/clipboard.js +182 -0
  83. package/out/lib/commandlist.d.ts +20 -0
  84. package/out/lib/commandlist.js +58 -0
  85. package/out/lib/decision.d.ts +22 -0
  86. package/out/lib/decision.js +50 -0
  87. package/out/lib/fingerprint.d.ts +25 -0
  88. package/out/lib/fingerprint.js +58 -0
  89. package/out/lib/gateway.d.ts +190 -2
  90. package/out/lib/gateway.js +59 -4
  91. package/out/lib/history.d.ts +24 -0
  92. package/out/lib/history.js +137 -0
  93. package/out/lib/ide.d.ts +19 -0
  94. package/out/lib/ide.js +131 -0
  95. package/out/lib/keyboard.d.ts +98 -0
  96. package/out/lib/keyboard.js +390 -0
  97. package/out/lib/lastlogin.d.ts +14 -0
  98. package/out/lib/lastlogin.js +105 -0
  99. package/out/lib/machine.d.ts +21 -0
  100. package/out/lib/machine.js +91 -0
  101. package/out/lib/notify.d.ts +4 -0
  102. package/out/lib/notify.js +52 -0
  103. package/out/lib/output.d.ts +48 -0
  104. package/out/lib/output.js +108 -0
  105. package/out/lib/picker.d.ts +1 -0
  106. package/out/lib/picker.js +158 -0
  107. package/out/lib/project-ops.d.ts +19 -0
  108. package/out/lib/project-ops.js +146 -0
  109. package/out/lib/project.d.ts +28 -0
  110. package/out/lib/project.js +114 -0
  111. package/out/lib/prompt.d.ts +3 -0
  112. package/out/lib/prompt.js +36 -2
  113. package/out/lib/queue.d.ts +13 -0
  114. package/out/lib/queue.js +116 -0
  115. package/out/lib/readiness.d.ts +19 -0
  116. package/out/lib/readiness.js +170 -1
  117. package/out/lib/self.d.ts +23 -0
  118. package/out/lib/self.js +124 -0
  119. package/out/lib/sessions.d.ts +19 -0
  120. package/out/lib/sessions.js +221 -0
  121. package/out/lib/startcache.d.ts +13 -0
  122. package/out/lib/startcache.js +78 -0
  123. package/out/lib/stdin.d.ts +32 -0
  124. package/out/lib/stdin.js +117 -0
  125. package/out/lib/store.d.ts +40 -0
  126. package/out/lib/store.js +138 -0
  127. package/out/lib/sync.d.ts +18 -0
  128. package/out/lib/sync.js +81 -0
  129. package/out/lib/ui.d.ts +10 -4
  130. package/out/lib/ui.js +65 -25
  131. package/out/lib/voice.d.ts +36 -0
  132. package/out/lib/voice.js +160 -1
  133. package/out/native/dictate +0 -0
  134. package/out/postinstall.js +42 -17
  135. package/out/providers/anthropic.d.ts +22 -0
  136. package/out/providers/anthropic.js +80 -0
  137. package/out/providers/index.d.ts +8 -0
  138. package/out/providers/index.js +83 -0
  139. package/out/providers/openai.d.ts +11 -0
  140. package/out/providers/openai.js +57 -0
  141. package/out/thin.js +595 -33
  142. package/package.json +11 -50
@@ -127,15 +127,147 @@ async function post(url, path, body, tenant) {
127
127
  catch { /* non-JSON body */ }
128
128
  return { status: res.status, json };
129
129
  }
130
+ const DEVICE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code';
131
+ /** The built in public client the portal accepts for this product's CLI. */
132
+ const CLIENT_ID = 'completionprism-cli';
133
+ function headersFor(tenant) {
134
+ return tenant ? { 'X-Tenant-ID': tenant } : {};
135
+ }
136
+ /** Whether this workspace's company signs in through its own identity provider. */
137
+ async function usesCompanyLogin(url, tenant) {
138
+ try {
139
+ const res = await fetch(`${url}/api/v1/auth/sso/providers`, { headers: headersFor(tenant) });
140
+ if (!res.ok)
141
+ return false;
142
+ const body = await res.json();
143
+ return (body.providers?.length ?? 0) > 0 && body.password_enabled === false;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ function openInBrowser(address) {
150
+ const { spawn } = require('node:child_process');
151
+ const [cmd, argv] = process.platform === 'darwin' ? ['open', [address]]
152
+ : process.platform === 'win32' ? ['cmd', ['/c', 'start', '', address]]
153
+ : ['xdg-open', [address]];
154
+ try {
155
+ spawn(cmd, argv, { stdio: 'ignore', detached: true }).on('error', () => { }).unref();
156
+ }
157
+ catch { /* the address is printed as well */ }
158
+ }
159
+ /** The portal's own device sign in: approve in a browser, get a token back. */
160
+ async function approveInBrowser(url, tenant, noBrowser) {
161
+ const form = (fields) => ({
162
+ method: 'POST',
163
+ headers: { ...headersFor(tenant), 'content-type': 'application/x-www-form-urlencoded' },
164
+ body: new URLSearchParams(fields).toString(),
165
+ });
166
+ let started;
167
+ try {
168
+ const res = await fetch(`${url}/oauth/device_authorization`, form({ client_id: CLIENT_ID, scope: 'api:read api:write' }));
169
+ if (!res.ok) {
170
+ out(ui.fail(res.status === 404 || res.status === 405
171
+ ? 'That workspace is running a build without browser sign in.'
172
+ : 'The workspace would not start a browser sign in.'));
173
+ return '';
174
+ }
175
+ started = await res.json();
176
+ }
177
+ catch {
178
+ out(ui.fail(`Could not reach ${url}. Check your connection and try again.`));
179
+ return '';
180
+ }
181
+ out();
182
+ if (noBrowser) {
183
+ out(` On any device, open ${ui.c.bold(started.verification_uri)}`);
184
+ out(` and enter the code ${ui.c.bold(started.user_code)}`);
185
+ }
186
+ else {
187
+ out(ui.info('Your company signs in through its own login page. Opening your browser.'));
188
+ out(` If it does not open, go to ${started.verification_uri_complete}`);
189
+ out(` and confirm the code ${ui.c.bold(started.user_code)}`);
190
+ openInBrowser(started.verification_uri_complete);
191
+ }
192
+ out(ui.c.dim(' waiting for you to approve it there...'));
193
+ const deadline = Date.now() + Math.max(30, started.expires_in || 600) * 1000;
194
+ let wait = Math.max(1, started.interval || 5);
195
+ while (Date.now() < deadline) {
196
+ await new Promise((r) => setTimeout(r, wait * 1000));
197
+ let res;
198
+ try {
199
+ res = await fetch(`${url}/oauth/token`, form({ grant_type: DEVICE_GRANT, device_code: started.device_code, client_id: CLIENT_ID }));
200
+ }
201
+ catch {
202
+ continue;
203
+ }
204
+ const body = await res.json().catch(() => ({}));
205
+ if (res.ok && body.access_token)
206
+ return body.access_token;
207
+ if (body.error === 'authorization_pending')
208
+ continue;
209
+ if (body.error === 'slow_down') {
210
+ wait += 5;
211
+ continue;
212
+ }
213
+ out(ui.fail(body.error === 'access_denied' ? 'The sign in was declined in the browser.'
214
+ : 'That sign in was not approved.'));
215
+ return '';
216
+ }
217
+ out(ui.fail('That code expired before it was approved. Run comprism login again.'));
218
+ return '';
219
+ }
130
220
  async function runLogin(args) {
131
- out(ui.banner('Sign in'));
221
+ // SIGN IN WITH AN INSTALL KEY (web manual 12.1). The key made on the
222
+ // Install and setup page is this machine's credential, so setup is the one
223
+ // command the page shows and nothing is typed. It needs no terminal, because
224
+ // the install script runs it on a pipe.
225
+ const keyAt = args.indexOf('--key');
226
+ if (keyAt >= 0)
227
+ return loginWithKey(args[keyAt + 1], args);
228
+ // Password and second factor entry require a person at a terminal. Do not
229
+ // open readline on a pipe: it can wait forever, or exit successfully at EOF
230
+ // without signing in. Check before workspace lookup or reading credentials.
231
+ if (process.stdin.isTTY !== true) {
232
+ out(ui.fail('Signing in requires an interactive terminal.'));
233
+ out(' Run comprism login in a terminal, then run your command again.');
234
+ process.exitCode = 1;
235
+ return;
236
+ }
237
+ // Under the session's own mark when the session is about to open, so the
238
+ // name is not drawn twice in a row.
239
+ out(args.includes('--into-session') ? ` ${ui.c.bold('Sign in')}` : ui.banner('Sign in'));
132
240
  out();
133
241
  // Flags still work, for a script or a second machine. A person gets asked.
134
242
  const flag = (name) => {
135
243
  const i = args.indexOf(name);
136
244
  return i >= 0 ? args[i + 1] : undefined;
137
245
  };
138
- const username = flag('--username') || flag('--email') || await (0, prompt_1.ask)(' email: ');
246
+ // THE LAST PERSON, OFFERED. Signing in again is Enter: the email and the
247
+ // workspace they used last time, exactly as they were, so a local workspace
248
+ // stays local instead of being looked up again and sent to production.
249
+ const { readLast, saveLast, forgetLast, savedPassword, savePassword, forgetPassword, keychainAvailable } = await Promise.resolve().then(() => __importStar(require('../lib/lastlogin')));
250
+ if (args.includes('--forget')) {
251
+ forgetLast();
252
+ out(ui.ok('The last sign in and any saved password on this Mac are forgotten.'));
253
+ out();
254
+ return;
255
+ }
256
+ const last = flag('--username') || flag('--email') || flag('--workspace') ? null : readLast();
257
+ if (last) {
258
+ out(` ${ui.c.dim('last time')} ${ui.c.bold(last.email)} ${ui.c.dim('at')} ${ui.c.bold(last.workspace)}`);
259
+ out(` ${ui.c.dim('press Enter to sign in as them, or type another email')}`);
260
+ }
261
+ const typedEmail = flag('--username') || flag('--email') || await (0, prompt_1.askOrCancel)(' email: ');
262
+ // Ctrl+C is "never mind", not "yes, the last person".
263
+ if (typedEmail === null) {
264
+ out();
265
+ out(ui.c.dim(' sign in canceled'));
266
+ out();
267
+ return;
268
+ }
269
+ const username = typedEmail || last?.email || '';
270
+ const again = Boolean(last && username === last.email);
139
271
  if (!username) {
140
272
  out(ui.fail('An email address is needed.'));
141
273
  out();
@@ -143,16 +275,21 @@ async function runLogin(args) {
143
275
  return;
144
276
  }
145
277
  // Where this person's workspace is. Asked of the product, not of them.
146
- let typedWorkspace = flag('--workspace');
278
+ let typedWorkspace = flag('--workspace') || (again ? last?.workspace : undefined);
147
279
  // Captured BEFORE the email lookup can fill it in. A workspace the product
148
280
  // worked out is not a workspace the person typed, and only the second may be
149
- // a local address. See the fence in `writeConnection`.
150
- const typedByHand = Boolean(typedWorkspace);
281
+ // a local address. See the fence in `writeConnection`. The last sign in
282
+ // counts as typed only if it was typed then.
283
+ const typedByHand = Boolean(flag('--workspace')) || Boolean(again && last?.typed);
151
284
  if (!typedWorkspace) {
152
285
  const live = new ui.LiveIndicator();
153
286
  live.start('finding your workspace');
154
- typedWorkspace = (await resolveWorkspace(username)) || undefined;
155
- live.stop();
287
+ try {
288
+ typedWorkspace = (await resolveWorkspace(username)) || undefined;
289
+ }
290
+ finally {
291
+ live.stop();
292
+ }
156
293
  // The indicator clears its own line but leaves the cursor mid-row, so the
157
294
  // next prompt landed after a run of blanks.
158
295
  process.stdout.write('\r');
@@ -167,8 +304,27 @@ async function runLogin(args) {
167
304
  }
168
305
  const url = workspaceUrl(typedWorkspace);
169
306
  const tenant = tenantFor(url, typedWorkspace, flag('--tenant'));
170
- const password = await (0, prompt_1.askHidden)(' password: ');
171
- if (!password) {
307
+ // Said before the password is asked for, so a person can see which company
308
+ // they are about to sign in to (manual 1.3).
309
+ out(` workspace: ${url}`);
310
+ // COMPANY LOGIN (manual 1.10). A workspace whose company signs in through
311
+ // its own identity provider is signed in to in a browser; this tool never
312
+ // sees that password. `--device` is the same thing for a machine with no
313
+ // browser: a short code to approve from any other device.
314
+ const byCompany = args.includes('--device') || args.includes('--browser')
315
+ || await usesCompanyLogin(url, tenant);
316
+ const deviceToken = byCompany ? await approveInBrowser(url, tenant, args.includes('--device')) : '';
317
+ if (byCompany && !deviceToken) {
318
+ out();
319
+ process.exitCode = 1;
320
+ return;
321
+ }
322
+ // The keychain first, when the person chose to keep it there last time.
323
+ const kept = byCompany ? null : savedPassword(username, typedWorkspace);
324
+ if (kept)
325
+ out(` ${ui.c.dim('password: from your Mac keychain')}`);
326
+ const password = byCompany ? '' : kept || await (0, prompt_1.askHidden)(' password: ');
327
+ if (!byCompany && !password) {
172
328
  out(ui.fail('A password is needed.'));
173
329
  out();
174
330
  process.exitCode = 1;
@@ -185,7 +341,7 @@ async function runLogin(args) {
185
341
  let signInMessage = '';
186
342
  {
187
343
  let reply = await (0, gateway_1.askAt)(url, {
188
- intent: 'sign_in', prompt: username, password,
344
+ intent: 'sign_in', prompt: username, password, deviceToken,
189
345
  machineLabel: `comprism on ${node_os_1.default.hostname()}`,
190
346
  }, tenant);
191
347
  if (reply.served === false) {
@@ -198,8 +354,9 @@ async function runLogin(args) {
198
354
  }
199
355
  if (!reply.ok) {
200
356
  out();
201
- out(ui.fail(`Could not reach ${url}.`));
202
- out(` ${ui.c.dim('check the workspace name, and that you are on the network')}`);
357
+ out(ui.fail(reply.httpStatus
358
+ ? (reply.message || 'The service refused the sign in request.')
359
+ : `Could not reach ${url}. Check your connection and try again.`));
203
360
  out();
204
361
  process.exitCode = 1;
205
362
  return;
@@ -210,7 +367,7 @@ async function runLogin(args) {
210
367
  if (reply.sign_in?.needs_second_factor && reply.sign_in.challenge) {
211
368
  out();
212
369
  out(ui.info('This account uses a second step. Check your authenticator app or your email.'));
213
- const code = await (0, prompt_1.ask)(' code: ');
370
+ const code = await (0, prompt_1.askHidden)(' code from your authenticator app: ');
214
371
  if (!code) {
215
372
  out(ui.fail('A code is needed to finish signing in.'));
216
373
  out();
@@ -231,6 +388,10 @@ async function runLogin(args) {
231
388
  }
232
389
  }
233
390
  if (!reply.sign_in?.token) {
391
+ // A kept password that no longer works is removed, so the next sign in
392
+ // asks for the new one instead of failing the same way again.
393
+ if (kept)
394
+ forgetPassword(username, typedWorkspace);
234
395
  out();
235
396
  out(ui.fail(reply.message || 'That username and password were not accepted.'));
236
397
  out(` ${ui.c.dim('check the workspace name too: a right password in the wrong workspace looks the same')}`);
@@ -254,31 +415,10 @@ async function runLogin(args) {
254
415
  process.exitCode = 1;
255
416
  return;
256
417
  }
257
- // Enrol the machine while we still hold the session, so signing in is one
258
- // step rather than a sign in followed by something else to remember.
259
- // The machine credential came back WITH the sign in, from the one service,
260
- // which is why there is no second call here any more. An existing one is
261
- // kept: signing in twice must not leave two entries in the workspace's own
262
- // list of machines.
418
+ // A previous credential may belong to another account or workspace, or
419
+ // have been revoked during this sign in. Only retain the newly issued key.
263
420
  const existing = (0, connection_1.readConnection)();
264
- // The key this sign in just issued wins.
265
- //
266
- // This used to prefer whatever was already on the machine, so that signing
267
- // in twice did not leave two entries in the workspace's own list of
268
- // machines. The rule was right for the same person signing in again and
269
- // wrong for every other case: sign in as a different account, or into a
270
- // different workspace, and the machine carried on holding the OLD key. The
271
- // sign in reported success, the header then said the workspace could not be
272
- // reached, and nothing anywhere said that the credential belonged to
273
- // somebody else. A revoked key behaved the same way.
274
- //
275
- // The server already ends the previous credential for this machine when it
276
- // issues one, so taking the new key does not leave two live. The old one is
277
- // kept only when the server issued nothing at all, which is the case where
278
- // there is nothing better to hold.
279
- const workspaceKey = signedIn?.machine_credential
280
- || existing?.workspaceKey
281
- || undefined;
421
+ const workspaceKey = signedIn?.machine_credential || undefined;
282
422
  // `typedByHand` is the second half of the fence in `writeConnection`. It is
283
423
  // true only when the person put the address on this command line, in this
284
424
  // run. A workspace resolved from their email domain, or an address a server
@@ -312,16 +452,39 @@ async function runLogin(args) {
312
452
  process.exitCode = 1;
313
453
  return;
314
454
  }
455
+ // So a new terminal presents the key just issued, not the last one or none.
456
+ for (const profile of (await Promise.resolve().then(() => __importStar(require('./install')))).finishSetup())
457
+ out(` Finishing setup: added one line to ${profile}`);
458
+ // Remembered for next time: who, and where, exactly as used now.
459
+ saveLast({ email: username, workspace: typedWorkspace, typed: typedByHand });
460
+ if (!byCompany && !kept && password && keychainAvailable()) {
461
+ const keep = (await (0, prompt_1.ask)(' keep your password in your Mac keychain, so next time is just Enter? (y/n) '))
462
+ .trim().toLowerCase();
463
+ if (keep === 'y' || keep === 'yes') {
464
+ out(savePassword(username, typedWorkspace, password)
465
+ ? ` ${ui.c.dim('kept in your keychain; remove it with')} ${ui.c.bold('comprism login --forget')}`
466
+ : ` ${ui.c.dim('the keychain would not take it; you will be asked next time')}`);
467
+ }
468
+ }
315
469
  out();
316
- out(ui.ok(`Signed in to ${ui.c.bold(url)}`));
470
+ // Where this machine will actually talk to: the address typed, when one was.
471
+ const where = (typedByHand && url) || signedIn?.workspace_url || url;
472
+ const onLaptop = /localhost|127\.0\.0\.1/.test(where);
473
+ out(ui.ok(`Signed in as ${ui.c.bold(username)} ${onLaptop
474
+ ? `on this laptop (${ui.c.bold(where.replace(/^https?:\/\//, ''))})`
475
+ : `at ${ui.c.bold(where)}`}.`));
317
476
  if (workspaceKey) {
318
- out(` ${ui.c.dim('this machine is enrolled and revocable on its own')}`);
477
+ out(` ${ui.c.dim('This machine is enrolled and can be revoked from Administration.')}`);
319
478
  // Straight into the welcome screen, so the first thing after signing in is
320
479
  // where they are rather than another instruction.
321
- const { showWelcome } = await Promise.resolve().then(() => __importStar(require('./welcome')));
322
- await showWelcome();
480
+ // Unless the session is about to open, which draws it itself.
481
+ if (!args.includes('--into-session')) {
482
+ const { showWelcome } = await Promise.resolve().then(() => __importStar(require('./welcome')));
483
+ await showWelcome();
484
+ }
323
485
  }
324
486
  else {
487
+ process.exitCode = 1;
325
488
  out(ui.warn('Signed in, but this machine could not be enrolled.'));
326
489
  // The workspace's OWN reason, carried back with the sign in. It used to
327
490
  // come from a local variable set by a separate call, which meant a blank
@@ -331,6 +494,48 @@ async function runLogin(args) {
331
494
  }
332
495
  out();
333
496
  }
497
+ /** Enrol this machine with an install key: checked with the service, then kept. */
498
+ async function loginWithKey(key, args) {
499
+ const flag = (name) => {
500
+ const i = args.indexOf(name);
501
+ return i >= 0 ? args[i + 1] : undefined;
502
+ };
503
+ const typed = flag('--workspace');
504
+ if (!key || !typed) {
505
+ out(ui.fail('Signing in with a key needs the key and the workspace: comprism login --key <key> --workspace <address>'));
506
+ process.exitCode = 1;
507
+ return;
508
+ }
509
+ const url = workspaceUrl(typed);
510
+ const tenant = tenantFor(url, typed, flag('--tenant'));
511
+ const { ask: askWith } = await Promise.resolve().then(() => __importStar(require('../lib/gateway')));
512
+ const existing = (0, connection_1.readConnection)();
513
+ try {
514
+ (0, connection_1.writeConnection)({
515
+ url, token: '', workspaceKey: key, ...(tenant ? { tenant } : {}),
516
+ everyMinutes: existing?.everyMinutes ?? 5,
517
+ connectedAt: new Date().toISOString(),
518
+ }, true);
519
+ }
520
+ catch (err) {
521
+ out(ui.fail(err instanceof Error ? err.message : 'the workspace address was refused'));
522
+ process.exitCode = 1;
523
+ return;
524
+ }
525
+ const reply = await askWith({ intent: 'catalog', prompt: '' });
526
+ if (!reply.ok) {
527
+ if (existing)
528
+ (0, connection_1.writeConnection)(existing, true);
529
+ out(ui.fail(reply.message || `That key was not accepted by ${url}.`));
530
+ out(` ${ui.c.dim('make a new one on the Install and setup page')}`);
531
+ process.exitCode = 1;
532
+ return;
533
+ }
534
+ for (const profile of (await Promise.resolve().then(() => __importStar(require('./install')))).finishSetup())
535
+ out(` Finishing setup: added one line to ${profile}`);
536
+ out(ui.ok(`This machine is enrolled at ${ui.c.bold(url)}.`));
537
+ out(` ${ui.c.dim('It can be revoked from Developers, Install and setup.')}`);
538
+ }
334
539
  /**
335
540
  * Which workspace an email belongs to.
336
541
  *
@@ -359,8 +564,9 @@ async function resolveWorkspace(email) {
359
564
  // because a person looking for their workspace does not have one yet.
360
565
  const root = `https://${WORKSPACE_DOMAIN}`;
361
566
  const reply = await (0, gateway_1.askAt)(root, { intent: 'resolve_workspace', prompt: email });
362
- if (!reply.ok || reply.served === false)
363
- return null;
567
+ if (!reply.ok || reply.served === false || reply.complete === false) {
568
+ throw new Error(reply.message || 'Your workspace could not be looked up. Try again later.');
569
+ }
364
570
  const url = reply.session?.workspace_url;
365
571
  if (!url)
366
572
  return null;
@@ -0,0 +1,3 @@
1
+ /** `comprism memory add` and the session word `remember`. */
2
+ export declare function addNote(text: string): Promise<number>;
3
+ export declare function cmdMemory(args: string[]): Promise<number>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.addNote = addNote;
37
+ exports.cmdMemory = cmdMemory;
38
+ /**
39
+ * `comprism memory` (manual 2.9): this project's notes. They are files under
40
+ * `.completionprism/memory/`; this command lists them, and adds, removes and
41
+ * opens them as the service says. Which notes are used, numbering and every
42
+ * word shown are the service's.
43
+ */
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const project_ops_1 = require("../lib/project-ops");
47
+ const ui = __importStar(require("../lib/ui"));
48
+ const out = (s = "") => process.stdout.write(s + "\n");
49
+ const FOLDER = ".completionprism/memory";
50
+ const SETTINGS_FILES = [".completionprism/settings.local.json", ".completionprism/settings.json"];
51
+ function notes(root) {
52
+ let names = [];
53
+ try {
54
+ names = fs.readdirSync(path.join(root, FOLDER)).filter((n) => n.endsWith(".md")).sort();
55
+ }
56
+ catch { /* none yet */ }
57
+ return names
58
+ .map((n) => ({ file: `${FOLDER}/${n}`, text: (0, project_ops_1.readInside)(root, `${FOLDER}/${n}`) ?? "" }))
59
+ .filter((n) => n.text.trim());
60
+ }
61
+ /** Carry out what the service said to write or delete, inside the project. */
62
+ function apply(facts, data) {
63
+ const write = data?.write;
64
+ if (write?.key)
65
+ (0, project_ops_1.writeSetting)(facts.root, write.file, write.key, write.value);
66
+ else if (write?.file && typeof write.content === "string") {
67
+ const full = path.join(facts.root, write.file);
68
+ fs.mkdirSync(path.dirname(full), { recursive: true });
69
+ fs.writeFileSync(full, write.content);
70
+ }
71
+ const gone = data?.delete;
72
+ if (typeof gone === "string" && gone.startsWith(`${FOLDER}/`))
73
+ fs.rmSync(path.join(facts.root, gone), { force: true });
74
+ }
75
+ /** `comprism memory add` and the session word `remember`. */
76
+ async function addNote(text) {
77
+ const facts = await (0, project_ops_1.currentProject)();
78
+ const reply = await (0, project_ops_1.call)("memory", "add", { text }, facts);
79
+ if (!reply)
80
+ return 1;
81
+ apply(facts, reply.data);
82
+ return 0;
83
+ }
84
+ async function cmdMemory(args) {
85
+ const [verb, ...rest] = args;
86
+ const facts = await (0, project_ops_1.currentProject)();
87
+ const base = { notes: notes(facts.root), files: (0, project_ops_1.readSome)(facts.root, SETTINGS_FILES) };
88
+ if (verb === "add")
89
+ return addNote(rest.join(" "));
90
+ if (verb === "edit") {
91
+ const files = base.notes.map((n) => path.join(facts.root, n.file));
92
+ if (!files.length) {
93
+ out(ui.info('No notes yet. Add one with: comprism memory add "<text>"'));
94
+ return 0;
95
+ }
96
+ return (await (0, project_ops_1.openEditor)(files)) ? 0 : 1;
97
+ }
98
+ if (verb === "forget" || verb === "on" || verb === "off") {
99
+ const reply = await (0, project_ops_1.call)("memory", verb, { ...base, number: rest[0] }, facts);
100
+ if (!reply)
101
+ return 1;
102
+ apply(facts, reply.data);
103
+ return 0;
104
+ }
105
+ if (verb) {
106
+ out(ui.fail(`\`memory ${verb}\` is not a command. Use add, edit, forget N, on or off.`));
107
+ return 2;
108
+ }
109
+ out();
110
+ const reply = await (0, project_ops_1.call)("memory", "list", base, facts);
111
+ out();
112
+ return reply ? 0 : 1;
113
+ }
@@ -0,0 +1 @@
1
+ export declare function cmdPermissions(args: string[]): Promise<number>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.cmdPermissions = cmdPermissions;
37
+ /**
38
+ * `comprism permissions` and `comprism permissions why <action> "<target>"`
39
+ * (manual 4.14).
40
+ *
41
+ * This reads the project's settings files and sends their text; the service
42
+ * decides and writes every line printed, with the same decision the agent's
43
+ * jobs use, so `why` and a real job can never disagree.
44
+ */
45
+ const fs = __importStar(require("node:fs"));
46
+ const path = __importStar(require("node:path"));
47
+ const gateway_1 = require("../lib/gateway");
48
+ const ui = __importStar(require("../lib/ui"));
49
+ const sessions_1 = require("../lib/sessions");
50
+ /** The settings files that can hold rules, relative to the project root. The
51
+ * service decides which of them count and how. */
52
+ const RULE_FILES = [
53
+ '.completionprism/settings.json', '.completionprism/settings.local.json',
54
+ '.comprism/settings.json', '.comprism/settings.local.json',
55
+ ];
56
+ const USAGE = 'Usage: comprism permissions, or comprism permissions why <action> "<target>"';
57
+ async function cmdPermissions(args) {
58
+ const [op, action, ...rest] = args;
59
+ if (op && op !== 'why') {
60
+ process.stderr.write(` ${USAGE}\n`);
61
+ return 2;
62
+ }
63
+ if (op === 'why' && (!action || rest.length === 0)) {
64
+ process.stderr.write(` ${USAGE}\n`);
65
+ return 2;
66
+ }
67
+ const { resolveProject, asWire } = await Promise.resolve().then(() => __importStar(require('../lib/project')));
68
+ const { currentWorkspace } = await Promise.resolve().then(() => __importStar(require('./settings')));
69
+ const facts = resolveProject(currentWorkspace().path, []);
70
+ const files = {};
71
+ for (const rel of RULE_FILES) {
72
+ try {
73
+ files[rel] = fs.readFileSync(path.join(facts.root, rel), 'utf8');
74
+ }
75
+ catch { /* none */ }
76
+ }
77
+ const reply = await (0, gateway_1.ask)({
78
+ intent: 'permissions',
79
+ prompt: '',
80
+ project: asWire(facts),
81
+ permissionsPayload: {
82
+ op: op === 'why' ? 'why' : 'list', action, target: rest.join(' '), files,
83
+ },
84
+ });
85
+ const lines = reply.permissions?.lines;
86
+ if (!reply.ok || !Array.isArray(lines)) {
87
+ process.stderr.write(ui.fail(reply.served === false
88
+ ? 'Your workspace is running a build without permission answers yet.'
89
+ : (0, sessions_1.failure)(reply)) + '\n');
90
+ return (0, sessions_1.exitFor)(reply);
91
+ }
92
+ process.stdout.write(`${lines.join('\n')}\n`);
93
+ return 0;
94
+ }
@@ -0,0 +1,4 @@
1
+ /** Every text file in the folder, by path inside it. Never runs anything. */
2
+ export declare function readFolder(dir: string): Record<string, string>;
3
+ export declare const PLUGINS_HELP = " comprism plugins installed plugins and their state\n comprism plugins install <name|folder> install from your company's catalog or a folder\n --scope project|personal skip the question\n comprism plugins enable <name> comprism plugins disable <name>\n comprism plugins update <name> shows what changes, then asks\n comprism plugins remove <name>";
4
+ export declare function cmdPlugins(args: string[]): Promise<number>;