@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.cjs
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,190 +30,783 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
26
|
-
// src/cli/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
var import_node_os3 = require("os");
|
|
33
|
-
var import_node_path4 = require("path");
|
|
34
|
-
var import_commander = require("commander");
|
|
35
|
-
|
|
36
|
-
// src/adapters/claude-code/index.ts
|
|
37
|
-
var import_node_child_process = require("child_process");
|
|
38
|
-
var import_zod = require("zod");
|
|
39
|
-
|
|
40
|
-
// src/adapters/claude-code/output-parser.ts
|
|
41
|
-
var MAX_OUTPUT_CHARS = 64 * 1024;
|
|
42
|
-
function parseHeadlessOutput(stdout) {
|
|
43
|
-
let out = "";
|
|
44
|
-
let toolCalls = 0;
|
|
45
|
-
for (const line of stdout.split("\n")) {
|
|
46
|
-
const trimmed = line.trim();
|
|
47
|
-
if (!trimmed) continue;
|
|
48
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
49
|
-
try {
|
|
50
|
-
const obj = JSON.parse(trimmed);
|
|
51
|
-
if (obj.event === "tool_call" || obj.tool) {
|
|
52
|
-
toolCalls += 1;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (typeof obj.text === "string") {
|
|
56
|
-
out += obj.text;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
out += line + "\n";
|
|
33
|
+
// src/cli/ui.ts
|
|
34
|
+
function thisDirname() {
|
|
35
|
+
try {
|
|
36
|
+
return path.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
|
|
37
|
+
} catch {
|
|
38
|
+
return process.cwd();
|
|
63
39
|
}
|
|
64
|
-
const truncated = out.length > MAX_OUTPUT_CHARS;
|
|
65
|
-
if (truncated) out = out.slice(0, MAX_OUTPUT_CHARS) + "\n\u2026[truncated]";
|
|
66
|
-
return { output: out.trim(), toolCalls, truncated };
|
|
67
40
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
maxTurns: import_zod.z.number().int().positive().default(20),
|
|
87
|
-
// External-model route (1.9.x extension — optional, no breaking change)
|
|
88
|
-
baseURL: import_zod.z.string().url().optional(),
|
|
89
|
-
apiKeyRef: import_zod.z.string().regex(/^(env|keychain):[A-Za-z0-9_][A-Za-z0-9_.\-]*$/).optional(),
|
|
90
|
-
// informational tag — dispatch does not branch on this value yet
|
|
91
|
-
route: import_zod.z.enum(["default", "prismer", "omniroute"]).default("default")
|
|
92
|
-
});
|
|
93
|
-
var claudeCodeAdapter = {
|
|
94
|
-
name: "claude-code",
|
|
95
|
-
kind: "interactive",
|
|
96
|
-
capabilities: ["shell", "code", "mcp", "edit"],
|
|
97
|
-
workspaceSchema: CCConfigSchema,
|
|
98
|
-
validate(config) {
|
|
99
|
-
const r = CCConfigSchema.safeParse(config);
|
|
100
|
-
if (r.success) return { ok: true };
|
|
101
|
-
return {
|
|
102
|
-
ok: false,
|
|
103
|
-
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
async dispatch(profile, task) {
|
|
107
|
-
const config = CCConfigSchema.parse(profile.config);
|
|
108
|
-
const args = ["--print"];
|
|
109
|
-
if (config.model) {
|
|
110
|
-
args.push("--model", config.model);
|
|
111
|
-
}
|
|
112
|
-
if (config.allowedTools && config.allowedTools.length > 0) {
|
|
113
|
-
args.push("--allowed-tools", config.allowedTools.join(","));
|
|
114
|
-
}
|
|
115
|
-
if (config.systemPrompt) {
|
|
116
|
-
args.push("--system-prompt", config.systemPrompt);
|
|
117
|
-
}
|
|
118
|
-
args.push(task.prompt);
|
|
119
|
-
const startedAt = Date.now();
|
|
120
|
-
const env = { ...process.env, ...config.envVars ?? {} };
|
|
121
|
-
if (config.baseURL) {
|
|
122
|
-
env.ANTHROPIC_API_BASE = config.baseURL;
|
|
41
|
+
function findIconPath(size = "big") {
|
|
42
|
+
const name = size === "big" ? "icon" : "smallicon";
|
|
43
|
+
const here = thisDirname();
|
|
44
|
+
const candidates = [
|
|
45
|
+
// npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
|
|
46
|
+
path.resolve(here, "../assets", name),
|
|
47
|
+
// alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
|
|
48
|
+
path.resolve(here, "../../assets", name),
|
|
49
|
+
// source/typecheck: src/cli/ui.ts → ../../assets
|
|
50
|
+
path.resolve(here, "../../assets", name),
|
|
51
|
+
// dev mode: cwd happens to be runtime root
|
|
52
|
+
path.resolve(process.cwd(), "assets", name),
|
|
53
|
+
path.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
|
|
54
|
+
];
|
|
55
|
+
for (const candidate of candidates) {
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
58
|
+
} catch {
|
|
123
59
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function getUI() {
|
|
64
|
+
if (!_ui) _ui = new UI();
|
|
65
|
+
return _ui;
|
|
66
|
+
}
|
|
67
|
+
function setUI(ui) {
|
|
68
|
+
_ui = ui;
|
|
69
|
+
}
|
|
70
|
+
function applyCommonFlags(argv) {
|
|
71
|
+
let mode = "pretty";
|
|
72
|
+
const isTTY = process.stdout.isTTY === true;
|
|
73
|
+
const noColorEnv = Boolean(process.env["NO_COLOR"]);
|
|
74
|
+
let color2 = isTTY && !noColorEnv;
|
|
75
|
+
const rest = [];
|
|
76
|
+
for (const arg of argv) {
|
|
77
|
+
switch (arg) {
|
|
78
|
+
case "--no-color":
|
|
79
|
+
color2 = false;
|
|
80
|
+
break;
|
|
81
|
+
case "--color":
|
|
82
|
+
color2 = true;
|
|
83
|
+
break;
|
|
84
|
+
case "--json":
|
|
85
|
+
case "--pretty-json":
|
|
86
|
+
mode = "json";
|
|
87
|
+
if (arg === "--json") rest.push(arg);
|
|
88
|
+
break;
|
|
89
|
+
case "--quiet":
|
|
90
|
+
mode = "quiet";
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
rest.push(arg);
|
|
127
94
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
95
|
+
}
|
|
96
|
+
return { mode, color: color2, restArgv: rest };
|
|
97
|
+
}
|
|
98
|
+
var fs, path, import_node_url, import_meta2, BRAILLE_FRAMES, COMPACT_BANNER, UI, _ui;
|
|
99
|
+
var init_ui = __esm({
|
|
100
|
+
"src/cli/ui.ts"() {
|
|
101
|
+
"use strict";
|
|
102
|
+
fs = __toESM(require("fs"), 1);
|
|
103
|
+
path = __toESM(require("path"), 1);
|
|
104
|
+
import_node_url = require("url");
|
|
105
|
+
import_meta2 = {};
|
|
106
|
+
BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
107
|
+
COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
|
|
108
|
+
UI = class {
|
|
109
|
+
mode;
|
|
110
|
+
colorEnabled;
|
|
111
|
+
stream;
|
|
112
|
+
errStream;
|
|
113
|
+
constructor(opts) {
|
|
114
|
+
this.mode = opts?.mode ?? "pretty";
|
|
115
|
+
this.stream = opts?.stream ?? process.stdout;
|
|
116
|
+
this.errStream = opts?.errStream ?? process.stderr;
|
|
117
|
+
if (opts?.color !== void 0) {
|
|
118
|
+
this.colorEnabled = opts.color;
|
|
119
|
+
} else {
|
|
120
|
+
const isTTY = this.stream.isTTY === true;
|
|
121
|
+
const noColor = Boolean(process.env["NO_COLOR"]);
|
|
122
|
+
this.colorEnabled = isTTY && !noColor;
|
|
123
|
+
}
|
|
138
124
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
144
|
-
});
|
|
145
|
-
let stdout = "";
|
|
146
|
-
let stderr = "";
|
|
147
|
-
child.stdout.on("data", (d) => {
|
|
148
|
-
stdout += d.toString();
|
|
149
|
-
});
|
|
150
|
-
child.stderr.on("data", (d) => {
|
|
151
|
-
stderr += d.toString();
|
|
152
|
-
});
|
|
153
|
-
const onAbort = () => {
|
|
154
|
-
try {
|
|
155
|
-
child.kill("SIGTERM");
|
|
156
|
-
} catch {
|
|
125
|
+
// ---- Internal color helpers ----
|
|
126
|
+
ansi(open, close, text) {
|
|
127
|
+
if (!this.colorEnabled) return text;
|
|
128
|
+
return `\x1B[${open}m${text}\x1B[${close}m`;
|
|
157
129
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
130
|
+
green(t) {
|
|
131
|
+
return this.ansi(32, 39, t);
|
|
132
|
+
}
|
|
133
|
+
red(t) {
|
|
134
|
+
return this.ansi(31, 39, t);
|
|
135
|
+
}
|
|
136
|
+
yellow(t) {
|
|
137
|
+
return this.ansi(33, 39, t);
|
|
138
|
+
}
|
|
139
|
+
cyan(t) {
|
|
140
|
+
return this.ansi(36, 39, t);
|
|
141
|
+
}
|
|
142
|
+
dim(t) {
|
|
143
|
+
return this.ansi(2, 22, t);
|
|
144
|
+
}
|
|
145
|
+
bold(t) {
|
|
146
|
+
return this.ansi(1, 22, t);
|
|
147
|
+
}
|
|
148
|
+
gray(t) {
|
|
149
|
+
return this.ansi(90, 39, t);
|
|
150
|
+
}
|
|
151
|
+
brandMark() {
|
|
152
|
+
return this.cyan("\u25C7");
|
|
153
|
+
}
|
|
154
|
+
colorBrandLine(line) {
|
|
155
|
+
let out = "";
|
|
156
|
+
for (const ch of line) {
|
|
157
|
+
if (ch === "\u2592") {
|
|
158
|
+
out += this.cyan(ch);
|
|
159
|
+
} else if (ch === "\u2593") {
|
|
160
|
+
out += this.dim(ch);
|
|
161
|
+
} else {
|
|
162
|
+
out += ch;
|
|
163
|
+
}
|
|
169
164
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
};
|
|
197
|
-
},
|
|
198
|
-
async health() {
|
|
199
|
-
return new Promise((resolve3) => {
|
|
200
|
-
const proc = (0, import_node_child_process.spawn)("claude", ["--version"], { stdio: "ignore" });
|
|
201
|
-
proc.on(
|
|
202
|
-
"exit",
|
|
203
|
-
(code) => resolve3(
|
|
204
|
-
code === 0 ? { available: true } : {
|
|
205
|
-
available: false,
|
|
206
|
-
reason: "claude CLI not in PATH",
|
|
207
|
-
hint: "npm install -g @anthropic-ai/claude-code"
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
// ---- Core write helpers ----
|
|
168
|
+
write(text) {
|
|
169
|
+
this.stream.write(text);
|
|
170
|
+
}
|
|
171
|
+
writeErr(text) {
|
|
172
|
+
this.errStream.write(text);
|
|
173
|
+
}
|
|
174
|
+
// ---- Level 1: Header ----
|
|
175
|
+
header(text) {
|
|
176
|
+
if (this.mode === "json") return;
|
|
177
|
+
const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
|
|
178
|
+
this.write(prefix + this.bold(text) + "\n");
|
|
179
|
+
}
|
|
180
|
+
smallHeader(subtitle) {
|
|
181
|
+
if (this.mode === "json" || this.mode === "quiet") return;
|
|
182
|
+
const iconPath = findIconPath("small");
|
|
183
|
+
if (iconPath !== null) {
|
|
184
|
+
try {
|
|
185
|
+
const raw = fs.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
|
|
186
|
+
for (const line of raw.split("\n")) {
|
|
187
|
+
this.write(this.cyan(line) + "\n");
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
|
|
208
191
|
}
|
|
209
|
-
|
|
192
|
+
} else {
|
|
193
|
+
this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
|
|
194
|
+
}
|
|
195
|
+
if (subtitle !== void 0 && subtitle.length > 0) {
|
|
196
|
+
this.write(this.dim(" " + subtitle) + "\n");
|
|
197
|
+
}
|
|
198
|
+
this.blank();
|
|
199
|
+
}
|
|
200
|
+
banner(subtitle, opts) {
|
|
201
|
+
if (this.mode === "json" || this.mode === "quiet") return;
|
|
202
|
+
const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
|
|
203
|
+
const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
|
|
204
|
+
const iconPath = findIconPath("big");
|
|
205
|
+
const shouldUseFull = opts?.full === true || width >= 120;
|
|
206
|
+
if (shouldUseFull && iconPath !== null) {
|
|
207
|
+
try {
|
|
208
|
+
const raw = fs.readFileSync(iconPath, "utf-8");
|
|
209
|
+
const lines = raw.split("\n");
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
|
|
212
|
+
const stripped = brandedLine.trimEnd();
|
|
213
|
+
if (stripped.length === 0) {
|
|
214
|
+
this.write("\n");
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
|
|
218
|
+
this.write(this.colorBrandLine(clipped) + "\n");
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
|
|
222
|
+
this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
|
|
226
|
+
this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
|
|
227
|
+
}
|
|
228
|
+
if (subtitle !== void 0 && subtitle.length > 0) {
|
|
229
|
+
this.write(this.dim(" " + subtitle) + "\n");
|
|
230
|
+
}
|
|
231
|
+
this.blank();
|
|
232
|
+
}
|
|
233
|
+
// ---- Level 2: Primary data ----
|
|
234
|
+
blank() {
|
|
235
|
+
if (this.mode === "json") return;
|
|
236
|
+
this.write("\n");
|
|
237
|
+
}
|
|
238
|
+
line(text) {
|
|
239
|
+
if (this.mode === "json") return;
|
|
240
|
+
this.write(text + "\n");
|
|
241
|
+
}
|
|
242
|
+
info(text) {
|
|
243
|
+
this.line(text);
|
|
244
|
+
}
|
|
245
|
+
// ---- Level 3: Secondary ----
|
|
246
|
+
secondary(text, indent = 2) {
|
|
247
|
+
if (this.mode === "json") return;
|
|
248
|
+
this.write(" ".repeat(indent) + this.dim(text) + "\n");
|
|
249
|
+
}
|
|
250
|
+
// ---- Level 4: Action tips ----
|
|
251
|
+
tip(text) {
|
|
252
|
+
if (this.mode === "json") return;
|
|
253
|
+
this.write(this.cyan("Tip:") + " " + text + "\n");
|
|
254
|
+
}
|
|
255
|
+
next(text) {
|
|
256
|
+
if (this.mode === "json") return;
|
|
257
|
+
this.write(this.cyan("Next:") + " " + text + "\n");
|
|
258
|
+
}
|
|
259
|
+
// ---- Level 5: Status indicators ----
|
|
260
|
+
ok(text, detail) {
|
|
261
|
+
if (this.mode === "json") return;
|
|
262
|
+
const suffix = detail ? " " + this.dim(detail) : "";
|
|
263
|
+
this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
|
|
264
|
+
}
|
|
265
|
+
success(text, detail) {
|
|
266
|
+
this.ok(text, detail);
|
|
267
|
+
}
|
|
268
|
+
fail(text, detail) {
|
|
269
|
+
if (this.mode === "json") return;
|
|
270
|
+
const suffix = detail ? " " + this.dim(detail) : "";
|
|
271
|
+
this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
|
|
272
|
+
}
|
|
273
|
+
online(text) {
|
|
274
|
+
if (this.mode === "json") return;
|
|
275
|
+
this.write(" " + this.green("\u25CF") + " " + text + "\n");
|
|
276
|
+
}
|
|
277
|
+
offline(text) {
|
|
278
|
+
if (this.mode === "json") return;
|
|
279
|
+
this.write(" " + this.gray("\u25CB") + " " + text + "\n");
|
|
280
|
+
}
|
|
281
|
+
notInstalled(text) {
|
|
282
|
+
if (this.mode === "json") return;
|
|
283
|
+
this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
|
|
284
|
+
}
|
|
285
|
+
pending(text) {
|
|
286
|
+
if (this.mode === "json") return;
|
|
287
|
+
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
288
|
+
}
|
|
289
|
+
warn(text, detail) {
|
|
290
|
+
if (this.mode === "json") return;
|
|
291
|
+
const suffix = detail ? " " + this.dim(detail) : "";
|
|
292
|
+
this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
|
|
293
|
+
}
|
|
294
|
+
// ---- Level 6: Error block ----
|
|
295
|
+
error(what, cause, fix) {
|
|
296
|
+
if (this.mode === "json") return;
|
|
297
|
+
this.writeErr(this.red("\u2717") + " " + what + "\n");
|
|
298
|
+
if (cause !== void 0) {
|
|
299
|
+
this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
|
|
300
|
+
}
|
|
301
|
+
if (fix !== void 0) {
|
|
302
|
+
this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
table(rowsOrOpts, maybeOpts) {
|
|
306
|
+
if (this.mode === "json") return;
|
|
307
|
+
const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
|
|
308
|
+
const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
|
|
309
|
+
if (!opts) throw new Error("table() requires columns");
|
|
310
|
+
const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
|
|
311
|
+
const cols = opts.columns;
|
|
312
|
+
const widths = cols.map((col) => col.length);
|
|
313
|
+
for (const row of rows) {
|
|
314
|
+
cols.forEach((col, i) => {
|
|
315
|
+
const val = row[col] ?? "";
|
|
316
|
+
const w = widths[i] ?? 0;
|
|
317
|
+
if (val.length > w) widths[i] = val.length;
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
|
|
321
|
+
if (totalWidth > maxWidth) {
|
|
322
|
+
for (let i = 0; i < rows.length; i++) {
|
|
323
|
+
const row = rows[i];
|
|
324
|
+
if (!row) continue;
|
|
325
|
+
for (const col of cols) {
|
|
326
|
+
const val = row[col] ?? "";
|
|
327
|
+
this.write(" " + this.bold(col + ":") + " " + val + "\n");
|
|
328
|
+
}
|
|
329
|
+
if (i < rows.length - 1) this.write("\n");
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const header2 = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
|
|
334
|
+
this.write(" " + this.dim(header2) + "\n");
|
|
335
|
+
for (const row of rows) {
|
|
336
|
+
const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
|
|
337
|
+
this.write(" " + line + "\n");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// ---- Spinner ----
|
|
341
|
+
spinner(text) {
|
|
342
|
+
if (this.mode === "quiet" || this.mode === "json") {
|
|
343
|
+
return {
|
|
344
|
+
update() {
|
|
345
|
+
},
|
|
346
|
+
stop() {
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const isTTY = this.stream.isTTY === true;
|
|
351
|
+
if (!isTTY || !this.colorEnabled) {
|
|
352
|
+
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
353
|
+
return {
|
|
354
|
+
update: (t) => {
|
|
355
|
+
this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
|
|
356
|
+
},
|
|
357
|
+
stop: (final) => {
|
|
358
|
+
if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
let current = text;
|
|
363
|
+
let frameIdx = 0;
|
|
364
|
+
let stopped = false;
|
|
365
|
+
const write = this.write.bind(this);
|
|
366
|
+
const colorFn = this.yellow.bind(this);
|
|
367
|
+
const greenFn = this.green.bind(this);
|
|
368
|
+
function renderFrame() {
|
|
369
|
+
const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
|
|
370
|
+
const line = " " + colorFn(frame) + " " + current;
|
|
371
|
+
write("\r" + line);
|
|
372
|
+
frameIdx++;
|
|
373
|
+
}
|
|
374
|
+
renderFrame();
|
|
375
|
+
const timer = setInterval(renderFrame, 80);
|
|
376
|
+
return {
|
|
377
|
+
update(t) {
|
|
378
|
+
if (stopped) return;
|
|
379
|
+
current = t;
|
|
380
|
+
},
|
|
381
|
+
stop(final) {
|
|
382
|
+
if (stopped) return;
|
|
383
|
+
stopped = true;
|
|
384
|
+
clearInterval(timer);
|
|
385
|
+
write("\r\x1B[2K");
|
|
386
|
+
if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// ---- Progress bar ----
|
|
391
|
+
progress(text, total) {
|
|
392
|
+
if (this.mode === "quiet" || this.mode === "json") {
|
|
393
|
+
return {
|
|
394
|
+
update() {
|
|
395
|
+
},
|
|
396
|
+
stop() {
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const isTTY = this.stream.isTTY === true;
|
|
401
|
+
const start = Date.now();
|
|
402
|
+
const write = this.write.bind(this);
|
|
403
|
+
const colorFn = this.cyan.bind(this);
|
|
404
|
+
const dimFn = this.dim.bind(this);
|
|
405
|
+
const greenFn = this.green.bind(this);
|
|
406
|
+
let last = 0;
|
|
407
|
+
let lastDetail = "";
|
|
408
|
+
let stopped = false;
|
|
409
|
+
const render = () => {
|
|
410
|
+
if (stopped) return;
|
|
411
|
+
const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
|
|
412
|
+
const pct = Math.floor(frac * 100);
|
|
413
|
+
const width = 20;
|
|
414
|
+
const filled = Math.floor(frac * width);
|
|
415
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
416
|
+
const elapsed = (Date.now() - start) / 1e3;
|
|
417
|
+
const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
|
|
418
|
+
const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
|
|
419
|
+
const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
|
|
420
|
+
const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
|
|
421
|
+
if (isTTY && this.colorEnabled) {
|
|
422
|
+
write("\r\x1B[2K" + line);
|
|
423
|
+
} else {
|
|
424
|
+
write(line + "\n");
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
render();
|
|
428
|
+
return {
|
|
429
|
+
update: (current, detail) => {
|
|
430
|
+
if (stopped) return;
|
|
431
|
+
last = current;
|
|
432
|
+
if (detail !== void 0) lastDetail = detail;
|
|
433
|
+
render();
|
|
434
|
+
},
|
|
435
|
+
stop: (final) => {
|
|
436
|
+
if (stopped) return;
|
|
437
|
+
stopped = true;
|
|
438
|
+
if (isTTY && this.colorEnabled) write("\r\x1B[2K");
|
|
439
|
+
if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
// ---- JSON output ----
|
|
444
|
+
json(payload, opts) {
|
|
445
|
+
const indent = opts?.pretty ? 2 : void 0;
|
|
446
|
+
this.write(JSON.stringify(payload, null, indent) + "\n");
|
|
447
|
+
}
|
|
448
|
+
result(pretty, jsonPayload) {
|
|
449
|
+
if (this.mode === "pretty") {
|
|
450
|
+
pretty();
|
|
451
|
+
} else {
|
|
452
|
+
this.json(jsonPayload);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
_ui = null;
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// src/cli/util.ts
|
|
461
|
+
var util_exports = {};
|
|
462
|
+
__export(util_exports, {
|
|
463
|
+
DEFAULT_CLOUD_BASE_URL: () => DEFAULT_CLOUD_BASE_URL,
|
|
464
|
+
clearPidFile: () => clearPidFile,
|
|
465
|
+
color: () => color,
|
|
466
|
+
exitWithError: () => exitWithError,
|
|
467
|
+
fail: () => fail,
|
|
468
|
+
header: () => header,
|
|
469
|
+
info: () => info,
|
|
470
|
+
normalizeCloudUrl: () => normalizeCloudUrl,
|
|
471
|
+
ok: () => ok,
|
|
472
|
+
pidAlive: () => pidAlive,
|
|
473
|
+
pidFilePath: () => pidFilePath,
|
|
474
|
+
printBanner: () => printBanner,
|
|
475
|
+
printJson: () => printJson,
|
|
476
|
+
readPidFile: () => readPidFile,
|
|
477
|
+
runAction: () => runAction,
|
|
478
|
+
table: () => table,
|
|
479
|
+
tip: () => tip,
|
|
480
|
+
warn: () => warn,
|
|
481
|
+
writePidFile: () => writePidFile
|
|
482
|
+
});
|
|
483
|
+
function color(kind, text) {
|
|
484
|
+
if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
|
|
485
|
+
return `${ANSI[kind]}${text}${ANSI.reset}`;
|
|
486
|
+
}
|
|
487
|
+
function printJson(v) {
|
|
488
|
+
getUI().json(v, { pretty: true });
|
|
489
|
+
}
|
|
490
|
+
function exitWithError(message, opts) {
|
|
491
|
+
const o = typeof opts === "number" ? { exitCode: opts } : opts ?? {};
|
|
492
|
+
const exitCode = o.exitCode ?? 1;
|
|
493
|
+
const ui = getUI();
|
|
494
|
+
if (ui.mode === "json") {
|
|
495
|
+
const payload = {
|
|
496
|
+
ok: false,
|
|
497
|
+
error: { code: o.code ?? "cli_error", message },
|
|
498
|
+
...o.details ? { details: o.details } : {}
|
|
499
|
+
};
|
|
500
|
+
ui.json(payload, { pretty: true });
|
|
501
|
+
} else {
|
|
502
|
+
process.stderr.write(`Error: ${message}
|
|
503
|
+
`);
|
|
504
|
+
}
|
|
505
|
+
process.exit(exitCode);
|
|
506
|
+
}
|
|
507
|
+
function normalizeCloudUrl(input) {
|
|
508
|
+
const raw = input.trim();
|
|
509
|
+
if (!raw) throw new Error("Cloud URL is empty.");
|
|
510
|
+
if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(raw)) {
|
|
511
|
+
let parsed;
|
|
512
|
+
try {
|
|
513
|
+
parsed = new URL(raw);
|
|
514
|
+
} catch {
|
|
515
|
+
throw new Error(`Invalid --cloud URL: ${raw}`);
|
|
516
|
+
}
|
|
517
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
518
|
+
throw new Error(`Invalid --cloud URL scheme: ${parsed.protocol} (expected http:// or https://)`);
|
|
519
|
+
}
|
|
520
|
+
return raw.replace(/\/$/, "");
|
|
521
|
+
}
|
|
522
|
+
if (!/^[A-Za-z0-9.\-_:[\]/]+$/.test(raw)) {
|
|
523
|
+
throw new Error(`Invalid --cloud URL: ${raw} (must be http://\u2026 or https://\u2026 or host:port)`);
|
|
524
|
+
}
|
|
525
|
+
const candidate = `http://${raw}`;
|
|
526
|
+
try {
|
|
527
|
+
new URL(candidate);
|
|
528
|
+
} catch {
|
|
529
|
+
throw new Error(`Invalid --cloud URL: ${raw}`);
|
|
530
|
+
}
|
|
531
|
+
return candidate.replace(/\/$/, "");
|
|
532
|
+
}
|
|
533
|
+
function runAction(fn, opts = {}) {
|
|
534
|
+
return async (...args) => {
|
|
535
|
+
try {
|
|
536
|
+
await fn(...args);
|
|
537
|
+
} catch (err) {
|
|
538
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
539
|
+
const message = opts.sanitize ? opts.sanitize(raw) : raw;
|
|
540
|
+
exitWithError(message, { code: opts.code });
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function printBanner(opts = {}) {
|
|
545
|
+
const ui = getUI();
|
|
546
|
+
if (opts.compact) {
|
|
547
|
+
ui.smallHeader("Runtime CLI v1.9.7");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
ui.banner("Runtime CLI v1.9.7", { full: true });
|
|
551
|
+
}
|
|
552
|
+
function ok(label, detail) {
|
|
553
|
+
getUI().ok(label, detail);
|
|
554
|
+
}
|
|
555
|
+
function warn(label, detail) {
|
|
556
|
+
getUI().warn(label, detail);
|
|
557
|
+
}
|
|
558
|
+
function fail(label, detail) {
|
|
559
|
+
getUI().fail(label, detail);
|
|
560
|
+
}
|
|
561
|
+
function tip(command, detail) {
|
|
562
|
+
const text = detail ? `${command} ${detail}` : command;
|
|
563
|
+
getUI().tip(text);
|
|
564
|
+
}
|
|
565
|
+
function info(message) {
|
|
566
|
+
getUI().info(message);
|
|
567
|
+
}
|
|
568
|
+
function header(title) {
|
|
569
|
+
getUI().header(title);
|
|
570
|
+
getUI().blank();
|
|
571
|
+
}
|
|
572
|
+
function table(rows, columns) {
|
|
573
|
+
getUI().table(rows, { columns });
|
|
574
|
+
}
|
|
575
|
+
function pidFilePath(paths) {
|
|
576
|
+
return (0, import_node_path3.join)(paths.root, "daemon.pid");
|
|
577
|
+
}
|
|
578
|
+
function writePidFile(paths, pid) {
|
|
579
|
+
(0, import_node_fs3.writeFileSync)(pidFilePath(paths), `${pid}
|
|
580
|
+
`, "utf8");
|
|
581
|
+
}
|
|
582
|
+
function readPidFile(paths) {
|
|
583
|
+
const p = pidFilePath(paths);
|
|
584
|
+
if (!(0, import_node_fs3.existsSync)(p)) return void 0;
|
|
585
|
+
const raw = (0, import_node_fs3.readFileSync)(p, "utf8").trim();
|
|
586
|
+
const pid = Number.parseInt(raw, 10);
|
|
587
|
+
return Number.isFinite(pid) ? pid : void 0;
|
|
588
|
+
}
|
|
589
|
+
function clearPidFile(paths) {
|
|
590
|
+
const p = pidFilePath(paths);
|
|
591
|
+
if ((0, import_node_fs3.existsSync)(p)) {
|
|
592
|
+
try {
|
|
593
|
+
(0, import_node_fs3.unlinkSync)(p);
|
|
594
|
+
} catch {
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function pidAlive(pid) {
|
|
599
|
+
try {
|
|
600
|
+
process.kill(pid, 0);
|
|
601
|
+
return true;
|
|
602
|
+
} catch {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
var import_node_fs3, import_node_path3, DEFAULT_CLOUD_BASE_URL, ANSI;
|
|
607
|
+
var init_util = __esm({
|
|
608
|
+
"src/cli/util.ts"() {
|
|
609
|
+
"use strict";
|
|
610
|
+
import_node_fs3 = require("fs");
|
|
611
|
+
import_node_path3 = require("path");
|
|
612
|
+
init_ui();
|
|
613
|
+
DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
|
|
614
|
+
ANSI = {
|
|
615
|
+
reset: "\x1B[0m",
|
|
616
|
+
bold: "\x1B[1m",
|
|
617
|
+
dim: "\x1B[2m",
|
|
618
|
+
cyan: "\x1B[36m",
|
|
619
|
+
green: "\x1B[32m",
|
|
620
|
+
yellow: "\x1B[33m",
|
|
621
|
+
red: "\x1B[31m"
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// src/cli/index.ts
|
|
627
|
+
var import_commander18 = require("commander");
|
|
628
|
+
|
|
629
|
+
// src/cli/commands/adapter.ts
|
|
630
|
+
var import_node_child_process4 = require("child_process");
|
|
631
|
+
var import_node_fs4 = require("fs");
|
|
632
|
+
var import_node_os3 = require("os");
|
|
633
|
+
var import_node_path4 = require("path");
|
|
634
|
+
var import_commander = require("commander");
|
|
635
|
+
|
|
636
|
+
// src/adapters/claude-code/index.ts
|
|
637
|
+
var import_node_child_process = require("child_process");
|
|
638
|
+
var import_zod = require("zod");
|
|
639
|
+
|
|
640
|
+
// src/adapters/claude-code/output-parser.ts
|
|
641
|
+
var MAX_OUTPUT_CHARS = 64 * 1024;
|
|
642
|
+
function parseHeadlessOutput(stdout) {
|
|
643
|
+
let out = "";
|
|
644
|
+
let toolCalls = 0;
|
|
645
|
+
for (const line of stdout.split("\n")) {
|
|
646
|
+
const trimmed = line.trim();
|
|
647
|
+
if (!trimmed) continue;
|
|
648
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
649
|
+
try {
|
|
650
|
+
const obj = JSON.parse(trimmed);
|
|
651
|
+
if (obj.event === "tool_call" || obj.tool) {
|
|
652
|
+
toolCalls += 1;
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (typeof obj.text === "string") {
|
|
656
|
+
out += obj.text;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
out += line + "\n";
|
|
663
|
+
}
|
|
664
|
+
const truncated = out.length > MAX_OUTPUT_CHARS;
|
|
665
|
+
if (truncated) out = out.slice(0, MAX_OUTPUT_CHARS) + "\n\u2026[truncated]";
|
|
666
|
+
return { output: out.trim(), toolCalls, truncated };
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/adapters/claude-code/index.ts
|
|
670
|
+
var McpServerSchema = import_zod.z.object({
|
|
671
|
+
name: import_zod.z.string(),
|
|
672
|
+
command: import_zod.z.string(),
|
|
673
|
+
args: import_zod.z.array(import_zod.z.string()).optional(),
|
|
674
|
+
env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
|
|
675
|
+
});
|
|
676
|
+
var CCConfigSchema = import_zod.z.object({
|
|
677
|
+
cwd: import_zod.z.string().min(1),
|
|
678
|
+
// claude 2.x accepts aliases ('sonnet', 'haiku', 'opus') and full ids
|
|
679
|
+
// (e.g. 'claude-sonnet-4-6'). The 1.x default 'claude-3-5-sonnet' is no
|
|
680
|
+
// longer a valid model id under the current CLI.
|
|
681
|
+
model: import_zod.z.string().default("sonnet"),
|
|
682
|
+
systemPrompt: import_zod.z.string().optional(),
|
|
683
|
+
envVars: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
|
|
684
|
+
mcpServers: import_zod.z.array(McpServerSchema).optional(),
|
|
685
|
+
allowedTools: import_zod.z.array(import_zod.z.string()).optional(),
|
|
686
|
+
maxTurns: import_zod.z.number().int().positive().default(20),
|
|
687
|
+
// External-model route (1.9.x extension — optional, no breaking change)
|
|
688
|
+
baseURL: import_zod.z.string().url().optional(),
|
|
689
|
+
apiKeyRef: import_zod.z.string().regex(/^(env|keychain):[A-Za-z0-9_][A-Za-z0-9_.\-]*$/).optional(),
|
|
690
|
+
// informational tag — dispatch does not branch on this value yet
|
|
691
|
+
route: import_zod.z.enum(["default", "prismer", "omniroute"]).default("default")
|
|
692
|
+
});
|
|
693
|
+
var claudeCodeAdapter = {
|
|
694
|
+
name: "claude-code",
|
|
695
|
+
kind: "interactive",
|
|
696
|
+
capabilities: ["shell", "code", "mcp", "edit"],
|
|
697
|
+
workspaceSchema: CCConfigSchema,
|
|
698
|
+
validate(config) {
|
|
699
|
+
const r = CCConfigSchema.safeParse(config);
|
|
700
|
+
if (r.success) return { ok: true };
|
|
701
|
+
return {
|
|
702
|
+
ok: false,
|
|
703
|
+
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
704
|
+
};
|
|
705
|
+
},
|
|
706
|
+
async dispatch(profile, task) {
|
|
707
|
+
const config = CCConfigSchema.parse(profile.config);
|
|
708
|
+
const args = ["--print"];
|
|
709
|
+
if (config.model) {
|
|
710
|
+
args.push("--model", config.model);
|
|
711
|
+
}
|
|
712
|
+
if (config.allowedTools && config.allowedTools.length > 0) {
|
|
713
|
+
args.push("--allowed-tools", config.allowedTools.join(","));
|
|
714
|
+
}
|
|
715
|
+
if (config.systemPrompt) {
|
|
716
|
+
args.push("--system-prompt", config.systemPrompt);
|
|
717
|
+
}
|
|
718
|
+
args.push(task.prompt);
|
|
719
|
+
const startedAt = Date.now();
|
|
720
|
+
const env = { ...process.env, ...config.envVars ?? {} };
|
|
721
|
+
if (config.baseURL) {
|
|
722
|
+
env.ANTHROPIC_API_BASE = config.baseURL;
|
|
723
|
+
}
|
|
724
|
+
const outboxDir = typeof task.metadata?.prismerOutboxDir === "string" ? task.metadata.prismerOutboxDir : void 0;
|
|
725
|
+
if (outboxDir) {
|
|
726
|
+
env.PRISMER_OUTBOX_DIR = outboxDir;
|
|
727
|
+
}
|
|
728
|
+
if (config.apiKeyRef) {
|
|
729
|
+
const resolved = resolveKeyRef(config.apiKeyRef);
|
|
730
|
+
if (resolved) {
|
|
731
|
+
env.ANTHROPIC_API_KEY = resolved;
|
|
732
|
+
} else {
|
|
733
|
+
const platformHint = config.apiKeyRef.startsWith("keychain:") && process.platform !== "darwin" ? " (keychain: scheme requires darwin)" : "";
|
|
734
|
+
process.stderr.write(
|
|
735
|
+
`[claude-code] warning: apiKeyRef "${config.apiKeyRef}" could not be resolved${platformHint}; ANTHROPIC_API_KEY will not be injected
|
|
736
|
+
`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const child = (0, import_node_child_process.spawn)("claude", args, {
|
|
741
|
+
cwd: config.cwd,
|
|
742
|
+
env,
|
|
743
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
744
|
+
});
|
|
745
|
+
let stdout = "";
|
|
746
|
+
let stderr = "";
|
|
747
|
+
child.stdout.on("data", (d) => {
|
|
748
|
+
stdout += d.toString();
|
|
749
|
+
});
|
|
750
|
+
child.stderr.on("data", (d) => {
|
|
751
|
+
stderr += d.toString();
|
|
752
|
+
});
|
|
753
|
+
const onAbort = () => {
|
|
754
|
+
try {
|
|
755
|
+
child.kill("SIGTERM");
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
if (task.signal) {
|
|
760
|
+
if (task.signal.aborted) onAbort();
|
|
761
|
+
else task.signal.addEventListener("abort", onAbort, { once: true });
|
|
762
|
+
}
|
|
763
|
+
let timeoutHandle;
|
|
764
|
+
if (task.timeoutMs && task.timeoutMs > 0) {
|
|
765
|
+
timeoutHandle = setTimeout(() => {
|
|
766
|
+
try {
|
|
767
|
+
child.kill("SIGTERM");
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
}, task.timeoutMs);
|
|
771
|
+
}
|
|
772
|
+
const exitCode = await new Promise((resolve3) => {
|
|
773
|
+
child.on("exit", (code) => resolve3(code));
|
|
774
|
+
child.on("error", () => resolve3(-1));
|
|
775
|
+
});
|
|
776
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
777
|
+
const durationMs = Date.now() - startedAt;
|
|
778
|
+
if (task.signal?.aborted) {
|
|
779
|
+
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
780
|
+
}
|
|
781
|
+
if (exitCode !== 0) {
|
|
782
|
+
return {
|
|
783
|
+
ok: false,
|
|
784
|
+
error: {
|
|
785
|
+
code: exitCode === null ? "adapter_dispatch_failed" : "adapter_dispatch_failed",
|
|
786
|
+
message: `claude exit ${exitCode}: ${stderr.slice(0, 1024) || "<no stderr>"}`
|
|
787
|
+
},
|
|
788
|
+
metrics: { durationMs }
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const parsed = parseHeadlessOutput(stdout);
|
|
792
|
+
return {
|
|
793
|
+
ok: true,
|
|
794
|
+
output: parsed.output,
|
|
795
|
+
metrics: { durationMs }
|
|
796
|
+
};
|
|
797
|
+
},
|
|
798
|
+
async health() {
|
|
799
|
+
return new Promise((resolve3) => {
|
|
800
|
+
const proc = (0, import_node_child_process.spawn)("claude", ["--version"], { stdio: "ignore" });
|
|
801
|
+
proc.on(
|
|
802
|
+
"exit",
|
|
803
|
+
(code) => resolve3(
|
|
804
|
+
code === 0 ? { available: true } : {
|
|
805
|
+
available: false,
|
|
806
|
+
reason: "claude CLI not in PATH",
|
|
807
|
+
hint: "npm install -g @anthropic-ai/claude-code"
|
|
808
|
+
}
|
|
809
|
+
)
|
|
210
810
|
);
|
|
211
811
|
proc.on(
|
|
212
812
|
"error",
|
|
@@ -659,7 +1259,7 @@ var HermesProfileConfigSchema = import_zod3.z.object({
|
|
|
659
1259
|
*/
|
|
660
1260
|
prismerMcpServerPath: import_zod3.z.string().optional(),
|
|
661
1261
|
/** Model sent to Prismer's /api/v1/chat/completions endpoint. */
|
|
662
|
-
model: import_zod3.z.string().min(1).default("us-kimi-k2.
|
|
1262
|
+
model: import_zod3.z.string().min(1).default("us-kimi-k2.6"),
|
|
663
1263
|
/** Named custom provider written into Hermes config.yaml. */
|
|
664
1264
|
prismerProviderName: import_zod3.z.string().min(1).default("prismer"),
|
|
665
1265
|
/** Override cloud provider base. Defaults to PRISMER_BASE_URL + /api/v1. */
|
|
@@ -685,7 +1285,9 @@ var HermesProfileConfigSchema = import_zod3.z.object({
|
|
|
685
1285
|
* surface but the local source tree contains hermes_cli/kanban_db.py.
|
|
686
1286
|
*/
|
|
687
1287
|
hermesSourceDir: import_zod3.z.string().optional(),
|
|
688
|
-
nativeMirrorTimeoutMs: import_zod3.z.number().int().positive().default(2e3)
|
|
1288
|
+
nativeMirrorTimeoutMs: import_zod3.z.number().int().positive().default(2e3),
|
|
1289
|
+
/** Task authority level: executor (default) or orchestrator. */
|
|
1290
|
+
taskAuthority: import_zod3.z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
689
1291
|
});
|
|
690
1292
|
var hermesAdapter = {
|
|
691
1293
|
name: "hermes",
|
|
@@ -1170,13 +1772,13 @@ function resolvePrismerMcpServerPath(config) {
|
|
|
1170
1772
|
}
|
|
1171
1773
|
try {
|
|
1172
1774
|
const { fileURLToPath: fileURLToPath2 } = require("url");
|
|
1173
|
-
const { join:
|
|
1775
|
+
const { join: join14, dirname: dirname8 } = require("path");
|
|
1174
1776
|
const here = dirname8(fileURLToPath2(import_meta.url));
|
|
1175
|
-
const candidate =
|
|
1777
|
+
const candidate = join14(here, "../../mcp/dist/index.js");
|
|
1176
1778
|
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
1177
|
-
const candidate2 =
|
|
1779
|
+
const candidate2 = join14(here, "../../../mcp/dist/index.js");
|
|
1178
1780
|
if ((0, import_node_fs.existsSync)(candidate2)) return candidate2;
|
|
1179
|
-
const candidate3 =
|
|
1781
|
+
const candidate3 = join14(here, "../../../../mcp/dist/index.js");
|
|
1180
1782
|
if ((0, import_node_fs.existsSync)(candidate3)) return candidate3;
|
|
1181
1783
|
} catch {
|
|
1182
1784
|
}
|
|
@@ -1586,673 +2188,125 @@ var OpenClawService = class {
|
|
|
1586
2188
|
if (ERR_PREFIX_RE.test(output)) {
|
|
1587
2189
|
process.stderr.write(
|
|
1588
2190
|
`[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
|
|
1589
|
-
`
|
|
1590
|
-
);
|
|
1591
|
-
output = output.replace(ERR_PREFIX_RE, "");
|
|
1592
|
-
}
|
|
1593
|
-
return {
|
|
1594
|
-
ok: true,
|
|
1595
|
-
output,
|
|
1596
|
-
metrics: { durationMs: Date.now() - startedAt }
|
|
1597
|
-
};
|
|
1598
|
-
} catch (err) {
|
|
1599
|
-
if (task.signal?.aborted || err?.name === "AbortError") {
|
|
1600
|
-
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
1601
|
-
}
|
|
1602
|
-
return {
|
|
1603
|
-
ok: false,
|
|
1604
|
-
error: { code: "adapter_dispatch_failed", message: err.message }
|
|
1605
|
-
};
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
};
|
|
1609
|
-
async function checkHealth2(baseUrl, apiKey) {
|
|
1610
|
-
try {
|
|
1611
|
-
const res = await fetch(`${baseUrl}/health`, {
|
|
1612
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1613
|
-
signal: AbortSignal.timeout(2e3)
|
|
1614
|
-
});
|
|
1615
|
-
return res.ok;
|
|
1616
|
-
} catch {
|
|
1617
|
-
return false;
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
// src/config.ts
|
|
1622
|
-
var TOML = __toESM(require("@iarna/toml"), 1);
|
|
1623
|
-
var import_node_fs2 = require("fs");
|
|
1624
|
-
var import_node_os2 = require("os");
|
|
1625
|
-
var import_node_path2 = require("path");
|
|
1626
|
-
var import_zod5 = require("zod");
|
|
1627
|
-
var ConfigSchema = import_zod5.z.object({
|
|
1628
|
-
/** API key from `prismer setup`, or env override. */
|
|
1629
|
-
api_key: import_zod5.z.string().min(1),
|
|
1630
|
-
/** Cloud REST + WS base; `ws://` is derived by stripping `http`. */
|
|
1631
|
-
cloud_api_base: import_zod5.z.string().url(),
|
|
1632
|
-
/** Stable per-machine daemon identifier. Generated once on first setup. */
|
|
1633
|
-
daemon_id: import_zod5.z.string().min(1),
|
|
1634
|
-
/** Optional adapter-specific overrides keyed by adapter name. */
|
|
1635
|
-
adapters: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown())).optional(),
|
|
1636
|
-
/** Local daemon shell execution. Default disabled. */
|
|
1637
|
-
shell: import_zod5.z.object({
|
|
1638
|
-
enabled: import_zod5.z.boolean().default(false),
|
|
1639
|
-
default_cwd: import_zod5.z.string().optional(),
|
|
1640
|
-
defaultCwd: import_zod5.z.string().optional(),
|
|
1641
|
-
shell: import_zod5.z.enum(["bash", "zsh", "sh"]).optional(),
|
|
1642
|
-
max_timeout_ms: import_zod5.z.number().int().positive().optional(),
|
|
1643
|
-
maxTimeoutMs: import_zod5.z.number().int().positive().optional(),
|
|
1644
|
-
max_output_bytes: import_zod5.z.number().int().positive().optional(),
|
|
1645
|
-
maxOutputBytes: import_zod5.z.number().int().positive().optional(),
|
|
1646
|
-
allowed_workspaces: import_zod5.z.array(import_zod5.z.string()).optional(),
|
|
1647
|
-
allowedWorkspaces: import_zod5.z.array(import_zod5.z.string()).optional()
|
|
1648
|
-
}).optional(),
|
|
1649
|
-
/** Local cache settings. */
|
|
1650
|
-
cache: import_zod5.z.object({
|
|
1651
|
-
max_bytes: import_zod5.z.number().int().positive().default(5 * 1024 * 1024 * 1024)
|
|
1652
|
-
}).optional()
|
|
1653
|
-
});
|
|
1654
|
-
function resolvePaths(home) {
|
|
1655
|
-
const root = home ?? process.env.PRISMER_HOME ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".prismer");
|
|
1656
|
-
return {
|
|
1657
|
-
root,
|
|
1658
|
-
configFile: (0, import_node_path2.join)(root, "config.toml"),
|
|
1659
|
-
localDb: (0, import_node_path2.join)(root, "local.db"),
|
|
1660
|
-
cacheDir: (0, import_node_path2.join)(root, "cache"),
|
|
1661
|
-
logsDir: (0, import_node_path2.join)(root, "logs"),
|
|
1662
|
-
runsDir: (0, import_node_path2.join)(root, "runs")
|
|
1663
|
-
};
|
|
1664
|
-
}
|
|
1665
|
-
function configExists(paths = resolvePaths()) {
|
|
1666
|
-
return (0, import_node_fs2.existsSync)(paths.configFile);
|
|
1667
|
-
}
|
|
1668
|
-
function loadConfig(paths = resolvePaths()) {
|
|
1669
|
-
if (!(0, import_node_fs2.existsSync)(paths.configFile)) {
|
|
1670
|
-
throw new Error(
|
|
1671
|
-
`Config not found at ${paths.configFile}. Run \`prismer setup\` to create it.`
|
|
1672
|
-
);
|
|
1673
|
-
}
|
|
1674
|
-
const raw = (0, import_node_fs2.readFileSync)(paths.configFile, "utf8");
|
|
1675
|
-
const parsed = TOML.parse(raw);
|
|
1676
|
-
const merged = {
|
|
1677
|
-
...parsed,
|
|
1678
|
-
api_key: process.env.PRISMER_API_KEY ?? parsed.api_key,
|
|
1679
|
-
cloud_api_base: process.env.PRISMER_BASE_URL ?? parsed.cloud_api_base
|
|
1680
|
-
};
|
|
1681
|
-
const result = ConfigSchema.safeParse(merged);
|
|
1682
|
-
if (!result.success) {
|
|
1683
|
-
const detail = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1684
|
-
throw new Error(`Invalid config at ${paths.configFile}: ${detail}`);
|
|
1685
|
-
}
|
|
1686
|
-
return result.data;
|
|
1687
|
-
}
|
|
1688
|
-
function saveConfig(config, paths = resolvePaths()) {
|
|
1689
|
-
if (!(0, import_node_fs2.existsSync)(paths.root)) {
|
|
1690
|
-
(0, import_node_fs2.mkdirSync)(paths.root, { recursive: true });
|
|
1691
|
-
}
|
|
1692
|
-
if (!(0, import_node_fs2.existsSync)((0, import_node_path2.dirname)(paths.configFile))) {
|
|
1693
|
-
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(paths.configFile), { recursive: true });
|
|
1694
|
-
}
|
|
1695
|
-
ConfigSchema.parse(config);
|
|
1696
|
-
(0, import_node_fs2.writeFileSync)(paths.configFile, TOML.stringify(config), "utf8");
|
|
1697
|
-
}
|
|
1698
|
-
function deriveWsUrl(httpBase) {
|
|
1699
|
-
const u = new URL(httpBase);
|
|
1700
|
-
u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
|
|
1701
|
-
u.pathname = (u.pathname.replace(/\/$/, "") || "") + "/ws";
|
|
1702
|
-
return u.toString();
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
// src/cli/util.ts
|
|
1706
|
-
var import_node_fs3 = require("fs");
|
|
1707
|
-
var import_node_path3 = require("path");
|
|
1708
|
-
|
|
1709
|
-
// src/cli/ui.ts
|
|
1710
|
-
var fs = __toESM(require("fs"), 1);
|
|
1711
|
-
var path = __toESM(require("path"), 1);
|
|
1712
|
-
var import_node_url = require("url");
|
|
1713
|
-
var import_meta2 = {};
|
|
1714
|
-
var BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1715
|
-
var COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
|
|
1716
|
-
function thisDirname() {
|
|
1717
|
-
try {
|
|
1718
|
-
return path.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
|
|
1719
|
-
} catch {
|
|
1720
|
-
return process.cwd();
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
function findIconPath(size = "big") {
|
|
1724
|
-
const name = size === "big" ? "icon" : "smallicon";
|
|
1725
|
-
const here = thisDirname();
|
|
1726
|
-
const candidates = [
|
|
1727
|
-
// npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
|
|
1728
|
-
path.resolve(here, "../assets", name),
|
|
1729
|
-
// alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
|
|
1730
|
-
path.resolve(here, "../../assets", name),
|
|
1731
|
-
// source/typecheck: src/cli/ui.ts → ../../assets
|
|
1732
|
-
path.resolve(here, "../../assets", name),
|
|
1733
|
-
// dev mode: cwd happens to be runtime root
|
|
1734
|
-
path.resolve(process.cwd(), "assets", name),
|
|
1735
|
-
path.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
|
|
1736
|
-
];
|
|
1737
|
-
for (const candidate of candidates) {
|
|
1738
|
-
try {
|
|
1739
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
1740
|
-
} catch {
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
return null;
|
|
1744
|
-
}
|
|
1745
|
-
var UI = class {
|
|
1746
|
-
mode;
|
|
1747
|
-
colorEnabled;
|
|
1748
|
-
stream;
|
|
1749
|
-
errStream;
|
|
1750
|
-
constructor(opts) {
|
|
1751
|
-
this.mode = opts?.mode ?? "pretty";
|
|
1752
|
-
this.stream = opts?.stream ?? process.stdout;
|
|
1753
|
-
this.errStream = opts?.errStream ?? process.stderr;
|
|
1754
|
-
if (opts?.color !== void 0) {
|
|
1755
|
-
this.colorEnabled = opts.color;
|
|
1756
|
-
} else {
|
|
1757
|
-
const isTTY = this.stream.isTTY === true;
|
|
1758
|
-
const noColor = Boolean(process.env["NO_COLOR"]);
|
|
1759
|
-
this.colorEnabled = isTTY && !noColor;
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
// ---- Internal color helpers ----
|
|
1763
|
-
ansi(open, close, text) {
|
|
1764
|
-
if (!this.colorEnabled) return text;
|
|
1765
|
-
return `\x1B[${open}m${text}\x1B[${close}m`;
|
|
1766
|
-
}
|
|
1767
|
-
green(t) {
|
|
1768
|
-
return this.ansi(32, 39, t);
|
|
1769
|
-
}
|
|
1770
|
-
red(t) {
|
|
1771
|
-
return this.ansi(31, 39, t);
|
|
1772
|
-
}
|
|
1773
|
-
yellow(t) {
|
|
1774
|
-
return this.ansi(33, 39, t);
|
|
1775
|
-
}
|
|
1776
|
-
cyan(t) {
|
|
1777
|
-
return this.ansi(36, 39, t);
|
|
1778
|
-
}
|
|
1779
|
-
dim(t) {
|
|
1780
|
-
return this.ansi(2, 22, t);
|
|
1781
|
-
}
|
|
1782
|
-
bold(t) {
|
|
1783
|
-
return this.ansi(1, 22, t);
|
|
1784
|
-
}
|
|
1785
|
-
gray(t) {
|
|
1786
|
-
return this.ansi(90, 39, t);
|
|
1787
|
-
}
|
|
1788
|
-
brandMark() {
|
|
1789
|
-
return this.cyan("\u25C7");
|
|
1790
|
-
}
|
|
1791
|
-
colorBrandLine(line) {
|
|
1792
|
-
let out = "";
|
|
1793
|
-
for (const ch of line) {
|
|
1794
|
-
if (ch === "\u2592") {
|
|
1795
|
-
out += this.cyan(ch);
|
|
1796
|
-
} else if (ch === "\u2593") {
|
|
1797
|
-
out += this.dim(ch);
|
|
1798
|
-
} else {
|
|
1799
|
-
out += ch;
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
return out;
|
|
1803
|
-
}
|
|
1804
|
-
// ---- Core write helpers ----
|
|
1805
|
-
write(text) {
|
|
1806
|
-
this.stream.write(text);
|
|
1807
|
-
}
|
|
1808
|
-
writeErr(text) {
|
|
1809
|
-
this.errStream.write(text);
|
|
1810
|
-
}
|
|
1811
|
-
// ---- Level 1: Header ----
|
|
1812
|
-
header(text) {
|
|
1813
|
-
if (this.mode === "json") return;
|
|
1814
|
-
const prefix = text.startsWith("Prismer") ? this.brandMark() + " " : "";
|
|
1815
|
-
this.write(prefix + this.bold(text) + "\n");
|
|
1816
|
-
}
|
|
1817
|
-
smallHeader(subtitle) {
|
|
1818
|
-
if (this.mode === "json" || this.mode === "quiet") return;
|
|
1819
|
-
const iconPath = findIconPath("small");
|
|
1820
|
-
if (iconPath !== null) {
|
|
1821
|
-
try {
|
|
1822
|
-
const raw = fs.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
|
|
1823
|
-
for (const line of raw.split("\n")) {
|
|
1824
|
-
this.write(this.cyan(line) + "\n");
|
|
1825
|
-
}
|
|
1826
|
-
} catch {
|
|
1827
|
-
this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
|
|
1828
|
-
}
|
|
1829
|
-
} else {
|
|
1830
|
-
this.write(this.brandMark() + " " + this.bold("Prismer") + "\n");
|
|
1831
|
-
}
|
|
1832
|
-
if (subtitle !== void 0 && subtitle.length > 0) {
|
|
1833
|
-
this.write(this.dim(" " + subtitle) + "\n");
|
|
1834
|
-
}
|
|
1835
|
-
this.blank();
|
|
1836
|
-
}
|
|
1837
|
-
banner(subtitle, opts) {
|
|
1838
|
-
if (this.mode === "json" || this.mode === "quiet") return;
|
|
1839
|
-
const envColumns = process.env["COLUMNS"] !== void 0 ? parseInt(process.env["COLUMNS"], 10) : NaN;
|
|
1840
|
-
const width = this.stream.columns ?? process.stdout.columns ?? (Number.isFinite(envColumns) ? envColumns : 80);
|
|
1841
|
-
const iconPath = findIconPath("big");
|
|
1842
|
-
const shouldUseFull = opts?.full === true || width >= 120;
|
|
1843
|
-
if (shouldUseFull && iconPath !== null) {
|
|
1844
|
-
try {
|
|
1845
|
-
const raw = fs.readFileSync(iconPath, "utf-8");
|
|
1846
|
-
const lines = raw.split("\n");
|
|
1847
|
-
for (const line of lines) {
|
|
1848
|
-
const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
|
|
1849
|
-
const stripped = brandedLine.trimEnd();
|
|
1850
|
-
if (stripped.length === 0) {
|
|
1851
|
-
this.write("\n");
|
|
1852
|
-
continue;
|
|
1853
|
-
}
|
|
1854
|
-
const clipped = stripped.length >= width ? stripped.slice(0, Math.max(width - 1, 1)) : stripped;
|
|
1855
|
-
this.write(this.colorBrandLine(clipped) + "\n");
|
|
1856
|
-
}
|
|
1857
|
-
} catch {
|
|
1858
|
-
this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
|
|
1859
|
-
this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
|
|
1860
|
-
}
|
|
1861
|
-
} else {
|
|
1862
|
-
this.write(this.cyan(COMPACT_BANNER[0] ?? "\u25C7 PRISMER") + "\n");
|
|
1863
|
-
this.write(this.dim(COMPACT_BANNER[1] ?? " Runtime CLI") + "\n");
|
|
1864
|
-
}
|
|
1865
|
-
if (subtitle !== void 0 && subtitle.length > 0) {
|
|
1866
|
-
this.write(this.dim(" " + subtitle) + "\n");
|
|
1867
|
-
}
|
|
1868
|
-
this.blank();
|
|
1869
|
-
}
|
|
1870
|
-
// ---- Level 2: Primary data ----
|
|
1871
|
-
blank() {
|
|
1872
|
-
if (this.mode === "json") return;
|
|
1873
|
-
this.write("\n");
|
|
1874
|
-
}
|
|
1875
|
-
line(text) {
|
|
1876
|
-
if (this.mode === "json") return;
|
|
1877
|
-
this.write(text + "\n");
|
|
1878
|
-
}
|
|
1879
|
-
info(text) {
|
|
1880
|
-
this.line(text);
|
|
1881
|
-
}
|
|
1882
|
-
// ---- Level 3: Secondary ----
|
|
1883
|
-
secondary(text, indent = 2) {
|
|
1884
|
-
if (this.mode === "json") return;
|
|
1885
|
-
this.write(" ".repeat(indent) + this.dim(text) + "\n");
|
|
1886
|
-
}
|
|
1887
|
-
// ---- Level 4: Action tips ----
|
|
1888
|
-
tip(text) {
|
|
1889
|
-
if (this.mode === "json") return;
|
|
1890
|
-
this.write(this.cyan("Tip:") + " " + text + "\n");
|
|
1891
|
-
}
|
|
1892
|
-
next(text) {
|
|
1893
|
-
if (this.mode === "json") return;
|
|
1894
|
-
this.write(this.cyan("Next:") + " " + text + "\n");
|
|
1895
|
-
}
|
|
1896
|
-
// ---- Level 5: Status indicators ----
|
|
1897
|
-
ok(text, detail) {
|
|
1898
|
-
if (this.mode === "json") return;
|
|
1899
|
-
const suffix = detail ? " " + this.dim(detail) : "";
|
|
1900
|
-
this.write(" " + this.green("\u2713") + " " + text + suffix + "\n");
|
|
1901
|
-
}
|
|
1902
|
-
success(text, detail) {
|
|
1903
|
-
this.ok(text, detail);
|
|
1904
|
-
}
|
|
1905
|
-
fail(text, detail) {
|
|
1906
|
-
if (this.mode === "json") return;
|
|
1907
|
-
const suffix = detail ? " " + this.dim(detail) : "";
|
|
1908
|
-
this.write(" " + this.red("\u2717") + " " + text + suffix + "\n");
|
|
1909
|
-
}
|
|
1910
|
-
online(text) {
|
|
1911
|
-
if (this.mode === "json") return;
|
|
1912
|
-
this.write(" " + this.green("\u25CF") + " " + text + "\n");
|
|
1913
|
-
}
|
|
1914
|
-
offline(text) {
|
|
1915
|
-
if (this.mode === "json") return;
|
|
1916
|
-
this.write(" " + this.gray("\u25CB") + " " + text + "\n");
|
|
1917
|
-
}
|
|
1918
|
-
notInstalled(text) {
|
|
1919
|
-
if (this.mode === "json") return;
|
|
1920
|
-
this.write(" " + this.dim("\xB7") + " " + this.dim(text) + "\n");
|
|
1921
|
-
}
|
|
1922
|
-
pending(text) {
|
|
1923
|
-
if (this.mode === "json") return;
|
|
1924
|
-
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
1925
|
-
}
|
|
1926
|
-
warn(text, detail) {
|
|
1927
|
-
if (this.mode === "json") return;
|
|
1928
|
-
const suffix = detail ? " " + this.dim(detail) : "";
|
|
1929
|
-
this.write(" " + this.yellow("!") + " " + text + suffix + "\n");
|
|
1930
|
-
}
|
|
1931
|
-
// ---- Level 6: Error block ----
|
|
1932
|
-
error(what, cause, fix) {
|
|
1933
|
-
if (this.mode === "json") return;
|
|
1934
|
-
this.writeErr(this.red("\u2717") + " " + what + "\n");
|
|
1935
|
-
if (cause !== void 0) {
|
|
1936
|
-
this.writeErr(" " + this.dim("Cause:") + " " + this.dim(cause) + "\n");
|
|
1937
|
-
}
|
|
1938
|
-
if (fix !== void 0) {
|
|
1939
|
-
this.writeErr(" " + this.cyan("Fix:") + " " + fix + "\n");
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
table(rowsOrOpts, maybeOpts) {
|
|
1943
|
-
if (this.mode === "json") return;
|
|
1944
|
-
const rows = Array.isArray(rowsOrOpts) ? rowsOrOpts : rowsOrOpts.rows;
|
|
1945
|
-
const opts = Array.isArray(rowsOrOpts) ? maybeOpts : { columns: rowsOrOpts.columns, maxWidth: rowsOrOpts.maxWidth };
|
|
1946
|
-
if (!opts) throw new Error("table() requires columns");
|
|
1947
|
-
const maxWidth = opts.maxWidth ?? (process.stdout.columns || 80);
|
|
1948
|
-
const cols = opts.columns;
|
|
1949
|
-
const widths = cols.map((col) => col.length);
|
|
1950
|
-
for (const row of rows) {
|
|
1951
|
-
cols.forEach((col, i) => {
|
|
1952
|
-
const val = row[col] ?? "";
|
|
1953
|
-
const w = widths[i] ?? 0;
|
|
1954
|
-
if (val.length > w) widths[i] = val.length;
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
const totalWidth = widths.reduce((a, b) => a + b, 0) + (cols.length - 1) * 2 + 2;
|
|
1958
|
-
if (totalWidth > maxWidth) {
|
|
1959
|
-
for (let i = 0; i < rows.length; i++) {
|
|
1960
|
-
const row = rows[i];
|
|
1961
|
-
if (!row) continue;
|
|
1962
|
-
for (const col of cols) {
|
|
1963
|
-
const val = row[col] ?? "";
|
|
1964
|
-
this.write(" " + this.bold(col + ":") + " " + val + "\n");
|
|
1965
|
-
}
|
|
1966
|
-
if (i < rows.length - 1) this.write("\n");
|
|
2191
|
+
`
|
|
2192
|
+
);
|
|
2193
|
+
output = output.replace(ERR_PREFIX_RE, "");
|
|
1967
2194
|
}
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
const header = cols.map((col, i) => col.toUpperCase().padEnd(widths[i] ?? col.length)).join(" ");
|
|
1971
|
-
this.write(" " + this.dim(header) + "\n");
|
|
1972
|
-
for (const row of rows) {
|
|
1973
|
-
const line = cols.map((col, i) => (row[col] ?? "").padEnd(widths[i] ?? col.length)).join(" ");
|
|
1974
|
-
this.write(" " + line + "\n");
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
// ---- Spinner ----
|
|
1978
|
-
spinner(text) {
|
|
1979
|
-
if (this.mode === "quiet" || this.mode === "json") {
|
|
1980
2195
|
return {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
}
|
|
1985
|
-
};
|
|
1986
|
-
}
|
|
1987
|
-
const isTTY = this.stream.isTTY === true;
|
|
1988
|
-
if (!isTTY || !this.colorEnabled) {
|
|
1989
|
-
this.write(" " + this.yellow("\u27F3") + " " + text + "\n");
|
|
1990
|
-
return {
|
|
1991
|
-
update: (t) => {
|
|
1992
|
-
this.write(" " + this.yellow("\u27F3") + " " + t + "\n");
|
|
1993
|
-
},
|
|
1994
|
-
stop: (final) => {
|
|
1995
|
-
if (final) this.write(" " + this.green("\u2713") + " " + final + "\n");
|
|
1996
|
-
}
|
|
2196
|
+
ok: true,
|
|
2197
|
+
output,
|
|
2198
|
+
metrics: { durationMs: Date.now() - startedAt }
|
|
1997
2199
|
};
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
let stopped = false;
|
|
2002
|
-
const write = this.write.bind(this);
|
|
2003
|
-
const colorFn = this.yellow.bind(this);
|
|
2004
|
-
const greenFn = this.green.bind(this);
|
|
2005
|
-
function renderFrame() {
|
|
2006
|
-
const frame = BRAILLE_FRAMES[frameIdx % BRAILLE_FRAMES.length] ?? "\u280B";
|
|
2007
|
-
const line = " " + colorFn(frame) + " " + current;
|
|
2008
|
-
write("\r" + line);
|
|
2009
|
-
frameIdx++;
|
|
2010
|
-
}
|
|
2011
|
-
renderFrame();
|
|
2012
|
-
const timer = setInterval(renderFrame, 80);
|
|
2013
|
-
return {
|
|
2014
|
-
update(t) {
|
|
2015
|
-
if (stopped) return;
|
|
2016
|
-
current = t;
|
|
2017
|
-
},
|
|
2018
|
-
stop(final) {
|
|
2019
|
-
if (stopped) return;
|
|
2020
|
-
stopped = true;
|
|
2021
|
-
clearInterval(timer);
|
|
2022
|
-
write("\r\x1B[2K");
|
|
2023
|
-
if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
if (task.signal?.aborted || err?.name === "AbortError") {
|
|
2202
|
+
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
2024
2203
|
}
|
|
2025
|
-
};
|
|
2026
|
-
}
|
|
2027
|
-
// ---- Progress bar ----
|
|
2028
|
-
progress(text, total) {
|
|
2029
|
-
if (this.mode === "quiet" || this.mode === "json") {
|
|
2030
2204
|
return {
|
|
2031
|
-
|
|
2032
|
-
}
|
|
2033
|
-
stop() {
|
|
2034
|
-
}
|
|
2205
|
+
ok: false,
|
|
2206
|
+
error: { code: "adapter_dispatch_failed", message: err.message }
|
|
2035
2207
|
};
|
|
2036
2208
|
}
|
|
2037
|
-
const isTTY = this.stream.isTTY === true;
|
|
2038
|
-
const start = Date.now();
|
|
2039
|
-
const write = this.write.bind(this);
|
|
2040
|
-
const colorFn = this.cyan.bind(this);
|
|
2041
|
-
const dimFn = this.dim.bind(this);
|
|
2042
|
-
const greenFn = this.green.bind(this);
|
|
2043
|
-
let last = 0;
|
|
2044
|
-
let lastDetail = "";
|
|
2045
|
-
let stopped = false;
|
|
2046
|
-
const render = () => {
|
|
2047
|
-
if (stopped) return;
|
|
2048
|
-
const frac = total > 0 ? Math.min(1, Math.max(0, last / total)) : 0;
|
|
2049
|
-
const pct = Math.floor(frac * 100);
|
|
2050
|
-
const width = 20;
|
|
2051
|
-
const filled = Math.floor(frac * width);
|
|
2052
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
2053
|
-
const elapsed = (Date.now() - start) / 1e3;
|
|
2054
|
-
const eta = frac > 0.01 ? Math.max(0, elapsed / frac - elapsed) : 0;
|
|
2055
|
-
const etaStr = frac >= 1 ? "" : ` \xB7 ${eta < 1 ? "<1s" : Math.round(eta) + "s"} left`;
|
|
2056
|
-
const detailStr = lastDetail ? ` \xB7 ${lastDetail}` : "";
|
|
2057
|
-
const line = ` ${text} [${colorFn(bar)}] ${String(pct).padStart(3)}%${detailStr}${dimFn(etaStr)}`;
|
|
2058
|
-
if (isTTY && this.colorEnabled) {
|
|
2059
|
-
write("\r\x1B[2K" + line);
|
|
2060
|
-
} else {
|
|
2061
|
-
write(line + "\n");
|
|
2062
|
-
}
|
|
2063
|
-
};
|
|
2064
|
-
render();
|
|
2065
|
-
return {
|
|
2066
|
-
update: (current, detail) => {
|
|
2067
|
-
if (stopped) return;
|
|
2068
|
-
last = current;
|
|
2069
|
-
if (detail !== void 0) lastDetail = detail;
|
|
2070
|
-
render();
|
|
2071
|
-
},
|
|
2072
|
-
stop: (final) => {
|
|
2073
|
-
if (stopped) return;
|
|
2074
|
-
stopped = true;
|
|
2075
|
-
if (isTTY && this.colorEnabled) write("\r\x1B[2K");
|
|
2076
|
-
if (final) write(" " + greenFn("\u2713") + " " + final + "\n");
|
|
2077
|
-
}
|
|
2078
|
-
};
|
|
2079
|
-
}
|
|
2080
|
-
// ---- JSON output ----
|
|
2081
|
-
json(payload, opts) {
|
|
2082
|
-
const indent = opts?.pretty ? 2 : void 0;
|
|
2083
|
-
this.write(JSON.stringify(payload, null, indent) + "\n");
|
|
2084
|
-
}
|
|
2085
|
-
result(pretty, jsonPayload) {
|
|
2086
|
-
if (this.mode === "pretty") {
|
|
2087
|
-
pretty();
|
|
2088
|
-
} else {
|
|
2089
|
-
this.json(jsonPayload);
|
|
2090
|
-
}
|
|
2091
2209
|
}
|
|
2092
2210
|
};
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
let mode = "pretty";
|
|
2103
|
-
const isTTY = process.stdout.isTTY === true;
|
|
2104
|
-
const noColorEnv = Boolean(process.env["NO_COLOR"]);
|
|
2105
|
-
let color2 = isTTY && !noColorEnv;
|
|
2106
|
-
const rest = [];
|
|
2107
|
-
for (const arg of argv) {
|
|
2108
|
-
switch (arg) {
|
|
2109
|
-
case "--no-color":
|
|
2110
|
-
color2 = false;
|
|
2111
|
-
break;
|
|
2112
|
-
case "--color":
|
|
2113
|
-
color2 = true;
|
|
2114
|
-
break;
|
|
2115
|
-
case "--json":
|
|
2116
|
-
case "--pretty-json":
|
|
2117
|
-
mode = "json";
|
|
2118
|
-
if (arg === "--json") rest.push(arg);
|
|
2119
|
-
break;
|
|
2120
|
-
case "--quiet":
|
|
2121
|
-
mode = "quiet";
|
|
2122
|
-
break;
|
|
2123
|
-
default:
|
|
2124
|
-
rest.push(arg);
|
|
2125
|
-
}
|
|
2211
|
+
async function checkHealth2(baseUrl, apiKey) {
|
|
2212
|
+
try {
|
|
2213
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
2214
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2215
|
+
signal: AbortSignal.timeout(2e3)
|
|
2216
|
+
});
|
|
2217
|
+
return res.ok;
|
|
2218
|
+
} catch {
|
|
2219
|
+
return false;
|
|
2126
2220
|
}
|
|
2127
|
-
return { mode, color: color2, restArgv: rest };
|
|
2128
2221
|
}
|
|
2129
2222
|
|
|
2130
|
-
// src/
|
|
2131
|
-
var
|
|
2132
|
-
var
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2223
|
+
// src/config.ts
|
|
2224
|
+
var TOML = __toESM(require("@iarna/toml"), 1);
|
|
2225
|
+
var import_node_fs2 = require("fs");
|
|
2226
|
+
var import_node_os2 = require("os");
|
|
2227
|
+
var import_node_path2 = require("path");
|
|
2228
|
+
var import_zod5 = require("zod");
|
|
2229
|
+
var ConfigSchema = import_zod5.z.object({
|
|
2230
|
+
/** API key from `prismer setup`, or env override. */
|
|
2231
|
+
api_key: import_zod5.z.string().min(1),
|
|
2232
|
+
/** Cloud REST + WS base; `ws://` is derived by stripping `http`. */
|
|
2233
|
+
cloud_api_base: import_zod5.z.string().url(),
|
|
2234
|
+
/** Stable per-machine daemon identifier. Generated once on first setup. */
|
|
2235
|
+
daemon_id: import_zod5.z.string().min(1),
|
|
2236
|
+
/** Optional adapter-specific overrides keyed by adapter name. */
|
|
2237
|
+
adapters: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown())).optional(),
|
|
2238
|
+
/** Local daemon shell execution. Default disabled. */
|
|
2239
|
+
shell: import_zod5.z.object({
|
|
2240
|
+
enabled: import_zod5.z.boolean().default(false),
|
|
2241
|
+
default_cwd: import_zod5.z.string().optional(),
|
|
2242
|
+
defaultCwd: import_zod5.z.string().optional(),
|
|
2243
|
+
shell: import_zod5.z.enum(["bash", "zsh", "sh"]).optional(),
|
|
2244
|
+
max_timeout_ms: import_zod5.z.number().int().positive().optional(),
|
|
2245
|
+
maxTimeoutMs: import_zod5.z.number().int().positive().optional(),
|
|
2246
|
+
max_output_bytes: import_zod5.z.number().int().positive().optional(),
|
|
2247
|
+
maxOutputBytes: import_zod5.z.number().int().positive().optional(),
|
|
2248
|
+
allowed_workspaces: import_zod5.z.array(import_zod5.z.string()).optional(),
|
|
2249
|
+
allowedWorkspaces: import_zod5.z.array(import_zod5.z.string()).optional()
|
|
2250
|
+
}).optional(),
|
|
2251
|
+
/** Local cache settings. */
|
|
2252
|
+
cache: import_zod5.z.object({
|
|
2253
|
+
max_bytes: import_zod5.z.number().int().positive().default(5 * 1024 * 1024 * 1024)
|
|
2254
|
+
}).optional()
|
|
2255
|
+
});
|
|
2256
|
+
function resolvePaths(home) {
|
|
2257
|
+
const root = home ?? process.env.PRISMER_HOME ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".prismer");
|
|
2258
|
+
return {
|
|
2259
|
+
root,
|
|
2260
|
+
configFile: (0, import_node_path2.join)(root, "config.toml"),
|
|
2261
|
+
localDb: (0, import_node_path2.join)(root, "local.db"),
|
|
2262
|
+
cacheDir: (0, import_node_path2.join)(root, "cache"),
|
|
2263
|
+
logsDir: (0, import_node_path2.join)(root, "logs"),
|
|
2264
|
+
runsDir: (0, import_node_path2.join)(root, "runs")
|
|
2265
|
+
};
|
|
2147
2266
|
}
|
|
2148
|
-
function
|
|
2149
|
-
|
|
2150
|
-
const exitCode = o.exitCode ?? 1;
|
|
2151
|
-
const ui = getUI();
|
|
2152
|
-
if (ui.mode === "json") {
|
|
2153
|
-
const payload = {
|
|
2154
|
-
ok: false,
|
|
2155
|
-
error: { code: o.code ?? "cli_error", message },
|
|
2156
|
-
...o.details ? { details: o.details } : {}
|
|
2157
|
-
};
|
|
2158
|
-
ui.json(payload, { pretty: true });
|
|
2159
|
-
} else {
|
|
2160
|
-
process.stderr.write(`Error: ${message}
|
|
2161
|
-
`);
|
|
2162
|
-
}
|
|
2163
|
-
process.exit(exitCode);
|
|
2267
|
+
function configExists(paths = resolvePaths()) {
|
|
2268
|
+
return (0, import_node_fs2.existsSync)(paths.configFile);
|
|
2164
2269
|
}
|
|
2165
|
-
function
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
try {
|
|
2171
|
-
parsed = new URL(raw);
|
|
2172
|
-
} catch {
|
|
2173
|
-
throw new Error(`Invalid --cloud URL: ${raw}`);
|
|
2174
|
-
}
|
|
2175
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2176
|
-
throw new Error(`Invalid --cloud URL scheme: ${parsed.protocol} (expected http:// or https://)`);
|
|
2177
|
-
}
|
|
2178
|
-
return raw.replace(/\/$/, "");
|
|
2179
|
-
}
|
|
2180
|
-
if (!/^[A-Za-z0-9.\-_:[\]/]+$/.test(raw)) {
|
|
2181
|
-
throw new Error(`Invalid --cloud URL: ${raw} (must be http://\u2026 or https://\u2026 or host:port)`);
|
|
2182
|
-
}
|
|
2183
|
-
const candidate = `http://${raw}`;
|
|
2184
|
-
try {
|
|
2185
|
-
new URL(candidate);
|
|
2186
|
-
} catch {
|
|
2187
|
-
throw new Error(`Invalid --cloud URL: ${raw}`);
|
|
2270
|
+
function loadConfig(paths = resolvePaths()) {
|
|
2271
|
+
if (!(0, import_node_fs2.existsSync)(paths.configFile)) {
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
`Config not found at ${paths.configFile}. Run \`prismer setup\` to create it.`
|
|
2274
|
+
);
|
|
2188
2275
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
} catch (err) {
|
|
2196
|
-
const raw = err instanceof Error ? err.message : String(err);
|
|
2197
|
-
const message = opts.sanitize ? opts.sanitize(raw) : raw;
|
|
2198
|
-
exitWithError(message, { code: opts.code });
|
|
2199
|
-
}
|
|
2276
|
+
const raw = (0, import_node_fs2.readFileSync)(paths.configFile, "utf8");
|
|
2277
|
+
const parsed = TOML.parse(raw);
|
|
2278
|
+
const merged = {
|
|
2279
|
+
...parsed,
|
|
2280
|
+
api_key: process.env.PRISMER_API_KEY ?? parsed.api_key,
|
|
2281
|
+
cloud_api_base: process.env.PRISMER_BASE_URL ?? parsed.cloud_api_base
|
|
2200
2282
|
};
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
ui.smallHeader("Runtime CLI v1.9.7");
|
|
2206
|
-
return;
|
|
2283
|
+
const result = ConfigSchema.safeParse(merged);
|
|
2284
|
+
if (!result.success) {
|
|
2285
|
+
const detail = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2286
|
+
throw new Error(`Invalid config at ${paths.configFile}: ${detail}`);
|
|
2207
2287
|
}
|
|
2208
|
-
|
|
2209
|
-
}
|
|
2210
|
-
function ok(label, detail) {
|
|
2211
|
-
getUI().ok(label, detail);
|
|
2212
|
-
}
|
|
2213
|
-
function warn(label, detail) {
|
|
2214
|
-
getUI().warn(label, detail);
|
|
2215
|
-
}
|
|
2216
|
-
function fail(label, detail) {
|
|
2217
|
-
getUI().fail(label, detail);
|
|
2218
|
-
}
|
|
2219
|
-
function tip(command, detail) {
|
|
2220
|
-
const text = detail ? `${command} ${detail}` : command;
|
|
2221
|
-
getUI().tip(text);
|
|
2222
|
-
}
|
|
2223
|
-
function pidFilePath(paths) {
|
|
2224
|
-
return (0, import_node_path3.join)(paths.root, "daemon.pid");
|
|
2225
|
-
}
|
|
2226
|
-
function writePidFile(paths, pid) {
|
|
2227
|
-
(0, import_node_fs3.writeFileSync)(pidFilePath(paths), `${pid}
|
|
2228
|
-
`, "utf8");
|
|
2229
|
-
}
|
|
2230
|
-
function readPidFile(paths) {
|
|
2231
|
-
const p = pidFilePath(paths);
|
|
2232
|
-
if (!(0, import_node_fs3.existsSync)(p)) return void 0;
|
|
2233
|
-
const raw = (0, import_node_fs3.readFileSync)(p, "utf8").trim();
|
|
2234
|
-
const pid = Number.parseInt(raw, 10);
|
|
2235
|
-
return Number.isFinite(pid) ? pid : void 0;
|
|
2288
|
+
return result.data;
|
|
2236
2289
|
}
|
|
2237
|
-
function
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
try {
|
|
2241
|
-
(0, import_node_fs3.unlinkSync)(p);
|
|
2242
|
-
} catch {
|
|
2243
|
-
}
|
|
2290
|
+
function saveConfig(config, paths = resolvePaths()) {
|
|
2291
|
+
if (!(0, import_node_fs2.existsSync)(paths.root)) {
|
|
2292
|
+
(0, import_node_fs2.mkdirSync)(paths.root, { recursive: true });
|
|
2244
2293
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
try {
|
|
2248
|
-
process.kill(pid, 0);
|
|
2249
|
-
return true;
|
|
2250
|
-
} catch {
|
|
2251
|
-
return false;
|
|
2294
|
+
if (!(0, import_node_fs2.existsSync)((0, import_node_path2.dirname)(paths.configFile))) {
|
|
2295
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(paths.configFile), { recursive: true });
|
|
2252
2296
|
}
|
|
2297
|
+
ConfigSchema.parse(config);
|
|
2298
|
+
(0, import_node_fs2.writeFileSync)(paths.configFile, TOML.stringify(config), "utf8");
|
|
2299
|
+
}
|
|
2300
|
+
function deriveWsUrl(httpBase) {
|
|
2301
|
+
const u = new URL(httpBase);
|
|
2302
|
+
u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
|
|
2303
|
+
u.pathname = (u.pathname.replace(/\/$/, "") || "") + "/ws";
|
|
2304
|
+
return u.toString();
|
|
2253
2305
|
}
|
|
2254
2306
|
|
|
2255
2307
|
// src/cli/commands/adapter.ts
|
|
2308
|
+
init_util();
|
|
2309
|
+
init_ui();
|
|
2256
2310
|
var BUILTIN_ADAPTERS = [hermesAdapter, claudeCodeAdapter, openclawAdapter, codexAdapter];
|
|
2257
2311
|
var INSTALL_SPECS = {
|
|
2258
2312
|
"claude-code": {
|
|
@@ -3003,6 +3057,32 @@ var MIGRATIONS = [
|
|
|
3003
3057
|
);
|
|
3004
3058
|
CREATE INDEX IF NOT EXISTS idx_files_hash ON workspace_files_mirror (content_hash);
|
|
3005
3059
|
`
|
|
3060
|
+
},
|
|
3061
|
+
{
|
|
3062
|
+
version: 3,
|
|
3063
|
+
up: `
|
|
3064
|
+
-- Asset metadata index (#filename reference resolution \u2014 daemon/asset/metadata-index.ts)
|
|
3065
|
+
CREATE TABLE IF NOT EXISTS asset_metadata_index (
|
|
3066
|
+
workspace_id TEXT NOT NULL,
|
|
3067
|
+
asset_id TEXT NOT NULL,
|
|
3068
|
+
content_hash TEXT NOT NULL,
|
|
3069
|
+
filename TEXT,
|
|
3070
|
+
folder_path TEXT,
|
|
3071
|
+
mime TEXT NOT NULL,
|
|
3072
|
+
kind TEXT NOT NULL,
|
|
3073
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
3074
|
+
description TEXT,
|
|
3075
|
+
asset_index_seq INTEGER NOT NULL,
|
|
3076
|
+
updated_at INTEGER NOT NULL,
|
|
3077
|
+
PRIMARY KEY (workspace_id, asset_id)
|
|
3078
|
+
);
|
|
3079
|
+
|
|
3080
|
+
CREATE INDEX IF NOT EXISTS idx_asset_meta_filename
|
|
3081
|
+
ON asset_metadata_index(workspace_id, filename);
|
|
3082
|
+
|
|
3083
|
+
CREATE INDEX IF NOT EXISTS idx_asset_meta_seq
|
|
3084
|
+
ON asset_metadata_index(workspace_id, asset_index_seq);
|
|
3085
|
+
`
|
|
3006
3086
|
}
|
|
3007
3087
|
];
|
|
3008
3088
|
function runSql(db, sql) {
|
|
@@ -3035,6 +3115,8 @@ function runMigrations(db) {
|
|
|
3035
3115
|
}
|
|
3036
3116
|
|
|
3037
3117
|
// src/cli/commands/agent.ts
|
|
3118
|
+
init_util();
|
|
3119
|
+
init_ui();
|
|
3038
3120
|
var ADAPTER_BINARY = {
|
|
3039
3121
|
"claude-code": "claude",
|
|
3040
3122
|
codex: "codex",
|
|
@@ -3416,6 +3498,8 @@ function whichBinary2(bin) {
|
|
|
3416
3498
|
var import_commander3 = require("commander");
|
|
3417
3499
|
var import_node_fs6 = require("fs");
|
|
3418
3500
|
var import_node_path6 = require("path");
|
|
3501
|
+
init_util();
|
|
3502
|
+
init_ui();
|
|
3419
3503
|
function buildAssetCommand() {
|
|
3420
3504
|
const cmd = new import_commander3.Command("asset").description("Inspect IM assets");
|
|
3421
3505
|
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) => {
|
|
@@ -3612,6 +3696,7 @@ function stringField(obj, key) {
|
|
|
3612
3696
|
|
|
3613
3697
|
// src/cli/commands/banner.ts
|
|
3614
3698
|
var import_commander4 = require("commander");
|
|
3699
|
+
init_util();
|
|
3615
3700
|
function buildBannerCommand() {
|
|
3616
3701
|
return new import_commander4.Command("banner").description("Show the Prismer runtime CLI banner").option("--compact", "Show a single-line banner").option("--json", "Accept --json for global flag compatibility (banner is suppressed in JSON mode)").action((opts) => {
|
|
3617
3702
|
printBanner({ compact: opts.compact });
|
|
@@ -3620,6 +3705,8 @@ function buildBannerCommand() {
|
|
|
3620
3705
|
|
|
3621
3706
|
// src/cli/commands/chat.ts
|
|
3622
3707
|
var import_commander5 = require("commander");
|
|
3708
|
+
init_util();
|
|
3709
|
+
init_ui();
|
|
3623
3710
|
function buildChatCommand() {
|
|
3624
3711
|
const cmd = new import_commander5.Command("chat").description("Use IM chat and group APIs");
|
|
3625
3712
|
cmd.command("me").description("Show the current IM identity").option("--json", "Print raw JSON response").action(async (opts) => {
|
|
@@ -3794,6 +3881,8 @@ function sanitizeError(message) {
|
|
|
3794
3881
|
|
|
3795
3882
|
// src/cli/commands/config.ts
|
|
3796
3883
|
var import_commander6 = require("commander");
|
|
3884
|
+
init_util();
|
|
3885
|
+
init_ui();
|
|
3797
3886
|
var SETTABLE_KEYS = ["cloud_api_base", "api_key", "daemon_id"];
|
|
3798
3887
|
function redactApiKey(key) {
|
|
3799
3888
|
if (!key.startsWith("sk-prismer-")) return "***";
|
|
@@ -3889,6 +3978,8 @@ function buildConfigCommand() {
|
|
|
3889
3978
|
|
|
3890
3979
|
// src/cli/commands/cookbook.ts
|
|
3891
3980
|
var import_commander7 = require("commander");
|
|
3981
|
+
init_util();
|
|
3982
|
+
init_ui();
|
|
3892
3983
|
function buildCookbookCommand() {
|
|
3893
3984
|
const cmd = new import_commander7.Command("cookbook").description("Run CLI-only 54release MVP regression suites");
|
|
3894
3985
|
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) => {
|
|
@@ -4146,13 +4237,13 @@ function parsePositiveInt2(value) {
|
|
|
4146
4237
|
// src/cli/commands/daemon.ts
|
|
4147
4238
|
var import_commander8 = require("commander");
|
|
4148
4239
|
var import_node_child_process7 = require("child_process");
|
|
4149
|
-
var
|
|
4240
|
+
var import_node_fs14 = require("fs");
|
|
4150
4241
|
var import_promises = require("timers/promises");
|
|
4151
|
-
var
|
|
4242
|
+
var import_node_path10 = require("path");
|
|
4152
4243
|
|
|
4153
4244
|
// src/daemon/runner.ts
|
|
4154
4245
|
var import_node_events3 = require("events");
|
|
4155
|
-
var
|
|
4246
|
+
var import_node_fs13 = require("fs");
|
|
4156
4247
|
var import_node_os4 = require("os");
|
|
4157
4248
|
|
|
4158
4249
|
// src/adapters/registry.ts
|
|
@@ -4687,6 +4778,14 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
4687
4778
|
sendReply(deps.ws, reply, requestId);
|
|
4688
4779
|
return reply;
|
|
4689
4780
|
}
|
|
4781
|
+
let hashRefResult = { text: payload.prompt, resolutions: [] };
|
|
4782
|
+
if (deps.assetMetadataIndexes && profile.workspaceId) {
|
|
4783
|
+
const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
|
|
4784
|
+
if (assetIndex) {
|
|
4785
|
+
hashRefResult = await resolveHashRefs(payload.prompt, assetIndex, deps.cloud);
|
|
4786
|
+
payload.prompt = hashRefResult.text;
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4690
4789
|
const rewrittenPrompt = await deps.uriResolver.rewrite(payload.prompt, { pin: true });
|
|
4691
4790
|
resolvedHashes.push(...rewrittenPrompt.resolvedHashes);
|
|
4692
4791
|
let rewrittenContext = [];
|
|
@@ -4901,6 +5000,84 @@ function isTextLikeMime(mime) {
|
|
|
4901
5000
|
if (m.endsWith("+json") || m.endsWith("+xml") || m.endsWith("+csv")) return true;
|
|
4902
5001
|
return false;
|
|
4903
5002
|
}
|
|
5003
|
+
var HASH_REF_RE = /(?:^|\s)#([^\s#]+)/g;
|
|
5004
|
+
var HEX_COLOR_RE = /^[0-9a-fA-F]{3,8}$/;
|
|
5005
|
+
var FILE_EXT_RE = /\.[a-zA-Z0-9]{1,10}$/;
|
|
5006
|
+
var TRAILING_PUNCT_RE = /[,.;:!?)\]}'"]+$/;
|
|
5007
|
+
async function resolveHashRefs(prompt, assetIndex, cloud) {
|
|
5008
|
+
const resolutions = [];
|
|
5009
|
+
const candidates = [];
|
|
5010
|
+
let match;
|
|
5011
|
+
const re = new RegExp(HASH_REF_RE.source, "g");
|
|
5012
|
+
while ((match = re.exec(prompt)) !== null) {
|
|
5013
|
+
const refName = match[1];
|
|
5014
|
+
const leading = match[0].startsWith("#") ? 0 : 1;
|
|
5015
|
+
const start = match.index + leading;
|
|
5016
|
+
const end = match.index + match[0].length;
|
|
5017
|
+
if (HEX_COLOR_RE.test(refName)) continue;
|
|
5018
|
+
let cleanRef = refName;
|
|
5019
|
+
let stripped = "";
|
|
5020
|
+
const punctMatch = TRAILING_PUNCT_RE.exec(cleanRef);
|
|
5021
|
+
if (punctMatch) {
|
|
5022
|
+
stripped = punctMatch[0];
|
|
5023
|
+
cleanRef = cleanRef.slice(0, -stripped.length);
|
|
5024
|
+
}
|
|
5025
|
+
if (!cleanRef) continue;
|
|
5026
|
+
if (HEX_COLOR_RE.test(cleanRef)) continue;
|
|
5027
|
+
const hasExtension = FILE_EXT_RE.test(cleanRef);
|
|
5028
|
+
candidates.push({ ref: cleanRef, start, end: end - stripped.length, hasExtension });
|
|
5029
|
+
}
|
|
5030
|
+
if (candidates.length === 0) {
|
|
5031
|
+
return { text: prompt, resolutions: [] };
|
|
5032
|
+
}
|
|
5033
|
+
const allFilenames = candidates.map((c) => c.ref);
|
|
5034
|
+
const localResults = assetIndex.resolveByFilenames(allFilenames);
|
|
5035
|
+
const needsCloud = candidates.filter(
|
|
5036
|
+
(c) => c.hasExtension && !localResults.has(c.ref)
|
|
5037
|
+
);
|
|
5038
|
+
const cloudResults = /* @__PURE__ */ new Map();
|
|
5039
|
+
if (needsCloud.length > 0) {
|
|
5040
|
+
await Promise.allSettled(
|
|
5041
|
+
needsCloud.map(async (c) => {
|
|
5042
|
+
try {
|
|
5043
|
+
const items = await cloud.get(
|
|
5044
|
+
`/api/im/assets?workspaceId=${encodeURIComponent(assetIndex.workspaceId)}&q=${encodeURIComponent(c.ref)}&limit=1`
|
|
5045
|
+
);
|
|
5046
|
+
if (Array.isArray(items) && items.length > 0) {
|
|
5047
|
+
const item = items[0];
|
|
5048
|
+
cloudResults.set(c.ref, item.contentHash);
|
|
5049
|
+
}
|
|
5050
|
+
} catch {
|
|
5051
|
+
}
|
|
5052
|
+
})
|
|
5053
|
+
);
|
|
5054
|
+
}
|
|
5055
|
+
for (const c of candidates) {
|
|
5056
|
+
const local = localResults.get(c.ref);
|
|
5057
|
+
if (local) {
|
|
5058
|
+
resolutions.push({
|
|
5059
|
+
ref: c.ref,
|
|
5060
|
+
start: c.start,
|
|
5061
|
+
end: c.end,
|
|
5062
|
+
resolvedUri: `prismer://workspace/${encodeURIComponent(assetIndex.workspaceId)}/asset/${local.contentHash}`
|
|
5063
|
+
});
|
|
5064
|
+
} else if (c.hasExtension) {
|
|
5065
|
+
const cloudHash = cloudResults.get(c.ref);
|
|
5066
|
+
resolutions.push({
|
|
5067
|
+
ref: c.ref,
|
|
5068
|
+
start: c.start,
|
|
5069
|
+
end: c.end,
|
|
5070
|
+
resolvedUri: cloudHash ? `prismer://workspace/${encodeURIComponent(assetIndex.workspaceId)}/asset/${cloudHash}` : void 0
|
|
5071
|
+
});
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
let result = prompt;
|
|
5075
|
+
const sorted = [...resolutions].filter((r) => r.resolvedUri).sort((a, b) => b.start - a.start);
|
|
5076
|
+
for (const r of sorted) {
|
|
5077
|
+
result = result.slice(0, r.start) + r.resolvedUri + result.slice(r.end);
|
|
5078
|
+
}
|
|
5079
|
+
return { text: result, resolutions };
|
|
5080
|
+
}
|
|
4904
5081
|
async function resolveAssetRefs(refs, cache) {
|
|
4905
5082
|
const out = { promptBlocks: [], observability: [], pinnedHashes: [] };
|
|
4906
5083
|
if (!refs || refs.length === 0) return out;
|
|
@@ -4973,8 +5150,8 @@ async function resolveAssetRefs(refs, cache) {
|
|
|
4973
5150
|
return out;
|
|
4974
5151
|
}
|
|
4975
5152
|
function formatInlineAssetBlock(ref, mime, body, strategy) {
|
|
4976
|
-
const
|
|
4977
|
-
return `${
|
|
5153
|
+
const header2 = `[Attached file] id=${ref.assetId} mime=${mime ?? "unknown"}${strategy === "inline-text-truncated" ? " (truncated)" : ""}`;
|
|
5154
|
+
return `${header2}
|
|
4978
5155
|
---
|
|
4979
5156
|
${body}
|
|
4980
5157
|
---`;
|
|
@@ -5277,20 +5454,35 @@ var LocalServer = class {
|
|
|
5277
5454
|
respond(res, 204, null);
|
|
5278
5455
|
return;
|
|
5279
5456
|
}
|
|
5457
|
+
const handlers = [];
|
|
5280
5458
|
if (this.opts.attachMemory) {
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
});
|
|
5289
|
-
});
|
|
5459
|
+
handlers.push({ name: "memory", fn: this.opts.attachMemory });
|
|
5460
|
+
}
|
|
5461
|
+
if (this.opts.attachAsset) {
|
|
5462
|
+
handlers.push({ name: "asset", fn: this.opts.attachAsset });
|
|
5463
|
+
}
|
|
5464
|
+
if (handlers.length > 0) {
|
|
5465
|
+
void this.runHandlers(req, res, handlers, 0);
|
|
5290
5466
|
return;
|
|
5291
5467
|
}
|
|
5292
5468
|
this.routeStandard(req, res);
|
|
5293
5469
|
}
|
|
5470
|
+
async runHandlers(req, res, handlers, idx) {
|
|
5471
|
+
if (idx >= handlers.length) {
|
|
5472
|
+
this.routeStandard(req, res);
|
|
5473
|
+
return;
|
|
5474
|
+
}
|
|
5475
|
+
try {
|
|
5476
|
+
const handled = await handlers[idx].fn(req, res);
|
|
5477
|
+
if (handled) return;
|
|
5478
|
+
await this.runHandlers(req, res, handlers, idx + 1);
|
|
5479
|
+
} catch (err) {
|
|
5480
|
+
respond(res, 500, {
|
|
5481
|
+
error: `attach_${handlers[idx].name}_threw`,
|
|
5482
|
+
message: err instanceof Error ? err.message : String(err)
|
|
5483
|
+
});
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5294
5486
|
routeStandard(req, res) {
|
|
5295
5487
|
const url = req.url ?? "/";
|
|
5296
5488
|
if (req.method === "GET" && url === "/healthz") {
|
|
@@ -5305,7 +5497,8 @@ var LocalServer = class {
|
|
|
5305
5497
|
wsConnected: state.wsConnected,
|
|
5306
5498
|
hostedAgents: state.hostedAgents,
|
|
5307
5499
|
observability: state.observability,
|
|
5308
|
-
memoryReady: this.opts.attachMemory != null
|
|
5500
|
+
memoryReady: this.opts.attachMemory != null,
|
|
5501
|
+
assetReady: this.opts.attachAsset != null
|
|
5309
5502
|
});
|
|
5310
5503
|
return;
|
|
5311
5504
|
}
|
|
@@ -5759,13 +5952,14 @@ var MemoryStore = class {
|
|
|
5759
5952
|
const pageId = existing?.id ?? `page_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "").slice(0, 22)}`;
|
|
5760
5953
|
const newVersion = (existing?.version ?? 0) + 1;
|
|
5761
5954
|
const createdAt = existing?.createdAt ?? now;
|
|
5955
|
+
const staleFlag = input.stale ? 1 : 0;
|
|
5762
5956
|
const insertPage = db.prepare(`
|
|
5763
5957
|
INSERT INTO memory_pages (
|
|
5764
5958
|
id, workspaceId, path, title, description, contentHash, version,
|
|
5765
5959
|
pageType, visibilityKind, visibilityImUserId, encrypted, stale,
|
|
5766
5960
|
archivedAt, sourceAssetId, sourceRefsJson, syncStatus,
|
|
5767
5961
|
createdAt, updatedAt
|
|
5768
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0,
|
|
5962
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, NULL, ?, ?, 'local-only', ?, ?)
|
|
5769
5963
|
ON CONFLICT(workspaceId, path) DO UPDATE SET
|
|
5770
5964
|
title = excluded.title,
|
|
5771
5965
|
description = excluded.description,
|
|
@@ -5776,6 +5970,7 @@ var MemoryStore = class {
|
|
|
5776
5970
|
visibilityImUserId = excluded.visibilityImUserId,
|
|
5777
5971
|
sourceAssetId = excluded.sourceAssetId,
|
|
5778
5972
|
sourceRefsJson = excluded.sourceRefsJson,
|
|
5973
|
+
stale = excluded.stale,
|
|
5779
5974
|
updatedAt = excluded.updatedAt
|
|
5780
5975
|
`);
|
|
5781
5976
|
const insertVersion = db.prepare(`
|
|
@@ -5803,6 +5998,7 @@ var MemoryStore = class {
|
|
|
5803
5998
|
input.pageType ?? "leaf",
|
|
5804
5999
|
visibility.kind,
|
|
5805
6000
|
visibilityImUserId,
|
|
6001
|
+
staleFlag,
|
|
5806
6002
|
input.sourceAssetId ?? null,
|
|
5807
6003
|
sourceRefsJson,
|
|
5808
6004
|
createdAt,
|
|
@@ -5883,6 +6079,16 @@ var MemoryStore = class {
|
|
|
5883
6079
|
dbPath: this.opts.dbPath
|
|
5884
6080
|
};
|
|
5885
6081
|
}
|
|
6082
|
+
/**
|
|
6083
|
+
* Record sync cursor for incremental sync. Used by cloud-sync.ts to
|
|
6084
|
+
* persist the high-water mark for future cursor-based catch-up.
|
|
6085
|
+
*/
|
|
6086
|
+
recordCursor(workspaceId, cursor) {
|
|
6087
|
+
const now = Date.now();
|
|
6088
|
+
this.requireDb().prepare(
|
|
6089
|
+
`INSERT OR REPLACE INTO memory_inbox_cursor (workspaceId, cursor, updatedAt) VALUES (?, ?, ?)`
|
|
6090
|
+
).run(workspaceId, cursor, now);
|
|
6091
|
+
}
|
|
5886
6092
|
/**
|
|
5887
6093
|
* Internal accessor for outbox.ts — outbox writes its own table within the
|
|
5888
6094
|
* same DB. Returning the live Database handle keeps outbox transactions
|
|
@@ -6533,6 +6739,84 @@ function defaultLog() {
|
|
|
6533
6739
|
};
|
|
6534
6740
|
}
|
|
6535
6741
|
|
|
6742
|
+
// src/daemon/memory/cloud-sync.ts
|
|
6743
|
+
var LOG = "[CloudMemorySync]";
|
|
6744
|
+
var CLOUD_PAGE_LIMIT = 300;
|
|
6745
|
+
async function initialSyncFromCloud(runtime, cloud, workspaceId) {
|
|
6746
|
+
const slot = runtime.peek(workspaceId);
|
|
6747
|
+
if (!slot) {
|
|
6748
|
+
console.log(`${LOG} No store for workspace=${workspaceId} \u2014 skipping`);
|
|
6749
|
+
return { pulled: 0, skipped: 0 };
|
|
6750
|
+
}
|
|
6751
|
+
const cursorRow = slot.store.rawDb().prepare("SELECT cursor FROM memory_inbox_cursor WHERE workspaceId = ?").get(workspaceId);
|
|
6752
|
+
if (cursorRow) {
|
|
6753
|
+
console.log(`${LOG} Workspace=${workspaceId} already synced (cursor: ${cursorRow.cursor.slice(0, 20)}...) \u2014 skip`);
|
|
6754
|
+
return { pulled: 0, skipped: 0 };
|
|
6755
|
+
}
|
|
6756
|
+
console.log(`${LOG} Fetching cloud pages for workspace=${workspaceId}...`);
|
|
6757
|
+
const resp = await cloud.request(
|
|
6758
|
+
"GET",
|
|
6759
|
+
`/api/im/memory/pages?workspaceId=${encodeURIComponent(workspaceId)}&limit=${CLOUD_PAGE_LIMIT}&stale=all`,
|
|
6760
|
+
{ timeoutMs: 15e3 }
|
|
6761
|
+
);
|
|
6762
|
+
if (!resp.ok) {
|
|
6763
|
+
console.warn(
|
|
6764
|
+
`${LOG} Cloud GET /memory/pages returned ${resp.status}: ${resp.error?.message ?? "unknown"}`
|
|
6765
|
+
);
|
|
6766
|
+
return { pulled: 0, skipped: 0 };
|
|
6767
|
+
}
|
|
6768
|
+
const envelope2 = resp.data;
|
|
6769
|
+
if (!envelope2 || !envelope2.ok) {
|
|
6770
|
+
console.log(`${LOG} Cloud returned non-ok envelope for workspace=${workspaceId}`);
|
|
6771
|
+
return { pulled: 0, skipped: 0 };
|
|
6772
|
+
}
|
|
6773
|
+
const pages = envelope2.data;
|
|
6774
|
+
if (!pages || !Array.isArray(pages) || pages.length === 0) {
|
|
6775
|
+
console.log(`${LOG} No cloud pages to sync for workspace=${workspaceId}`);
|
|
6776
|
+
return { pulled: 0, skipped: 0 };
|
|
6777
|
+
}
|
|
6778
|
+
let pulled = 0;
|
|
6779
|
+
let skipped = 0;
|
|
6780
|
+
for (const page of pages) {
|
|
6781
|
+
let content = page.content ?? "";
|
|
6782
|
+
if (!content) {
|
|
6783
|
+
try {
|
|
6784
|
+
const detailResp = await cloud.request(
|
|
6785
|
+
"GET",
|
|
6786
|
+
`/api/im/memory/pages/${encodeURIComponent(page.id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
|
|
6787
|
+
{ timeoutMs: 5e3 }
|
|
6788
|
+
);
|
|
6789
|
+
if (detailResp.ok && detailResp.data?.data?.content) {
|
|
6790
|
+
content = detailResp.data.data.content;
|
|
6791
|
+
}
|
|
6792
|
+
} catch {
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
const visibility = page.visibility === "agent" ? { kind: "agent", imUserId: "" } : { kind: "workspace" };
|
|
6796
|
+
try {
|
|
6797
|
+
slot.store.write({
|
|
6798
|
+
workspaceId,
|
|
6799
|
+
path: page.path,
|
|
6800
|
+
title: page.title ?? void 0,
|
|
6801
|
+
content: content || "",
|
|
6802
|
+
pageType: page.pageType || "leaf",
|
|
6803
|
+
visibility,
|
|
6804
|
+
actorImUserId: "cloud-sync",
|
|
6805
|
+
actorKind: "agent"
|
|
6806
|
+
});
|
|
6807
|
+
pulled++;
|
|
6808
|
+
} catch (err) {
|
|
6809
|
+
console.warn(`${LOG} write failed for ${page.path}:`, err.message);
|
|
6810
|
+
skipped++;
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
slot.store.recordCursor(workspaceId, `synced:${Date.now()}`);
|
|
6814
|
+
console.log(
|
|
6815
|
+
`${LOG} Synced ${pulled} pages${skipped ? `, ${skipped} skipped` : ""} for workspace=${workspaceId}`
|
|
6816
|
+
);
|
|
6817
|
+
return { pulled, skipped };
|
|
6818
|
+
}
|
|
6819
|
+
|
|
6536
6820
|
// src/daemon/memory/runner-wiring.ts
|
|
6537
6821
|
function attachMemoryRunner(opts) {
|
|
6538
6822
|
const runtime = new MemoryRuntime({ baseDir: opts.baseDir, deviceId: opts.deviceId });
|
|
@@ -6563,6 +6847,24 @@ function attachMemoryRunner(opts) {
|
|
|
6563
6847
|
}
|
|
6564
6848
|
};
|
|
6565
6849
|
}
|
|
6850
|
+
async function syncMemoryFromCloud(wiring, cloud, workspaceIds) {
|
|
6851
|
+
const uniqueIds = [...new Set(workspaceIds.filter(Boolean))];
|
|
6852
|
+
if (uniqueIds.length === 0) return;
|
|
6853
|
+
console.log(`[MemorySync] Initial cloud-to-local sync for ${uniqueIds.length} workspace(s)...`);
|
|
6854
|
+
for (const wsId of uniqueIds) {
|
|
6855
|
+
try {
|
|
6856
|
+
wiring.runtime.resolve(wsId);
|
|
6857
|
+
const result = await initialSyncFromCloud(wiring.runtime, cloud, wsId);
|
|
6858
|
+
if (result.pulled > 0 || result.skipped > 0) {
|
|
6859
|
+
console.log(
|
|
6860
|
+
`[MemorySync] workspace=${wsId}: ${result.pulled} pulled, ${result.skipped} skipped`
|
|
6861
|
+
);
|
|
6862
|
+
}
|
|
6863
|
+
} catch (err) {
|
|
6864
|
+
console.error(`[MemorySync] workspace=${wsId} failed:`, err.message);
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6566
6868
|
|
|
6567
6869
|
// src/daemon/memory/fork/select-memories.ts
|
|
6568
6870
|
var SELECT_MEMORIES_SYSTEM_PROMPT = [
|
|
@@ -6970,8 +7272,235 @@ function parsePrismerUri(uri) {
|
|
|
6970
7272
|
return { workspaceId, path: path7 };
|
|
6971
7273
|
}
|
|
6972
7274
|
|
|
6973
|
-
// src/daemon/
|
|
7275
|
+
// src/daemon/asset/metadata-index.ts
|
|
6974
7276
|
var import_node_fs10 = require("fs");
|
|
7277
|
+
var import_node_path8 = require("path");
|
|
7278
|
+
var DEFAULT_LIMIT = 8;
|
|
7279
|
+
var PULL_PAGE_SIZE = 500;
|
|
7280
|
+
var THROTTLE_MS = 3e4;
|
|
7281
|
+
function rowToMetadata(row) {
|
|
7282
|
+
return {
|
|
7283
|
+
assetId: row.asset_id,
|
|
7284
|
+
contentHash: row.content_hash,
|
|
7285
|
+
filename: row.filename,
|
|
7286
|
+
folderPath: row.folder_path,
|
|
7287
|
+
mime: row.mime,
|
|
7288
|
+
kind: row.kind,
|
|
7289
|
+
sizeBytes: row.size_bytes,
|
|
7290
|
+
description: row.description,
|
|
7291
|
+
assetIndexSeq: row.asset_index_seq
|
|
7292
|
+
};
|
|
7293
|
+
}
|
|
7294
|
+
var AssetMetadataIndex = class {
|
|
7295
|
+
db;
|
|
7296
|
+
cloud;
|
|
7297
|
+
/** Workspace ID — exposed for prismer:// URI construction. */
|
|
7298
|
+
workspaceId;
|
|
7299
|
+
cursorPath;
|
|
7300
|
+
_lastSyncMs = 0;
|
|
7301
|
+
constructor(opts) {
|
|
7302
|
+
this.db = opts.db;
|
|
7303
|
+
this.cloud = opts.cloud;
|
|
7304
|
+
this.workspaceId = opts.workspaceId;
|
|
7305
|
+
if (!(0, import_node_fs10.existsSync)(opts.workspaceStateDir)) {
|
|
7306
|
+
(0, import_node_fs10.mkdirSync)(opts.workspaceStateDir, { recursive: true });
|
|
7307
|
+
}
|
|
7308
|
+
this.cursorPath = (0, import_node_path8.join)(opts.workspaceStateDir, "asset-metadata-cursor.json");
|
|
7309
|
+
}
|
|
7310
|
+
/** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
|
|
7311
|
+
readCursor() {
|
|
7312
|
+
if (!(0, import_node_fs10.existsSync)(this.cursorPath)) return 0;
|
|
7313
|
+
try {
|
|
7314
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(this.cursorPath, "utf8"));
|
|
7315
|
+
if (parsed.workspaceId !== this.workspaceId) return 0;
|
|
7316
|
+
return parsed.cursor;
|
|
7317
|
+
} catch {
|
|
7318
|
+
return 0;
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
writeCursor(cursor) {
|
|
7322
|
+
const payload = {
|
|
7323
|
+
workspaceId: this.workspaceId,
|
|
7324
|
+
cursor,
|
|
7325
|
+
writtenAt: Date.now()
|
|
7326
|
+
};
|
|
7327
|
+
(0, import_node_fs10.writeFileSync)(this.cursorPath, JSON.stringify(payload, null, 2));
|
|
7328
|
+
}
|
|
7329
|
+
/**
|
|
7330
|
+
* Pull incremental asset metadata changes since the persisted cursor and
|
|
7331
|
+
* upsert into the local index. Newer rows overwrite older ones by
|
|
7332
|
+
* (workspace_id, asset_id) primary key.
|
|
7333
|
+
*
|
|
7334
|
+
* Returns the count of items applied + the new cursor. Throttled: if called
|
|
7335
|
+
* within 30s of the last successful pull, returns immediately.
|
|
7336
|
+
*/
|
|
7337
|
+
async pullDelta(opts) {
|
|
7338
|
+
const now = Date.now();
|
|
7339
|
+
if (now - this._lastSyncMs < THROTTLE_MS) {
|
|
7340
|
+
return { applied: 0, cursor: this.readCursor() };
|
|
7341
|
+
}
|
|
7342
|
+
const since = this.readCursor();
|
|
7343
|
+
const sinceParam = since > 0 ? `&since=${since}` : "";
|
|
7344
|
+
const envelope2 = await this.cloud.get(
|
|
7345
|
+
`/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
|
|
7346
|
+
{ signal: opts?.signal }
|
|
7347
|
+
);
|
|
7348
|
+
const upsert = this.db.prepare(`
|
|
7349
|
+
INSERT INTO asset_metadata_index
|
|
7350
|
+
(workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
|
|
7351
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7352
|
+
ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
|
|
7353
|
+
content_hash = excluded.content_hash,
|
|
7354
|
+
filename = excluded.filename,
|
|
7355
|
+
folder_path = excluded.folder_path,
|
|
7356
|
+
mime = excluded.mime,
|
|
7357
|
+
kind = excluded.kind,
|
|
7358
|
+
size_bytes = excluded.size_bytes,
|
|
7359
|
+
description = excluded.description,
|
|
7360
|
+
asset_index_seq = excluded.asset_index_seq,
|
|
7361
|
+
updated_at = excluded.updated_at
|
|
7362
|
+
`);
|
|
7363
|
+
const nowTs = Date.now();
|
|
7364
|
+
let applied = 0;
|
|
7365
|
+
const tx = this.db.transaction((items) => {
|
|
7366
|
+
for (const item of items) {
|
|
7367
|
+
upsert.run(
|
|
7368
|
+
this.workspaceId,
|
|
7369
|
+
item.assetId,
|
|
7370
|
+
item.contentHash,
|
|
7371
|
+
item.filename ?? null,
|
|
7372
|
+
item.folderPath ?? null,
|
|
7373
|
+
item.mime,
|
|
7374
|
+
item.kind,
|
|
7375
|
+
item.sizeBytes,
|
|
7376
|
+
item.description ?? null,
|
|
7377
|
+
item.assetIndexSeq,
|
|
7378
|
+
nowTs
|
|
7379
|
+
);
|
|
7380
|
+
applied += 1;
|
|
7381
|
+
}
|
|
7382
|
+
});
|
|
7383
|
+
try {
|
|
7384
|
+
tx(envelope2.items);
|
|
7385
|
+
this.writeCursor(envelope2.cursor);
|
|
7386
|
+
} catch (err) {
|
|
7387
|
+
throw err;
|
|
7388
|
+
}
|
|
7389
|
+
this._lastSyncMs = now;
|
|
7390
|
+
return { applied, cursor: envelope2.cursor };
|
|
7391
|
+
}
|
|
7392
|
+
/**
|
|
7393
|
+
* Search local index by filename or description substring.
|
|
7394
|
+
* Escapes LIKE wildcards (% and _). Default limit 8.
|
|
7395
|
+
*/
|
|
7396
|
+
search(query, limit) {
|
|
7397
|
+
const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
7398
|
+
const pattern = `%${escaped}%`;
|
|
7399
|
+
const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
|
|
7400
|
+
const rows = this.db.prepare(
|
|
7401
|
+
`SELECT * FROM asset_metadata_index
|
|
7402
|
+
WHERE workspace_id = ?
|
|
7403
|
+
AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
|
|
7404
|
+
ORDER BY asset_index_seq DESC
|
|
7405
|
+
LIMIT ?`
|
|
7406
|
+
).all(this.workspaceId, pattern, pattern, limitVal);
|
|
7407
|
+
return rows.map(rowToMetadata);
|
|
7408
|
+
}
|
|
7409
|
+
/** Exact match on filename column. Returns undefined if not indexed. */
|
|
7410
|
+
resolveByFilename(filename) {
|
|
7411
|
+
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
|
|
7412
|
+
return row ? rowToMetadata(row) : void 0;
|
|
7413
|
+
}
|
|
7414
|
+
/** Batch exact match — returns Map for O(1) access. */
|
|
7415
|
+
resolveByFilenames(filenames) {
|
|
7416
|
+
if (filenames.length === 0) return /* @__PURE__ */ new Map();
|
|
7417
|
+
const placeholders = filenames.map(() => "?").join(",");
|
|
7418
|
+
const params = [this.workspaceId, ...filenames];
|
|
7419
|
+
const rows = this.db.prepare(
|
|
7420
|
+
`SELECT * FROM asset_metadata_index
|
|
7421
|
+
WHERE workspace_id = ? AND filename IN (${placeholders})`
|
|
7422
|
+
).all(...params);
|
|
7423
|
+
const map = /* @__PURE__ */ new Map();
|
|
7424
|
+
for (const row of rows) {
|
|
7425
|
+
if (row.filename) map.set(row.filename, rowToMetadata(row));
|
|
7426
|
+
}
|
|
7427
|
+
return map;
|
|
7428
|
+
}
|
|
7429
|
+
};
|
|
7430
|
+
|
|
7431
|
+
// src/daemon/asset/rpc.ts
|
|
7432
|
+
var ASSET_PATH_PREFIX = "/local/asset/";
|
|
7433
|
+
function attachAssetRpc(opts) {
|
|
7434
|
+
return async (req, res) => {
|
|
7435
|
+
const url = req.url ?? "/";
|
|
7436
|
+
if (!url.startsWith(ASSET_PATH_PREFIX)) return false;
|
|
7437
|
+
const [pathOnly = ""] = url.split("?", 2);
|
|
7438
|
+
const subpath = pathOnly.slice(ASSET_PATH_PREFIX.length);
|
|
7439
|
+
const method = req.method ?? "GET";
|
|
7440
|
+
try {
|
|
7441
|
+
if (method === "POST" && subpath === "search") {
|
|
7442
|
+
const body = await readJson3(req);
|
|
7443
|
+
return handleSearch2(opts.resolveIndex, body, res);
|
|
7444
|
+
}
|
|
7445
|
+
respond3(res, 404, { error: "asset_route_not_found", path: url });
|
|
7446
|
+
return true;
|
|
7447
|
+
} catch (err) {
|
|
7448
|
+
respond3(res, 500, {
|
|
7449
|
+
error: "asset_rpc_failed",
|
|
7450
|
+
message: err instanceof Error ? err.message : String(err)
|
|
7451
|
+
});
|
|
7452
|
+
return true;
|
|
7453
|
+
}
|
|
7454
|
+
};
|
|
7455
|
+
}
|
|
7456
|
+
function handleSearch2(resolveIndex, body, res) {
|
|
7457
|
+
if (!body || typeof body !== "object") {
|
|
7458
|
+
return respond4002(res, "request body must be a JSON object");
|
|
7459
|
+
}
|
|
7460
|
+
const b = body;
|
|
7461
|
+
if (typeof b.workspaceId !== "string" || !b.workspaceId) {
|
|
7462
|
+
return respond4002(res, "workspaceId is required (string)");
|
|
7463
|
+
}
|
|
7464
|
+
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
7465
|
+
return respond4002(res, "query is required (non-empty string)");
|
|
7466
|
+
}
|
|
7467
|
+
const index = resolveIndex(b.workspaceId);
|
|
7468
|
+
if (!index) {
|
|
7469
|
+
respond3(res, 404, {
|
|
7470
|
+
error: "workspace_index_not_found",
|
|
7471
|
+
workspaceId: b.workspaceId,
|
|
7472
|
+
message: "No asset metadata index for this workspace. Ensure the daemon has synced asset metadata."
|
|
7473
|
+
});
|
|
7474
|
+
return true;
|
|
7475
|
+
}
|
|
7476
|
+
const limit = typeof b.limit === "number" && b.limit > 0 ? b.limit : void 0;
|
|
7477
|
+
const items = index.search(b.query.trim(), limit);
|
|
7478
|
+
respond3(res, 200, { items });
|
|
7479
|
+
return true;
|
|
7480
|
+
}
|
|
7481
|
+
function respond3(res, status, body) {
|
|
7482
|
+
res.statusCode = status;
|
|
7483
|
+
res.setHeader("Content-Type", "application/json");
|
|
7484
|
+
res.end(JSON.stringify(body));
|
|
7485
|
+
}
|
|
7486
|
+
function respond4002(res, message) {
|
|
7487
|
+
respond3(res, 400, { error: "invalid_request", message });
|
|
7488
|
+
return true;
|
|
7489
|
+
}
|
|
7490
|
+
async function readJson3(req) {
|
|
7491
|
+
let raw = "";
|
|
7492
|
+
req.setEncoding("utf8");
|
|
7493
|
+
for await (const chunk of req) raw += chunk;
|
|
7494
|
+
if (!raw) return {};
|
|
7495
|
+
try {
|
|
7496
|
+
return JSON.parse(raw);
|
|
7497
|
+
} catch {
|
|
7498
|
+
throw new Error("invalid_json");
|
|
7499
|
+
}
|
|
7500
|
+
}
|
|
7501
|
+
|
|
7502
|
+
// src/daemon/outbox-watcher.ts
|
|
7503
|
+
var import_node_fs11 = require("fs");
|
|
6975
7504
|
var path6 = __toESM(require("path"), 1);
|
|
6976
7505
|
var DEFAULT_INTERVAL_MS = 2e3;
|
|
6977
7506
|
var RESERVED_SUBDIR = "_uploaded";
|
|
@@ -7139,7 +7668,7 @@ var OutboxWatcher = class {
|
|
|
7139
7668
|
async scanDir(dir, kind, task) {
|
|
7140
7669
|
let entries = [];
|
|
7141
7670
|
try {
|
|
7142
|
-
entries = await
|
|
7671
|
+
entries = await import_node_fs11.promises.readdir(dir);
|
|
7143
7672
|
} catch (err) {
|
|
7144
7673
|
const code = err.code;
|
|
7145
7674
|
if (code === "ENOENT") return;
|
|
@@ -7152,7 +7681,7 @@ var OutboxWatcher = class {
|
|
|
7152
7681
|
const full = path6.join(dir, name);
|
|
7153
7682
|
let st;
|
|
7154
7683
|
try {
|
|
7155
|
-
st = await
|
|
7684
|
+
st = await import_node_fs11.promises.stat(full);
|
|
7156
7685
|
} catch {
|
|
7157
7686
|
continue;
|
|
7158
7687
|
}
|
|
@@ -7177,7 +7706,7 @@ var OutboxWatcher = class {
|
|
|
7177
7706
|
this.log("warn", `skip ${filePath}: no active taskId (no dispatch/handoff received yet)`);
|
|
7178
7707
|
return;
|
|
7179
7708
|
}
|
|
7180
|
-
const bytes = await
|
|
7709
|
+
const bytes = await import_node_fs11.promises.readFile(filePath);
|
|
7181
7710
|
const fileName = path6.basename(filePath);
|
|
7182
7711
|
const blob = new Blob([new Uint8Array(bytes)]);
|
|
7183
7712
|
const form = new FormData();
|
|
@@ -7273,8 +7802,8 @@ var ServicePool = class {
|
|
|
7273
7802
|
|
|
7274
7803
|
// src/daemon/shell-executor.ts
|
|
7275
7804
|
var import_node_child_process6 = require("child_process");
|
|
7276
|
-
var
|
|
7277
|
-
var
|
|
7805
|
+
var import_node_fs12 = require("fs");
|
|
7806
|
+
var import_node_path9 = require("path");
|
|
7278
7807
|
var DEFAULT_OUTPUT_LIMIT = 256 * 1024;
|
|
7279
7808
|
var DEFAULT_TIMEOUT = 6e4;
|
|
7280
7809
|
var HARD_MAX_TIMEOUT = 30 * 6e4;
|
|
@@ -7310,7 +7839,7 @@ async function executeShellDispatch(payload, deps) {
|
|
|
7310
7839
|
const command = readCommand(payload, execution);
|
|
7311
7840
|
if (!command.trim()) return fail2(payload.taskId, "shell_command_required", "Shell command is required");
|
|
7312
7841
|
const cwd = resolveCwd(execution.cwd, deps.config.defaultCwd);
|
|
7313
|
-
if (!(0,
|
|
7842
|
+
if (!(0, import_node_fs12.existsSync)(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
|
|
7314
7843
|
const timeoutMs = Math.min(
|
|
7315
7844
|
typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : deps.config.maxTimeoutMs,
|
|
7316
7845
|
deps.config.maxTimeoutMs
|
|
@@ -7412,8 +7941,8 @@ function readCommand(payload, execution) {
|
|
|
7412
7941
|
return payload.prompt;
|
|
7413
7942
|
}
|
|
7414
7943
|
function resolveCwd(raw, fallback) {
|
|
7415
|
-
if (typeof raw !== "string" || raw.trim() === "") return (0,
|
|
7416
|
-
return (0,
|
|
7944
|
+
if (typeof raw !== "string" || raw.trim() === "") return (0, import_node_path9.resolve)(fallback);
|
|
7945
|
+
return (0, import_node_path9.resolve)(raw);
|
|
7417
7946
|
}
|
|
7418
7947
|
function clampNumber(value, fallback, min, max) {
|
|
7419
7948
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
@@ -7565,6 +8094,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7565
8094
|
localServer;
|
|
7566
8095
|
outboxWatcher;
|
|
7567
8096
|
memoryWiring;
|
|
8097
|
+
assetMetadataIndexes = /* @__PURE__ */ new Map();
|
|
7568
8098
|
state = "idle";
|
|
7569
8099
|
startedAt = 0;
|
|
7570
8100
|
workspaceId = "";
|
|
@@ -7581,6 +8111,9 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7581
8111
|
this.paths = this.opts.paths ?? resolvePaths();
|
|
7582
8112
|
this.config = this.opts.configOverride ?? loadConfig(this.paths);
|
|
7583
8113
|
this.shellConfig = resolveShellConfig(this.config.shell);
|
|
8114
|
+
if (process.env.PRISMER_WORKSPACE_ID) {
|
|
8115
|
+
this.workspaceId = process.env.PRISMER_WORKSPACE_ID;
|
|
8116
|
+
}
|
|
7584
8117
|
process.env.PRISMER_BASE_URL = this.config.cloud_api_base;
|
|
7585
8118
|
process.env.PRISMER_API_KEY = this.config.api_key;
|
|
7586
8119
|
this.db = openLocalDb(this.paths.localDb);
|
|
@@ -7618,6 +8151,16 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7618
8151
|
baseDir: `${this.paths.root}/memory`,
|
|
7619
8152
|
deviceId: this.config.daemon_id
|
|
7620
8153
|
});
|
|
8154
|
+
if (this.memoryWiring && this.workspaceId) {
|
|
8155
|
+
syncMemoryFromCloud(this.memoryWiring, this.cloud, [this.workspaceId]).catch(
|
|
8156
|
+
(err) => console.error("[Daemon] Initial memory sync failed:", err.message)
|
|
8157
|
+
);
|
|
8158
|
+
}
|
|
8159
|
+
if (this.workspaceId) {
|
|
8160
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
8161
|
+
(err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
|
|
8162
|
+
);
|
|
8163
|
+
}
|
|
7621
8164
|
const containerId = process.env.PRISMER_CONTAINER_ID;
|
|
7622
8165
|
const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
|
|
7623
8166
|
this.outboxWatcher = new OutboxWatcher({
|
|
@@ -7665,7 +8208,10 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7665
8208
|
// wiring step so it could be reviewed alongside the host-adapter
|
|
7666
8209
|
// consumer (Hermes T2-B), which is what surfaces these routes to
|
|
7667
8210
|
// an actual agent process.
|
|
7668
|
-
attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0
|
|
8211
|
+
attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
|
|
8212
|
+
attachAsset: attachAssetRpc({
|
|
8213
|
+
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
8214
|
+
})
|
|
7669
8215
|
});
|
|
7670
8216
|
await this.localServer.start();
|
|
7671
8217
|
}
|
|
@@ -7861,10 +8407,10 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7861
8407
|
const rawJson = process.env.PRISMER_HOSTED_AGENT_JSON;
|
|
7862
8408
|
let raw;
|
|
7863
8409
|
if (rawFile) {
|
|
7864
|
-
if (!(0,
|
|
8410
|
+
if (!(0, import_node_fs13.existsSync)(rawFile)) {
|
|
7865
8411
|
throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
|
|
7866
8412
|
}
|
|
7867
|
-
raw = (0,
|
|
8413
|
+
raw = (0, import_node_fs13.readFileSync)(rawFile, "utf8");
|
|
7868
8414
|
} else if (rawJson) {
|
|
7869
8415
|
raw = rawJson;
|
|
7870
8416
|
}
|
|
@@ -7994,12 +8540,23 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7994
8540
|
case "workspace_file.changed":
|
|
7995
8541
|
this.onWorkspaceFileChanged(msg.payload);
|
|
7996
8542
|
return;
|
|
8543
|
+
case "asset.changed":
|
|
8544
|
+
void this.onAssetChanged(msg.payload);
|
|
8545
|
+
return;
|
|
7997
8546
|
default:
|
|
7998
8547
|
this.emit("unknown-message", msg);
|
|
7999
8548
|
}
|
|
8000
8549
|
}
|
|
8001
8550
|
async onHostAcked(payload) {
|
|
8002
8551
|
this.workspaceId = payload.workspaceId;
|
|
8552
|
+
if (this.memoryWiring && this.workspaceId) {
|
|
8553
|
+
syncMemoryFromCloud(this.memoryWiring, this.cloud, [this.workspaceId]).catch(
|
|
8554
|
+
(err) => console.error("[Daemon] Initial memory sync failed:", err.message)
|
|
8555
|
+
);
|
|
8556
|
+
}
|
|
8557
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
8558
|
+
(err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
|
|
8559
|
+
);
|
|
8003
8560
|
for (const id of payload.profilesToSync) {
|
|
8004
8561
|
try {
|
|
8005
8562
|
await this.syncProfileFromCloud(id);
|
|
@@ -8059,6 +8616,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8059
8616
|
paths: this.paths,
|
|
8060
8617
|
signal: ctrl.signal,
|
|
8061
8618
|
ensureService: (profile, adapter) => this.servicePool.ensureService(profile, adapter),
|
|
8619
|
+
assetMetadataIndexes: this.assetMetadataIndexes,
|
|
8062
8620
|
onProgress: () => {
|
|
8063
8621
|
const running = this.runningTasks.get(payload.taskId);
|
|
8064
8622
|
if (running) running.lastProgressAt = Date.now();
|
|
@@ -8159,6 +8717,19 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8159
8717
|
this.emit("sync-error", err);
|
|
8160
8718
|
}
|
|
8161
8719
|
}
|
|
8720
|
+
async onAssetChanged(payload) {
|
|
8721
|
+
if (!payload.workspaceId) return;
|
|
8722
|
+
const index = this.assetMetadataIndexes.get(payload.workspaceId);
|
|
8723
|
+
if (!index) return;
|
|
8724
|
+
try {
|
|
8725
|
+
const result = await index.pullDelta();
|
|
8726
|
+
if (result.applied > 0) {
|
|
8727
|
+
console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
|
|
8728
|
+
}
|
|
8729
|
+
} catch (err) {
|
|
8730
|
+
console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
|
|
8731
|
+
}
|
|
8732
|
+
}
|
|
8162
8733
|
onWorkspaceFileChanged(payload) {
|
|
8163
8734
|
if (payload.operation === "delete") {
|
|
8164
8735
|
this.db.prepare("DELETE FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").run(payload.workspaceId, payload.path);
|
|
@@ -8194,6 +8765,28 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8194
8765
|
}
|
|
8195
8766
|
};
|
|
8196
8767
|
}
|
|
8768
|
+
/**
|
|
8769
|
+
* Ensure an AssetMetadataIndex exists for the given workspace and pull
|
|
8770
|
+
* delta from cloud. Idempotent — creates the index on first call, reuses
|
|
8771
|
+
* it on subsequent calls. Same cursor-catch-up semantics as WorkspaceMirror.
|
|
8772
|
+
*/
|
|
8773
|
+
async syncAssetMetadata(workspaceId) {
|
|
8774
|
+
let index = this.assetMetadataIndexes.get(workspaceId);
|
|
8775
|
+
if (!index) {
|
|
8776
|
+
const stateDir = `${this.paths.root}/${workspaceId}`;
|
|
8777
|
+
index = new AssetMetadataIndex({
|
|
8778
|
+
db: this.db,
|
|
8779
|
+
cloud: this.cloud,
|
|
8780
|
+
workspaceId,
|
|
8781
|
+
workspaceStateDir: stateDir
|
|
8782
|
+
});
|
|
8783
|
+
this.assetMetadataIndexes.set(workspaceId, index);
|
|
8784
|
+
}
|
|
8785
|
+
const result = await index.pullDelta();
|
|
8786
|
+
if (result.applied > 0) {
|
|
8787
|
+
console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8197
8790
|
/**
|
|
8198
8791
|
* SyncWorker FlushFn — pushes local writes to cloud.
|
|
8199
8792
|
*
|
|
@@ -8358,6 +8951,8 @@ function safeJsonParse(raw) {
|
|
|
8358
8951
|
}
|
|
8359
8952
|
|
|
8360
8953
|
// src/cli/commands/daemon.ts
|
|
8954
|
+
init_util();
|
|
8955
|
+
init_ui();
|
|
8361
8956
|
function buildDaemonCommand() {
|
|
8362
8957
|
const cmd = new import_commander8.Command("daemon").description("Manage the prismer daemon process");
|
|
8363
8958
|
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) => {
|
|
@@ -8429,9 +9024,9 @@ function buildDaemonCommand() {
|
|
|
8429
9024
|
if (existingPid && pidAlive(existingPid)) {
|
|
8430
9025
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
8431
9026
|
}
|
|
8432
|
-
if (!(0,
|
|
8433
|
-
const logFile = (0,
|
|
8434
|
-
const fd = (0,
|
|
9027
|
+
if (!(0, import_node_fs14.existsSync)(paths.logsDir)) (0, import_node_fs14.mkdirSync)(paths.logsDir, { recursive: true });
|
|
9028
|
+
const logFile = (0, import_node_path10.join)(paths.logsDir, "daemon.log");
|
|
9029
|
+
const fd = (0, import_node_fs14.openSync)(logFile, "a");
|
|
8435
9030
|
const args = [process.argv[1], "daemon", "run"];
|
|
8436
9031
|
if (opts.port) args.push("--port", String(opts.port));
|
|
8437
9032
|
if (opts.localServer === false) args.push("--no-local-server");
|
|
@@ -8514,8 +9109,8 @@ function buildDaemonCommand() {
|
|
|
8514
9109
|
});
|
|
8515
9110
|
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) => {
|
|
8516
9111
|
const paths = resolvePaths();
|
|
8517
|
-
const logFile = (0,
|
|
8518
|
-
if (!(0,
|
|
9112
|
+
const logFile = (0, import_node_path10.join)(paths.logsDir, "daemon.log");
|
|
9113
|
+
if (!(0, import_node_fs14.existsSync)(logFile)) {
|
|
8519
9114
|
exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
|
|
8520
9115
|
}
|
|
8521
9116
|
const lines = Math.max(1, opts.tail);
|
|
@@ -8556,13 +9151,13 @@ async function tailFromEnd(path7, lines) {
|
|
|
8556
9151
|
}
|
|
8557
9152
|
}
|
|
8558
9153
|
async function followFile(path7) {
|
|
8559
|
-
let offset = (0,
|
|
9154
|
+
let offset = (0, import_node_fs14.statSync)(path7).size;
|
|
8560
9155
|
for (; ; ) {
|
|
8561
9156
|
await (0, import_promises.setTimeout)(1e3);
|
|
8562
|
-
const size = (0,
|
|
9157
|
+
const size = (0, import_node_fs14.statSync)(path7).size;
|
|
8563
9158
|
if (size < offset) offset = 0;
|
|
8564
9159
|
if (size === offset) continue;
|
|
8565
|
-
const stream = (0,
|
|
9160
|
+
const stream = (0, import_node_fs14.createReadStream)(path7, { start: offset, end: size - 1, encoding: "utf8" });
|
|
8566
9161
|
for await (const chunk of stream) process.stdout.write(chunk);
|
|
8567
9162
|
offset = size;
|
|
8568
9163
|
}
|
|
@@ -8570,15 +9165,16 @@ async function followFile(path7) {
|
|
|
8570
9165
|
|
|
8571
9166
|
// src/cli/commands/events.ts
|
|
8572
9167
|
var import_commander9 = require("commander");
|
|
8573
|
-
var
|
|
9168
|
+
var import_node_fs15 = require("fs");
|
|
8574
9169
|
var import_node_os5 = require("os");
|
|
8575
|
-
var
|
|
9170
|
+
var import_node_path11 = require("path");
|
|
8576
9171
|
var import_node_readline = require("readline");
|
|
8577
|
-
|
|
9172
|
+
init_util();
|
|
9173
|
+
var DEFAULT_LIMIT2 = 50;
|
|
8578
9174
|
function buildEventsCommand() {
|
|
8579
9175
|
return addEventOptions(new import_commander9.Command("events").description("Read local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
|
|
8580
9176
|
const file = eventsPath();
|
|
8581
|
-
if (!(0,
|
|
9177
|
+
if (!(0, import_node_fs15.existsSync)(file)) {
|
|
8582
9178
|
printJson(unavailable(file));
|
|
8583
9179
|
process.exitCode = 1;
|
|
8584
9180
|
return;
|
|
@@ -8595,7 +9191,7 @@ function buildEventsCommand() {
|
|
|
8595
9191
|
function buildEventsStatsCommand() {
|
|
8596
9192
|
return addEventOptions(new import_commander9.Command("events:stats").description("Summarize local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
|
|
8597
9193
|
const file = eventsPath();
|
|
8598
|
-
if (!(0,
|
|
9194
|
+
if (!(0, import_node_fs15.existsSync)(file)) {
|
|
8599
9195
|
printJson(unavailable(file));
|
|
8600
9196
|
process.exitCode = 1;
|
|
8601
9197
|
return;
|
|
@@ -8610,10 +9206,10 @@ function buildEventsStatsCommand() {
|
|
|
8610
9206
|
});
|
|
8611
9207
|
}
|
|
8612
9208
|
function addEventOptions(cmd) {
|
|
8613
|
-
return cmd.option("--limit <n>", "Max events to return/read", parsePositiveInt3,
|
|
9209
|
+
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)");
|
|
8614
9210
|
}
|
|
8615
9211
|
function eventsPath() {
|
|
8616
|
-
return (0,
|
|
9212
|
+
return (0, import_node_path11.join)(process.env.PRISMER_HOME ?? (0, import_node_path11.join)((0, import_node_os5.homedir)(), ".prismer"), "para", "events.jsonl");
|
|
8617
9213
|
}
|
|
8618
9214
|
function unavailable(file) {
|
|
8619
9215
|
return {
|
|
@@ -8629,7 +9225,7 @@ function unavailable(file) {
|
|
|
8629
9225
|
async function readEvents(file, filters) {
|
|
8630
9226
|
const out = [];
|
|
8631
9227
|
const rl = (0, import_node_readline.createInterface)({
|
|
8632
|
-
input: (0,
|
|
9228
|
+
input: (0, import_node_fs15.createReadStream)(file, { encoding: "utf8" }),
|
|
8633
9229
|
crlfDelay: Infinity
|
|
8634
9230
|
});
|
|
8635
9231
|
for await (const line of rl) {
|
|
@@ -8681,7 +9277,7 @@ function bump(map, key) {
|
|
|
8681
9277
|
}
|
|
8682
9278
|
function normalizeFilters(opts) {
|
|
8683
9279
|
return {
|
|
8684
|
-
limit: Math.max(1, Math.min(1e4, Number.isFinite(opts.limit) ? opts.limit :
|
|
9280
|
+
limit: Math.max(1, Math.min(1e4, Number.isFinite(opts.limit) ? opts.limit : DEFAULT_LIMIT2)),
|
|
8685
9281
|
agentId: opts.agentId,
|
|
8686
9282
|
sessionId: opts.sessionId,
|
|
8687
9283
|
family: opts.family,
|
|
@@ -8693,13 +9289,14 @@ function cleanFilters(filters) {
|
|
|
8693
9289
|
}
|
|
8694
9290
|
function parsePositiveInt3(v) {
|
|
8695
9291
|
const n = Number.parseInt(v, 10);
|
|
8696
|
-
return Number.isFinite(n) && n > 0 ? n :
|
|
9292
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
|
|
8697
9293
|
}
|
|
8698
9294
|
|
|
8699
9295
|
// src/cli/commands/memory.ts
|
|
8700
9296
|
var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
|
|
8701
9297
|
var import_commander10 = require("commander");
|
|
8702
|
-
var
|
|
9298
|
+
var import_node_fs16 = require("fs");
|
|
9299
|
+
init_util();
|
|
8703
9300
|
var LOCAL_BASE = process.env.PRISMER_DAEMON_URL ?? "http://127.0.0.1:3210";
|
|
8704
9301
|
function buildMemoryCommand() {
|
|
8705
9302
|
const cmd = new import_commander10.Command("memory").description("Inspect local daemon memory/cache state");
|
|
@@ -8791,7 +9388,7 @@ async function tryDaemon(methods, paths) {
|
|
|
8791
9388
|
try {
|
|
8792
9389
|
const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
|
|
8793
9390
|
if (res.status === 404) continue;
|
|
8794
|
-
const body = await
|
|
9391
|
+
const body = await readJson4(res);
|
|
8795
9392
|
if (!res.ok) {
|
|
8796
9393
|
return {
|
|
8797
9394
|
ok: false,
|
|
@@ -8810,7 +9407,7 @@ async function tryDaemon(methods, paths) {
|
|
|
8810
9407
|
}
|
|
8811
9408
|
return void 0;
|
|
8812
9409
|
}
|
|
8813
|
-
async function
|
|
9410
|
+
async function readJson4(res) {
|
|
8814
9411
|
const text = await res.text();
|
|
8815
9412
|
if (!text) return null;
|
|
8816
9413
|
try {
|
|
@@ -8850,7 +9447,7 @@ function readCacheSnapshot(limit) {
|
|
|
8850
9447
|
const paths = resolvePaths();
|
|
8851
9448
|
const empty = {
|
|
8852
9449
|
dbPath: paths.localDb,
|
|
8853
|
-
dbExists: (0,
|
|
9450
|
+
dbExists: (0, import_node_fs16.existsSync)(paths.localDb),
|
|
8854
9451
|
tables: {
|
|
8855
9452
|
cached_assets: { exists: false, count: 0, sizeBytes: 0 },
|
|
8856
9453
|
workspace_files_mirror: { exists: false, count: 0 }
|
|
@@ -8904,9 +9501,9 @@ function readCacheSnapshot(limit) {
|
|
|
8904
9501
|
db?.close();
|
|
8905
9502
|
}
|
|
8906
9503
|
}
|
|
8907
|
-
function tableExists(db,
|
|
8908
|
-
const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(
|
|
8909
|
-
return row?.name ===
|
|
9504
|
+
function tableExists(db, table2) {
|
|
9505
|
+
const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table2);
|
|
9506
|
+
return row?.name === table2;
|
|
8910
9507
|
}
|
|
8911
9508
|
function assetRow(row) {
|
|
8912
9509
|
const hash = String(row.content_hash ?? "");
|
|
@@ -8972,16 +9569,17 @@ function messageFromBody(body) {
|
|
|
8972
9569
|
var import_commander11 = require("commander");
|
|
8973
9570
|
|
|
8974
9571
|
// src/pair.ts
|
|
8975
|
-
var
|
|
8976
|
-
var
|
|
9572
|
+
var import_node_crypto5 = require("crypto");
|
|
9573
|
+
var import_node_os7 = require("os");
|
|
8977
9574
|
var import_promises2 = require("timers/promises");
|
|
8978
9575
|
var import_qrcode = __toESM(require("qrcode"), 1);
|
|
8979
9576
|
|
|
8980
9577
|
// src/daemon-id.ts
|
|
8981
|
-
var
|
|
9578
|
+
var import_node_os6 = require("os");
|
|
8982
9579
|
var PREFIX = "daemon-";
|
|
8983
9580
|
function newDaemonId() {
|
|
8984
|
-
|
|
9581
|
+
const host = (0, import_node_os6.hostname)().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
|
|
9582
|
+
return `${PREFIX}${host}`;
|
|
8985
9583
|
}
|
|
8986
9584
|
|
|
8987
9585
|
// src/pair.ts
|
|
@@ -8999,7 +9597,7 @@ async function pair(opts) {
|
|
|
8999
9597
|
"pair: --as-user requires LOCAL_ONLY=1. Without that gate, this would skip mobile approval and silently mint a key for the named user."
|
|
9000
9598
|
);
|
|
9001
9599
|
}
|
|
9002
|
-
const { publicKey } = (0,
|
|
9600
|
+
const { publicKey } = (0, import_node_crypto5.generateKeyPairSync)("ed25519");
|
|
9003
9601
|
const devicePub = publicKey.export({ format: "der", type: "spki" }).toString("base64");
|
|
9004
9602
|
const cloud = new CloudClient({
|
|
9005
9603
|
baseUrl: opts.cloudBaseUrl,
|
|
@@ -9012,7 +9610,7 @@ async function pair(opts) {
|
|
|
9012
9610
|
"/api/im/pair/offer",
|
|
9013
9611
|
{
|
|
9014
9612
|
auth: false,
|
|
9015
|
-
body: { devicePub, deviceName: opts.deviceName ?? (0,
|
|
9613
|
+
body: { devicePub, deviceName: opts.deviceName ?? (0, import_node_os7.hostname)() }
|
|
9016
9614
|
}
|
|
9017
9615
|
);
|
|
9018
9616
|
if (!offerRes.ok) {
|
|
@@ -9089,6 +9687,8 @@ function unwrapEnvelope2(raw) {
|
|
|
9089
9687
|
}
|
|
9090
9688
|
|
|
9091
9689
|
// src/cli/commands/pair.ts
|
|
9690
|
+
init_util();
|
|
9691
|
+
init_ui();
|
|
9092
9692
|
function buildPairCommand() {
|
|
9093
9693
|
return new import_commander11.Command("pair").description("Legacy QR approval path; use `prismer setup` to bind this runtime").option(
|
|
9094
9694
|
"--cloud <url>",
|
|
@@ -9131,9 +9731,9 @@ function buildPairCommand() {
|
|
|
9131
9731
|
|
|
9132
9732
|
// src/cli/commands/profile.ts
|
|
9133
9733
|
var import_commander12 = require("commander");
|
|
9134
|
-
var
|
|
9135
|
-
var
|
|
9136
|
-
var
|
|
9734
|
+
var import_node_fs17 = require("fs");
|
|
9735
|
+
var import_node_os8 = require("os");
|
|
9736
|
+
var import_node_path12 = require("path");
|
|
9137
9737
|
var import_node_child_process8 = require("child_process");
|
|
9138
9738
|
|
|
9139
9739
|
// src/templates/roles/product-manager.json
|
|
@@ -9143,7 +9743,7 @@ var product_manager_default = {
|
|
|
9143
9743
|
description: "Writes PRDs, defines requirements, verifies implementations",
|
|
9144
9744
|
applicableAdapters: ["hermes", "openclaw", "claude-code"],
|
|
9145
9745
|
configSchema: {
|
|
9146
|
-
model: "
|
|
9746
|
+
model: "us-kimi-k2.6",
|
|
9147
9747
|
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",
|
|
9148
9748
|
allowedTools: ["Read", "Write", "WebSearch"],
|
|
9149
9749
|
maxTokens: 8e3
|
|
@@ -9157,7 +9757,7 @@ var engineer_default = {
|
|
|
9157
9757
|
description: "Implements features per PRD, writes code, runs tests",
|
|
9158
9758
|
applicableAdapters: ["hermes", "openclaw", "claude-code"],
|
|
9159
9759
|
configSchema: {
|
|
9160
|
-
model: "
|
|
9760
|
+
model: "us-kimi-k2.6",
|
|
9161
9761
|
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",
|
|
9162
9762
|
allowedTools: ["Read", "Write", "Edit", "Bash", "Grep"],
|
|
9163
9763
|
maxTokens: 16e3
|
|
@@ -9171,7 +9771,7 @@ var ceo_default = {
|
|
|
9171
9771
|
description: "Sets strategic direction, asks tough questions, makes go/no-go calls",
|
|
9172
9772
|
applicableAdapters: ["hermes", "openclaw", "claude-code"],
|
|
9173
9773
|
configSchema: {
|
|
9174
|
-
model: "
|
|
9774
|
+
model: "us-kimi-k2.6",
|
|
9175
9775
|
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",
|
|
9176
9776
|
allowedTools: ["Read", "WebSearch"],
|
|
9177
9777
|
maxTokens: 4e3
|
|
@@ -9185,7 +9785,7 @@ var researcher_default = {
|
|
|
9185
9785
|
description: "Investigates topics, gathers sources, writes research memos with citations",
|
|
9186
9786
|
applicableAdapters: ["hermes", "openclaw", "claude-code"],
|
|
9187
9787
|
configSchema: {
|
|
9188
|
-
model: "
|
|
9788
|
+
model: "us-kimi-k2.6",
|
|
9189
9789
|
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",
|
|
9190
9790
|
allowedTools: ["Read", "Write", "WebSearch", "WebFetch"],
|
|
9191
9791
|
maxTokens: 12e3
|
|
@@ -9211,6 +9811,7 @@ function listRoleTemplates() {
|
|
|
9211
9811
|
}
|
|
9212
9812
|
|
|
9213
9813
|
// src/cli/commands/profile.ts
|
|
9814
|
+
init_util();
|
|
9214
9815
|
function buildProfileCommand() {
|
|
9215
9816
|
const cmd = new import_commander12.Command("profile").description("Manage AgentProfile (per-agent adapter config)");
|
|
9216
9817
|
cmd.command("templates").description("List built-in role templates (PM / Engineer / CEO \u2026)").option("--json", "Output JSON (default)").action(() => {
|
|
@@ -9221,7 +9822,7 @@ function buildProfileCommand() {
|
|
|
9221
9822
|
const data = await cloud.get(`/api/im/agent_profiles?agentId=${encodeURIComponent(opts.agent)}`);
|
|
9222
9823
|
printJson(data);
|
|
9223
9824
|
}, { code: "profile_list_failed" }));
|
|
9224
|
-
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) => {
|
|
9825
|
+
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) => {
|
|
9225
9826
|
let configObj = {};
|
|
9226
9827
|
let adapterName = opts.adapter ?? "hermes";
|
|
9227
9828
|
if (opts.fromTemplate) {
|
|
@@ -9234,6 +9835,9 @@ function buildProfileCommand() {
|
|
|
9234
9835
|
const inline = readJsonArg(opts.config);
|
|
9235
9836
|
configObj = { ...configObj, ...inline };
|
|
9236
9837
|
}
|
|
9838
|
+
if (opts.model) {
|
|
9839
|
+
configObj.model = opts.model;
|
|
9840
|
+
}
|
|
9237
9841
|
const cloud = mkCloud4();
|
|
9238
9842
|
const wsId = opts.workspaceId ?? await resolveDefaultWorkspaceId(cloud);
|
|
9239
9843
|
const res = await cloud.request("POST", "/api/im/agent_profiles", {
|
|
@@ -9253,12 +9857,12 @@ function buildProfileCommand() {
|
|
|
9253
9857
|
const profile = await cloud.get(
|
|
9254
9858
|
`/api/im/agent_profiles/${encodeURIComponent(profileId)}`
|
|
9255
9859
|
);
|
|
9256
|
-
const tmpFile = (0,
|
|
9257
|
-
(0,
|
|
9860
|
+
const tmpFile = (0, import_node_path12.join)((0, import_node_os8.tmpdir)(), `prismer-profile-${profileId}.json`);
|
|
9861
|
+
(0, import_node_fs17.writeFileSync)(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
|
|
9258
9862
|
const editor = process.env.EDITOR || "vi";
|
|
9259
9863
|
const ed = (0, import_node_child_process8.spawnSync)(editor, [tmpFile], { stdio: "inherit" });
|
|
9260
9864
|
if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
|
|
9261
|
-
const newConfig = JSON.parse((0,
|
|
9865
|
+
const newConfig = JSON.parse((0, import_node_fs17.readFileSync)(tmpFile, "utf8"));
|
|
9262
9866
|
const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
|
|
9263
9867
|
body: { config: newConfig, version: profile.version }
|
|
9264
9868
|
});
|
|
@@ -9280,8 +9884,8 @@ function mkCloud4() {
|
|
|
9280
9884
|
function readJsonArg(arg) {
|
|
9281
9885
|
if (arg.startsWith("@")) {
|
|
9282
9886
|
const path7 = arg.slice(1);
|
|
9283
|
-
if (!(0,
|
|
9284
|
-
return JSON.parse((0,
|
|
9887
|
+
if (!(0, import_node_fs17.existsSync)(path7)) throw new Error(`File not found: ${path7}`);
|
|
9888
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(path7, "utf8"));
|
|
9285
9889
|
}
|
|
9286
9890
|
return JSON.parse(arg);
|
|
9287
9891
|
}
|
|
@@ -9294,6 +9898,8 @@ async function resolveDefaultWorkspaceId(cloud) {
|
|
|
9294
9898
|
|
|
9295
9899
|
// src/cli/commands/sandbox.ts
|
|
9296
9900
|
var import_commander13 = require("commander");
|
|
9901
|
+
init_util();
|
|
9902
|
+
init_ui();
|
|
9297
9903
|
function buildSandboxCommand() {
|
|
9298
9904
|
const cmd = new import_commander13.Command("sandbox").description("Inspect and smoke-test sandbox lifecycle");
|
|
9299
9905
|
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) => {
|
|
@@ -9495,11 +10101,13 @@ async function safeParseResponse(res) {
|
|
|
9495
10101
|
|
|
9496
10102
|
// src/cli/commands/setup.ts
|
|
9497
10103
|
var import_commander14 = require("commander");
|
|
9498
|
-
var
|
|
10104
|
+
var import_node_os9 = require("os");
|
|
9499
10105
|
var import_node_child_process9 = require("child_process");
|
|
9500
|
-
var
|
|
9501
|
-
var
|
|
10106
|
+
var import_node_crypto6 = require("crypto");
|
|
10107
|
+
var import_node_fs18 = require("fs");
|
|
9502
10108
|
var import_node_http2 = require("http");
|
|
10109
|
+
init_util();
|
|
10110
|
+
init_ui();
|
|
9503
10111
|
function buildSetupCommand() {
|
|
9504
10112
|
return new import_commander14.Command("setup").description("Set up this local runtime and bind it to Prismer Cloud").argument("[api-key]", "Prismer daemon API key; primarily for manual recovery and automation").option(
|
|
9505
10113
|
"--cloud <url>",
|
|
@@ -9522,6 +10130,7 @@ function buildSetupCommand() {
|
|
|
9522
10130
|
getUI().blank();
|
|
9523
10131
|
}
|
|
9524
10132
|
const shouldStart = opts.start !== false;
|
|
10133
|
+
stopRunningDaemon(paths);
|
|
9525
10134
|
if (opts.pair || opts.asUser) {
|
|
9526
10135
|
if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
|
|
9527
10136
|
if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
|
|
@@ -9538,7 +10147,7 @@ function buildSetupCommand() {
|
|
|
9538
10147
|
}
|
|
9539
10148
|
const result = await pair({
|
|
9540
10149
|
cloudBaseUrl,
|
|
9541
|
-
deviceName: opts.deviceName ?? (0,
|
|
10150
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
|
|
9542
10151
|
force: opts.force,
|
|
9543
10152
|
paths,
|
|
9544
10153
|
asUserEmail: opts.asUser
|
|
@@ -9555,13 +10164,13 @@ function buildSetupCommand() {
|
|
|
9555
10164
|
apiKey = await mintDaemonApiKey({
|
|
9556
10165
|
cloudBaseUrl,
|
|
9557
10166
|
token: authToken,
|
|
9558
|
-
deviceName: opts.deviceName ?? (0,
|
|
10167
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)()
|
|
9559
10168
|
});
|
|
9560
10169
|
}
|
|
9561
10170
|
if (!apiKey && !authToken && !opts.check && opts.browser !== false) {
|
|
9562
10171
|
apiKey = await runBrowserSetup({
|
|
9563
10172
|
cloudBaseUrl,
|
|
9564
|
-
deviceName: opts.deviceName ?? (0,
|
|
10173
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
|
|
9565
10174
|
json: Boolean(opts.json)
|
|
9566
10175
|
});
|
|
9567
10176
|
}
|
|
@@ -9645,7 +10254,7 @@ function buildSetupCommand() {
|
|
|
9645
10254
|
}, { code: "setup_failed" }));
|
|
9646
10255
|
}
|
|
9647
10256
|
async function runBrowserSetup(input) {
|
|
9648
|
-
const state = (0,
|
|
10257
|
+
const state = (0, import_node_crypto6.randomBytes)(18).toString("base64url");
|
|
9649
10258
|
const server = (0, import_node_http2.createServer)();
|
|
9650
10259
|
const apiKeyPromise = waitForSetupCallback(server, state);
|
|
9651
10260
|
await new Promise((resolve3, reject) => {
|
|
@@ -9742,13 +10351,35 @@ function startDaemonDetached(home) {
|
|
|
9742
10351
|
});
|
|
9743
10352
|
child.unref();
|
|
9744
10353
|
}
|
|
10354
|
+
function stopRunningDaemon(paths) {
|
|
10355
|
+
const pid = readPidFile(paths);
|
|
10356
|
+
if (!pid || !pidAlive(pid)) {
|
|
10357
|
+
if (pid) clearPidFile(paths);
|
|
10358
|
+
return;
|
|
10359
|
+
}
|
|
10360
|
+
try {
|
|
10361
|
+
process.kill(pid, "SIGTERM");
|
|
10362
|
+
} catch {
|
|
10363
|
+
return;
|
|
10364
|
+
}
|
|
10365
|
+
const deadline = Date.now() + 5e3;
|
|
10366
|
+
while (Date.now() < deadline) {
|
|
10367
|
+
if (!pidAlive(pid)) {
|
|
10368
|
+
clearPidFile(paths);
|
|
10369
|
+
return;
|
|
10370
|
+
}
|
|
10371
|
+
const start = Date.now();
|
|
10372
|
+
while (Date.now() - start < 200) {
|
|
10373
|
+
}
|
|
10374
|
+
}
|
|
10375
|
+
}
|
|
9745
10376
|
function shouldArchiveLocalDb(previous, next) {
|
|
9746
10377
|
return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
|
|
9747
10378
|
}
|
|
9748
10379
|
function archiveLocalDb(localDbPath) {
|
|
9749
|
-
if (!(0,
|
|
10380
|
+
if (!(0, import_node_fs18.existsSync)(localDbPath)) return;
|
|
9750
10381
|
const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
9751
|
-
(0,
|
|
10382
|
+
(0, import_node_fs18.renameSync)(localDbPath, archived);
|
|
9752
10383
|
}
|
|
9753
10384
|
async function mintDaemonApiKey(input) {
|
|
9754
10385
|
const label = `Daemon: ${input.deviceName} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
@@ -9769,6 +10400,8 @@ async function mintDaemonApiKey(input) {
|
|
|
9769
10400
|
|
|
9770
10401
|
// src/cli/commands/status.ts
|
|
9771
10402
|
var import_commander15 = require("commander");
|
|
10403
|
+
init_util();
|
|
10404
|
+
init_ui();
|
|
9772
10405
|
function buildStatusCommand() {
|
|
9773
10406
|
return new import_commander15.Command("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
9774
10407
|
const paths = resolvePaths();
|
|
@@ -9795,10 +10428,34 @@ function buildStatusCommand() {
|
|
|
9795
10428
|
const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
9796
10429
|
let cloudOk = false;
|
|
9797
10430
|
let me = null;
|
|
10431
|
+
let devices = null;
|
|
10432
|
+
let agents = null;
|
|
9798
10433
|
try {
|
|
9799
|
-
const
|
|
9800
|
-
cloudOk =
|
|
9801
|
-
me =
|
|
10434
|
+
const meRes = await cloud.request("GET", "/api/im/me", { timeoutMs: 3e3 });
|
|
10435
|
+
cloudOk = meRes.ok;
|
|
10436
|
+
me = meRes.data ?? null;
|
|
10437
|
+
if (cloudOk) {
|
|
10438
|
+
const wsRes = await cloud.request("GET", "/api/im/workspaces", { timeoutMs: 3e3 });
|
|
10439
|
+
if (wsRes.ok) {
|
|
10440
|
+
const wsBody = wsRes.data;
|
|
10441
|
+
const wsList = wsBody?.data;
|
|
10442
|
+
if (Array.isArray(wsList) && wsList.length > 0) {
|
|
10443
|
+
const wsId = wsList[0]?.id;
|
|
10444
|
+
if (wsId) {
|
|
10445
|
+
const devRes = await cloud.request("GET", `/api/workspace/runtime-installations?workspaceId=${encodeURIComponent(wsId)}&includeStopped=false`, { timeoutMs: 3e3 });
|
|
10446
|
+
if (devRes.ok) {
|
|
10447
|
+
const devBody = devRes.data;
|
|
10448
|
+
devices = devBody?.data;
|
|
10449
|
+
}
|
|
10450
|
+
const agRes = await cloud.request("GET", `/api/im/workspaces/${encodeURIComponent(wsId)}/agents`, { timeoutMs: 3e3 });
|
|
10451
|
+
if (agRes.ok) {
|
|
10452
|
+
const agBody = agRes.data;
|
|
10453
|
+
agents = agBody?.data;
|
|
10454
|
+
}
|
|
10455
|
+
}
|
|
10456
|
+
}
|
|
10457
|
+
}
|
|
10458
|
+
}
|
|
9802
10459
|
} catch {
|
|
9803
10460
|
cloudOk = false;
|
|
9804
10461
|
}
|
|
@@ -9810,10 +10467,15 @@ function buildStatusCommand() {
|
|
|
9810
10467
|
daemon: {
|
|
9811
10468
|
running: daemonRunning,
|
|
9812
10469
|
pid: daemonStatus.pid ?? pid ?? null,
|
|
9813
|
-
wsConnected: daemonStatus.wsConnected ?? null
|
|
10470
|
+
wsConnected: daemonStatus.wsConnected ?? null,
|
|
10471
|
+
info: daemonStatus.info ?? {}
|
|
9814
10472
|
},
|
|
9815
|
-
cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me },
|
|
9816
|
-
local
|
|
10473
|
+
cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
|
|
10474
|
+
local,
|
|
10475
|
+
binding: {
|
|
10476
|
+
daemonId: cfg.daemon_id,
|
|
10477
|
+
apiKey: cfg.api_key
|
|
10478
|
+
}
|
|
9817
10479
|
};
|
|
9818
10480
|
if (opts.json) {
|
|
9819
10481
|
printJson(report);
|
|
@@ -9827,12 +10489,19 @@ async function readDaemonStatus() {
|
|
|
9827
10489
|
const res = await fetch("http://127.0.0.1:3210/healthz", {
|
|
9828
10490
|
signal: AbortSignal.timeout(1e3)
|
|
9829
10491
|
});
|
|
9830
|
-
if (
|
|
9831
|
-
|
|
9832
|
-
|
|
10492
|
+
if (res.ok) {
|
|
10493
|
+
const data = await res.json();
|
|
10494
|
+
return { running: true, pid: data.pid, wsConnected: data.wsConnected, info: data };
|
|
10495
|
+
}
|
|
9833
10496
|
} catch {
|
|
9834
|
-
return { running: false };
|
|
9835
10497
|
}
|
|
10498
|
+
const paths = resolvePaths();
|
|
10499
|
+
const pid = readPidFile(paths);
|
|
10500
|
+
if (pid) {
|
|
10501
|
+
const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
|
|
10502
|
+
if (pidAlive2(pid)) return { running: true, pid };
|
|
10503
|
+
}
|
|
10504
|
+
return { running: false };
|
|
9836
10505
|
}
|
|
9837
10506
|
function readLocalCounts(localDbPath) {
|
|
9838
10507
|
try {
|
|
@@ -9858,21 +10527,59 @@ function printPretty(report) {
|
|
|
9858
10527
|
ui.blank();
|
|
9859
10528
|
ok("Config", report.paths.config);
|
|
9860
10529
|
if (report.daemon.running) {
|
|
9861
|
-
|
|
10530
|
+
const ws = report.daemon.wsConnected ? "connected" : "pending";
|
|
10531
|
+
ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
|
|
10532
|
+
if (report.daemon.info) {
|
|
10533
|
+
const info2 = report.daemon.info;
|
|
10534
|
+
if (info2.version) ui.line(` Version: ${info2.version}`);
|
|
10535
|
+
if (info2.uptime) ui.line(` Uptime: ${Math.round(info2.uptime / 60)}m`);
|
|
10536
|
+
if (info2.memoryMb) ui.line(` Memory: ${info2.memoryMb} MB`);
|
|
10537
|
+
}
|
|
9862
10538
|
} else {
|
|
9863
10539
|
warn("Daemon", "not running");
|
|
9864
10540
|
tip("prismer daemon start");
|
|
9865
10541
|
}
|
|
9866
|
-
if (report.cloud.reachable)
|
|
9867
|
-
|
|
10542
|
+
if (report.cloud.reachable) {
|
|
10543
|
+
ok("Cloud", report.cloud.base);
|
|
10544
|
+
const me = report.cloud.me;
|
|
10545
|
+
if (me?.user) {
|
|
10546
|
+
const roleTag = me.user.role ? ` role=${me.user.role}` : "";
|
|
10547
|
+
ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
|
|
10548
|
+
}
|
|
10549
|
+
if (me?.credits) {
|
|
10550
|
+
ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
|
|
10551
|
+
}
|
|
10552
|
+
} else {
|
|
9868
10553
|
fail("Cloud", `${report.cloud.base} unreachable or unauthorized`);
|
|
9869
10554
|
tip("prismer setup --force");
|
|
9870
10555
|
}
|
|
10556
|
+
if (report.binding) {
|
|
10557
|
+
ui.blank();
|
|
10558
|
+
ui.line(` Daemon ID: ${report.binding.daemonId}`);
|
|
10559
|
+
const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
|
|
10560
|
+
ui.line(` API Key: ${masked}`);
|
|
10561
|
+
}
|
|
10562
|
+
if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
|
|
10563
|
+
const devs = report.cloud.devices;
|
|
10564
|
+
ui.blank();
|
|
10565
|
+
ui.line(` Workspace Devices (${devs.length}):`);
|
|
10566
|
+
for (const d of devs) {
|
|
10567
|
+
const statusIcon = d.daemonStatus === "connected" ? "\u25CF" : "\u25CB";
|
|
10568
|
+
const kind = d.runtimeKind === "docker" ? "Local" : "K8s";
|
|
10569
|
+
const declared = d.hostedAgentSummary?.declared ?? 0;
|
|
10570
|
+
ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
|
|
10571
|
+
}
|
|
10572
|
+
} else {
|
|
10573
|
+
ui.line(` Workspace Devices: none`);
|
|
10574
|
+
}
|
|
10575
|
+
if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
|
|
10576
|
+
ui.line(` Hosted agents: ${report.cloud.agents.length}`);
|
|
10577
|
+
}
|
|
9871
10578
|
if (report.local) {
|
|
9872
10579
|
ui.blank();
|
|
9873
|
-
ui.line(`
|
|
9874
|
-
ui.line(` Profiles:
|
|
9875
|
-
ui.line(`
|
|
10580
|
+
ui.line(` Local agents: ${report.local.agents}`);
|
|
10581
|
+
ui.line(` Profiles: ${report.local.profiles}`);
|
|
10582
|
+
ui.line(` Running tasks: ${report.local.runningTasks}`);
|
|
9876
10583
|
} else {
|
|
9877
10584
|
warn("Local DB", "unavailable");
|
|
9878
10585
|
}
|
|
@@ -9881,6 +10588,7 @@ function printPretty(report) {
|
|
|
9881
10588
|
// src/cli/commands/task.ts
|
|
9882
10589
|
var import_commander16 = require("commander");
|
|
9883
10590
|
var import_promises3 = require("timers/promises");
|
|
10591
|
+
init_util();
|
|
9884
10592
|
function describeStatus(status) {
|
|
9885
10593
|
return status === 0 ? "network error" : `HTTP ${status}`;
|
|
9886
10594
|
}
|
|
@@ -10000,6 +10708,8 @@ function taskFrom2(raw) {
|
|
|
10000
10708
|
|
|
10001
10709
|
// src/cli/commands/workspace.ts
|
|
10002
10710
|
var import_commander17 = require("commander");
|
|
10711
|
+
init_util();
|
|
10712
|
+
init_ui();
|
|
10003
10713
|
function buildWorkspaceCommand() {
|
|
10004
10714
|
const cmd = new import_commander17.Command("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
|
|
10005
10715
|
cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
@@ -10234,6 +10944,7 @@ async function readResponseError(res) {
|
|
|
10234
10944
|
}
|
|
10235
10945
|
|
|
10236
10946
|
// src/cli/index.ts
|
|
10947
|
+
init_ui();
|
|
10237
10948
|
var VERSION = "1.9.7";
|
|
10238
10949
|
function buildProgram() {
|
|
10239
10950
|
const program = new import_commander18.Command("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
|