@illuminis/comprism 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +281 -0
  3. package/out/agent/command.d.ts +86 -0
  4. package/out/agent/command.js +259 -0
  5. package/out/agent/render.d.ts +97 -0
  6. package/out/agent/render.js +255 -0
  7. package/out/agent/session.d.ts +175 -0
  8. package/out/agent/session.js +573 -0
  9. package/out/commands/ask.d.ts +1 -0
  10. package/out/commands/ask.js +146 -0
  11. package/out/commands/codemap.d.ts +2 -0
  12. package/out/commands/codemap.js +151 -0
  13. package/out/commands/commands-thin.d.ts +39 -0
  14. package/out/commands/commands-thin.js +182 -0
  15. package/out/commands/install.d.ts +163 -0
  16. package/out/commands/install.js +543 -0
  17. package/out/commands/keys.d.ts +55 -0
  18. package/out/commands/keys.js +344 -0
  19. package/out/commands/login.d.ts +9 -0
  20. package/out/commands/login.js +384 -0
  21. package/out/commands/repl.d.ts +1 -0
  22. package/out/commands/repl.js +752 -0
  23. package/out/commands/settings.d.ts +21 -0
  24. package/out/commands/settings.js +244 -0
  25. package/out/commands/welcome.d.ts +1 -0
  26. package/out/commands/welcome.js +196 -0
  27. package/out/executor/documents.d.ts +40 -0
  28. package/out/executor/documents.js +170 -0
  29. package/out/executor/files.d.ts +2 -0
  30. package/out/executor/files.js +360 -0
  31. package/out/executor/git.d.ts +48 -0
  32. package/out/executor/git.js +132 -0
  33. package/out/executor/hooks.d.ts +67 -0
  34. package/out/executor/hooks.js +247 -0
  35. package/out/executor/index.d.ts +29 -0
  36. package/out/executor/index.js +221 -0
  37. package/out/executor/notebook.d.ts +2 -0
  38. package/out/executor/notebook.js +147 -0
  39. package/out/executor/paths.d.ts +15 -0
  40. package/out/executor/paths.js +126 -0
  41. package/out/executor/shell.d.ts +41 -0
  42. package/out/executor/shell.js +336 -0
  43. package/out/graph/build.d.ts +45 -0
  44. package/out/graph/build.js +91 -0
  45. package/out/graph/facts.d.ts +47 -0
  46. package/out/graph/facts.js +12 -0
  47. package/out/graph/files.d.ts +45 -0
  48. package/out/graph/files.js +207 -0
  49. package/out/graph/read-locales.d.ts +29 -0
  50. package/out/graph/read-locales.js +246 -0
  51. package/out/graph/read-python.d.ts +11 -0
  52. package/out/graph/read-python.js +115 -0
  53. package/out/graph/read-typescript.d.ts +16 -0
  54. package/out/graph/read-typescript.js +292 -0
  55. package/out/graph/sync.d.ts +66 -0
  56. package/out/graph/sync.js +242 -0
  57. package/out/lib/attach.d.ts +62 -0
  58. package/out/lib/attach.js +228 -0
  59. package/out/lib/config.d.ts +93 -0
  60. package/out/lib/config.js +198 -0
  61. package/out/lib/connection.d.ts +73 -0
  62. package/out/lib/connection.js +188 -0
  63. package/out/lib/gateway.d.ts +239 -0
  64. package/out/lib/gateway.js +171 -0
  65. package/out/lib/prompt.d.ts +34 -0
  66. package/out/lib/prompt.js +108 -0
  67. package/out/lib/types.d.ts +417 -0
  68. package/out/lib/types.js +21 -0
  69. package/out/lib/ui.d.ts +114 -0
  70. package/out/lib/ui.js +265 -0
  71. package/out/lib/version.d.ts +24 -0
  72. package/out/lib/version.js +27 -0
  73. package/out/lib/voice.d.ts +50 -0
  74. package/out/lib/voice.js +218 -0
  75. package/out/postinstall.d.ts +2 -0
  76. package/out/postinstall.js +92 -0
  77. package/out/thin.d.ts +2 -0
  78. package/out/thin.js +259 -0
  79. package/package.json +101 -0
  80. package/scripts/read_python.py +270 -0
@@ -0,0 +1,384 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.workspaceUrl = workspaceUrl;
40
+ exports.cmdLogin = cmdLogin;
41
+ /**
42
+ * `comprism login` — the whole sign in, asked one question at a time.
43
+ *
44
+ * ── Why this replaced what was there ──────────────────────────────────────
45
+ *
46
+ * Signing in used to mean typing this, correctly, from memory:
47
+ *
48
+ * comprism connect --url https://base-tenant.completionprism.illuminis.ai --email you@company.com
49
+ *
50
+ * A new customer does not know their workspace address, has never seen that
51
+ * flag, and gets no second chance at a first impression. Worse, if their
52
+ * account required a second factor the command refused outright and told them
53
+ * to go and find a token in a browser, which meant the terminal product was
54
+ * unreachable for every account a security team had done the right thing to.
55
+ *
56
+ * So: one word, then two questions, then the code if their account asks for one.
57
+ *
58
+ * comprism login
59
+ * email: you@acme.com
60
+ * password: ••••••••
61
+ * code: 123456
62
+ *
63
+ * THE WORKSPACE IS NOT ASKED FOR. It is looked up from the email's domain,
64
+ * which the product already does for the browser sign in: a domain maps to at
65
+ * most one workspace, enforced by a unique index, so there is never a choice to
66
+ * put in front of anybody. An unknown domain, which is what a personal email
67
+ * is, resolves to the shared individuals workspace, because that is exactly
68
+ * where signing up puts it.
69
+ *
70
+ * The lookup reads the workspace registry and never the user list, so it cannot
71
+ * be used to find out who has an account.
72
+ *
73
+ * `--workspace` still works, for a person whose company runs a workspace under
74
+ * a domain that is not in their email, and for our own testing.
75
+ */
76
+ const node_os_1 = __importDefault(require("node:os"));
77
+ const connection_1 = require("../lib/connection");
78
+ const gateway_1 = require("../lib/gateway");
79
+ const ui = __importStar(require("../lib/ui"));
80
+ const prompt_1 = require("../lib/prompt");
81
+ const out = (s = '') => process.stdout.write(s + '\n');
82
+ /** Where a named workspace lives. One format, stated once. */
83
+ const WORKSPACE_DOMAIN = 'completionprism.illuminis.ai';
84
+ /**
85
+ * Turn what a person typed into the address of their workspace.
86
+ *
87
+ * Accepts the workspace name, which is the normal case, and also a full URL or
88
+ * a bare host, because somebody who was given a link will paste the link. All
89
+ * three arrive at the same place rather than at an error message.
90
+ */
91
+ function workspaceUrl(typed) {
92
+ const value = typed.trim().replace(/\/+$/, '');
93
+ if (/^https?:\/\//i.test(value))
94
+ return value;
95
+ // A host with a port, or a machine on this network, is already an address.
96
+ // Without this, `localhost:8009` had our domain appended to it and the
97
+ // command reported a network problem for what was really a parsing one.
98
+ if (/^(localhost|127\.0\.0\.1)(:\d+)?$/i.test(value))
99
+ return `http://${value}`;
100
+ if (value.includes(':'))
101
+ return `http://${value}`;
102
+ if (value.includes('.'))
103
+ return `https://${value}`;
104
+ return `https://${value}.${WORKSPACE_DOMAIN}`;
105
+ }
106
+ /** Localhost needs the tenant named, because there is no subdomain to read. */
107
+ function tenantFor(url, typed, flagged) {
108
+ if (flagged)
109
+ return flagged;
110
+ if (!/localhost|127\.0\.0\.1/.test(url))
111
+ return undefined;
112
+ // There is no subdomain on a local address to read the tenant from, so the
113
+ // developer tenant is assumed unless one was named.
114
+ return 'base-tenant';
115
+ }
116
+ async function post(url, path, body, tenant) {
117
+ const headers = { 'content-type': 'application/json' };
118
+ if (tenant)
119
+ headers['X-Tenant-ID'] = tenant;
120
+ const res = await fetch(`${url}${path}`, {
121
+ method: 'POST', headers, body: JSON.stringify(body),
122
+ });
123
+ let json = {};
124
+ try {
125
+ json = (await res.json());
126
+ }
127
+ catch { /* non-JSON body */ }
128
+ return { status: res.status, json };
129
+ }
130
+ async function runLogin(args) {
131
+ out(ui.banner('Sign in'));
132
+ out();
133
+ // Flags still work, for a script or a second machine. A person gets asked.
134
+ const flag = (name) => {
135
+ const i = args.indexOf(name);
136
+ return i >= 0 ? args[i + 1] : undefined;
137
+ };
138
+ const username = flag('--username') || flag('--email') || await (0, prompt_1.ask)(' email: ');
139
+ if (!username) {
140
+ out(ui.fail('An email address is needed.'));
141
+ out();
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ // Where this person's workspace is. Asked of the product, not of them.
146
+ let typedWorkspace = flag('--workspace');
147
+ // Captured BEFORE the email lookup can fill it in. A workspace the product
148
+ // 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);
151
+ if (!typedWorkspace) {
152
+ const live = new ui.LiveIndicator();
153
+ live.start('finding your workspace');
154
+ typedWorkspace = (await resolveWorkspace(username)) || undefined;
155
+ live.stop();
156
+ // The indicator clears its own line but leaves the cursor mid-row, so the
157
+ // next prompt landed after a run of blanks.
158
+ process.stdout.write('\r');
159
+ if (!typedWorkspace) {
160
+ out();
161
+ out(ui.fail('No workspace is registered for that email address.'));
162
+ out(` ${ui.c.dim('if your company uses a different domain, pass --workspace <name>')}`);
163
+ out();
164
+ process.exitCode = 1;
165
+ return;
166
+ }
167
+ }
168
+ const url = workspaceUrl(typedWorkspace);
169
+ const tenant = tenantFor(url, typedWorkspace, flag('--tenant'));
170
+ const password = await (0, prompt_1.askHidden)(' password: ');
171
+ if (!password) {
172
+ out(ui.fail('A password is needed.'));
173
+ out();
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+ // EVERYTHING BELOW GOES THROUGH THE ONE SERVICE.
178
+ //
179
+ // This used to call three separate addresses in sequence: sign in, second
180
+ // factor, then machine enrollment. Three doors for one act, and the tool had
181
+ // to know the order to knock on them in, which is product logic living on a
182
+ // customer's laptop. It is now one question, asked twice only when a second
183
+ // factor is genuinely required.
184
+ let signedIn = null;
185
+ let signInMessage = '';
186
+ {
187
+ let reply = await (0, gateway_1.askAt)(url, {
188
+ intent: 'sign_in', prompt: username, password,
189
+ machineLabel: `comprism on ${node_os_1.default.hostname()}`,
190
+ }, tenant);
191
+ if (reply.served === false) {
192
+ out();
193
+ out(ui.fail('That workspace is running a build that cannot sign you in yet.'));
194
+ out(` ${ui.c.dim(reply.message || 'the workspace is behind this copy of the tool')}`);
195
+ out();
196
+ process.exitCode = 1;
197
+ return;
198
+ }
199
+ if (!reply.ok) {
200
+ 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')}`);
203
+ out();
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+ // A second factor is a normal ANSWER, not a failure. This is the whole
208
+ // reason the command exists: an older one gave up here and sent people to a
209
+ // browser to find a token by hand.
210
+ if (reply.sign_in?.needs_second_factor && reply.sign_in.challenge) {
211
+ out();
212
+ 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: ');
214
+ if (!code) {
215
+ out(ui.fail('A code is needed to finish signing in.'));
216
+ out();
217
+ process.exitCode = 1;
218
+ return;
219
+ }
220
+ reply = await (0, gateway_1.askAt)(url, {
221
+ intent: 'sign_in', prompt: username,
222
+ challenge: reply.sign_in.challenge, code,
223
+ machineLabel: `comprism on ${node_os_1.default.hostname()}`,
224
+ }, tenant);
225
+ if (!reply.ok || !reply.sign_in?.token) {
226
+ out();
227
+ out(ui.fail(reply.message || 'That code was not accepted. Codes expire after a few minutes.'));
228
+ out();
229
+ process.exitCode = 1;
230
+ return;
231
+ }
232
+ }
233
+ if (!reply.sign_in?.token) {
234
+ out();
235
+ out(ui.fail(reply.message || 'That username and password were not accepted.'));
236
+ out(` ${ui.c.dim('check the workspace name too: a right password in the wrong workspace looks the same')}`);
237
+ out();
238
+ process.exitCode = 1;
239
+ return;
240
+ }
241
+ signedIn = reply.sign_in;
242
+ signInMessage = reply.message || '';
243
+ if (reply.complete === false && reply.message) {
244
+ // Signed in, but something about it is incomplete. Said, not swallowed.
245
+ out();
246
+ out(ui.warn(reply.message));
247
+ }
248
+ }
249
+ const token = signedIn?.token;
250
+ if (!token) {
251
+ out();
252
+ out(ui.fail('Sign in succeeded but no session came back.'));
253
+ out();
254
+ process.exitCode = 1;
255
+ return;
256
+ }
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.
263
+ 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;
282
+ // `typedByHand` is the second half of the fence in `writeConnection`. It is
283
+ // true only when the person put the address on this command line, in this
284
+ // run. A workspace resolved from their email domain, or an address a server
285
+ // handed back, can never install a laptop address however convincingly it
286
+ // asks.
287
+ try {
288
+ (0, connection_1.writeConnection)({
289
+ // The address the SERVER says it answers on, UNLESS the person named one
290
+ // themselves on this command line.
291
+ //
292
+ // Preferring the server is right for everybody who did not type an
293
+ // address: a tool that stores its own guess can never be told by anybody
294
+ // that the guess was wrong, which is how a machine sat pointed at a dead
295
+ // port for an evening. It is exactly wrong for somebody who DID type one.
296
+ // They typed it because they meant it, and the server, being asked where
297
+ // it lives, answers with its public address and quietly replaces the one
298
+ // they chose. That made testing a change against a local copy impossible:
299
+ // sign in, and the tool went straight back to production.
300
+ url: (typedByHand && url) || signedIn?.workspace_url || url,
301
+ token,
302
+ ...(tenant ? { tenant } : {}),
303
+ ...(workspaceKey ? { workspaceKey } : {}),
304
+ everyMinutes: existing?.everyMinutes ?? 5,
305
+ connectedAt: existing?.connectedAt || new Date().toISOString(),
306
+ }, typedByHand);
307
+ }
308
+ catch (err) {
309
+ out();
310
+ out(ui.fail(err instanceof Error ? err.message : 'the workspace address was refused'));
311
+ out();
312
+ process.exitCode = 1;
313
+ return;
314
+ }
315
+ out();
316
+ out(ui.ok(`Signed in to ${ui.c.bold(url)}`));
317
+ if (workspaceKey) {
318
+ out(` ${ui.c.dim('this machine is enrolled and revocable on its own')}`);
319
+ // Straight into the welcome screen, so the first thing after signing in is
320
+ // where they are rather than another instruction.
321
+ const { showWelcome } = await Promise.resolve().then(() => __importStar(require('./welcome')));
322
+ await showWelcome();
323
+ }
324
+ else {
325
+ out(ui.warn('Signed in, but this machine could not be enrolled.'));
326
+ // The workspace's OWN reason, carried back with the sign in. It used to
327
+ // come from a local variable set by a separate call, which meant a blank
328
+ // line whenever that call had not been the thing that failed.
329
+ out(` ${ui.c.dim(signInMessage || 'the workspace did not say why')}`);
330
+ out(` ${ui.c.dim('you are signed in; this machine cannot ask for work until it is enrolled')}`);
331
+ }
332
+ out();
333
+ }
334
+ /**
335
+ * Which workspace an email belongs to.
336
+ *
337
+ * Asked of the app root, which is tenant-agnostic by design and serves this
338
+ * exact question for the browser sign in. Returns the workspace name, or null
339
+ * when the product cannot place the address.
340
+ */
341
+ async function resolveWorkspace(email) {
342
+ // ONE root, and it is production.
343
+ //
344
+ // This list used to put `http://localhost:8009` first whenever COMPRISM_DEV
345
+ // was set. An environment variable exported once, in a shell profile or by a
346
+ // test, then silently redirected every future sign in on that machine to a
347
+ // port that is usually not listening. That is half of how a developer's own
348
+ // machine came to be pointed at his laptop on 17 September 2026, reporting
349
+ // that his account had no keys while four were stored and active.
350
+ //
351
+ // Working against a local server is still supported and is now something a
352
+ // person SAYS rather than something an environment does to them:
353
+ // `comprism login --workspace localhost:8009`.
354
+ // ASKED OF THE ONE SERVICE, at the product's own address.
355
+ //
356
+ // This used to post to the authentication route directly, which made the
357
+ // lookup a second door the tool had to know about and knock on in the right
358
+ // order. It is now an intent like every other, answered without a credential
359
+ // because a person looking for their workspace does not have one yet.
360
+ const root = `https://${WORKSPACE_DOMAIN}`;
361
+ const reply = await (0, gateway_1.askAt)(root, { intent: 'resolve_workspace', prompt: email });
362
+ if (!reply.ok || reply.served === false)
363
+ return null;
364
+ const url = reply.session?.workspace_url;
365
+ if (!url)
366
+ return null;
367
+ try {
368
+ // The answer is a sign-in address. Its host is the workspace address.
369
+ return new URL(url).host;
370
+ }
371
+ catch {
372
+ return null;
373
+ }
374
+ }
375
+ async function cmdLogin(args) {
376
+ try {
377
+ await runLogin(args);
378
+ }
379
+ finally {
380
+ // Always, on every path. A reader left open holds the terminal and the
381
+ // command appears to hang after it has finished its work.
382
+ (0, prompt_1.closePrompt)();
383
+ }
384
+ }
@@ -0,0 +1 @@
1
+ export declare function runRepl(): Promise<void>;