@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/cli.js CHANGED
@@ -1,10 +1,611 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
4
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
5
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
6
  }) : x)(function(x) {
5
7
  if (typeof require !== "undefined") return require.apply(this, arguments);
6
8
  throw Error('Dynamic require of "' + x + '" is not supported');
7
9
  });
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/cli/ui.ts
19
+ import * as fs from "fs";
20
+ import * as path from "path";
21
+ import { fileURLToPath } from "url";
22
+ function thisDirname() {
23
+ try {
24
+ return path.dirname(fileURLToPath(import.meta.url));
25
+ } catch {
26
+ return process.cwd();
27
+ }
28
+ }
29
+ function findIconPath(size = "big") {
30
+ const name = size === "big" ? "icon" : "smallicon";
31
+ const here = thisDirname();
32
+ const candidates = [
33
+ // npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
34
+ path.resolve(here, "../assets", name),
35
+ // alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
36
+ path.resolve(here, "../../assets", name),
37
+ // source/typecheck: src/cli/ui.ts → ../../assets
38
+ path.resolve(here, "../../assets", name),
39
+ // dev mode: cwd happens to be runtime root
40
+ path.resolve(process.cwd(), "assets", name),
41
+ path.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
42
+ ];
43
+ for (const candidate of candidates) {
44
+ try {
45
+ if (fs.existsSync(candidate)) return candidate;
46
+ } catch {
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ function getUI() {
52
+ if (!_ui) _ui = new UI();
53
+ return _ui;
54
+ }
55
+ function setUI(ui) {
56
+ _ui = ui;
57
+ }
58
+ function applyCommonFlags(argv) {
59
+ let mode = "pretty";
60
+ const isTTY = process.stdout.isTTY === true;
61
+ const noColorEnv = Boolean(process.env["NO_COLOR"]);
62
+ let color2 = isTTY && !noColorEnv;
63
+ const rest = [];
64
+ for (const arg of argv) {
65
+ switch (arg) {
66
+ case "--no-color":
67
+ color2 = false;
68
+ break;
69
+ case "--color":
70
+ color2 = true;
71
+ break;
72
+ case "--json":
73
+ case "--pretty-json":
74
+ mode = "json";
75
+ if (arg === "--json") rest.push(arg);
76
+ break;
77
+ case "--quiet":
78
+ mode = "quiet";
79
+ break;
80
+ default:
81
+ rest.push(arg);
82
+ }
83
+ }
84
+ return { mode, color: color2, restArgv: rest };
85
+ }
86
+ var BRAILLE_FRAMES, COMPACT_BANNER, UI, _ui;
87
+ var init_ui = __esm({
88
+ "src/cli/ui.ts"() {
89
+ "use strict";
90
+ BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
91
+ COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
92
+ UI = class {
93
+ mode;
94
+ colorEnabled;
95
+ stream;
96
+ errStream;
97
+ constructor(opts) {
98
+ this.mode = opts?.mode ?? "pretty";
99
+ this.stream = opts?.stream ?? process.stdout;
100
+ this.errStream = opts?.errStream ?? process.stderr;
101
+ if (opts?.color !== void 0) {
102
+ this.colorEnabled = opts.color;
103
+ } else {
104
+ const isTTY = this.stream.isTTY === true;
105
+ const noColor = Boolean(process.env["NO_COLOR"]);
106
+ this.colorEnabled = isTTY && !noColor;
107
+ }
108
+ }
109
+ // ---- Internal color helpers ----
110
+ ansi(open, close, text) {
111
+ if (!this.colorEnabled) return text;
112
+ return `\x1B[${open}m${text}\x1B[${close}m`;
113
+ }
114
+ green(t) {
115
+ return this.ansi(32, 39, t);
116
+ }
117
+ red(t) {
118
+ return this.ansi(31, 39, t);
119
+ }
120
+ yellow(t) {
121
+ return this.ansi(33, 39, t);
122
+ }
123
+ cyan(t) {
124
+ return this.ansi(36, 39, t);
125
+ }
126
+ dim(t) {
127
+ return this.ansi(2, 22, t);
128
+ }
129
+ bold(t) {
130
+ return this.ansi(1, 22, t);
131
+ }
132
+ gray(t) {
133
+ return this.ansi(90, 39, t);
134
+ }
135
+ brandMark() {
136
+ return this.cyan("\u25C7");
137
+ }
138
+ colorBrandLine(line) {
139
+ let out = "";
140
+ for (const ch of line) {
141
+ if (ch === "\u2592") {
142
+ out += this.cyan(ch);
143
+ } else if (ch === "\u2593") {
144
+ out += this.dim(ch);
145
+ } else {
146
+ out += ch;
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+ // ---- Core write helpers ----
152
+ write(text) {
153
+ this.stream.write(text);
154
+ }
155
+ writeErr(text) {
156
+ this.errStream.write(text);
157
+ }
158
+ // ---- Level 1: Header ----
159
+ header(text) {
160
+ if (this.mode === "json") return;
161
+ const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
162
+ this.write(prefix + this.bold(text) + "\n");
163
+ }
164
+ smallHeader(subtitle) {
165
+ if (this.mode === "json" || this.mode === "quiet") return;
166
+ const iconPath = findIconPath("small");
167
+ if (iconPath !== null) {
168
+ try {
169
+ const raw = fs.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
170
+ for (const line of raw.split("\n")) {
171
+ this.write(this.cyan(line) + "\n");
172
+ }
173
+ } catch {
174
+ this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
175
+ }
176
+ } else {
177
+ this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
178
+ }
179
+ if (subtitle !== void 0 && subtitle.length > 0) {
180
+ this.write(this.dim(" " + subtitle) + "\n");
181
+ }
182
+ this.blank();
183
+ }
184
+ banner(subtitle, opts) {
185
+ if (this.mode === "json" || this.mode === "quiet") return;
186
+ const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
187
+ const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
188
+ const iconPath = findIconPath("big");
189
+ const shouldUseFull = opts?.full === true || width >= 120;
190
+ if (shouldUseFull && iconPath !== null) {
191
+ try {
192
+ const raw = fs.readFileSync(iconPath, "utf-8");
193
+ const lines = raw.split("\n");
194
+ for (const line of lines) {
195
+ const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
196
+ const stripped = brandedLine.trimEnd();
197
+ if (stripped.length === 0) {
198
+ this.write("\n");
199
+ continue;
200
+ }
201
+ const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
202
+ this.write(this.colorBrandLine(clipped) + "\n");
203
+ }
204
+ } catch {
205
+ this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
206
+ this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
207
+ }
208
+ } else {
209
+ this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
210
+ this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
211
+ }
212
+ if (subtitle !== void 0 && subtitle.length > 0) {
213
+ this.write(this.dim(" " + subtitle) + "\n");
214
+ }
215
+ this.blank();
216
+ }
217
+ // ---- Level 2: Primary data ----
218
+ blank() {
219
+ if (this.mode === "json") return;
220
+ this.write("\n");
221
+ }
222
+ line(text) {
223
+ if (this.mode === "json") return;
224
+ this.write(text + "\n");
225
+ }
226
+ info(text) {
227
+ this.line(text);
228
+ }
229
+ // ---- Level 3: Secondary ----
230
+ secondary(text, indent = 2) {
231
+ if (this.mode === "json") return;
232
+ this.write(" ".repeat(indent) + this.dim(text) + "\n");
233
+ }
234
+ // ---- Level 4: Action tips ----
235
+ tip(text) {
236
+ if (this.mode === "json") return;
237
+ this.write(this.cyan("Tip:") + " " + text + "\n");
238
+ }
239
+ next(text) {
240
+ if (this.mode === "json") return;
241
+ this.write(this.cyan("Next:") + " " + text + "\n");
242
+ }
243
+ // ---- Level 5: Status indicators ----
244
+ ok(text, detail) {
245
+ if (this.mode === "json") return;
246
+ const suffix = detail ? " " + this.dim(detail) : "";
247
+ this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
248
+ }
249
+ success(text, detail) {
250
+ this.ok(text, detail);
251
+ }
252
+ fail(text, detail) {
253
+ if (this.mode === "json") return;
254
+ const suffix = detail ? " " + this.dim(detail) : "";
255
+ this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
256
+ }
257
+ online(text) {
258
+ if (this.mode === "json") return;
259
+ this.write(" " + this.green("\u25CF") + " " + text + "\n");
260
+ }
261
+ offline(text) {
262
+ if (this.mode === "json") return;
263
+ this.write(" " + this.gray("\u25CB") + " " + text + "\n");
264
+ }
265
+ notInstalled(text) {
266
+ if (this.mode === "json") return;
267
+ this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
268
+ }
269
+ pending(text) {
270
+ if (this.mode === "json") return;
271
+ this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
272
+ }
273
+ warn(text, detail) {
274
+ if (this.mode === "json") return;
275
+ const suffix = detail ? " " + this.dim(detail) : "";
276
+ this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
277
+ }
278
+ // ---- Level 6: Error block ----
279
+ error(what, cause, fix) {
280
+ if (this.mode === "json") return;
281
+ this.writeErr(this.red("\u2717") + " " + what + "\n");
282
+ if (cause !== void 0) {
283
+ this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
284
+ }
285
+ if (fix !== void 0) {
286
+ this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
287
+ }
288
+ }
289
+ table(rowsOrOpts, maybeOpts) {
290
+ if (this.mode === "json") return;
291
+ const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
292
+ const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
293
+ if (!opts) throw new Error("table() requires columns");
294
+ const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
295
+ const cols = opts.columns;
296
+ const widths = cols.map((col) => col.length);
297
+ for (const row of rows) {
298
+ cols.forEach((col, i) => {
299
+ const val = row[col] ?? "";
300
+ const w = widths[i] ?? 0;
301
+ if (val.length > w) widths[i] = val.length;
302
+ });
303
+ }
304
+ const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
305
+ if (totalWidth > maxWidth) {
306
+ for (let i = 0; i < rows.length; i++) {
307
+ const row = rows[i];
308
+ if (!row) continue;
309
+ for (const col of cols) {
310
+ const val = row[col] ?? "";
311
+ this.write(" " + this.bold(col + ":") + " " + val + "\n");
312
+ }
313
+ if (i < rows.length - 1) this.write("\n");
314
+ }
315
+ return;
316
+ }
317
+ const header2 = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
318
+ this.write(" " + this.dim(header2) + "\n");
319
+ for (const row of rows) {
320
+ const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
321
+ this.write(" " + line + "\n");
322
+ }
323
+ }
324
+ // ---- Spinner ----
325
+ spinner(text) {
326
+ if (this.mode === "quiet" || this.mode === "json") {
327
+ return {
328
+ update() {
329
+ },
330
+ stop() {
331
+ }
332
+ };
333
+ }
334
+ const isTTY = this.stream.isTTY === true;
335
+ if (!isTTY || !this.colorEnabled) {
336
+ this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
337
+ return {
338
+ update: (t) => {
339
+ this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
340
+ },
341
+ stop: (final) => {
342
+ if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
343
+ }
344
+ };
345
+ }
346
+ let current = text;
347
+ let frameIdx = 0;
348
+ let stopped = false;
349
+ const write = this.write.bind(this);
350
+ const colorFn = this.yellow.bind(this);
351
+ const greenFn = this.green.bind(this);
352
+ function renderFrame() {
353
+ const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
354
+ const line = " " + colorFn(frame) + " " + current;
355
+ write("\r" + line);
356
+ frameIdx++;
357
+ }
358
+ renderFrame();
359
+ const timer = setInterval(renderFrame, 80);
360
+ return {
361
+ update(t) {
362
+ if (stopped) return;
363
+ current = t;
364
+ },
365
+ stop(final) {
366
+ if (stopped) return;
367
+ stopped = true;
368
+ clearInterval(timer);
369
+ write("\r\x1B[2K");
370
+ if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
371
+ }
372
+ };
373
+ }
374
+ // ---- Progress bar ----
375
+ progress(text, total) {
376
+ if (this.mode === "quiet" || this.mode === "json") {
377
+ return {
378
+ update() {
379
+ },
380
+ stop() {
381
+ }
382
+ };
383
+ }
384
+ const isTTY = this.stream.isTTY === true;
385
+ const start = Date.now();
386
+ const write = this.write.bind(this);
387
+ const colorFn = this.cyan.bind(this);
388
+ const dimFn = this.dim.bind(this);
389
+ const greenFn = this.green.bind(this);
390
+ let last = 0;
391
+ let lastDetail = "";
392
+ let stopped = false;
393
+ const render = () => {
394
+ if (stopped) return;
395
+ const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
396
+ const pct = Math.floor(frac * 100);
397
+ const width = 20;
398
+ const filled = Math.floor(frac * width);
399
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
400
+ const elapsed = (Date.now() - start) / 1e3;
401
+ const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
402
+ const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
403
+ const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
404
+ const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
405
+ if (isTTY && this.colorEnabled) {
406
+ write("\r\x1B[2K" + line);
407
+ } else {
408
+ write(line + "\n");
409
+ }
410
+ };
411
+ render();
412
+ return {
413
+ update: (current, detail) => {
414
+ if (stopped) return;
415
+ last = current;
416
+ if (detail !== void 0) lastDetail = detail;
417
+ render();
418
+ },
419
+ stop: (final) => {
420
+ if (stopped) return;
421
+ stopped = true;
422
+ if (isTTY && this.colorEnabled) write("\r\x1B[2K");
423
+ if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
424
+ }
425
+ };
426
+ }
427
+ // ---- JSON output ----
428
+ json(payload, opts) {
429
+ const indent = opts?.pretty ? 2 : void 0;
430
+ this.write(JSON.stringify(payload, null, indent) + "\n");
431
+ }
432
+ result(pretty, jsonPayload) {
433
+ if (this.mode === "pretty") {
434
+ pretty();
435
+ } else {
436
+ this.json(jsonPayload);
437
+ }
438
+ }
439
+ };
440
+ _ui = null;
441
+ }
442
+ });
443
+
444
+ // src/cli/util.ts
445
+ var util_exports = {};
446
+ __export(util_exports, {
447
+ DEFAULT_CLOUD_BASE_URL: () => DEFAULT_CLOUD_BASE_URL,
448
+ clearPidFile: () => clearPidFile,
449
+ color: () => color,
450
+ exitWithError: () => exitWithError,
451
+ fail: () => fail,
452
+ header: () => header,
453
+ info: () => info,
454
+ normalizeCloudUrl: () => normalizeCloudUrl,
455
+ ok: () => ok,
456
+ pidAlive: () => pidAlive,
457
+ pidFilePath: () => pidFilePath,
458
+ printBanner: () => printBanner,
459
+ printJson: () => printJson,
460
+ readPidFile: () => readPidFile,
461
+ runAction: () => runAction,
462
+ table: () => table,
463
+ tip: () => tip,
464
+ warn: () => warn,
465
+ writePidFile: () => writePidFile
466
+ });
467
+ import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
468
+ import { join as join3 } from "path";
469
+ function color(kind, text) {
470
+ if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
471
+ return `${ANSI[kind]}${text}${ANSI.reset}`;
472
+ }
473
+ function printJson(v) {
474
+ getUI().json(v, { pretty: true });
475
+ }
476
+ function exitWithError(message, opts) {
477
+ const o = typeof opts === "number" ? { exitCode: opts } : opts ?? {};
478
+ const exitCode = o.exitCode ?? 1;
479
+ const ui = getUI();
480
+ if (ui.mode === "json") {
481
+ const payload = {
482
+ ok: false,
483
+ error: { code: o.code ?? "cli_error", message },
484
+ ...o.details ? { details: o.details } : {}
485
+ };
486
+ ui.json(payload, { pretty: true });
487
+ } else {
488
+ process.stderr.write(`Error: ${message}
489
+ `);
490
+ }
491
+ process.exit(exitCode);
492
+ }
493
+ function normalizeCloudUrl(input) {
494
+ const raw = input.trim();
495
+ if (!raw) throw new Error("Cloud URL is empty.");
496
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(raw)) {
497
+ let parsed;
498
+ try {
499
+ parsed = new URL(raw);
500
+ } catch {
501
+ throw new Error(`Invalid --cloud URL: ${raw}`);
502
+ }
503
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
504
+ throw new Error(`Invalid --cloud URL scheme: ${parsed.protocol} (expected http:// or https://)`);
505
+ }
506
+ return raw.replace(/\/$/, "");
507
+ }
508
+ if (!/^[A-Za-z0-9.\-_:[\]/]+$/.test(raw)) {
509
+ throw new Error(`Invalid --cloud URL: ${raw} (must be http://\u2026 or https://\u2026 or host:port)`);
510
+ }
511
+ const candidate = `http://${raw}`;
512
+ try {
513
+ new URL(candidate);
514
+ } catch {
515
+ throw new Error(`Invalid --cloud URL: ${raw}`);
516
+ }
517
+ return candidate.replace(/\/$/, "");
518
+ }
519
+ function runAction(fn, opts = {}) {
520
+ return async (...args) => {
521
+ try {
522
+ await fn(...args);
523
+ } catch (err) {
524
+ const raw = err instanceof Error ? err.message : String(err);
525
+ const message = opts.sanitize ? opts.sanitize(raw) : raw;
526
+ exitWithError(message, { code: opts.code });
527
+ }
528
+ };
529
+ }
530
+ function printBanner(opts = {}) {
531
+ const ui = getUI();
532
+ if (opts.compact) {
533
+ ui.smallHeader("Runtime CLI v1.9.7");
534
+ return;
535
+ }
536
+ ui.banner("Runtime CLI v1.9.7", { full: true });
537
+ }
538
+ function ok(label, detail) {
539
+ getUI().ok(label, detail);
540
+ }
541
+ function warn(label, detail) {
542
+ getUI().warn(label, detail);
543
+ }
544
+ function fail(label, detail) {
545
+ getUI().fail(label, detail);
546
+ }
547
+ function tip(command, detail) {
548
+ const text = detail ? `${command} ${detail}` : command;
549
+ getUI().tip(text);
550
+ }
551
+ function info(message) {
552
+ getUI().info(message);
553
+ }
554
+ function header(title) {
555
+ getUI().header(title);
556
+ getUI().blank();
557
+ }
558
+ function table(rows, columns) {
559
+ getUI().table(rows, { columns });
560
+ }
561
+ function pidFilePath(paths) {
562
+ return join3(paths.root, "daemon.pid");
563
+ }
564
+ function writePidFile(paths, pid) {
565
+ writeFileSync3(pidFilePath(paths), `${pid}
566
+ `, "utf8");
567
+ }
568
+ function readPidFile(paths) {
569
+ const p = pidFilePath(paths);
570
+ if (!existsSync4(p)) return void 0;
571
+ const raw = readFileSync4(p, "utf8").trim();
572
+ const pid = Number.parseInt(raw, 10);
573
+ return Number.isFinite(pid) ? pid : void 0;
574
+ }
575
+ function clearPidFile(paths) {
576
+ const p = pidFilePath(paths);
577
+ if (existsSync4(p)) {
578
+ try {
579
+ unlinkSync(p);
580
+ } catch {
581
+ }
582
+ }
583
+ }
584
+ function pidAlive(pid) {
585
+ try {
586
+ process.kill(pid, 0);
587
+ return true;
588
+ } catch {
589
+ return false;
590
+ }
591
+ }
592
+ var DEFAULT_CLOUD_BASE_URL, ANSI;
593
+ var init_util = __esm({
594
+ "src/cli/util.ts"() {
595
+ "use strict";
596
+ init_ui();
597
+ DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
598
+ ANSI = {
599
+ reset: "\x1B[0m",
600
+ bold: "\x1B[1m",
601
+ dim: "\x1B[2m",
602
+ cyan: "\x1B[36m",
603
+ green: "\x1B[32m",
604
+ yellow: "\x1B[33m",
605
+ red: "\x1B[31m"
606
+ };
607
+ }
608
+ });
8
609
 
9
610
  // src/cli/index.ts
10
611
  import { Command as Command18 } from "commander";
@@ -648,7 +1249,7 @@ var HermesProfileConfigSchema = z3.object({
648
1249
  */
649
1250
  prismerMcpServerPath: z3.string().optional(),
650
1251
  /** Model sent to Prismer's /api/v1/chat/completions endpoint. */
651
- model: z3.string().min(1).default("us-kimi-k2.5"),
1252
+ model: z3.string().min(1).default("us-kimi-k2.6"),
652
1253
  /** Named custom provider written into Hermes config.yaml. */
653
1254
  prismerProviderName: z3.string().min(1).default("prismer"),
654
1255
  /** Override cloud provider base. Defaults to PRISMER_BASE_URL + /api/v1. */
@@ -674,7 +1275,9 @@ var HermesProfileConfigSchema = z3.object({
674
1275
  * surface but the local source tree contains hermes_cli/kanban_db.py.
675
1276
  */
676
1277
  hermesSourceDir: z3.string().optional(),
677
- nativeMirrorTimeoutMs: z3.number().int().positive().default(2e3)
1278
+ nativeMirrorTimeoutMs: z3.number().int().positive().default(2e3),
1279
+ /** Task authority level: executor (default) or orchestrator. */
1280
+ taskAuthority: z3.enum(["executor", "orchestrator"]).optional().default("executor")
678
1281
  });
679
1282
  var hermesAdapter = {
680
1283
  name: "hermes",
@@ -1159,13 +1762,13 @@ function resolvePrismerMcpServerPath(config) {
1159
1762
  }
1160
1763
  try {
1161
1764
  const { fileURLToPath: fileURLToPath2 } = __require("url");
1162
- const { join: join13, dirname: dirname8 } = __require("path");
1765
+ const { join: join14, dirname: dirname8 } = __require("path");
1163
1766
  const here = dirname8(fileURLToPath2(import.meta.url));
1164
- const candidate = join13(here, "../../mcp/dist/index.js");
1767
+ const candidate = join14(here, "../../mcp/dist/index.js");
1165
1768
  if (existsSync(candidate)) return candidate;
1166
- const candidate2 = join13(here, "../../../mcp/dist/index.js");
1769
+ const candidate2 = join14(here, "../../../mcp/dist/index.js");
1167
1770
  if (existsSync(candidate2)) return candidate2;
1168
- const candidate3 = join13(here, "../../../../mcp/dist/index.js");
1771
+ const candidate3 = join14(here, "../../../../mcp/dist/index.js");
1169
1772
  if (existsSync(candidate3)) return candidate3;
1170
1773
  } catch {
1171
1774
  }
@@ -1555,692 +2158,145 @@ var OpenClawService = class {
1555
2158
  body: JSON.stringify({
1556
2159
  model: this.model,
1557
2160
  messages,
1558
- stream: false
1559
- }),
1560
- signal: task.signal
1561
- });
1562
- if (!res.ok) {
1563
- const body = await res.text().catch(() => "") ?? "";
1564
- return {
1565
- ok: false,
1566
- error: {
1567
- code: res.status === 401 || res.status === 403 ? "auth_invalid" : "adapter_dispatch_failed",
1568
- message: `OpenClaw ${res.status}: ${body.slice(0, 400)}`
1569
- }
1570
- };
1571
- }
1572
- const json = await res.json();
1573
- let output = json.choices?.[0]?.message?.content ?? "";
1574
- const ERR_PREFIX_RE = /^Cannot read properties of undefined \(reading '[^']+'\)\s*\n+/;
1575
- if (ERR_PREFIX_RE.test(output)) {
1576
- process.stderr.write(
1577
- `[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
1578
- `
1579
- );
1580
- output = output.replace(ERR_PREFIX_RE, "");
1581
- }
1582
- return {
1583
- ok: true,
1584
- output,
1585
- metrics: { durationMs: Date.now() - startedAt }
1586
- };
1587
- } catch (err) {
1588
- if (task.signal?.aborted || err?.name === "AbortError") {
1589
- return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
1590
- }
1591
- return {
1592
- ok: false,
1593
- error: { code: "adapter_dispatch_failed", message: err.message }
1594
- };
1595
- }
1596
- }
1597
- };
1598
- async function checkHealth2(baseUrl, apiKey) {
1599
- try {
1600
- const res = await fetch(`${baseUrl}/health`, {
1601
- headers: { Authorization: `Bearer ${apiKey}` },
1602
- signal: AbortSignal.timeout(2e3)
1603
- });
1604
- return res.ok;
1605
- } catch {
1606
- return false;
1607
- }
1608
- }
1609
-
1610
- // src/config.ts
1611
- import * as TOML from "@iarna/toml";
1612
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1613
- import { homedir as homedir2 } from "os";
1614
- import { dirname as dirname2, join as join2 } from "path";
1615
- import { z as z5 } from "zod";
1616
- var ConfigSchema = z5.object({
1617
- /** API key from `prismer setup`, or env override. */
1618
- api_key: z5.string().min(1),
1619
- /** Cloud REST + WS base; `ws://` is derived by stripping `http`. */
1620
- cloud_api_base: z5.string().url(),
1621
- /** Stable per-machine daemon identifier. Generated once on first setup. */
1622
- daemon_id: z5.string().min(1),
1623
- /** Optional adapter-specific overrides keyed by adapter name. */
1624
- adapters: z5.record(z5.string(), z5.record(z5.string(), z5.unknown())).optional(),
1625
- /** Local daemon shell execution. Default disabled. */
1626
- shell: z5.object({
1627
- enabled: z5.boolean().default(false),
1628
- default_cwd: z5.string().optional(),
1629
- defaultCwd: z5.string().optional(),
1630
- shell: z5.enum(["bash", "zsh", "sh"]).optional(),
1631
- max_timeout_ms: z5.number().int().positive().optional(),
1632
- maxTimeoutMs: z5.number().int().positive().optional(),
1633
- max_output_bytes: z5.number().int().positive().optional(),
1634
- maxOutputBytes: z5.number().int().positive().optional(),
1635
- allowed_workspaces: z5.array(z5.string()).optional(),
1636
- allowedWorkspaces: z5.array(z5.string()).optional()
1637
- }).optional(),
1638
- /** Local cache settings. */
1639
- cache: z5.object({
1640
- max_bytes: z5.number().int().positive().default(5 * 1024 * 1024 * 1024)
1641
- }).optional()
1642
- });
1643
- function resolvePaths(home) {
1644
- const root = home ?? process.env.PRISMER_HOME ?? join2(homedir2(), ".prismer");
1645
- return {
1646
- root,
1647
- configFile: join2(root, "config.toml"),
1648
- localDb: join2(root, "local.db"),
1649
- cacheDir: join2(root, "cache"),
1650
- logsDir: join2(root, "logs"),
1651
- runsDir: join2(root, "runs")
1652
- };
1653
- }
1654
- function configExists(paths = resolvePaths()) {
1655
- return existsSync2(paths.configFile);
1656
- }
1657
- function loadConfig(paths = resolvePaths()) {
1658
- if (!existsSync2(paths.configFile)) {
1659
- throw new Error(
1660
- `Config not found at ${paths.configFile}. Run \`prismer setup\` to create it.`
1661
- );
1662
- }
1663
- const raw = readFileSync2(paths.configFile, "utf8");
1664
- const parsed = TOML.parse(raw);
1665
- const merged = {
1666
- ...parsed,
1667
- api_key: process.env.PRISMER_API_KEY ?? parsed.api_key,
1668
- cloud_api_base: process.env.PRISMER_BASE_URL ?? parsed.cloud_api_base
1669
- };
1670
- const result = ConfigSchema.safeParse(merged);
1671
- if (!result.success) {
1672
- const detail = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
1673
- throw new Error(`Invalid config at ${paths.configFile}: ${detail}`);
1674
- }
1675
- return result.data;
1676
- }
1677
- function saveConfig(config, paths = resolvePaths()) {
1678
- if (!existsSync2(paths.root)) {
1679
- mkdirSync2(paths.root, { recursive: true });
1680
- }
1681
- if (!existsSync2(dirname2(paths.configFile))) {
1682
- mkdirSync2(dirname2(paths.configFile), { recursive: true });
1683
- }
1684
- ConfigSchema.parse(config);
1685
- writeFileSync2(paths.configFile, TOML.stringify(config), "utf8");
1686
- }
1687
- function deriveWsUrl(httpBase) {
1688
- const u = new URL(httpBase);
1689
- u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
1690
- u.pathname = (u.pathname.replace(/\/$/, "") || "") + "/ws";
1691
- return u.toString();
1692
- }
1693
-
1694
- // src/cli/util.ts
1695
- import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
1696
- import { join as join3 } from "path";
1697
-
1698
- // src/cli/ui.ts
1699
- import * as fs from "fs";
1700
- import * as path from "path";
1701
- import { fileURLToPath } from "url";
1702
- var BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1703
- var COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
1704
- function thisDirname() {
1705
- try {
1706
- return path.dirname(fileURLToPath(import.meta.url));
1707
- } catch {
1708
- return process.cwd();
1709
- }
1710
- }
1711
- function findIconPath(size = "big") {
1712
- const name = size === "big" ? "icon" : "smallicon";
1713
- const here = thisDirname();
1714
- const candidates = [
1715
- // npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
1716
- path.resolve(here, "../assets", name),
1717
- // alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
1718
- path.resolve(here, "../../assets", name),
1719
- // source/typecheck: src/cli/ui.ts → ../../assets
1720
- path.resolve(here, "../../assets", name),
1721
- // dev mode: cwd happens to be runtime root
1722
- path.resolve(process.cwd(), "assets", name),
1723
- path.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
1724
- ];
1725
- for (const candidate of candidates) {
1726
- try {
1727
- if (fs.existsSync(candidate)) return candidate;
1728
- } catch {
1729
- }
1730
- }
1731
- return null;
1732
- }
1733
- var UI = class {
1734
- mode;
1735
- colorEnabled;
1736
- stream;
1737
- errStream;
1738
- constructor(opts) {
1739
- this.mode = opts?.mode ?? "pretty";
1740
- this.stream = opts?.stream ?? process.stdout;
1741
- this.errStream = opts?.errStream ?? process.stderr;
1742
- if (opts?.color !== void 0) {
1743
- this.colorEnabled = opts.color;
1744
- } else {
1745
- const isTTY = this.stream.isTTY === true;
1746
- const noColor = Boolean(process.env["NO_COLOR"]);
1747
- this.colorEnabled = isTTY && !noColor;
1748
- }
1749
- }
1750
- // ---- Internal color helpers ----
1751
- ansi(open, close, text) {
1752
- if (!this.colorEnabled) return text;
1753
- return `\x1B[${open}m${text}\x1B[${close}m`;
1754
- }
1755
- green(t) {
1756
- return this.ansi(32, 39, t);
1757
- }
1758
- red(t) {
1759
- return this.ansi(31, 39, t);
1760
- }
1761
- yellow(t) {
1762
- return this.ansi(33, 39, t);
1763
- }
1764
- cyan(t) {
1765
- return this.ansi(36, 39, t);
1766
- }
1767
- dim(t) {
1768
- return this.ansi(2, 22, t);
1769
- }
1770
- bold(t) {
1771
- return this.ansi(1, 22, t);
1772
- }
1773
- gray(t) {
1774
- return this.ansi(90, 39, t);
1775
- }
1776
- brandMark() {
1777
- return this.cyan("\u25C7");
1778
- }
1779
- colorBrandLine(line) {
1780
- let out = "";
1781
- for (const ch of line) {
1782
- if (ch === "\u2592") {
1783
- out += this.cyan(ch);
1784
- } else if (ch === "\u2593") {
1785
- out += this.dim(ch);
1786
- } else {
1787
- out += ch;
1788
- }
1789
- }
1790
- return out;
1791
- }
1792
- // ---- Core write helpers ----
1793
- write(text) {
1794
- this.stream.write(text);
1795
- }
1796
- writeErr(text) {
1797
- this.errStream.write(text);
1798
- }
1799
- // ---- Level 1: Header ----
1800
- header(text) {
1801
- if (this.mode === "json") return;
1802
- const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
1803
- this.write(prefix + this.bold(text) + "\n");
1804
- }
1805
- smallHeader(subtitle) {
1806
- if (this.mode === "json" || this.mode === "quiet") return;
1807
- const iconPath = findIconPath("small");
1808
- if (iconPath !== null) {
1809
- try {
1810
- const raw = fs.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
1811
- for (const line of raw.split("\n")) {
1812
- this.write(this.cyan(line) + "\n");
1813
- }
1814
- } catch {
1815
- this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
1816
- }
1817
- } else {
1818
- this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
1819
- }
1820
- if (subtitle !== void 0 && subtitle.length > 0) {
1821
- this.write(this.dim(" " + subtitle) + "\n");
1822
- }
1823
- this.blank();
1824
- }
1825
- banner(subtitle, opts) {
1826
- if (this.mode === "json" || this.mode === "quiet") return;
1827
- const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
1828
- const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
1829
- const iconPath = findIconPath("big");
1830
- const shouldUseFull = opts?.full === true || width >= 120;
1831
- if (shouldUseFull && iconPath !== null) {
1832
- try {
1833
- const raw = fs.readFileSync(iconPath, "utf-8");
1834
- const lines = raw.split("\n");
1835
- for (const line of lines) {
1836
- const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
1837
- const stripped = brandedLine.trimEnd();
1838
- if (stripped.length === 0) {
1839
- this.write("\n");
1840
- continue;
2161
+ stream: false
2162
+ }),
2163
+ signal: task.signal
2164
+ });
2165
+ if (!res.ok) {
2166
+ const body = await res.text().catch(() => "") ?? "";
2167
+ return {
2168
+ ok: false,
2169
+ error: {
2170
+ code: res.status === 401 || res.status === 403 ? "auth_invalid" : "adapter_dispatch_failed",
2171
+ message: `OpenClaw ${res.status}: ${body.slice(0, 400)}`
1841
2172
  }
1842
- const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
1843
- this.write(this.colorBrandLine(clipped) + "\n");
1844
- }
1845
- } catch {
1846
- this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
1847
- this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
2173
+ };
1848
2174
  }
1849
- } else {
1850
- this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
1851
- this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
1852
- }
1853
- if (subtitle !== void 0 && subtitle.length > 0) {
1854
- this.write(this.dim(" " + subtitle) + "\n");
1855
- }
1856
- this.blank();
1857
- }
1858
- // ---- Level 2: Primary data ----
1859
- blank() {
1860
- if (this.mode === "json") return;
1861
- this.write("\n");
1862
- }
1863
- line(text) {
1864
- if (this.mode === "json") return;
1865
- this.write(text + "\n");
1866
- }
1867
- info(text) {
1868
- this.line(text);
1869
- }
1870
- // ---- Level 3: Secondary ----
1871
- secondary(text, indent = 2) {
1872
- if (this.mode === "json") return;
1873
- this.write(" ".repeat(indent) + this.dim(text) + "\n");
1874
- }
1875
- // ---- Level 4: Action tips ----
1876
- tip(text) {
1877
- if (this.mode === "json") return;
1878
- this.write(this.cyan("Tip:") + " " + text + "\n");
1879
- }
1880
- next(text) {
1881
- if (this.mode === "json") return;
1882
- this.write(this.cyan("Next:") + " " + text + "\n");
1883
- }
1884
- // ---- Level 5: Status indicators ----
1885
- ok(text, detail) {
1886
- if (this.mode === "json") return;
1887
- const suffix = detail ? " " + this.dim(detail) : "";
1888
- this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
1889
- }
1890
- success(text, detail) {
1891
- this.ok(text, detail);
1892
- }
1893
- fail(text, detail) {
1894
- if (this.mode === "json") return;
1895
- const suffix = detail ? " " + this.dim(detail) : "";
1896
- this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
1897
- }
1898
- online(text) {
1899
- if (this.mode === "json") return;
1900
- this.write(" " + this.green("\u25CF") + " " + text + "\n");
1901
- }
1902
- offline(text) {
1903
- if (this.mode === "json") return;
1904
- this.write(" " + this.gray("\u25CB") + " " + text + "\n");
1905
- }
1906
- notInstalled(text) {
1907
- if (this.mode === "json") return;
1908
- this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
1909
- }
1910
- pending(text) {
1911
- if (this.mode === "json") return;
1912
- this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
1913
- }
1914
- warn(text, detail) {
1915
- if (this.mode === "json") return;
1916
- const suffix = detail ? " " + this.dim(detail) : "";
1917
- this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
1918
- }
1919
- // ---- Level 6: Error block ----
1920
- error(what, cause, fix) {
1921
- if (this.mode === "json") return;
1922
- this.writeErr(this.red("\u2717") + " " + what + "\n");
1923
- if (cause !== void 0) {
1924
- this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
1925
- }
1926
- if (fix !== void 0) {
1927
- this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
1928
- }
1929
- }
1930
- table(rowsOrOpts, maybeOpts) {
1931
- if (this.mode === "json") return;
1932
- const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
1933
- const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
1934
- if (!opts) throw new Error("table() requires columns");
1935
- const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
1936
- const cols = opts.columns;
1937
- const widths = cols.map((col) => col.length);
1938
- for (const row of rows) {
1939
- cols.forEach((col, i) => {
1940
- const val = row[col] ?? "";
1941
- const w = widths[i] ?? 0;
1942
- if (val.length > w) widths[i] = val.length;
1943
- });
1944
- }
1945
- const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
1946
- if (totalWidth > maxWidth) {
1947
- for (let i = 0; i < rows.length; i++) {
1948
- const row = rows[i];
1949
- if (!row) continue;
1950
- for (const col of cols) {
1951
- const val = row[col] ?? "";
1952
- this.write(" " + this.bold(col + ":") + " " + val + "\n");
1953
- }
1954
- if (i < rows.length - 1) this.write("\n");
2175
+ const json = await res.json();
2176
+ let output = json.choices?.[0]?.message?.content ?? "";
2177
+ const ERR_PREFIX_RE = /^Cannot read properties of undefined \(reading '[^']+'\)\s*\n+/;
2178
+ if (ERR_PREFIX_RE.test(output)) {
2179
+ process.stderr.write(
2180
+ `[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
2181
+ `
2182
+ );
2183
+ output = output.replace(ERR_PREFIX_RE, "");
1955
2184
  }
1956
- return;
1957
- }
1958
- const header = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
1959
- this.write(" " + this.dim(header) + "\n");
1960
- for (const row of rows) {
1961
- const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
1962
- this.write(" " + line + "\n");
1963
- }
1964
- }
1965
- // ---- Spinner ----
1966
- spinner(text) {
1967
- if (this.mode === "quiet" || this.mode === "json") {
1968
- return {
1969
- update() {
1970
- },
1971
- stop() {
1972
- }
1973
- };
1974
- }
1975
- const isTTY = this.stream.isTTY === true;
1976
- if (!isTTY || !this.colorEnabled) {
1977
- this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
1978
2185
  return {
1979
- update: (t) => {
1980
- this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
1981
- },
1982
- stop: (final) => {
1983
- if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
1984
- }
2186
+ ok: true,
2187
+ output,
2188
+ metrics: { durationMs: Date.now() - startedAt }
1985
2189
  };
1986
- }
1987
- let current = text;
1988
- let frameIdx = 0;
1989
- let stopped = false;
1990
- const write = this.write.bind(this);
1991
- const colorFn = this.yellow.bind(this);
1992
- const greenFn = this.green.bind(this);
1993
- function renderFrame() {
1994
- const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
1995
- const line = " " + colorFn(frame) + " " + current;
1996
- write("\r" + line);
1997
- frameIdx++;
1998
- }
1999
- renderFrame();
2000
- const timer = setInterval(renderFrame, 80);
2001
- return {
2002
- update(t) {
2003
- if (stopped) return;
2004
- current = t;
2005
- },
2006
- stop(final) {
2007
- if (stopped) return;
2008
- stopped = true;
2009
- clearInterval(timer);
2010
- write("\r\x1B[2K");
2011
- if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
2190
+ } catch (err) {
2191
+ if (task.signal?.aborted || err?.name === "AbortError") {
2192
+ return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
2012
2193
  }
2013
- };
2014
- }
2015
- // ---- Progress bar ----
2016
- progress(text, total) {
2017
- if (this.mode === "quiet" || this.mode === "json") {
2018
2194
  return {
2019
- update() {
2020
- },
2021
- stop() {
2022
- }
2195
+ ok: false,
2196
+ error: { code: "adapter_dispatch_failed", message: err.message }
2023
2197
  };
2024
2198
  }
2025
- const isTTY = this.stream.isTTY === true;
2026
- const start = Date.now();
2027
- const write = this.write.bind(this);
2028
- const colorFn = this.cyan.bind(this);
2029
- const dimFn = this.dim.bind(this);
2030
- const greenFn = this.green.bind(this);
2031
- let last = 0;
2032
- let lastDetail = "";
2033
- let stopped = false;
2034
- const render = () => {
2035
- if (stopped) return;
2036
- const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
2037
- const pct = Math.floor(frac * 100);
2038
- const width = 20;
2039
- const filled = Math.floor(frac * width);
2040
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
2041
- const elapsed = (Date.now() - start) / 1e3;
2042
- const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
2043
- const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
2044
- const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
2045
- const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
2046
- if (isTTY && this.colorEnabled) {
2047
- write("\r\x1B[2K" + line);
2048
- } else {
2049
- write(line + "\n");
2050
- }
2051
- };
2052
- render();
2053
- return {
2054
- update: (current, detail) => {
2055
- if (stopped) return;
2056
- last = current;
2057
- if (detail !== void 0) lastDetail = detail;
2058
- render();
2059
- },
2060
- stop: (final) => {
2061
- if (stopped) return;
2062
- stopped = true;
2063
- if (isTTY && this.colorEnabled) write("\r\x1B[2K");
2064
- if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
2065
- }
2066
- };
2067
- }
2068
- // ---- JSON output ----
2069
- json(payload, opts) {
2070
- const indent = opts?.pretty ? 2 : void 0;
2071
- this.write(JSON.stringify(payload, null, indent) + "\n");
2072
- }
2073
- result(pretty, jsonPayload) {
2074
- if (this.mode === "pretty") {
2075
- pretty();
2076
- } else {
2077
- this.json(jsonPayload);
2078
- }
2079
- }
2080
- };
2081
- var _ui = null;
2082
- function getUI() {
2083
- if (!_ui) _ui = new UI();
2084
- return _ui;
2085
- }
2086
- function setUI(ui) {
2087
- _ui = ui;
2088
- }
2089
- function applyCommonFlags(argv) {
2090
- let mode = "pretty";
2091
- const isTTY = process.stdout.isTTY === true;
2092
- const noColorEnv = Boolean(process.env["NO_COLOR"]);
2093
- let color2 = isTTY && !noColorEnv;
2094
- const rest = [];
2095
- for (const arg of argv) {
2096
- switch (arg) {
2097
- case "--no-color":
2098
- color2 = false;
2099
- break;
2100
- case "--color":
2101
- color2 = true;
2102
- break;
2103
- case "--json":
2104
- case "--pretty-json":
2105
- mode = "json";
2106
- if (arg === "--json") rest.push(arg);
2107
- break;
2108
- case "--quiet":
2109
- mode = "quiet";
2110
- break;
2111
- default:
2112
- rest.push(arg);
2113
- }
2114
2199
  }
2115
- return { mode, color: color2, restArgv: rest };
2116
- }
2117
-
2118
- // src/cli/util.ts
2119
- var DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
2120
- var ANSI = {
2121
- reset: "\x1B[0m",
2122
- bold: "\x1B[1m",
2123
- dim: "\x1B[2m",
2124
- cyan: "\x1B[36m",
2125
- green: "\x1B[32m",
2126
- yellow: "\x1B[33m",
2127
- red: "\x1B[31m"
2128
2200
  };
2129
- function color(kind, text) {
2130
- if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
2131
- return `${ANSI[kind]}${text}${ANSI.reset}`;
2132
- }
2133
- function printJson(v) {
2134
- getUI().json(v, { pretty: true });
2135
- }
2136
- function exitWithError(message, opts) {
2137
- const o = typeof opts === "number" ? { exitCode: opts } : opts ?? {};
2138
- const exitCode = o.exitCode ?? 1;
2139
- const ui = getUI();
2140
- if (ui.mode === "json") {
2141
- const payload = {
2142
- ok: false,
2143
- error: { code: o.code ?? "cli_error", message },
2144
- ...o.details ? { details: o.details } : {}
2145
- };
2146
- ui.json(payload, { pretty: true });
2147
- } else {
2148
- process.stderr.write(`Error: ${message}
2149
- `);
2150
- }
2151
- process.exit(exitCode);
2152
- }
2153
- function normalizeCloudUrl(input) {
2154
- const raw = input.trim();
2155
- if (!raw) throw new Error("Cloud URL is empty.");
2156
- if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(raw)) {
2157
- let parsed;
2158
- try {
2159
- parsed = new URL(raw);
2160
- } catch {
2161
- throw new Error(`Invalid --cloud URL: ${raw}`);
2162
- }
2163
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2164
- throw new Error(`Invalid --cloud URL scheme: ${parsed.protocol} (expected http:// or https://)`);
2165
- }
2166
- return raw.replace(/\/$/, "");
2167
- }
2168
- if (!/^[A-Za-z0-9.\-_:[\]/]+$/.test(raw)) {
2169
- throw new Error(`Invalid --cloud URL: ${raw} (must be http://\u2026 or https://\u2026 or host:port)`);
2170
- }
2171
- const candidate = `http://${raw}`;
2201
+ async function checkHealth2(baseUrl, apiKey) {
2172
2202
  try {
2173
- new URL(candidate);
2203
+ const res = await fetch(`${baseUrl}/health`, {
2204
+ headers: { Authorization: `Bearer ${apiKey}` },
2205
+ signal: AbortSignal.timeout(2e3)
2206
+ });
2207
+ return res.ok;
2174
2208
  } catch {
2175
- throw new Error(`Invalid --cloud URL: ${raw}`);
2209
+ return false;
2176
2210
  }
2177
- return candidate.replace(/\/$/, "");
2178
2211
  }
2179
- function runAction(fn, opts = {}) {
2180
- return async (...args) => {
2181
- try {
2182
- await fn(...args);
2183
- } catch (err) {
2184
- const raw = err instanceof Error ? err.message : String(err);
2185
- const message = opts.sanitize ? opts.sanitize(raw) : raw;
2186
- exitWithError(message, { code: opts.code });
2187
- }
2212
+
2213
+ // src/config.ts
2214
+ import * as TOML from "@iarna/toml";
2215
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2216
+ import { homedir as homedir2 } from "os";
2217
+ import { dirname as dirname2, join as join2 } from "path";
2218
+ import { z as z5 } from "zod";
2219
+ var ConfigSchema = z5.object({
2220
+ /** API key from `prismer setup`, or env override. */
2221
+ api_key: z5.string().min(1),
2222
+ /** Cloud REST + WS base; `ws://` is derived by stripping `http`. */
2223
+ cloud_api_base: z5.string().url(),
2224
+ /** Stable per-machine daemon identifier. Generated once on first setup. */
2225
+ daemon_id: z5.string().min(1),
2226
+ /** Optional adapter-specific overrides keyed by adapter name. */
2227
+ adapters: z5.record(z5.string(), z5.record(z5.string(), z5.unknown())).optional(),
2228
+ /** Local daemon shell execution. Default disabled. */
2229
+ shell: z5.object({
2230
+ enabled: z5.boolean().default(false),
2231
+ default_cwd: z5.string().optional(),
2232
+ defaultCwd: z5.string().optional(),
2233
+ shell: z5.enum(["bash", "zsh", "sh"]).optional(),
2234
+ max_timeout_ms: z5.number().int().positive().optional(),
2235
+ maxTimeoutMs: z5.number().int().positive().optional(),
2236
+ max_output_bytes: z5.number().int().positive().optional(),
2237
+ maxOutputBytes: z5.number().int().positive().optional(),
2238
+ allowed_workspaces: z5.array(z5.string()).optional(),
2239
+ allowedWorkspaces: z5.array(z5.string()).optional()
2240
+ }).optional(),
2241
+ /** Local cache settings. */
2242
+ cache: z5.object({
2243
+ max_bytes: z5.number().int().positive().default(5 * 1024 * 1024 * 1024)
2244
+ }).optional()
2245
+ });
2246
+ function resolvePaths(home) {
2247
+ const root = home ?? process.env.PRISMER_HOME ?? join2(homedir2(), ".prismer");
2248
+ return {
2249
+ root,
2250
+ configFile: join2(root, "config.toml"),
2251
+ localDb: join2(root, "local.db"),
2252
+ cacheDir: join2(root, "cache"),
2253
+ logsDir: join2(root, "logs"),
2254
+ runsDir: join2(root, "runs")
2188
2255
  };
2189
2256
  }
2190
- function printBanner(opts = {}) {
2191
- const ui = getUI();
2192
- if (opts.compact) {
2193
- ui.smallHeader("Runtime CLI v1.9.3");
2194
- return;
2195
- }
2196
- ui.banner("Runtime CLI v1.9.3", { full: true });
2197
- }
2198
- function ok(label, detail) {
2199
- getUI().ok(label, detail);
2200
- }
2201
- function warn(label, detail) {
2202
- getUI().warn(label, detail);
2203
- }
2204
- function fail(label, detail) {
2205
- getUI().fail(label, detail);
2206
- }
2207
- function tip(command, detail) {
2208
- const text = detail ? `${command} ${detail}` : command;
2209
- getUI().tip(text);
2210
- }
2211
- function pidFilePath(paths) {
2212
- return join3(paths.root, "daemon.pid");
2213
- }
2214
- function writePidFile(paths, pid) {
2215
- writeFileSync3(pidFilePath(paths), `${pid}
2216
- `, "utf8");
2217
- }
2218
- function readPidFile(paths) {
2219
- const p = pidFilePath(paths);
2220
- if (!existsSync4(p)) return void 0;
2221
- const raw = readFileSync4(p, "utf8").trim();
2222
- const pid = Number.parseInt(raw, 10);
2223
- return Number.isFinite(pid) ? pid : void 0;
2257
+ function configExists(paths = resolvePaths()) {
2258
+ return existsSync2(paths.configFile);
2224
2259
  }
2225
- function clearPidFile(paths) {
2226
- const p = pidFilePath(paths);
2227
- if (existsSync4(p)) {
2228
- try {
2229
- unlinkSync(p);
2230
- } catch {
2231
- }
2260
+ function loadConfig(paths = resolvePaths()) {
2261
+ if (!existsSync2(paths.configFile)) {
2262
+ throw new Error(
2263
+ `Config not found at ${paths.configFile}. Run \`prismer setup\` to create it.`
2264
+ );
2265
+ }
2266
+ const raw = readFileSync2(paths.configFile, "utf8");
2267
+ const parsed = TOML.parse(raw);
2268
+ const merged = {
2269
+ ...parsed,
2270
+ api_key: process.env.PRISMER_API_KEY ?? parsed.api_key,
2271
+ cloud_api_base: process.env.PRISMER_BASE_URL ?? parsed.cloud_api_base
2272
+ };
2273
+ const result = ConfigSchema.safeParse(merged);
2274
+ if (!result.success) {
2275
+ const detail = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
2276
+ throw new Error(`Invalid config at ${paths.configFile}: ${detail}`);
2232
2277
  }
2278
+ return result.data;
2233
2279
  }
2234
- function pidAlive(pid) {
2235
- try {
2236
- process.kill(pid, 0);
2237
- return true;
2238
- } catch {
2239
- return false;
2280
+ function saveConfig(config, paths = resolvePaths()) {
2281
+ if (!existsSync2(paths.root)) {
2282
+ mkdirSync2(paths.root, { recursive: true });
2240
2283
  }
2284
+ if (!existsSync2(dirname2(paths.configFile))) {
2285
+ mkdirSync2(dirname2(paths.configFile), { recursive: true });
2286
+ }
2287
+ ConfigSchema.parse(config);
2288
+ writeFileSync2(paths.configFile, TOML.stringify(config), "utf8");
2289
+ }
2290
+ function deriveWsUrl(httpBase) {
2291
+ const u = new URL(httpBase);
2292
+ u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
2293
+ u.pathname = (u.pathname.replace(/\/$/, "") || "") + "/ws";
2294
+ return u.toString();
2241
2295
  }
2242
2296
 
2243
2297
  // src/cli/commands/adapter.ts
2298
+ init_util();
2299
+ init_ui();
2244
2300
  var BUILTIN_ADAPTERS = [hermesAdapter, claudeCodeAdapter, openclawAdapter, codexAdapter];
2245
2301
  var INSTALL_SPECS = {
2246
2302
  "claude-code": {
@@ -2991,6 +3047,32 @@ var MIGRATIONS = [
2991
3047
  );
2992
3048
  CREATE INDEX IF NOT EXISTS idx_files_hash ON workspace_files_mirror (content_hash);
2993
3049
  `
3050
+ },
3051
+ {
3052
+ version: 3,
3053
+ up: `
3054
+ -- Asset metadata index (#filename reference resolution \u2014 daemon/asset/metadata-index.ts)
3055
+ CREATE TABLE IF NOT EXISTS asset_metadata_index (
3056
+ workspace_id TEXT NOT NULL,
3057
+ asset_id TEXT NOT NULL,
3058
+ content_hash TEXT NOT NULL,
3059
+ filename TEXT,
3060
+ folder_path TEXT,
3061
+ mime TEXT NOT NULL,
3062
+ kind TEXT NOT NULL,
3063
+ size_bytes INTEGER NOT NULL DEFAULT 0,
3064
+ description TEXT,
3065
+ asset_index_seq INTEGER NOT NULL,
3066
+ updated_at INTEGER NOT NULL,
3067
+ PRIMARY KEY (workspace_id, asset_id)
3068
+ );
3069
+
3070
+ CREATE INDEX IF NOT EXISTS idx_asset_meta_filename
3071
+ ON asset_metadata_index(workspace_id, filename);
3072
+
3073
+ CREATE INDEX IF NOT EXISTS idx_asset_meta_seq
3074
+ ON asset_metadata_index(workspace_id, asset_index_seq);
3075
+ `
2994
3076
  }
2995
3077
  ];
2996
3078
  function runSql(db, sql) {
@@ -3023,6 +3105,8 @@ function runMigrations(db) {
3023
3105
  }
3024
3106
 
3025
3107
  // src/cli/commands/agent.ts
3108
+ init_util();
3109
+ init_ui();
3026
3110
  var ADAPTER_BINARY = {
3027
3111
  "claude-code": "claude",
3028
3112
  codex: "codex",
@@ -3404,6 +3488,8 @@ function whichBinary2(bin) {
3404
3488
  import { Command as Command3 } from "commander";
3405
3489
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3406
3490
  import { basename } from "path";
3491
+ init_util();
3492
+ init_ui();
3407
3493
  function buildAssetCommand() {
3408
3494
  const cmd = new Command3("asset").description("Inspect IM assets");
3409
3495
  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) => {
@@ -3599,6 +3685,7 @@ function stringField(obj, key) {
3599
3685
  }
3600
3686
 
3601
3687
  // src/cli/commands/banner.ts
3688
+ init_util();
3602
3689
  import { Command as Command4 } from "commander";
3603
3690
  function buildBannerCommand() {
3604
3691
  return new Command4("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) => {
@@ -3608,6 +3695,8 @@ function buildBannerCommand() {
3608
3695
 
3609
3696
  // src/cli/commands/chat.ts
3610
3697
  import { Command as Command5 } from "commander";
3698
+ init_util();
3699
+ init_ui();
3611
3700
  function buildChatCommand() {
3612
3701
  const cmd = new Command5("chat").description("Use IM chat and group APIs");
3613
3702
  cmd.command("me").description("Show the current IM identity").option("--json", "Print raw JSON response").action(async (opts) => {
@@ -3782,6 +3871,8 @@ function sanitizeError(message) {
3782
3871
 
3783
3872
  // src/cli/commands/config.ts
3784
3873
  import { Command as Command6 } from "commander";
3874
+ init_util();
3875
+ init_ui();
3785
3876
  var SETTABLE_KEYS = ["cloud_api_base", "api_key", "daemon_id"];
3786
3877
  function redactApiKey(key) {
3787
3878
  if (!key.startsWith("sk-prismer-")) return "***";
@@ -3877,6 +3968,8 @@ function buildConfigCommand() {
3877
3968
 
3878
3969
  // src/cli/commands/cookbook.ts
3879
3970
  import { Command as Command7 } from "commander";
3971
+ init_util();
3972
+ init_ui();
3880
3973
  function buildCookbookCommand() {
3881
3974
  const cmd = new Command7("cookbook").description("Run CLI-only 54release MVP regression suites");
3882
3975
  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) => {
@@ -4134,13 +4227,13 @@ function parsePositiveInt2(value) {
4134
4227
  // src/cli/commands/daemon.ts
4135
4228
  import { Command as Command8 } from "commander";
4136
4229
  import { spawn as spawn5 } from "child_process";
4137
- import { createReadStream, existsSync as existsSync12, mkdirSync as mkdirSync7, openSync, statSync as statSync3 } from "fs";
4230
+ import { createReadStream, existsSync as existsSync13, mkdirSync as mkdirSync8, openSync, statSync as statSync3 } from "fs";
4138
4231
  import { setTimeout as sleep } from "timers/promises";
4139
- import { join as join10 } from "path";
4232
+ import { join as join11 } from "path";
4140
4233
 
4141
4234
  // src/daemon/runner.ts
4142
4235
  import { EventEmitter as EventEmitter3 } from "events";
4143
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
4236
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
4144
4237
  import { platform } from "os";
4145
4238
 
4146
4239
  // src/adapters/registry.ts
@@ -4675,6 +4768,14 @@ async function handleDispatch(payload, requestId, deps) {
4675
4768
  sendReply(deps.ws, reply, requestId);
4676
4769
  return reply;
4677
4770
  }
4771
+ let hashRefResult = { text: payload.prompt, resolutions: [] };
4772
+ if (deps.assetMetadataIndexes && profile.workspaceId) {
4773
+ const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
4774
+ if (assetIndex) {
4775
+ hashRefResult = await resolveHashRefs(payload.prompt, assetIndex, deps.cloud);
4776
+ payload.prompt = hashRefResult.text;
4777
+ }
4778
+ }
4678
4779
  const rewrittenPrompt = await deps.uriResolver.rewrite(payload.prompt, { pin: true });
4679
4780
  resolvedHashes.push(...rewrittenPrompt.resolvedHashes);
4680
4781
  let rewrittenContext = [];
@@ -4889,6 +4990,84 @@ function isTextLikeMime(mime) {
4889
4990
  if (m.endsWith("+json") || m.endsWith("+xml") || m.endsWith("+csv")) return true;
4890
4991
  return false;
4891
4992
  }
4993
+ var HASH_REF_RE = /(?:^|\s)#([^\s#]+)/g;
4994
+ var HEX_COLOR_RE = /^[0-9a-fA-F]{3,8}$/;
4995
+ var FILE_EXT_RE = /\.[a-zA-Z0-9]{1,10}$/;
4996
+ var TRAILING_PUNCT_RE = /[,.;:!?)\]}'"]+$/;
4997
+ async function resolveHashRefs(prompt, assetIndex, cloud) {
4998
+ const resolutions = [];
4999
+ const candidates = [];
5000
+ let match;
5001
+ const re = new RegExp(HASH_REF_RE.source, "g");
5002
+ while ((match = re.exec(prompt)) !== null) {
5003
+ const refName = match[1];
5004
+ const leading = match[0].startsWith("#") ? 0 : 1;
5005
+ const start = match.index + leading;
5006
+ const end = match.index + match[0].length;
5007
+ if (HEX_COLOR_RE.test(refName)) continue;
5008
+ let cleanRef = refName;
5009
+ let stripped = "";
5010
+ const punctMatch = TRAILING_PUNCT_RE.exec(cleanRef);
5011
+ if (punctMatch) {
5012
+ stripped = punctMatch[0];
5013
+ cleanRef = cleanRef.slice(0, -stripped.length);
5014
+ }
5015
+ if (!cleanRef) continue;
5016
+ if (HEX_COLOR_RE.test(cleanRef)) continue;
5017
+ const hasExtension = FILE_EXT_RE.test(cleanRef);
5018
+ candidates.push({ ref: cleanRef, start, end: end - stripped.length, hasExtension });
5019
+ }
5020
+ if (candidates.length === 0) {
5021
+ return { text: prompt, resolutions: [] };
5022
+ }
5023
+ const allFilenames = candidates.map((c) => c.ref);
5024
+ const localResults = assetIndex.resolveByFilenames(allFilenames);
5025
+ const needsCloud = candidates.filter(
5026
+ (c) => c.hasExtension && !localResults.has(c.ref)
5027
+ );
5028
+ const cloudResults = /* @__PURE__ */ new Map();
5029
+ if (needsCloud.length > 0) {
5030
+ await Promise.allSettled(
5031
+ needsCloud.map(async (c) => {
5032
+ try {
5033
+ const items = await cloud.get(
5034
+ `/api/im/assets?workspaceId=${encodeURIComponent(assetIndex.workspaceId)}&q=${encodeURIComponent(c.ref)}&limit=1`
5035
+ );
5036
+ if (Array.isArray(items) && items.length > 0) {
5037
+ const item = items[0];
5038
+ cloudResults.set(c.ref, item.contentHash);
5039
+ }
5040
+ } catch {
5041
+ }
5042
+ })
5043
+ );
5044
+ }
5045
+ for (const c of candidates) {
5046
+ const local = localResults.get(c.ref);
5047
+ if (local) {
5048
+ resolutions.push({
5049
+ ref: c.ref,
5050
+ start: c.start,
5051
+ end: c.end,
5052
+ resolvedUri: `prismer://workspace/${encodeURIComponent(assetIndex.workspaceId)}/asset/${local.contentHash}`
5053
+ });
5054
+ } else if (c.hasExtension) {
5055
+ const cloudHash = cloudResults.get(c.ref);
5056
+ resolutions.push({
5057
+ ref: c.ref,
5058
+ start: c.start,
5059
+ end: c.end,
5060
+ resolvedUri: cloudHash ? `prismer://workspace/${encodeURIComponent(assetIndex.workspaceId)}/asset/${cloudHash}` : void 0
5061
+ });
5062
+ }
5063
+ }
5064
+ let result = prompt;
5065
+ const sorted = [...resolutions].filter((r) => r.resolvedUri).sort((a, b) => b.start - a.start);
5066
+ for (const r of sorted) {
5067
+ result = result.slice(0, r.start) + r.resolvedUri + result.slice(r.end);
5068
+ }
5069
+ return { text: result, resolutions };
5070
+ }
4892
5071
  async function resolveAssetRefs(refs, cache) {
4893
5072
  const out = { promptBlocks: [], observability: [], pinnedHashes: [] };
4894
5073
  if (!refs || refs.length === 0) return out;
@@ -4961,8 +5140,8 @@ async function resolveAssetRefs(refs, cache) {
4961
5140
  return out;
4962
5141
  }
4963
5142
  function formatInlineAssetBlock(ref, mime, body, strategy) {
4964
- const header = `[Attached file] id=${ref.assetId} mime=${mime ?? "unknown"}${strategy === "inline-text-truncated" ? " (truncated)" : ""}`;
4965
- return `${header}
5143
+ const header2 = `[Attached file] id=${ref.assetId} mime=${mime ?? "unknown"}${strategy === "inline-text-truncated" ? " (truncated)" : ""}`;
5144
+ return `${header2}
4966
5145
  ---
4967
5146
  ${body}
4968
5147
  ---`;
@@ -5265,20 +5444,35 @@ var LocalServer = class {
5265
5444
  respond(res, 204, null);
5266
5445
  return;
5267
5446
  }
5447
+ const handlers = [];
5268
5448
  if (this.opts.attachMemory) {
5269
- void this.opts.attachMemory(req, res).then((handled) => {
5270
- if (handled) return;
5271
- this.routeStandard(req, res);
5272
- }).catch((err) => {
5273
- respond(res, 500, {
5274
- error: "attach_memory_threw",
5275
- message: err instanceof Error ? err.message : String(err)
5276
- });
5277
- });
5449
+ handlers.push({ name: "memory", fn: this.opts.attachMemory });
5450
+ }
5451
+ if (this.opts.attachAsset) {
5452
+ handlers.push({ name: "asset", fn: this.opts.attachAsset });
5453
+ }
5454
+ if (handlers.length > 0) {
5455
+ void this.runHandlers(req, res, handlers, 0);
5278
5456
  return;
5279
5457
  }
5280
5458
  this.routeStandard(req, res);
5281
5459
  }
5460
+ async runHandlers(req, res, handlers, idx) {
5461
+ if (idx >= handlers.length) {
5462
+ this.routeStandard(req, res);
5463
+ return;
5464
+ }
5465
+ try {
5466
+ const handled = await handlers[idx].fn(req, res);
5467
+ if (handled) return;
5468
+ await this.runHandlers(req, res, handlers, idx + 1);
5469
+ } catch (err) {
5470
+ respond(res, 500, {
5471
+ error: `attach_${handlers[idx].name}_threw`,
5472
+ message: err instanceof Error ? err.message : String(err)
5473
+ });
5474
+ }
5475
+ }
5282
5476
  routeStandard(req, res) {
5283
5477
  const url = req.url ?? "/";
5284
5478
  if (req.method === "GET" && url === "/healthz") {
@@ -5293,7 +5487,8 @@ var LocalServer = class {
5293
5487
  wsConnected: state.wsConnected,
5294
5488
  hostedAgents: state.hostedAgents,
5295
5489
  observability: state.observability,
5296
- memoryReady: this.opts.attachMemory != null
5490
+ memoryReady: this.opts.attachMemory != null,
5491
+ assetReady: this.opts.attachAsset != null
5297
5492
  });
5298
5493
  return;
5299
5494
  }
@@ -5747,13 +5942,14 @@ var MemoryStore = class {
5747
5942
  const pageId = existing?.id ?? `page_${randomUUID2().replace(/-/g, "").slice(0, 22)}`;
5748
5943
  const newVersion = (existing?.version ?? 0) + 1;
5749
5944
  const createdAt = existing?.createdAt ?? now;
5945
+ const staleFlag = input.stale ? 1 : 0;
5750
5946
  const insertPage = db.prepare(`
5751
5947
  INSERT INTO memory_pages (
5752
5948
  id, workspaceId, path, title, description, contentHash, version,
5753
5949
  pageType, visibilityKind, visibilityImUserId, encrypted, stale,
5754
5950
  archivedAt, sourceAssetId, sourceRefsJson, syncStatus,
5755
5951
  createdAt, updatedAt
5756
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, 'local-only', ?, ?)
5952
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, NULL, ?, ?, 'local-only', ?, ?)
5757
5953
  ON CONFLICT(workspaceId, path) DO UPDATE SET
5758
5954
  title = excluded.title,
5759
5955
  description = excluded.description,
@@ -5764,6 +5960,7 @@ var MemoryStore = class {
5764
5960
  visibilityImUserId = excluded.visibilityImUserId,
5765
5961
  sourceAssetId = excluded.sourceAssetId,
5766
5962
  sourceRefsJson = excluded.sourceRefsJson,
5963
+ stale = excluded.stale,
5767
5964
  updatedAt = excluded.updatedAt
5768
5965
  `);
5769
5966
  const insertVersion = db.prepare(`
@@ -5791,6 +5988,7 @@ var MemoryStore = class {
5791
5988
  input.pageType ?? "leaf",
5792
5989
  visibility.kind,
5793
5990
  visibilityImUserId,
5991
+ staleFlag,
5794
5992
  input.sourceAssetId ?? null,
5795
5993
  sourceRefsJson,
5796
5994
  createdAt,
@@ -5871,6 +6069,16 @@ var MemoryStore = class {
5871
6069
  dbPath: this.opts.dbPath
5872
6070
  };
5873
6071
  }
6072
+ /**
6073
+ * Record sync cursor for incremental sync. Used by cloud-sync.ts to
6074
+ * persist the high-water mark for future cursor-based catch-up.
6075
+ */
6076
+ recordCursor(workspaceId, cursor) {
6077
+ const now = Date.now();
6078
+ this.requireDb().prepare(
6079
+ `INSERT OR REPLACE INTO memory_inbox_cursor (workspaceId, cursor, updatedAt) VALUES (?, ?, ?)`
6080
+ ).run(workspaceId, cursor, now);
6081
+ }
5874
6082
  /**
5875
6083
  * Internal accessor for outbox.ts — outbox writes its own table within the
5876
6084
  * same DB. Returning the live Database handle keeps outbox transactions
@@ -6521,6 +6729,84 @@ function defaultLog() {
6521
6729
  };
6522
6730
  }
6523
6731
 
6732
+ // src/daemon/memory/cloud-sync.ts
6733
+ var LOG = "[CloudMemorySync]";
6734
+ var CLOUD_PAGE_LIMIT = 300;
6735
+ async function initialSyncFromCloud(runtime, cloud, workspaceId) {
6736
+ const slot = runtime.peek(workspaceId);
6737
+ if (!slot) {
6738
+ console.log(`${LOG} No store for workspace=${workspaceId} \u2014 skipping`);
6739
+ return { pulled: 0, skipped: 0 };
6740
+ }
6741
+ const cursorRow = slot.store.rawDb().prepare("SELECT cursor FROM memory_inbox_cursor WHERE workspaceId = ?").get(workspaceId);
6742
+ if (cursorRow) {
6743
+ console.log(`${LOG} Workspace=${workspaceId} already synced (cursor: ${cursorRow.cursor.slice(0, 20)}...) \u2014 skip`);
6744
+ return { pulled: 0, skipped: 0 };
6745
+ }
6746
+ console.log(`${LOG} Fetching cloud pages for workspace=${workspaceId}...`);
6747
+ const resp = await cloud.request(
6748
+ "GET",
6749
+ `/api/im/memory/pages?workspaceId=${encodeURIComponent(workspaceId)}&limit=${CLOUD_PAGE_LIMIT}&stale=all`,
6750
+ { timeoutMs: 15e3 }
6751
+ );
6752
+ if (!resp.ok) {
6753
+ console.warn(
6754
+ `${LOG} Cloud GET /memory/pages returned ${resp.status}: ${resp.error?.message ?? "unknown"}`
6755
+ );
6756
+ return { pulled: 0, skipped: 0 };
6757
+ }
6758
+ const envelope2 = resp.data;
6759
+ if (!envelope2 || !envelope2.ok) {
6760
+ console.log(`${LOG} Cloud returned non-ok envelope for workspace=${workspaceId}`);
6761
+ return { pulled: 0, skipped: 0 };
6762
+ }
6763
+ const pages = envelope2.data;
6764
+ if (!pages || !Array.isArray(pages) || pages.length === 0) {
6765
+ console.log(`${LOG} No cloud pages to sync for workspace=${workspaceId}`);
6766
+ return { pulled: 0, skipped: 0 };
6767
+ }
6768
+ let pulled = 0;
6769
+ let skipped = 0;
6770
+ for (const page of pages) {
6771
+ let content = page.content ?? "";
6772
+ if (!content) {
6773
+ try {
6774
+ const detailResp = await cloud.request(
6775
+ "GET",
6776
+ `/api/im/memory/pages/${encodeURIComponent(page.id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
6777
+ { timeoutMs: 5e3 }
6778
+ );
6779
+ if (detailResp.ok && detailResp.data?.data?.content) {
6780
+ content = detailResp.data.data.content;
6781
+ }
6782
+ } catch {
6783
+ }
6784
+ }
6785
+ const visibility = page.visibility === "agent" ? { kind: "agent", imUserId: "" } : { kind: "workspace" };
6786
+ try {
6787
+ slot.store.write({
6788
+ workspaceId,
6789
+ path: page.path,
6790
+ title: page.title ?? void 0,
6791
+ content: content || "",
6792
+ pageType: page.pageType || "leaf",
6793
+ visibility,
6794
+ actorImUserId: "cloud-sync",
6795
+ actorKind: "agent"
6796
+ });
6797
+ pulled++;
6798
+ } catch (err) {
6799
+ console.warn(`${LOG} write failed for ${page.path}:`, err.message);
6800
+ skipped++;
6801
+ }
6802
+ }
6803
+ slot.store.recordCursor(workspaceId, `synced:${Date.now()}`);
6804
+ console.log(
6805
+ `${LOG} Synced ${pulled} pages${skipped ? `, ${skipped} skipped` : ""} for workspace=${workspaceId}`
6806
+ );
6807
+ return { pulled, skipped };
6808
+ }
6809
+
6524
6810
  // src/daemon/memory/runner-wiring.ts
6525
6811
  function attachMemoryRunner(opts) {
6526
6812
  const runtime = new MemoryRuntime({ baseDir: opts.baseDir, deviceId: opts.deviceId });
@@ -6551,6 +6837,24 @@ function attachMemoryRunner(opts) {
6551
6837
  }
6552
6838
  };
6553
6839
  }
6840
+ async function syncMemoryFromCloud(wiring, cloud, workspaceIds) {
6841
+ const uniqueIds = [...new Set(workspaceIds.filter(Boolean))];
6842
+ if (uniqueIds.length === 0) return;
6843
+ console.log(`[MemorySync] Initial cloud-to-local sync for ${uniqueIds.length} workspace(s)...`);
6844
+ for (const wsId of uniqueIds) {
6845
+ try {
6846
+ wiring.runtime.resolve(wsId);
6847
+ const result = await initialSyncFromCloud(wiring.runtime, cloud, wsId);
6848
+ if (result.pulled > 0 || result.skipped > 0) {
6849
+ console.log(
6850
+ `[MemorySync] workspace=${wsId}: ${result.pulled} pulled, ${result.skipped} skipped`
6851
+ );
6852
+ }
6853
+ } catch (err) {
6854
+ console.error(`[MemorySync] workspace=${wsId} failed:`, err.message);
6855
+ }
6856
+ }
6857
+ }
6554
6858
 
6555
6859
  // src/daemon/memory/fork/select-memories.ts
6556
6860
  var SELECT_MEMORIES_SYSTEM_PROMPT = [
@@ -6958,6 +7262,233 @@ function parsePrismerUri(uri) {
6958
7262
  return { workspaceId, path: path7 };
6959
7263
  }
6960
7264
 
7265
+ // src/daemon/asset/metadata-index.ts
7266
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
7267
+ import { join as join9 } from "path";
7268
+ var DEFAULT_LIMIT = 8;
7269
+ var PULL_PAGE_SIZE = 500;
7270
+ var THROTTLE_MS = 3e4;
7271
+ function rowToMetadata(row) {
7272
+ return {
7273
+ assetId: row.asset_id,
7274
+ contentHash: row.content_hash,
7275
+ filename: row.filename,
7276
+ folderPath: row.folder_path,
7277
+ mime: row.mime,
7278
+ kind: row.kind,
7279
+ sizeBytes: row.size_bytes,
7280
+ description: row.description,
7281
+ assetIndexSeq: row.asset_index_seq
7282
+ };
7283
+ }
7284
+ var AssetMetadataIndex = class {
7285
+ db;
7286
+ cloud;
7287
+ /** Workspace ID — exposed for prismer:// URI construction. */
7288
+ workspaceId;
7289
+ cursorPath;
7290
+ _lastSyncMs = 0;
7291
+ constructor(opts) {
7292
+ this.db = opts.db;
7293
+ this.cloud = opts.cloud;
7294
+ this.workspaceId = opts.workspaceId;
7295
+ if (!existsSync10(opts.workspaceStateDir)) {
7296
+ mkdirSync7(opts.workspaceStateDir, { recursive: true });
7297
+ }
7298
+ this.cursorPath = join9(opts.workspaceStateDir, "asset-metadata-cursor.json");
7299
+ }
7300
+ /** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
7301
+ readCursor() {
7302
+ if (!existsSync10(this.cursorPath)) return 0;
7303
+ try {
7304
+ const parsed = JSON.parse(readFileSync8(this.cursorPath, "utf8"));
7305
+ if (parsed.workspaceId !== this.workspaceId) return 0;
7306
+ return parsed.cursor;
7307
+ } catch {
7308
+ return 0;
7309
+ }
7310
+ }
7311
+ writeCursor(cursor) {
7312
+ const payload = {
7313
+ workspaceId: this.workspaceId,
7314
+ cursor,
7315
+ writtenAt: Date.now()
7316
+ };
7317
+ writeFileSync7(this.cursorPath, JSON.stringify(payload, null, 2));
7318
+ }
7319
+ /**
7320
+ * Pull incremental asset metadata changes since the persisted cursor and
7321
+ * upsert into the local index. Newer rows overwrite older ones by
7322
+ * (workspace_id, asset_id) primary key.
7323
+ *
7324
+ * Returns the count of items applied + the new cursor. Throttled: if called
7325
+ * within 30s of the last successful pull, returns immediately.
7326
+ */
7327
+ async pullDelta(opts) {
7328
+ const now = Date.now();
7329
+ if (now - this._lastSyncMs < THROTTLE_MS) {
7330
+ return { applied: 0, cursor: this.readCursor() };
7331
+ }
7332
+ const since = this.readCursor();
7333
+ const sinceParam = since > 0 ? `&since=${since}` : "";
7334
+ const envelope2 = await this.cloud.get(
7335
+ `/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
7336
+ { signal: opts?.signal }
7337
+ );
7338
+ const upsert = this.db.prepare(`
7339
+ INSERT INTO asset_metadata_index
7340
+ (workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
7341
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7342
+ ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
7343
+ content_hash = excluded.content_hash,
7344
+ filename = excluded.filename,
7345
+ folder_path = excluded.folder_path,
7346
+ mime = excluded.mime,
7347
+ kind = excluded.kind,
7348
+ size_bytes = excluded.size_bytes,
7349
+ description = excluded.description,
7350
+ asset_index_seq = excluded.asset_index_seq,
7351
+ updated_at = excluded.updated_at
7352
+ `);
7353
+ const nowTs = Date.now();
7354
+ let applied = 0;
7355
+ const tx = this.db.transaction((items) => {
7356
+ for (const item of items) {
7357
+ upsert.run(
7358
+ this.workspaceId,
7359
+ item.assetId,
7360
+ item.contentHash,
7361
+ item.filename ?? null,
7362
+ item.folderPath ?? null,
7363
+ item.mime,
7364
+ item.kind,
7365
+ item.sizeBytes,
7366
+ item.description ?? null,
7367
+ item.assetIndexSeq,
7368
+ nowTs
7369
+ );
7370
+ applied += 1;
7371
+ }
7372
+ });
7373
+ try {
7374
+ tx(envelope2.items);
7375
+ this.writeCursor(envelope2.cursor);
7376
+ } catch (err) {
7377
+ throw err;
7378
+ }
7379
+ this._lastSyncMs = now;
7380
+ return { applied, cursor: envelope2.cursor };
7381
+ }
7382
+ /**
7383
+ * Search local index by filename or description substring.
7384
+ * Escapes LIKE wildcards (% and _). Default limit 8.
7385
+ */
7386
+ search(query, limit) {
7387
+ const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
7388
+ const pattern = `%${escaped}%`;
7389
+ const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
7390
+ const rows = this.db.prepare(
7391
+ `SELECT * FROM asset_metadata_index
7392
+ WHERE workspace_id = ?
7393
+ AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
7394
+ ORDER BY asset_index_seq DESC
7395
+ LIMIT ?`
7396
+ ).all(this.workspaceId, pattern, pattern, limitVal);
7397
+ return rows.map(rowToMetadata);
7398
+ }
7399
+ /** Exact match on filename column. Returns undefined if not indexed. */
7400
+ resolveByFilename(filename) {
7401
+ const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
7402
+ return row ? rowToMetadata(row) : void 0;
7403
+ }
7404
+ /** Batch exact match — returns Map for O(1) access. */
7405
+ resolveByFilenames(filenames) {
7406
+ if (filenames.length === 0) return /* @__PURE__ */ new Map();
7407
+ const placeholders = filenames.map(() => "?").join(",");
7408
+ const params = [this.workspaceId, ...filenames];
7409
+ const rows = this.db.prepare(
7410
+ `SELECT * FROM asset_metadata_index
7411
+ WHERE workspace_id = ? AND filename IN (${placeholders})`
7412
+ ).all(...params);
7413
+ const map = /* @__PURE__ */ new Map();
7414
+ for (const row of rows) {
7415
+ if (row.filename) map.set(row.filename, rowToMetadata(row));
7416
+ }
7417
+ return map;
7418
+ }
7419
+ };
7420
+
7421
+ // src/daemon/asset/rpc.ts
7422
+ var ASSET_PATH_PREFIX = "/local/asset/";
7423
+ function attachAssetRpc(opts) {
7424
+ return async (req, res) => {
7425
+ const url = req.url ?? "/";
7426
+ if (!url.startsWith(ASSET_PATH_PREFIX)) return false;
7427
+ const [pathOnly = ""] = url.split("?", 2);
7428
+ const subpath = pathOnly.slice(ASSET_PATH_PREFIX.length);
7429
+ const method = req.method ?? "GET";
7430
+ try {
7431
+ if (method === "POST" && subpath === "search") {
7432
+ const body = await readJson3(req);
7433
+ return handleSearch2(opts.resolveIndex, body, res);
7434
+ }
7435
+ respond3(res, 404, { error: "asset_route_not_found", path: url });
7436
+ return true;
7437
+ } catch (err) {
7438
+ respond3(res, 500, {
7439
+ error: "asset_rpc_failed",
7440
+ message: err instanceof Error ? err.message : String(err)
7441
+ });
7442
+ return true;
7443
+ }
7444
+ };
7445
+ }
7446
+ function handleSearch2(resolveIndex, body, res) {
7447
+ if (!body || typeof body !== "object") {
7448
+ return respond4002(res, "request body must be a JSON object");
7449
+ }
7450
+ const b = body;
7451
+ if (typeof b.workspaceId !== "string" || !b.workspaceId) {
7452
+ return respond4002(res, "workspaceId is required (string)");
7453
+ }
7454
+ if (typeof b.query !== "string" || !b.query.trim()) {
7455
+ return respond4002(res, "query is required (non-empty string)");
7456
+ }
7457
+ const index = resolveIndex(b.workspaceId);
7458
+ if (!index) {
7459
+ respond3(res, 404, {
7460
+ error: "workspace_index_not_found",
7461
+ workspaceId: b.workspaceId,
7462
+ message: "No asset metadata index for this workspace. Ensure the daemon has synced asset metadata."
7463
+ });
7464
+ return true;
7465
+ }
7466
+ const limit = typeof b.limit === "number" && b.limit > 0 ? b.limit : void 0;
7467
+ const items = index.search(b.query.trim(), limit);
7468
+ respond3(res, 200, { items });
7469
+ return true;
7470
+ }
7471
+ function respond3(res, status, body) {
7472
+ res.statusCode = status;
7473
+ res.setHeader("Content-Type", "application/json");
7474
+ res.end(JSON.stringify(body));
7475
+ }
7476
+ function respond4002(res, message) {
7477
+ respond3(res, 400, { error: "invalid_request", message });
7478
+ return true;
7479
+ }
7480
+ async function readJson3(req) {
7481
+ let raw = "";
7482
+ req.setEncoding("utf8");
7483
+ for await (const chunk of req) raw += chunk;
7484
+ if (!raw) return {};
7485
+ try {
7486
+ return JSON.parse(raw);
7487
+ } catch {
7488
+ throw new Error("invalid_json");
7489
+ }
7490
+ }
7491
+
6961
7492
  // src/daemon/outbox-watcher.ts
6962
7493
  import { promises as fs4 } from "fs";
6963
7494
  import * as path6 from "path";
@@ -7261,7 +7792,7 @@ var ServicePool = class {
7261
7792
 
7262
7793
  // src/daemon/shell-executor.ts
7263
7794
  import { spawn as spawn4 } from "child_process";
7264
- import { existsSync as existsSync10 } from "fs";
7795
+ import { existsSync as existsSync11 } from "fs";
7265
7796
  import { resolve as resolve2 } from "path";
7266
7797
  var DEFAULT_OUTPUT_LIMIT = 256 * 1024;
7267
7798
  var DEFAULT_TIMEOUT = 6e4;
@@ -7298,7 +7829,7 @@ async function executeShellDispatch(payload, deps) {
7298
7829
  const command = readCommand(payload, execution);
7299
7830
  if (!command.trim()) return fail2(payload.taskId, "shell_command_required", "Shell command is required");
7300
7831
  const cwd = resolveCwd(execution.cwd, deps.config.defaultCwd);
7301
- if (!existsSync10(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
7832
+ if (!existsSync11(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
7302
7833
  const timeoutMs = Math.min(
7303
7834
  typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : deps.config.maxTimeoutMs,
7304
7835
  deps.config.maxTimeoutMs
@@ -7553,6 +8084,7 @@ var Runner = class extends EventEmitter3 {
7553
8084
  localServer;
7554
8085
  outboxWatcher;
7555
8086
  memoryWiring;
8087
+ assetMetadataIndexes = /* @__PURE__ */ new Map();
7556
8088
  state = "idle";
7557
8089
  startedAt = 0;
7558
8090
  workspaceId = "";
@@ -7569,6 +8101,9 @@ var Runner = class extends EventEmitter3 {
7569
8101
  this.paths = this.opts.paths ?? resolvePaths();
7570
8102
  this.config = this.opts.configOverride ?? loadConfig(this.paths);
7571
8103
  this.shellConfig = resolveShellConfig(this.config.shell);
8104
+ if (process.env.PRISMER_WORKSPACE_ID) {
8105
+ this.workspaceId = process.env.PRISMER_WORKSPACE_ID;
8106
+ }
7572
8107
  process.env.PRISMER_BASE_URL = this.config.cloud_api_base;
7573
8108
  process.env.PRISMER_API_KEY = this.config.api_key;
7574
8109
  this.db = openLocalDb(this.paths.localDb);
@@ -7606,6 +8141,16 @@ var Runner = class extends EventEmitter3 {
7606
8141
  baseDir: `${this.paths.root}/memory`,
7607
8142
  deviceId: this.config.daemon_id
7608
8143
  });
8144
+ if (this.memoryWiring && this.workspaceId) {
8145
+ syncMemoryFromCloud(this.memoryWiring, this.cloud, [this.workspaceId]).catch(
8146
+ (err) => console.error("[Daemon] Initial memory sync failed:", err.message)
8147
+ );
8148
+ }
8149
+ if (this.workspaceId) {
8150
+ this.syncAssetMetadata(this.workspaceId).catch(
8151
+ (err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
8152
+ );
8153
+ }
7609
8154
  const containerId = process.env.PRISMER_CONTAINER_ID;
7610
8155
  const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
7611
8156
  this.outboxWatcher = new OutboxWatcher({
@@ -7653,7 +8198,10 @@ var Runner = class extends EventEmitter3 {
7653
8198
  // wiring step so it could be reviewed alongside the host-adapter
7654
8199
  // consumer (Hermes T2-B), which is what surfaces these routes to
7655
8200
  // an actual agent process.
7656
- attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0
8201
+ attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
8202
+ attachAsset: attachAssetRpc({
8203
+ resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
8204
+ })
7657
8205
  });
7658
8206
  await this.localServer.start();
7659
8207
  }
@@ -7849,10 +8397,10 @@ var Runner = class extends EventEmitter3 {
7849
8397
  const rawJson = process.env.PRISMER_HOSTED_AGENT_JSON;
7850
8398
  let raw;
7851
8399
  if (rawFile) {
7852
- if (!existsSync11(rawFile)) {
8400
+ if (!existsSync12(rawFile)) {
7853
8401
  throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
7854
8402
  }
7855
- raw = readFileSync8(rawFile, "utf8");
8403
+ raw = readFileSync9(rawFile, "utf8");
7856
8404
  } else if (rawJson) {
7857
8405
  raw = rawJson;
7858
8406
  }
@@ -7982,12 +8530,23 @@ var Runner = class extends EventEmitter3 {
7982
8530
  case "workspace_file.changed":
7983
8531
  this.onWorkspaceFileChanged(msg.payload);
7984
8532
  return;
8533
+ case "asset.changed":
8534
+ void this.onAssetChanged(msg.payload);
8535
+ return;
7985
8536
  default:
7986
8537
  this.emit("unknown-message", msg);
7987
8538
  }
7988
8539
  }
7989
8540
  async onHostAcked(payload) {
7990
8541
  this.workspaceId = payload.workspaceId;
8542
+ if (this.memoryWiring && this.workspaceId) {
8543
+ syncMemoryFromCloud(this.memoryWiring, this.cloud, [this.workspaceId]).catch(
8544
+ (err) => console.error("[Daemon] Initial memory sync failed:", err.message)
8545
+ );
8546
+ }
8547
+ this.syncAssetMetadata(this.workspaceId).catch(
8548
+ (err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
8549
+ );
7991
8550
  for (const id of payload.profilesToSync) {
7992
8551
  try {
7993
8552
  await this.syncProfileFromCloud(id);
@@ -8047,6 +8606,7 @@ var Runner = class extends EventEmitter3 {
8047
8606
  paths: this.paths,
8048
8607
  signal: ctrl.signal,
8049
8608
  ensureService: (profile, adapter) => this.servicePool.ensureService(profile, adapter),
8609
+ assetMetadataIndexes: this.assetMetadataIndexes,
8050
8610
  onProgress: () => {
8051
8611
  const running = this.runningTasks.get(payload.taskId);
8052
8612
  if (running) running.lastProgressAt = Date.now();
@@ -8147,6 +8707,19 @@ var Runner = class extends EventEmitter3 {
8147
8707
  this.emit("sync-error", err);
8148
8708
  }
8149
8709
  }
8710
+ async onAssetChanged(payload) {
8711
+ if (!payload.workspaceId) return;
8712
+ const index = this.assetMetadataIndexes.get(payload.workspaceId);
8713
+ if (!index) return;
8714
+ try {
8715
+ const result = await index.pullDelta();
8716
+ if (result.applied > 0) {
8717
+ console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
8718
+ }
8719
+ } catch (err) {
8720
+ console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
8721
+ }
8722
+ }
8150
8723
  onWorkspaceFileChanged(payload) {
8151
8724
  if (payload.operation === "delete") {
8152
8725
  this.db.prepare("DELETE FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").run(payload.workspaceId, payload.path);
@@ -8182,6 +8755,28 @@ var Runner = class extends EventEmitter3 {
8182
8755
  }
8183
8756
  };
8184
8757
  }
8758
+ /**
8759
+ * Ensure an AssetMetadataIndex exists for the given workspace and pull
8760
+ * delta from cloud. Idempotent — creates the index on first call, reuses
8761
+ * it on subsequent calls. Same cursor-catch-up semantics as WorkspaceMirror.
8762
+ */
8763
+ async syncAssetMetadata(workspaceId) {
8764
+ let index = this.assetMetadataIndexes.get(workspaceId);
8765
+ if (!index) {
8766
+ const stateDir = `${this.paths.root}/${workspaceId}`;
8767
+ index = new AssetMetadataIndex({
8768
+ db: this.db,
8769
+ cloud: this.cloud,
8770
+ workspaceId,
8771
+ workspaceStateDir: stateDir
8772
+ });
8773
+ this.assetMetadataIndexes.set(workspaceId, index);
8774
+ }
8775
+ const result = await index.pullDelta();
8776
+ if (result.applied > 0) {
8777
+ console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
8778
+ }
8779
+ }
8185
8780
  /**
8186
8781
  * SyncWorker FlushFn — pushes local writes to cloud.
8187
8782
  *
@@ -8346,6 +8941,8 @@ function safeJsonParse(raw) {
8346
8941
  }
8347
8942
 
8348
8943
  // src/cli/commands/daemon.ts
8944
+ init_util();
8945
+ init_ui();
8349
8946
  function buildDaemonCommand() {
8350
8947
  const cmd = new Command8("daemon").description("Manage the prismer daemon process");
8351
8948
  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) => {
@@ -8417,8 +9014,8 @@ function buildDaemonCommand() {
8417
9014
  if (existingPid && pidAlive(existingPid)) {
8418
9015
  exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
8419
9016
  }
8420
- if (!existsSync12(paths.logsDir)) mkdirSync7(paths.logsDir, { recursive: true });
8421
- const logFile = join10(paths.logsDir, "daemon.log");
9017
+ if (!existsSync13(paths.logsDir)) mkdirSync8(paths.logsDir, { recursive: true });
9018
+ const logFile = join11(paths.logsDir, "daemon.log");
8422
9019
  const fd = openSync(logFile, "a");
8423
9020
  const args = [process.argv[1], "daemon", "run"];
8424
9021
  if (opts.port) args.push("--port", String(opts.port));
@@ -8502,8 +9099,8 @@ function buildDaemonCommand() {
8502
9099
  });
8503
9100
  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) => {
8504
9101
  const paths = resolvePaths();
8505
- const logFile = join10(paths.logsDir, "daemon.log");
8506
- if (!existsSync12(logFile)) {
9102
+ const logFile = join11(paths.logsDir, "daemon.log");
9103
+ if (!existsSync13(logFile)) {
8507
9104
  exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
8508
9105
  }
8509
9106
  const lines = Math.max(1, opts.tail);
@@ -8557,16 +9154,17 @@ async function followFile(path7) {
8557
9154
  }
8558
9155
 
8559
9156
  // src/cli/commands/events.ts
9157
+ init_util();
8560
9158
  import { Command as Command9 } from "commander";
8561
- import { createReadStream as createReadStream2, existsSync as existsSync13 } from "fs";
9159
+ import { createReadStream as createReadStream2, existsSync as existsSync14 } from "fs";
8562
9160
  import { homedir as homedir5 } from "os";
8563
- import { join as join11 } from "path";
9161
+ import { join as join12 } from "path";
8564
9162
  import { createInterface } from "readline";
8565
- var DEFAULT_LIMIT = 50;
9163
+ var DEFAULT_LIMIT2 = 50;
8566
9164
  function buildEventsCommand() {
8567
9165
  return addEventOptions(new Command9("events").description("Read local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
8568
9166
  const file = eventsPath();
8569
- if (!existsSync13(file)) {
9167
+ if (!existsSync14(file)) {
8570
9168
  printJson(unavailable(file));
8571
9169
  process.exitCode = 1;
8572
9170
  return;
@@ -8583,7 +9181,7 @@ function buildEventsCommand() {
8583
9181
  function buildEventsStatsCommand() {
8584
9182
  return addEventOptions(new Command9("events:stats").description("Summarize local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
8585
9183
  const file = eventsPath();
8586
- if (!existsSync13(file)) {
9184
+ if (!existsSync14(file)) {
8587
9185
  printJson(unavailable(file));
8588
9186
  process.exitCode = 1;
8589
9187
  return;
@@ -8598,10 +9196,10 @@ function buildEventsStatsCommand() {
8598
9196
  });
8599
9197
  }
8600
9198
  function addEventOptions(cmd) {
8601
- 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)");
9199
+ 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)");
8602
9200
  }
8603
9201
  function eventsPath() {
8604
- return join11(process.env.PRISMER_HOME ?? join11(homedir5(), ".prismer"), "para", "events.jsonl");
9202
+ return join12(process.env.PRISMER_HOME ?? join12(homedir5(), ".prismer"), "para", "events.jsonl");
8605
9203
  }
8606
9204
  function unavailable(file) {
8607
9205
  return {
@@ -8669,7 +9267,7 @@ function bump(map, key) {
8669
9267
  }
8670
9268
  function normalizeFilters(opts) {
8671
9269
  return {
8672
- limit: Math.max(1, Math.min(1e4, Number.isFinite(opts.limit) ? opts.limit : DEFAULT_LIMIT)),
9270
+ limit: Math.max(1, Math.min(1e4, Number.isFinite(opts.limit) ? opts.limit : DEFAULT_LIMIT2)),
8673
9271
  agentId: opts.agentId,
8674
9272
  sessionId: opts.sessionId,
8675
9273
  family: opts.family,
@@ -8681,13 +9279,14 @@ function cleanFilters(filters) {
8681
9279
  }
8682
9280
  function parsePositiveInt3(v) {
8683
9281
  const n = Number.parseInt(v, 10);
8684
- return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT;
9282
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
8685
9283
  }
8686
9284
 
8687
9285
  // src/cli/commands/memory.ts
8688
9286
  import Database4 from "better-sqlite3";
8689
9287
  import { Command as Command10 } from "commander";
8690
- import { existsSync as existsSync14 } from "fs";
9288
+ import { existsSync as existsSync15 } from "fs";
9289
+ init_util();
8691
9290
  var LOCAL_BASE = process.env.PRISMER_DAEMON_URL ?? "http://127.0.0.1:3210";
8692
9291
  function buildMemoryCommand() {
8693
9292
  const cmd = new Command10("memory").description("Inspect local daemon memory/cache state");
@@ -8779,7 +9378,7 @@ async function tryDaemon(methods, paths) {
8779
9378
  try {
8780
9379
  const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
8781
9380
  if (res.status === 404) continue;
8782
- const body = await readJson3(res);
9381
+ const body = await readJson4(res);
8783
9382
  if (!res.ok) {
8784
9383
  return {
8785
9384
  ok: false,
@@ -8798,7 +9397,7 @@ async function tryDaemon(methods, paths) {
8798
9397
  }
8799
9398
  return void 0;
8800
9399
  }
8801
- async function readJson3(res) {
9400
+ async function readJson4(res) {
8802
9401
  const text = await res.text();
8803
9402
  if (!text) return null;
8804
9403
  try {
@@ -8838,7 +9437,7 @@ function readCacheSnapshot(limit) {
8838
9437
  const paths = resolvePaths();
8839
9438
  const empty = {
8840
9439
  dbPath: paths.localDb,
8841
- dbExists: existsSync14(paths.localDb),
9440
+ dbExists: existsSync15(paths.localDb),
8842
9441
  tables: {
8843
9442
  cached_assets: { exists: false, count: 0, sizeBytes: 0 },
8844
9443
  workspace_files_mirror: { exists: false, count: 0 }
@@ -8892,9 +9491,9 @@ function readCacheSnapshot(limit) {
8892
9491
  db?.close();
8893
9492
  }
8894
9493
  }
8895
- function tableExists(db, table) {
8896
- const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
8897
- return row?.name === table;
9494
+ function tableExists(db, table2) {
9495
+ const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table2);
9496
+ return row?.name === table2;
8898
9497
  }
8899
9498
  function assetRow(row) {
8900
9499
  const hash = String(row.content_hash ?? "");
@@ -8961,15 +9560,16 @@ import { Command as Command11 } from "commander";
8961
9560
 
8962
9561
  // src/pair.ts
8963
9562
  import { generateKeyPairSync } from "crypto";
8964
- import { hostname } from "os";
9563
+ import { hostname as hostname2 } from "os";
8965
9564
  import { setTimeout as sleep2 } from "timers/promises";
8966
9565
  import qrcode from "qrcode";
8967
9566
 
8968
9567
  // src/daemon-id.ts
8969
- import { randomUUID as randomUUID5 } from "crypto";
9568
+ import { hostname } from "os";
8970
9569
  var PREFIX = "daemon-";
8971
9570
  function newDaemonId() {
8972
- return `${PREFIX}${randomUUID5()}`;
9571
+ const host = hostname().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
9572
+ return `${PREFIX}${host}`;
8973
9573
  }
8974
9574
 
8975
9575
  // src/pair.ts
@@ -9000,7 +9600,7 @@ async function pair(opts) {
9000
9600
  "/api/im/pair/offer",
9001
9601
  {
9002
9602
  auth: false,
9003
- body: { devicePub, deviceName: opts.deviceName ?? hostname() }
9603
+ body: { devicePub, deviceName: opts.deviceName ?? hostname2() }
9004
9604
  }
9005
9605
  );
9006
9606
  if (!offerRes.ok) {
@@ -9077,6 +9677,8 @@ function unwrapEnvelope2(raw) {
9077
9677
  }
9078
9678
 
9079
9679
  // src/cli/commands/pair.ts
9680
+ init_util();
9681
+ init_ui();
9080
9682
  function buildPairCommand() {
9081
9683
  return new Command11("pair").description("Legacy QR approval path; use `prismer setup` to bind this runtime").option(
9082
9684
  "--cloud <url>",
@@ -9119,9 +9721,9 @@ function buildPairCommand() {
9119
9721
 
9120
9722
  // src/cli/commands/profile.ts
9121
9723
  import { Command as Command12 } from "commander";
9122
- import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
9724
+ import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
9123
9725
  import { tmpdir } from "os";
9124
- import { join as join12 } from "path";
9726
+ import { join as join13 } from "path";
9125
9727
  import { spawnSync as spawnSync4 } from "child_process";
9126
9728
 
9127
9729
  // src/templates/roles/product-manager.json
@@ -9131,7 +9733,7 @@ var product_manager_default = {
9131
9733
  description: "Writes PRDs, defines requirements, verifies implementations",
9132
9734
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
9133
9735
  configSchema: {
9134
- model: "claude-3-5-sonnet",
9736
+ model: "us-kimi-k2.6",
9135
9737
  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",
9136
9738
  allowedTools: ["Read", "Write", "WebSearch"],
9137
9739
  maxTokens: 8e3
@@ -9145,7 +9747,7 @@ var engineer_default = {
9145
9747
  description: "Implements features per PRD, writes code, runs tests",
9146
9748
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
9147
9749
  configSchema: {
9148
- model: "claude-3-5-sonnet",
9750
+ model: "us-kimi-k2.6",
9149
9751
  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",
9150
9752
  allowedTools: ["Read", "Write", "Edit", "Bash", "Grep"],
9151
9753
  maxTokens: 16e3
@@ -9159,7 +9761,7 @@ var ceo_default = {
9159
9761
  description: "Sets strategic direction, asks tough questions, makes go/no-go calls",
9160
9762
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
9161
9763
  configSchema: {
9162
- model: "claude-3-5-sonnet",
9764
+ model: "us-kimi-k2.6",
9163
9765
  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",
9164
9766
  allowedTools: ["Read", "WebSearch"],
9165
9767
  maxTokens: 4e3
@@ -9173,7 +9775,7 @@ var researcher_default = {
9173
9775
  description: "Investigates topics, gathers sources, writes research memos with citations",
9174
9776
  applicableAdapters: ["hermes", "openclaw", "claude-code"],
9175
9777
  configSchema: {
9176
- model: "claude-3-5-sonnet",
9778
+ model: "us-kimi-k2.6",
9177
9779
  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",
9178
9780
  allowedTools: ["Read", "Write", "WebSearch", "WebFetch"],
9179
9781
  maxTokens: 12e3
@@ -9199,6 +9801,7 @@ function listRoleTemplates() {
9199
9801
  }
9200
9802
 
9201
9803
  // src/cli/commands/profile.ts
9804
+ init_util();
9202
9805
  function buildProfileCommand() {
9203
9806
  const cmd = new Command12("profile").description("Manage AgentProfile (per-agent adapter config)");
9204
9807
  cmd.command("templates").description("List built-in role templates (PM / Engineer / CEO \u2026)").option("--json", "Output JSON (default)").action(() => {
@@ -9209,7 +9812,7 @@ function buildProfileCommand() {
9209
9812
  const data = await cloud.get(`/api/im/agent_profiles?agentId=${encodeURIComponent(opts.agent)}`);
9210
9813
  printJson(data);
9211
9814
  }, { code: "profile_list_failed" }));
9212
- 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) => {
9815
+ 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) => {
9213
9816
  let configObj = {};
9214
9817
  let adapterName = opts.adapter ?? "hermes";
9215
9818
  if (opts.fromTemplate) {
@@ -9222,6 +9825,9 @@ function buildProfileCommand() {
9222
9825
  const inline = readJsonArg(opts.config);
9223
9826
  configObj = { ...configObj, ...inline };
9224
9827
  }
9828
+ if (opts.model) {
9829
+ configObj.model = opts.model;
9830
+ }
9225
9831
  const cloud = mkCloud4();
9226
9832
  const wsId = opts.workspaceId ?? await resolveDefaultWorkspaceId(cloud);
9227
9833
  const res = await cloud.request("POST", "/api/im/agent_profiles", {
@@ -9241,12 +9847,12 @@ function buildProfileCommand() {
9241
9847
  const profile = await cloud.get(
9242
9848
  `/api/im/agent_profiles/${encodeURIComponent(profileId)}`
9243
9849
  );
9244
- const tmpFile = join12(tmpdir(), `prismer-profile-${profileId}.json`);
9245
- writeFileSync7(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
9850
+ const tmpFile = join13(tmpdir(), `prismer-profile-${profileId}.json`);
9851
+ writeFileSync8(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
9246
9852
  const editor = process.env.EDITOR || "vi";
9247
9853
  const ed = spawnSync4(editor, [tmpFile], { stdio: "inherit" });
9248
9854
  if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
9249
- const newConfig = JSON.parse(readFileSync9(tmpFile, "utf8"));
9855
+ const newConfig = JSON.parse(readFileSync10(tmpFile, "utf8"));
9250
9856
  const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
9251
9857
  body: { config: newConfig, version: profile.version }
9252
9858
  });
@@ -9268,8 +9874,8 @@ function mkCloud4() {
9268
9874
  function readJsonArg(arg) {
9269
9875
  if (arg.startsWith("@")) {
9270
9876
  const path7 = arg.slice(1);
9271
- if (!existsSync15(path7)) throw new Error(`File not found: ${path7}`);
9272
- return JSON.parse(readFileSync9(path7, "utf8"));
9877
+ if (!existsSync16(path7)) throw new Error(`File not found: ${path7}`);
9878
+ return JSON.parse(readFileSync10(path7, "utf8"));
9273
9879
  }
9274
9880
  return JSON.parse(arg);
9275
9881
  }
@@ -9282,6 +9888,8 @@ async function resolveDefaultWorkspaceId(cloud) {
9282
9888
 
9283
9889
  // src/cli/commands/sandbox.ts
9284
9890
  import { Command as Command13 } from "commander";
9891
+ init_util();
9892
+ init_ui();
9285
9893
  function buildSandboxCommand() {
9286
9894
  const cmd = new Command13("sandbox").description("Inspect and smoke-test sandbox lifecycle");
9287
9895
  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) => {
@@ -9483,11 +10091,13 @@ async function safeParseResponse(res) {
9483
10091
 
9484
10092
  // src/cli/commands/setup.ts
9485
10093
  import { Command as Command14 } from "commander";
9486
- import { hostname as hostname2 } from "os";
10094
+ import { hostname as hostname3 } from "os";
9487
10095
  import { spawn as spawn6 } from "child_process";
9488
10096
  import { randomBytes } from "crypto";
9489
- import { existsSync as existsSync16, renameSync } from "fs";
10097
+ import { existsSync as existsSync17, renameSync } from "fs";
9490
10098
  import { createServer as createServer2 } from "http";
10099
+ init_util();
10100
+ init_ui();
9491
10101
  function buildSetupCommand() {
9492
10102
  return new Command14("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(
9493
10103
  "--cloud <url>",
@@ -9510,6 +10120,7 @@ function buildSetupCommand() {
9510
10120
  getUI().blank();
9511
10121
  }
9512
10122
  const shouldStart = opts.start !== false;
10123
+ stopRunningDaemon(paths);
9513
10124
  if (opts.pair || opts.asUser) {
9514
10125
  if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
9515
10126
  if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
@@ -9526,7 +10137,7 @@ function buildSetupCommand() {
9526
10137
  }
9527
10138
  const result = await pair({
9528
10139
  cloudBaseUrl,
9529
- deviceName: opts.deviceName ?? hostname2(),
10140
+ deviceName: opts.deviceName ?? hostname3(),
9530
10141
  force: opts.force,
9531
10142
  paths,
9532
10143
  asUserEmail: opts.asUser
@@ -9543,13 +10154,13 @@ function buildSetupCommand() {
9543
10154
  apiKey = await mintDaemonApiKey({
9544
10155
  cloudBaseUrl,
9545
10156
  token: authToken,
9546
- deviceName: opts.deviceName ?? hostname2()
10157
+ deviceName: opts.deviceName ?? hostname3()
9547
10158
  });
9548
10159
  }
9549
10160
  if (!apiKey && !authToken && !opts.check && opts.browser !== false) {
9550
10161
  apiKey = await runBrowserSetup({
9551
10162
  cloudBaseUrl,
9552
- deviceName: opts.deviceName ?? hostname2(),
10163
+ deviceName: opts.deviceName ?? hostname3(),
9553
10164
  json: Boolean(opts.json)
9554
10165
  });
9555
10166
  }
@@ -9588,9 +10199,13 @@ function buildSetupCommand() {
9588
10199
  if (configExists(paths) && !opts.force) {
9589
10200
  const cfg = loadConfig(paths);
9590
10201
  if (opts.json) {
9591
- printJson({ ok: true, alreadyConfigured: true, config: paths.configFile, daemonId: cfg.daemon_id });
10202
+ printJson({ ok: true, alreadyConfigured: true, config: paths.configFile, daemonId: cfg.daemon_id, daemonStartRequested: shouldStart });
9592
10203
  } else {
9593
10204
  warn("Already configured", paths.configFile);
10205
+ if (shouldStart) {
10206
+ startDaemonDetached(paths.root);
10207
+ ok("Daemon start requested");
10208
+ }
9594
10209
  tip("prismer setup --force <api-key>", "overwrite config");
9595
10210
  }
9596
10211
  return;
@@ -9673,14 +10288,15 @@ function waitForSetupCallback(server, expectedState) {
9673
10288
  }
9674
10289
  const state = url.searchParams.get("state");
9675
10290
  const key = url.searchParams.get("key");
10291
+ 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>';
9676
10292
  if (state !== expectedState || !key || !/^sk-prismer-/.test(key)) {
9677
10293
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
9678
- res.end("<h1>Prismer setup failed</h1><p>Invalid setup callback. You can close this tab and retry.</p>");
10294
+ 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>`);
9679
10295
  reject(new Error("setup: invalid browser callback"));
9680
10296
  return;
9681
10297
  }
9682
10298
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
9683
- res.end("<h1>Prismer setup complete</h1><p>You can close this tab and return to your terminal.</p>");
10299
+ 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>`);
9684
10300
  resolve3(key);
9685
10301
  } catch (err) {
9686
10302
  reject(err);
@@ -9725,11 +10341,33 @@ function startDaemonDetached(home) {
9725
10341
  });
9726
10342
  child.unref();
9727
10343
  }
10344
+ function stopRunningDaemon(paths) {
10345
+ const pid = readPidFile(paths);
10346
+ if (!pid || !pidAlive(pid)) {
10347
+ if (pid) clearPidFile(paths);
10348
+ return;
10349
+ }
10350
+ try {
10351
+ process.kill(pid, "SIGTERM");
10352
+ } catch {
10353
+ return;
10354
+ }
10355
+ const deadline = Date.now() + 5e3;
10356
+ while (Date.now() < deadline) {
10357
+ if (!pidAlive(pid)) {
10358
+ clearPidFile(paths);
10359
+ return;
10360
+ }
10361
+ const start = Date.now();
10362
+ while (Date.now() - start < 200) {
10363
+ }
10364
+ }
10365
+ }
9728
10366
  function shouldArchiveLocalDb(previous, next) {
9729
10367
  return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
9730
10368
  }
9731
10369
  function archiveLocalDb(localDbPath) {
9732
- if (!existsSync16(localDbPath)) return;
10370
+ if (!existsSync17(localDbPath)) return;
9733
10371
  const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
9734
10372
  renameSync(localDbPath, archived);
9735
10373
  }
@@ -9752,6 +10390,8 @@ async function mintDaemonApiKey(input) {
9752
10390
 
9753
10391
  // src/cli/commands/status.ts
9754
10392
  import { Command as Command15 } from "commander";
10393
+ init_util();
10394
+ init_ui();
9755
10395
  function buildStatusCommand() {
9756
10396
  return new Command15("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
9757
10397
  const paths = resolvePaths();
@@ -9778,10 +10418,34 @@ function buildStatusCommand() {
9778
10418
  const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
9779
10419
  let cloudOk = false;
9780
10420
  let me = null;
10421
+ let devices = null;
10422
+ let agents = null;
9781
10423
  try {
9782
- const res = await cloud.request("GET", "/api/im/me", { timeoutMs: 3e3 });
9783
- cloudOk = res.ok;
9784
- me = res.data ?? null;
10424
+ const meRes = await cloud.request("GET", "/api/im/me", { timeoutMs: 3e3 });
10425
+ cloudOk = meRes.ok;
10426
+ me = meRes.data ?? null;
10427
+ if (cloudOk) {
10428
+ const wsRes = await cloud.request("GET", "/api/im/workspaces", { timeoutMs: 3e3 });
10429
+ if (wsRes.ok) {
10430
+ const wsBody = wsRes.data;
10431
+ const wsList = wsBody?.data;
10432
+ if (Array.isArray(wsList) && wsList.length > 0) {
10433
+ const wsId = wsList[0]?.id;
10434
+ if (wsId) {
10435
+ const devRes = await cloud.request("GET", `/api/workspace/runtime-installations?workspaceId=${encodeURIComponent(wsId)}&includeStopped=false`, { timeoutMs: 3e3 });
10436
+ if (devRes.ok) {
10437
+ const devBody = devRes.data;
10438
+ devices = devBody?.data;
10439
+ }
10440
+ const agRes = await cloud.request("GET", `/api/im/workspaces/${encodeURIComponent(wsId)}/agents`, { timeoutMs: 3e3 });
10441
+ if (agRes.ok) {
10442
+ const agBody = agRes.data;
10443
+ agents = agBody?.data;
10444
+ }
10445
+ }
10446
+ }
10447
+ }
10448
+ }
9785
10449
  } catch {
9786
10450
  cloudOk = false;
9787
10451
  }
@@ -9793,10 +10457,15 @@ function buildStatusCommand() {
9793
10457
  daemon: {
9794
10458
  running: daemonRunning,
9795
10459
  pid: daemonStatus.pid ?? pid ?? null,
9796
- wsConnected: daemonStatus.wsConnected ?? null
10460
+ wsConnected: daemonStatus.wsConnected ?? null,
10461
+ info: daemonStatus.info ?? {}
9797
10462
  },
9798
- cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me },
9799
- local
10463
+ cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
10464
+ local,
10465
+ binding: {
10466
+ daemonId: cfg.daemon_id,
10467
+ apiKey: cfg.api_key
10468
+ }
9800
10469
  };
9801
10470
  if (opts.json) {
9802
10471
  printJson(report);
@@ -9810,12 +10479,19 @@ async function readDaemonStatus() {
9810
10479
  const res = await fetch("http://127.0.0.1:3210/healthz", {
9811
10480
  signal: AbortSignal.timeout(1e3)
9812
10481
  });
9813
- if (!res.ok) return { running: false };
9814
- const data = await res.json();
9815
- return { running: true, pid: data.pid, wsConnected: data.wsConnected };
10482
+ if (res.ok) {
10483
+ const data = await res.json();
10484
+ return { running: true, pid: data.pid, wsConnected: data.wsConnected, info: data };
10485
+ }
9816
10486
  } catch {
9817
- return { running: false };
9818
10487
  }
10488
+ const paths = resolvePaths();
10489
+ const pid = readPidFile(paths);
10490
+ if (pid) {
10491
+ const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
10492
+ if (pidAlive2(pid)) return { running: true, pid };
10493
+ }
10494
+ return { running: false };
9819
10495
  }
9820
10496
  function readLocalCounts(localDbPath) {
9821
10497
  try {
@@ -9841,21 +10517,59 @@ function printPretty(report) {
9841
10517
  ui.blank();
9842
10518
  ok("Config", report.paths.config);
9843
10519
  if (report.daemon.running) {
9844
- ok("Daemon", `pid=${report.daemon.pid ?? "?"} ws=${report.daemon.wsConnected ? "connected" : "pending"}`);
10520
+ const ws = report.daemon.wsConnected ? "connected" : "pending";
10521
+ ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
10522
+ if (report.daemon.info) {
10523
+ const info2 = report.daemon.info;
10524
+ if (info2.version) ui.line(` Version: ${info2.version}`);
10525
+ if (info2.uptime) ui.line(` Uptime: ${Math.round(info2.uptime / 60)}m`);
10526
+ if (info2.memoryMb) ui.line(` Memory: ${info2.memoryMb} MB`);
10527
+ }
9845
10528
  } else {
9846
10529
  warn("Daemon", "not running");
9847
10530
  tip("prismer daemon start");
9848
10531
  }
9849
- if (report.cloud.reachable) ok("Cloud", report.cloud.base);
9850
- else {
10532
+ if (report.cloud.reachable) {
10533
+ ok("Cloud", report.cloud.base);
10534
+ const me = report.cloud.me;
10535
+ if (me?.user) {
10536
+ const roleTag = me.user.role ? ` role=${me.user.role}` : "";
10537
+ ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
10538
+ }
10539
+ if (me?.credits) {
10540
+ ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
10541
+ }
10542
+ } else {
9851
10543
  fail("Cloud", `${report.cloud.base} unreachable or unauthorized`);
9852
10544
  tip("prismer setup --force");
9853
10545
  }
10546
+ if (report.binding) {
10547
+ ui.blank();
10548
+ ui.line(` Daemon ID: ${report.binding.daemonId}`);
10549
+ const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
10550
+ ui.line(` API Key: ${masked}`);
10551
+ }
10552
+ if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
10553
+ const devs = report.cloud.devices;
10554
+ ui.blank();
10555
+ ui.line(` Workspace Devices (${devs.length}):`);
10556
+ for (const d of devs) {
10557
+ const statusIcon = d.daemonStatus === "connected" ? "\u25CF" : "\u25CB";
10558
+ const kind = d.runtimeKind === "docker" ? "Local" : "K8s";
10559
+ const declared = d.hostedAgentSummary?.declared ?? 0;
10560
+ ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
10561
+ }
10562
+ } else {
10563
+ ui.line(` Workspace Devices: none`);
10564
+ }
10565
+ if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
10566
+ ui.line(` Hosted agents: ${report.cloud.agents.length}`);
10567
+ }
9854
10568
  if (report.local) {
9855
10569
  ui.blank();
9856
- ui.line(` Agents: ${report.local.agents}`);
9857
- ui.line(` Profiles: ${report.local.profiles}`);
9858
- ui.line(` Tasks: ${report.local.runningTasks} running locally`);
10570
+ ui.line(` Local agents: ${report.local.agents}`);
10571
+ ui.line(` Profiles: ${report.local.profiles}`);
10572
+ ui.line(` Running tasks: ${report.local.runningTasks}`);
9859
10573
  } else {
9860
10574
  warn("Local DB", "unavailable");
9861
10575
  }
@@ -9864,6 +10578,7 @@ function printPretty(report) {
9864
10578
  // src/cli/commands/task.ts
9865
10579
  import { Command as Command16 } from "commander";
9866
10580
  import { setTimeout as sleep3 } from "timers/promises";
10581
+ init_util();
9867
10582
  function describeStatus(status) {
9868
10583
  return status === 0 ? "network error" : `HTTP ${status}`;
9869
10584
  }
@@ -9983,6 +10698,8 @@ function taskFrom2(raw) {
9983
10698
 
9984
10699
  // src/cli/commands/workspace.ts
9985
10700
  import { Command as Command17 } from "commander";
10701
+ init_util();
10702
+ init_ui();
9986
10703
  function buildWorkspaceCommand() {
9987
10704
  const cmd = new Command17("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
9988
10705
  cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
@@ -10217,7 +10934,8 @@ async function readResponseError(res) {
10217
10934
  }
10218
10935
 
10219
10936
  // src/cli/index.ts
10220
- var VERSION = "1.9.3";
10937
+ init_ui();
10938
+ var VERSION = "1.9.7";
10221
10939
  function buildProgram() {
10222
10940
  const program = new Command18("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
10223
10941
  program.addCommand(buildBannerCommand());