@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.
Files changed (131) hide show
  1. package/README.md +98 -36
  2. package/out/agent/command.d.ts +75 -4
  3. package/out/agent/command.js +220 -25
  4. package/out/agent/render.d.ts +17 -2
  5. package/out/agent/render.js +157 -14
  6. package/out/agent/session.d.ts +141 -2
  7. package/out/agent/session.js +735 -149
  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 +78 -11
  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 +188 -36
  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 +8 -1
  39. package/out/commands/repl.js +1207 -118
  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/worktrees.d.ts +5 -0
  55. package/out/commands/worktrees.js +186 -0
  56. package/out/executor/browser.d.ts +14 -0
  57. package/out/executor/browser.js +270 -0
  58. package/out/executor/diagnostics.d.ts +2 -0
  59. package/out/executor/diagnostics.js +181 -0
  60. package/out/executor/files.js +270 -40
  61. package/out/executor/git.js +42 -29
  62. package/out/executor/hooks.d.ts +42 -58
  63. package/out/executor/hooks.js +89 -182
  64. package/out/executor/index.d.ts +21 -5
  65. package/out/executor/index.js +160 -14
  66. package/out/executor/paths.d.ts +6 -1
  67. package/out/executor/paths.js +34 -6
  68. package/out/executor/sandbox.d.ts +40 -0
  69. package/out/executor/sandbox.js +299 -0
  70. package/out/executor/shell.d.ts +49 -4
  71. package/out/executor/shell.js +302 -56
  72. package/out/executor/toolservers.d.ts +20 -0
  73. package/out/executor/toolservers.js +189 -0
  74. package/out/executor/worktree.d.ts +9 -0
  75. package/out/executor/worktree.js +119 -0
  76. package/out/graph/read-python.js +2 -1
  77. package/out/lib/attach.d.ts +56 -12
  78. package/out/lib/attach.js +230 -63
  79. package/out/lib/clipboard.d.ts +23 -0
  80. package/out/lib/clipboard.js +182 -0
  81. package/out/lib/commandlist.d.ts +20 -0
  82. package/out/lib/commandlist.js +58 -0
  83. package/out/lib/decision.d.ts +22 -0
  84. package/out/lib/decision.js +50 -0
  85. package/out/lib/fingerprint.d.ts +25 -0
  86. package/out/lib/fingerprint.js +58 -0
  87. package/out/lib/gateway.d.ts +186 -2
  88. package/out/lib/gateway.js +59 -4
  89. package/out/lib/history.d.ts +24 -0
  90. package/out/lib/history.js +137 -0
  91. package/out/lib/ide.d.ts +19 -0
  92. package/out/lib/ide.js +131 -0
  93. package/out/lib/keyboard.d.ts +95 -0
  94. package/out/lib/keyboard.js +383 -0
  95. package/out/lib/machine.d.ts +21 -0
  96. package/out/lib/machine.js +91 -0
  97. package/out/lib/notify.d.ts +4 -0
  98. package/out/lib/notify.js +52 -0
  99. package/out/lib/output.d.ts +48 -0
  100. package/out/lib/output.js +108 -0
  101. package/out/lib/project-ops.d.ts +19 -0
  102. package/out/lib/project-ops.js +146 -0
  103. package/out/lib/project.d.ts +28 -0
  104. package/out/lib/project.js +114 -0
  105. package/out/lib/prompt.js +15 -2
  106. package/out/lib/queue.d.ts +13 -0
  107. package/out/lib/queue.js +116 -0
  108. package/out/lib/readiness.d.ts +19 -0
  109. package/out/lib/readiness.js +170 -1
  110. package/out/lib/self.d.ts +23 -0
  111. package/out/lib/self.js +124 -0
  112. package/out/lib/sessions.d.ts +19 -0
  113. package/out/lib/sessions.js +221 -0
  114. package/out/lib/stdin.d.ts +32 -0
  115. package/out/lib/stdin.js +117 -0
  116. package/out/lib/store.d.ts +40 -0
  117. package/out/lib/store.js +138 -0
  118. package/out/lib/sync.d.ts +18 -0
  119. package/out/lib/sync.js +81 -0
  120. package/out/lib/ui.d.ts +2 -4
  121. package/out/lib/ui.js +31 -25
  122. package/out/lib/voice.js +24 -0
  123. package/out/postinstall.js +42 -14
  124. package/out/providers/anthropic.d.ts +22 -0
  125. package/out/providers/anthropic.js +80 -0
  126. package/out/providers/index.d.ts +8 -0
  127. package/out/providers/index.js +83 -0
  128. package/out/providers/openai.d.ts +11 -0
  129. package/out/providers/openai.js +57 -0
  130. package/out/thin.js +609 -21
  131. package/package.json +9 -49
@@ -0,0 +1,383 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Keyboard = exports.LINE_BREAK = void 0;
4
+ exports.asOneLine = asOneLine;
5
+ /**
6
+ * The keystrokes a session's reader never sees on their own (manual 6.2, 6.3,
7
+ * 6.5).
8
+ *
9
+ * Node's line reader edits one line well and knows nothing about a paste: every
10
+ * line break inside a pasted stack trace reached it as Enter, so a 240 line
11
+ * paste was sent as 240 requests, each billed. It also has no Alt+Enter and no
12
+ * search of earlier requests.
13
+ *
14
+ * So the session's reader reads from this filter instead of from the keyboard.
15
+ * The filter passes ordinary typing straight through and handles four things
16
+ * itself:
17
+ *
18
+ * * A paste, bracketed by the terminal, becomes one piece of the request. The
19
+ * reader is given a short label, `[pasted 240 lines]`, that can be edited
20
+ * around like any word, and the label is swapped back for the exact text,
21
+ * tabs and trailing spaces included, when the request is sent.
22
+ * * A terminal that does not bracket pastes still delivers a paste in one read,
23
+ * so several lines arriving in one read are treated the same way. A person
24
+ * typing never produces a line break followed by more text in one read.
25
+ * * Alt+Enter continues the request on a new line, exactly as `\` then Enter.
26
+ * * Ctrl+R searches earlier requests; Enter puts the match at the prompt to be
27
+ * edited or sent, and Esc or Ctrl+C puts back what was there.
28
+ *
29
+ * Nothing here is written anywhere. Pieces live in memory for the session.
30
+ */
31
+ const node_stream_1 = require("node:stream");
32
+ const node_string_decoder_1 = require("node:string_decoder");
33
+ const PASTE_ON = '\x1b[?2004h';
34
+ const PASTE_OFF = '\x1b[?2004l';
35
+ const PASTE_START = '\x1b[200~';
36
+ const PASTE_END = '\x1b[201~';
37
+ /** Stands for a line break inside a request shown on one line, such as a
38
+ * recalled request. Turned back into a line break when it is sent. */
39
+ exports.LINE_BREAK = '␤';
40
+ /** A request as it is shown on one editable line. */
41
+ function asOneLine(text) {
42
+ return text.replace(/\r\n?|\n/g, exports.LINE_BREAK);
43
+ }
44
+ class Keyboard {
45
+ history;
46
+ /** What the session's reader reads. */
47
+ input = new node_stream_1.PassThrough();
48
+ decoder = new node_string_decoder_1.StringDecoder('utf8');
49
+ pieces = [];
50
+ pasting = null;
51
+ carry = '';
52
+ continued = false;
53
+ search = null;
54
+ reader = null;
55
+ listening = false;
56
+ onData = (chunk) => this.take(typeof chunk === 'string' ? chunk : this.decoder.write(chunk));
57
+ /** Esc on its own (manual 6.7). Returns true when it was used, so the key
58
+ * goes no further; false passes it to the reader as before. */
59
+ onEscape = null;
60
+ /** Ctrl+O, the short or full view (manual 6.9). */
61
+ onFullView = null;
62
+ /** Ctrl+G, the request in your own editor (manual 6.6). */
63
+ onEditor = null;
64
+ /** Ctrl+V, the clipboard's picture, where the terminal passes it (3.4). */
65
+ onPasteImage = null;
66
+ /** `keys vi` (manual 6.10): Esc at the prompt enters normal mode. */
67
+ vi = false;
68
+ normal = false;
69
+ pendingD = false;
70
+ /** `history` is the session's earlier requests, newest first, as shown. */
71
+ constructor(history) {
72
+ this.history = history;
73
+ }
74
+ /** The reader whose line a search edits. */
75
+ bind(reader) {
76
+ this.reader = reader;
77
+ }
78
+ /**
79
+ * Start reading the keyboard. Raw, because the reader no longer holds the
80
+ * keyboard itself and so cannot switch it; bracketed, so the terminal marks
81
+ * where a paste begins and ends.
82
+ */
83
+ attach() {
84
+ if (this.listening || !process.stdin.isTTY)
85
+ return;
86
+ this.listening = true;
87
+ process.stdin.setRawMode(true);
88
+ process.stdin.on('data', this.onData);
89
+ process.stdin.resume();
90
+ process.stdout.write(PASTE_ON);
91
+ }
92
+ /**
93
+ * Stop reading, and give the keyboard back exactly as it was found, for a
94
+ * command that asks its own question and for the shell afterwards. A shell
95
+ * left with bracketed paste on prints markers around everything pasted.
96
+ */
97
+ detach() {
98
+ if (!this.listening)
99
+ return;
100
+ this.listening = false;
101
+ process.stdin.off('data', this.onData);
102
+ process.stdout.write(PASTE_OFF);
103
+ if (process.stdin.isTTY)
104
+ process.stdin.setRawMode(false);
105
+ process.stdin.pause();
106
+ this.search = null;
107
+ }
108
+ /** True once after Alt+Enter, so the line just ended continues the request. */
109
+ takeContinued() {
110
+ const was = this.continued;
111
+ this.continued = false;
112
+ return was;
113
+ }
114
+ /** The request as it will be sent: every paste label swapped for its text,
115
+ * and every shown line break made a real one again. */
116
+ expand(text) {
117
+ let out = text;
118
+ for (const p of this.pieces)
119
+ out = out.split(p.label).join(p.text);
120
+ return out.split(exports.LINE_BREAK).join('\n');
121
+ }
122
+ /** Feed raw keyboard text. Public so a test can type without a terminal. */
123
+ take(s) {
124
+ s = this.carry + s;
125
+ this.carry = '';
126
+ while (s) {
127
+ if (this.pasting !== null) {
128
+ const end = s.indexOf(PASTE_END);
129
+ if (end === -1) {
130
+ const keep = partialTail(s, PASTE_END);
131
+ this.pasting += s.slice(0, s.length - keep);
132
+ this.carry = s.slice(s.length - keep);
133
+ return;
134
+ }
135
+ const pasted = this.pasting + s.slice(0, end);
136
+ this.pasting = null;
137
+ s = s.slice(end + PASTE_END.length);
138
+ this.paste(pasted);
139
+ continue;
140
+ }
141
+ const start = s.indexOf(PASTE_START);
142
+ if (start === -1) {
143
+ // Only a tail that is clearly the start of a marker is held back. A
144
+ // lone Esc is a key of its own and must not wait for the next read.
145
+ const keep = partialTail(s, PASTE_START);
146
+ const held = keep >= 3 ? keep : 0;
147
+ this.keys(s.slice(0, s.length - held));
148
+ this.carry = s.slice(s.length - held);
149
+ return;
150
+ }
151
+ this.keys(s.slice(0, start));
152
+ s = s.slice(start + PASTE_START.length);
153
+ this.pasting = '';
154
+ }
155
+ }
156
+ forward(s) {
157
+ if (s)
158
+ this.input.write(s);
159
+ }
160
+ /** Typing, outside a bracketed paste. */
161
+ keys(s) {
162
+ if (!s)
163
+ return;
164
+ if (this.search)
165
+ return this.searchKeys(s);
166
+ // A lone Esc is a key of its own; a longer read starting with it is an
167
+ // arrow or another sequence and goes to the reader.
168
+ if (s === '\x1b' && this.onEscape?.())
169
+ return;
170
+ if (this.vi) {
171
+ if (s === '\x1b') {
172
+ this.normal = true;
173
+ return;
174
+ }
175
+ if (this.normal)
176
+ return this.viKeys(s);
177
+ }
178
+ // Several lines in one read is a paste from a terminal that does not mark
179
+ // them. An escape sequence is a key, never a paste.
180
+ if (s.length > 1 && !s.startsWith('\x1b') && /[\r\n]/.test(s.slice(0, -1))) {
181
+ return this.paste(s);
182
+ }
183
+ for (let i = 0; i < s.length; i += 1) {
184
+ const ch = s[i];
185
+ if (ch === '\x1b' && (s[i + 1] === '\r' || s[i + 1] === '\n')) {
186
+ this.continued = true;
187
+ this.forward('\r');
188
+ i += 1;
189
+ continue;
190
+ }
191
+ if (ch === '\x12') {
192
+ this.startSearch();
193
+ return this.keys(s.slice(i + 1));
194
+ }
195
+ if (ch === '\x0f' && this.onFullView) {
196
+ this.onFullView();
197
+ continue;
198
+ }
199
+ if (ch === '\x07' && this.onEditor) {
200
+ this.onEditor();
201
+ continue;
202
+ }
203
+ if (ch === '\x16' && this.onPasteImage) {
204
+ this.onPasteImage();
205
+ continue;
206
+ }
207
+ // One character at a time only up to the next special key; the rest is
208
+ // passed on whole so escape sequences stay together.
209
+ const next = nextSpecial(s, i);
210
+ this.forward(s.slice(i, next));
211
+ i = next - 1;
212
+ }
213
+ }
214
+ paste(raw) {
215
+ const text = raw.replace(/\r\n?/g, '\n');
216
+ // A paste of plain words is typing that arrived quickly. A tab would
217
+ // reach the reader as Tab and ask for completion, and a line break as
218
+ // Enter and send, so either makes it a piece.
219
+ if (!/[\n\t]/.test(text))
220
+ return this.keys(text);
221
+ const lines = text.replace(/\n$/, '').split('\n').length;
222
+ let label = `[pasted ${lines} line${lines === 1 ? '' : 's'}]`;
223
+ const same = this.pieces.filter((p) => p.label.startsWith(label.slice(0, -1))).length;
224
+ if (same > 0)
225
+ label = `${label.slice(0, -1)} #${same + 1}]`;
226
+ this.pieces.push({ label, text });
227
+ this.forward(label);
228
+ }
229
+ // ── keys vi ─────────────────────────────────────────────────────────────
230
+ // Normal mode, the handful of keys a line needs. Each is turned into the
231
+ // key the line editor already understands, so there is one editor, not two.
232
+ viKeys(s) {
233
+ const KEY = {
234
+ h: '\x1b[D', l: '\x1b[C', '0': '\x01', '$': '\x05', w: '\x1bf', b: '\x1bb',
235
+ x: '\x1b[3~', k: '\x1b[A', j: '\x1b[B', D: '\x0b',
236
+ };
237
+ for (let at = 0; at < s.length; at += 1) {
238
+ const ch = s[at];
239
+ // Back to typing: whatever follows in this read is typed as it is.
240
+ if (!this.normal)
241
+ return this.keys(s.slice(at));
242
+ if (this.pendingD) {
243
+ this.pendingD = false;
244
+ if (ch === 'd')
245
+ this.forward('\x01\x0b');
246
+ continue;
247
+ }
248
+ if (ch === 'd') {
249
+ this.pendingD = true;
250
+ continue;
251
+ }
252
+ if (ch === 'i') {
253
+ this.normal = false;
254
+ continue;
255
+ }
256
+ if (ch === 'a') {
257
+ this.forward('\x1b[C');
258
+ this.normal = false;
259
+ continue;
260
+ }
261
+ if (ch === 'A') {
262
+ this.forward('\x05');
263
+ this.normal = false;
264
+ continue;
265
+ }
266
+ if (ch === 'I') {
267
+ this.forward('\x01');
268
+ this.normal = false;
269
+ continue;
270
+ }
271
+ if (ch === '\r' || ch === '\n') {
272
+ this.normal = false;
273
+ this.forward('\r');
274
+ continue;
275
+ }
276
+ if (ch === '\x03') {
277
+ this.normal = false;
278
+ this.forward(ch);
279
+ continue;
280
+ }
281
+ const key = KEY[ch];
282
+ if (key)
283
+ this.forward(key);
284
+ }
285
+ }
286
+ // ── Ctrl+R ────────────────────────────────────────────────────────────────
287
+ editable() {
288
+ return this.reader;
289
+ }
290
+ startSearch() {
291
+ const r = this.editable();
292
+ if (!r)
293
+ return;
294
+ this.search = { query: '', skip: 0, before: r.line, prompt: r.getPrompt() };
295
+ this.drawSearch();
296
+ }
297
+ match() {
298
+ const s = this.search;
299
+ if (!s)
300
+ return null;
301
+ const q = s.query.toLowerCase();
302
+ const hits = this.history().filter((h) => h.toLowerCase().includes(q));
303
+ return hits[Math.min(s.skip, hits.length - 1)] ?? null;
304
+ }
305
+ drawSearch() {
306
+ const r = this.editable();
307
+ const s = this.search;
308
+ if (!r || !s)
309
+ return;
310
+ const hit = this.match();
311
+ r.setPrompt(` search earlier requests: ${s.query}${hit === null && s.query ? ' (no match)' : ''} > `);
312
+ this.setLine(hit ?? '');
313
+ }
314
+ setLine(text) {
315
+ const r = this.editable();
316
+ if (!r)
317
+ return;
318
+ r.line = text;
319
+ r.cursor = text.length;
320
+ r._refreshLine();
321
+ }
322
+ endSearch(keep) {
323
+ const r = this.editable();
324
+ const s = this.search;
325
+ const hit = keep ? this.match() : null;
326
+ this.search = null;
327
+ if (!r || !s)
328
+ return;
329
+ r.setPrompt(s.prompt);
330
+ this.setLine(keep ? (hit ?? s.before) : s.before);
331
+ }
332
+ searchKeys(s) {
333
+ const state = this.search;
334
+ for (let i = 0; i < s.length; i += 1) {
335
+ const ch = s[i];
336
+ if (ch === '\x1b') {
337
+ // A lone Esc cancels. An arrow or any other key sequence accepts the
338
+ // match and is then handled as that key.
339
+ if (i === s.length - 1)
340
+ return this.endSearch(false);
341
+ this.endSearch(true);
342
+ return this.keys(s.slice(i));
343
+ }
344
+ if (ch === '\r' || ch === '\n') {
345
+ this.endSearch(true);
346
+ return this.keys(s.slice(i + 1));
347
+ }
348
+ if (ch === '\x03' || ch === '\x07')
349
+ return this.endSearch(false);
350
+ if (ch === '\x12')
351
+ state.skip += 1;
352
+ else if (ch === '\x7f' || ch === '\b') {
353
+ state.query = state.query.slice(0, -1);
354
+ state.skip = 0;
355
+ }
356
+ else if (ch >= ' ') {
357
+ state.query += ch;
358
+ state.skip = 0;
359
+ }
360
+ }
361
+ this.drawSearch();
362
+ }
363
+ }
364
+ exports.Keyboard = Keyboard;
365
+ /** How many characters at the end of `s` could be the start of `marker`. */
366
+ function partialTail(s, marker) {
367
+ for (let n = Math.min(marker.length - 1, s.length); n > 0; n -= 1) {
368
+ if (marker.startsWith(s.slice(s.length - n)))
369
+ return n;
370
+ }
371
+ return 0;
372
+ }
373
+ /** Where the next key this filter handles itself begins, from `i + 1`. */
374
+ function nextSpecial(s, i) {
375
+ for (let j = i + 1; j < s.length; j += 1) {
376
+ const ch = s[j];
377
+ if (ch === '\x12' || ch === '\x0f' || ch === '\x07' || ch === '\x16')
378
+ return j;
379
+ if (ch === '\x1b' && (s[j + 1] === '\r' || s[j + 1] === '\n'))
380
+ return j;
381
+ }
382
+ return s.length;
383
+ }
@@ -0,0 +1,21 @@
1
+ export interface MachineKey {
2
+ values: string[] | null;
3
+ fallback: string;
4
+ what: string;
5
+ }
6
+ /** Every machine setting, its allowed values, and its default. `editor` takes
7
+ * any command. */
8
+ export declare const MACHINE_KEYS: Record<string, MachineKey>;
9
+ export declare function machineSettings(): Record<string, string>;
10
+ /** Whether a value was stored for this key, rather than the default. */
11
+ export declare function isSet(key: string): boolean;
12
+ /** Store one value. Returns why not, or null when it was stored. */
13
+ export declare function setMachine(key: string, value: string): string | null;
14
+ export declare function unsetMachine(key: string): string | null;
15
+ /**
16
+ * Apply the look before anything is drawn (manual 6.10, 6.12). `NO_COLOR`,
17
+ * `--plain`, `plain true` and `theme plain` all mean the same thing: no color
18
+ * and every update on a new line.
19
+ */
20
+ export declare function applyLook(plainFlag: boolean): void;
21
+ export declare function isPlain(): boolean;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MACHINE_KEYS = void 0;
4
+ exports.machineSettings = machineSettings;
5
+ exports.isSet = isSet;
6
+ exports.setMachine = setMachine;
7
+ exports.unsetMachine = unsetMachine;
8
+ exports.applyLook = applyLook;
9
+ exports.isPlain = isPlain;
10
+ /**
11
+ * Settings that describe this machine and nothing else (manual 2.10, 6.5,
12
+ * 6.10 to 6.12): how it looks, which keys edit a line, how it tells you a job
13
+ * finished, and whether requests are remembered.
14
+ *
15
+ * Kept in the tool's own `config.json`, never on the service, because none of
16
+ * them changes what the agent does: two people sharing a project can each have
17
+ * their own theme. Settings that do change what the agent does are the
18
+ * service's and the project's (manual 2.10), and are not written here.
19
+ */
20
+ const config_1 = require("./config");
21
+ /** Every machine setting, its allowed values, and its default. `editor` takes
22
+ * any command. */
23
+ exports.MACHINE_KEYS = {
24
+ theme: { values: ['dark', 'light', 'plain'], fallback: 'dark', what: 'colors for a dark or light terminal, or none' },
25
+ keys: { values: ['emacs', 'vi'], fallback: 'emacs', what: 'line editing keys' },
26
+ notify: { values: ['desktop', 'bell', 'off'], fallback: 'bell', what: 'how you are told a long job finished' },
27
+ plain: { values: ['true', 'false'], fallback: 'false', what: 'no color and no redrawn lines' },
28
+ history: { values: ['on', 'off'], fallback: 'on', what: 'remember earlier requests' },
29
+ editor: { values: null, fallback: '', what: 'editor for Ctrl+G when VISUAL and EDITOR are not set' },
30
+ };
31
+ function machineSettings() {
32
+ const stored = ((0, config_1.loadConfig)().machine ?? {});
33
+ const out = {};
34
+ for (const [k, spec] of Object.entries(exports.MACHINE_KEYS)) {
35
+ const v = stored[k];
36
+ out[k] = typeof v === 'string' && (spec.values === null || spec.values.includes(v)) ? v : spec.fallback;
37
+ }
38
+ return out;
39
+ }
40
+ /** Whether a value was stored for this key, rather than the default. */
41
+ function isSet(key) {
42
+ const stored = ((0, config_1.loadConfig)().machine ?? {});
43
+ return typeof stored[key] === 'string';
44
+ }
45
+ /** Store one value. Returns why not, or null when it was stored. */
46
+ function setMachine(key, value) {
47
+ const spec = exports.MACHINE_KEYS[key];
48
+ if (!spec)
49
+ return `${key} is not a setting for this machine.`;
50
+ if (spec.values !== null && !spec.values.includes(value)) {
51
+ return `${key} is one of ${spec.values.join(', ')}.`;
52
+ }
53
+ const cfg = (0, config_1.loadConfig)();
54
+ cfg.machine = { ...(cfg.machine ?? {}), [key]: value };
55
+ (0, config_1.saveConfig)(cfg);
56
+ return null;
57
+ }
58
+ function unsetMachine(key) {
59
+ if (!exports.MACHINE_KEYS[key])
60
+ return `${key} is not a setting for this machine.`;
61
+ const cfg = (0, config_1.loadConfig)();
62
+ const next = { ...(cfg.machine ?? {}) };
63
+ delete next[key];
64
+ cfg.machine = next;
65
+ (0, config_1.saveConfig)(cfg);
66
+ return null;
67
+ }
68
+ /**
69
+ * Apply the look before anything is drawn (manual 6.10, 6.12). `NO_COLOR`,
70
+ * `--plain`, `plain true` and `theme plain` all mean the same thing: no color
71
+ * and every update on a new line.
72
+ */
73
+ function applyLook(plainFlag) {
74
+ let m;
75
+ try {
76
+ m = machineSettings();
77
+ }
78
+ catch {
79
+ m = { theme: 'dark', plain: 'false' };
80
+ }
81
+ const plain = plainFlag || m.plain === 'true' || m.theme === 'plain'
82
+ || (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '');
83
+ if (plain) {
84
+ process.env.NO_COLOR = process.env.NO_COLOR || '1';
85
+ process.env.COMPRISM_PLAIN = '1';
86
+ }
87
+ process.env.COMPRISM_THEME = m.theme === 'light' ? 'light' : 'dark';
88
+ }
89
+ function isPlain() {
90
+ return process.env.COMPRISM_PLAIN === '1';
91
+ }
@@ -0,0 +1,4 @@
1
+ /** The way this machine will notify, after falling back. */
2
+ export declare function howNotified(): 'desktop' | 'bell' | 'off';
3
+ /** Tell the person. `say` prints a line in the session. */
4
+ export declare function notify(message: string, say: (line: string) => void): void;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.howNotified = howNotified;
4
+ exports.notify = notify;
5
+ /**
6
+ * Being told when it finishes (manual 6.11).
7
+ *
8
+ * `notify desktop` asks the operating system to show a notification: the
9
+ * built in `osascript` on macOS, `notify-send` on Linux. Where neither is
10
+ * there, the terminal bell rings instead and the session says so once. `bell`
11
+ * rings the bell, `off` does nothing. What to say is decided by the caller;
12
+ * this only reaches the operating system.
13
+ */
14
+ const node_child_process_1 = require("node:child_process");
15
+ const machine_1 = require("./machine");
16
+ let saidFallback = false;
17
+ function hasProgram(name) {
18
+ const r = (0, node_child_process_1.spawnSync)(process.platform === 'win32' ? 'where' : 'which', [name], { stdio: 'ignore' });
19
+ return r.status === 0;
20
+ }
21
+ /** The way this machine will notify, after falling back. */
22
+ function howNotified() {
23
+ const wanted = (0, machine_1.machineSettings)().notify;
24
+ if (wanted === 'off')
25
+ return 'off';
26
+ if (wanted === 'desktop') {
27
+ if (process.platform === 'darwin' && hasProgram('osascript'))
28
+ return 'desktop';
29
+ if (process.platform === 'linux' && hasProgram('notify-send'))
30
+ return 'desktop';
31
+ }
32
+ return 'bell';
33
+ }
34
+ /** Tell the person. `say` prints a line in the session. */
35
+ function notify(message, say) {
36
+ const how = howNotified();
37
+ if (how === 'off')
38
+ return;
39
+ if (how === 'desktop') {
40
+ const args = process.platform === 'darwin'
41
+ ? ['-e', `display notification ${JSON.stringify(message)} with title "comprism"`]
42
+ : ['comprism', message];
43
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
44
+ (0, node_child_process_1.spawn)(process.platform === 'darwin' ? 'osascript' : 'notify-send', args, { stdio: 'ignore', detached: true }).unref();
45
+ return;
46
+ }
47
+ if ((0, machine_1.machineSettings)().notify === 'desktop' && !saidFallback) {
48
+ saidFallback = true;
49
+ say(' desktop notifications are not available here, so the terminal bell is used instead');
50
+ }
51
+ process.stdout.write('\x07');
52
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * How a one request job writes its result for a script (manual 10.2, 10.3,
3
+ * Appendix B).
4
+ *
5
+ * Nothing here decides how a job ended. The service sends a reason word and
6
+ * the table that turns reason words into exit numbers, and this looks the word
7
+ * up. The only numbers known here are for the endings that happen when the
8
+ * service never answered: a typing mistake, no sign in, no connection, Ctrl+C.
9
+ */
10
+ export type Format = 'text' | 'json' | 'stream';
11
+ export interface OutputChoice {
12
+ format: Format;
13
+ /** Only the answer on standard output, even at a terminal (10.2). */
14
+ quiet: boolean;
15
+ /** Standard output is a file or a pipe: only the result goes there. */
16
+ plain: boolean;
17
+ }
18
+ export declare const FORMATS: Format[];
19
+ /** Read `--output` and `--quiet`. Null format means a value nobody accepts. */
20
+ export declare function outputChoice(args: string[]): OutputChoice | {
21
+ bad: string;
22
+ };
23
+ /** Whether progress belongs on standard error rather than standard output. */
24
+ export declare function progressToErr(o: OutputChoice): boolean;
25
+ /** The exit number for a reason, from the service's table when it sent one. */
26
+ export declare function exitFor(reason: string, table?: Record<string, number> | null): number;
27
+ /** The result object for an ending the service never saw, in its published
28
+ * shape: every field present, nothing known left as anything but null. */
29
+ export declare function localResult(reason: string, error: string): Record<string, unknown>;
30
+ /**
31
+ * Read a JSON file named on the line (`--schema`, `--config`): a permitted
32
+ * local read. The service decides what the contents mean.
33
+ */
34
+ export declare function readJsonFile(path: string, flagName: string): {
35
+ value: Record<string, unknown>;
36
+ } | {
37
+ bad: string;
38
+ };
39
+ /** Print the result object as one line of JSON on standard output. */
40
+ export declare function printJson(result: Record<string, unknown>): void;
41
+ /**
42
+ * Refuse a flag this command does not know (Appendix B, exit 2).
43
+ *
44
+ * `known` lists flags that take a value; `switches` lists flags that do not.
45
+ * A word that is a flag's value is skipped, so a request that happens to
46
+ * follow `--model` is never read as a flag.
47
+ */
48
+ export declare function unknownFlag(args: string[], known: string[], switches: string[]): string | null;