@prismer/runtime 1.9.7 → 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/CHANGELOG.md +24 -0
- package/dist/cli.cjs +1634 -923
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1459 -746
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1667 -962
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -1
- package/dist/index.d.ts +90 -1
- package/dist/index.js +2127 -1416
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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:
|
|
1765
|
+
const { join: join14, dirname: dirname8 } = __require("path");
|
|
1163
1766
|
const here = dirname8(fileURLToPath2(import.meta.url));
|
|
1164
|
-
const candidate =
|
|
1767
|
+
const candidate = join14(here, "../../mcp/dist/index.js");
|
|
1165
1768
|
if (existsSync(candidate)) return candidate;
|
|
1166
|
-
const candidate2 =
|
|
1769
|
+
const candidate2 = join14(here, "../../../mcp/dist/index.js");
|
|
1167
1770
|
if (existsSync(candidate2)) return candidate2;
|
|
1168
|
-
const candidate3 =
|
|
1771
|
+
const candidate3 = join14(here, "../../../../mcp/dist/index.js");
|
|
1169
1772
|
if (existsSync(candidate3)) return candidate3;
|
|
1170
1773
|
} catch {
|
|
1171
1774
|
}
|
|
@@ -1556,691 +2159,144 @@ var OpenClawService = class {
|
|
|
1556
2159
|
model: this.model,
|
|
1557
2160
|
messages,
|
|
1558
2161
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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
2185
|
return {
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
}
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
const isTTY = this.stream.isTTY === true;
|
|
1976
|
-
if (!isTTY || !this.colorEnabled) {
|
|
1977
|
-
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
1978
|
-
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
|
-
|
|
1988
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2209
|
+
return false;
|
|
2176
2210
|
}
|
|
2177
|
-
return candidate.replace(/\/$/, "");
|
|
2178
2211
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
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
|
|
2191
|
-
|
|
2192
|
-
if (opts.compact) {
|
|
2193
|
-
ui.smallHeader("Runtime CLI v1.9.7");
|
|
2194
|
-
return;
|
|
2195
|
-
}
|
|
2196
|
-
ui.banner("Runtime CLI v1.9.7", { 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
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
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
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2280
|
+
function saveConfig(config, paths = resolvePaths()) {
|
|
2281
|
+
if (!existsSync2(paths.root)) {
|
|
2282
|
+
mkdirSync2(paths.root, { recursive: true });
|
|
2283
|
+
}
|
|
2284
|
+
if (!existsSync2(dirname2(paths.configFile))) {
|
|
2285
|
+
mkdirSync2(dirname2(paths.configFile), { recursive: true });
|
|
2240
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
|
|
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
|
|
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
|
|
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
|
|
4965
|
-
return `${
|
|
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
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
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,
|
|
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
|
|
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 (!
|
|
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 (!
|
|
8400
|
+
if (!existsSync12(rawFile)) {
|
|
7853
8401
|
throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
|
|
7854
8402
|
}
|
|
7855
|
-
raw =
|
|
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 (!
|
|
8421
|
-
const logFile =
|
|
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 =
|
|
8506
|
-
if (!
|
|
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
|
|
9159
|
+
import { createReadStream as createReadStream2, existsSync as existsSync14 } from "fs";
|
|
8562
9160
|
import { homedir as homedir5 } from "os";
|
|
8563
|
-
import { join as
|
|
9161
|
+
import { join as join12 } from "path";
|
|
8564
9162
|
import { createInterface } from "readline";
|
|
8565
|
-
var
|
|
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 (!
|
|
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 (!
|
|
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,
|
|
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
|
|
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 :
|
|
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 :
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
8896
|
-
const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(
|
|
8897
|
-
return row?.name ===
|
|
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 {
|
|
9568
|
+
import { hostname } from "os";
|
|
8970
9569
|
var PREFIX = "daemon-";
|
|
8971
9570
|
function newDaemonId() {
|
|
8972
|
-
|
|
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 ??
|
|
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
|
|
9724
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
9123
9725
|
import { tmpdir } from "os";
|
|
9124
|
-
import { join as
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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 =
|
|
9245
|
-
|
|
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(
|
|
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 (!
|
|
9272
|
-
return JSON.parse(
|
|
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
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
10163
|
+
deviceName: opts.deviceName ?? hostname3(),
|
|
9553
10164
|
json: Boolean(opts.json)
|
|
9554
10165
|
});
|
|
9555
10166
|
}
|
|
@@ -9730,11 +10341,33 @@ function startDaemonDetached(home) {
|
|
|
9730
10341
|
});
|
|
9731
10342
|
child.unref();
|
|
9732
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
|
+
}
|
|
9733
10366
|
function shouldArchiveLocalDb(previous, next) {
|
|
9734
10367
|
return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
|
|
9735
10368
|
}
|
|
9736
10369
|
function archiveLocalDb(localDbPath) {
|
|
9737
|
-
if (!
|
|
10370
|
+
if (!existsSync17(localDbPath)) return;
|
|
9738
10371
|
const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
9739
10372
|
renameSync(localDbPath, archived);
|
|
9740
10373
|
}
|
|
@@ -9757,6 +10390,8 @@ async function mintDaemonApiKey(input) {
|
|
|
9757
10390
|
|
|
9758
10391
|
// src/cli/commands/status.ts
|
|
9759
10392
|
import { Command as Command15 } from "commander";
|
|
10393
|
+
init_util();
|
|
10394
|
+
init_ui();
|
|
9760
10395
|
function buildStatusCommand() {
|
|
9761
10396
|
return new Command15("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
9762
10397
|
const paths = resolvePaths();
|
|
@@ -9783,10 +10418,34 @@ function buildStatusCommand() {
|
|
|
9783
10418
|
const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
9784
10419
|
let cloudOk = false;
|
|
9785
10420
|
let me = null;
|
|
10421
|
+
let devices = null;
|
|
10422
|
+
let agents = null;
|
|
9786
10423
|
try {
|
|
9787
|
-
const
|
|
9788
|
-
cloudOk =
|
|
9789
|
-
me =
|
|
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
|
+
}
|
|
9790
10449
|
} catch {
|
|
9791
10450
|
cloudOk = false;
|
|
9792
10451
|
}
|
|
@@ -9798,10 +10457,15 @@ function buildStatusCommand() {
|
|
|
9798
10457
|
daemon: {
|
|
9799
10458
|
running: daemonRunning,
|
|
9800
10459
|
pid: daemonStatus.pid ?? pid ?? null,
|
|
9801
|
-
wsConnected: daemonStatus.wsConnected ?? null
|
|
10460
|
+
wsConnected: daemonStatus.wsConnected ?? null,
|
|
10461
|
+
info: daemonStatus.info ?? {}
|
|
9802
10462
|
},
|
|
9803
|
-
cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me },
|
|
9804
|
-
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
|
+
}
|
|
9805
10469
|
};
|
|
9806
10470
|
if (opts.json) {
|
|
9807
10471
|
printJson(report);
|
|
@@ -9815,12 +10479,19 @@ async function readDaemonStatus() {
|
|
|
9815
10479
|
const res = await fetch("http://127.0.0.1:3210/healthz", {
|
|
9816
10480
|
signal: AbortSignal.timeout(1e3)
|
|
9817
10481
|
});
|
|
9818
|
-
if (
|
|
9819
|
-
|
|
9820
|
-
|
|
10482
|
+
if (res.ok) {
|
|
10483
|
+
const data = await res.json();
|
|
10484
|
+
return { running: true, pid: data.pid, wsConnected: data.wsConnected, info: data };
|
|
10485
|
+
}
|
|
9821
10486
|
} catch {
|
|
9822
|
-
return { running: false };
|
|
9823
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 };
|
|
9824
10495
|
}
|
|
9825
10496
|
function readLocalCounts(localDbPath) {
|
|
9826
10497
|
try {
|
|
@@ -9846,21 +10517,59 @@ function printPretty(report) {
|
|
|
9846
10517
|
ui.blank();
|
|
9847
10518
|
ok("Config", report.paths.config);
|
|
9848
10519
|
if (report.daemon.running) {
|
|
9849
|
-
|
|
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
|
+
}
|
|
9850
10528
|
} else {
|
|
9851
10529
|
warn("Daemon", "not running");
|
|
9852
10530
|
tip("prismer daemon start");
|
|
9853
10531
|
}
|
|
9854
|
-
if (report.cloud.reachable)
|
|
9855
|
-
|
|
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 {
|
|
9856
10543
|
fail("Cloud", `${report.cloud.base} unreachable or unauthorized`);
|
|
9857
10544
|
tip("prismer setup --force");
|
|
9858
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
|
+
}
|
|
9859
10568
|
if (report.local) {
|
|
9860
10569
|
ui.blank();
|
|
9861
|
-
ui.line(`
|
|
9862
|
-
ui.line(` Profiles:
|
|
9863
|
-
ui.line(`
|
|
10570
|
+
ui.line(` Local agents: ${report.local.agents}`);
|
|
10571
|
+
ui.line(` Profiles: ${report.local.profiles}`);
|
|
10572
|
+
ui.line(` Running tasks: ${report.local.runningTasks}`);
|
|
9864
10573
|
} else {
|
|
9865
10574
|
warn("Local DB", "unavailable");
|
|
9866
10575
|
}
|
|
@@ -9869,6 +10578,7 @@ function printPretty(report) {
|
|
|
9869
10578
|
// src/cli/commands/task.ts
|
|
9870
10579
|
import { Command as Command16 } from "commander";
|
|
9871
10580
|
import { setTimeout as sleep3 } from "timers/promises";
|
|
10581
|
+
init_util();
|
|
9872
10582
|
function describeStatus(status) {
|
|
9873
10583
|
return status === 0 ? "network error" : `HTTP ${status}`;
|
|
9874
10584
|
}
|
|
@@ -9988,6 +10698,8 @@ function taskFrom2(raw) {
|
|
|
9988
10698
|
|
|
9989
10699
|
// src/cli/commands/workspace.ts
|
|
9990
10700
|
import { Command as Command17 } from "commander";
|
|
10701
|
+
init_util();
|
|
10702
|
+
init_ui();
|
|
9991
10703
|
function buildWorkspaceCommand() {
|
|
9992
10704
|
const cmd = new Command17("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
|
|
9993
10705
|
cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
@@ -10222,6 +10934,7 @@ async function readResponseError(res) {
|
|
|
10222
10934
|
}
|
|
10223
10935
|
|
|
10224
10936
|
// src/cli/index.ts
|
|
10937
|
+
init_ui();
|
|
10225
10938
|
var VERSION = "1.9.7";
|
|
10226
10939
|
function buildProgram() {
|
|
10227
10940
|
const program = new Command18("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
|