@illuminis/comprism 0.1.4 → 0.1.6

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 (116) hide show
  1. package/out/agent/command.js +5 -5
  2. package/out/agent/render.d.ts +39 -6
  3. package/out/agent/render.js +287 -28
  4. package/out/agent/session.d.ts +21 -1
  5. package/out/agent/session.js +81 -17
  6. package/out/commands/agents.js +3 -3
  7. package/out/commands/ask.js +20 -11
  8. package/out/commands/codemap.d.ts +1 -1
  9. package/out/commands/codemap.js +3 -3
  10. package/out/commands/commands-thin.js +4 -4
  11. package/out/commands/config.js +1 -1
  12. package/out/commands/cost.js +4 -4
  13. package/out/commands/hooks.js +1 -1
  14. package/out/commands/install.d.ts +1 -1
  15. package/out/commands/install.js +3 -2
  16. package/out/commands/instructions.js +1 -1
  17. package/out/commands/integrations.js +4 -4
  18. package/out/commands/keys.js +6 -6
  19. package/out/commands/login.js +97 -27
  20. package/out/commands/permissions.js +2 -2
  21. package/out/commands/plugins.js +3 -3
  22. package/out/commands/privacy.js +2 -2
  23. package/out/commands/repl.d.ts +4 -0
  24. package/out/commands/repl.js +563 -101
  25. package/out/commands/review.js +6 -6
  26. package/out/commands/settings.js +30 -123
  27. package/out/commands/skills.js +2 -2
  28. package/out/commands/unattended.js +6 -6
  29. package/out/commands/update.js +1 -1
  30. package/out/commands/welcome.d.ts +25 -1
  31. package/out/commands/welcome.js +65 -38
  32. package/out/commands/worktrees.d.ts +1 -1
  33. package/out/graph/sync.d.ts +1 -1
  34. package/out/graph/sync.js +6 -6
  35. package/out/lib/attach.js +4 -3
  36. package/out/lib/commandlist.d.ts +1 -1
  37. package/out/lib/commandlist.js +2 -2
  38. package/out/lib/config.d.ts +3 -2
  39. package/out/lib/config.js +7 -8
  40. package/out/lib/connection.js +1 -1
  41. package/out/lib/gateway.d.ts +9 -5
  42. package/out/lib/keyboard.d.ts +3 -0
  43. package/out/lib/keyboard.js +7 -0
  44. package/out/lib/lastlogin.d.ts +14 -0
  45. package/out/lib/lastlogin.js +105 -0
  46. package/out/lib/machine.js +1 -1
  47. package/out/lib/picker.d.ts +1 -0
  48. package/out/lib/picker.js +158 -0
  49. package/out/lib/project-ops.d.ts +1 -1
  50. package/out/lib/project-ops.js +4 -4
  51. package/out/lib/prompt.d.ts +3 -0
  52. package/out/lib/prompt.js +21 -0
  53. package/out/lib/queue.js +1 -1
  54. package/out/lib/readiness.d.ts +3 -30
  55. package/out/lib/readiness.js +13 -82
  56. package/out/lib/servicecommand.d.ts +14 -0
  57. package/out/lib/servicecommand.js +74 -0
  58. package/out/lib/sessions.js +3 -3
  59. package/out/lib/startcache.d.ts +13 -0
  60. package/out/lib/startcache.js +78 -0
  61. package/out/lib/ui.d.ts +11 -0
  62. package/out/lib/ui.js +28 -0
  63. package/out/lib/voice.d.ts +36 -0
  64. package/out/lib/voice.js +137 -2
  65. package/out/lib/words.d.ts +20 -0
  66. package/out/lib/words.js +27 -0
  67. package/out/machine/codemap/build.d.ts +45 -0
  68. package/out/machine/codemap/build.js +91 -0
  69. package/out/machine/codemap/facts.d.ts +47 -0
  70. package/out/machine/codemap/facts.js +12 -0
  71. package/out/machine/codemap/files.d.ts +45 -0
  72. package/out/machine/codemap/files.js +207 -0
  73. package/out/machine/codemap/read-locales.d.ts +29 -0
  74. package/out/machine/codemap/read-locales.js +246 -0
  75. package/out/machine/codemap/read-python.d.ts +11 -0
  76. package/out/machine/codemap/read-python.js +116 -0
  77. package/out/machine/codemap/read-typescript.d.ts +16 -0
  78. package/out/machine/codemap/read-typescript.js +292 -0
  79. package/out/machine/executor/browser.d.ts +14 -0
  80. package/out/machine/executor/browser.js +270 -0
  81. package/out/machine/executor/diagnostics.d.ts +2 -0
  82. package/out/machine/executor/diagnostics.js +181 -0
  83. package/out/machine/executor/documents.d.ts +40 -0
  84. package/out/machine/executor/documents.js +170 -0
  85. package/out/machine/executor/files.d.ts +2 -0
  86. package/out/machine/executor/files.js +590 -0
  87. package/out/machine/executor/git.d.ts +48 -0
  88. package/out/machine/executor/git.js +145 -0
  89. package/out/machine/executor/hooks.d.ts +51 -0
  90. package/out/machine/executor/hooks.js +154 -0
  91. package/out/machine/executor/index.d.ts +45 -0
  92. package/out/machine/executor/index.js +367 -0
  93. package/out/machine/executor/notebook.d.ts +2 -0
  94. package/out/machine/executor/notebook.js +147 -0
  95. package/out/machine/executor/paths.d.ts +20 -0
  96. package/out/machine/executor/paths.js +154 -0
  97. package/out/machine/executor/sandbox.d.ts +40 -0
  98. package/out/machine/executor/sandbox.js +299 -0
  99. package/out/machine/executor/shell.d.ts +86 -0
  100. package/out/machine/executor/shell.js +582 -0
  101. package/out/machine/executor/toolservers.d.ts +20 -0
  102. package/out/machine/executor/toolservers.js +189 -0
  103. package/out/machine/executor/worktree.d.ts +9 -0
  104. package/out/machine/executor/worktree.js +119 -0
  105. package/out/machine/folder/project.d.ts +28 -0
  106. package/out/machine/folder/project.js +114 -0
  107. package/out/machine/runtime/home.d.ts +2 -0
  108. package/out/machine/runtime/home.js +48 -0
  109. package/out/machine/runtime/needs.d.ts +30 -0
  110. package/out/machine/runtime/needs.js +89 -0
  111. package/out/machine/runtime/self.d.ts +23 -0
  112. package/out/machine/runtime/self.js +124 -0
  113. package/out/native/dictate +0 -0
  114. package/out/providers/index.js +2 -1
  115. package/out/thin.js +13 -12
  116. package/package.json +5 -4
@@ -36,10 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.REQUIREMENTS = void 0;
40
- exports.present = present;
41
- exports.howToInstall = howToInstall;
42
- exports.check = check;
39
+ exports.present = exports.howToInstall = exports.check = exports.REQUIREMENTS = void 0;
43
40
  exports.systemName = systemName;
44
41
  exports.diagnose = diagnose;
45
42
  /**
@@ -72,82 +69,16 @@ exports.diagnose = diagnose;
72
69
  * is not friction worth trading a customer's trust for.
73
70
  */
74
71
  const node_child_process_1 = require("node:child_process");
72
+ const needs_1 = require("../machine/runtime/needs");
73
+ Object.defineProperty(exports, "REQUIREMENTS", { enumerable: true, get: function () { return needs_1.REQUIREMENTS; } });
74
+ Object.defineProperty(exports, "check", { enumerable: true, get: function () { return needs_1.check; } });
75
+ Object.defineProperty(exports, "howToInstall", { enumerable: true, get: function () { return needs_1.howToInstall; } });
76
+ Object.defineProperty(exports, "present", { enumerable: true, get: function () { return needs_1.present; } });
75
77
  const node_os_1 = __importDefault(require("node:os"));
76
78
  const install_1 = require("../commands/install");
77
79
  const connection_1 = require("./connection");
78
80
  const gateway_1 = require("./gateway");
79
81
  const node_fs_1 = __importDefault(require("node:fs"));
80
- exports.REQUIREMENTS = [
81
- {
82
- purpose: 'working with a repository',
83
- anyOf: ['git'],
84
- without: 'reading, branching and committing a project',
85
- essential: false,
86
- },
87
- {
88
- purpose: 'reading a PDF you already have',
89
- // Three ways to do the same job. The first keeps the layout, the second is
90
- // close, the third is usually there on any machine with Python at all.
91
- anyOf: ['pdftotext', 'mutool', 'python3'],
92
- without: 'reading a specification or a contract that arrived as a PDF',
93
- essential: false,
94
- },
95
- ];
96
- /** Is this program on the machine, by name, without running anything of theirs. */
97
- function present(program) {
98
- try {
99
- // Windows has no /usr/bin/env, so every program read as missing there.
100
- if (node_os_1.default.platform() === 'win32')
101
- (0, node_child_process_1.execFileSync)('where', [program], { stdio: 'ignore' });
102
- else
103
- (0, node_child_process_1.execFileSync)('/usr/bin/env', ['which', program], { stdio: 'ignore' });
104
- return true;
105
- }
106
- catch {
107
- return false;
108
- }
109
- }
110
- /**
111
- * The one command that installs what is missing, for the machine it is on.
112
- *
113
- * Named per platform rather than guessed: telling somebody on Ubuntu to run a
114
- * Homebrew command is worse than saying nothing, because they now have to work
115
- * out that the instruction was wrong before they can work out what is right.
116
- */
117
- function howToInstall(missing) {
118
- if (missing.length === 0)
119
- return '';
120
- const platform = node_os_1.default.platform();
121
- const parts = missing.map((m) => (m === 'pdftotext' ? 'poppler' : m));
122
- if (platform === 'darwin')
123
- return `brew install ${parts.join(' ')}`;
124
- if (platform === 'linux') {
125
- const apt = missing.map((m) => (m === 'pdftotext' ? 'poppler-utils' : m));
126
- return `sudo apt-get install -y ${apt.join(' ')}`;
127
- }
128
- if (platform === 'win32')
129
- return `winget install ${parts.join(' ')}`;
130
- return `install: ${parts.join(', ')}`;
131
- }
132
- /** What this machine can and cannot do, and how to close the gap. */
133
- function check() {
134
- const lines = [];
135
- const missing = [];
136
- for (const req of exports.REQUIREMENTS) {
137
- const found = req.anyOf.find(present);
138
- if (found) {
139
- lines.push(` ready ${req.purpose}`);
140
- }
141
- else {
142
- // The FIRST of the alternatives is the one named, because a list of three
143
- // equivalent programs is a decision nobody wants to make about a thing
144
- // they did not know they needed.
145
- missing.push(req.anyOf[0]);
146
- lines.push(` missing ${req.purpose}, so ${req.without} will not work`);
147
- }
148
- }
149
- return { ready: missing.length === 0, lines, fix: howToInstall(missing) };
150
- }
151
82
  /** The system this is, in the words its owner would use. */
152
83
  function systemName() {
153
84
  try {
@@ -205,7 +136,7 @@ async function diagnose() {
205
136
  const major = Number(node.split('.')[0]);
206
137
  // The install script and Python installs carry their own Node (manual 1.1),
207
138
  // so the line says so rather than asking for a Node nobody installed.
208
- const { installMethod, isStandalone } = await Promise.resolve().then(() => __importStar(require('./self')));
139
+ const { installMethod, isStandalone } = await Promise.resolve().then(() => __importStar(require('../machine/runtime/self')));
209
140
  if (isStandalone()) {
210
141
  const how = installMethod() === 'script' ? 'the install script' : 'Python (pip)';
211
142
  found.push({ ok: true, required: true, line: `installed with ${how}; Node ${node} is built in, nothing else needed` });
@@ -260,17 +191,17 @@ async function diagnose() {
260
191
  const git = version('git', ['--version']);
261
192
  found.push(git !== null
262
193
  ? { ok: true, required: false, line: `git ${git}` }
263
- : { ok: false, required: false, line: 'git not found: reading, branching and committing a project will not work',
264
- fix: howToInstall(['git']) });
194
+ : { ok: false, required: false, line: 'git not found: reading, branching and committing a workspace will not work',
195
+ fix: (0, needs_1.howToInstall)(['git']) });
265
196
  const rg = version('rg', ['--version']);
266
197
  found.push(rg !== null
267
198
  ? { ok: true, required: false, line: `ripgrep ${rg}` }
268
199
  : { ok: false, required: false, line: 'ripgrep not found: searches will be slower',
269
- fix: howToInstall(['ripgrep']) });
270
- const pdf = exports.REQUIREMENTS[1];
271
- found.push(pdf.anyOf.some(present)
200
+ fix: (0, needs_1.howToInstall)(['ripgrep']) });
201
+ const pdf = needs_1.REQUIREMENTS[1];
202
+ found.push(pdf.anyOf.some(needs_1.present)
272
203
  ? { ok: true, required: false, line: 'a PDF reader' }
273
204
  : { ok: false, required: false, line: `no PDF reader: ${pdf.without} will not work`,
274
- fix: howToInstall(['pdftotext']) });
205
+ fix: (0, needs_1.howToInstall)(['pdftotext']) });
275
206
  return found;
276
207
  }
@@ -0,0 +1,14 @@
1
+ export interface ServiceAnswer {
2
+ /** False when nobody answered; `lines` then holds nothing. */
3
+ reached: boolean;
4
+ /** Whether the service did what was asked. */
5
+ ok: boolean;
6
+ lines: string[];
7
+ /** The one change to this client's own state the service asked for. */
8
+ action: Record<string, string> | null;
9
+ message?: string;
10
+ }
11
+ export declare function runServiceCommand(words: string, facts: {
12
+ cwd: string;
13
+ current?: string;
14
+ }): Promise<ServiceAnswer>;
@@ -0,0 +1,74 @@
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.runServiceCommand = runServiceCommand;
37
+ /**
38
+ * Ask the service to run a command it owns (web manual 13.2 to 13.5): what was
39
+ * typed goes up, the service decides what it means and writes every line, and
40
+ * this answers its one question only this computer can, whether a folder exists.
41
+ *
42
+ * Shared by the session and by the commands typed straight at the shell, so a
43
+ * service command behaves the same wherever it is typed.
44
+ */
45
+ const fs = __importStar(require("node:fs"));
46
+ const os = __importStar(require("node:os"));
47
+ const gateway_1 = require("./gateway");
48
+ async function runServiceCommand(words, facts) {
49
+ const exists = {};
50
+ for (let round = 0; round < 4; round += 1) {
51
+ const reply = await (0, gateway_1.ask)({
52
+ intent: 'command', prompt: '',
53
+ payload: { words, cwd: facts.cwd, home: os.homedir(), current: facts.current ?? '', exists },
54
+ });
55
+ if (!reply.lines?.length && !reply.data) {
56
+ return { reached: false, ok: false, lines: [], action: null, message: reply.message ?? undefined };
57
+ }
58
+ const data = (reply.data ?? {});
59
+ const wanted = data.wants?.exists ?? [];
60
+ if (wanted.length) {
61
+ for (const p of wanted) {
62
+ try {
63
+ exists[p] = fs.statSync(p).isDirectory();
64
+ }
65
+ catch {
66
+ exists[p] = false;
67
+ }
68
+ }
69
+ continue;
70
+ }
71
+ return { reached: true, ok: reply.granted !== false, lines: reply.lines ?? [], action: data.action ?? null };
72
+ }
73
+ return { reached: false, ok: false, lines: [], action: null };
74
+ }
@@ -51,7 +51,7 @@ const gateway_1 = require("./gateway");
51
51
  const out = (s = '') => process.stdout.write(s + '\n');
52
52
  /** Ask the service one sessions question, with where this machine is. */
53
53
  async function sessions(op, extra = {}, folder) {
54
- const { resolveProject, asWire } = await Promise.resolve().then(() => __importStar(require('./project')));
54
+ const { resolveProject, asWire } = await Promise.resolve().then(() => __importStar(require('../machine/folder/project')));
55
55
  const { currentWorkspace } = await Promise.resolve().then(() => __importStar(require('../commands/settings')));
56
56
  const facts = resolveProject(folder ?? currentWorkspace().path, []);
57
57
  return (0, gateway_1.ask)({
@@ -69,9 +69,9 @@ async function sessions(op, extra = {}, folder) {
69
69
  /** Why a sessions question got no answer, in the service's words when it has them. */
70
70
  function failure(reply) {
71
71
  if (reply.served === false) {
72
- return 'Your workspace is running a build without sessions yet.';
72
+ return 'Your organization is running a build without sessions yet.';
73
73
  }
74
- return reply.message || 'Your workspace could not be reached.';
74
+ return reply.message || 'Your organization could not be reached.';
75
75
  }
76
76
  /** The exit number for a sessions question that got no answer (Appendix B). */
77
77
  function exitFor(reply) {
@@ -0,0 +1,13 @@
1
+ import type { SessionOut } from './gateway';
2
+ import type { CommandList } from './commandlist';
3
+ export interface StartCache {
4
+ /** Which workspace these answers came from; another workspace's are never used. */
5
+ url?: string;
6
+ me?: SessionOut | null;
7
+ hasKey?: boolean;
8
+ /** The project line, by project folder. */
9
+ lines?: Record<string, string>;
10
+ list?: CommandList | null;
11
+ }
12
+ export declare function readStart(url: string): StartCache;
13
+ export declare function saveStart(url: string, change: Partial<StartCache>): void;
@@ -0,0 +1,78 @@
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.readStart = readStart;
37
+ exports.saveStart = saveStart;
38
+ /**
39
+ * What the service said the last time a session opened, kept so the next one
40
+ * can open at once.
41
+ *
42
+ * Opening a session asks the service four things (who you are, whether a key
43
+ * is stored, the project line, the command list). Asked from a laptop whose
44
+ * database is a region away, each takes seconds, and the prompt waited for all
45
+ * of them. Now the last answers are drawn immediately and the same questions
46
+ * are asked again in the background; the fresh answers are kept for next time,
47
+ * and a change that matters (no key any more) is said as soon as it is known.
48
+ *
49
+ * Only the service's own answers are kept, exactly as sent. Nothing here is
50
+ * decided on this machine, and a missing or unreadable file simply means the
51
+ * session waits for the service, as it always did.
52
+ */
53
+ const fs = __importStar(require("node:fs"));
54
+ const path = __importStar(require("node:path"));
55
+ const config_1 = require("./config");
56
+ function file() {
57
+ return path.join((0, config_1.ensureHome)(), 'start-cache.json');
58
+ }
59
+ function readStart(url) {
60
+ try {
61
+ const got = JSON.parse(fs.readFileSync(file(), 'utf8'));
62
+ return got.url === url ? got : { url };
63
+ }
64
+ catch {
65
+ return { url };
66
+ }
67
+ }
68
+ function saveStart(url, change) {
69
+ try {
70
+ const now = readStart(url);
71
+ const next = {
72
+ ...now, ...change, url,
73
+ lines: { ...(now.lines ?? {}), ...(change.lines ?? {}) },
74
+ };
75
+ fs.writeFileSync(file(), JSON.stringify(next), { mode: 0o600 });
76
+ }
77
+ catch { /* a cache that cannot be written only means the next start waits */ }
78
+ }
package/out/lib/ui.d.ts CHANGED
@@ -35,6 +35,17 @@ export declare const c: {
35
35
  * and the brand is the one thing a customer notices before the engine.
36
36
  */
37
37
  export declare function spectrumAt(index: number, glyph: string): string;
38
+ /**
39
+ * A small picture, one pixel to a character cell, each pixel a colored space.
40
+ *
41
+ * A cell's background is painted edge to edge by every terminal, so the
42
+ * picture has no seams. Half block glyphs pack two pixels into a cell but
43
+ * leave thin gaps and offsets in some fonts, which made the mark look broken
44
+ * in the macOS Terminal (owner, 28 September 2026). Each row of `pixels` is
45
+ * six hex digits per pixel, `......` for a clear one. Returns nothing without
46
+ * color, because the picture is only color.
47
+ */
48
+ export declare function cellArt(pixels: string[]): string[];
38
49
  export declare function prismRule(w?: number): string;
39
50
  export declare function banner(subtitle?: string): string;
40
51
  export declare function money(v: number | null): string;
package/out/lib/ui.js CHANGED
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.LiveIndicator = exports.c = void 0;
20
20
  exports.width = width;
21
21
  exports.spectrumAt = spectrumAt;
22
+ exports.cellArt = cellArt;
22
23
  exports.prismRule = prismRule;
23
24
  exports.banner = banner;
24
25
  exports.money = money;
@@ -86,6 +87,33 @@ function spectrumAt(index, glyph) {
86
87
  const [r, g, b] = SPECTRUM[Math.abs(index) % SPECTRUM.length];
87
88
  return `\x1b[38;2;${r};${g};${b}m${glyph}\x1b[0m`;
88
89
  }
90
+ /**
91
+ * A small picture, one pixel to a character cell, each pixel a colored space.
92
+ *
93
+ * A cell's background is painted edge to edge by every terminal, so the
94
+ * picture has no seams. Half block glyphs pack two pixels into a cell but
95
+ * leave thin gaps and offsets in some fonts, which made the mark look broken
96
+ * in the macOS Terminal (owner, 28 September 2026). Each row of `pixels` is
97
+ * six hex digits per pixel, `......` for a clear one. Returns nothing without
98
+ * color, because the picture is only color.
99
+ */
100
+ function cellArt(pixels) {
101
+ if (!useColorNow())
102
+ return [];
103
+ return pixels.map((row) => {
104
+ let line = '';
105
+ for (let x = 0; x < row.length / 6; x++) {
106
+ const hex = row.slice(x * 6, x * 6 + 6);
107
+ if (hex === '......') {
108
+ line += ' ';
109
+ continue;
110
+ }
111
+ const rgb = [0, 2, 4].map((k) => parseInt(hex.slice(k, k + 2), 16)).join(';');
112
+ line += `\x1b[48;2;${rgb}m \x1b[0m`;
113
+ }
114
+ return line;
115
+ });
116
+ }
89
117
  function prismRule(w = width()) {
90
118
  if (!useColorNow())
91
119
  return '-'.repeat(w);
@@ -34,6 +34,42 @@ export interface Recording {
34
34
  * headed off with `missing` so the person gets a sentence rather than an error.
35
35
  */
36
36
  export declare function start(): Recording;
37
+ /** Live dictation in progress, on the Mac's own recogniser. */
38
+ export interface LiveDictation {
39
+ /** The loudness now, 0 to 1. */
40
+ level: () => number;
41
+ /** The words so far. */
42
+ words: () => string;
43
+ /** Stop listening and get the final words, or null when it could not listen. */
44
+ stop: () => Promise<string | null>;
45
+ /** Why it could not listen, when it could not. */
46
+ problem: () => string;
47
+ }
48
+ /**
49
+ * Words as they are spoken, the way the web app shows them.
50
+ *
51
+ * Nothing is recorded to a file and nothing leaves the machine: the Mac's own
52
+ * recogniser listens and reports what it hears, several times a second.
53
+ * Returns null where there is no helper, and the caller records and has the
54
+ * server write it down instead, as before.
55
+ */
56
+ export declare function listenLive(language?: string): LiveDictation | null;
57
+ /**
58
+ * How loud the last tenth of a second was, from 0 to 1.
59
+ *
60
+ * Read off the file as it is written, so somebody speaking sees the meter move
61
+ * and knows they are being heard, before a single word has come back. Sixteen
62
+ * bit mono at 16 kHz is 3,200 bytes a tenth of a second.
63
+ */
64
+ export declare function level(file: string): number;
65
+ /**
66
+ * What has been said so far, as a WAV file a transcriber can read.
67
+ *
68
+ * The recorder writes the header's lengths only when it stops, so a copy taken
69
+ * mid recording claims no audio at all. The copy gets the lengths of what is
70
+ * actually there. Returns null until there is at least half a second to send.
71
+ */
72
+ export declare function soFar(file: string): string | null;
37
73
  /** Delete the recording and the folder holding it. Always, and immediately. */
38
74
  export declare function discard(file: string): void;
39
75
  /**
package/out/lib/voice.js CHANGED
@@ -36,6 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.recorder = recorder;
37
37
  exports.missing = missing;
38
38
  exports.start = start;
39
+ exports.listenLive = listenLive;
40
+ exports.level = level;
41
+ exports.soFar = soFar;
39
42
  exports.discard = discard;
40
43
  exports.transcribe = transcribe;
41
44
  /**
@@ -102,7 +105,9 @@ const RECORDERS = [
102
105
  ...(process.platform === 'darwin'
103
106
  ? ['-f', 'avfoundation', '-i', ':default']
104
107
  : ['-f', 'alsa', '-i', 'default']),
105
- '-ac', '1', '-ar', RATE, '-y', out,
108
+ // Written as it is heard, not held until the end, so the meter and the
109
+ // words so far have something to read while the person is speaking.
110
+ '-ac', '1', '-ar', RATE, '-flush_packets', '1', '-y', out,
106
111
  ],
107
112
  },
108
113
  // Linux's own, present on most desktop installs.
@@ -206,6 +211,136 @@ function start() {
206
211
  };
207
212
  return { file, by: tool.name, stop };
208
213
  }
214
+ /** The helper built beside this file on a Mac, when there is one. */
215
+ function helper() {
216
+ if (process.platform !== 'darwin')
217
+ return null;
218
+ const p = path.join(__dirname, '..', 'native', 'dictate');
219
+ return fs.existsSync(p) ? p : null;
220
+ }
221
+ /**
222
+ * Words as they are spoken, the way the web app shows them.
223
+ *
224
+ * Nothing is recorded to a file and nothing leaves the machine: the Mac's own
225
+ * recogniser listens and reports what it hears, several times a second.
226
+ * Returns null where there is no helper, and the caller records and has the
227
+ * server write it down instead, as before.
228
+ */
229
+ function listenLive(language = 'en-US') {
230
+ const bin = helper();
231
+ if (!bin)
232
+ return null;
233
+ const child = (0, node_child_process_1.spawn)(bin, [language], { stdio: ['pipe', 'pipe', 'ignore'] });
234
+ let loud = 0;
235
+ let heard = '';
236
+ let final = null;
237
+ let why = '';
238
+ let rest = '';
239
+ const ended = new Promise((resolve) => child.once('close', () => resolve()));
240
+ child.stdout?.on('data', (chunk) => {
241
+ rest += chunk.toString('utf8');
242
+ const lines = rest.split('\n');
243
+ rest = lines.pop() ?? '';
244
+ for (const line of lines) {
245
+ const kind = line.slice(0, 1);
246
+ const text = line.slice(2);
247
+ if (kind === 'L')
248
+ loud = Number(text) || 0;
249
+ else if (kind === 'P')
250
+ heard = text;
251
+ else if (kind === 'F')
252
+ final = text;
253
+ else if (kind === 'E')
254
+ why = text;
255
+ }
256
+ });
257
+ // The helper never outlives the session.
258
+ const abandon = () => { try {
259
+ child.kill('SIGKILL');
260
+ }
261
+ catch { /* gone */ } };
262
+ process.once('exit', abandon);
263
+ return {
264
+ level: () => loud,
265
+ words: () => heard,
266
+ problem: () => why,
267
+ stop: async () => {
268
+ // Closing its input is "Enter was pressed": it says its final words and ends.
269
+ try {
270
+ child.stdin?.end();
271
+ }
272
+ catch { /* gone */ }
273
+ const timeout = new Promise((resolve) => setTimeout(resolve, 4000).unref());
274
+ await Promise.race([ended, timeout]);
275
+ abandon();
276
+ process.off('exit', abandon);
277
+ if (why && !final && !heard)
278
+ return null;
279
+ return (final ?? heard).trim();
280
+ },
281
+ };
282
+ }
283
+ /** Where the samples start in a WAV file this recorder wrote, or -1. */
284
+ function dataStart(buf) {
285
+ const at = buf.indexOf('data', 12, 'ascii');
286
+ return at < 0 ? -1 : at + 8;
287
+ }
288
+ /**
289
+ * How loud the last tenth of a second was, from 0 to 1.
290
+ *
291
+ * Read off the file as it is written, so somebody speaking sees the meter move
292
+ * and knows they are being heard, before a single word has come back. Sixteen
293
+ * bit mono at 16 kHz is 3,200 bytes a tenth of a second.
294
+ */
295
+ function level(file) {
296
+ try {
297
+ const size = fs.statSync(file).size;
298
+ const n = 3200;
299
+ if (size < 44 + n)
300
+ return 0;
301
+ const fd = fs.openSync(file, 'r');
302
+ const buf = Buffer.alloc(n);
303
+ fs.readSync(fd, buf, 0, n, size - n - (size - 44) % 2);
304
+ fs.closeSync(fd);
305
+ let sum = 0;
306
+ for (let i = 0; i + 1 < n; i += 2) {
307
+ const s = buf.readInt16LE(i) / 32768;
308
+ sum += s * s;
309
+ }
310
+ const rms = Math.sqrt(sum / (n / 2));
311
+ // Speech sits roughly between -50 dB (quiet room) and -10 dB (close and loud).
312
+ const db = 20 * Math.log10(rms || 1e-9);
313
+ return Math.max(0, Math.min(1, (db + 50) / 40));
314
+ }
315
+ catch {
316
+ return 0;
317
+ }
318
+ }
319
+ /**
320
+ * What has been said so far, as a WAV file a transcriber can read.
321
+ *
322
+ * The recorder writes the header's lengths only when it stops, so a copy taken
323
+ * mid recording claims no audio at all. The copy gets the lengths of what is
324
+ * actually there. Returns null until there is at least half a second to send.
325
+ */
326
+ function soFar(file) {
327
+ try {
328
+ const buf = fs.readFileSync(file);
329
+ const start = dataStart(buf);
330
+ if (start < 0 || buf.length - start < 16000)
331
+ return null;
332
+ const usable = buf.length - ((buf.length - start) % 2);
333
+ const copy = Buffer.from(buf.subarray(0, usable));
334
+ copy.writeUInt32LE(usable - 8, 4);
335
+ copy.writeUInt32LE(usable - start, start - 4);
336
+ const out = path.join(path.dirname(file), 'so-far.wav');
337
+ fs.writeFileSync(out, copy);
338
+ return out;
339
+ }
340
+ catch {
341
+ return null;
342
+ }
343
+ }
209
344
  /** Delete the recording and the folder holding it. Always, and immediately. */
210
345
  function discard(file) {
211
346
  try {
@@ -229,7 +364,7 @@ async function transcribe(baseUrl, credential, file) {
229
364
  const granted = await (0, gateway_1.ask)({ intent: 'transcribe', prompt: '' });
230
365
  const pass = granted.ok && granted.served !== false ? granted.upload : null;
231
366
  if (granted.ok && granted.served !== false && !pass) {
232
- throw new Error(granted.message || 'your workspace would not allow that recording');
367
+ throw new Error(granted.message || 'your organization would not allow that recording');
233
368
  }
234
369
  const where = pass?.upload_path || '/api/v1/agent/transcribe';
235
370
  const res = await fetch(`${baseUrl.replace(/\/+$/, '')}${where}${pass?.ticket ? `?ticket=${encodeURIComponent(pass.ticket)}` : ''}`, { method: 'POST', headers: { Authorization: `Bearer ${credential}` }, body: form });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The few words the published command line tool says on its own, in one place.
3
+ *
4
+ * Everything about the work arrives from the service already worded (rule 17).
5
+ * What is left here is what the tool must say when the service, a file or the
6
+ * operating system gives it nothing to say: a refused address, a file it cannot
7
+ * read, a call that failed without a reason. Same shape as the editor's
8
+ * `i18n.ts` and the desktop app's `labels.js`.
9
+ */
10
+ declare const EN: {
11
+ readonly connectorNotStarted: "the connector could not be started";
12
+ readonly addressRefused: "the organization address was refused";
13
+ readonly fileNotFound: "not found: {{file}}";
14
+ readonly fileNoPermission: "cannot read {{file}}: permission denied";
15
+ readonly providerCallFailed: "provider call failed";
16
+ readonly somethingWrong: "something went wrong";
17
+ };
18
+ export type WordKey = keyof typeof EN;
19
+ export declare function word(key: WordKey, vars?: Record<string, string | number>): string;
20
+ export {};
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.word = word;
4
+ /**
5
+ * The few words the published command line tool says on its own, in one place.
6
+ *
7
+ * Everything about the work arrives from the service already worded (rule 17).
8
+ * What is left here is what the tool must say when the service, a file or the
9
+ * operating system gives it nothing to say: a refused address, a file it cannot
10
+ * read, a call that failed without a reason. Same shape as the editor's
11
+ * `i18n.ts` and the desktop app's `labels.js`.
12
+ */
13
+ const EN = {
14
+ connectorNotStarted: 'the connector could not be started',
15
+ addressRefused: 'the organization address was refused',
16
+ fileNotFound: 'not found: {{file}}',
17
+ fileNoPermission: 'cannot read {{file}}: permission denied',
18
+ providerCallFailed: 'provider call failed',
19
+ somethingWrong: 'something went wrong',
20
+ };
21
+ function word(key, vars) {
22
+ let s = EN[key];
23
+ if (vars)
24
+ for (const [k, v] of Object.entries(vars))
25
+ s = s.split(`{{${k}}}`).join(String(v));
26
+ return s;
27
+ }