@prismer/runtime 1.9.6 → 1.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,118 +30,711 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- AdapterRegistry: () => AdapterRegistry,
34
- AssetCache: () => AssetCache,
35
- BUILTIN_ROLE_TEMPLATES: () => BUILTIN_ROLE_TEMPLATES,
36
- CloudClient: () => CloudClient,
37
- CloudError: () => CloudError,
38
- ConfigSchema: () => ConfigSchema,
39
- LocalServer: () => LocalServer,
40
- ParseClaimController: () => ParseClaimController,
41
- Runner: () => Runner,
42
- ServicePool: () => ServicePool,
43
- SyncQueue: () => SyncQueue,
44
- SyncWorker: () => SyncWorker,
45
- TARGET_SCHEMA_VERSION: () => TARGET_SCHEMA_VERSION,
46
- UriResolver: () => UriResolver,
47
- WS_CLOSE: () => WS_CLOSE,
48
- WorkspaceMirror: () => WorkspaceMirror,
49
- WsClient: () => WsClient,
50
- buildProgram: () => buildProgram,
51
- claudeCodeAdapter: () => claudeCodeAdapter,
52
- codexAdapter: () => codexAdapter,
53
- composePrompt: () => composePrompt,
54
- configExists: () => configExists,
55
- currentSchemaVersion: () => currentSchemaVersion,
56
- deriveWsUrl: () => deriveWsUrl,
57
- envelope: () => envelope,
58
- getRoleTemplate: () => getRoleTemplate,
59
- handleDispatch: () => handleDispatch,
60
- hermesAdapter: () => hermesAdapter,
61
- isDaemonId: () => isDaemonId,
62
- listRoleTemplates: () => listRoleTemplates,
63
- loadConfig: () => loadConfig,
64
- newDaemonId: () => newDaemonId,
65
- nextBackoffMs: () => nextBackoffMs,
66
- openLocalDb: () => openLocalDb,
67
- pair: () => pair,
68
- parseCodexOutput: () => parseCodexOutput,
69
- parseUris: () => parseUris,
70
- resolvePaths: () => resolvePaths,
71
- runCli: () => runCli,
72
- runMigrations: () => runMigrations,
73
- saveConfig: () => saveConfig
74
- });
75
- module.exports = __toCommonJS(src_exports);
76
-
77
- // src/adapters/registry.ts
78
- var AdapterRegistry = class {
79
- adapters = /* @__PURE__ */ new Map();
80
- register(adapter) {
81
- if (!adapter.name || typeof adapter.name !== "string") {
82
- throw new Error("AdapterRegistry.register: adapter.name required");
83
- }
84
- const hasDispatch = typeof adapter.dispatch === "function";
85
- const hasEnsureService = typeof adapter.ensureService === "function";
86
- if (!hasDispatch && !hasEnsureService) {
87
- throw new Error(
88
- `AdapterRegistry.register: adapter "${adapter.name}" must implement dispatch() (interactive) or ensureService() (long-running)`
89
- );
90
- }
91
- this.adapters.set(adapter.name, adapter);
92
- }
93
- unregister(name) {
94
- return this.adapters.delete(name);
95
- }
96
- has(name) {
97
- return this.adapters.has(name);
98
- }
99
- get(name) {
100
- return this.adapters.get(name);
33
+ // src/cli/ui.ts
34
+ function thisDirname() {
35
+ try {
36
+ return path6.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
37
+ } catch {
38
+ return process.cwd();
101
39
  }
102
- list() {
103
- return Array.from(this.adapters.values());
40
+ }
41
+ function findIconPath(size = "big") {
42
+ const name = size === "big" ? "icon" : "smallicon";
43
+ const here = thisDirname();
44
+ const candidates = [
45
+ // npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
46
+ path6.resolve(here, "../assets", name),
47
+ // alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
48
+ path6.resolve(here, "../../assets", name),
49
+ // source/typecheck: src/cli/ui.ts → ../../assets
50
+ path6.resolve(here, "../../assets", name),
51
+ // dev mode: cwd happens to be runtime root
52
+ path6.resolve(process.cwd(), "assets", name),
53
+ path6.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
54
+ ];
55
+ for (const candidate of candidates) {
56
+ try {
57
+ if (fs4.existsSync(candidate)) return candidate;
58
+ } catch {
59
+ }
104
60
  }
105
- size() {
106
- return this.adapters.size;
61
+ return null;
62
+ }
63
+ function getUI() {
64
+ if (!_ui) _ui = new UI();
65
+ return _ui;
66
+ }
67
+ function setUI(ui) {
68
+ _ui = ui;
69
+ }
70
+ function applyCommonFlags(argv) {
71
+ let mode = "pretty";
72
+ const isTTY = process.stdout.isTTY === true;
73
+ const noColorEnv = Boolean(process.env["NO_COLOR"]);
74
+ let color2 = isTTY && !noColorEnv;
75
+ const rest = [];
76
+ for (const arg of argv) {
77
+ switch (arg) {
78
+ case "--no-color":
79
+ color2 = false;
80
+ break;
81
+ case "--color":
82
+ color2 = true;
83
+ break;
84
+ case "--json":
85
+ case "--pretty-json":
86
+ mode = "json";
87
+ if (arg === "--json") rest.push(arg);
88
+ break;
89
+ case "--quiet":
90
+ mode = "quiet";
91
+ break;
92
+ default:
93
+ rest.push(arg);
94
+ }
107
95
  }
108
- /**
109
- * Find adapters that satisfy a capability tag.
110
- *
111
- * Match rules (deterministic, sorted by name):
112
- * - exact: adapter declared the tag verbatim
113
- * - wildcard: adapter declared `code.*` → matches `code.write`
114
- */
115
- findByCapability(capability) {
116
- const matches2 = [];
117
- for (const adapter of this.adapters.values()) {
118
- if (adapter.capabilities.includes(capability)) {
119
- matches2.push(adapter);
120
- continue;
96
+ return { mode, color: color2, restArgv: rest };
97
+ }
98
+ var fs4, path6, import_node_url, import_meta2, BRAILLE_FRAMES, COMPACT_BANNER, UI, _ui;
99
+ var init_ui = __esm({
100
+ "src/cli/ui.ts"() {
101
+ "use strict";
102
+ fs4 = __toESM(require("fs"), 1);
103
+ path6 = __toESM(require("path"), 1);
104
+ import_node_url = require("url");
105
+ import_meta2 = {};
106
+ BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
107
+ COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
108
+ UI = class {
109
+ mode;
110
+ colorEnabled;
111
+ stream;
112
+ errStream;
113
+ constructor(opts) {
114
+ this.mode = opts?.mode ?? "pretty";
115
+ this.stream = opts?.stream ?? process.stdout;
116
+ this.errStream = opts?.errStream ?? process.stderr;
117
+ if (opts?.color !== void 0) {
118
+ this.colorEnabled = opts.color;
119
+ } else {
120
+ const isTTY = this.stream.isTTY === true;
121
+ const noColor = Boolean(process.env["NO_COLOR"]);
122
+ this.colorEnabled = isTTY && !noColor;
123
+ }
121
124
  }
122
- if (adapter.capabilities.some(
123
- (tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
124
- )) {
125
- matches2.push(adapter);
125
+ // ---- Internal color helpers ----
126
+ ansi(open, close, text) {
127
+ if (!this.colorEnabled) return text;
128
+ return `\x1B[${open}m${text}\x1B[${close}m`;
126
129
  }
127
- }
128
- matches2.sort((a, b) => a.name.localeCompare(b.name));
129
- return matches2;
130
+ green(t) {
131
+ return this.ansi(32, 39, t);
132
+ }
133
+ red(t) {
134
+ return this.ansi(31, 39, t);
135
+ }
136
+ yellow(t) {
137
+ return this.ansi(33, 39, t);
138
+ }
139
+ cyan(t) {
140
+ return this.ansi(36, 39, t);
141
+ }
142
+ dim(t) {
143
+ return this.ansi(2, 22, t);
144
+ }
145
+ bold(t) {
146
+ return this.ansi(1, 22, t);
147
+ }
148
+ gray(t) {
149
+ return this.ansi(90, 39, t);
150
+ }
151
+ brandMark() {
152
+ return this.cyan("\u25C7");
153
+ }
154
+ colorBrandLine(line) {
155
+ let out = "";
156
+ for (const ch of line) {
157
+ if (ch === "\u2592") {
158
+ out += this.cyan(ch);
159
+ } else if (ch === "\u2593") {
160
+ out += this.dim(ch);
161
+ } else {
162
+ out += ch;
163
+ }
164
+ }
165
+ return out;
166
+ }
167
+ // ---- Core write helpers ----
168
+ write(text) {
169
+ this.stream.write(text);
170
+ }
171
+ writeErr(text) {
172
+ this.errStream.write(text);
173
+ }
174
+ // ---- Level 1: Header ----
175
+ header(text) {
176
+ if (this.mode === "json") return;
177
+ const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
178
+ this.write(prefix + this.bold(text) + "\n");
179
+ }
180
+ smallHeader(subtitle) {
181
+ if (this.mode === "json" || this.mode === "quiet") return;
182
+ const iconPath = findIconPath("small");
183
+ if (iconPath !== null) {
184
+ try {
185
+ const raw = fs4.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
186
+ for (const line of raw.split("\n")) {
187
+ this.write(this.cyan(line) + "\n");
188
+ }
189
+ } catch {
190
+ this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
191
+ }
192
+ } else {
193
+ this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
194
+ }
195
+ if (subtitle !== void 0 && subtitle.length > 0) {
196
+ this.write(this.dim(" " + subtitle) + "\n");
197
+ }
198
+ this.blank();
199
+ }
200
+ banner(subtitle, opts) {
201
+ if (this.mode === "json" || this.mode === "quiet") return;
202
+ const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
203
+ const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
204
+ const iconPath = findIconPath("big");
205
+ const shouldUseFull = opts?.full === true || width >= 120;
206
+ if (shouldUseFull && iconPath !== null) {
207
+ try {
208
+ const raw = fs4.readFileSync(iconPath, "utf-8");
209
+ const lines = raw.split("\n");
210
+ for (const line of lines) {
211
+ const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
212
+ const stripped = brandedLine.trimEnd();
213
+ if (stripped.length === 0) {
214
+ this.write("\n");
215
+ continue;
216
+ }
217
+ const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
218
+ this.write(this.colorBrandLine(clipped) + "\n");
219
+ }
220
+ } catch {
221
+ this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
222
+ this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
223
+ }
224
+ } else {
225
+ this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
226
+ this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
227
+ }
228
+ if (subtitle !== void 0 && subtitle.length > 0) {
229
+ this.write(this.dim(" " + subtitle) + "\n");
230
+ }
231
+ this.blank();
232
+ }
233
+ // ---- Level 2: Primary data ----
234
+ blank() {
235
+ if (this.mode === "json") return;
236
+ this.write("\n");
237
+ }
238
+ line(text) {
239
+ if (this.mode === "json") return;
240
+ this.write(text + "\n");
241
+ }
242
+ info(text) {
243
+ this.line(text);
244
+ }
245
+ // ---- Level 3: Secondary ----
246
+ secondary(text, indent = 2) {
247
+ if (this.mode === "json") return;
248
+ this.write(" ".repeat(indent) + this.dim(text) + "\n");
249
+ }
250
+ // ---- Level 4: Action tips ----
251
+ tip(text) {
252
+ if (this.mode === "json") return;
253
+ this.write(this.cyan("Tip:") + " " + text + "\n");
254
+ }
255
+ next(text) {
256
+ if (this.mode === "json") return;
257
+ this.write(this.cyan("Next:") + " " + text + "\n");
258
+ }
259
+ // ---- Level 5: Status indicators ----
260
+ ok(text, detail) {
261
+ if (this.mode === "json") return;
262
+ const suffix = detail ? " " + this.dim(detail) : "";
263
+ this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
264
+ }
265
+ success(text, detail) {
266
+ this.ok(text, detail);
267
+ }
268
+ fail(text, detail) {
269
+ if (this.mode === "json") return;
270
+ const suffix = detail ? " " + this.dim(detail) : "";
271
+ this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
272
+ }
273
+ online(text) {
274
+ if (this.mode === "json") return;
275
+ this.write(" " + this.green("\u25CF") + " " + text + "\n");
276
+ }
277
+ offline(text) {
278
+ if (this.mode === "json") return;
279
+ this.write(" " + this.gray("\u25CB") + " " + text + "\n");
280
+ }
281
+ notInstalled(text) {
282
+ if (this.mode === "json") return;
283
+ this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
284
+ }
285
+ pending(text) {
286
+ if (this.mode === "json") return;
287
+ this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
288
+ }
289
+ warn(text, detail) {
290
+ if (this.mode === "json") return;
291
+ const suffix = detail ? " " + this.dim(detail) : "";
292
+ this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
293
+ }
294
+ // ---- Level 6: Error block ----
295
+ error(what, cause, fix) {
296
+ if (this.mode === "json") return;
297
+ this.writeErr(this.red("\u2717") + " " + what + "\n");
298
+ if (cause !== void 0) {
299
+ this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
300
+ }
301
+ if (fix !== void 0) {
302
+ this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
303
+ }
304
+ }
305
+ table(rowsOrOpts, maybeOpts) {
306
+ if (this.mode === "json") return;
307
+ const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
308
+ const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
309
+ if (!opts) throw new Error("table() requires columns");
310
+ const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
311
+ const cols = opts.columns;
312
+ const widths = cols.map((col) => col.length);
313
+ for (const row of rows) {
314
+ cols.forEach((col, i) => {
315
+ const val = row[col] ?? "";
316
+ const w = widths[i] ?? 0;
317
+ if (val.length > w) widths[i] = val.length;
318
+ });
319
+ }
320
+ const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
321
+ if (totalWidth > maxWidth) {
322
+ for (let i = 0; i < rows.length; i++) {
323
+ const row = rows[i];
324
+ if (!row) continue;
325
+ for (const col of cols) {
326
+ const val = row[col] ?? "";
327
+ this.write(" " + this.bold(col + ":") + " " + val + "\n");
328
+ }
329
+ if (i < rows.length - 1) this.write("\n");
330
+ }
331
+ return;
332
+ }
333
+ const header2 = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
334
+ this.write(" " + this.dim(header2) + "\n");
335
+ for (const row of rows) {
336
+ const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
337
+ this.write(" " + line + "\n");
338
+ }
339
+ }
340
+ // ---- Spinner ----
341
+ spinner(text) {
342
+ if (this.mode === "quiet" || this.mode === "json") {
343
+ return {
344
+ update() {
345
+ },
346
+ stop() {
347
+ }
348
+ };
349
+ }
350
+ const isTTY = this.stream.isTTY === true;
351
+ if (!isTTY || !this.colorEnabled) {
352
+ this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
353
+ return {
354
+ update: (t) => {
355
+ this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
356
+ },
357
+ stop: (final) => {
358
+ if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
359
+ }
360
+ };
361
+ }
362
+ let current = text;
363
+ let frameIdx = 0;
364
+ let stopped = false;
365
+ const write = this.write.bind(this);
366
+ const colorFn = this.yellow.bind(this);
367
+ const greenFn = this.green.bind(this);
368
+ function renderFrame() {
369
+ const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
370
+ const line = " " + colorFn(frame) + " " + current;
371
+ write("\r" + line);
372
+ frameIdx++;
373
+ }
374
+ renderFrame();
375
+ const timer = setInterval(renderFrame, 80);
376
+ return {
377
+ update(t) {
378
+ if (stopped) return;
379
+ current = t;
380
+ },
381
+ stop(final) {
382
+ if (stopped) return;
383
+ stopped = true;
384
+ clearInterval(timer);
385
+ write("\r\x1B[2K");
386
+ if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
387
+ }
388
+ };
389
+ }
390
+ // ---- Progress bar ----
391
+ progress(text, total) {
392
+ if (this.mode === "quiet" || this.mode === "json") {
393
+ return {
394
+ update() {
395
+ },
396
+ stop() {
397
+ }
398
+ };
399
+ }
400
+ const isTTY = this.stream.isTTY === true;
401
+ const start = Date.now();
402
+ const write = this.write.bind(this);
403
+ const colorFn = this.cyan.bind(this);
404
+ const dimFn = this.dim.bind(this);
405
+ const greenFn = this.green.bind(this);
406
+ let last = 0;
407
+ let lastDetail = "";
408
+ let stopped = false;
409
+ const render = () => {
410
+ if (stopped) return;
411
+ const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
412
+ const pct = Math.floor(frac * 100);
413
+ const width = 20;
414
+ const filled = Math.floor(frac * width);
415
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
416
+ const elapsed = (Date.now() - start) / 1e3;
417
+ const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
418
+ const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
419
+ const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
420
+ const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
421
+ if (isTTY && this.colorEnabled) {
422
+ write("\r\x1B[2K" + line);
423
+ } else {
424
+ write(line + "\n");
425
+ }
426
+ };
427
+ render();
428
+ return {
429
+ update: (current, detail) => {
430
+ if (stopped) return;
431
+ last = current;
432
+ if (detail !== void 0) lastDetail = detail;
433
+ render();
434
+ },
435
+ stop: (final) => {
436
+ if (stopped) return;
437
+ stopped = true;
438
+ if (isTTY && this.colorEnabled) write("\r\x1B[2K");
439
+ if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
440
+ }
441
+ };
442
+ }
443
+ // ---- JSON output ----
444
+ json(payload, opts) {
445
+ const indent = opts?.pretty ? 2 : void 0;
446
+ this.write(JSON.stringify(payload, null, indent) + "\n");
447
+ }
448
+ result(pretty, jsonPayload) {
449
+ if (this.mode === "pretty") {
450
+ pretty();
451
+ } else {
452
+ this.json(jsonPayload);
453
+ }
454
+ }
455
+ };
456
+ _ui = null;
130
457
  }
131
- };
458
+ });
132
459
 
133
- // src/adapters/hermes/index.ts
134
- var import_node_child_process = require("child_process");
135
- var import_node_fs = require("fs");
136
- var import_node_os = require("os");
137
- var import_node_path = require("path");
138
- var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
139
- var YAML = __toESM(require("yaml"), 1);
140
- var import_zod = require("zod");
141
- var import_meta = {};
460
+ // src/cli/util.ts
461
+ var util_exports = {};
462
+ __export(util_exports, {
463
+ DEFAULT_CLOUD_BASE_URL: () => DEFAULT_CLOUD_BASE_URL,
464
+ clearPidFile: () => clearPidFile,
465
+ color: () => color,
466
+ exitWithError: () => exitWithError,
467
+ fail: () => fail2,
468
+ header: () => header,
469
+ info: () => info,
470
+ normalizeCloudUrl: () => normalizeCloudUrl,
471
+ ok: () => ok,
472
+ pidAlive: () => pidAlive,
473
+ pidFilePath: () => pidFilePath,
474
+ printBanner: () => printBanner,
475
+ printJson: () => printJson,
476
+ readPidFile: () => readPidFile,
477
+ runAction: () => runAction,
478
+ table: () => table,
479
+ tip: () => tip,
480
+ warn: () => warn,
481
+ writePidFile: () => writePidFile
482
+ });
483
+ function color(kind, text) {
484
+ if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
485
+ return `${ANSI[kind]}${text}${ANSI.reset}`;
486
+ }
487
+ function printJson(v) {
488
+ getUI().json(v, { pretty: true });
489
+ }
490
+ function exitWithError(message, opts) {
491
+ const o = typeof opts === "number" ? { exitCode: opts } : opts ?? {};
492
+ const exitCode = o.exitCode ?? 1;
493
+ const ui = getUI();
494
+ if (ui.mode === "json") {
495
+ const payload = {
496
+ ok: false,
497
+ error: { code: o.code ?? "cli_error", message },
498
+ ...o.details ? { details: o.details } : {}
499
+ };
500
+ ui.json(payload, { pretty: true });
501
+ } else {
502
+ process.stderr.write(`Error: ${message}
503
+ `);
504
+ }
505
+ process.exit(exitCode);
506
+ }
507
+ function normalizeCloudUrl(input) {
508
+ const raw = input.trim();
509
+ if (!raw) throw new Error("Cloud URL is empty.");
510
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(raw)) {
511
+ let parsed;
512
+ try {
513
+ parsed = new URL(raw);
514
+ } catch {
515
+ throw new Error(`Invalid --cloud URL: ${raw}`);
516
+ }
517
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
518
+ throw new Error(`Invalid --cloud URL scheme: ${parsed.protocol} (expected http:// or https://)`);
519
+ }
520
+ return raw.replace(/\/$/, "");
521
+ }
522
+ if (!/^[A-Za-z0-9.\-_:[\]/]+$/.test(raw)) {
523
+ throw new Error(`Invalid --cloud URL: ${raw} (must be http://\u2026 or https://\u2026 or host:port)`);
524
+ }
525
+ const candidate = `http://${raw}`;
526
+ try {
527
+ new URL(candidate);
528
+ } catch {
529
+ throw new Error(`Invalid --cloud URL: ${raw}`);
530
+ }
531
+ return candidate.replace(/\/$/, "");
532
+ }
533
+ function runAction(fn, opts = {}) {
534
+ return async (...args) => {
535
+ try {
536
+ await fn(...args);
537
+ } catch (err) {
538
+ const raw = err instanceof Error ? err.message : String(err);
539
+ const message = opts.sanitize ? opts.sanitize(raw) : raw;
540
+ exitWithError(message, { code: opts.code });
541
+ }
542
+ };
543
+ }
544
+ function printBanner(opts = {}) {
545
+ const ui = getUI();
546
+ if (opts.compact) {
547
+ ui.smallHeader("Runtime CLI v1.9.7");
548
+ return;
549
+ }
550
+ ui.banner("Runtime CLI v1.9.7", { full: true });
551
+ }
552
+ function ok(label, detail) {
553
+ getUI().ok(label, detail);
554
+ }
555
+ function warn(label, detail) {
556
+ getUI().warn(label, detail);
557
+ }
558
+ function fail2(label, detail) {
559
+ getUI().fail(label, detail);
560
+ }
561
+ function tip(command, detail) {
562
+ const text = detail ? `${command} ${detail}` : command;
563
+ getUI().tip(text);
564
+ }
565
+ function info(message) {
566
+ getUI().info(message);
567
+ }
568
+ function header(title) {
569
+ getUI().header(title);
570
+ getUI().blank();
571
+ }
572
+ function table(rows, columns) {
573
+ getUI().table(rows, { columns });
574
+ }
575
+ function pidFilePath(paths) {
576
+ return (0, import_node_path8.join)(paths.root, "daemon.pid");
577
+ }
578
+ function writePidFile(paths, pid) {
579
+ (0, import_node_fs12.writeFileSync)(pidFilePath(paths), `${pid}
580
+ `, "utf8");
581
+ }
582
+ function readPidFile(paths) {
583
+ const p = pidFilePath(paths);
584
+ if (!(0, import_node_fs12.existsSync)(p)) return void 0;
585
+ const raw = (0, import_node_fs12.readFileSync)(p, "utf8").trim();
586
+ const pid = Number.parseInt(raw, 10);
587
+ return Number.isFinite(pid) ? pid : void 0;
588
+ }
589
+ function clearPidFile(paths) {
590
+ const p = pidFilePath(paths);
591
+ if ((0, import_node_fs12.existsSync)(p)) {
592
+ try {
593
+ (0, import_node_fs12.unlinkSync)(p);
594
+ } catch {
595
+ }
596
+ }
597
+ }
598
+ function pidAlive(pid) {
599
+ try {
600
+ process.kill(pid, 0);
601
+ return true;
602
+ } catch {
603
+ return false;
604
+ }
605
+ }
606
+ var import_node_fs12, import_node_path8, DEFAULT_CLOUD_BASE_URL, ANSI;
607
+ var init_util = __esm({
608
+ "src/cli/util.ts"() {
609
+ "use strict";
610
+ import_node_fs12 = require("fs");
611
+ import_node_path8 = require("path");
612
+ init_ui();
613
+ DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
614
+ ANSI = {
615
+ reset: "\x1B[0m",
616
+ bold: "\x1B[1m",
617
+ dim: "\x1B[2m",
618
+ cyan: "\x1B[36m",
619
+ green: "\x1B[32m",
620
+ yellow: "\x1B[33m",
621
+ red: "\x1B[31m"
622
+ };
623
+ }
624
+ });
625
+
626
+ // src/index.ts
627
+ var src_exports = {};
628
+ __export(src_exports, {
629
+ AdapterRegistry: () => AdapterRegistry,
630
+ AssetCache: () => AssetCache,
631
+ BUILTIN_ROLE_TEMPLATES: () => BUILTIN_ROLE_TEMPLATES,
632
+ CloudClient: () => CloudClient,
633
+ CloudError: () => CloudError,
634
+ ConfigSchema: () => ConfigSchema,
635
+ LocalServer: () => LocalServer,
636
+ ParseClaimController: () => ParseClaimController,
637
+ Runner: () => Runner,
638
+ ServicePool: () => ServicePool,
639
+ SyncQueue: () => SyncQueue,
640
+ SyncWorker: () => SyncWorker,
641
+ TARGET_SCHEMA_VERSION: () => TARGET_SCHEMA_VERSION,
642
+ UriResolver: () => UriResolver,
643
+ WS_CLOSE: () => WS_CLOSE,
644
+ WorkspaceMirror: () => WorkspaceMirror,
645
+ WsClient: () => WsClient,
646
+ buildProgram: () => buildProgram,
647
+ claudeCodeAdapter: () => claudeCodeAdapter,
648
+ codexAdapter: () => codexAdapter,
649
+ composePrompt: () => composePrompt,
650
+ configExists: () => configExists,
651
+ currentSchemaVersion: () => currentSchemaVersion,
652
+ deriveWsUrl: () => deriveWsUrl,
653
+ envelope: () => envelope,
654
+ getRoleTemplate: () => getRoleTemplate,
655
+ handleDispatch: () => handleDispatch,
656
+ hermesAdapter: () => hermesAdapter,
657
+ isDaemonId: () => isDaemonId,
658
+ listRoleTemplates: () => listRoleTemplates,
659
+ loadConfig: () => loadConfig,
660
+ newDaemonId: () => newDaemonId,
661
+ nextBackoffMs: () => nextBackoffMs,
662
+ openLocalDb: () => openLocalDb,
663
+ pair: () => pair,
664
+ parseCodexOutput: () => parseCodexOutput,
665
+ parseUris: () => parseUris,
666
+ resolvePaths: () => resolvePaths,
667
+ runCli: () => runCli,
668
+ runMigrations: () => runMigrations,
669
+ saveConfig: () => saveConfig
670
+ });
671
+ module.exports = __toCommonJS(src_exports);
672
+
673
+ // src/adapters/registry.ts
674
+ var AdapterRegistry = class {
675
+ adapters = /* @__PURE__ */ new Map();
676
+ register(adapter) {
677
+ if (!adapter.name || typeof adapter.name !== "string") {
678
+ throw new Error("AdapterRegistry.register: adapter.name required");
679
+ }
680
+ const hasDispatch = typeof adapter.dispatch === "function";
681
+ const hasEnsureService = typeof adapter.ensureService === "function";
682
+ if (!hasDispatch && !hasEnsureService) {
683
+ throw new Error(
684
+ `AdapterRegistry.register: adapter "${adapter.name}" must implement dispatch() (interactive) or ensureService() (long-running)`
685
+ );
686
+ }
687
+ this.adapters.set(adapter.name, adapter);
688
+ }
689
+ unregister(name) {
690
+ return this.adapters.delete(name);
691
+ }
692
+ has(name) {
693
+ return this.adapters.has(name);
694
+ }
695
+ get(name) {
696
+ return this.adapters.get(name);
697
+ }
698
+ list() {
699
+ return Array.from(this.adapters.values());
700
+ }
701
+ size() {
702
+ return this.adapters.size;
703
+ }
704
+ /**
705
+ * Find adapters that satisfy a capability tag.
706
+ *
707
+ * Match rules (deterministic, sorted by name):
708
+ * - exact: adapter declared the tag verbatim
709
+ * - wildcard: adapter declared `code.*` → matches `code.write`
710
+ */
711
+ findByCapability(capability) {
712
+ const matches2 = [];
713
+ for (const adapter of this.adapters.values()) {
714
+ if (adapter.capabilities.includes(capability)) {
715
+ matches2.push(adapter);
716
+ continue;
717
+ }
718
+ if (adapter.capabilities.some(
719
+ (tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
720
+ )) {
721
+ matches2.push(adapter);
722
+ }
723
+ }
724
+ matches2.sort((a, b) => a.name.localeCompare(b.name));
725
+ return matches2;
726
+ }
727
+ };
728
+
729
+ // src/adapters/hermes/index.ts
730
+ var import_node_child_process = require("child_process");
731
+ var import_node_fs = require("fs");
732
+ var import_node_os = require("os");
733
+ var import_node_path = require("path");
734
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
735
+ var YAML = __toESM(require("yaml"), 1);
736
+ var import_zod = require("zod");
737
+ var import_meta = {};
142
738
  var PRISMER_IM_SKILL_NAME = "prismer-im-collab";
143
739
  var PRISMER_IM_SKILL_CONTENT = `# Prismer IM Collaboration
144
740
 
@@ -242,7 +838,7 @@ var HermesProfileConfigSchema = import_zod.z.object({
242
838
  */
243
839
  prismerMcpServerPath: import_zod.z.string().optional(),
244
840
  /** Model sent to Prismer's /api/v1/chat/completions endpoint. */
245
- model: import_zod.z.string().min(1).default("us-kimi-k2.5"),
841
+ model: import_zod.z.string().min(1).default("us-kimi-k2.6"),
246
842
  /** Named custom provider written into Hermes config.yaml. */
247
843
  prismerProviderName: import_zod.z.string().min(1).default("prismer"),
248
844
  /** Override cloud provider base. Defaults to PRISMER_BASE_URL + /api/v1. */
@@ -268,7 +864,9 @@ var HermesProfileConfigSchema = import_zod.z.object({
268
864
  * surface but the local source tree contains hermes_cli/kanban_db.py.
269
865
  */
270
866
  hermesSourceDir: import_zod.z.string().optional(),
271
- nativeMirrorTimeoutMs: import_zod.z.number().int().positive().default(2e3)
867
+ nativeMirrorTimeoutMs: import_zod.z.number().int().positive().default(2e3),
868
+ /** Task authority level: executor (default) or orchestrator. */
869
+ taskAuthority: import_zod.z.enum(["executor", "orchestrator"]).optional().default("executor")
272
870
  });
273
871
  var hermesAdapter = {
274
872
  name: "hermes",
@@ -753,13 +1351,13 @@ function resolvePrismerMcpServerPath(config) {
753
1351
  }
754
1352
  try {
755
1353
  const { fileURLToPath: fileURLToPath2 } = require("url");
756
- const { join: join14, dirname: dirname8 } = require("path");
1354
+ const { join: join15, dirname: dirname8 } = require("path");
757
1355
  const here = dirname8(fileURLToPath2(import_meta.url));
758
- const candidate = join14(here, "../../mcp/dist/index.js");
1356
+ const candidate = join15(here, "../../mcp/dist/index.js");
759
1357
  if ((0, import_node_fs.existsSync)(candidate)) return candidate;
760
- const candidate2 = join14(here, "../../../mcp/dist/index.js");
1358
+ const candidate2 = join15(here, "../../../mcp/dist/index.js");
761
1359
  if ((0, import_node_fs.existsSync)(candidate2)) return candidate2;
762
- const candidate3 = join14(here, "../../../../mcp/dist/index.js");
1360
+ const candidate3 = join15(here, "../../../../mcp/dist/index.js");
763
1361
  if ((0, import_node_fs.existsSync)(candidate3)) return candidate3;
764
1362
  } catch {
765
1363
  }
@@ -1181,7 +1779,7 @@ var WsClient = class extends import_node_events.EventEmitter {
1181
1779
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
1182
1780
  var import_node_fs2 = require("fs");
1183
1781
  var import_node_path2 = require("path");
1184
- var SCHEMA_VERSION = 2;
1782
+ var SCHEMA_VERSION = 3;
1185
1783
  var MIGRATIONS = [
1186
1784
  {
1187
1785
  version: 1,
@@ -1281,6 +1879,32 @@ var MIGRATIONS = [
1281
1879
  );
1282
1880
  CREATE INDEX IF NOT EXISTS idx_files_hash ON workspace_files_mirror (content_hash);
1283
1881
  `
1882
+ },
1883
+ {
1884
+ version: 3,
1885
+ up: `
1886
+ -- Asset metadata index (#filename reference resolution \u2014 daemon/asset/metadata-index.ts)
1887
+ CREATE TABLE IF NOT EXISTS asset_metadata_index (
1888
+ workspace_id TEXT NOT NULL,
1889
+ asset_id TEXT NOT NULL,
1890
+ content_hash TEXT NOT NULL,
1891
+ filename TEXT,
1892
+ folder_path TEXT,
1893
+ mime TEXT NOT NULL,
1894
+ kind TEXT NOT NULL,
1895
+ size_bytes INTEGER NOT NULL DEFAULT 0,
1896
+ description TEXT,
1897
+ asset_index_seq INTEGER NOT NULL,
1898
+ updated_at INTEGER NOT NULL,
1899
+ PRIMARY KEY (workspace_id, asset_id)
1900
+ );
1901
+
1902
+ CREATE INDEX IF NOT EXISTS idx_asset_meta_filename
1903
+ ON asset_metadata_index(workspace_id, filename);
1904
+
1905
+ CREATE INDEX IF NOT EXISTS idx_asset_meta_seq
1906
+ ON asset_metadata_index(workspace_id, asset_index_seq);
1907
+ `
1284
1908
  }
1285
1909
  ];
1286
1910
  function runSql(db, sql) {
@@ -1456,7 +2080,7 @@ var product_manager_default = {
1456
2080
  description: "Writes PRDs, defines requirements, verifies implementations",
1457
2081
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
1458
2082
  configSchema: {
1459
- model: "claude-3-5-sonnet",
2083
+ model: "us-kimi-k2.6",
1460
2084
  systemPrompt: "\u4F60\u662F\u4E00\u4F4D\u8D44\u6DF1\u4EA7\u54C1\u7ECF\u7406\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A\u2460\u5199\u6E05\u6670\u7684 PRD\uFF08\u7528\u6237\u6545\u4E8B + \u9A8C\u6536\u6807\u51C6 + \u8FB9\u754C\u6761\u4EF6\uFF09\u2461\u8BC4\u5BA1\u5DE5\u7A0B\u5B9E\u73B0\uFF08\u6838\u5BF9\u9A8C\u6536\u6807\u51C6 + \u7ED9\u53CD\u9988\uFF09\u2462\u5728\u7FA4\u804A\u91CC @engineer \u89E6\u53D1\u5B9E\u73B0\uFF0C@verifier \u89E6\u53D1\u9A8C\u6536\u3002\n\n\u7FA4\u804A\u534F\u4F5C\u7EA6\u5B9A\uFF1A\n- \u4F60\u7684\u8F93\u51FA PRD \u5E94\u8BE5\u4E0A\u4F20\u4E3A workspace file `docs/PROJECT-prd.md`\n- \u4F60\u5B8C\u6210 PRD \u540E\uFF0C\u53D1\u6D88\u606F @engineer-name \u8BA9\u4ED6\u5B9E\u73B0\uFF0C\u9644 prismer://file/<wsId>/docs/PROJECT-prd.md \u94FE\u63A5\n- \u6536\u5230 engineer \u5B8C\u6210\u6D88\u606F\u540E\uFF0C\u4E3B\u52A8 review\uFF0C\u5199\u53CD\u9988\u5230\u7FA4\u91CC",
1461
2085
  allowedTools: ["Read", "Write", "WebSearch"],
1462
2086
  maxTokens: 8e3
@@ -1470,7 +2094,7 @@ var engineer_default = {
1470
2094
  description: "Implements features per PRD, writes code, runs tests",
1471
2095
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
1472
2096
  configSchema: {
1473
- model: "claude-3-5-sonnet",
2097
+ model: "us-kimi-k2.6",
1474
2098
  systemPrompt: "\u4F60\u662F\u8D44\u6DF1\u5DE5\u7A0B\u5E08\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A\u2460\u6839\u636E PRD \u5B9E\u73B0 feature \u2461\u5199\u6D4B\u8BD5\u8986\u76D6 \u2462\u628A\u4EE3\u7801\u4E0A\u4F20\u4E3A workspace file \u540E\u5728\u7FA4\u804A\u6C47\u62A5\u3002\n\n\u7FA4\u804A\u534F\u4F5C\u7EA6\u5B9A\uFF1A\n- \u6536\u5230 PRD \u540E\u5148 Read prismer://file \u94FE\u63A5\u62C9\u5230\u7684\u672C\u5730\u8DEF\u5F84\n- \u5B9E\u73B0\u5B8C\u6BD5\u540E\u7528 daemon \u4E0A\u4F20 src/<feature>.* \u5230 workspace_files\n- \u5728\u7FA4\u804A\u56DE\u590D @pm-name\uFF0C\u9644\u5B9E\u73B0\u7684 prismer://file URI",
1475
2099
  allowedTools: ["Read", "Write", "Edit", "Bash", "Grep"],
1476
2100
  maxTokens: 16e3
@@ -1484,7 +2108,7 @@ var ceo_default = {
1484
2108
  description: "Sets strategic direction, asks tough questions, makes go/no-go calls",
1485
2109
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
1486
2110
  configSchema: {
1487
- model: "claude-3-5-sonnet",
2111
+ model: "us-kimi-k2.6",
1488
2112
  systemPrompt: "\u4F60\u662F CEO\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A\u2460\u57FA\u4E8E\u5E02\u573A\u53CD\u9988\u548C\u6570\u636E\u505A\u6218\u7565\u51B3\u7B56 \u2461\u8BC4\u5BA1 PRD \u662F\u5426\u5BF9\u9F50\u6218\u7565 \u2462\u5728\u6267\u884C\u4E2D\u63D0\u51FA\u5C16\u9510\u7684'\u4E3A\u4EC0\u4E48'\u95EE\u9898\u3002",
1489
2113
  allowedTools: ["Read", "WebSearch"],
1490
2114
  maxTokens: 4e3
@@ -1498,7 +2122,7 @@ var researcher_default = {
1498
2122
  description: "Investigates topics, gathers sources, writes research memos with citations",
1499
2123
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
1500
2124
  configSchema: {
1501
- model: "claude-3-5-sonnet",
2125
+ model: "us-kimi-k2.6",
1502
2126
  systemPrompt: "\u4F60\u662F\u4E00\u4F4D\u8D44\u6DF1\u7814\u7A76\u5458\u3002\u4EFB\u52A1\uFF1A\u2460\u4F9D\u636E\u95EE\u9898\u5236\u5B9A\u8C03\u7814\u63D0\u7EB2 \u2461\u6293\u53D6/\u9605\u8BFB\u8D44\u6599\u5E76\u63D0\u53D6\u8981\u70B9 \u2462\u4EA7\u51FA\u5E26\u5F15\u7528\u7684\u7814\u7A76\u5907\u5FD8\u5F55\u3002\n\n\u7FA4\u804A\u534F\u4F5C\u7EA6\u5B9A\uFF1A\n- \u7814\u7A76\u4EA7\u51FA\u4E0A\u4F20\u4E3A workspace file `research/<topic>.md`\n- \u5B8C\u6210\u540E\u5728\u7FA4\u804A\u56DE\u590D @pm-name\uFF0C\u9644 prismer://file/<wsId>/research/<topic>.md\n- \u5F15\u7528\u5FC5\u987B\u7ED9\u51FA\u539F\u59CB\u94FE\u63A5\u6216 prismer://asset URI\uFF1B\u4E0D\u5F97\u4F2A\u9020\u6765\u6E90",
1503
2127
  allowedTools: ["Read", "Write", "WebSearch", "WebFetch"],
1504
2128
  maxTokens: 12e3
@@ -1608,10 +2232,11 @@ function deriveWsUrl(httpBase) {
1608
2232
  }
1609
2233
 
1610
2234
  // src/daemon-id.ts
1611
- var import_node_crypto = require("crypto");
2235
+ var import_node_os3 = require("os");
1612
2236
  var PREFIX = "daemon-";
1613
2237
  function newDaemonId() {
1614
- return `${PREFIX}${(0, import_node_crypto.randomUUID)()}`;
2238
+ const host = (0, import_node_os3.hostname)().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
2239
+ return `${PREFIX}${host}`;
1615
2240
  }
1616
2241
  function isDaemonId(s) {
1617
2242
  return s.startsWith(PREFIX) && s.length > PREFIX.length;
@@ -2332,6 +2957,14 @@ async function handleDispatch(payload, requestId, deps) {
2332
2957
  sendReply(deps.ws, reply, requestId);
2333
2958
  return reply;
2334
2959
  }
2960
+ let hashRefResult = { text: payload.prompt, resolutions: [] };
2961
+ if (deps.assetMetadataIndexes && profile.workspaceId) {
2962
+ const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
2963
+ if (assetIndex) {
2964
+ hashRefResult = await resolveHashRefs(payload.prompt, assetIndex, deps.cloud);
2965
+ payload.prompt = hashRefResult.text;
2966
+ }
2967
+ }
2335
2968
  const rewrittenPrompt = await deps.uriResolver.rewrite(payload.prompt, { pin: true });
2336
2969
  resolvedHashes.push(...rewrittenPrompt.resolvedHashes);
2337
2970
  let rewrittenContext = [];
@@ -2546,6 +3179,84 @@ function isTextLikeMime(mime) {
2546
3179
  if (m.endsWith("+json") || m.endsWith("+xml") || m.endsWith("+csv")) return true;
2547
3180
  return false;
2548
3181
  }
3182
+ var HASH_REF_RE = /(?:^|\s)#([^\s#]+)/g;
3183
+ var HEX_COLOR_RE = /^[0-9a-fA-F]{3,8}$/;
3184
+ var FILE_EXT_RE = /\.[a-zA-Z0-9]{1,10}$/;
3185
+ var TRAILING_PUNCT_RE = /[,.;:!?)\]}'"]+$/;
3186
+ async function resolveHashRefs(prompt, assetIndex, cloud) {
3187
+ const resolutions = [];
3188
+ const candidates = [];
3189
+ let match;
3190
+ const re = new RegExp(HASH_REF_RE.source, "g");
3191
+ while ((match = re.exec(prompt)) !== null) {
3192
+ const refName = match[1];
3193
+ const leading = match[0].startsWith("#") ? 0 : 1;
3194
+ const start = match.index + leading;
3195
+ const end = match.index + match[0].length;
3196
+ if (HEX_COLOR_RE.test(refName)) continue;
3197
+ let cleanRef = refName;
3198
+ let stripped = "";
3199
+ const punctMatch = TRAILING_PUNCT_RE.exec(cleanRef);
3200
+ if (punctMatch) {
3201
+ stripped = punctMatch[0];
3202
+ cleanRef = cleanRef.slice(0, -stripped.length);
3203
+ }
3204
+ if (!cleanRef) continue;
3205
+ if (HEX_COLOR_RE.test(cleanRef)) continue;
3206
+ const hasExtension = FILE_EXT_RE.test(cleanRef);
3207
+ candidates.push({ ref: cleanRef, start, end: end - stripped.length, hasExtension });
3208
+ }
3209
+ if (candidates.length === 0) {
3210
+ return { text: prompt, resolutions: [] };
3211
+ }
3212
+ const allFilenames = candidates.map((c) => c.ref);
3213
+ const localResults = assetIndex.resolveByFilenames(allFilenames);
3214
+ const needsCloud = candidates.filter(
3215
+ (c) => c.hasExtension && !localResults.has(c.ref)
3216
+ );
3217
+ const cloudResults = /* @__PURE__ */ new Map();
3218
+ if (needsCloud.length > 0) {
3219
+ await Promise.allSettled(
3220
+ needsCloud.map(async (c) => {
3221
+ try {
3222
+ const items = await cloud.get(
3223
+ `/api/im/assets?workspaceId=${encodeURIComponent(assetIndex.workspaceId)}&q=${encodeURIComponent(c.ref)}&limit=1`
3224
+ );
3225
+ if (Array.isArray(items) && items.length > 0) {
3226
+ const item = items[0];
3227
+ cloudResults.set(c.ref, item.contentHash);
3228
+ }
3229
+ } catch {
3230
+ }
3231
+ })
3232
+ );
3233
+ }
3234
+ for (const c of candidates) {
3235
+ const local = localResults.get(c.ref);
3236
+ if (local) {
3237
+ resolutions.push({
3238
+ ref: c.ref,
3239
+ start: c.start,
3240
+ end: c.end,
3241
+ resolvedUri: `prismer://workspace/${encodeURIComponent(assetIndex.workspaceId)}/asset/${local.contentHash}`
3242
+ });
3243
+ } else if (c.hasExtension) {
3244
+ const cloudHash = cloudResults.get(c.ref);
3245
+ resolutions.push({
3246
+ ref: c.ref,
3247
+ start: c.start,
3248
+ end: c.end,
3249
+ resolvedUri: cloudHash ? `prismer://workspace/${encodeURIComponent(assetIndex.workspaceId)}/asset/${cloudHash}` : void 0
3250
+ });
3251
+ }
3252
+ }
3253
+ let result = prompt;
3254
+ const sorted = [...resolutions].filter((r) => r.resolvedUri).sort((a, b) => b.start - a.start);
3255
+ for (const r of sorted) {
3256
+ result = result.slice(0, r.start) + r.resolvedUri + result.slice(r.end);
3257
+ }
3258
+ return { text: result, resolutions };
3259
+ }
2549
3260
  async function resolveAssetRefs(refs, cache) {
2550
3261
  const out = { promptBlocks: [], observability: [], pinnedHashes: [] };
2551
3262
  if (!refs || refs.length === 0) return out;
@@ -2618,8 +3329,8 @@ async function resolveAssetRefs(refs, cache) {
2618
3329
  return out;
2619
3330
  }
2620
3331
  function formatInlineAssetBlock(ref, mime, body, strategy) {
2621
- const header = `[Attached file] id=${ref.assetId} mime=${mime ?? "unknown"}${strategy === "inline-text-truncated" ? " (truncated)" : ""}`;
2622
- return `${header}
3332
+ const header2 = `[Attached file] id=${ref.assetId} mime=${mime ?? "unknown"}${strategy === "inline-text-truncated" ? " (truncated)" : ""}`;
3333
+ return `${header2}
2623
3334
  ---
2624
3335
  ${body}
2625
3336
  ---`;
@@ -2934,7 +3645,7 @@ var ServicePool = class {
2934
3645
 
2935
3646
  // src/daemon/local-server.ts
2936
3647
  var import_node_http = require("http");
2937
- var import_node_crypto2 = require("crypto");
3648
+ var import_node_crypto = require("crypto");
2938
3649
  var import_node_fs7 = require("fs");
2939
3650
  var path2 = __toESM(require("path"), 1);
2940
3651
  var LocalServer = class {
@@ -2968,20 +3679,35 @@ var LocalServer = class {
2968
3679
  respond(res, 204, null);
2969
3680
  return;
2970
3681
  }
3682
+ const handlers = [];
2971
3683
  if (this.opts.attachMemory) {
2972
- void this.opts.attachMemory(req, res).then((handled) => {
2973
- if (handled) return;
2974
- this.routeStandard(req, res);
2975
- }).catch((err) => {
2976
- respond(res, 500, {
2977
- error: "attach_memory_threw",
2978
- message: err instanceof Error ? err.message : String(err)
2979
- });
2980
- });
3684
+ handlers.push({ name: "memory", fn: this.opts.attachMemory });
3685
+ }
3686
+ if (this.opts.attachAsset) {
3687
+ handlers.push({ name: "asset", fn: this.opts.attachAsset });
3688
+ }
3689
+ if (handlers.length > 0) {
3690
+ void this.runHandlers(req, res, handlers, 0);
2981
3691
  return;
2982
3692
  }
2983
3693
  this.routeStandard(req, res);
2984
3694
  }
3695
+ async runHandlers(req, res, handlers, idx) {
3696
+ if (idx >= handlers.length) {
3697
+ this.routeStandard(req, res);
3698
+ return;
3699
+ }
3700
+ try {
3701
+ const handled = await handlers[idx].fn(req, res);
3702
+ if (handled) return;
3703
+ await this.runHandlers(req, res, handlers, idx + 1);
3704
+ } catch (err) {
3705
+ respond(res, 500, {
3706
+ error: `attach_${handlers[idx].name}_threw`,
3707
+ message: err instanceof Error ? err.message : String(err)
3708
+ });
3709
+ }
3710
+ }
2985
3711
  routeStandard(req, res) {
2986
3712
  const url = req.url ?? "/";
2987
3713
  if (req.method === "GET" && url === "/healthz") {
@@ -2996,7 +3722,8 @@ var LocalServer = class {
2996
3722
  wsConnected: state.wsConnected,
2997
3723
  hostedAgents: state.hostedAgents,
2998
3724
  observability: state.observability,
2999
- memoryReady: this.opts.attachMemory != null
3725
+ memoryReady: this.opts.attachMemory != null,
3726
+ assetReady: this.opts.attachAsset != null
3000
3727
  });
3001
3728
  return;
3002
3729
  }
@@ -3148,7 +3875,7 @@ var LocalServer = class {
3148
3875
  respond(res, 400, { error: "missing_taskId" });
3149
3876
  return;
3150
3877
  }
3151
- const runId = (0, import_node_crypto2.randomUUID)();
3878
+ const runId = (0, import_node_crypto.randomUUID)();
3152
3879
  try {
3153
3880
  this.opts.onDispatch?.(payload, runId);
3154
3881
  } catch (err) {
@@ -3226,7 +3953,7 @@ async function walkAndDigest(root, current) {
3226
3953
  }
3227
3954
  if (!st.isFile()) continue;
3228
3955
  const buf = await import_node_fs7.promises.readFile(full);
3229
- const sha2562 = (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
3956
+ const sha2562 = (0, import_node_crypto.createHash)("sha256").update(buf).digest("hex");
3230
3957
  out.push({
3231
3958
  path: rel,
3232
3959
  sha256: sha2562,
@@ -3239,8 +3966,8 @@ async function walkAndDigest(root, current) {
3239
3966
 
3240
3967
  // src/daemon/runner.ts
3241
3968
  var import_node_events3 = require("events");
3242
- var import_node_fs10 = require("fs");
3243
- var import_node_os3 = require("os");
3969
+ var import_node_fs11 = require("fs");
3970
+ var import_node_os4 = require("os");
3244
3971
 
3245
3972
  // src/adapters/claude-code/index.ts
3246
3973
  var import_node_child_process2 = require("child_process");
@@ -3891,7 +4618,7 @@ var path4 = __toESM(require("path"), 1);
3891
4618
  var import_better_sqlite33 = __toESM(require("better-sqlite3"), 1);
3892
4619
  var fs2 = __toESM(require("fs"), 1);
3893
4620
  var path3 = __toESM(require("path"), 1);
3894
- var import_node_crypto3 = require("crypto");
4621
+ var import_node_crypto2 = require("crypto");
3895
4622
 
3896
4623
  // src/daemon/memory/crypto.ts
3897
4624
  function sealPlaintext(content) {
@@ -4093,16 +4820,17 @@ var MemoryStore = class {
4093
4820
  const existing = db.prepare(
4094
4821
  "SELECT id, version, createdAt FROM memory_pages WHERE workspaceId = ? AND path = ?"
4095
4822
  ).get(this.opts.workspaceId, input.path);
4096
- const pageId = existing?.id ?? `page_${(0, import_node_crypto3.randomUUID)().replace(/-/g, "").slice(0, 22)}`;
4823
+ const pageId = existing?.id ?? `page_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "").slice(0, 22)}`;
4097
4824
  const newVersion = (existing?.version ?? 0) + 1;
4098
4825
  const createdAt = existing?.createdAt ?? now;
4826
+ const staleFlag = input.stale ? 1 : 0;
4099
4827
  const insertPage = db.prepare(`
4100
4828
  INSERT INTO memory_pages (
4101
4829
  id, workspaceId, path, title, description, contentHash, version,
4102
4830
  pageType, visibilityKind, visibilityImUserId, encrypted, stale,
4103
4831
  archivedAt, sourceAssetId, sourceRefsJson, syncStatus,
4104
4832
  createdAt, updatedAt
4105
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, 'local-only', ?, ?)
4833
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, NULL, ?, ?, 'local-only', ?, ?)
4106
4834
  ON CONFLICT(workspaceId, path) DO UPDATE SET
4107
4835
  title = excluded.title,
4108
4836
  description = excluded.description,
@@ -4113,6 +4841,7 @@ var MemoryStore = class {
4113
4841
  visibilityImUserId = excluded.visibilityImUserId,
4114
4842
  sourceAssetId = excluded.sourceAssetId,
4115
4843
  sourceRefsJson = excluded.sourceRefsJson,
4844
+ stale = excluded.stale,
4116
4845
  updatedAt = excluded.updatedAt
4117
4846
  `);
4118
4847
  const insertVersion = db.prepare(`
@@ -4140,6 +4869,7 @@ var MemoryStore = class {
4140
4869
  input.pageType ?? "leaf",
4141
4870
  visibility.kind,
4142
4871
  visibilityImUserId,
4872
+ staleFlag,
4143
4873
  input.sourceAssetId ?? null,
4144
4874
  sourceRefsJson,
4145
4875
  createdAt,
@@ -4220,6 +4950,16 @@ var MemoryStore = class {
4220
4950
  dbPath: this.opts.dbPath
4221
4951
  };
4222
4952
  }
4953
+ /**
4954
+ * Record sync cursor for incremental sync. Used by cloud-sync.ts to
4955
+ * persist the high-water mark for future cursor-based catch-up.
4956
+ */
4957
+ recordCursor(workspaceId, cursor) {
4958
+ const now = Date.now();
4959
+ this.requireDb().prepare(
4960
+ `INSERT OR REPLACE INTO memory_inbox_cursor (workspaceId, cursor, updatedAt) VALUES (?, ?, ?)`
4961
+ ).run(workspaceId, cursor, now);
4962
+ }
4223
4963
  /**
4224
4964
  * Internal accessor for outbox.ts — outbox writes its own table within the
4225
4965
  * same DB. Returning the live Database handle keeps outbox transactions
@@ -4264,7 +5004,7 @@ var MemoryStore = class {
4264
5004
  }
4265
5005
  };
4266
5006
  function sha256(s) {
4267
- return (0, import_node_crypto3.createHash)("sha256").update(s, "utf8").digest("hex");
5007
+ return (0, import_node_crypto2.createHash)("sha256").update(s, "utf8").digest("hex");
4268
5008
  }
4269
5009
 
4270
5010
  // src/daemon/memory/search.ts
@@ -4370,7 +5110,7 @@ function clamp(n, lo, hi) {
4370
5110
  }
4371
5111
 
4372
5112
  // src/daemon/memory/outbox.ts
4373
- var import_node_crypto4 = require("crypto");
5113
+ var import_node_crypto3 = require("crypto");
4374
5114
 
4375
5115
  // src/daemon/memory/envelope.ts
4376
5116
  var import_zod6 = require("zod");
@@ -4503,7 +5243,7 @@ var MemoryOutbox = class {
4503
5243
  const now = Date.now();
4504
5244
  const parsed = MemoryOutboxEnvelope.safeParse(event);
4505
5245
  if (!parsed.success) {
4506
- const dlId = `dl_${(0, import_node_crypto4.randomUUID)()}`;
5246
+ const dlId = `dl_${(0, import_node_crypto3.randomUUID)()}`;
4507
5247
  db.prepare(
4508
5248
  `INSERT INTO memory_outbox_dead_letter (id, eventType, rawJson, errorJson, createdAt)
4509
5249
  VALUES (?, ?, ?, ?, ?)`
@@ -4520,7 +5260,7 @@ var MemoryOutbox = class {
4520
5260
  if (validated.eventType === "memory.feedback") {
4521
5261
  const check = validateFeedbackTarget(validated);
4522
5262
  if (!check.ok) {
4523
- const dlId = `dl_${(0, import_node_crypto4.randomUUID)()}`;
5263
+ const dlId = `dl_${(0, import_node_crypto3.randomUUID)()}`;
4524
5264
  db.prepare(
4525
5265
  `INSERT INTO memory_outbox_dead_letter (id, eventType, rawJson, errorJson, createdAt)
4526
5266
  VALUES (?, ?, ?, ?, ?)`
@@ -4534,7 +5274,7 @@ var MemoryOutbox = class {
4534
5274
  return { id: dlId, deadLetter: true };
4535
5275
  }
4536
5276
  }
4537
- const rowId = `out_${(0, import_node_crypto4.randomUUID)()}`;
5277
+ const rowId = `out_${(0, import_node_crypto3.randomUUID)()}`;
4538
5278
  try {
4539
5279
  db.prepare(
4540
5280
  `INSERT INTO memory_outbox (id, eventType, envelopeJson, idempotencyKey, status, createdAt)
@@ -4870,6 +5610,84 @@ function defaultLog() {
4870
5610
  };
4871
5611
  }
4872
5612
 
5613
+ // src/daemon/memory/cloud-sync.ts
5614
+ var LOG = "[CloudMemorySync]";
5615
+ var CLOUD_PAGE_LIMIT = 300;
5616
+ async function initialSyncFromCloud(runtime, cloud, workspaceId) {
5617
+ const slot = runtime.peek(workspaceId);
5618
+ if (!slot) {
5619
+ console.log(`${LOG} No store for workspace=${workspaceId} \u2014 skipping`);
5620
+ return { pulled: 0, skipped: 0 };
5621
+ }
5622
+ const cursorRow = slot.store.rawDb().prepare("SELECT cursor FROM memory_inbox_cursor WHERE workspaceId = ?").get(workspaceId);
5623
+ if (cursorRow) {
5624
+ console.log(`${LOG} Workspace=${workspaceId} already synced (cursor: ${cursorRow.cursor.slice(0, 20)}...) \u2014 skip`);
5625
+ return { pulled: 0, skipped: 0 };
5626
+ }
5627
+ console.log(`${LOG} Fetching cloud pages for workspace=${workspaceId}...`);
5628
+ const resp = await cloud.request(
5629
+ "GET",
5630
+ `/api/im/memory/pages?workspaceId=${encodeURIComponent(workspaceId)}&limit=${CLOUD_PAGE_LIMIT}&stale=all`,
5631
+ { timeoutMs: 15e3 }
5632
+ );
5633
+ if (!resp.ok) {
5634
+ console.warn(
5635
+ `${LOG} Cloud GET /memory/pages returned ${resp.status}: ${resp.error?.message ?? "unknown"}`
5636
+ );
5637
+ return { pulled: 0, skipped: 0 };
5638
+ }
5639
+ const envelope2 = resp.data;
5640
+ if (!envelope2 || !envelope2.ok) {
5641
+ console.log(`${LOG} Cloud returned non-ok envelope for workspace=${workspaceId}`);
5642
+ return { pulled: 0, skipped: 0 };
5643
+ }
5644
+ const pages = envelope2.data;
5645
+ if (!pages || !Array.isArray(pages) || pages.length === 0) {
5646
+ console.log(`${LOG} No cloud pages to sync for workspace=${workspaceId}`);
5647
+ return { pulled: 0, skipped: 0 };
5648
+ }
5649
+ let pulled = 0;
5650
+ let skipped = 0;
5651
+ for (const page of pages) {
5652
+ let content = page.content ?? "";
5653
+ if (!content) {
5654
+ try {
5655
+ const detailResp = await cloud.request(
5656
+ "GET",
5657
+ `/api/im/memory/pages/${encodeURIComponent(page.id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
5658
+ { timeoutMs: 5e3 }
5659
+ );
5660
+ if (detailResp.ok && detailResp.data?.data?.content) {
5661
+ content = detailResp.data.data.content;
5662
+ }
5663
+ } catch {
5664
+ }
5665
+ }
5666
+ const visibility = page.visibility === "agent" ? { kind: "agent", imUserId: "" } : { kind: "workspace" };
5667
+ try {
5668
+ slot.store.write({
5669
+ workspaceId,
5670
+ path: page.path,
5671
+ title: page.title ?? void 0,
5672
+ content: content || "",
5673
+ pageType: page.pageType || "leaf",
5674
+ visibility,
5675
+ actorImUserId: "cloud-sync",
5676
+ actorKind: "agent"
5677
+ });
5678
+ pulled++;
5679
+ } catch (err) {
5680
+ console.warn(`${LOG} write failed for ${page.path}:`, err.message);
5681
+ skipped++;
5682
+ }
5683
+ }
5684
+ slot.store.recordCursor(workspaceId, `synced:${Date.now()}`);
5685
+ console.log(
5686
+ `${LOG} Synced ${pulled} pages${skipped ? `, ${skipped} skipped` : ""} for workspace=${workspaceId}`
5687
+ );
5688
+ return { pulled, skipped };
5689
+ }
5690
+
4873
5691
  // src/daemon/memory/runner-wiring.ts
4874
5692
  function attachMemoryRunner(opts) {
4875
5693
  const runtime = new MemoryRuntime({ baseDir: opts.baseDir, deviceId: opts.deviceId });
@@ -4900,6 +5718,24 @@ function attachMemoryRunner(opts) {
4900
5718
  }
4901
5719
  };
4902
5720
  }
5721
+ async function syncMemoryFromCloud(wiring, cloud, workspaceIds) {
5722
+ const uniqueIds = [...new Set(workspaceIds.filter(Boolean))];
5723
+ if (uniqueIds.length === 0) return;
5724
+ console.log(`[MemorySync] Initial cloud-to-local sync for ${uniqueIds.length} workspace(s)...`);
5725
+ for (const wsId of uniqueIds) {
5726
+ try {
5727
+ wiring.runtime.resolve(wsId);
5728
+ const result = await initialSyncFromCloud(wiring.runtime, cloud, wsId);
5729
+ if (result.pulled > 0 || result.skipped > 0) {
5730
+ console.log(
5731
+ `[MemorySync] workspace=${wsId}: ${result.pulled} pulled, ${result.skipped} skipped`
5732
+ );
5733
+ }
5734
+ } catch (err) {
5735
+ console.error(`[MemorySync] workspace=${wsId} failed:`, err.message);
5736
+ }
5737
+ }
5738
+ }
4903
5739
 
4904
5740
  // src/daemon/memory/fork/select-memories.ts
4905
5741
  var SELECT_MEMORIES_SYSTEM_PROMPT = [
@@ -5018,7 +5854,7 @@ function clamp2(n, lo, hi) {
5018
5854
  }
5019
5855
 
5020
5856
  // src/daemon/memory/fork/tracing.ts
5021
- var import_node_crypto5 = require("crypto");
5857
+ var import_node_crypto4 = require("crypto");
5022
5858
 
5023
5859
  // src/daemon/memory/fork/runner.ts
5024
5860
  var DEFAULT_SNIPPET_MAX_BYTES = 500;
@@ -5307,8 +6143,235 @@ function parsePrismerUri(uri) {
5307
6143
  return { workspaceId, path: path7 };
5308
6144
  }
5309
6145
 
5310
- // src/daemon/outbox-watcher.ts
6146
+ // src/daemon/asset/metadata-index.ts
5311
6147
  var import_node_fs8 = require("fs");
6148
+ var import_node_path6 = require("path");
6149
+ var DEFAULT_LIMIT = 8;
6150
+ var PULL_PAGE_SIZE = 500;
6151
+ var THROTTLE_MS = 3e4;
6152
+ function rowToMetadata(row) {
6153
+ return {
6154
+ assetId: row.asset_id,
6155
+ contentHash: row.content_hash,
6156
+ filename: row.filename,
6157
+ folderPath: row.folder_path,
6158
+ mime: row.mime,
6159
+ kind: row.kind,
6160
+ sizeBytes: row.size_bytes,
6161
+ description: row.description,
6162
+ assetIndexSeq: row.asset_index_seq
6163
+ };
6164
+ }
6165
+ var AssetMetadataIndex = class {
6166
+ db;
6167
+ cloud;
6168
+ /** Workspace ID — exposed for prismer:// URI construction. */
6169
+ workspaceId;
6170
+ cursorPath;
6171
+ _lastSyncMs = 0;
6172
+ constructor(opts) {
6173
+ this.db = opts.db;
6174
+ this.cloud = opts.cloud;
6175
+ this.workspaceId = opts.workspaceId;
6176
+ if (!(0, import_node_fs8.existsSync)(opts.workspaceStateDir)) {
6177
+ (0, import_node_fs8.mkdirSync)(opts.workspaceStateDir, { recursive: true });
6178
+ }
6179
+ this.cursorPath = (0, import_node_path6.join)(opts.workspaceStateDir, "asset-metadata-cursor.json");
6180
+ }
6181
+ /** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
6182
+ readCursor() {
6183
+ if (!(0, import_node_fs8.existsSync)(this.cursorPath)) return 0;
6184
+ try {
6185
+ const parsed = JSON.parse((0, import_node_fs8.readFileSync)(this.cursorPath, "utf8"));
6186
+ if (parsed.workspaceId !== this.workspaceId) return 0;
6187
+ return parsed.cursor;
6188
+ } catch {
6189
+ return 0;
6190
+ }
6191
+ }
6192
+ writeCursor(cursor) {
6193
+ const payload = {
6194
+ workspaceId: this.workspaceId,
6195
+ cursor,
6196
+ writtenAt: Date.now()
6197
+ };
6198
+ (0, import_node_fs8.writeFileSync)(this.cursorPath, JSON.stringify(payload, null, 2));
6199
+ }
6200
+ /**
6201
+ * Pull incremental asset metadata changes since the persisted cursor and
6202
+ * upsert into the local index. Newer rows overwrite older ones by
6203
+ * (workspace_id, asset_id) primary key.
6204
+ *
6205
+ * Returns the count of items applied + the new cursor. Throttled: if called
6206
+ * within 30s of the last successful pull, returns immediately.
6207
+ */
6208
+ async pullDelta(opts) {
6209
+ const now = Date.now();
6210
+ if (now - this._lastSyncMs < THROTTLE_MS) {
6211
+ return { applied: 0, cursor: this.readCursor() };
6212
+ }
6213
+ const since = this.readCursor();
6214
+ const sinceParam = since > 0 ? `&since=${since}` : "";
6215
+ const envelope2 = await this.cloud.get(
6216
+ `/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
6217
+ { signal: opts?.signal }
6218
+ );
6219
+ const upsert = this.db.prepare(`
6220
+ INSERT INTO asset_metadata_index
6221
+ (workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
6222
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6223
+ ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
6224
+ content_hash = excluded.content_hash,
6225
+ filename = excluded.filename,
6226
+ folder_path = excluded.folder_path,
6227
+ mime = excluded.mime,
6228
+ kind = excluded.kind,
6229
+ size_bytes = excluded.size_bytes,
6230
+ description = excluded.description,
6231
+ asset_index_seq = excluded.asset_index_seq,
6232
+ updated_at = excluded.updated_at
6233
+ `);
6234
+ const nowTs = Date.now();
6235
+ let applied = 0;
6236
+ const tx = this.db.transaction((items) => {
6237
+ for (const item of items) {
6238
+ upsert.run(
6239
+ this.workspaceId,
6240
+ item.assetId,
6241
+ item.contentHash,
6242
+ item.filename ?? null,
6243
+ item.folderPath ?? null,
6244
+ item.mime,
6245
+ item.kind,
6246
+ item.sizeBytes,
6247
+ item.description ?? null,
6248
+ item.assetIndexSeq,
6249
+ nowTs
6250
+ );
6251
+ applied += 1;
6252
+ }
6253
+ });
6254
+ try {
6255
+ tx(envelope2.items);
6256
+ this.writeCursor(envelope2.cursor);
6257
+ } catch (err) {
6258
+ throw err;
6259
+ }
6260
+ this._lastSyncMs = now;
6261
+ return { applied, cursor: envelope2.cursor };
6262
+ }
6263
+ /**
6264
+ * Search local index by filename or description substring.
6265
+ * Escapes LIKE wildcards (% and _). Default limit 8.
6266
+ */
6267
+ search(query, limit) {
6268
+ const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
6269
+ const pattern = `%${escaped}%`;
6270
+ const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
6271
+ const rows = this.db.prepare(
6272
+ `SELECT * FROM asset_metadata_index
6273
+ WHERE workspace_id = ?
6274
+ AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
6275
+ ORDER BY asset_index_seq DESC
6276
+ LIMIT ?`
6277
+ ).all(this.workspaceId, pattern, pattern, limitVal);
6278
+ return rows.map(rowToMetadata);
6279
+ }
6280
+ /** Exact match on filename column. Returns undefined if not indexed. */
6281
+ resolveByFilename(filename) {
6282
+ const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
6283
+ return row ? rowToMetadata(row) : void 0;
6284
+ }
6285
+ /** Batch exact match — returns Map for O(1) access. */
6286
+ resolveByFilenames(filenames) {
6287
+ if (filenames.length === 0) return /* @__PURE__ */ new Map();
6288
+ const placeholders = filenames.map(() => "?").join(",");
6289
+ const params = [this.workspaceId, ...filenames];
6290
+ const rows = this.db.prepare(
6291
+ `SELECT * FROM asset_metadata_index
6292
+ WHERE workspace_id = ? AND filename IN (${placeholders})`
6293
+ ).all(...params);
6294
+ const map = /* @__PURE__ */ new Map();
6295
+ for (const row of rows) {
6296
+ if (row.filename) map.set(row.filename, rowToMetadata(row));
6297
+ }
6298
+ return map;
6299
+ }
6300
+ };
6301
+
6302
+ // src/daemon/asset/rpc.ts
6303
+ var ASSET_PATH_PREFIX = "/local/asset/";
6304
+ function attachAssetRpc(opts) {
6305
+ return async (req, res) => {
6306
+ const url = req.url ?? "/";
6307
+ if (!url.startsWith(ASSET_PATH_PREFIX)) return false;
6308
+ const [pathOnly = ""] = url.split("?", 2);
6309
+ const subpath = pathOnly.slice(ASSET_PATH_PREFIX.length);
6310
+ const method = req.method ?? "GET";
6311
+ try {
6312
+ if (method === "POST" && subpath === "search") {
6313
+ const body = await readJson3(req);
6314
+ return handleSearch2(opts.resolveIndex, body, res);
6315
+ }
6316
+ respond3(res, 404, { error: "asset_route_not_found", path: url });
6317
+ return true;
6318
+ } catch (err) {
6319
+ respond3(res, 500, {
6320
+ error: "asset_rpc_failed",
6321
+ message: err instanceof Error ? err.message : String(err)
6322
+ });
6323
+ return true;
6324
+ }
6325
+ };
6326
+ }
6327
+ function handleSearch2(resolveIndex, body, res) {
6328
+ if (!body || typeof body !== "object") {
6329
+ return respond4002(res, "request body must be a JSON object");
6330
+ }
6331
+ const b = body;
6332
+ if (typeof b.workspaceId !== "string" || !b.workspaceId) {
6333
+ return respond4002(res, "workspaceId is required (string)");
6334
+ }
6335
+ if (typeof b.query !== "string" || !b.query.trim()) {
6336
+ return respond4002(res, "query is required (non-empty string)");
6337
+ }
6338
+ const index = resolveIndex(b.workspaceId);
6339
+ if (!index) {
6340
+ respond3(res, 404, {
6341
+ error: "workspace_index_not_found",
6342
+ workspaceId: b.workspaceId,
6343
+ message: "No asset metadata index for this workspace. Ensure the daemon has synced asset metadata."
6344
+ });
6345
+ return true;
6346
+ }
6347
+ const limit = typeof b.limit === "number" && b.limit > 0 ? b.limit : void 0;
6348
+ const items = index.search(b.query.trim(), limit);
6349
+ respond3(res, 200, { items });
6350
+ return true;
6351
+ }
6352
+ function respond3(res, status, body) {
6353
+ res.statusCode = status;
6354
+ res.setHeader("Content-Type", "application/json");
6355
+ res.end(JSON.stringify(body));
6356
+ }
6357
+ function respond4002(res, message) {
6358
+ respond3(res, 400, { error: "invalid_request", message });
6359
+ return true;
6360
+ }
6361
+ async function readJson3(req) {
6362
+ let raw = "";
6363
+ req.setEncoding("utf8");
6364
+ for await (const chunk of req) raw += chunk;
6365
+ if (!raw) return {};
6366
+ try {
6367
+ return JSON.parse(raw);
6368
+ } catch {
6369
+ throw new Error("invalid_json");
6370
+ }
6371
+ }
6372
+
6373
+ // src/daemon/outbox-watcher.ts
6374
+ var import_node_fs9 = require("fs");
5312
6375
  var path5 = __toESM(require("path"), 1);
5313
6376
  var DEFAULT_INTERVAL_MS = 2e3;
5314
6377
  var RESERVED_SUBDIR = "_uploaded";
@@ -5476,7 +6539,7 @@ var OutboxWatcher = class {
5476
6539
  async scanDir(dir, kind, task) {
5477
6540
  let entries = [];
5478
6541
  try {
5479
- entries = await import_node_fs8.promises.readdir(dir);
6542
+ entries = await import_node_fs9.promises.readdir(dir);
5480
6543
  } catch (err) {
5481
6544
  const code = err.code;
5482
6545
  if (code === "ENOENT") return;
@@ -5489,7 +6552,7 @@ var OutboxWatcher = class {
5489
6552
  const full = path5.join(dir, name);
5490
6553
  let st;
5491
6554
  try {
5492
- st = await import_node_fs8.promises.stat(full);
6555
+ st = await import_node_fs9.promises.stat(full);
5493
6556
  } catch {
5494
6557
  continue;
5495
6558
  }
@@ -5514,7 +6577,7 @@ var OutboxWatcher = class {
5514
6577
  this.log("warn", `skip ${filePath}: no active taskId (no dispatch/handoff received yet)`);
5515
6578
  return;
5516
6579
  }
5517
- const bytes = await import_node_fs8.promises.readFile(filePath);
6580
+ const bytes = await import_node_fs9.promises.readFile(filePath);
5518
6581
  const fileName = path5.basename(filePath);
5519
6582
  const blob = new Blob([new Uint8Array(bytes)]);
5520
6583
  const form = new FormData();
@@ -5564,8 +6627,8 @@ var OutboxWatcher = class {
5564
6627
 
5565
6628
  // src/daemon/shell-executor.ts
5566
6629
  var import_node_child_process4 = require("child_process");
5567
- var import_node_fs9 = require("fs");
5568
- var import_node_path6 = require("path");
6630
+ var import_node_fs10 = require("fs");
6631
+ var import_node_path7 = require("path");
5569
6632
  var DEFAULT_OUTPUT_LIMIT = 256 * 1024;
5570
6633
  var DEFAULT_TIMEOUT = 6e4;
5571
6634
  var HARD_MAX_TIMEOUT = 30 * 6e4;
@@ -5601,7 +6664,7 @@ async function executeShellDispatch(payload, deps) {
5601
6664
  const command = readCommand(payload, execution);
5602
6665
  if (!command.trim()) return fail(payload.taskId, "shell_command_required", "Shell command is required");
5603
6666
  const cwd = resolveCwd(execution.cwd, deps.config.defaultCwd);
5604
- if (!(0, import_node_fs9.existsSync)(cwd)) return fail(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
6667
+ if (!(0, import_node_fs10.existsSync)(cwd)) return fail(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
5605
6668
  const timeoutMs = Math.min(
5606
6669
  typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : deps.config.maxTimeoutMs,
5607
6670
  deps.config.maxTimeoutMs
@@ -5703,8 +6766,8 @@ function readCommand(payload, execution) {
5703
6766
  return payload.prompt;
5704
6767
  }
5705
6768
  function resolveCwd(raw, fallback) {
5706
- if (typeof raw !== "string" || raw.trim() === "") return (0, import_node_path6.resolve)(fallback);
5707
- return (0, import_node_path6.resolve)(raw);
6769
+ if (typeof raw !== "string" || raw.trim() === "") return (0, import_node_path7.resolve)(fallback);
6770
+ return (0, import_node_path7.resolve)(raw);
5708
6771
  }
5709
6772
  function clampNumber(value, fallback, min, max) {
5710
6773
  const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
@@ -5753,6 +6816,7 @@ var Runner = class extends import_node_events3.EventEmitter {
5753
6816
  localServer;
5754
6817
  outboxWatcher;
5755
6818
  memoryWiring;
6819
+ assetMetadataIndexes = /* @__PURE__ */ new Map();
5756
6820
  state = "idle";
5757
6821
  startedAt = 0;
5758
6822
  workspaceId = "";
@@ -5769,6 +6833,9 @@ var Runner = class extends import_node_events3.EventEmitter {
5769
6833
  this.paths = this.opts.paths ?? resolvePaths();
5770
6834
  this.config = this.opts.configOverride ?? loadConfig(this.paths);
5771
6835
  this.shellConfig = resolveShellConfig(this.config.shell);
6836
+ if (process.env.PRISMER_WORKSPACE_ID) {
6837
+ this.workspaceId = process.env.PRISMER_WORKSPACE_ID;
6838
+ }
5772
6839
  process.env.PRISMER_BASE_URL = this.config.cloud_api_base;
5773
6840
  process.env.PRISMER_API_KEY = this.config.api_key;
5774
6841
  this.db = openLocalDb(this.paths.localDb);
@@ -5806,6 +6873,16 @@ var Runner = class extends import_node_events3.EventEmitter {
5806
6873
  baseDir: `${this.paths.root}/memory`,
5807
6874
  deviceId: this.config.daemon_id
5808
6875
  });
6876
+ if (this.memoryWiring && this.workspaceId) {
6877
+ syncMemoryFromCloud(this.memoryWiring, this.cloud, [this.workspaceId]).catch(
6878
+ (err) => console.error("[Daemon] Initial memory sync failed:", err.message)
6879
+ );
6880
+ }
6881
+ if (this.workspaceId) {
6882
+ this.syncAssetMetadata(this.workspaceId).catch(
6883
+ (err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
6884
+ );
6885
+ }
5809
6886
  const containerId = process.env.PRISMER_CONTAINER_ID;
5810
6887
  const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
5811
6888
  this.outboxWatcher = new OutboxWatcher({
@@ -5853,7 +6930,10 @@ var Runner = class extends import_node_events3.EventEmitter {
5853
6930
  // wiring step so it could be reviewed alongside the host-adapter
5854
6931
  // consumer (Hermes T2-B), which is what surfaces these routes to
5855
6932
  // an actual agent process.
5856
- attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0
6933
+ attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
6934
+ attachAsset: attachAssetRpc({
6935
+ resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
6936
+ })
5857
6937
  });
5858
6938
  await this.localServer.start();
5859
6939
  }
@@ -6049,10 +7129,10 @@ var Runner = class extends import_node_events3.EventEmitter {
6049
7129
  const rawJson = process.env.PRISMER_HOSTED_AGENT_JSON;
6050
7130
  let raw;
6051
7131
  if (rawFile) {
6052
- if (!(0, import_node_fs10.existsSync)(rawFile)) {
7132
+ if (!(0, import_node_fs11.existsSync)(rawFile)) {
6053
7133
  throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
6054
7134
  }
6055
- raw = (0, import_node_fs10.readFileSync)(rawFile, "utf8");
7135
+ raw = (0, import_node_fs11.readFileSync)(rawFile, "utf8");
6056
7136
  } else if (rawJson) {
6057
7137
  raw = rawJson;
6058
7138
  }
@@ -6148,7 +7228,7 @@ var Runner = class extends import_node_events3.EventEmitter {
6148
7228
  const payload = {
6149
7229
  daemonId: this.config.daemon_id,
6150
7230
  daemonVersion: this.opts.daemonVersion ?? "0.0.0",
6151
- platform: (0, import_node_os3.platform)() === "win32" ? "win32" : (0, import_node_os3.platform)() === "linux" ? "linux" : "darwin",
7231
+ platform: (0, import_node_os4.platform)() === "win32" ? "win32" : (0, import_node_os4.platform)() === "linux" ? "linux" : "darwin",
6152
7232
  agents: Array.from(this.hostedAgents.values()).map((a) => ({
6153
7233
  imUserId: a.imUserId,
6154
7234
  name: a.name,
@@ -6182,12 +7262,23 @@ var Runner = class extends import_node_events3.EventEmitter {
6182
7262
  case "workspace_file.changed":
6183
7263
  this.onWorkspaceFileChanged(msg.payload);
6184
7264
  return;
7265
+ case "asset.changed":
7266
+ void this.onAssetChanged(msg.payload);
7267
+ return;
6185
7268
  default:
6186
7269
  this.emit("unknown-message", msg);
6187
7270
  }
6188
7271
  }
6189
7272
  async onHostAcked(payload) {
6190
7273
  this.workspaceId = payload.workspaceId;
7274
+ if (this.memoryWiring && this.workspaceId) {
7275
+ syncMemoryFromCloud(this.memoryWiring, this.cloud, [this.workspaceId]).catch(
7276
+ (err) => console.error("[Daemon] Initial memory sync failed:", err.message)
7277
+ );
7278
+ }
7279
+ this.syncAssetMetadata(this.workspaceId).catch(
7280
+ (err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
7281
+ );
6191
7282
  for (const id of payload.profilesToSync) {
6192
7283
  try {
6193
7284
  await this.syncProfileFromCloud(id);
@@ -6247,6 +7338,7 @@ var Runner = class extends import_node_events3.EventEmitter {
6247
7338
  paths: this.paths,
6248
7339
  signal: ctrl.signal,
6249
7340
  ensureService: (profile, adapter) => this.servicePool.ensureService(profile, adapter),
7341
+ assetMetadataIndexes: this.assetMetadataIndexes,
6250
7342
  onProgress: () => {
6251
7343
  const running = this.runningTasks.get(payload.taskId);
6252
7344
  if (running) running.lastProgressAt = Date.now();
@@ -6347,6 +7439,19 @@ var Runner = class extends import_node_events3.EventEmitter {
6347
7439
  this.emit("sync-error", err);
6348
7440
  }
6349
7441
  }
7442
+ async onAssetChanged(payload) {
7443
+ if (!payload.workspaceId) return;
7444
+ const index = this.assetMetadataIndexes.get(payload.workspaceId);
7445
+ if (!index) return;
7446
+ try {
7447
+ const result = await index.pullDelta();
7448
+ if (result.applied > 0) {
7449
+ console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
7450
+ }
7451
+ } catch (err) {
7452
+ console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
7453
+ }
7454
+ }
6350
7455
  onWorkspaceFileChanged(payload) {
6351
7456
  if (payload.operation === "delete") {
6352
7457
  this.db.prepare("DELETE FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").run(payload.workspaceId, payload.path);
@@ -6382,6 +7487,28 @@ var Runner = class extends import_node_events3.EventEmitter {
6382
7487
  }
6383
7488
  };
6384
7489
  }
7490
+ /**
7491
+ * Ensure an AssetMetadataIndex exists for the given workspace and pull
7492
+ * delta from cloud. Idempotent — creates the index on first call, reuses
7493
+ * it on subsequent calls. Same cursor-catch-up semantics as WorkspaceMirror.
7494
+ */
7495
+ async syncAssetMetadata(workspaceId) {
7496
+ let index = this.assetMetadataIndexes.get(workspaceId);
7497
+ if (!index) {
7498
+ const stateDir = `${this.paths.root}/${workspaceId}`;
7499
+ index = new AssetMetadataIndex({
7500
+ db: this.db,
7501
+ cloud: this.cloud,
7502
+ workspaceId,
7503
+ workspaceStateDir: stateDir
7504
+ });
7505
+ this.assetMetadataIndexes.set(workspaceId, index);
7506
+ }
7507
+ const result = await index.pullDelta();
7508
+ if (result.applied > 0) {
7509
+ console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
7510
+ }
7511
+ }
6385
7512
  /**
6386
7513
  * SyncWorker FlushFn — pushes local writes to cloud.
6387
7514
  *
@@ -6505,715 +7632,165 @@ function validateStaticHostedAgent(raw) {
6505
7632
  if (!raw || typeof raw !== "object") throw new Error("binding must be a JSON object");
6506
7633
  const obj = raw;
6507
7634
  const profile = obj.profile;
6508
- const capabilities = obj.capabilities;
6509
- if (typeof obj.workspaceId !== "string" || obj.workspaceId.length === 0) throw new Error("workspaceId is required");
6510
- if (typeof obj.imUserId !== "string" || obj.imUserId.length === 0) throw new Error("imUserId is required");
6511
- if (typeof obj.name !== "string" || obj.name.length === 0) throw new Error("name is required");
6512
- if (typeof obj.adapterName !== "string" || obj.adapterName.length === 0) throw new Error("adapterName is required");
6513
- if (!Array.isArray(capabilities) || capabilities.some((v) => typeof v !== "string")) {
6514
- throw new Error("capabilities must be a string array");
6515
- }
6516
- if (!profile || typeof profile !== "object") throw new Error("profile is required");
6517
- if (typeof profile.id !== "string" || profile.id.length === 0) throw new Error("profile.id is required");
6518
- if (typeof profile.name !== "string" || profile.name.length === 0) throw new Error("profile.name is required");
6519
- if (typeof profile.adapterName !== "string" || profile.adapterName.length === 0) {
6520
- throw new Error("profile.adapterName is required");
6521
- }
6522
- if (profile.config !== void 0 && (!profile.config || typeof profile.config !== "object" || Array.isArray(profile.config))) {
6523
- throw new Error("profile.config must be a JSON object");
6524
- }
6525
- return {
6526
- workspaceId: obj.workspaceId,
6527
- imUserId: obj.imUserId,
6528
- name: obj.name,
6529
- adapterName: obj.adapterName,
6530
- capabilities,
6531
- profile: {
6532
- id: profile.id,
6533
- name: profile.name,
6534
- adapterName: profile.adapterName,
6535
- config: profile.config ?? {},
6536
- version: typeof profile.version === "number" && Number.isFinite(profile.version) ? profile.version : 1
6537
- }
6538
- };
6539
- }
6540
- function safeJsonParse(raw) {
6541
- try {
6542
- return JSON.parse(raw);
6543
- } catch {
6544
- return raw;
6545
- }
6546
- }
6547
-
6548
- // src/pair.ts
6549
- var import_node_crypto6 = require("crypto");
6550
- var import_node_os4 = require("os");
6551
- var import_promises = require("timers/promises");
6552
- var import_qrcode = __toESM(require("qrcode"), 1);
6553
- async function pair(opts) {
6554
- const paths = opts.paths ?? resolvePaths();
6555
- if (configExists(paths) && !opts.force) {
6556
- throw new Error(
6557
- `Config already exists at ${paths.configFile}. Pass --force to overwrite, or run \`prismer status\` to inspect.`
6558
- );
6559
- }
6560
- const isLocalOnly = opts.isLocalOnly ?? (() => process.env.LOCAL_ONLY === "1");
6561
- const localOnlyMode = !!opts.asUserEmail;
6562
- if (localOnlyMode && !isLocalOnly()) {
6563
- throw new Error(
6564
- "pair: --as-user requires LOCAL_ONLY=1. Without that gate, this would skip mobile approval and silently mint a key for the named user."
6565
- );
6566
- }
6567
- const { publicKey } = (0, import_node_crypto6.generateKeyPairSync)("ed25519");
6568
- const devicePub = publicKey.export({ format: "der", type: "spki" }).toString("base64");
6569
- const cloud = new CloudClient({
6570
- baseUrl: opts.cloudBaseUrl,
6571
- apiKey: "pending",
6572
- // not used: we pass auth:false
6573
- fetchImpl: opts.fetchImpl
6574
- });
6575
- const offerRes = await cloud.request(
6576
- "POST",
6577
- "/api/im/pair/offer",
6578
- {
6579
- auth: false,
6580
- body: { devicePub, deviceName: opts.deviceName ?? (0, import_node_os4.hostname)() }
6581
- }
6582
- );
6583
- if (!offerRes.ok) {
6584
- throw new Error(`pair: offer failed (${offerRes.status}): ${offerRes.error?.message}`);
6585
- }
6586
- const offer = unwrapEnvelope(offerRes.data);
6587
- if (!offer.nonce || !offer.qrUrl) {
6588
- throw new Error("pair: cloud returned no nonce/qrUrl");
6589
- }
6590
- if (localOnlyMode) {
6591
- const approveRes = await cloud.request(
6592
- "POST",
6593
- "/api/im/pair/local-only-approve",
6594
- {
6595
- auth: false,
6596
- body: { nonce: offer.nonce, asUserEmail: opts.asUserEmail }
6597
- }
6598
- );
6599
- if (!approveRes.ok) {
6600
- throw new Error(
6601
- `pair: local-only-approve failed (${approveRes.status}): ${approveRes.error?.message ?? "unknown"}`
6602
- );
6603
- }
6604
- process.stdout.write(`[pair] LOCAL_ONLY approved as ${opts.asUserEmail} \u2014 no QR shown
6605
- `);
6606
- } else {
6607
- const qrAscii = await import_qrcode.default.toString(offer.qrUrl, { type: "terminal", small: true });
6608
- process.stdout.write(qrAscii);
6609
- process.stdout.write(`
6610
- Scan with Lumin to approve, or open: ${offer.qrUrl}
6611
-
6612
- `);
6613
- opts.onQrReady?.(offer.qrUrl);
6614
- }
6615
- const pollPath = `/api/im/pair/poll/${encodeURIComponent(offer.nonce)}?devicePub=${encodeURIComponent(devicePub)}`;
6616
- const maxAttempts = opts.maxPollAttempts ?? 60;
6617
- const pollIntervalMs = opts.pollIntervalMs ?? 5e3;
6618
- for (let i = 0; i < maxAttempts; i += 1) {
6619
- if (i > 0 || !localOnlyMode) {
6620
- await (0, import_promises.setTimeout)(pollIntervalMs);
6621
- }
6622
- const res = await cloud.request(
6623
- "GET",
6624
- pollPath,
6625
- { auth: false }
6626
- );
6627
- if (res.ok) {
6628
- const body = unwrapEnvelope(res.data);
6629
- if (body.apiKey) {
6630
- const config = {
6631
- api_key: body.apiKey,
6632
- cloud_api_base: opts.cloudBaseUrl,
6633
- daemon_id: newDaemonId()
6634
- };
6635
- saveConfig(config, paths);
6636
- return { config, paths };
6637
- }
6638
- }
6639
- if (res.status === 404 || res.status === 202) {
6640
- continue;
6641
- }
6642
- if (res.status >= 400 && res.status !== 404) {
6643
- throw new Error(`pair: poll failed (${res.status}): ${res.error?.message}`);
6644
- }
6645
- }
6646
- throw new Error("pair: timed out waiting for approval (5 min)");
6647
- }
6648
- function unwrapEnvelope(raw) {
6649
- if (raw && typeof raw === "object" && "ok" in raw) {
6650
- const env = raw;
6651
- if (env.ok && env.data) return env.data;
6652
- }
6653
- return raw;
6654
- }
6655
-
6656
- // src/cli/index.ts
6657
- var import_commander18 = require("commander");
6658
-
6659
- // src/cli/commands/adapter.ts
6660
- var import_node_child_process5 = require("child_process");
6661
- var import_node_fs12 = require("fs");
6662
- var import_node_os5 = require("os");
6663
- var import_node_path8 = require("path");
6664
- var import_commander = require("commander");
6665
-
6666
- // src/cli/util.ts
6667
- var import_node_fs11 = require("fs");
6668
- var import_node_path7 = require("path");
6669
-
6670
- // src/cli/ui.ts
6671
- var fs4 = __toESM(require("fs"), 1);
6672
- var path6 = __toESM(require("path"), 1);
6673
- var import_node_url = require("url");
6674
- var import_meta2 = {};
6675
- var BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
6676
- var COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
6677
- function thisDirname() {
6678
- try {
6679
- return path6.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
6680
- } catch {
6681
- return process.cwd();
6682
- }
6683
- }
6684
- function findIconPath(size = "big") {
6685
- const name = size === "big" ? "icon" : "smallicon";
6686
- const here = thisDirname();
6687
- const candidates = [
6688
- // npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
6689
- path6.resolve(here, "../assets", name),
6690
- // alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
6691
- path6.resolve(here, "../../assets", name),
6692
- // source/typecheck: src/cli/ui.ts → ../../assets
6693
- path6.resolve(here, "../../assets", name),
6694
- // dev mode: cwd happens to be runtime root
6695
- path6.resolve(process.cwd(), "assets", name),
6696
- path6.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
6697
- ];
6698
- for (const candidate of candidates) {
6699
- try {
6700
- if (fs4.existsSync(candidate)) return candidate;
6701
- } catch {
6702
- }
6703
- }
6704
- return null;
6705
- }
6706
- var UI = class {
6707
- mode;
6708
- colorEnabled;
6709
- stream;
6710
- errStream;
6711
- constructor(opts) {
6712
- this.mode = opts?.mode ?? "pretty";
6713
- this.stream = opts?.stream ?? process.stdout;
6714
- this.errStream = opts?.errStream ?? process.stderr;
6715
- if (opts?.color !== void 0) {
6716
- this.colorEnabled = opts.color;
6717
- } else {
6718
- const isTTY = this.stream.isTTY === true;
6719
- const noColor = Boolean(process.env["NO_COLOR"]);
6720
- this.colorEnabled = isTTY && !noColor;
6721
- }
6722
- }
6723
- // ---- Internal color helpers ----
6724
- ansi(open, close, text) {
6725
- if (!this.colorEnabled) return text;
6726
- return `\x1B[${open}m${text}\x1B[${close}m`;
6727
- }
6728
- green(t) {
6729
- return this.ansi(32, 39, t);
6730
- }
6731
- red(t) {
6732
- return this.ansi(31, 39, t);
6733
- }
6734
- yellow(t) {
6735
- return this.ansi(33, 39, t);
6736
- }
6737
- cyan(t) {
6738
- return this.ansi(36, 39, t);
6739
- }
6740
- dim(t) {
6741
- return this.ansi(2, 22, t);
6742
- }
6743
- bold(t) {
6744
- return this.ansi(1, 22, t);
6745
- }
6746
- gray(t) {
6747
- return this.ansi(90, 39, t);
6748
- }
6749
- brandMark() {
6750
- return this.cyan("\u25C7");
6751
- }
6752
- colorBrandLine(line) {
6753
- let out = "";
6754
- for (const ch of line) {
6755
- if (ch === "\u2592") {
6756
- out += this.cyan(ch);
6757
- } else if (ch === "\u2593") {
6758
- out += this.dim(ch);
6759
- } else {
6760
- out += ch;
6761
- }
6762
- }
6763
- return out;
6764
- }
6765
- // ---- Core write helpers ----
6766
- write(text) {
6767
- this.stream.write(text);
6768
- }
6769
- writeErr(text) {
6770
- this.errStream.write(text);
6771
- }
6772
- // ---- Level 1: Header ----
6773
- header(text) {
6774
- if (this.mode === "json") return;
6775
- const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
6776
- this.write(prefix + this.bold(text) + "\n");
6777
- }
6778
- smallHeader(subtitle) {
6779
- if (this.mode === "json" || this.mode === "quiet") return;
6780
- const iconPath = findIconPath("small");
6781
- if (iconPath !== null) {
6782
- try {
6783
- const raw = fs4.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
6784
- for (const line of raw.split("\n")) {
6785
- this.write(this.cyan(line) + "\n");
6786
- }
6787
- } catch {
6788
- this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
6789
- }
6790
- } else {
6791
- this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
6792
- }
6793
- if (subtitle !== void 0 && subtitle.length > 0) {
6794
- this.write(this.dim(" " + subtitle) + "\n");
6795
- }
6796
- this.blank();
6797
- }
6798
- banner(subtitle, opts) {
6799
- if (this.mode === "json" || this.mode === "quiet") return;
6800
- const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
6801
- const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
6802
- const iconPath = findIconPath("big");
6803
- const shouldUseFull = opts?.full === true || width >= 120;
6804
- if (shouldUseFull && iconPath !== null) {
6805
- try {
6806
- const raw = fs4.readFileSync(iconPath, "utf-8");
6807
- const lines = raw.split("\n");
6808
- for (const line of lines) {
6809
- const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
6810
- const stripped = brandedLine.trimEnd();
6811
- if (stripped.length === 0) {
6812
- this.write("\n");
6813
- continue;
6814
- }
6815
- const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
6816
- this.write(this.colorBrandLine(clipped) + "\n");
6817
- }
6818
- } catch {
6819
- this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
6820
- this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
6821
- }
6822
- } else {
6823
- this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
6824
- this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
6825
- }
6826
- if (subtitle !== void 0 && subtitle.length > 0) {
6827
- this.write(this.dim(" " + subtitle) + "\n");
6828
- }
6829
- this.blank();
6830
- }
6831
- // ---- Level 2: Primary data ----
6832
- blank() {
6833
- if (this.mode === "json") return;
6834
- this.write("\n");
6835
- }
6836
- line(text) {
6837
- if (this.mode === "json") return;
6838
- this.write(text + "\n");
6839
- }
6840
- info(text) {
6841
- this.line(text);
6842
- }
6843
- // ---- Level 3: Secondary ----
6844
- secondary(text, indent = 2) {
6845
- if (this.mode === "json") return;
6846
- this.write(" ".repeat(indent) + this.dim(text) + "\n");
6847
- }
6848
- // ---- Level 4: Action tips ----
6849
- tip(text) {
6850
- if (this.mode === "json") return;
6851
- this.write(this.cyan("Tip:") + " " + text + "\n");
6852
- }
6853
- next(text) {
6854
- if (this.mode === "json") return;
6855
- this.write(this.cyan("Next:") + " " + text + "\n");
6856
- }
6857
- // ---- Level 5: Status indicators ----
6858
- ok(text, detail) {
6859
- if (this.mode === "json") return;
6860
- const suffix = detail ? " " + this.dim(detail) : "";
6861
- this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
6862
- }
6863
- success(text, detail) {
6864
- this.ok(text, detail);
6865
- }
6866
- fail(text, detail) {
6867
- if (this.mode === "json") return;
6868
- const suffix = detail ? " " + this.dim(detail) : "";
6869
- this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
6870
- }
6871
- online(text) {
6872
- if (this.mode === "json") return;
6873
- this.write(" " + this.green("\u25CF") + " " + text + "\n");
6874
- }
6875
- offline(text) {
6876
- if (this.mode === "json") return;
6877
- this.write(" " + this.gray("\u25CB") + " " + text + "\n");
6878
- }
6879
- notInstalled(text) {
6880
- if (this.mode === "json") return;
6881
- this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
6882
- }
6883
- pending(text) {
6884
- if (this.mode === "json") return;
6885
- this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
6886
- }
6887
- warn(text, detail) {
6888
- if (this.mode === "json") return;
6889
- const suffix = detail ? " " + this.dim(detail) : "";
6890
- this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
6891
- }
6892
- // ---- Level 6: Error block ----
6893
- error(what, cause, fix) {
6894
- if (this.mode === "json") return;
6895
- this.writeErr(this.red("\u2717") + " " + what + "\n");
6896
- if (cause !== void 0) {
6897
- this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
6898
- }
6899
- if (fix !== void 0) {
6900
- this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
6901
- }
6902
- }
6903
- table(rowsOrOpts, maybeOpts) {
6904
- if (this.mode === "json") return;
6905
- const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
6906
- const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
6907
- if (!opts) throw new Error("table() requires columns");
6908
- const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
6909
- const cols = opts.columns;
6910
- const widths = cols.map((col) => col.length);
6911
- for (const row of rows) {
6912
- cols.forEach((col, i) => {
6913
- const val = row[col] ?? "";
6914
- const w = widths[i] ?? 0;
6915
- if (val.length > w) widths[i] = val.length;
6916
- });
6917
- }
6918
- const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
6919
- if (totalWidth > maxWidth) {
6920
- for (let i = 0; i < rows.length; i++) {
6921
- const row = rows[i];
6922
- if (!row) continue;
6923
- for (const col of cols) {
6924
- const val = row[col] ?? "";
6925
- this.write(" " + this.bold(col + ":") + " " + val + "\n");
6926
- }
6927
- if (i < rows.length - 1) this.write("\n");
6928
- }
6929
- return;
6930
- }
6931
- const header = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
6932
- this.write(" " + this.dim(header) + "\n");
6933
- for (const row of rows) {
6934
- const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
6935
- this.write(" " + line + "\n");
6936
- }
6937
- }
6938
- // ---- Spinner ----
6939
- spinner(text) {
6940
- if (this.mode === "quiet" || this.mode === "json") {
6941
- return {
6942
- update() {
6943
- },
6944
- stop() {
6945
- }
6946
- };
6947
- }
6948
- const isTTY = this.stream.isTTY === true;
6949
- if (!isTTY || !this.colorEnabled) {
6950
- this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
6951
- return {
6952
- update: (t) => {
6953
- this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
6954
- },
6955
- stop: (final) => {
6956
- if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
6957
- }
6958
- };
6959
- }
6960
- let current = text;
6961
- let frameIdx = 0;
6962
- let stopped = false;
6963
- const write = this.write.bind(this);
6964
- const colorFn = this.yellow.bind(this);
6965
- const greenFn = this.green.bind(this);
6966
- function renderFrame() {
6967
- const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
6968
- const line = " " + colorFn(frame) + " " + current;
6969
- write("\r" + line);
6970
- frameIdx++;
6971
- }
6972
- renderFrame();
6973
- const timer = setInterval(renderFrame, 80);
6974
- return {
6975
- update(t) {
6976
- if (stopped) return;
6977
- current = t;
6978
- },
6979
- stop(final) {
6980
- if (stopped) return;
6981
- stopped = true;
6982
- clearInterval(timer);
6983
- write("\r\x1B[2K");
6984
- if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
6985
- }
6986
- };
6987
- }
6988
- // ---- Progress bar ----
6989
- progress(text, total) {
6990
- if (this.mode === "quiet" || this.mode === "json") {
6991
- return {
6992
- update() {
6993
- },
6994
- stop() {
6995
- }
6996
- };
6997
- }
6998
- const isTTY = this.stream.isTTY === true;
6999
- const start = Date.now();
7000
- const write = this.write.bind(this);
7001
- const colorFn = this.cyan.bind(this);
7002
- const dimFn = this.dim.bind(this);
7003
- const greenFn = this.green.bind(this);
7004
- let last = 0;
7005
- let lastDetail = "";
7006
- let stopped = false;
7007
- const render = () => {
7008
- if (stopped) return;
7009
- const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
7010
- const pct = Math.floor(frac * 100);
7011
- const width = 20;
7012
- const filled = Math.floor(frac * width);
7013
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
7014
- const elapsed = (Date.now() - start) / 1e3;
7015
- const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
7016
- const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
7017
- const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
7018
- const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
7019
- if (isTTY && this.colorEnabled) {
7020
- write("\r\x1B[2K" + line);
7021
- } else {
7022
- write(line + "\n");
7023
- }
7024
- };
7025
- render();
7026
- return {
7027
- update: (current, detail) => {
7028
- if (stopped) return;
7029
- last = current;
7030
- if (detail !== void 0) lastDetail = detail;
7031
- render();
7032
- },
7033
- stop: (final) => {
7034
- if (stopped) return;
7035
- stopped = true;
7036
- if (isTTY && this.colorEnabled) write("\r\x1B[2K");
7037
- if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
7038
- }
7039
- };
7040
- }
7041
- // ---- JSON output ----
7042
- json(payload, opts) {
7043
- const indent = opts?.pretty ? 2 : void 0;
7044
- this.write(JSON.stringify(payload, null, indent) + "\n");
7045
- }
7046
- result(pretty, jsonPayload) {
7047
- if (this.mode === "pretty") {
7048
- pretty();
7049
- } else {
7050
- this.json(jsonPayload);
7051
- }
7052
- }
7053
- };
7054
- var _ui = null;
7055
- function getUI() {
7056
- if (!_ui) _ui = new UI();
7057
- return _ui;
7058
- }
7059
- function setUI(ui) {
7060
- _ui = ui;
7061
- }
7062
- function applyCommonFlags(argv) {
7063
- let mode = "pretty";
7064
- const isTTY = process.stdout.isTTY === true;
7065
- const noColorEnv = Boolean(process.env["NO_COLOR"]);
7066
- let color2 = isTTY && !noColorEnv;
7067
- const rest = [];
7068
- for (const arg of argv) {
7069
- switch (arg) {
7070
- case "--no-color":
7071
- color2 = false;
7072
- break;
7073
- case "--color":
7074
- color2 = true;
7075
- break;
7076
- case "--json":
7077
- case "--pretty-json":
7078
- mode = "json";
7079
- if (arg === "--json") rest.push(arg);
7080
- break;
7081
- case "--quiet":
7082
- mode = "quiet";
7083
- break;
7084
- default:
7085
- rest.push(arg);
7086
- }
7087
- }
7088
- return { mode, color: color2, restArgv: rest };
7089
- }
7090
-
7091
- // src/cli/util.ts
7092
- var DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
7093
- var ANSI = {
7094
- reset: "\x1B[0m",
7095
- bold: "\x1B[1m",
7096
- dim: "\x1B[2m",
7097
- cyan: "\x1B[36m",
7098
- green: "\x1B[32m",
7099
- yellow: "\x1B[33m",
7100
- red: "\x1B[31m"
7101
- };
7102
- function color(kind, text) {
7103
- if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
7104
- return `${ANSI[kind]}${text}${ANSI.reset}`;
7105
- }
7106
- function printJson(v) {
7107
- getUI().json(v, { pretty: true });
7108
- }
7109
- function exitWithError(message, opts) {
7110
- const o = typeof opts === "number" ? { exitCode: opts } : opts ?? {};
7111
- const exitCode = o.exitCode ?? 1;
7112
- const ui = getUI();
7113
- if (ui.mode === "json") {
7114
- const payload = {
7115
- ok: false,
7116
- error: { code: o.code ?? "cli_error", message },
7117
- ...o.details ? { details: o.details } : {}
7118
- };
7119
- ui.json(payload, { pretty: true });
7120
- } else {
7121
- process.stderr.write(`Error: ${message}
7122
- `);
7123
- }
7124
- process.exit(exitCode);
7125
- }
7126
- function normalizeCloudUrl(input) {
7127
- const raw = input.trim();
7128
- if (!raw) throw new Error("Cloud URL is empty.");
7129
- if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(raw)) {
7130
- let parsed;
7131
- try {
7132
- parsed = new URL(raw);
7133
- } catch {
7134
- throw new Error(`Invalid --cloud URL: ${raw}`);
7135
- }
7136
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
7137
- throw new Error(`Invalid --cloud URL scheme: ${parsed.protocol} (expected http:// or https://)`);
7138
- }
7139
- return raw.replace(/\/$/, "");
7635
+ const capabilities = obj.capabilities;
7636
+ if (typeof obj.workspaceId !== "string" || obj.workspaceId.length === 0) throw new Error("workspaceId is required");
7637
+ if (typeof obj.imUserId !== "string" || obj.imUserId.length === 0) throw new Error("imUserId is required");
7638
+ if (typeof obj.name !== "string" || obj.name.length === 0) throw new Error("name is required");
7639
+ if (typeof obj.adapterName !== "string" || obj.adapterName.length === 0) throw new Error("adapterName is required");
7640
+ if (!Array.isArray(capabilities) || capabilities.some((v) => typeof v !== "string")) {
7641
+ throw new Error("capabilities must be a string array");
7140
7642
  }
7141
- if (!/^[A-Za-z0-9.\-_:[\]/]+$/.test(raw)) {
7142
- throw new Error(`Invalid --cloud URL: ${raw} (must be http://\u2026 or https://\u2026 or host:port)`);
7643
+ if (!profile || typeof profile !== "object") throw new Error("profile is required");
7644
+ if (typeof profile.id !== "string" || profile.id.length === 0) throw new Error("profile.id is required");
7645
+ if (typeof profile.name !== "string" || profile.name.length === 0) throw new Error("profile.name is required");
7646
+ if (typeof profile.adapterName !== "string" || profile.adapterName.length === 0) {
7647
+ throw new Error("profile.adapterName is required");
7143
7648
  }
7144
- const candidate = `http://${raw}`;
7145
- try {
7146
- new URL(candidate);
7147
- } catch {
7148
- throw new Error(`Invalid --cloud URL: ${raw}`);
7649
+ if (profile.config !== void 0 && (!profile.config || typeof profile.config !== "object" || Array.isArray(profile.config))) {
7650
+ throw new Error("profile.config must be a JSON object");
7149
7651
  }
7150
- return candidate.replace(/\/$/, "");
7151
- }
7152
- function runAction(fn, opts = {}) {
7153
- return async (...args) => {
7154
- try {
7155
- await fn(...args);
7156
- } catch (err) {
7157
- const raw = err instanceof Error ? err.message : String(err);
7158
- const message = opts.sanitize ? opts.sanitize(raw) : raw;
7159
- exitWithError(message, { code: opts.code });
7652
+ return {
7653
+ workspaceId: obj.workspaceId,
7654
+ imUserId: obj.imUserId,
7655
+ name: obj.name,
7656
+ adapterName: obj.adapterName,
7657
+ capabilities,
7658
+ profile: {
7659
+ id: profile.id,
7660
+ name: profile.name,
7661
+ adapterName: profile.adapterName,
7662
+ config: profile.config ?? {},
7663
+ version: typeof profile.version === "number" && Number.isFinite(profile.version) ? profile.version : 1
7160
7664
  }
7161
7665
  };
7162
7666
  }
7163
- function printBanner(opts = {}) {
7164
- const ui = getUI();
7165
- if (opts.compact) {
7166
- ui.smallHeader("Runtime CLI v1.9.3");
7167
- return;
7667
+ function safeJsonParse(raw) {
7668
+ try {
7669
+ return JSON.parse(raw);
7670
+ } catch {
7671
+ return raw;
7168
7672
  }
7169
- ui.banner("Runtime CLI v1.9.3", { full: true });
7170
- }
7171
- function ok(label, detail) {
7172
- getUI().ok(label, detail);
7173
- }
7174
- function warn(label, detail) {
7175
- getUI().warn(label, detail);
7176
- }
7177
- function fail2(label, detail) {
7178
- getUI().fail(label, detail);
7179
- }
7180
- function tip(command, detail) {
7181
- const text = detail ? `${command} ${detail}` : command;
7182
- getUI().tip(text);
7183
- }
7184
- function pidFilePath(paths) {
7185
- return (0, import_node_path7.join)(paths.root, "daemon.pid");
7186
- }
7187
- function writePidFile(paths, pid) {
7188
- (0, import_node_fs11.writeFileSync)(pidFilePath(paths), `${pid}
7189
- `, "utf8");
7190
- }
7191
- function readPidFile(paths) {
7192
- const p = pidFilePath(paths);
7193
- if (!(0, import_node_fs11.existsSync)(p)) return void 0;
7194
- const raw = (0, import_node_fs11.readFileSync)(p, "utf8").trim();
7195
- const pid = Number.parseInt(raw, 10);
7196
- return Number.isFinite(pid) ? pid : void 0;
7197
7673
  }
7198
- function clearPidFile(paths) {
7199
- const p = pidFilePath(paths);
7200
- if ((0, import_node_fs11.existsSync)(p)) {
7201
- try {
7202
- (0, import_node_fs11.unlinkSync)(p);
7203
- } catch {
7674
+
7675
+ // src/pair.ts
7676
+ var import_node_crypto5 = require("crypto");
7677
+ var import_node_os5 = require("os");
7678
+ var import_promises = require("timers/promises");
7679
+ var import_qrcode = __toESM(require("qrcode"), 1);
7680
+ async function pair(opts) {
7681
+ const paths = opts.paths ?? resolvePaths();
7682
+ if (configExists(paths) && !opts.force) {
7683
+ throw new Error(
7684
+ `Config already exists at ${paths.configFile}. Pass --force to overwrite, or run \`prismer status\` to inspect.`
7685
+ );
7686
+ }
7687
+ const isLocalOnly = opts.isLocalOnly ?? (() => process.env.LOCAL_ONLY === "1");
7688
+ const localOnlyMode = !!opts.asUserEmail;
7689
+ if (localOnlyMode && !isLocalOnly()) {
7690
+ throw new Error(
7691
+ "pair: --as-user requires LOCAL_ONLY=1. Without that gate, this would skip mobile approval and silently mint a key for the named user."
7692
+ );
7693
+ }
7694
+ const { publicKey } = (0, import_node_crypto5.generateKeyPairSync)("ed25519");
7695
+ const devicePub = publicKey.export({ format: "der", type: "spki" }).toString("base64");
7696
+ const cloud = new CloudClient({
7697
+ baseUrl: opts.cloudBaseUrl,
7698
+ apiKey: "pending",
7699
+ // not used: we pass auth:false
7700
+ fetchImpl: opts.fetchImpl
7701
+ });
7702
+ const offerRes = await cloud.request(
7703
+ "POST",
7704
+ "/api/im/pair/offer",
7705
+ {
7706
+ auth: false,
7707
+ body: { devicePub, deviceName: opts.deviceName ?? (0, import_node_os5.hostname)() }
7708
+ }
7709
+ );
7710
+ if (!offerRes.ok) {
7711
+ throw new Error(`pair: offer failed (${offerRes.status}): ${offerRes.error?.message}`);
7712
+ }
7713
+ const offer = unwrapEnvelope(offerRes.data);
7714
+ if (!offer.nonce || !offer.qrUrl) {
7715
+ throw new Error("pair: cloud returned no nonce/qrUrl");
7716
+ }
7717
+ if (localOnlyMode) {
7718
+ const approveRes = await cloud.request(
7719
+ "POST",
7720
+ "/api/im/pair/local-only-approve",
7721
+ {
7722
+ auth: false,
7723
+ body: { nonce: offer.nonce, asUserEmail: opts.asUserEmail }
7724
+ }
7725
+ );
7726
+ if (!approveRes.ok) {
7727
+ throw new Error(
7728
+ `pair: local-only-approve failed (${approveRes.status}): ${approveRes.error?.message ?? "unknown"}`
7729
+ );
7730
+ }
7731
+ process.stdout.write(`[pair] LOCAL_ONLY approved as ${opts.asUserEmail} \u2014 no QR shown
7732
+ `);
7733
+ } else {
7734
+ const qrAscii = await import_qrcode.default.toString(offer.qrUrl, { type: "terminal", small: true });
7735
+ process.stdout.write(qrAscii);
7736
+ process.stdout.write(`
7737
+ Scan with Lumin to approve, or open: ${offer.qrUrl}
7738
+
7739
+ `);
7740
+ opts.onQrReady?.(offer.qrUrl);
7741
+ }
7742
+ const pollPath = `/api/im/pair/poll/${encodeURIComponent(offer.nonce)}?devicePub=${encodeURIComponent(devicePub)}`;
7743
+ const maxAttempts = opts.maxPollAttempts ?? 60;
7744
+ const pollIntervalMs = opts.pollIntervalMs ?? 5e3;
7745
+ for (let i = 0; i < maxAttempts; i += 1) {
7746
+ if (i > 0 || !localOnlyMode) {
7747
+ await (0, import_promises.setTimeout)(pollIntervalMs);
7748
+ }
7749
+ const res = await cloud.request(
7750
+ "GET",
7751
+ pollPath,
7752
+ { auth: false }
7753
+ );
7754
+ if (res.ok) {
7755
+ const body = unwrapEnvelope(res.data);
7756
+ if (body.apiKey) {
7757
+ const config = {
7758
+ api_key: body.apiKey,
7759
+ cloud_api_base: opts.cloudBaseUrl,
7760
+ daemon_id: newDaemonId()
7761
+ };
7762
+ saveConfig(config, paths);
7763
+ return { config, paths };
7764
+ }
7765
+ }
7766
+ if (res.status === 404 || res.status === 202) {
7767
+ continue;
7768
+ }
7769
+ if (res.status >= 400 && res.status !== 404) {
7770
+ throw new Error(`pair: poll failed (${res.status}): ${res.error?.message}`);
7204
7771
  }
7205
7772
  }
7773
+ throw new Error("pair: timed out waiting for approval (5 min)");
7206
7774
  }
7207
- function pidAlive(pid) {
7208
- try {
7209
- process.kill(pid, 0);
7210
- return true;
7211
- } catch {
7212
- return false;
7775
+ function unwrapEnvelope(raw) {
7776
+ if (raw && typeof raw === "object" && "ok" in raw) {
7777
+ const env = raw;
7778
+ if (env.ok && env.data) return env.data;
7213
7779
  }
7780
+ return raw;
7214
7781
  }
7215
7782
 
7783
+ // src/cli/index.ts
7784
+ var import_commander18 = require("commander");
7785
+
7216
7786
  // src/cli/commands/adapter.ts
7787
+ var import_node_child_process5 = require("child_process");
7788
+ var import_node_fs13 = require("fs");
7789
+ var import_node_os6 = require("os");
7790
+ var import_node_path9 = require("path");
7791
+ var import_commander = require("commander");
7792
+ init_util();
7793
+ init_ui();
7217
7794
  var BUILTIN_ADAPTERS = [hermesAdapter, claudeCodeAdapter, openclawAdapter, codexAdapter];
7218
7795
  var INSTALL_SPECS = {
7219
7796
  "claude-code": {
@@ -7223,7 +7800,7 @@ var INSTALL_SPECS = {
7223
7800
  binary: "claude",
7224
7801
  hint: "Set ANTHROPIC_API_KEY in your shell profile, or rely on Claude Code OAuth login. Run `claude login` to sign in.",
7225
7802
  authHints: ["ANTHROPIC_API_KEY or Claude Code OAuth login (`claude login`)"],
7226
- hookTarget: { path: (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".claude", "hooks.json"), kind: "json-file" }
7803
+ hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".claude", "hooks.json"), kind: "json-file" }
7227
7804
  },
7228
7805
  openclaw: {
7229
7806
  name: "openclaw",
@@ -7232,7 +7809,7 @@ var INSTALL_SPECS = {
7232
7809
  binary: "openclaw",
7233
7810
  hint: "OpenClaw runs as a gateway. Configure ~/.openclaw/openclaw.json and start it with `openclaw gateway` before tasks dispatch.",
7234
7811
  authHints: ["~/.openclaw/openclaw.json gateway.auth.bearerTokens", "Prismer daemon api_key"],
7235
- hookTarget: { path: (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".openclaw", "hooks"), kind: "directory" }
7812
+ hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".openclaw", "hooks"), kind: "directory" }
7236
7813
  },
7237
7814
  codex: {
7238
7815
  name: "codex",
@@ -7241,7 +7818,7 @@ var INSTALL_SPECS = {
7241
7818
  binary: "codex",
7242
7819
  hint: "Set OPENAI_API_KEY in your shell profile. Verify with `codex --help`.",
7243
7820
  authHints: ["OPENAI_API_KEY or Codex CLI account login"],
7244
- hookTarget: { path: (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".codex", "hooks.json"), kind: "json-file" }
7821
+ hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".codex", "hooks.json"), kind: "json-file" }
7245
7822
  },
7246
7823
  hermes: {
7247
7824
  name: "hermes",
@@ -7250,7 +7827,7 @@ var INSTALL_SPECS = {
7250
7827
  binary: "hermes",
7251
7828
  hint: "Hermes runs as a long-lived HTTP gateway in your own Python venv. Start with `hermes -p <profile> gateway` after configuring ~/.hermes/.env.",
7252
7829
  authHints: ["~/.hermes/.env API_SERVER_KEY", "Hermes profile provider credentials"],
7253
- hookTarget: { path: (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".hermes", "hooks.json"), kind: "json-file" }
7830
+ hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".hermes", "hooks.json"), kind: "json-file" }
7254
7831
  }
7255
7832
  };
7256
7833
  function buildAdapterCommand() {
@@ -7465,15 +8042,15 @@ function runHooks(spec, opts) {
7465
8042
  }
7466
8043
  let backup;
7467
8044
  if (planned.kind === "directory") {
7468
- (0, import_node_fs12.mkdirSync)(planned.path, { recursive: true });
7469
- const markerPath = (0, import_node_path8.join)(planned.path, "prismer.json");
8045
+ (0, import_node_fs13.mkdirSync)(planned.path, { recursive: true });
8046
+ const markerPath = (0, import_node_path9.join)(planned.path, "prismer.json");
7470
8047
  backup = backupIfExists(markerPath);
7471
- (0, import_node_fs12.writeFileSync)(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
8048
+ (0, import_node_fs13.writeFileSync)(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
7472
8049
  } else {
7473
- (0, import_node_fs12.mkdirSync)((0, import_node_path8.dirname)(planned.path), { recursive: true });
8050
+ (0, import_node_fs13.mkdirSync)((0, import_node_path9.dirname)(planned.path), { recursive: true });
7474
8051
  backup = backupIfExists(planned.path);
7475
8052
  const merged = mergeHookJson(planned.path, spec);
7476
- (0, import_node_fs12.writeFileSync)(planned.path, JSON.stringify(merged, null, 2) + "\n", "utf8");
8053
+ (0, import_node_fs13.writeFileSync)(planned.path, JSON.stringify(merged, null, 2) + "\n", "utf8");
7477
8054
  }
7478
8055
  return {
7479
8056
  ok: true,
@@ -7540,23 +8117,23 @@ function inspectAuthEnv(spec) {
7540
8117
  hints: spec.authHints ?? []
7541
8118
  };
7542
8119
  case "hermes": {
7543
- const envFile = (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".hermes", ".env");
8120
+ const envFile = (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".hermes", ".env");
7544
8121
  return {
7545
- ok: (0, import_node_fs12.existsSync)(envFile) || Boolean(process.env.HERMES_API_KEY || process.env.API_SERVER_KEY),
8122
+ ok: (0, import_node_fs13.existsSync)(envFile) || Boolean(process.env.HERMES_API_KEY || process.env.API_SERVER_KEY),
7546
8123
  present: [
7547
8124
  ...["HERMES_API_KEY", "API_SERVER_KEY"].filter((k) => Boolean(process.env[k])),
7548
- ...(0, import_node_fs12.existsSync)(envFile) ? [envFile] : []
8125
+ ...(0, import_node_fs13.existsSync)(envFile) ? [envFile] : []
7549
8126
  ],
7550
8127
  hints: spec.authHints ?? []
7551
8128
  };
7552
8129
  }
7553
8130
  case "openclaw": {
7554
- const cfgFile = (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".openclaw", "openclaw.json");
8131
+ const cfgFile = (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".openclaw", "openclaw.json");
7555
8132
  return {
7556
- ok: (0, import_node_fs12.existsSync)(cfgFile) || Boolean(process.env.OPENCLAW_API_KEY),
8133
+ ok: (0, import_node_fs13.existsSync)(cfgFile) || Boolean(process.env.OPENCLAW_API_KEY),
7557
8134
  present: [
7558
8135
  ...["OPENCLAW_API_KEY"].filter((k) => Boolean(process.env[k])),
7559
- ...(0, import_node_fs12.existsSync)(cfgFile) ? [cfgFile] : []
8136
+ ...(0, import_node_fs13.existsSync)(cfgFile) ? [cfgFile] : []
7560
8137
  ],
7561
8138
  hints: spec.authHints ?? []
7562
8139
  };
@@ -7571,32 +8148,32 @@ function inspectHook(spec) {
7571
8148
  return { supported: false, markerPresent: false };
7572
8149
  }
7573
8150
  if (spec.name === "openclaw") {
7574
- const jsonPath = (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".openclaw", "hooks.json");
7575
- const markerPath = (0, import_node_path8.join)(target.path, "prismer.json");
7576
- const jsonMarkerPresent = (0, import_node_fs12.existsSync)(jsonPath) && fileContainsPrismerMarker(jsonPath);
7577
- const dirMarkerPresent = (0, import_node_fs12.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath);
8151
+ const jsonPath = (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".openclaw", "hooks.json");
8152
+ const markerPath = (0, import_node_path9.join)(target.path, "prismer.json");
8153
+ const jsonMarkerPresent = (0, import_node_fs13.existsSync)(jsonPath) && fileContainsPrismerMarker(jsonPath);
8154
+ const dirMarkerPresent = (0, import_node_fs13.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath);
7578
8155
  return {
7579
8156
  supported: true,
7580
8157
  kind: "json-file-or-directory",
7581
8158
  path: target.path,
7582
8159
  jsonPath,
7583
- exists: (0, import_node_fs12.existsSync)(target.path) || (0, import_node_fs12.existsSync)(jsonPath),
8160
+ exists: (0, import_node_fs13.existsSync)(target.path) || (0, import_node_fs13.existsSync)(jsonPath),
7584
8161
  markerPath,
7585
8162
  markerPresent: jsonMarkerPresent || dirMarkerPresent
7586
8163
  };
7587
8164
  }
7588
8165
  if (target.kind === "directory") {
7589
- const markerPath = (0, import_node_path8.join)(target.path, "prismer.json");
8166
+ const markerPath = (0, import_node_path9.join)(target.path, "prismer.json");
7590
8167
  return {
7591
8168
  supported: true,
7592
8169
  kind: target.kind,
7593
8170
  path: target.path,
7594
- exists: (0, import_node_fs12.existsSync)(target.path),
8171
+ exists: (0, import_node_fs13.existsSync)(target.path),
7595
8172
  markerPath,
7596
- markerPresent: (0, import_node_fs12.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath)
8173
+ markerPresent: (0, import_node_fs13.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath)
7597
8174
  };
7598
8175
  }
7599
- const exists = (0, import_node_fs12.existsSync)(target.path);
8176
+ const exists = (0, import_node_fs13.existsSync)(target.path);
7600
8177
  if (!exists) {
7601
8178
  return {
7602
8179
  supported: true,
@@ -7632,14 +8209,14 @@ function prismerHookMarker(spec) {
7632
8209
  }
7633
8210
  function mergeHookJson(path7, spec) {
7634
8211
  const marker = prismerHookMarker(spec);
7635
- if (!(0, import_node_fs12.existsSync)(path7)) {
8212
+ if (!(0, import_node_fs13.existsSync)(path7)) {
7636
8213
  return {
7637
8214
  prismer: marker
7638
8215
  };
7639
8216
  }
7640
8217
  let parsed;
7641
8218
  try {
7642
- parsed = JSON.parse((0, import_node_fs12.readFileSync)(path7, "utf8"));
8219
+ parsed = JSON.parse((0, import_node_fs13.readFileSync)(path7, "utf8"));
7643
8220
  } catch (err) {
7644
8221
  throw new Error(`Cannot parse ${path7} as JSON: ${err.message}`);
7645
8222
  }
@@ -7663,17 +8240,17 @@ function mergeHookJson(path7, spec) {
7663
8240
  return next;
7664
8241
  }
7665
8242
  function backupIfExists(path7) {
7666
- if (!(0, import_node_fs12.existsSync)(path7)) return null;
8243
+ if (!(0, import_node_fs13.existsSync)(path7)) return null;
7667
8244
  const suffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7668
8245
  const backup = `${path7}.bak.${suffix}`;
7669
- const stat = (0, import_node_fs12.statSync)(path7);
8246
+ const stat = (0, import_node_fs13.statSync)(path7);
7670
8247
  if (stat.isDirectory()) return null;
7671
- (0, import_node_fs12.copyFileSync)(path7, backup);
8248
+ (0, import_node_fs13.copyFileSync)(path7, backup);
7672
8249
  return backup;
7673
8250
  }
7674
8251
  function fileContainsPrismerMarker(path7) {
7675
8252
  try {
7676
- return (0, import_node_fs12.readFileSync)(path7, "utf8").includes("prismer-daemon-runtime");
8253
+ return (0, import_node_fs13.readFileSync)(path7, "utf8").includes("prismer-daemon-runtime");
7677
8254
  } catch {
7678
8255
  return false;
7679
8256
  }
@@ -7736,6 +8313,8 @@ function clearInstallInConfig(name) {
7736
8313
  // src/cli/commands/agent.ts
7737
8314
  var import_commander2 = require("commander");
7738
8315
  var import_node_child_process6 = require("child_process");
8316
+ init_util();
8317
+ init_ui();
7739
8318
  var ADAPTER_BINARY = {
7740
8319
  "claude-code": "claude",
7741
8320
  codex: "codex",
@@ -8115,8 +8694,10 @@ function whichBinary2(bin) {
8115
8694
 
8116
8695
  // src/cli/commands/asset.ts
8117
8696
  var import_commander3 = require("commander");
8118
- var import_node_fs13 = require("fs");
8119
- var import_node_path9 = require("path");
8697
+ var import_node_fs14 = require("fs");
8698
+ var import_node_path10 = require("path");
8699
+ init_util();
8700
+ init_ui();
8120
8701
  function buildAssetCommand() {
8121
8702
  const cmd = new import_commander3.Command("asset").description("Inspect IM assets");
8122
8703
  cmd.command("list").description("List assets for a workspace, optionally filtered by task").option("--workspace-id <id>", "Workspace id").option("--task-id <id>", "Task id filter").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
@@ -8187,9 +8768,9 @@ function mkCloud() {
8187
8768
  return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
8188
8769
  }
8189
8770
  async function uploadAsset(file, opts) {
8190
- if (!(0, import_node_fs13.existsSync)(file)) exitWithError(`file not found: ${file}`);
8771
+ if (!(0, import_node_fs14.existsSync)(file)) exitWithError(`file not found: ${file}`);
8191
8772
  const cfg = loadConfig(resolvePaths());
8192
- const bytes = (0, import_node_fs13.readFileSync)(file);
8773
+ const bytes = (0, import_node_fs14.readFileSync)(file);
8193
8774
  const metadata = parseMetadata(opts.metadata);
8194
8775
  if (opts.taskId && metadata.taskId === void 0) metadata.taskId = opts.taskId;
8195
8776
  if (opts.containerId && metadata.containerId === void 0) metadata.containerId = opts.containerId;
@@ -8199,7 +8780,7 @@ async function uploadAsset(file, opts) {
8199
8780
  if (opts.agentId) form.set("sourceAgentImUserId", opts.agentId);
8200
8781
  if (opts.taskId) form.set("sourceTaskId", opts.taskId);
8201
8782
  if (Object.keys(metadata).length > 0) form.set("metadata", JSON.stringify(metadata));
8202
- form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), (0, import_node_path9.basename)(file));
8783
+ form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), (0, import_node_path10.basename)(file));
8203
8784
  const url = `${cfg.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
8204
8785
  let res;
8205
8786
  try {
@@ -8225,7 +8806,7 @@ async function downloadAsset(assetId, outPath) {
8225
8806
  exitWithError(`asset download failed (${res.status}): ${errorMessage(body)}`);
8226
8807
  }
8227
8808
  const bytes = Buffer.from(await res.arrayBuffer());
8228
- (0, import_node_fs13.writeFileSync)(outPath, bytes);
8809
+ (0, import_node_fs14.writeFileSync)(outPath, bytes);
8229
8810
  }
8230
8811
  function parseMetadata(raw) {
8231
8812
  if (!raw) return {};
@@ -8313,6 +8894,7 @@ function stringField(obj, key) {
8313
8894
 
8314
8895
  // src/cli/commands/banner.ts
8315
8896
  var import_commander4 = require("commander");
8897
+ init_util();
8316
8898
  function buildBannerCommand() {
8317
8899
  return new import_commander4.Command("banner").description("Show the Prismer runtime CLI banner").option("--compact", "Show a single-line banner").option("--json", "Accept --json for global flag compatibility (banner is suppressed in JSON mode)").action((opts) => {
8318
8900
  printBanner({ compact: opts.compact });
@@ -8321,6 +8903,8 @@ function buildBannerCommand() {
8321
8903
 
8322
8904
  // src/cli/commands/chat.ts
8323
8905
  var import_commander5 = require("commander");
8906
+ init_util();
8907
+ init_ui();
8324
8908
  function buildChatCommand() {
8325
8909
  const cmd = new import_commander5.Command("chat").description("Use IM chat and group APIs");
8326
8910
  cmd.command("me").description("Show the current IM identity").option("--json", "Print raw JSON response").action(async (opts) => {
@@ -8495,6 +9079,8 @@ function sanitizeError(message) {
8495
9079
 
8496
9080
  // src/cli/commands/config.ts
8497
9081
  var import_commander6 = require("commander");
9082
+ init_util();
9083
+ init_ui();
8498
9084
  var SETTABLE_KEYS = ["cloud_api_base", "api_key", "daemon_id"];
8499
9085
  function redactApiKey(key) {
8500
9086
  if (!key.startsWith("sk-prismer-")) return "***";
@@ -8590,6 +9176,8 @@ function buildConfigCommand() {
8590
9176
 
8591
9177
  // src/cli/commands/cookbook.ts
8592
9178
  var import_commander7 = require("commander");
9179
+ init_util();
9180
+ init_ui();
8593
9181
  function buildCookbookCommand() {
8594
9182
  const cmd = new import_commander7.Command("cookbook").description("Run CLI-only 54release MVP regression suites");
8595
9183
  cmd.command("run").description("Run one or more cookbook smoke suites using the configured API key").option("--suite <name>", "status|im|task|group|asset|sandbox|all, comma-separated", "all").option("--workspace-id <id>", "Workspace id for workspace-scoped suites").option("--agent-id <id>", "Agent IM user id for task create smoke").option("--group-id <id>", "Group/conversation id for group message history smoke").option("--sandbox-id <id>", "Sandbox id for sandbox status smoke").option("--prompt <text>", "Prompt for optional task create smoke").option("--timeout-ms <ms>", "Request/task timeout", parsePositiveInt2, 6e4).option("--strict", "Treat skipped optional checks as failure").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
@@ -8847,9 +9435,11 @@ function parsePositiveInt2(value) {
8847
9435
  // src/cli/commands/daemon.ts
8848
9436
  var import_commander8 = require("commander");
8849
9437
  var import_node_child_process7 = require("child_process");
8850
- var import_node_fs14 = require("fs");
9438
+ var import_node_fs15 = require("fs");
8851
9439
  var import_promises2 = require("timers/promises");
8852
- var import_node_path10 = require("path");
9440
+ var import_node_path11 = require("path");
9441
+ init_util();
9442
+ init_ui();
8853
9443
  function buildDaemonCommand() {
8854
9444
  const cmd = new import_commander8.Command("daemon").description("Manage the prismer daemon process");
8855
9445
  cmd.command("start").description("Start the daemon in the background (use --foreground for Docker/systemd)").option("--port <port>", "Local server port (default 3210)", (v) => Number.parseInt(v, 10)).option("--no-local-server", "Skip starting the local 127.0.0.1 server").option("--foreground", "Run in the foreground instead of daemonizing").option("--json", "Output machine-readable JSON").action(async (opts) => {
@@ -8921,9 +9511,9 @@ function buildDaemonCommand() {
8921
9511
  if (existingPid && pidAlive(existingPid)) {
8922
9512
  exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
8923
9513
  }
8924
- if (!(0, import_node_fs14.existsSync)(paths.logsDir)) (0, import_node_fs14.mkdirSync)(paths.logsDir, { recursive: true });
8925
- const logFile = (0, import_node_path10.join)(paths.logsDir, "daemon.log");
8926
- const fd = (0, import_node_fs14.openSync)(logFile, "a");
9514
+ if (!(0, import_node_fs15.existsSync)(paths.logsDir)) (0, import_node_fs15.mkdirSync)(paths.logsDir, { recursive: true });
9515
+ const logFile = (0, import_node_path11.join)(paths.logsDir, "daemon.log");
9516
+ const fd = (0, import_node_fs15.openSync)(logFile, "a");
8927
9517
  const args = [process.argv[1], "daemon", "run"];
8928
9518
  if (opts.port) args.push("--port", String(opts.port));
8929
9519
  if (opts.localServer === false) args.push("--no-local-server");
@@ -9006,8 +9596,8 @@ function buildDaemonCommand() {
9006
9596
  });
9007
9597
  cmd.command("logs").description("Show daemon logs").option("--tail <n>", "Number of lines to show", (v) => Number.parseInt(v, 10), 80).option("--follow", "Follow log output").option("--json", "Accept --json for global flag compatibility (raw log bytes are streamed)").action(async (opts) => {
9008
9598
  const paths = resolvePaths();
9009
- const logFile = (0, import_node_path10.join)(paths.logsDir, "daemon.log");
9010
- if (!(0, import_node_fs14.existsSync)(logFile)) {
9599
+ const logFile = (0, import_node_path11.join)(paths.logsDir, "daemon.log");
9600
+ if (!(0, import_node_fs15.existsSync)(logFile)) {
9011
9601
  exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
9012
9602
  }
9013
9603
  const lines = Math.max(1, opts.tail);
@@ -9048,13 +9638,13 @@ async function tailFromEnd(path7, lines) {
9048
9638
  }
9049
9639
  }
9050
9640
  async function followFile(path7) {
9051
- let offset = (0, import_node_fs14.statSync)(path7).size;
9641
+ let offset = (0, import_node_fs15.statSync)(path7).size;
9052
9642
  for (; ; ) {
9053
9643
  await (0, import_promises2.setTimeout)(1e3);
9054
- const size = (0, import_node_fs14.statSync)(path7).size;
9644
+ const size = (0, import_node_fs15.statSync)(path7).size;
9055
9645
  if (size < offset) offset = 0;
9056
9646
  if (size === offset) continue;
9057
- const stream = (0, import_node_fs14.createReadStream)(path7, { start: offset, end: size - 1, encoding: "utf8" });
9647
+ const stream = (0, import_node_fs15.createReadStream)(path7, { start: offset, end: size - 1, encoding: "utf8" });
9058
9648
  for await (const chunk of stream) process.stdout.write(chunk);
9059
9649
  offset = size;
9060
9650
  }
@@ -9062,15 +9652,16 @@ async function followFile(path7) {
9062
9652
 
9063
9653
  // src/cli/commands/events.ts
9064
9654
  var import_commander9 = require("commander");
9065
- var import_node_fs15 = require("fs");
9066
- var import_node_os6 = require("os");
9067
- var import_node_path11 = require("path");
9655
+ var import_node_fs16 = require("fs");
9656
+ var import_node_os7 = require("os");
9657
+ var import_node_path12 = require("path");
9068
9658
  var import_node_readline = require("readline");
9069
- var DEFAULT_LIMIT = 50;
9659
+ init_util();
9660
+ var DEFAULT_LIMIT2 = 50;
9070
9661
  function buildEventsCommand() {
9071
9662
  return addEventOptions(new import_commander9.Command("events").description("Read local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
9072
9663
  const file = eventsPath();
9073
- if (!(0, import_node_fs15.existsSync)(file)) {
9664
+ if (!(0, import_node_fs16.existsSync)(file)) {
9074
9665
  printJson(unavailable(file));
9075
9666
  process.exitCode = 1;
9076
9667
  return;
@@ -9087,7 +9678,7 @@ function buildEventsCommand() {
9087
9678
  function buildEventsStatsCommand() {
9088
9679
  return addEventOptions(new import_commander9.Command("events:stats").description("Summarize local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
9089
9680
  const file = eventsPath();
9090
- if (!(0, import_node_fs15.existsSync)(file)) {
9681
+ if (!(0, import_node_fs16.existsSync)(file)) {
9091
9682
  printJson(unavailable(file));
9092
9683
  process.exitCode = 1;
9093
9684
  return;
@@ -9102,10 +9693,10 @@ function buildEventsStatsCommand() {
9102
9693
  });
9103
9694
  }
9104
9695
  function addEventOptions(cmd) {
9105
- return cmd.option("--limit <n>", "Max events to return/read", parsePositiveInt3, DEFAULT_LIMIT).option("--agent-id <id>", "Filter by agent id").option("--session-id <id>", "Filter by session id").option("--family <name>", "Filter by event family").option("--type <name>", "Filter by event type").option("--json", "Output JSON (default)");
9696
+ return cmd.option("--limit <n>", "Max events to return/read", parsePositiveInt3, DEFAULT_LIMIT2).option("--agent-id <id>", "Filter by agent id").option("--session-id <id>", "Filter by session id").option("--family <name>", "Filter by event family").option("--type <name>", "Filter by event type").option("--json", "Output JSON (default)");
9106
9697
  }
9107
9698
  function eventsPath() {
9108
- return (0, import_node_path11.join)(process.env.PRISMER_HOME ?? (0, import_node_path11.join)((0, import_node_os6.homedir)(), ".prismer"), "para", "events.jsonl");
9699
+ return (0, import_node_path12.join)(process.env.PRISMER_HOME ?? (0, import_node_path12.join)((0, import_node_os7.homedir)(), ".prismer"), "para", "events.jsonl");
9109
9700
  }
9110
9701
  function unavailable(file) {
9111
9702
  return {
@@ -9121,7 +9712,7 @@ function unavailable(file) {
9121
9712
  async function readEvents(file, filters) {
9122
9713
  const out = [];
9123
9714
  const rl = (0, import_node_readline.createInterface)({
9124
- input: (0, import_node_fs15.createReadStream)(file, { encoding: "utf8" }),
9715
+ input: (0, import_node_fs16.createReadStream)(file, { encoding: "utf8" }),
9125
9716
  crlfDelay: Infinity
9126
9717
  });
9127
9718
  for await (const line of rl) {
@@ -9173,7 +9764,7 @@ function bump(map, key) {
9173
9764
  }
9174
9765
  function normalizeFilters(opts) {
9175
9766
  return {
9176
- limit: Math.max(1, Math.min(1e4, Number.isFinite(opts.limit) ? opts.limit : DEFAULT_LIMIT)),
9767
+ limit: Math.max(1, Math.min(1e4, Number.isFinite(opts.limit) ? opts.limit : DEFAULT_LIMIT2)),
9177
9768
  agentId: opts.agentId,
9178
9769
  sessionId: opts.sessionId,
9179
9770
  family: opts.family,
@@ -9185,13 +9776,14 @@ function cleanFilters(filters) {
9185
9776
  }
9186
9777
  function parsePositiveInt3(v) {
9187
9778
  const n = Number.parseInt(v, 10);
9188
- return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT;
9779
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
9189
9780
  }
9190
9781
 
9191
9782
  // src/cli/commands/memory.ts
9192
9783
  var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
9193
9784
  var import_commander10 = require("commander");
9194
- var import_node_fs16 = require("fs");
9785
+ var import_node_fs17 = require("fs");
9786
+ init_util();
9195
9787
  var LOCAL_BASE = process.env.PRISMER_DAEMON_URL ?? "http://127.0.0.1:3210";
9196
9788
  function buildMemoryCommand() {
9197
9789
  const cmd = new import_commander10.Command("memory").description("Inspect local daemon memory/cache state");
@@ -9283,7 +9875,7 @@ async function tryDaemon(methods, paths) {
9283
9875
  try {
9284
9876
  const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
9285
9877
  if (res.status === 404) continue;
9286
- const body = await readJson3(res);
9878
+ const body = await readJson4(res);
9287
9879
  if (!res.ok) {
9288
9880
  return {
9289
9881
  ok: false,
@@ -9302,7 +9894,7 @@ async function tryDaemon(methods, paths) {
9302
9894
  }
9303
9895
  return void 0;
9304
9896
  }
9305
- async function readJson3(res) {
9897
+ async function readJson4(res) {
9306
9898
  const text = await res.text();
9307
9899
  if (!text) return null;
9308
9900
  try {
@@ -9342,7 +9934,7 @@ function readCacheSnapshot(limit) {
9342
9934
  const paths = resolvePaths();
9343
9935
  const empty = {
9344
9936
  dbPath: paths.localDb,
9345
- dbExists: (0, import_node_fs16.existsSync)(paths.localDb),
9937
+ dbExists: (0, import_node_fs17.existsSync)(paths.localDb),
9346
9938
  tables: {
9347
9939
  cached_assets: { exists: false, count: 0, sizeBytes: 0 },
9348
9940
  workspace_files_mirror: { exists: false, count: 0 }
@@ -9396,9 +9988,9 @@ function readCacheSnapshot(limit) {
9396
9988
  db?.close();
9397
9989
  }
9398
9990
  }
9399
- function tableExists(db, table) {
9400
- const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
9401
- return row?.name === table;
9991
+ function tableExists(db, table2) {
9992
+ const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table2);
9993
+ return row?.name === table2;
9402
9994
  }
9403
9995
  function assetRow(row) {
9404
9996
  const hash = String(row.content_hash ?? "");
@@ -9462,6 +10054,8 @@ function messageFromBody(body) {
9462
10054
 
9463
10055
  // src/cli/commands/pair.ts
9464
10056
  var import_commander11 = require("commander");
10057
+ init_util();
10058
+ init_ui();
9465
10059
  function buildPairCommand() {
9466
10060
  return new import_commander11.Command("pair").description("Legacy QR approval path; use `prismer setup` to bind this runtime").option(
9467
10061
  "--cloud <url>",
@@ -9504,10 +10098,11 @@ function buildPairCommand() {
9504
10098
 
9505
10099
  // src/cli/commands/profile.ts
9506
10100
  var import_commander12 = require("commander");
9507
- var import_node_fs17 = require("fs");
9508
- var import_node_os7 = require("os");
9509
- var import_node_path12 = require("path");
10101
+ var import_node_fs18 = require("fs");
10102
+ var import_node_os8 = require("os");
10103
+ var import_node_path13 = require("path");
9510
10104
  var import_node_child_process8 = require("child_process");
10105
+ init_util();
9511
10106
  function buildProfileCommand() {
9512
10107
  const cmd = new import_commander12.Command("profile").description("Manage AgentProfile (per-agent adapter config)");
9513
10108
  cmd.command("templates").description("List built-in role templates (PM / Engineer / CEO \u2026)").option("--json", "Output JSON (default)").action(() => {
@@ -9518,7 +10113,7 @@ function buildProfileCommand() {
9518
10113
  const data = await cloud.get(`/api/im/agent_profiles?agentId=${encodeURIComponent(opts.agent)}`);
9519
10114
  printJson(data);
9520
10115
  }, { code: "profile_list_failed" }));
9521
- cmd.command("create").description("Create an AgentProfile").requiredOption("--agent <imUserId>", "Agent IMUser.id this profile belongs to").requiredOption("--name <name>", "Profile display name (unique within workspace+agent)").option("--adapter <name>", "Adapter name (defaults to template.applicableAdapters[0] or hermes)").option("--config <jsonOrPath>", "Inline JSON or @path/to/file containing adapter config").option("--from-template <name>", "Use a built-in role template (run `prismer profile templates` to list)").option("--workspace-id <id>", "Workspace id (cloud derives Personal default if omitted)").option("--json", "Output JSON (default)").action(runAction(async (opts) => {
10116
+ cmd.command("create").description("Create an AgentProfile").requiredOption("--agent <imUserId>", "Agent IMUser.id this profile belongs to").requiredOption("--name <name>", "Profile display name (unique within workspace+agent)").option("--adapter <name>", "Adapter name (defaults to template.applicableAdapters[0] or hermes)").option("--config <jsonOrPath>", "Inline JSON or @path/to/file containing adapter config").option("--from-template <name>", "Use a built-in role template (run `prismer profile templates` to list)").option("--model <name>", "Model id (overrides template default; fetched from cloud if omitted)").option("--workspace-id <id>", "Workspace id (cloud derives Personal default if omitted)").option("--json", "Output JSON (default)").action(runAction(async (opts) => {
9522
10117
  let configObj = {};
9523
10118
  let adapterName = opts.adapter ?? "hermes";
9524
10119
  if (opts.fromTemplate) {
@@ -9531,6 +10126,9 @@ function buildProfileCommand() {
9531
10126
  const inline = readJsonArg(opts.config);
9532
10127
  configObj = { ...configObj, ...inline };
9533
10128
  }
10129
+ if (opts.model) {
10130
+ configObj.model = opts.model;
10131
+ }
9534
10132
  const cloud = mkCloud4();
9535
10133
  const wsId = opts.workspaceId ?? await resolveDefaultWorkspaceId(cloud);
9536
10134
  const res = await cloud.request("POST", "/api/im/agent_profiles", {
@@ -9550,12 +10148,12 @@ function buildProfileCommand() {
9550
10148
  const profile = await cloud.get(
9551
10149
  `/api/im/agent_profiles/${encodeURIComponent(profileId)}`
9552
10150
  );
9553
- const tmpFile = (0, import_node_path12.join)((0, import_node_os7.tmpdir)(), `prismer-profile-${profileId}.json`);
9554
- (0, import_node_fs17.writeFileSync)(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
10151
+ const tmpFile = (0, import_node_path13.join)((0, import_node_os8.tmpdir)(), `prismer-profile-${profileId}.json`);
10152
+ (0, import_node_fs18.writeFileSync)(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
9555
10153
  const editor = process.env.EDITOR || "vi";
9556
10154
  const ed = (0, import_node_child_process8.spawnSync)(editor, [tmpFile], { stdio: "inherit" });
9557
10155
  if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
9558
- const newConfig = JSON.parse((0, import_node_fs17.readFileSync)(tmpFile, "utf8"));
10156
+ const newConfig = JSON.parse((0, import_node_fs18.readFileSync)(tmpFile, "utf8"));
9559
10157
  const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
9560
10158
  body: { config: newConfig, version: profile.version }
9561
10159
  });
@@ -9577,8 +10175,8 @@ function mkCloud4() {
9577
10175
  function readJsonArg(arg) {
9578
10176
  if (arg.startsWith("@")) {
9579
10177
  const path7 = arg.slice(1);
9580
- if (!(0, import_node_fs17.existsSync)(path7)) throw new Error(`File not found: ${path7}`);
9581
- return JSON.parse((0, import_node_fs17.readFileSync)(path7, "utf8"));
10178
+ if (!(0, import_node_fs18.existsSync)(path7)) throw new Error(`File not found: ${path7}`);
10179
+ return JSON.parse((0, import_node_fs18.readFileSync)(path7, "utf8"));
9582
10180
  }
9583
10181
  return JSON.parse(arg);
9584
10182
  }
@@ -9591,6 +10189,8 @@ async function resolveDefaultWorkspaceId(cloud) {
9591
10189
 
9592
10190
  // src/cli/commands/sandbox.ts
9593
10191
  var import_commander13 = require("commander");
10192
+ init_util();
10193
+ init_ui();
9594
10194
  function buildSandboxCommand() {
9595
10195
  const cmd = new import_commander13.Command("sandbox").description("Inspect and smoke-test sandbox lifecycle");
9596
10196
  cmd.command("list").description("List sandbox containers in a workspace").requiredOption("--workspace-id <id>", "Workspace id").option("--status <status>", "Status filter").option("--limit <n>", "Max rows", parsePositiveInt5, 50).option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
@@ -9792,11 +10392,13 @@ async function safeParseResponse(res) {
9792
10392
 
9793
10393
  // src/cli/commands/setup.ts
9794
10394
  var import_commander14 = require("commander");
9795
- var import_node_os8 = require("os");
10395
+ var import_node_os9 = require("os");
9796
10396
  var import_node_child_process9 = require("child_process");
9797
- var import_node_crypto7 = require("crypto");
9798
- var import_node_fs18 = require("fs");
10397
+ var import_node_crypto6 = require("crypto");
10398
+ var import_node_fs19 = require("fs");
9799
10399
  var import_node_http2 = require("http");
10400
+ init_util();
10401
+ init_ui();
9800
10402
  function buildSetupCommand() {
9801
10403
  return new import_commander14.Command("setup").description("Set up this local runtime and bind it to Prismer Cloud").argument("[api-key]", "Prismer daemon API key; primarily for manual recovery and automation").option(
9802
10404
  "--cloud <url>",
@@ -9819,6 +10421,7 @@ function buildSetupCommand() {
9819
10421
  getUI().blank();
9820
10422
  }
9821
10423
  const shouldStart = opts.start !== false;
10424
+ stopRunningDaemon(paths);
9822
10425
  if (opts.pair || opts.asUser) {
9823
10426
  if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
9824
10427
  if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
@@ -9835,7 +10438,7 @@ function buildSetupCommand() {
9835
10438
  }
9836
10439
  const result = await pair({
9837
10440
  cloudBaseUrl,
9838
- deviceName: opts.deviceName ?? (0, import_node_os8.hostname)(),
10441
+ deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
9839
10442
  force: opts.force,
9840
10443
  paths,
9841
10444
  asUserEmail: opts.asUser
@@ -9852,13 +10455,13 @@ function buildSetupCommand() {
9852
10455
  apiKey = await mintDaemonApiKey({
9853
10456
  cloudBaseUrl,
9854
10457
  token: authToken,
9855
- deviceName: opts.deviceName ?? (0, import_node_os8.hostname)()
10458
+ deviceName: opts.deviceName ?? (0, import_node_os9.hostname)()
9856
10459
  });
9857
10460
  }
9858
10461
  if (!apiKey && !authToken && !opts.check && opts.browser !== false) {
9859
10462
  apiKey = await runBrowserSetup({
9860
10463
  cloudBaseUrl,
9861
- deviceName: opts.deviceName ?? (0, import_node_os8.hostname)(),
10464
+ deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
9862
10465
  json: Boolean(opts.json)
9863
10466
  });
9864
10467
  }
@@ -9897,9 +10500,13 @@ function buildSetupCommand() {
9897
10500
  if (configExists(paths) && !opts.force) {
9898
10501
  const cfg = loadConfig(paths);
9899
10502
  if (opts.json) {
9900
- printJson({ ok: true, alreadyConfigured: true, config: paths.configFile, daemonId: cfg.daemon_id });
10503
+ printJson({ ok: true, alreadyConfigured: true, config: paths.configFile, daemonId: cfg.daemon_id, daemonStartRequested: shouldStart });
9901
10504
  } else {
9902
10505
  warn("Already configured", paths.configFile);
10506
+ if (shouldStart) {
10507
+ startDaemonDetached(paths.root);
10508
+ ok("Daemon start requested");
10509
+ }
9903
10510
  tip("prismer setup --force <api-key>", "overwrite config");
9904
10511
  }
9905
10512
  return;
@@ -9938,7 +10545,7 @@ function buildSetupCommand() {
9938
10545
  }, { code: "setup_failed" }));
9939
10546
  }
9940
10547
  async function runBrowserSetup(input) {
9941
- const state = (0, import_node_crypto7.randomBytes)(18).toString("base64url");
10548
+ const state = (0, import_node_crypto6.randomBytes)(18).toString("base64url");
9942
10549
  const server = (0, import_node_http2.createServer)();
9943
10550
  const apiKeyPromise = waitForSetupCallback(server, state);
9944
10551
  await new Promise((resolve3, reject) => {
@@ -9982,14 +10589,15 @@ function waitForSetupCallback(server, expectedState) {
9982
10589
  }
9983
10590
  const state = url.searchParams.get("state");
9984
10591
  const key = url.searchParams.get("key");
10592
+ const PAGE = '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Prismer Setup</title><style>body{background:#0a0a0a;color:#e5e5e5;font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;text-align:center}div{max-width:480px;padding:40px}h1{font-size:28px;margin-bottom:16px}p{font-size:15px;color:#888;line-height:1.6}.check{color:#4ade80;font-size:48px;margin-bottom:16px}.cross{color:#ef4444;font-size:48px;margin-bottom:16px}</style></head><body><div>';
9985
10593
  if (state !== expectedState || !key || !/^sk-prismer-/.test(key)) {
9986
10594
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
9987
- res.end("<h1>Prismer setup failed</h1><p>Invalid setup callback. You can close this tab and retry.</p>");
10595
+ res.end(`${PAGE}<div class="cross">&#10007;</div><h1>Prismer setup failed</h1><p>Invalid setup callback. You can close this tab and retry.</p></div></body></html>`);
9988
10596
  reject(new Error("setup: invalid browser callback"));
9989
10597
  return;
9990
10598
  }
9991
10599
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
9992
- res.end("<h1>Prismer setup complete</h1><p>You can close this tab and return to your terminal.</p>");
10600
+ res.end(`${PAGE}<div class="check">&#10003;</div><h1>Prismer setup complete</h1><p>You can close this tab and return to your terminal.</p></div></body></html>`);
9993
10601
  resolve3(key);
9994
10602
  } catch (err) {
9995
10603
  reject(err);
@@ -10034,13 +10642,35 @@ function startDaemonDetached(home) {
10034
10642
  });
10035
10643
  child.unref();
10036
10644
  }
10645
+ function stopRunningDaemon(paths) {
10646
+ const pid = readPidFile(paths);
10647
+ if (!pid || !pidAlive(pid)) {
10648
+ if (pid) clearPidFile(paths);
10649
+ return;
10650
+ }
10651
+ try {
10652
+ process.kill(pid, "SIGTERM");
10653
+ } catch {
10654
+ return;
10655
+ }
10656
+ const deadline = Date.now() + 5e3;
10657
+ while (Date.now() < deadline) {
10658
+ if (!pidAlive(pid)) {
10659
+ clearPidFile(paths);
10660
+ return;
10661
+ }
10662
+ const start = Date.now();
10663
+ while (Date.now() - start < 200) {
10664
+ }
10665
+ }
10666
+ }
10037
10667
  function shouldArchiveLocalDb(previous, next) {
10038
10668
  return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
10039
10669
  }
10040
10670
  function archiveLocalDb(localDbPath) {
10041
- if (!(0, import_node_fs18.existsSync)(localDbPath)) return;
10671
+ if (!(0, import_node_fs19.existsSync)(localDbPath)) return;
10042
10672
  const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
10043
- (0, import_node_fs18.renameSync)(localDbPath, archived);
10673
+ (0, import_node_fs19.renameSync)(localDbPath, archived);
10044
10674
  }
10045
10675
  async function mintDaemonApiKey(input) {
10046
10676
  const label = `Daemon: ${input.deviceName} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
@@ -10061,6 +10691,8 @@ async function mintDaemonApiKey(input) {
10061
10691
 
10062
10692
  // src/cli/commands/status.ts
10063
10693
  var import_commander15 = require("commander");
10694
+ init_util();
10695
+ init_ui();
10064
10696
  function buildStatusCommand() {
10065
10697
  return new import_commander15.Command("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
10066
10698
  const paths = resolvePaths();
@@ -10087,10 +10719,34 @@ function buildStatusCommand() {
10087
10719
  const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
10088
10720
  let cloudOk = false;
10089
10721
  let me = null;
10722
+ let devices = null;
10723
+ let agents = null;
10090
10724
  try {
10091
- const res = await cloud.request("GET", "/api/im/me", { timeoutMs: 3e3 });
10092
- cloudOk = res.ok;
10093
- me = res.data ?? null;
10725
+ const meRes = await cloud.request("GET", "/api/im/me", { timeoutMs: 3e3 });
10726
+ cloudOk = meRes.ok;
10727
+ me = meRes.data ?? null;
10728
+ if (cloudOk) {
10729
+ const wsRes = await cloud.request("GET", "/api/im/workspaces", { timeoutMs: 3e3 });
10730
+ if (wsRes.ok) {
10731
+ const wsBody = wsRes.data;
10732
+ const wsList = wsBody?.data;
10733
+ if (Array.isArray(wsList) && wsList.length > 0) {
10734
+ const wsId = wsList[0]?.id;
10735
+ if (wsId) {
10736
+ const devRes = await cloud.request("GET", `/api/workspace/runtime-installations?workspaceId=${encodeURIComponent(wsId)}&includeStopped=false`, { timeoutMs: 3e3 });
10737
+ if (devRes.ok) {
10738
+ const devBody = devRes.data;
10739
+ devices = devBody?.data;
10740
+ }
10741
+ const agRes = await cloud.request("GET", `/api/im/workspaces/${encodeURIComponent(wsId)}/agents`, { timeoutMs: 3e3 });
10742
+ if (agRes.ok) {
10743
+ const agBody = agRes.data;
10744
+ agents = agBody?.data;
10745
+ }
10746
+ }
10747
+ }
10748
+ }
10749
+ }
10094
10750
  } catch {
10095
10751
  cloudOk = false;
10096
10752
  }
@@ -10102,10 +10758,15 @@ function buildStatusCommand() {
10102
10758
  daemon: {
10103
10759
  running: daemonRunning,
10104
10760
  pid: daemonStatus.pid ?? pid ?? null,
10105
- wsConnected: daemonStatus.wsConnected ?? null
10761
+ wsConnected: daemonStatus.wsConnected ?? null,
10762
+ info: daemonStatus.info ?? {}
10106
10763
  },
10107
- cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me },
10108
- local
10764
+ cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
10765
+ local,
10766
+ binding: {
10767
+ daemonId: cfg.daemon_id,
10768
+ apiKey: cfg.api_key
10769
+ }
10109
10770
  };
10110
10771
  if (opts.json) {
10111
10772
  printJson(report);
@@ -10119,12 +10780,19 @@ async function readDaemonStatus() {
10119
10780
  const res = await fetch("http://127.0.0.1:3210/healthz", {
10120
10781
  signal: AbortSignal.timeout(1e3)
10121
10782
  });
10122
- if (!res.ok) return { running: false };
10123
- const data = await res.json();
10124
- return { running: true, pid: data.pid, wsConnected: data.wsConnected };
10783
+ if (res.ok) {
10784
+ const data = await res.json();
10785
+ return { running: true, pid: data.pid, wsConnected: data.wsConnected, info: data };
10786
+ }
10125
10787
  } catch {
10126
- return { running: false };
10127
10788
  }
10789
+ const paths = resolvePaths();
10790
+ const pid = readPidFile(paths);
10791
+ if (pid) {
10792
+ const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
10793
+ if (pidAlive2(pid)) return { running: true, pid };
10794
+ }
10795
+ return { running: false };
10128
10796
  }
10129
10797
  function readLocalCounts(localDbPath) {
10130
10798
  try {
@@ -10150,21 +10818,59 @@ function printPretty(report) {
10150
10818
  ui.blank();
10151
10819
  ok("Config", report.paths.config);
10152
10820
  if (report.daemon.running) {
10153
- ok("Daemon", `pid=${report.daemon.pid ?? "?"} ws=${report.daemon.wsConnected ? "connected" : "pending"}`);
10821
+ const ws = report.daemon.wsConnected ? "connected" : "pending";
10822
+ ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
10823
+ if (report.daemon.info) {
10824
+ const info2 = report.daemon.info;
10825
+ if (info2.version) ui.line(` Version: ${info2.version}`);
10826
+ if (info2.uptime) ui.line(` Uptime: ${Math.round(info2.uptime / 60)}m`);
10827
+ if (info2.memoryMb) ui.line(` Memory: ${info2.memoryMb} MB`);
10828
+ }
10154
10829
  } else {
10155
10830
  warn("Daemon", "not running");
10156
10831
  tip("prismer daemon start");
10157
10832
  }
10158
- if (report.cloud.reachable) ok("Cloud", report.cloud.base);
10159
- else {
10833
+ if (report.cloud.reachable) {
10834
+ ok("Cloud", report.cloud.base);
10835
+ const me = report.cloud.me;
10836
+ if (me?.user) {
10837
+ const roleTag = me.user.role ? ` role=${me.user.role}` : "";
10838
+ ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
10839
+ }
10840
+ if (me?.credits) {
10841
+ ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
10842
+ }
10843
+ } else {
10160
10844
  fail2("Cloud", `${report.cloud.base} unreachable or unauthorized`);
10161
10845
  tip("prismer setup --force");
10162
10846
  }
10847
+ if (report.binding) {
10848
+ ui.blank();
10849
+ ui.line(` Daemon ID: ${report.binding.daemonId}`);
10850
+ const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
10851
+ ui.line(` API Key: ${masked}`);
10852
+ }
10853
+ if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
10854
+ const devs = report.cloud.devices;
10855
+ ui.blank();
10856
+ ui.line(` Workspace Devices (${devs.length}):`);
10857
+ for (const d of devs) {
10858
+ const statusIcon = d.daemonStatus === "connected" ? "\u25CF" : "\u25CB";
10859
+ const kind = d.runtimeKind === "docker" ? "Local" : "K8s";
10860
+ const declared = d.hostedAgentSummary?.declared ?? 0;
10861
+ ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
10862
+ }
10863
+ } else {
10864
+ ui.line(` Workspace Devices: none`);
10865
+ }
10866
+ if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
10867
+ ui.line(` Hosted agents: ${report.cloud.agents.length}`);
10868
+ }
10163
10869
  if (report.local) {
10164
10870
  ui.blank();
10165
- ui.line(` Agents: ${report.local.agents}`);
10166
- ui.line(` Profiles: ${report.local.profiles}`);
10167
- ui.line(` Tasks: ${report.local.runningTasks} running locally`);
10871
+ ui.line(` Local agents: ${report.local.agents}`);
10872
+ ui.line(` Profiles: ${report.local.profiles}`);
10873
+ ui.line(` Running tasks: ${report.local.runningTasks}`);
10168
10874
  } else {
10169
10875
  warn("Local DB", "unavailable");
10170
10876
  }
@@ -10173,6 +10879,7 @@ function printPretty(report) {
10173
10879
  // src/cli/commands/task.ts
10174
10880
  var import_commander16 = require("commander");
10175
10881
  var import_promises3 = require("timers/promises");
10882
+ init_util();
10176
10883
  function describeStatus(status) {
10177
10884
  return status === 0 ? "network error" : `HTTP ${status}`;
10178
10885
  }
@@ -10292,6 +10999,8 @@ function taskFrom2(raw) {
10292
10999
 
10293
11000
  // src/cli/commands/workspace.ts
10294
11001
  var import_commander17 = require("commander");
11002
+ init_util();
11003
+ init_ui();
10295
11004
  function buildWorkspaceCommand() {
10296
11005
  const cmd = new import_commander17.Command("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
10297
11006
  cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
@@ -10526,7 +11235,8 @@ async function readResponseError(res) {
10526
11235
  }
10527
11236
 
10528
11237
  // src/cli/index.ts
10529
- var VERSION = "1.9.3";
11238
+ init_ui();
11239
+ var VERSION = "1.9.7";
10530
11240
  function buildProgram() {
10531
11241
  const program = new import_commander18.Command("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
10532
11242
  program.addCommand(buildBannerCommand());