@prismer/sdk 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -36,9 +36,9 @@ __export(cli_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(cli_exports);
38
38
  var import_commander = require("commander");
39
- var fs4 = __toESM(require("fs"));
40
- var path4 = __toESM(require("path"));
41
- var os2 = __toESM(require("os"));
39
+ var fs7 = __toESM(require("fs"));
40
+ var path5 = __toESM(require("path"));
41
+ var os3 = __toESM(require("os"));
42
42
  var TOML2 = __toESM(require("@iarna/toml"));
43
43
 
44
44
  // src/realtime.ts
@@ -163,7 +163,7 @@ var RealtimeWSClient = class extends TypedEmitter {
163
163
  if (this._state === "connected" || this._state === "connecting") return;
164
164
  this._state = "connecting";
165
165
  this.intentionalClose = false;
166
- return new Promise((resolve3, reject) => {
166
+ return new Promise((resolve5, reject) => {
167
167
  try {
168
168
  this.ws = new this.WS(this.wsUrl);
169
169
  } catch (err) {
@@ -185,7 +185,7 @@ var RealtimeWSClient = class extends TypedEmitter {
185
185
  this.emit("connected", void 0);
186
186
  this.ws.removeEventListener("message", onFirstMessage);
187
187
  this.ws.addEventListener("message", this.handleMessage);
188
- resolve3();
188
+ resolve5();
189
189
  }
190
190
  } catch (_) {
191
191
  }
@@ -257,12 +257,12 @@ var RealtimeWSClient = class extends TypedEmitter {
257
257
  }
258
258
  ping() {
259
259
  const requestId = `ping-${++this.pingCounter}`;
260
- return new Promise((resolve3, reject) => {
260
+ return new Promise((resolve5, reject) => {
261
261
  const timer = setTimeout(() => {
262
262
  this.pendingPings.delete(requestId);
263
263
  reject(new Error("Ping timeout"));
264
264
  }, 1e4);
265
- this.pendingPings.set(requestId, { resolve: resolve3, timer });
265
+ this.pendingPings.set(requestId, { resolve: resolve5, timer });
266
266
  this.sendRaw({ type: "ping", payload: { requestId } });
267
267
  });
268
268
  }
@@ -515,9 +515,9 @@ var WRITE_PATTERNS = [
515
515
  { method: "POST", pattern: /\/api\/im\/community\/posts\/[^/]+\/comments$/, opType: "community_comment" },
516
516
  { method: "POST", pattern: /\/api\/im\/community\/vote$/, opType: "community_vote" }
517
517
  ];
518
- function matchWriteOp(method, path5) {
518
+ function matchWriteOp(method, path6) {
519
519
  for (const { method: m, pattern, opType } of WRITE_PATTERNS) {
520
- if (method === m && pattern.test(path5)) return opType;
520
+ if (method === m && pattern.test(path6)) return opType;
521
521
  }
522
522
  return null;
523
523
  }
@@ -585,18 +585,18 @@ var OfflineManager = class extends OfflineEmitter {
585
585
  /**
586
586
  * Dispatch an IM request. Write ops go through outbox; reads check local cache.
587
587
  */
588
- async dispatch(method, path5, body, query) {
589
- const opType = matchWriteOp(method, path5);
588
+ async dispatch(method, path6, body, query) {
589
+ const opType = matchWriteOp(method, path6);
590
590
  if (opType) {
591
- return this.dispatchWrite(opType, method, path5, body, query);
591
+ return this.dispatchWrite(opType, method, path6, body, query);
592
592
  }
593
593
  if (method === "GET") {
594
- const cached = await this.readFromCache(path5, query);
594
+ const cached = await this.readFromCache(path6, query);
595
595
  if (cached !== null) return cached;
596
596
  }
597
597
  try {
598
- const result = await this.networkRequest(method, path5, body, query);
599
- if (method === "GET") this.cacheReadResult(path5, query, result);
598
+ const result = await this.networkRequest(method, path6, body, query);
599
+ if (method === "GET") this.cacheReadResult(path6, query, result);
600
600
  return result;
601
601
  } catch {
602
602
  if (!this._isOnline) {
@@ -606,7 +606,7 @@ var OfflineManager = class extends OfflineEmitter {
606
606
  }
607
607
  }
608
608
  // ── Outbox: write operations ──────────────────────────────
609
- async dispatchWrite(opType, method, path5, body, query) {
609
+ async dispatchWrite(opType, method, path6, body, query) {
610
610
  const clientId = generateId();
611
611
  const idempotencyKey = `sdk-${clientId}`;
612
612
  let enrichedBody = body;
@@ -620,7 +620,7 @@ var OfflineManager = class extends OfflineEmitter {
620
620
  let localMessage;
621
621
  if (opType === "message.send" && body && typeof body === "object") {
622
622
  const b = body;
623
- const convIdMatch = path5.match(/\/(?:messages|direct|groups)\/([^/]+)/);
623
+ const convIdMatch = path6.match(/\/(?:messages|direct|groups)\/([^/]+)/);
624
624
  const conversationId = convIdMatch?.[1] ?? "";
625
625
  localMessage = {
626
626
  id: `local-${clientId}`,
@@ -641,7 +641,7 @@ var OfflineManager = class extends OfflineEmitter {
641
641
  id: clientId,
642
642
  type: opType,
643
643
  method,
644
- path: path5,
644
+ path: path6,
645
645
  body: enrichedBody,
646
646
  query,
647
647
  status: "pending",
@@ -961,28 +961,28 @@ var OfflineManager = class extends OfflineEmitter {
961
961
  return 0;
962
962
  }
963
963
  // ── Read cache ────────────────────────────────────────────
964
- async readFromCache(path5, query) {
965
- if (/\/api\/im\/conversations$/.test(path5)) {
964
+ async readFromCache(path6, query) {
965
+ if (/\/api\/im\/conversations$/.test(path6)) {
966
966
  const convos = await this.storage.getConversations({ limit: 50 });
967
967
  if (convos.length > 0) return { ok: true, data: convos };
968
968
  }
969
- const msgMatch = path5.match(/\/api\/im\/messages\/([^/]+)$/);
969
+ const msgMatch = path6.match(/\/api\/im\/messages\/([^/]+)$/);
970
970
  if (msgMatch) {
971
971
  const convId = msgMatch[1];
972
972
  const limit = query?.limit ? parseInt(query.limit) : 50;
973
973
  const messages = await this.storage.getMessages(convId, { limit, before: query?.before });
974
974
  if (messages.length > 0) return { ok: true, data: messages };
975
975
  }
976
- if (/\/api\/im\/contacts$/.test(path5)) {
976
+ if (/\/api\/im\/contacts$/.test(path6)) {
977
977
  const contacts = await this.storage.getContacts();
978
978
  if (contacts.length > 0) return { ok: true, data: contacts };
979
979
  }
980
980
  return null;
981
981
  }
982
- async cacheReadResult(path5, _query, result) {
982
+ async cacheReadResult(path6, _query, result) {
983
983
  if (!result?.ok || !result?.data) return;
984
984
  try {
985
- if (/\/api\/im\/conversations$/.test(path5) && Array.isArray(result.data)) {
985
+ if (/\/api\/im\/conversations$/.test(path6) && Array.isArray(result.data)) {
986
986
  const convos = result.data.map((c) => ({
987
987
  id: c.id,
988
988
  type: c.type ?? "direct",
@@ -996,7 +996,7 @@ var OfflineManager = class extends OfflineEmitter {
996
996
  }));
997
997
  await this.storage.putConversations(convos);
998
998
  }
999
- const msgMatch = path5.match(/\/api\/im\/messages\/([^/]+)$/);
999
+ const msgMatch = path6.match(/\/api\/im\/messages\/([^/]+)$/);
1000
1000
  if (msgMatch && Array.isArray(result.data)) {
1001
1001
  const messages = result.data.map((m) => ({
1002
1002
  id: m.id,
@@ -1011,7 +1011,7 @@ var OfflineManager = class extends OfflineEmitter {
1011
1011
  }));
1012
1012
  await this.storage.putMessages(messages);
1013
1013
  }
1014
- if (/\/api\/im\/contacts$/.test(path5) && Array.isArray(result.data)) {
1014
+ if (/\/api\/im\/contacts$/.test(path6) && Array.isArray(result.data)) {
1015
1015
  await this.storage.putContacts(result.data);
1016
1016
  }
1017
1017
  } catch {
@@ -2157,6 +2157,37 @@ var WorkspaceClient = class {
2157
2157
  var TasksClient = class {
2158
2158
  constructor(_r) {
2159
2159
  this._r = _r;
2160
+ // ── release201/10 rev 2 — SPEC.md ─────────────────────────────────────
2161
+ this.spec = {
2162
+ /** Read SPEC.md latest revision. */
2163
+ get: (taskId) => this._r("GET", `/api/im/tasks/${taskId}/spec`),
2164
+ /** Owner sets/updates SPEC.md (writes a new revision). */
2165
+ set: (taskId, input) => this._r("PUT", `/api/im/tasks/${taskId}/spec`, input)
2166
+ };
2167
+ // ── release201/10 rev 2 — TODO.md ─────────────────────────────────────
2168
+ this.todo = {
2169
+ list: (taskId) => this._r("GET", `/api/im/tasks/${taskId}/todo`),
2170
+ add: (taskId, input) => this._r("POST", `/api/im/tasks/${taskId}/todo/items`, input),
2171
+ toggle: (taskId, index, done) => this._r("PATCH", `/api/im/tasks/${taskId}/todo/items/${index}`, { done }),
2172
+ setText: (taskId, index, text) => this._r("PATCH", `/api/im/tasks/${taskId}/todo/items/${index}`, { text }),
2173
+ remove: (taskId, index) => this._r("DELETE", `/api/im/tasks/${taskId}/todo/items/${index}`)
2174
+ };
2175
+ this.criteria = {
2176
+ /** List current criteria via the acceptance view. */
2177
+ list: (taskId) => this._r("GET", `/api/im/tasks/${taskId}/acceptance`),
2178
+ /** Add a criterion (rev 2 — verifyMode + expectation + verifierAgentId). */
2179
+ add: (taskId, input) => this._r("POST", `/api/im/tasks/${taskId}/criteria`, input),
2180
+ /** Update a criterion in place. */
2181
+ update: (taskId, cid, patch) => this._r("PATCH", `/api/im/tasks/${taskId}/criteria/${cid}`, patch),
2182
+ /** Remove a criterion. */
2183
+ remove: (taskId, cid) => this._r("DELETE", `/api/im/tasks/${taskId}/criteria/${cid}`),
2184
+ /**
2185
+ * Unified verify entry (rev 2). Manual reviewer, agent-self-check
2186
+ * report, AND verifier-agent report all use this — cloud routes by
2187
+ * actor identity.
2188
+ */
2189
+ verify: (taskId, cid, input) => this._r("POST", `/api/im/tasks/${taskId}/criteria/${cid}/verify`, input)
2190
+ };
2160
2191
  }
2161
2192
  /** Create a new task */
2162
2193
  async create(options) {
@@ -2176,6 +2207,7 @@ var TasksClient = class {
2176
2207
  if (options?.limit != null) query.limit = String(options.limit);
2177
2208
  if (options?.cursor) query.cursor = options.cursor;
2178
2209
  if (options?.workspaceId) query.workspaceId = options.workspaceId;
2210
+ if (options?.projectId) query.projectId = options.projectId;
2179
2211
  if (options?.conversationId) query.conversationId = options.conversationId;
2180
2212
  return this._r("GET", "/api/im/tasks", void 0, query);
2181
2213
  }
@@ -2211,6 +2243,10 @@ var TasksClient = class {
2211
2243
  async update(taskId, options) {
2212
2244
  return this._r("PATCH", `/api/im/tasks/${taskId}`, options);
2213
2245
  }
2246
+ /** Move a task into a project, or pass null to return it to workspace-level. */
2247
+ async moveProject(taskId, targetProjectId) {
2248
+ return this._r("POST", `/api/im/tasks/${taskId}/move-project`, { targetProjectId });
2249
+ }
2214
2250
  /** Claim a pending task */
2215
2251
  async claim(taskId) {
2216
2252
  return this._r("POST", `/api/im/tasks/${taskId}/claim`);
@@ -2265,6 +2301,39 @@ var TasksClient = class {
2265
2301
  async forceTransition(taskId, options) {
2266
2302
  return this._r("POST", `/api/im/tasks/${taskId}/force-transition`, options);
2267
2303
  }
2304
+ // ── release201/10 — acceptance criteria ──────────────────────────────
2305
+ /** Get rolled-up acceptance + criteria list for a task. */
2306
+ async getAcceptance(taskId) {
2307
+ return this._r("GET", `/api/im/tasks/${taskId}/acceptance`);
2308
+ }
2309
+ /** Copy a template's criteria onto the task. */
2310
+ async applyTemplate(taskId, templateId) {
2311
+ return this._r("POST", `/api/im/tasks/${taskId}/apply-template`, { templateId });
2312
+ }
2313
+ };
2314
+ var CriteriaTemplatesClient = class {
2315
+ constructor(_r) {
2316
+ this._r = _r;
2317
+ }
2318
+ list(query) {
2319
+ const q = {};
2320
+ if (query?.capability) q.capability = query.capability;
2321
+ if (query?.workspaceId === null) q.workspaceId = "__global";
2322
+ else if (query?.workspaceId) q.workspaceId = query.workspaceId;
2323
+ return this._r("GET", "/api/im/criteria-templates", void 0, q);
2324
+ }
2325
+ get(id) {
2326
+ return this._r("GET", `/api/im/criteria-templates/${id}`);
2327
+ }
2328
+ create(input) {
2329
+ return this._r("POST", "/api/im/criteria-templates", input);
2330
+ }
2331
+ update(id, patch) {
2332
+ return this._r("PATCH", `/api/im/criteria-templates/${id}`, patch);
2333
+ }
2334
+ delete(id) {
2335
+ return this._r("DELETE", `/api/im/criteria-templates/${id}`);
2336
+ }
2268
2337
  };
2269
2338
  var MemoryClient = class {
2270
2339
  constructor(_r) {
@@ -2337,6 +2406,32 @@ var KnowledgeLinkClient = class {
2337
2406
  return this._r("GET", "/api/im/knowledge/links", void 0, { entityType, entityId });
2338
2407
  }
2339
2408
  };
2409
+ var MetricsClient = class {
2410
+ constructor(_r) {
2411
+ this._r = _r;
2412
+ }
2413
+ async emit(input) {
2414
+ return this._r("POST", "/api/im/metrics/emit", input);
2415
+ }
2416
+ async batch(events) {
2417
+ return this._r("POST", "/api/im/metrics/batch", { events });
2418
+ }
2419
+ async aggregate(opts) {
2420
+ const query = {
2421
+ namespace: opts.namespace,
2422
+ name: opts.name,
2423
+ agg: opts.agg
2424
+ };
2425
+ if (opts.range) query.range = opts.range;
2426
+ if (opts.from) query.from = opts.from;
2427
+ if (opts.to) query.to = opts.to;
2428
+ if (opts.groupBy && opts.groupBy.length) query.groupBy = opts.groupBy.join(",");
2429
+ if (opts.bucket) query.bucket = opts.bucket;
2430
+ const filterPairs = Object.entries(opts.filter).map(([k, v]) => `${k}:${v}`);
2431
+ if (filterPairs.length) query.filter = filterPairs.join(",");
2432
+ return this._r("GET", "/api/im/metrics/aggregate", void 0, query);
2433
+ }
2434
+ };
2340
2435
  var IdentityClient = class {
2341
2436
  constructor(_r) {
2342
2437
  this._r = _r;
@@ -2501,9 +2596,51 @@ var EvolutionSkillsClient = class {
2501
2596
  return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
2502
2597
  }
2503
2598
  };
2599
+ var AgentSkillsClient = class {
2600
+ constructor(_r) {
2601
+ this._r = _r;
2602
+ }
2603
+ /** List skills installed on the agent. */
2604
+ async list(agentId, options) {
2605
+ const query = {};
2606
+ if (options?.workspaceId) query.workspaceId = options.workspaceId;
2607
+ if (options?.includeInactive) query.includeInactive = "true";
2608
+ return this._r("GET", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, void 0, query);
2609
+ }
2610
+ /** Install a published skill onto the agent. */
2611
+ async install(agentId, input) {
2612
+ return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/skills`, input);
2613
+ }
2614
+ /** Uninstall (or disable, for built-ins) a skill from the agent. */
2615
+ async uninstall(agentId, skillIdOrSlug, options) {
2616
+ const query = {};
2617
+ if (options?.workspaceId) query.workspaceId = options.workspaceId;
2618
+ return this._r(
2619
+ "DELETE",
2620
+ `/api/im/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skillIdOrSlug)}`,
2621
+ void 0,
2622
+ query
2623
+ );
2624
+ }
2625
+ /**
2626
+ * PATCH /api/im/agents/:agentId/skills/:skillId — Update per-skill config.
2627
+ *
2628
+ * Validates against the skill's `executableJson.configSchema` server-side
2629
+ * (release201/13 §3.4 / 07 §2.6). Returns the updated agentSkill row;
2630
+ * `installedRevision` will be null until the next daemon sync poll.
2631
+ */
2632
+ async updateConfig(agentId, skillId, config, options) {
2633
+ return this._r(
2634
+ "PATCH",
2635
+ `/api/im/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skillId)}`,
2636
+ { config, workspaceId: options?.workspaceId }
2637
+ );
2638
+ }
2639
+ };
2504
2640
  var AgentsClient = class {
2505
2641
  constructor(_r) {
2506
2642
  this._r = _r;
2643
+ this.skills = new AgentSkillsClient(_r);
2507
2644
  }
2508
2645
  async spec(agentId, workspaceId) {
2509
2646
  const query = workspaceId ? { workspaceId } : void 0;
@@ -2540,6 +2677,63 @@ var AgentsClient = class {
2540
2677
  async deletePack(packIdOrSlug) {
2541
2678
  return this._r("DELETE", `/api/im/agent-packs/${encodeURIComponent(packIdOrSlug)}`);
2542
2679
  }
2680
+ // release201/09 §9.7.2 Phase 3 — agent quiesce + transfer endpoints.
2681
+ //
2682
+ // pause/resume: workspace owner / orchestrator / admin only. Used by
2683
+ // `prismer agent export` to halt cloud dispatch on the source device
2684
+ // before tarballing the agent dir, and by `prismer agent import` to
2685
+ // resume dispatch on the target device after rebind.
2686
+ async pause(agentId) {
2687
+ return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/pause`, {});
2688
+ }
2689
+ async resume(agentId) {
2690
+ return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/resume`, {});
2691
+ }
2692
+ async transfer(input) {
2693
+ return this._r("POST", "/api/im/agent-bindings/transfer", input);
2694
+ }
2695
+ };
2696
+ var StudioEvolutionClient = class {
2697
+ constructor(_r) {
2698
+ this._r = _r;
2699
+ }
2700
+ /** List capsules emitted by the target agent (workspace-owner-scoped). */
2701
+ async capsules(agentId, options) {
2702
+ const query = { agentId };
2703
+ if (options?.page != null) query.page = String(options.page);
2704
+ if (options?.limit != null) query.limit = String(options.limit);
2705
+ if (options?.scope) query.scope = options.scope;
2706
+ return this._r("GET", "/api/im/studio/evolution/capsules", void 0, query);
2707
+ }
2708
+ /** List genes owned by the target agent (workspace-owner-scoped). */
2709
+ async genes(agentId, options) {
2710
+ const query = { agentId };
2711
+ if (options?.signals) query.signals = options.signals;
2712
+ return this._r("GET", "/api/im/studio/evolution/genes", void 0, query);
2713
+ }
2714
+ };
2715
+ var StudioClient = class {
2716
+ constructor(_r) {
2717
+ this._r = _r;
2718
+ this.evolution = new StudioEvolutionClient(_r);
2719
+ }
2720
+ /** Studio overview — counts + recent activity for a workspace. */
2721
+ async getOverview(workspaceId) {
2722
+ const query = workspaceId ? { workspaceId } : void 0;
2723
+ return this._r("GET", "/api/im/studio/overview", void 0, query);
2724
+ }
2725
+ /** Studio Profile domain — agent identity / personality / credits. */
2726
+ async getProfile(agentId) {
2727
+ const query = agentId ? { agentId } : void 0;
2728
+ return this._r("GET", "/api/im/studio/profile", void 0, query);
2729
+ }
2730
+ /** Studio Installed domain — workspace agents + active agent's skills. */
2731
+ async getInstalled(options) {
2732
+ const query = {};
2733
+ if (options?.workspaceId) query.workspaceId = options.workspaceId;
2734
+ if (options?.agentId) query.agentId = options.agentId;
2735
+ return this._r("GET", "/api/im/studio/installed", void 0, query);
2736
+ }
2543
2737
  };
2544
2738
  var EvolutionClient = class {
2545
2739
  constructor(_r) {
@@ -2849,30 +3043,30 @@ var EvolutionClient = class {
2849
3043
  }
2850
3044
  const localPaths = [];
2851
3045
  try {
2852
- const fs5 = await import("fs");
2853
- const path5 = await import("path");
2854
- const os3 = await import("os");
2855
- const home = os3.homedir();
2856
- const pluginBase = process.env.PRISMER_PLUGIN_DIR || path5.join(home, ".claude", "plugins", "prismer");
3046
+ const fs8 = await import("fs");
3047
+ const path6 = await import("path");
3048
+ const os4 = await import("os");
3049
+ const home = os4.homedir();
3050
+ const pluginBase = process.env.PRISMER_PLUGIN_DIR || path6.join(home, ".claude", "plugins", "prismer");
2857
3051
  const platformPaths = options?.project ? {
2858
- "claude-code": path5.join(options.projectRoot || ".", ".claude", "skills", slug),
2859
- "openclaw": path5.join(options.projectRoot || ".", "skills", slug),
2860
- "opencode": path5.join(options.projectRoot || ".", ".opencode", "skills", slug),
2861
- "plugin": path5.join(options.projectRoot || ".", ".claude", "plugins", "prismer", "skills", slug)
3052
+ "claude-code": path6.join(options.projectRoot || ".", ".claude", "skills", slug),
3053
+ "openclaw": path6.join(options.projectRoot || ".", "skills", slug),
3054
+ "opencode": path6.join(options.projectRoot || ".", ".opencode", "skills", slug),
3055
+ "plugin": path6.join(options.projectRoot || ".", ".claude", "plugins", "prismer", "skills", slug)
2862
3056
  } : {
2863
- "claude-code": path5.join(home, ".claude", "skills", slug),
2864
- "openclaw": path5.join(home, ".openclaw", "skills", slug),
2865
- "opencode": path5.join(home, ".config", "opencode", "skills", slug),
2866
- "plugin": path5.join(pluginBase, "skills", slug)
3057
+ "claude-code": path6.join(home, ".claude", "skills", slug),
3058
+ "openclaw": path6.join(home, ".openclaw", "skills", slug),
3059
+ "opencode": path6.join(home, ".config", "opencode", "skills", slug),
3060
+ "plugin": path6.join(pluginBase, "skills", slug)
2867
3061
  };
2868
3062
  const targets = options?.platforms ?? Object.keys(platformPaths);
2869
3063
  for (const platform of targets) {
2870
3064
  const dir = platformPaths[platform];
2871
3065
  if (!dir) continue;
2872
3066
  try {
2873
- fs5.mkdirSync(dir, { recursive: true });
2874
- const filePath = path5.join(dir, "SKILL.md");
2875
- fs5.writeFileSync(filePath, content, "utf-8");
3067
+ fs8.mkdirSync(dir, { recursive: true });
3068
+ const filePath = path6.join(dir, "SKILL.md");
3069
+ fs8.writeFileSync(filePath, content, "utf-8");
2876
3070
  localPaths.push(filePath);
2877
3071
  } catch {
2878
3072
  }
@@ -2895,21 +3089,21 @@ var EvolutionClient = class {
2895
3089
  const slug = safeSlug(slugOrId);
2896
3090
  if (!slug) return withRemoved(result.data?.uninstalled ?? false, removedPaths);
2897
3091
  try {
2898
- const fs5 = await import("fs");
2899
- const path5 = await import("path");
2900
- const os3 = await import("os");
2901
- const home = os3.homedir();
2902
- const pluginBase = process.env.PRISMER_PLUGIN_DIR || path5.join(home, ".claude", "plugins", "prismer");
3092
+ const fs8 = await import("fs");
3093
+ const path6 = await import("path");
3094
+ const os4 = await import("os");
3095
+ const home = os4.homedir();
3096
+ const pluginBase = process.env.PRISMER_PLUGIN_DIR || path6.join(home, ".claude", "plugins", "prismer");
2903
3097
  const dirs = [
2904
- path5.join(home, ".claude", "skills", slug),
2905
- path5.join(home, ".openclaw", "skills", slug),
2906
- path5.join(home, ".config", "opencode", "skills", slug),
2907
- path5.join(pluginBase, "skills", slug)
3098
+ path6.join(home, ".claude", "skills", slug),
3099
+ path6.join(home, ".openclaw", "skills", slug),
3100
+ path6.join(home, ".config", "opencode", "skills", slug),
3101
+ path6.join(pluginBase, "skills", slug)
2908
3102
  ];
2909
3103
  for (const dir of dirs) {
2910
3104
  try {
2911
- if (fs5.existsSync(dir)) {
2912
- fs5.rmSync(dir, { recursive: true });
3105
+ if (fs8.existsSync(dir)) {
3106
+ fs8.rmSync(dir, { recursive: true });
2913
3107
  removedPaths.push(dir);
2914
3108
  }
2915
3109
  } catch {
@@ -2946,25 +3140,25 @@ var EvolutionClient = class {
2946
3140
  failed++;
2947
3141
  continue;
2948
3142
  }
2949
- const fs5 = await import("fs");
2950
- const path5 = await import("path");
2951
- const os3 = await import("os");
2952
- const home = os3.homedir();
2953
- const pluginBase = process.env.PRISMER_PLUGIN_DIR || path5.join(home, ".claude", "plugins", "prismer");
3143
+ const fs8 = await import("fs");
3144
+ const path6 = await import("path");
3145
+ const os4 = await import("os");
3146
+ const home = os4.homedir();
3147
+ const pluginBase = process.env.PRISMER_PLUGIN_DIR || path6.join(home, ".claude", "plugins", "prismer");
2954
3148
  const platformPaths = {
2955
- "claude-code": path5.join(home, ".claude", "skills", slug),
2956
- "openclaw": path5.join(home, ".openclaw", "skills", slug),
2957
- "opencode": path5.join(home, ".config", "opencode", "skills", slug),
2958
- "plugin": path5.join(pluginBase, "skills", slug)
3149
+ "claude-code": path6.join(home, ".claude", "skills", slug),
3150
+ "openclaw": path6.join(home, ".openclaw", "skills", slug),
3151
+ "opencode": path6.join(home, ".config", "opencode", "skills", slug),
3152
+ "plugin": path6.join(pluginBase, "skills", slug)
2959
3153
  };
2960
3154
  const targets = options?.platforms ?? Object.keys(platformPaths);
2961
3155
  for (const platform of targets) {
2962
3156
  const dir = platformPaths[platform];
2963
3157
  if (!dir) continue;
2964
3158
  try {
2965
- fs5.mkdirSync(dir, { recursive: true });
2966
- const filePath = path5.join(dir, "SKILL.md");
2967
- fs5.writeFileSync(filePath, content, "utf-8");
3159
+ fs8.mkdirSync(dir, { recursive: true });
3160
+ const filePath = path6.join(dir, "SKILL.md");
3161
+ fs8.writeFileSync(filePath, content, "utf-8");
2968
3162
  paths.push(filePath);
2969
3163
  } catch {
2970
3164
  }
@@ -3042,6 +3236,8 @@ function safeSlug(input) {
3042
3236
  var WorkspacesClient = class {
3043
3237
  constructor(_r) {
3044
3238
  this._r = _r;
3239
+ this.members = new WorkspaceMembersClient(_r);
3240
+ this.invites = new WorkspaceInvitesClient(_r);
3045
3241
  }
3046
3242
  /** List active workspaces owned by the caller. */
3047
3243
  async list() {
@@ -3097,6 +3293,101 @@ var WorkspacesClient = class {
3097
3293
  async revokeOrchestrator(workspaceId) {
3098
3294
  return this._r("DELETE", `/api/im/workspaces/${workspaceId}/orchestrator`);
3099
3295
  }
3296
+ // Sub-clients `members` / `invites` are declared at the top of the class and
3297
+ // wired up inside the constructor body — see `AgentsClient` for the same
3298
+ // pattern. Declaring them as field initializers that reference `this._r`
3299
+ // hits TS2729 because parameter-property assignment happens after class
3300
+ // field initializers under `useDefineForClassFields`.
3301
+ };
3302
+ var WorkspaceMembersClient = class {
3303
+ constructor(_r) {
3304
+ this._r = _r;
3305
+ }
3306
+ async list(workspaceId) {
3307
+ return this._r("GET", `/api/im/workspaces/${workspaceId}/members`);
3308
+ }
3309
+ async add(workspaceId, options) {
3310
+ return this._r("POST", `/api/im/workspaces/${workspaceId}/members`, options);
3311
+ }
3312
+ async update(workspaceId, memberId, options) {
3313
+ return this._r("PATCH", `/api/im/workspaces/${workspaceId}/members/${memberId}`, options);
3314
+ }
3315
+ async remove(workspaceId, memberId) {
3316
+ return this._r("DELETE", `/api/im/workspaces/${workspaceId}/members/${memberId}`);
3317
+ }
3318
+ };
3319
+ var WorkspaceInvitesClient = class {
3320
+ constructor(_r) {
3321
+ this._r = _r;
3322
+ }
3323
+ async create(workspaceId, options) {
3324
+ return this._r("POST", `/api/im/workspaces/${workspaceId}/invites`, options);
3325
+ }
3326
+ async list(workspaceId) {
3327
+ return this._r("GET", `/api/im/workspaces/${workspaceId}/invites`);
3328
+ }
3329
+ async revoke(workspaceId, inviteId) {
3330
+ return this._r("DELETE", `/api/im/workspaces/${workspaceId}/invites/${inviteId}`);
3331
+ }
3332
+ };
3333
+ var InvitesClient = class {
3334
+ constructor(_r) {
3335
+ this._r = _r;
3336
+ }
3337
+ async preview(token) {
3338
+ return this._r("GET", `/api/im/invites/${encodeURIComponent(token)}`);
3339
+ }
3340
+ async accept(token) {
3341
+ return this._r("POST", `/api/im/invites/${encodeURIComponent(token)}/accept`);
3342
+ }
3343
+ async reject(token) {
3344
+ return this._r("POST", `/api/im/invites/${encodeURIComponent(token)}/reject`);
3345
+ }
3346
+ };
3347
+ var ProjectMembersClient = class {
3348
+ constructor(_r) {
3349
+ this._r = _r;
3350
+ }
3351
+ async list(projectId) {
3352
+ return this._r("GET", `/api/im/projects/${projectId}/members`);
3353
+ }
3354
+ async add(projectId, options) {
3355
+ return this._r("POST", `/api/im/projects/${projectId}/members`, options);
3356
+ }
3357
+ async update(projectId, membershipId, options) {
3358
+ return this._r("PATCH", `/api/im/projects/${projectId}/members/${membershipId}`, options);
3359
+ }
3360
+ async remove(projectId, membershipId) {
3361
+ return this._r("DELETE", `/api/im/projects/${projectId}/members/${membershipId}`);
3362
+ }
3363
+ };
3364
+ var ProjectsClient = class {
3365
+ constructor(_r) {
3366
+ this._r = _r;
3367
+ this.members = new ProjectMembersClient(_r);
3368
+ }
3369
+ async list(options) {
3370
+ const query = { workspaceId: options.workspaceId };
3371
+ if (options.status) query.status = options.status;
3372
+ if (options.search) query.search = options.search;
3373
+ if (options.limit !== void 0) query.limit = String(options.limit);
3374
+ if (options.offset !== void 0) query.offset = String(options.offset);
3375
+ return this._r("GET", "/api/im/projects", void 0, query);
3376
+ }
3377
+ async create(options) {
3378
+ return this._r("POST", "/api/im/projects", options);
3379
+ }
3380
+ async get(projectId) {
3381
+ return this._r("GET", `/api/im/projects/${projectId}`);
3382
+ }
3383
+ async update(projectId, options) {
3384
+ return this._r("PATCH", `/api/im/projects/${projectId}`, options);
3385
+ }
3386
+ async delete(projectId, options) {
3387
+ const query = {};
3388
+ if (options?.cascade) query.cascade = options.cascade;
3389
+ return this._r("DELETE", `/api/im/projects/${projectId}`, void 0, query);
3390
+ }
3100
3391
  };
3101
3392
  var WorkspaceFilesClient = class {
3102
3393
  constructor(_r) {
@@ -3116,8 +3407,8 @@ var WorkspaceFilesClient = class {
3116
3407
  return this._r("POST", `/api/im/workspaces/${workspaceId}/files`, options);
3117
3408
  }
3118
3409
  /** Soft-delete the active binding at `path`. */
3119
- async delete(workspaceId, path5) {
3120
- return this._r("DELETE", `/api/im/workspaces/${workspaceId}/files`, void 0, { path: path5 });
3410
+ async delete(workspaceId, path6) {
3411
+ return this._r("DELETE", `/api/im/workspaces/${workspaceId}/files`, void 0, { path: path6 });
3121
3412
  }
3122
3413
  /** Daemon delta-sync workspace files since an ISO timestamp. */
3123
3414
  async sync(workspaceId, since) {
@@ -3142,8 +3433,8 @@ async function sha256BytesHex(bytes) {
3142
3433
  const digest = await globalThis.crypto.subtle.digest("SHA-256", ab);
3143
3434
  return toHex(new Uint8Array(digest));
3144
3435
  }
3145
- const { createHash } = await import("crypto");
3146
- return createHash("sha256").update(bytes).digest("hex");
3436
+ const { createHash: createHash4 } = await import("crypto");
3437
+ return createHash4("sha256").update(bytes).digest("hex");
3147
3438
  }
3148
3439
  function bytesToBlob(bytes, mimeType) {
3149
3440
  const ab = new ArrayBuffer(bytes.byteLength);
@@ -3182,11 +3473,11 @@ async function normalizeAssetUploadInput(input, options) {
3182
3473
  let bytes;
3183
3474
  let fileName;
3184
3475
  if (typeof input === "string") {
3185
- const fs5 = await import("fs");
3186
- const path5 = await import("path");
3187
- const buf = await fs5.promises.readFile(input);
3476
+ const fs8 = await import("fs");
3477
+ const path6 = await import("path");
3478
+ const buf = await fs8.promises.readFile(input);
3188
3479
  bytes = new Uint8Array(buf);
3189
- fileName = options.fileName || path5.basename(input);
3480
+ fileName = options.fileName || path6.basename(input);
3190
3481
  } else if (typeof Blob !== "undefined" && input instanceof Blob) {
3191
3482
  const ab = await input.arrayBuffer();
3192
3483
  bytes = new Uint8Array(ab);
@@ -3331,8 +3622,8 @@ var AssetsClient = class {
3331
3622
  return null;
3332
3623
  }
3333
3624
  }
3334
- async _postAssetJson(path5, body) {
3335
- const response = await this._fetchFn(`${this._baseUrl}/api/im/assets${path5}`, {
3625
+ async _postAssetJson(path6, body) {
3626
+ const response = await this._fetchFn(`${this._baseUrl}/api/im/assets${path6}`, {
3336
3627
  method: "POST",
3337
3628
  headers: {
3338
3629
  ...this._getAuthHeaders(),
@@ -3404,10 +3695,18 @@ var RuntimeInstallationsClient = class {
3404
3695
  constructor(_r) {
3405
3696
  this._r = _r;
3406
3697
  }
3407
- /** List runtime installations in a workspace. */
3698
+ /**
3699
+ * List runtime installations in a workspace.
3700
+ *
3701
+ * v2.0.8 M448 (release201/20 §1) — `projectId` narrows scope:
3702
+ * - omitted | `'all'` → no project filter (legacy behaviour)
3703
+ * - `'__unscoped'` → only workspace-level rows (projectId IS NULL)
3704
+ * - any other string → exact projectId match
3705
+ */
3408
3706
  async list(workspaceId, options) {
3409
3707
  const query = { workspaceId };
3410
3708
  if (options?.limit != null) query.limit = String(options.limit);
3709
+ if (options?.projectId) query.projectId = options.projectId;
3411
3710
  return this._r("GET", "/api/workspace/runtime-installations", void 0, query);
3412
3711
  }
3413
3712
  /**
@@ -3416,10 +3715,27 @@ var RuntimeInstallationsClient = class {
3416
3715
  * The daemon receives `PRISMER_API_KEY`, `PRISMER_DAEMON_ID`,
3417
3716
  * `PRISMER_BASE_URL`, `PRISMER_WORKSPACE_ID`, and
3418
3717
  * `PRISMER_RUNTIME_KIND=workspace-daemon` env vars.
3718
+ *
3719
+ * v2.0.8 M448 — `options.projectId` (optional) opts into project scope.
3720
+ * Server validates it belongs to the same workspace and is `active`,
3721
+ * otherwise rejects 422 `PROJECT_WORKSPACE_MISMATCH` / `PROJECT_NOT_ACTIVE`.
3419
3722
  */
3420
3723
  async create(options) {
3421
3724
  return this._r("POST", "/api/workspace/runtime-installations", options);
3422
3725
  }
3726
+ /**
3727
+ * v2.0.8 M448 (release201/20 §1) — update the project binding on an
3728
+ * existing runtime installation. Pass `projectId: null` to detach.
3729
+ * No-op when body omits `projectId`. Same workspace/active invariant as
3730
+ * create — returns 422 on mismatch.
3731
+ */
3732
+ async patch(runtimeInstallationId, options) {
3733
+ return this._r(
3734
+ "PATCH",
3735
+ `/api/workspace/runtime-installations/${runtimeInstallationId}`,
3736
+ options
3737
+ );
3738
+ }
3423
3739
  /**
3424
3740
  * Install an agent onto a runtime daemon. Resolves or creates the agent
3425
3741
  * profile, calls the controller's `installAgent` RPC, and stamps
@@ -3516,11 +3832,11 @@ var FilesClient = class {
3516
3832
  let bytes;
3517
3833
  let fileName;
3518
3834
  if (typeof input === "string") {
3519
- const fs5 = await import("fs");
3520
- const path5 = await import("path");
3521
- const buf = await fs5.promises.readFile(input);
3835
+ const fs8 = await import("fs");
3836
+ const path6 = await import("path");
3837
+ const buf = await fs8.promises.readFile(input);
3522
3838
  bytes = new Uint8Array(buf);
3523
- fileName = opts?.fileName || path5.basename(input);
3839
+ fileName = opts?.fileName || path6.basename(input);
3524
3840
  } else if (typeof Blob !== "undefined" && input instanceof Blob) {
3525
3841
  const ab = await input.arrayBuffer();
3526
3842
  bytes = new Uint8Array(ab);
@@ -3760,6 +4076,36 @@ var IMRealtimeClient = class {
3760
4076
  };
3761
4077
  }
3762
4078
  };
4079
+ var SkillsDraftClient = class {
4080
+ constructor(_r) {
4081
+ this._r = _r;
4082
+ }
4083
+ /** Create a draft skill from a manifest v1 payload. */
4084
+ async create(input) {
4085
+ return this._r("POST", "/api/im/skills/draft", input);
4086
+ }
4087
+ /** Apply incremental file ops to a draft. */
4088
+ async patch(id, input) {
4089
+ return this._r("PATCH", `/api/im/skills/${id}/draft`, input);
4090
+ }
4091
+ /** Request a regenerate session for a draft. */
4092
+ async regenerate(id, input) {
4093
+ return this._r("POST", `/api/im/skills/${id}/draft/regenerate`, input);
4094
+ }
4095
+ /** Fetch a single draft's manifest + revision history. */
4096
+ async show(id) {
4097
+ return this._r("GET", `/api/im/skills/draft/${id}`);
4098
+ }
4099
+ /** List drafts for a workspace (most-recently-updated first). */
4100
+ async list(workspaceId) {
4101
+ return this._r("GET", "/api/im/skills/drafts", void 0, { workspaceId });
4102
+ }
4103
+ };
4104
+ var SkillsClient = class {
4105
+ constructor(_r) {
4106
+ this.draft = new SkillsDraftClient(_r);
4107
+ }
4108
+ };
3763
4109
  var IMClient = class {
3764
4110
  constructor(request2, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
3765
4111
  this._request = request2;
@@ -3773,16 +4119,22 @@ var IMClient = class {
3773
4119
  this.credits = new CreditsClient(request2);
3774
4120
  this.workspace = new WorkspaceClient(request2);
3775
4121
  this.workspaces = new WorkspacesClient(request2);
4122
+ this.invites = new InvitesClient(request2);
4123
+ this.projects = new ProjectsClient(request2);
3776
4124
  this.workspaceFiles = new WorkspaceFilesClient(request2);
3777
4125
  this.assets = new AssetsClient(request2, wsBase, fetchFn, getAuthHeaders);
3778
4126
  this.runtimeInstallations = new RuntimeInstallationsClient(request2);
3779
4127
  this.tasks = new TasksClient(request2);
4128
+ this.criteriaTemplates = new CriteriaTemplatesClient(request2);
3780
4129
  this.memory = new MemoryClient(request2);
3781
4130
  this.knowledge = new KnowledgeLinkClient(request2);
4131
+ this.metrics = new MetricsClient(request2);
3782
4132
  this.identity = new IdentityClient(request2);
3783
4133
  this.security = new SecurityClient(request2);
3784
4134
  this.agents = new AgentsClient(request2);
3785
4135
  this.evolution = new EvolutionClient(request2);
4136
+ this.skills = new SkillsClient(request2);
4137
+ this.studio = new StudioClient(request2);
3786
4138
  this.community = new CommunityHub(request2, communityHubConfig ?? void 0);
3787
4139
  this.files = new FilesClient(request2, wsBase, fetchFn, getAuthHeaders);
3788
4140
  this.realtime = new IMRealtimeClient(wsBase, fetchFn);
@@ -3812,8 +4164,8 @@ var IMClient = class {
3812
4164
  * 'POST', '/api/im/approvals', { category, title, context, options },
3813
4165
  * );
3814
4166
  */
3815
- async request(method, path5, body, query) {
3816
- return this._request(method, path5, body, query);
4167
+ async request(method, path6, body, query) {
4168
+ return this._request(method, path6, body, query);
3817
4169
  }
3818
4170
  };
3819
4171
  var PrismerClient = class {
@@ -3865,20 +4217,20 @@ var PrismerClient = class {
3865
4217
  ) : (m, p, b, q, opts) => this._request(m, p, b, q, opts);
3866
4218
  if (config.identity) {
3867
4219
  const baseRequest = imRequest;
3868
- imRequest = (method, path5, body, query, opts) => {
3869
- if (method === "POST" && path5.includes("/messages") && body) {
4220
+ imRequest = (method, path6, body, query, opts) => {
4221
+ if (method === "POST" && path6.includes("/messages") && body) {
3870
4222
  const b = body;
3871
4223
  if (!b.signature && !b.skipSigning) {
3872
4224
  const ready = this._identityReady || Promise.resolve();
3873
4225
  return ready.then(() => {
3874
4226
  if (this._identity) {
3875
- return this._signAndSend(baseRequest, method, path5, b, query, opts);
4227
+ return this._signAndSend(baseRequest, method, path6, b, query, opts);
3876
4228
  }
3877
- return baseRequest(method, path5, body, query, opts);
4229
+ return baseRequest(method, path6, body, query, opts);
3878
4230
  });
3879
4231
  }
3880
4232
  }
3881
- return baseRequest(method, path5, body, query, opts);
4233
+ return baseRequest(method, path6, body, query, opts);
3882
4234
  };
3883
4235
  }
3884
4236
  this.im = new IMClient(
@@ -3890,6 +4242,8 @@ var PrismerClient = class {
3890
4242
  config.community ?? null
3891
4243
  );
3892
4244
  this.workspaces = this.im.workspaces;
4245
+ this.invites = this.im.invites;
4246
+ this.projects = this.im.projects;
3893
4247
  this.workspaceFiles = this.im.workspaceFiles;
3894
4248
  this.assets = this.im.assets;
3895
4249
  this.evolution = this.im.evolution;
@@ -3900,9 +4254,9 @@ var PrismerClient = class {
3900
4254
  return this._identity;
3901
4255
  }
3902
4256
  /** Auto-sign a message body and send (v1.8.0 S1) */
3903
- async _signAndSend(baseRequest, method, path5, body, query, opts) {
4257
+ async _signAndSend(baseRequest, method, path6, body, query, opts) {
3904
4258
  if (this._identityReady) await this._identityReady;
3905
- if (!this._identity) return baseRequest(method, path5, body, query, opts);
4259
+ if (!this._identity) return baseRequest(method, path6, body, query, opts);
3906
4260
  const content = body.content || "";
3907
4261
  const contentHashBytes = new Uint8Array(
3908
4262
  await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
@@ -3912,7 +4266,7 @@ var PrismerClient = class {
3912
4266
  const payload = `1|${this._identity.did}|${body.type || "text"}|${timestamp}|${contentHash}`;
3913
4267
  const payloadBytes = new TextEncoder().encode(payload);
3914
4268
  const signature = await this._identity.sign(payloadBytes);
3915
- return baseRequest(method, path5, {
4269
+ return baseRequest(method, path6, {
3916
4270
  ...body,
3917
4271
  secVersion: 1,
3918
4272
  senderDid: this._identity.did,
@@ -3977,11 +4331,11 @@ var PrismerClient = class {
3977
4331
  // --------------------------------------------------------------------------
3978
4332
  // Internal request helper
3979
4333
  // --------------------------------------------------------------------------
3980
- async _request(method, path5, body, query, opts, _isRetry) {
4334
+ async _request(method, path6, body, query, opts, _isRetry) {
3981
4335
  const controller = new AbortController();
3982
4336
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3983
4337
  try {
3984
- let url = `${this.baseUrl}${path5}`;
4338
+ let url = `${this.baseUrl}${path6}`;
3985
4339
  if (query && Object.keys(query).length > 0) {
3986
4340
  url += "?" + new URLSearchParams(query).toString();
3987
4341
  }
@@ -4002,12 +4356,12 @@ var PrismerClient = class {
4002
4356
  }
4003
4357
  const response = await this.fetchFn(url, init);
4004
4358
  const data = await response.json();
4005
- if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path5.includes("/token/refresh")) {
4359
+ if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path6.includes("/token/refresh")) {
4006
4360
  try {
4007
4361
  const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, void 0, true);
4008
4362
  if (refreshRes?.ok && refreshRes?.data?.token) {
4009
4363
  this.apiKey = refreshRes.data.token;
4010
- return this._request(method, path5, body, query, opts, true);
4364
+ return this._request(method, path6, body, query, opts, true);
4011
4365
  }
4012
4366
  } catch {
4013
4367
  }
@@ -5360,7 +5714,7 @@ function register3(parent, getIMClient2, _getAPIClient) {
5360
5714
  const maxIterations = 30;
5361
5715
  let lastStatus;
5362
5716
  for (let i = 0; i < maxIterations; i++) {
5363
- await new Promise((resolve3) => setTimeout(resolve3, 2e3));
5717
+ await new Promise((resolve5) => setTimeout(resolve5, 2e3));
5364
5718
  if (!opts.json) process.stdout.write(".");
5365
5719
  const statusRes = await client.im.evolution.getReportStatus(traceId);
5366
5720
  if (!statusRes.ok) break;
@@ -5743,6 +6097,9 @@ function register3(parent, getIMClient2, _getAPIClient) {
5743
6097
  }
5744
6098
 
5745
6099
  // src/commands/task.ts
6100
+ var import_node_fs = require("fs");
6101
+ var import_node_path = require("path");
6102
+ var import_node_crypto = require("crypto");
5746
6103
  var TASK_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running", "review", "completed", "failed", "cancelled"]);
5747
6104
  var TASK_PRIORITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "urgent"]);
5748
6105
  var TASK_KINDS = /* @__PURE__ */ new Set(["work_item", "goal"]);
@@ -5751,6 +6108,67 @@ function parseTaskStatus(raw) {
5751
6108
  if (TASK_STATUSES.has(raw)) return raw;
5752
6109
  throw new Error(`Invalid task status "${raw}".`);
5753
6110
  }
6111
+ function normalizeProjectForCreate(raw) {
6112
+ const value = raw ?? process.env.PRISMER_ACTIVE_PROJECT_ID;
6113
+ if (!value) return void 0;
6114
+ const trimmed = value.trim();
6115
+ if (!trimmed || trimmed === "all") return void 0;
6116
+ if (trimmed === "__unscoped" || trimmed === "_unscoped" || trimmed === "none" || trimmed === "null") return null;
6117
+ return trimmed;
6118
+ }
6119
+ function normalizeProjectForList(raw) {
6120
+ const value = raw ?? process.env.PRISMER_ACTIVE_PROJECT_ID;
6121
+ if (!value) return void 0;
6122
+ const trimmed = value.trim();
6123
+ if (!trimmed) return void 0;
6124
+ if (trimmed === "_unscoped" || trimmed === "none" || trimmed === "null") return "__unscoped";
6125
+ return trimmed;
6126
+ }
6127
+ async function runReviewCheckpoint(taskId, toStatus) {
6128
+ const port = process.env.PRISMER_DAEMON_PORT ?? "3210";
6129
+ const url = `http://127.0.0.1:${port}/v1/checkpoints/pre_status_change`;
6130
+ let response;
6131
+ try {
6132
+ response = await fetch(url, {
6133
+ method: "POST",
6134
+ headers: { "Content-Type": "application/json" },
6135
+ body: JSON.stringify({ taskId, toStatus })
6136
+ });
6137
+ } catch (err) {
6138
+ return {
6139
+ ok: false,
6140
+ indeterminate: true,
6141
+ message: `daemon unreachable at ${url}: ${err instanceof Error ? err.message : String(err)}`
6142
+ };
6143
+ }
6144
+ let body;
6145
+ try {
6146
+ body = await response.json();
6147
+ } catch {
6148
+ body = {};
6149
+ }
6150
+ if (response.status === 200 && body.ok === true) {
6151
+ return { ok: true };
6152
+ }
6153
+ if (response.status === 409 && Array.isArray(body.pendingFiles)) {
6154
+ return {
6155
+ ok: false,
6156
+ pendingFiles: body.pendingFiles,
6157
+ message: body.error?.message ?? "pending_attach"
6158
+ };
6159
+ }
6160
+ if (response.status === 503) {
6161
+ return {
6162
+ ok: false,
6163
+ indeterminate: true,
6164
+ message: body.error?.message ?? `daemon returned 503`
6165
+ };
6166
+ }
6167
+ return {
6168
+ ok: false,
6169
+ message: body.error?.message ?? `daemon returned ${response.status}`
6170
+ };
6171
+ }
5754
6172
  async function resolveAssigneeId(client, name) {
5755
6173
  const needle = name.trim().toLowerCase();
5756
6174
  const normalized = needle.replace(/^@/, "");
@@ -5773,7 +6191,7 @@ async function resolveAssigneeId(client, name) {
5773
6191
  }
5774
6192
  function register4(parent, getIMClient2, _getAPIClient) {
5775
6193
  const task = parent.command("task").description("Manage tasks in the task marketplace");
5776
- task.command("create").description("Create a new task").requiredOption("--title <title>", "task title").option("--description <description>", "task description").option("--capability <capability>", "required agent capability").option("--budget <budget>", "budget in credits", parseFloat).option("--reward <reward>", "alias for --budget (credits offered for completion)", parseFloat).option("--priority <priority>", "task priority: low | medium | high | urgent").option("--assignee-id <imUserId>", "directly assign to a specific agent by IM user id").option("--assignee-name <name>", "assign by @username / display name (resolved via discover)").option("--conversation-id <id>", "pin task to a conversation/session").option("--kind <kind>", "board projection kind: work_item (default) | goal", "work_item").option("--schedule-at <iso>", "one-shot ISO 8601 scheduled time (sets scheduleType=once)").option("--schedule-cron <expr>", "cron expression (sets scheduleType=cron)").option("--json", "output raw JSON response").action(async (opts) => {
6194
+ task.command("create").description("Create a new task").requiredOption("--title <title>", "task title").option("--description <description>", "task description").option("--capability <capability>", "required agent capability").option("--budget <budget>", "budget in credits", parseFloat).option("--reward <reward>", "alias for --budget (credits offered for completion)", parseFloat).option("--priority <priority>", "task priority: low | medium | high | urgent").option("--assignee-id <imUserId>", "directly assign to a specific agent by IM user id").option("--assignee-name <name>", "assign by @username / display name (resolved via discover)").option("--conversation-id <id>", "pin task to a conversation/session").option("--project <id>", "scope task to project id; use __unscoped/none for workspace-level").option("--kind <kind>", "board projection kind: work_item (default) | goal", "work_item").option("--schedule-at <iso>", "one-shot ISO 8601 scheduled time (sets scheduleType=once)").option("--schedule-cron <expr>", "cron expression (sets scheduleType=cron)").option("--json", "output raw JSON response").action(async (opts) => {
5777
6195
  const client = getIMClient2();
5778
6196
  try {
5779
6197
  if (opts.priority && !TASK_PRIORITIES.has(opts.priority)) {
@@ -5817,6 +6235,8 @@ function register4(parent, getIMClient2, _getAPIClient) {
5817
6235
  };
5818
6236
  if (assigneeId) createOpts.assigneeId = assigneeId;
5819
6237
  if (opts.conversationId) createOpts.conversationId = opts.conversationId;
6238
+ const projectId = normalizeProjectForCreate(opts.project);
6239
+ if (projectId !== void 0) createOpts.projectId = projectId;
5820
6240
  if (opts.scheduleAt) {
5821
6241
  createOpts.scheduleType = "once";
5822
6242
  createOpts.scheduleAt = opts.scheduleAt;
@@ -5858,12 +6278,13 @@ function register4(parent, getIMClient2, _getAPIClient) {
5858
6278
  process.exit(1);
5859
6279
  }
5860
6280
  });
5861
- task.command("list").description("List tasks").option("--status <status>", "filter by status").option("--capability <capability>", "filter by required capability").option("-n, --limit <n>", "maximum number of tasks to return", "20").option("--json", "output raw JSON response").action(async (opts) => {
6281
+ task.command("list").description("List tasks").option("--status <status>", "filter by status").option("--capability <capability>", "filter by required capability").option("--project <id>", "filter by project id; use all or __unscoped").option("-n, --limit <n>", "maximum number of tasks to return", "20").option("--json", "output raw JSON response").action(async (opts) => {
5862
6282
  const client = getIMClient2();
5863
6283
  try {
5864
6284
  const res = await client.im.tasks.list({
5865
6285
  status: parseTaskStatus(opts.status),
5866
6286
  capability: opts.capability,
6287
+ projectId: normalizeProjectForList(opts.project),
5867
6288
  limit: parseInt(opts.limit, 10)
5868
6289
  });
5869
6290
  if (opts.json) {
@@ -5884,9 +6305,9 @@ function register4(parent, getIMClient2, _getAPIClient) {
5884
6305
  const statusW = 12;
5885
6306
  const titleW = 40;
5886
6307
  const header = "ID".padEnd(idW) + "STATUS".padEnd(statusW) + "TITLE";
5887
- const sep = "-".repeat(idW + statusW + titleW);
6308
+ const sep3 = "-".repeat(idW + statusW + titleW);
5888
6309
  process.stdout.write(header + "\n");
5889
- process.stdout.write(sep + "\n");
6310
+ process.stdout.write(sep3 + "\n");
5890
6311
  for (const t of tasks) {
5891
6312
  const title = t.title.length > titleW ? t.title.slice(0, titleW - 3) + "..." : t.title;
5892
6313
  process.stdout.write(
@@ -5895,6 +6316,32 @@ function register4(parent, getIMClient2, _getAPIClient) {
5895
6316
  }
5896
6317
  process.stdout.write(`
5897
6318
  ${tasks.length} task(s) listed.
6319
+ `);
6320
+ } catch (err) {
6321
+ const message = err instanceof Error ? err.message : String(err);
6322
+ process.stderr.write(`Error: ${message}
6323
+ `);
6324
+ process.exit(1);
6325
+ }
6326
+ });
6327
+ task.command("move-project <task-id> [project-id]").description("Move a task to a project, or use --unscoped to return it to workspace-level").option("--unscoped", "set projectId to null", false).option("--json", "output raw JSON response").action(async (taskId, projectId, opts) => {
6328
+ const client = getIMClient2();
6329
+ try {
6330
+ if (!opts.unscoped && !projectId) {
6331
+ throw new Error("Provide <project-id> or pass --unscoped.");
6332
+ }
6333
+ const targetProjectId = opts.unscoped ? null : projectId.trim();
6334
+ const res = await client.im.tasks.moveProject(taskId, targetProjectId);
6335
+ if (opts.json) {
6336
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6337
+ return;
6338
+ }
6339
+ if (!res.ok) {
6340
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6341
+ `);
6342
+ process.exit(1);
6343
+ }
6344
+ process.stdout.write(`Task ${taskId} project: ${targetProjectId ?? "(workspace-level)"}
5898
6345
  `);
5899
6346
  } catch (err) {
5900
6347
  const message = err instanceof Error ? err.message : String(err);
@@ -5997,9 +6444,37 @@ Logs (${logs.length}):
5997
6444
  process.exit(1);
5998
6445
  }
5999
6446
  });
6000
- task.command("update <task-id>").description("Update a task").option("--title <title>", "new title").option("--description <description>", "new description").option("--status <status>", "new status").option("--progress <progress>", "progress (0.0 to 1.0)", parseFloat).option("--status-message <statusMessage>", "status message").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6447
+ task.command("update <task-id>").description("Update a task").option("--title <title>", "new title").option("--description <description>", "new description").option("--status <status>", "new status").option("--progress <progress>", "progress (0.0 to 1.0)", parseFloat).option("--status-message <statusMessage>", "status message").option("--skip-checkpoint", "bypass local daemon checkpoint when transitioning to review (release201/09 \xA79.4a.7)", false).option("--json", "output raw JSON response").action(async (taskId, opts) => {
6001
6448
  const client = getIMClient2();
6002
6449
  try {
6450
+ if (opts.status === "review" && !opts.skipCheckpoint) {
6451
+ const checkpoint = await runReviewCheckpoint(taskId, opts.status);
6452
+ if (!checkpoint.ok) {
6453
+ if (checkpoint.pendingFiles && checkpoint.pendingFiles.length > 0) {
6454
+ process.stderr.write(
6455
+ `[checkpoint] result/ \u542B ${checkpoint.pendingFiles.length} \u4E2A\u672A attach \u6587\u4EF6:
6456
+ `
6457
+ );
6458
+ for (const f of checkpoint.pendingFiles) {
6459
+ const sizeKb = (f.sizeBytes / 1024).toFixed(1);
6460
+ process.stderr.write(` - ${f.path} (sha256=${f.sha256.slice(0, 12)}\u2026, ${sizeKb}KB)
6461
+ `);
6462
+ }
6463
+ process.stderr.write(
6464
+ "\u8BF7 attach \u5B83\u4EEC\u518D retry status change, \u6216\u52A0 --skip-checkpoint \u663E\u5F0F\u5FFD\u7565\n"
6465
+ );
6466
+ } else if (checkpoint.indeterminate) {
6467
+ process.stderr.write(
6468
+ `[checkpoint] daemon unreachable or cloud lookup failed (${checkpoint.message}); pass --skip-checkpoint to bypass
6469
+ `
6470
+ );
6471
+ } else {
6472
+ process.stderr.write(`[checkpoint] failed: ${checkpoint.message ?? "unknown"}
6473
+ `);
6474
+ }
6475
+ process.exit(12);
6476
+ }
6477
+ }
6003
6478
  const res = await client.im.tasks.update(taskId, {
6004
6479
  title: opts.title,
6005
6480
  description: opts.description,
@@ -6159,6 +6634,83 @@ Logs (${logs.length}):
6159
6634
  } catch (err) {
6160
6635
  const message = err instanceof Error ? err.message : String(err);
6161
6636
  process.stderr.write(`Error: ${message}
6637
+ `);
6638
+ process.exit(1);
6639
+ }
6640
+ });
6641
+ task.command("attach <path>").description("Attach a file to a task as a user-deliverable (release201/09 \xA79.4a.5)").option("--task <taskId>", "target task id; defaults to PRISMER_TASK_ID env").option("--name <displayName>", "override the display filename; defaults to basename(path)").option("--json", "output raw JSON response").action(async (filePath, opts) => {
6642
+ const client = getIMClient2();
6643
+ try {
6644
+ const targetTaskId = opts.task ?? process.env.PRISMER_TASK_ID;
6645
+ if (!targetTaskId) {
6646
+ process.stderr.write("Error: --task is required (or set PRISMER_TASK_ID env)\n");
6647
+ process.exit(1);
6648
+ }
6649
+ const getRes = await client.im.tasks.get(targetTaskId);
6650
+ if (!getRes.ok || !getRes.data) {
6651
+ process.stderr.write(`Error: task ${targetTaskId} not found: ${getRes.error?.message ?? "unknown"}
6652
+ `);
6653
+ process.exit(1);
6654
+ }
6655
+ const wsId = getRes.data.task?.workspaceId;
6656
+ if (!wsId) {
6657
+ process.stderr.write(`Error: task ${targetTaskId} has no workspaceId; cannot upload
6658
+ `);
6659
+ process.exit(1);
6660
+ }
6661
+ let bytes;
6662
+ try {
6663
+ bytes = await import_node_fs.promises.readFile(filePath);
6664
+ } catch (err) {
6665
+ process.stderr.write(`Error: cannot read ${filePath}: ${err instanceof Error ? err.message : String(err)}
6666
+ `);
6667
+ process.exit(1);
6668
+ }
6669
+ const contentHash = (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
6670
+ const displayName = opts.name ?? (0, import_node_path.basename)(filePath);
6671
+ const uploadRes = await client.im.assets.upload(bytes, {
6672
+ workspaceId: wsId,
6673
+ sourceTaskId: targetTaskId,
6674
+ kind: "agent-output",
6675
+ fileName: displayName,
6676
+ metadata: {
6677
+ // boundKind stamped via metadata so service-side cloud code can
6678
+ // mirror onto the column. Service POST handler reads this and
6679
+ // populates IMAsset.boundKind='task-bound'.
6680
+ boundKind: "task-bound",
6681
+ filename: displayName,
6682
+ attachedBy: "cloud-task-attach"
6683
+ }
6684
+ });
6685
+ if (opts.json) {
6686
+ process.stdout.write(JSON.stringify(uploadRes, null, 2) + "\n");
6687
+ return;
6688
+ }
6689
+ if (!uploadRes.ok || !uploadRes.data) {
6690
+ process.stderr.write(`Error: attach failed: ${uploadRes.error?.message ?? "unknown"}
6691
+ `);
6692
+ process.exit(1);
6693
+ }
6694
+ const dedupHit = Boolean(uploadRes.meta?.dedup);
6695
+ const asset = uploadRes.data;
6696
+ process.stdout.write(`Attached ${displayName}
6697
+
6698
+ `);
6699
+ process.stdout.write(`AssetId: ${asset.id}
6700
+ `);
6701
+ process.stdout.write(`Task: ${targetTaskId}
6702
+ `);
6703
+ process.stdout.write(`ContentHash: ${contentHash}
6704
+ `);
6705
+ process.stdout.write(`SizeBytes: ${bytes.length}
6706
+ `);
6707
+ process.stdout.write(`Dedup: ${dedupHit ? "true (existing IMAsset row)" : "false (created)"}
6708
+ `);
6709
+ if (asset.cdnUrl) process.stdout.write(`CDN URL: ${asset.cdnUrl}
6710
+ `);
6711
+ } catch (err) {
6712
+ const message = err instanceof Error ? err.message : String(err);
6713
+ process.stderr.write(`Error: ${message}
6162
6714
  `);
6163
6715
  process.exit(1);
6164
6716
  }
@@ -6193,26 +6745,18 @@ Logs (${logs.length}):
6193
6745
  process.exit(1);
6194
6746
  }
6195
6747
  });
6196
- }
6197
-
6198
- // src/commands/memory.ts
6199
- var MEMORY_TYPES = /* @__PURE__ */ new Set(["user", "feedback", "project", "reference"]);
6200
- function register5(parent, getIMClient2, _getAPIClient) {
6201
- const mem = parent.command("memory").description("Agent memory file management");
6202
- mem.command("write").description("Write a memory file").requiredOption("-s, --scope <scope>", "memory scope").requiredOption("-p, --path <path>", "file path within scope").requiredOption("-c, --content <content>", "file content").option("--type <type>", "memory type: user | feedback | project | reference").option("--description <description>", "human-readable description of this memory").option("--json", "output raw JSON response").action(async (opts) => {
6748
+ task.command("spec-set <task-id>").description("Owner writes / updates SPEC.md for a task").option("-f, --file <path>", "read SPEC.md content from a file").option("-m, --markdown <markdown>", "inline SPEC.md content").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6203
6749
  const client = getIMClient2();
6204
6750
  try {
6205
- if (opts.type && !MEMORY_TYPES.has(opts.type)) {
6206
- throw new Error(`Invalid --type "${opts.type}". Use one of: user, feedback, project, reference.`);
6751
+ let md = "";
6752
+ if (opts.file) {
6753
+ md = await import_node_fs.promises.readFile(opts.file, "utf-8");
6754
+ } else if (opts.markdown) {
6755
+ md = opts.markdown;
6756
+ } else {
6757
+ throw new Error("one of --file or --markdown is required");
6207
6758
  }
6208
- const body = {
6209
- scope: opts.scope,
6210
- path: opts.path,
6211
- content: opts.content
6212
- };
6213
- if (opts.type) body.memoryType = opts.type;
6214
- if (opts.description) body.description = opts.description;
6215
- const res = await client.im.memory.createFile(body);
6759
+ const res = await client.im.tasks.spec.set(taskId, { markdown: md });
6216
6760
  if (opts.json) {
6217
6761
  process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6218
6762
  return;
@@ -6222,19 +6766,353 @@ function register5(parent, getIMClient2, _getAPIClient) {
6222
6766
  `);
6223
6767
  process.exit(1);
6224
6768
  }
6225
- const file = res.data;
6226
- if (!file) throw new Error("Memory file response missing data");
6227
- process.stdout.write(`Memory file created
6228
- `);
6229
- process.stdout.write(` ID: ${file.id}
6230
- `);
6231
- process.stdout.write(` Scope: ${file.scope}
6232
- `);
6233
- process.stdout.write(` Path: ${file.path}
6769
+ const v = res.data;
6770
+ process.stdout.write(`SPEC.md saved (revision ${v.revision})
6234
6771
  `);
6235
6772
  } catch (err) {
6236
- const message = err instanceof Error ? err.message : String(err);
6237
- process.stderr.write(`Error: ${message}
6773
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6774
+ `);
6775
+ process.exit(1);
6776
+ }
6777
+ });
6778
+ task.command("spec-show <task-id>").description("Print SPEC.md content for a task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6779
+ const client = getIMClient2();
6780
+ try {
6781
+ const res = await client.im.tasks.spec.get(taskId);
6782
+ if (opts.json) {
6783
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6784
+ return;
6785
+ }
6786
+ if (!res.ok) {
6787
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6788
+ `);
6789
+ process.exit(1);
6790
+ }
6791
+ const v = res.data;
6792
+ process.stdout.write(v.markdown);
6793
+ if (!v.markdown.endsWith("\n")) process.stdout.write("\n");
6794
+ process.stdout.write(`# revision ${v.revision}
6795
+ `);
6796
+ } catch (err) {
6797
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6798
+ `);
6799
+ process.exit(1);
6800
+ }
6801
+ });
6802
+ task.command("todo-add <task-id> <text...>").description("Append an item to the task TODO.md (assignee)").option("--depth <n>", "nesting depth (0..3)", (v) => parseInt(v, 10), 0).option("--json", "output raw JSON response").action(async (taskId, text, opts) => {
6803
+ const client = getIMClient2();
6804
+ try {
6805
+ const res = await client.im.tasks.todo.add(taskId, {
6806
+ text: text.join(" "),
6807
+ depth: opts.depth
6808
+ });
6809
+ if (opts.json) {
6810
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6811
+ return;
6812
+ }
6813
+ if (!res.ok) {
6814
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6815
+ `);
6816
+ process.exit(1);
6817
+ }
6818
+ const v = res.data;
6819
+ process.stdout.write(`TODO item added \u2014 ${v.doneCount}/${v.totalCount} (rev ${v.revision})
6820
+ `);
6821
+ } catch (err) {
6822
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6823
+ `);
6824
+ process.exit(1);
6825
+ }
6826
+ });
6827
+ task.command("todo-done <task-id> <index>").description("Mark a TODO item as done").option("--json", "output raw JSON response").action(async (taskId, indexRaw, opts) => {
6828
+ const client = getIMClient2();
6829
+ try {
6830
+ const idx = parseInt(indexRaw, 10);
6831
+ if (!Number.isFinite(idx) || idx < 0) throw new Error("index must be a non-negative integer");
6832
+ const res = await client.im.tasks.todo.toggle(taskId, idx, true);
6833
+ if (opts.json) {
6834
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6835
+ return;
6836
+ }
6837
+ if (!res.ok) {
6838
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6839
+ `);
6840
+ process.exit(1);
6841
+ }
6842
+ const v = res.data;
6843
+ process.stdout.write(
6844
+ `TODO[${idx}] \u2192 done. progress ${v.doneCount}/${v.totalCount} (${Math.round(v.progressPct * 100)}%)
6845
+ `
6846
+ );
6847
+ } catch (err) {
6848
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6849
+ `);
6850
+ process.exit(1);
6851
+ }
6852
+ });
6853
+ task.command("todo-uncheck <task-id> <index>").description("Un-tick a TODO item").option("--json", "output raw JSON response").action(async (taskId, indexRaw, opts) => {
6854
+ const client = getIMClient2();
6855
+ try {
6856
+ const idx = parseInt(indexRaw, 10);
6857
+ if (!Number.isFinite(idx) || idx < 0) throw new Error("index must be a non-negative integer");
6858
+ const res = await client.im.tasks.todo.toggle(taskId, idx, false);
6859
+ if (opts.json) {
6860
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6861
+ return;
6862
+ }
6863
+ if (!res.ok) {
6864
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6865
+ `);
6866
+ process.exit(1);
6867
+ }
6868
+ const v = res.data;
6869
+ process.stdout.write(`TODO[${idx}] \u2192 pending. progress ${v.doneCount}/${v.totalCount}
6870
+ `);
6871
+ } catch (err) {
6872
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6873
+ `);
6874
+ process.exit(1);
6875
+ }
6876
+ });
6877
+ task.command("todo-show <task-id>").description("Render TODO.md checklist + progress for a task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6878
+ const client = getIMClient2();
6879
+ try {
6880
+ const res = await client.im.tasks.todo.list(taskId);
6881
+ if (opts.json) {
6882
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6883
+ return;
6884
+ }
6885
+ if (!res.ok) {
6886
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6887
+ `);
6888
+ process.exit(1);
6889
+ }
6890
+ const v = res.data;
6891
+ process.stdout.write(
6892
+ `TODO progress: ${v.doneCount}/${v.totalCount} (${Math.round(v.progressPct * 100)}%) \xB7 rev ${v.revision}
6893
+ `
6894
+ );
6895
+ for (const it of v.items) {
6896
+ const tick = it.status === "done" ? "\u2611" : "\u2610";
6897
+ const indent = " ".repeat(it.depth);
6898
+ process.stdout.write(` ${indent}${tick} [${it.index}] ${it.text}
6899
+ `);
6900
+ }
6901
+ } catch (err) {
6902
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6903
+ `);
6904
+ process.exit(1);
6905
+ }
6906
+ });
6907
+ task.command("acceptance <task-id>").description("Show acceptance criteria + rolled-up status for a task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6908
+ const client = getIMClient2();
6909
+ try {
6910
+ const res = await client.im.tasks.getAcceptance(taskId);
6911
+ if (opts.json) {
6912
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6913
+ return;
6914
+ }
6915
+ if (!res.ok) {
6916
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6917
+ `);
6918
+ process.exit(1);
6919
+ }
6920
+ const v = res.data;
6921
+ process.stdout.write(`Acceptance for ${taskId}: ${v.overall} (${v.completedCount}/${v.totalCount})
6922
+ `);
6923
+ for (const c of v.criteria) {
6924
+ const mark = c.status === "passed" ? "\u2713" : c.status === "failed" ? "\u2717" : c.status === "n/a" ? "\xB7" : "\u25EF";
6925
+ const req = c.required === false ? " [optional]" : "";
6926
+ const ver = c.verifierAgentId ? ` @${c.verifierAgentId}` : "";
6927
+ process.stdout.write(` ${mark} [${c.verifyMode}${ver}] ${c.expectation}${req} (${c.status}) \u2014 ${c.id}
6928
+ `);
6929
+ }
6930
+ } catch (err) {
6931
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6932
+ `);
6933
+ process.exit(1);
6934
+ }
6935
+ });
6936
+ task.command("add-criterion <task-id>").description("Add an acceptance criterion to a task (rev 2 \u2014 verifyMode + expectation)").requiredOption(
6937
+ "--mode <mode>",
6938
+ "verifyMode: qualitative | quantitative | agent-self-check | manual"
6939
+ ).requiredOption("--expectation <markdown>", "markdown describing what done looks like").option("--verifier-agent <agentId>", "verifier agent id (default = creator)").option("--weight <n>", "weight (numeric, default 1)", parseFloat).option("--optional", "mark this criterion optional (default required)").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6940
+ const client = getIMClient2();
6941
+ try {
6942
+ const VALID = ["qualitative", "quantitative", "agent-self-check", "manual"];
6943
+ if (!VALID.includes(opts.mode)) {
6944
+ throw new Error(`--mode must be one of ${VALID.join(" | ")}`);
6945
+ }
6946
+ const res = await client.im.tasks.criteria.add(taskId, {
6947
+ verifyMode: opts.mode,
6948
+ expectation: opts.expectation,
6949
+ verifierAgentId: opts.verifierAgent ?? null,
6950
+ weight: opts.weight ?? 1,
6951
+ required: !opts.optional
6952
+ });
6953
+ if (opts.json) {
6954
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6955
+ return;
6956
+ }
6957
+ if (!res.ok) {
6958
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6959
+ `);
6960
+ process.exit(1);
6961
+ }
6962
+ const { criterion } = res.data;
6963
+ process.stdout.write(`Added criterion ${criterion.id} to task ${taskId}
6964
+ `);
6965
+ } catch (err) {
6966
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6967
+ `);
6968
+ process.exit(1);
6969
+ }
6970
+ });
6971
+ task.command("verify <task-id>").description(
6972
+ "Assignee self-check: list all agent-self-check criteria + mark them passed (run before status=review)"
6973
+ ).option("--note <note>", "note to attach to each self-check", "agent self-check via `cloud task verify`").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6974
+ const client = getIMClient2();
6975
+ try {
6976
+ const view = await client.im.tasks.getAcceptance(taskId);
6977
+ if (!view.ok) {
6978
+ process.stderr.write(`Error: ${view.error?.message || "Unknown error"}
6979
+ `);
6980
+ process.exit(1);
6981
+ }
6982
+ const v = view.data;
6983
+ const targets = v.criteria.filter(
6984
+ (c) => c.verifyMode === "agent-self-check" && c.status === "pending"
6985
+ );
6986
+ if (targets.length === 0) {
6987
+ process.stdout.write("No pending agent-self-check criteria.\n");
6988
+ return;
6989
+ }
6990
+ const results = [];
6991
+ let anyFailed = false;
6992
+ for (const c of targets) {
6993
+ const r = await client.im.tasks.criteria.verify(taskId, c.id, {
6994
+ outcome: "passed",
6995
+ note: opts.note
6996
+ });
6997
+ results.push({ id: c.id, outcome: r.ok ? "passed" : "error" });
6998
+ if (!r.ok) anyFailed = true;
6999
+ }
7000
+ if (opts.json) {
7001
+ process.stdout.write(JSON.stringify({ ok: !anyFailed, results }, null, 2) + "\n");
7002
+ return;
7003
+ }
7004
+ for (const r of results) {
7005
+ process.stdout.write(` ${r.outcome === "passed" ? "\u2713" : "\u2717"} ${r.id}
7006
+ `);
7007
+ }
7008
+ if (anyFailed) process.exit(1);
7009
+ } catch (err) {
7010
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7011
+ `);
7012
+ process.exit(1);
7013
+ }
7014
+ });
7015
+ task.command("verify-criterion <task-id> <criterion-id>").description(
7016
+ "Report verify outcome for one criterion. Used by reviewers (manual), verifier agents, and assignees (self-check)."
7017
+ ).requiredOption("--outcome <outcome>", "passed | failed | n/a | waived").option("--note <note>", "free-form markdown: method + result + repro steps").option("--evidence <ref...>", "evidence refs (repeatable; e.g. asset:<id>, url:..., taskRun:<id>)").option("--waive-reason <reason>", "required when --outcome waived").option("--json", "output raw JSON response").action(async (taskId, criterionId, opts) => {
7018
+ const client = getIMClient2();
7019
+ try {
7020
+ const VALID = ["passed", "failed", "n/a", "waived"];
7021
+ if (!VALID.includes(opts.outcome)) {
7022
+ throw new Error(`--outcome must be one of ${VALID.join(" | ")}`);
7023
+ }
7024
+ if (opts.outcome === "waived" && !opts.waiveReason) {
7025
+ throw new Error("--waive-reason is required when --outcome waived");
7026
+ }
7027
+ const outcome = opts.outcome;
7028
+ const res = await client.im.tasks.criteria.verify(taskId, criterionId, {
7029
+ outcome,
7030
+ note: opts.note,
7031
+ evidenceRefs: opts.evidence,
7032
+ waiveReason: opts.waiveReason
7033
+ });
7034
+ if (opts.json) {
7035
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7036
+ return;
7037
+ }
7038
+ if (!res.ok) {
7039
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7040
+ `);
7041
+ process.exit(1);
7042
+ }
7043
+ process.stdout.write(`Criterion ${criterionId} \u2192 ${opts.outcome}
7044
+ `);
7045
+ } catch (err) {
7046
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7047
+ `);
7048
+ process.exit(1);
7049
+ }
7050
+ });
7051
+ task.command("apply-template <task-id>").description("Apply an acceptance criteria template to a task").requiredOption("--template <templateId>", "template id to apply").option("--json", "output raw JSON response").action(async (taskId, opts) => {
7052
+ const client = getIMClient2();
7053
+ try {
7054
+ const res = await client.im.tasks.applyTemplate(taskId, opts.template);
7055
+ if (opts.json) {
7056
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7057
+ return;
7058
+ }
7059
+ if (!res.ok) {
7060
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7061
+ `);
7062
+ process.exit(1);
7063
+ }
7064
+ const v = res.data;
7065
+ process.stdout.write(`Applied template ${opts.template} (${v.totalCount} criteria total)
7066
+ `);
7067
+ } catch (err) {
7068
+ const message = err instanceof Error ? err.message : String(err);
7069
+ process.stderr.write(`Error: ${message}
7070
+ `);
7071
+ process.exit(1);
7072
+ }
7073
+ });
7074
+ }
7075
+
7076
+ // src/commands/memory.ts
7077
+ var MEMORY_TYPES = /* @__PURE__ */ new Set(["user", "feedback", "project", "reference"]);
7078
+ function register5(parent, getIMClient2, _getAPIClient) {
7079
+ const mem = parent.command("memory").description("Agent memory file management");
7080
+ mem.command("write").description("Write a memory file").requiredOption("-s, --scope <scope>", "memory scope").requiredOption("-p, --path <path>", "file path within scope").requiredOption("-c, --content <content>", "file content").option("--type <type>", "memory type: user | feedback | project | reference").option("--description <description>", "human-readable description of this memory").option("--json", "output raw JSON response").action(async (opts) => {
7081
+ const client = getIMClient2();
7082
+ try {
7083
+ if (opts.type && !MEMORY_TYPES.has(opts.type)) {
7084
+ throw new Error(`Invalid --type "${opts.type}". Use one of: user, feedback, project, reference.`);
7085
+ }
7086
+ const body = {
7087
+ scope: opts.scope,
7088
+ path: opts.path,
7089
+ content: opts.content
7090
+ };
7091
+ if (opts.type) body.memoryType = opts.type;
7092
+ if (opts.description) body.description = opts.description;
7093
+ const res = await client.im.memory.createFile(body);
7094
+ if (opts.json) {
7095
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7096
+ return;
7097
+ }
7098
+ if (!res.ok) {
7099
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7100
+ `);
7101
+ process.exit(1);
7102
+ }
7103
+ const file = res.data;
7104
+ if (!file) throw new Error("Memory file response missing data");
7105
+ process.stdout.write(`Memory file created
7106
+ `);
7107
+ process.stdout.write(` ID: ${file.id}
7108
+ `);
7109
+ process.stdout.write(` Scope: ${file.scope}
7110
+ `);
7111
+ process.stdout.write(` Path: ${file.path}
7112
+ `);
7113
+ } catch (err) {
7114
+ const message = err instanceof Error ? err.message : String(err);
7115
+ process.stderr.write(`Error: ${message}
6238
7116
  `);
6239
7117
  process.exit(1);
6240
7118
  }
@@ -6496,7 +7374,7 @@ function printFileTable(files) {
6496
7374
  const idLen = Math.max(2, ...files.map((f) => f.id.length));
6497
7375
  const scopeLen = Math.max(5, ...files.map((f) => f.scope.length));
6498
7376
  const pathLen = Math.max(4, ...files.map((f) => f.path.length));
6499
- const row = (id, scope, path5) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path5.padEnd(pathLen)}`;
7377
+ const row = (id, scope, path6) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path6.padEnd(pathLen)}`;
6500
7378
  process.stdout.write(row("ID", "SCOPE", "PATH") + "\n");
6501
7379
  process.stdout.write(`${"-".repeat(idLen)} ${"-".repeat(scopeLen)} ${"-".repeat(pathLen)}
6502
7380
  `);
@@ -6757,17 +7635,471 @@ ${result.content}
6757
7635
  });
6758
7636
  }
6759
7637
 
6760
- // src/commands/files.ts
7638
+ // src/commands/skill-draft.ts
7639
+ var fs3 = __toESM(require("fs"));
6761
7640
  function register7(parent, getIMClient2, _getAPIClient) {
6762
- const file = parent.command("file").description("File upload, transfer, quota, and type management");
6763
- file.command("upload <path>").description("Upload a file and get its upload ID and CDN URL").option("--mime <type>", "Override MIME type (e.g. image/png)").option("--json", "Output raw JSON response").action(async (filePath, opts) => {
6764
- const client = getIMClient2();
7641
+ const skill = parent.commands.find((c) => c.name() === "skill") ?? parent.command("skill");
7642
+ const draft = skill.command("draft").description("Author / patch / regenerate / show / list skill drafts (release201/07)");
7643
+ draft.command("create").description("Submit a new skill draft from a manifest.json file").option("--slug <slug>", "Draft slug (overrides manifest.slug)").option("--manifest <path>", "Path to manifest.json containing slug/name/description/files[]/...").option("--workspace <id>", "Override manifest.workspaceId").option("--owner-agent <id>", "Override manifest.ownerAgentId").option("--json", "Output raw JSON response").action(async (opts) => {
7644
+ if (!opts.manifest) {
7645
+ process.stderr.write("Error: --manifest <path-to-manifest.json> is required\n");
7646
+ process.exit(1);
7647
+ }
7648
+ let manifest;
6765
7649
  try {
6766
- const uploadOpts = {};
6767
- if (opts.mime) uploadOpts.mimeType = opts.mime;
6768
- const res = await client.im.files.upload(filePath, Object.keys(uploadOpts).length ? uploadOpts : void 0);
6769
- if (opts.json) {
6770
- process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7650
+ manifest = JSON.parse(fs3.readFileSync(opts.manifest, "utf-8"));
7651
+ } catch (err) {
7652
+ process.stderr.write(`Error: failed to read manifest ${opts.manifest}: ${err.message}
7653
+ `);
7654
+ process.exit(1);
7655
+ }
7656
+ const slug = opts.slug ?? manifest.slug;
7657
+ const workspaceId = opts.workspace ?? manifest.workspaceId;
7658
+ const ownerAgentId = opts.ownerAgent ?? manifest.ownerAgentId;
7659
+ if (!slug || !manifest.name || !manifest.description) {
7660
+ process.stderr.write("Error: manifest must include slug, name, description\n");
7661
+ process.exit(1);
7662
+ }
7663
+ if (!workspaceId) {
7664
+ process.stderr.write("Error: --workspace <id> or manifest.workspaceId is required\n");
7665
+ process.exit(1);
7666
+ }
7667
+ if (!Array.isArray(manifest.files) || manifest.files.length < 2) {
7668
+ process.stderr.write("Error: manifest.files[] must include at least SKILL.md + skill.json\n");
7669
+ process.exit(1);
7670
+ }
7671
+ const client = getIMClient2();
7672
+ try {
7673
+ const res = await client.im.skills.draft.create({
7674
+ slug,
7675
+ name: manifest.name,
7676
+ description: manifest.description,
7677
+ workspaceId,
7678
+ ownerAgentId,
7679
+ files: manifest.files,
7680
+ metadata: manifest.metadata
7681
+ });
7682
+ if (opts.json) {
7683
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7684
+ return;
7685
+ }
7686
+ if (!res.ok) {
7687
+ process.stderr.write(`Error: ${res.error ?? "create failed"}
7688
+ `);
7689
+ process.exit(1);
7690
+ }
7691
+ const data = res.data ?? res;
7692
+ process.stdout.write(`Draft submitted.
7693
+ `);
7694
+ process.stdout.write(` id: ${data.id}
7695
+ `);
7696
+ process.stdout.write(` slug: ${data.slug}
7697
+ `);
7698
+ process.stdout.write(` manifest revision: ${data.manifestRevision}
7699
+ `);
7700
+ if (data.reviewTaskId) {
7701
+ process.stdout.write(` review task: ${data.reviewTaskId}
7702
+ `);
7703
+ }
7704
+ const warnings = data.validationWarnings ?? [];
7705
+ if (warnings.length > 0) {
7706
+ process.stdout.write(` warnings:
7707
+ `);
7708
+ for (const w of warnings) {
7709
+ process.stdout.write(` [${w.gate}] ${w.message}
7710
+ `);
7711
+ }
7712
+ }
7713
+ } catch (err) {
7714
+ process.stderr.write(`Error: ${err.message}
7715
+ `);
7716
+ process.exit(1);
7717
+ }
7718
+ });
7719
+ draft.command("patch <draft-id>").description("Patch a draft \u2014 add / update / delete a single file").option("--file <path>", "Manifest file path inside the draft (e.g. SKILL.md or references/foo.md)").option("--op <op>", "Operation: add | update | delete", "update").option("--content <text>", "New utf-8 content for add/update (omit for --content-file)").option("--content-file <path>", "Read content from a local file").option("--reason <text>", "Free-form reason recorded in revision history").option("--json", "Output raw JSON response").action(async (draftId, opts) => {
7720
+ if (!opts.file) {
7721
+ process.stderr.write("Error: --file <path> is required\n");
7722
+ process.exit(1);
7723
+ }
7724
+ if (!["add", "update", "delete"].includes(opts.op)) {
7725
+ process.stderr.write(`Error: --op must be add | update | delete (got ${opts.op})
7726
+ `);
7727
+ process.exit(1);
7728
+ }
7729
+ let content;
7730
+ if (opts.op !== "delete") {
7731
+ if (opts.contentFile) {
7732
+ try {
7733
+ content = fs3.readFileSync(opts.contentFile, "utf-8");
7734
+ } catch (err) {
7735
+ process.stderr.write(`Error: failed to read --content-file: ${err.message}
7736
+ `);
7737
+ process.exit(1);
7738
+ }
7739
+ } else if (opts.content !== void 0) {
7740
+ content = opts.content;
7741
+ } else {
7742
+ process.stderr.write("Error: --content or --content-file is required for add/update\n");
7743
+ process.exit(1);
7744
+ }
7745
+ }
7746
+ const client = getIMClient2();
7747
+ try {
7748
+ const res = await client.im.skills.draft.patch(draftId, {
7749
+ files: [{ path: opts.file, op: opts.op, content }],
7750
+ reason: opts.reason
7751
+ });
7752
+ if (opts.json) {
7753
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7754
+ return;
7755
+ }
7756
+ if (!res.ok) {
7757
+ process.stderr.write(`Error: ${res.error ?? "patch failed"}
7758
+ `);
7759
+ process.exit(1);
7760
+ }
7761
+ const data = res.data ?? res;
7762
+ process.stdout.write(`Patch applied.
7763
+ `);
7764
+ process.stdout.write(` id: ${data.id}
7765
+ `);
7766
+ process.stdout.write(` manifest revision: ${data.manifestRevision}
7767
+ `);
7768
+ } catch (err) {
7769
+ process.stderr.write(`Error: ${err.message}
7770
+ `);
7771
+ process.exit(1);
7772
+ }
7773
+ });
7774
+ draft.command("regenerate <draft-id>").description("Request a regenerate session for a draft (does not delete current manifest)").option("--reason <text>", "Why the draft needs regeneration").option("--json", "Output raw JSON response").action(async (draftId, opts) => {
7775
+ if (!opts.reason) {
7776
+ process.stderr.write("Error: --reason <text> is required\n");
7777
+ process.exit(1);
7778
+ }
7779
+ const client = getIMClient2();
7780
+ try {
7781
+ const res = await client.im.skills.draft.regenerate(draftId, { reason: opts.reason });
7782
+ if (opts.json) {
7783
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7784
+ return;
7785
+ }
7786
+ if (!res.ok) {
7787
+ process.stderr.write(`Error: ${res.error ?? "regenerate failed"}
7788
+ `);
7789
+ process.exit(1);
7790
+ }
7791
+ const data = res.data ?? res;
7792
+ process.stdout.write(`Regenerate requested.
7793
+ `);
7794
+ process.stdout.write(` id: ${data.id}
7795
+ `);
7796
+ process.stdout.write(` session id: ${data.sessionId}
7797
+ `);
7798
+ } catch (err) {
7799
+ process.stderr.write(`Error: ${err.message}
7800
+ `);
7801
+ process.exit(1);
7802
+ }
7803
+ });
7804
+ draft.command("show <draft-id>").description("Show a draft's manifest + revision history").option("--json", "Output raw JSON response").action(async (draftId, opts) => {
7805
+ const client = getIMClient2();
7806
+ try {
7807
+ const res = await client.im.skills.draft.show(draftId);
7808
+ if (opts.json) {
7809
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7810
+ return;
7811
+ }
7812
+ if (!res.ok) {
7813
+ process.stderr.write(`Error: ${res.error ?? "show failed"}
7814
+ `);
7815
+ process.exit(1);
7816
+ }
7817
+ const data = res.data ?? res;
7818
+ process.stdout.write(`Draft ${data.id}
7819
+ `);
7820
+ process.stdout.write(` slug: ${data.slug}
7821
+ `);
7822
+ process.stdout.write(` name: ${data.name}
7823
+ `);
7824
+ process.stdout.write(` status: ${data.status}
7825
+ `);
7826
+ process.stdout.write(` workspace: ${data.workspaceId}
7827
+ `);
7828
+ process.stdout.write(` ownerAgent: ${data.ownerAgentId}
7829
+ `);
7830
+ process.stdout.write(` revision: ${data.manifestRevision}
7831
+ `);
7832
+ process.stdout.write(` license: ${data.license}
7833
+ `);
7834
+ process.stdout.write(` compatibility: ${(data.compatibility ?? []).join(", ")}
7835
+ `);
7836
+ process.stdout.write(` files:
7837
+ `);
7838
+ for (const f of data.files ?? []) {
7839
+ process.stdout.write(` ${f.path} (${f.size}B, sha256:${(f.sha256 ?? "").slice(0, 8)}\u2026)
7840
+ `);
7841
+ }
7842
+ } catch (err) {
7843
+ process.stderr.write(`Error: ${err.message}
7844
+ `);
7845
+ process.exit(1);
7846
+ }
7847
+ });
7848
+ draft.command("list").description("List drafts in a workspace").option("--workspace <id>", "Workspace id (required)").option("--json", "Output raw JSON response").action(async (opts) => {
7849
+ if (!opts.workspace) {
7850
+ process.stderr.write("Error: --workspace <id> is required\n");
7851
+ process.exit(1);
7852
+ }
7853
+ const client = getIMClient2();
7854
+ try {
7855
+ const res = await client.im.skills.draft.list(opts.workspace);
7856
+ if (opts.json) {
7857
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7858
+ return;
7859
+ }
7860
+ if (!res.ok) {
7861
+ process.stderr.write(`Error: ${res.error ?? "list failed"}
7862
+ `);
7863
+ process.exit(1);
7864
+ }
7865
+ const drafts = res.data ?? [];
7866
+ if (drafts.length === 0) {
7867
+ process.stdout.write("No drafts in this workspace.\n");
7868
+ return;
7869
+ }
7870
+ process.stdout.write(`Drafts (${drafts.length}):
7871
+ `);
7872
+ for (const d of drafts) {
7873
+ process.stdout.write(` ${d.id} ${d.slug} ${(d.name ?? "").slice(0, 32)} rev:${(d.contentManifestRevision ?? "").slice(0, 8)}\u2026 updated:${d.updatedAt}
7874
+ `);
7875
+ }
7876
+ } catch (err) {
7877
+ process.stderr.write(`Error: ${err.message}
7878
+ `);
7879
+ process.exit(1);
7880
+ }
7881
+ });
7882
+ }
7883
+
7884
+ // src/commands/code-grep.ts
7885
+ var fs4 = __toESM(require("fs"));
7886
+ var path2 = __toESM(require("path"));
7887
+ var crypto2 = __toESM(require("crypto"));
7888
+ var os = __toESM(require("os"));
7889
+ var DEFAULT_MAX_MATCHES = 50;
7890
+ var SCRATCH_DIR_GLOB = path2.join(os.homedir(), ".prismer", "agents");
7891
+ function isPathWhitelisted(repo) {
7892
+ const abs = path2.resolve(repo);
7893
+ const workspaceRoot = process.env.PRISMER_WORKSPACE_ROOT;
7894
+ if (workspaceRoot) {
7895
+ const wsAbs = path2.resolve(workspaceRoot);
7896
+ if (abs === wsAbs || abs.startsWith(wsAbs + path2.sep)) return true;
7897
+ }
7898
+ if (abs.startsWith(SCRATCH_DIR_GLOB + path2.sep)) {
7899
+ const rel = abs.slice(SCRATCH_DIR_GLOB.length + 1);
7900
+ const parts = rel.split(path2.sep);
7901
+ if (parts.length >= 2 && parts[1] === "scratch") return true;
7902
+ }
7903
+ return false;
7904
+ }
7905
+ function walk(dir, glob, acc) {
7906
+ let entries;
7907
+ try {
7908
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
7909
+ } catch {
7910
+ return;
7911
+ }
7912
+ for (const e of entries) {
7913
+ const full = path2.join(dir, e.name);
7914
+ if (e.isDirectory()) {
7915
+ if (e.name === "node_modules" || e.name === ".git" || e.name === "dist" || e.name === "build") continue;
7916
+ walk(full, glob, acc);
7917
+ } else if (e.isFile()) {
7918
+ if (!glob || matchGlob(e.name, glob)) acc.push(full);
7919
+ }
7920
+ }
7921
+ }
7922
+ function matchGlob(name, glob) {
7923
+ if (glob.startsWith("*.")) {
7924
+ return name.endsWith(glob.slice(1));
7925
+ }
7926
+ return name === glob;
7927
+ }
7928
+ function register8(parent, _getIMClient, _getAPIClient) {
7929
+ const code = parent.command("code").description("Local code-source helpers for skill-authoring (release201/07)");
7930
+ code.command("grep <pattern>").description("Grep a whitelisted repo path for a pattern; output matched snippets as JSON").option("--repo <abs-path>", "Absolute repo path (must be inside PRISMER_WORKSPACE_ROOT or ~/.prismer/agents/<id>/scratch)").option("--glob <pattern>", "File-name glob (e.g. *.ts)").option("--max-matches <n>", "Maximum number of matches to return", String(DEFAULT_MAX_MATCHES)).option("--json", "Output JSON (default)", true).action(async (pattern, opts) => {
7931
+ if (!opts.repo) {
7932
+ process.stderr.write("Error: --repo <abs-path> is required\n");
7933
+ process.exit(1);
7934
+ }
7935
+ if (!isPathWhitelisted(opts.repo)) {
7936
+ process.stderr.write(
7937
+ `Error: --repo path ${opts.repo} is not whitelisted; only PRISMER_WORKSPACE_ROOT or ~/.prismer/agents/<id>/scratch are allowed
7938
+ `
7939
+ );
7940
+ process.exit(1);
7941
+ }
7942
+ const maxMatches = parseInt(opts.maxMatches, 10) || DEFAULT_MAX_MATCHES;
7943
+ let re;
7944
+ try {
7945
+ re = new RegExp(pattern);
7946
+ } catch (err) {
7947
+ process.stderr.write(`Error: invalid regex pattern: ${err.message}
7948
+ `);
7949
+ process.exit(1);
7950
+ }
7951
+ const files = [];
7952
+ walk(opts.repo, opts.glob, files);
7953
+ const results = [];
7954
+ for (const file of files) {
7955
+ if (results.length >= maxMatches) break;
7956
+ let body;
7957
+ try {
7958
+ body = fs4.readFileSync(file, "utf-8");
7959
+ } catch {
7960
+ continue;
7961
+ }
7962
+ const lines = body.split("\n");
7963
+ for (let i = 0; i < lines.length; i++) {
7964
+ if (re.test(lines[i])) {
7965
+ const startLine = Math.max(0, i - 1);
7966
+ const endLine = Math.min(lines.length, i + 4);
7967
+ const snippet = lines.slice(startLine, endLine).join("\n");
7968
+ const sha256 = crypto2.createHash("sha256").update(snippet).digest("hex");
7969
+ results.push({
7970
+ path: path2.relative(opts.repo, file),
7971
+ line: i + 1,
7972
+ snippet,
7973
+ sha256
7974
+ });
7975
+ if (results.length >= maxMatches) break;
7976
+ }
7977
+ }
7978
+ }
7979
+ process.stdout.write(JSON.stringify(results, null, 2) + "\n");
7980
+ });
7981
+ }
7982
+
7983
+ // src/commands/service-introspect.ts
7984
+ var FETCH_TIMEOUT_MS = 1e4;
7985
+ async function fetchWithTimeout(url, init = {}) {
7986
+ const ctrl = new AbortController();
7987
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
7988
+ try {
7989
+ return await fetch(url, { ...init, signal: ctrl.signal });
7990
+ } finally {
7991
+ clearTimeout(timer);
7992
+ }
7993
+ }
7994
+ async function probeOpenApi(url) {
7995
+ const base = url.replace(/\/$/, "");
7996
+ const candidates = [
7997
+ `${base}/openapi.json`,
7998
+ `${base}/swagger.json`,
7999
+ `${base}/api/openapi.json`,
8000
+ `${base}/.well-known/openapi.json`
8001
+ ];
8002
+ for (const candidate of candidates) {
8003
+ try {
8004
+ const res = await fetchWithTimeout(candidate, { headers: { Accept: "application/json" } });
8005
+ if (res.ok) {
8006
+ const spec = await res.json();
8007
+ if (spec && (spec.openapi || spec.swagger)) {
8008
+ return { found: true, spec, specUrl: candidate };
8009
+ }
8010
+ }
8011
+ } catch {
8012
+ }
8013
+ }
8014
+ return { found: false };
8015
+ }
8016
+ async function probeMcp(url) {
8017
+ try {
8018
+ const res = await fetchWithTimeout(url, {
8019
+ method: "POST",
8020
+ headers: { "Content-Type": "application/json" },
8021
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" })
8022
+ });
8023
+ if (!res.ok) return { found: false };
8024
+ const body = await res.json();
8025
+ if (body?.result?.tools) {
8026
+ let resources = [];
8027
+ try {
8028
+ const r = await fetchWithTimeout(url, {
8029
+ method: "POST",
8030
+ headers: { "Content-Type": "application/json" },
8031
+ body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "resources/list" })
8032
+ });
8033
+ if (r.ok) {
8034
+ const rb = await r.json();
8035
+ if (Array.isArray(rb?.result?.resources)) resources = rb.result.resources;
8036
+ }
8037
+ } catch {
8038
+ }
8039
+ return { found: true, tools: body.result.tools, resources };
8040
+ }
8041
+ } catch {
8042
+ }
8043
+ return { found: false };
8044
+ }
8045
+ function register9(parent, _getIMClient, _getAPIClient) {
8046
+ const svc = parent.command("service").description("Local service-endpoint helpers for skill-authoring (release201/07)");
8047
+ svc.command("introspect <url>").description("Probe an HTTP / MCP / OpenAPI endpoint and emit a tool/endpoint catalog").option("--protocol <kind>", "Force protocol: auto | mcp | openapi", "auto").option("--json", "Output JSON (default)", true).action(async (url, opts) => {
8048
+ const protocol = opts.protocol ?? "auto";
8049
+ if (protocol !== "auto" && protocol !== "mcp" && protocol !== "openapi") {
8050
+ process.stderr.write(`Error: --protocol must be auto | mcp | openapi (got ${protocol})
8051
+ `);
8052
+ process.exit(1);
8053
+ }
8054
+ const result = { kind: "unknown", tools: [], endpoints: [], probedAt: (/* @__PURE__ */ new Date()).toISOString() };
8055
+ if (protocol === "mcp" || protocol === "auto") {
8056
+ const mcp = await probeMcp(url);
8057
+ if (mcp.found) {
8058
+ result.kind = "mcp";
8059
+ result.tools = mcp.tools ?? [];
8060
+ result.resources = mcp.resources ?? [];
8061
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
8062
+ return;
8063
+ }
8064
+ }
8065
+ if (protocol === "openapi" || protocol === "auto") {
8066
+ const oa = await probeOpenApi(url);
8067
+ if (oa.found) {
8068
+ result.kind = "openapi";
8069
+ result.specUrl = oa.specUrl;
8070
+ const paths = oa.spec?.paths ?? {};
8071
+ for (const p of Object.keys(paths)) {
8072
+ for (const method of Object.keys(paths[p])) {
8073
+ if (["get", "post", "put", "delete", "patch"].includes(method.toLowerCase())) {
8074
+ const op = paths[p][method];
8075
+ result.endpoints.push({
8076
+ path: p,
8077
+ method: method.toUpperCase(),
8078
+ operationId: op?.operationId ?? null,
8079
+ summary: op?.summary ?? null
8080
+ });
8081
+ }
8082
+ }
8083
+ }
8084
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
8085
+ return;
8086
+ }
8087
+ }
8088
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
8089
+ });
8090
+ }
8091
+
8092
+ // src/commands/files.ts
8093
+ function register10(parent, getIMClient2, _getAPIClient) {
8094
+ const file = parent.command("file").description("File upload, transfer, quota, and type management");
8095
+ file.command("upload <path>").description("Upload a file and get its upload ID and CDN URL").option("--mime <type>", "Override MIME type (e.g. image/png)").option("--json", "Output raw JSON response").action(async (filePath, opts) => {
8096
+ const client = getIMClient2();
8097
+ try {
8098
+ const uploadOpts = {};
8099
+ if (opts.mime) uploadOpts.mimeType = opts.mime;
8100
+ const res = await client.im.files.upload(filePath, Object.keys(uploadOpts).length ? uploadOpts : void 0);
8101
+ if (opts.json) {
8102
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6771
8103
  return;
6772
8104
  }
6773
8105
  process.stdout.write(`Uploaded: ${res.fileName}
@@ -6872,8 +8204,43 @@ function register7(parent, getIMClient2, _getAPIClient) {
6872
8204
  }
6873
8205
 
6874
8206
  // src/commands/workspace.ts
6875
- function register8(parent, getIMClient2, _getAPIClient) {
6876
- const workspace = parent.command("workspace").description("Workspace management \u2014 init, groups, and agent assignment");
8207
+ function fail(message) {
8208
+ process.stderr.write(`Error: ${message}
8209
+ `);
8210
+ process.exit(1);
8211
+ }
8212
+ function emitJson(data, json, fallback) {
8213
+ if (json) {
8214
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
8215
+ return;
8216
+ }
8217
+ fallback();
8218
+ }
8219
+ function parseAdminMemberRole(raw) {
8220
+ if (raw === "admin" || raw === "member") return raw;
8221
+ if (raw === void 0) return "member";
8222
+ if (raw === "owner") {
8223
+ fail("--role=owner is not allowed via CLI; use ownership transfer (v2.1+ RFC, release201/16 \xA75.2)");
8224
+ }
8225
+ fail("--role must be admin|member");
8226
+ }
8227
+ function printMemberList(items) {
8228
+ if (items.length === 0) {
8229
+ process.stdout.write("No members in this workspace.\n");
8230
+ return;
8231
+ }
8232
+ process.stdout.write(
8233
+ "ID".padEnd(28) + "ROLE".padEnd(10) + "IM_USER_ID".padEnd(38) + "JOINED\n"
8234
+ );
8235
+ for (const m of items) {
8236
+ process.stdout.write(
8237
+ `${m.id.padEnd(28)}${m.role.padEnd(10)}${m.memberImUserId.padEnd(38)}${m.joinedAt}
8238
+ `
8239
+ );
8240
+ }
8241
+ }
8242
+ function register11(parent, getIMClient2, _getAPIClient) {
8243
+ const workspace = parent.command("workspace").description("Workspace management \u2014 init, groups, agent assignment, and members (release201/16)");
6877
8244
  workspace.command("init <name>").description("Initialize a workspace with a user and agent").requiredOption("--user-id <id>", "User ID").requiredOption("--user-name <name>", "User display name").requiredOption("--agent-id <id>", "Agent ID").requiredOption("--agent-name <name>", "Agent display name").option("--agent-type <type>", "Agent type", "assistant").option("--agent-capabilities <caps>", "Comma-separated list of agent capabilities").option("--json", "Output raw JSON response").action(async (name, opts) => {
6878
8245
  const client = getIMClient2();
6879
8246
  try {
@@ -6928,68 +8295,305 @@ function register8(parent, getIMClient2, _getAPIClient) {
6928
8295
  }
6929
8296
  process.stdout.write(`Group workspace initialized (workspaceId: ${res.data?.workspaceId})
6930
8297
  `);
6931
- } catch (err) {
6932
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8298
+ } catch (err) {
8299
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8300
+ `);
8301
+ process.exit(1);
8302
+ }
8303
+ });
8304
+ workspace.command("add-agent <workspace-id> <agent-id>").description("Add an agent to a workspace").option("--json", "Output raw JSON response").action(async (workspaceId, agentId, opts) => {
8305
+ const client = getIMClient2();
8306
+ try {
8307
+ const res = await client.im.workspace.addAgent(workspaceId, agentId);
8308
+ if (!res.ok) {
8309
+ process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
8310
+ `);
8311
+ process.exit(1);
8312
+ }
8313
+ if (opts.json) {
8314
+ process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
8315
+ return;
8316
+ }
8317
+ process.stdout.write(`Agent ${agentId} added to workspace ${workspaceId}.
8318
+ `);
8319
+ } catch (err) {
8320
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8321
+ `);
8322
+ process.exit(1);
8323
+ }
8324
+ });
8325
+ workspace.command("agents <workspace-id>").description("List agents in a workspace").option("--json", "Output raw JSON response").action(async (workspaceId, opts) => {
8326
+ const client = getIMClient2();
8327
+ try {
8328
+ const res = await client.im.workspace.listAgents(workspaceId);
8329
+ if (!res.ok) {
8330
+ process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
8331
+ `);
8332
+ process.exit(1);
8333
+ }
8334
+ const agents = res.data || [];
8335
+ if (opts.json) {
8336
+ process.stdout.write(JSON.stringify(agents, null, 2) + "\n");
8337
+ return;
8338
+ }
8339
+ if (agents.length === 0) {
8340
+ process.stdout.write("No agents in this workspace.\n");
8341
+ return;
8342
+ }
8343
+ process.stdout.write("Agent ID".padEnd(36) + "Type".padEnd(14) + "Name\n");
8344
+ for (const a of agents) {
8345
+ process.stdout.write(
8346
+ `${(a.agentId || a.id || "").padEnd(36)}${(a.agentType || "").padEnd(14)}${a.name || a.displayName || ""}
8347
+ `
8348
+ );
8349
+ }
8350
+ } catch (err) {
8351
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8352
+ `);
8353
+ process.exit(1);
8354
+ }
8355
+ });
8356
+ const member = workspace.command("member").description("Workspace membership management (release201/16)");
8357
+ member.command("list <workspaceId>").description("List members of a workspace (any member can read)").option("--json", "Output JSON").action(async (workspaceId, opts) => {
8358
+ const client = getIMClient2();
8359
+ const res = await client.workspaces.members.list(workspaceId);
8360
+ if (!res.ok || !res.data) fail(res.error?.message || "list members failed");
8361
+ emitJson(res.data, opts.json === true, () => printMemberList(res.data));
8362
+ });
8363
+ member.command("add <workspaceId>").description("Add an existing user to a workspace as admin or member (owner only)").requiredOption("--user <imUserId>", "IM user id of the new member").option("--role <role>", "admin | member (default member)").option("--json", "Output JSON").action(async (workspaceId, opts) => {
8364
+ const role = parseAdminMemberRole(opts.role);
8365
+ const client = getIMClient2();
8366
+ const res = await client.workspaces.members.add(workspaceId, {
8367
+ memberImUserId: opts.user.trim(),
8368
+ role
8369
+ });
8370
+ if (!res.ok || !res.data) fail(res.error?.message || "add member failed");
8371
+ emitJson(res.data, opts.json === true, () => {
8372
+ const m = res.data;
8373
+ process.stdout.write(`Member added: ${m.memberImUserId} \u2192 ${m.role} (${m.id})
8374
+ `);
8375
+ });
8376
+ });
8377
+ member.command("update <workspaceId> <memberId>").description("Change a member role (owner only; owner row is immutable)").requiredOption("--role <role>", "admin | member").option("--json", "Output JSON").action(async (workspaceId, memberId, opts) => {
8378
+ const role = parseAdminMemberRole(opts.role);
8379
+ const client = getIMClient2();
8380
+ const res = await client.workspaces.members.update(workspaceId, memberId, { role });
8381
+ if (!res.ok || !res.data) fail(res.error?.message || "update member failed");
8382
+ emitJson(res.data, opts.json === true, () => {
8383
+ const m = res.data;
8384
+ process.stdout.write(`Member ${m.id} role \u2192 ${m.role}
8385
+ `);
8386
+ });
8387
+ });
8388
+ member.command("remove <workspaceId> <memberId>").description("Remove a member (owner only); cascades project memberships in the same workspace").option("--json", "Output JSON").action(async (workspaceId, memberId, opts) => {
8389
+ const client = getIMClient2();
8390
+ const res = await client.workspaces.members.remove(workspaceId, memberId);
8391
+ if (!res.ok || !res.data) fail(res.error?.message || "remove member failed");
8392
+ emitJson(res.data, opts.json === true, () => {
8393
+ const r = res.data;
8394
+ process.stdout.write(
8395
+ `Member removed: ${r.removed.memberImUserId} (${r.removed.id}); project memberships cascaded: ${r.projectMembershipsRemoved}
8396
+ `
8397
+ );
8398
+ });
8399
+ });
8400
+ }
8401
+
8402
+ // src/commands/project.ts
8403
+ function fail2(message) {
8404
+ process.stderr.write(`Error: ${message}
8405
+ `);
8406
+ process.exit(1);
8407
+ }
8408
+ function emit(data, json, lines) {
8409
+ if (json) {
8410
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
8411
+ return;
8412
+ }
8413
+ lines();
8414
+ }
8415
+ function parsePrincipal(raw) {
8416
+ const idx = raw.indexOf(":");
8417
+ if (idx <= 0) fail2("--principal must be `user:<id>` or `agent:<id>`");
8418
+ const kind = raw.slice(0, idx);
8419
+ const id = raw.slice(idx + 1).trim();
8420
+ if (kind !== "user" && kind !== "agent") fail2("principal kind must be user|agent");
8421
+ if (!id) fail2("principal id must be non-empty");
8422
+ return { kind, id };
8423
+ }
8424
+ function parseRole(raw) {
8425
+ if (raw === void 0) return void 0;
8426
+ if (raw === "owner" || raw === "contributor" || raw === "observer") return raw;
8427
+ fail2("--role must be owner|contributor|observer");
8428
+ }
8429
+ function printProjectList(items) {
8430
+ if (items.length === 0) {
8431
+ process.stdout.write("No projects found.\n");
8432
+ return;
8433
+ }
8434
+ process.stdout.write(
8435
+ "ID".padEnd(28) + "SLUG".padEnd(20) + "STATUS".padEnd(10) + "MEMBERS".padEnd(10) + "NAME\n"
8436
+ );
8437
+ for (const p of items) {
8438
+ process.stdout.write(
8439
+ `${p.id.padEnd(28)}${p.slug.padEnd(20)}${p.status.padEnd(10)}${String(p.memberCount).padEnd(10)}${p.name}
8440
+ `
8441
+ );
8442
+ }
8443
+ }
8444
+ function printProject(p, heading = "Project") {
8445
+ process.stdout.write(`${heading}: ${p.name}
8446
+ `);
8447
+ process.stdout.write(` id ${p.id}
8448
+ `);
8449
+ process.stdout.write(` slug ${p.slug}
8450
+ `);
8451
+ process.stdout.write(` workspaceId ${p.workspaceId}
8452
+ `);
8453
+ process.stdout.write(` status ${p.status}
8454
+ `);
8455
+ process.stdout.write(` owner ${p.ownerUserId}
8456
+ `);
8457
+ if ("memberCount" in p) process.stdout.write(` members ${p.memberCount}
6933
8458
  `);
6934
- process.exit(1);
8459
+ if (p.description) process.stdout.write(` description ${p.description}
8460
+ `);
8461
+ if (p.archivedAt) process.stdout.write(` archivedAt ${p.archivedAt}
8462
+ `);
8463
+ process.stdout.write(` createdAt ${p.createdAt}
8464
+ `);
8465
+ process.stdout.write(` updatedAt ${p.updatedAt}
8466
+ `);
8467
+ }
8468
+ function printMemberList2(items) {
8469
+ if (items.length === 0) {
8470
+ process.stdout.write("No members in this project.\n");
8471
+ return;
8472
+ }
8473
+ process.stdout.write(
8474
+ "ID".padEnd(28) + "KIND".padEnd(8) + "PRINCIPAL".padEnd(38) + "ROLE".padEnd(14) + "JOINED\n"
8475
+ );
8476
+ for (const m of items) {
8477
+ process.stdout.write(
8478
+ `${m.id.padEnd(28)}${m.principalKind.padEnd(8)}${m.principalId.padEnd(38)}${m.role.padEnd(14)}${m.joinedAt}
8479
+ `
8480
+ );
8481
+ }
8482
+ }
8483
+ function register12(parent, getIMClient2, _getAPIClient) {
8484
+ const project = parent.command("project").description("Project scope (release201/09) \u2014 CRUD + membership management");
8485
+ project.command("list").description("List projects in a workspace").requiredOption("--workspace <id>", "Workspace id").option("--status <status>", "Filter by status: active|archived").option("--search <q>", "Search name/slug substring").option("--limit <n>", "Page size (1-200)", (v) => Number(v)).option("--offset <n>", "Page offset", (v) => Number(v)).option("--json", "Output JSON").action(async (opts) => {
8486
+ if (opts.status && opts.status !== "active" && opts.status !== "archived") {
8487
+ fail2("--status must be active|archived");
6935
8488
  }
8489
+ const client = getIMClient2();
8490
+ const res = await client.projects.list({
8491
+ workspaceId: opts.workspace,
8492
+ status: opts.status,
8493
+ search: opts.search,
8494
+ limit: opts.limit,
8495
+ offset: opts.offset
8496
+ });
8497
+ if (!res.ok || !res.data) fail2(res.error?.message || "list failed");
8498
+ const data = res.data;
8499
+ emit(data, opts.json === true, () => {
8500
+ printProjectList(data.items);
8501
+ process.stdout.write(`
8502
+ Total: ${data.total}
8503
+ `);
8504
+ });
6936
8505
  });
6937
- workspace.command("add-agent <workspace-id> <agent-id>").description("Add an agent to a workspace").option("--json", "Output raw JSON response").action(async (workspaceId, agentId, opts) => {
8506
+ project.command("create").description("Create a project in a workspace").requiredOption("--workspace <id>", "Workspace id").requiredOption("--slug <slug>", "Project slug (1-64 chars, [a-z0-9-])").requiredOption("--name <name>", "Project display name").option("--description <text>", "Project description").option("--json", "Output JSON").action(async (opts) => {
6938
8507
  const client = getIMClient2();
6939
- try {
6940
- const res = await client.im.workspace.addAgent(workspaceId, agentId);
6941
- if (!res.ok) {
6942
- process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
8508
+ const res = await client.projects.create({
8509
+ workspaceId: opts.workspace,
8510
+ slug: opts.slug,
8511
+ name: opts.name,
8512
+ description: opts.description ?? null
8513
+ });
8514
+ if (!res.ok || !res.data) fail2(res.error?.message || "create failed");
8515
+ emit(res.data, opts.json === true, () => printProject(res.data, "Project created"));
8516
+ });
8517
+ project.command("show <projectId>").description("Show a project (with member count)").option("--json", "Output JSON").action(async (projectId, opts) => {
8518
+ const client = getIMClient2();
8519
+ const res = await client.projects.get(projectId);
8520
+ if (!res.ok || !res.data) fail2(res.error?.message || "show failed");
8521
+ emit(res.data, opts.json === true, () => printProject(res.data));
8522
+ });
8523
+ project.command("update <projectId>").description("Update project name / description / status").option("--name <name>", "New display name").option("--description <text>", "New description (empty string to clear)").option("--archive", "Set status=archived").option("--unarchive", "Set status=active").option("--json", "Output JSON").action(async (projectId, opts) => {
8524
+ if (opts.archive && opts.unarchive) fail2("--archive and --unarchive are mutually exclusive");
8525
+ const patch = {};
8526
+ if (opts.name !== void 0) patch.name = opts.name;
8527
+ if (opts.description !== void 0) patch.description = opts.description === "" ? null : opts.description;
8528
+ if (opts.archive) patch.status = "archived";
8529
+ if (opts.unarchive) patch.status = "active";
8530
+ if (Object.keys(patch).length === 0) fail2("Nothing to update \u2014 pass --name / --description / --archive / --unarchive");
8531
+ const client = getIMClient2();
8532
+ const res = await client.projects.update(projectId, patch);
8533
+ if (!res.ok || !res.data) fail2(res.error?.message || "update failed");
8534
+ emit(res.data, opts.json === true, () => printProject(res.data, "Project updated"));
8535
+ });
8536
+ project.command("delete <projectId>").description("Soft-delete a project (default cascade=archive). cascade=hard is reserved for v2.0.8+.").option("--cascade <mode>", "archive | null | hard", "archive").option("--json", "Output JSON").action(async (projectId, opts) => {
8537
+ const cascade = opts.cascade ?? "archive";
8538
+ if (cascade !== "archive" && cascade !== "null" && cascade !== "hard") {
8539
+ fail2("--cascade must be archive|null|hard");
8540
+ }
8541
+ const client = getIMClient2();
8542
+ const res = await client.projects.delete(projectId, { cascade });
8543
+ if (!res.ok) fail2(res.error?.message || "delete failed");
8544
+ emit(res.data, opts.json === true, () => {
8545
+ if (res.data) {
8546
+ printProject(res.data, "Project archived");
8547
+ } else {
8548
+ process.stdout.write(`Project ${projectId} archived.
6943
8549
  `);
6944
- process.exit(1);
6945
- }
6946
- if (opts.json) {
6947
- process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
6948
- return;
6949
8550
  }
6950
- process.stdout.write(`Agent ${agentId} added to workspace ${workspaceId}.
6951
- `);
6952
- } catch (err) {
6953
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8551
+ });
8552
+ });
8553
+ const members = project.command("members").description("Manage project memberships");
8554
+ members.command("list <projectId>").description("List members of a project").option("--json", "Output JSON").action(async (projectId, opts) => {
8555
+ const client = getIMClient2();
8556
+ const res = await client.projects.members.list(projectId);
8557
+ if (!res.ok || !res.data) fail2(res.error?.message || "list members failed");
8558
+ emit(res.data, opts.json === true, () => printMemberList2(res.data));
8559
+ });
8560
+ members.command("add <projectId>").description("Add a user or agent to the project").requiredOption("--principal <p>", "Principal in `user:<id>` or `agent:<id>` form").option("--role <role>", "owner | contributor | observer (default contributor)").option("--json", "Output JSON").action(async (projectId, opts) => {
8561
+ const { kind, id } = parsePrincipal(opts.principal);
8562
+ const role = parseRole(opts.role);
8563
+ const client = getIMClient2();
8564
+ const res = await client.projects.members.add(projectId, { principalKind: kind, principalId: id, role });
8565
+ if (!res.ok || !res.data) fail2(res.error?.message || "add member failed");
8566
+ emit(res.data, opts.json === true, () => {
8567
+ const m = res.data;
8568
+ process.stdout.write(`Member added: ${m.principalKind}:${m.principalId} \u2192 ${m.role} (${m.id})
6954
8569
  `);
6955
- process.exit(1);
6956
- }
8570
+ });
6957
8571
  });
6958
- workspace.command("agents <workspace-id>").description("List agents in a workspace").option("--json", "Output raw JSON response").action(async (workspaceId, opts) => {
8572
+ members.command("update <projectId> <membershipId>").description("Change a member role").requiredOption("--role <role>", "owner | contributor | observer").option("--json", "Output JSON").action(async (projectId, membershipId, opts) => {
8573
+ const role = parseRole(opts.role);
8574
+ if (!role) fail2("--role is required");
6959
8575
  const client = getIMClient2();
6960
- try {
6961
- const res = await client.im.workspace.listAgents(workspaceId);
6962
- if (!res.ok) {
6963
- process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
8576
+ const res = await client.projects.members.update(projectId, membershipId, { role });
8577
+ if (!res.ok || !res.data) fail2(res.error?.message || "update member failed");
8578
+ emit(res.data, opts.json === true, () => {
8579
+ const m = res.data;
8580
+ process.stdout.write(`Member ${m.id} role \u2192 ${m.role}
6964
8581
  `);
6965
- process.exit(1);
6966
- }
6967
- const agents = res.data || [];
6968
- if (opts.json) {
6969
- process.stdout.write(JSON.stringify(agents, null, 2) + "\n");
6970
- return;
6971
- }
6972
- if (agents.length === 0) {
6973
- process.stdout.write("No agents in this workspace.\n");
6974
- return;
6975
- }
6976
- process.stdout.write("Agent ID".padEnd(36) + "Type".padEnd(14) + "Name\n");
6977
- for (const a of agents) {
6978
- process.stdout.write(
6979
- `${(a.agentId || a.id || "").padEnd(36)}${(a.agentType || "").padEnd(14)}${a.name || a.displayName || ""}
6980
- `
6981
- );
6982
- }
6983
- } catch (err) {
6984
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8582
+ });
8583
+ });
8584
+ members.command("remove <projectId> <membershipId>").description("Remove a member from the project").option("--json", "Output JSON").action(async (projectId, membershipId, opts) => {
8585
+ const client = getIMClient2();
8586
+ const res = await client.projects.members.remove(projectId, membershipId);
8587
+ if (!res.ok) fail2(res.error?.message || "remove member failed");
8588
+ emit({ ok: true }, opts.json === true, () => {
8589
+ process.stdout.write(`Membership ${membershipId} removed.
6985
8590
  `);
6986
- process.exit(1);
6987
- }
8591
+ });
6988
8592
  });
6989
8593
  }
6990
8594
 
6991
8595
  // src/commands/security.ts
6992
- function register9(parent, getIMClient2, _getAPIClient) {
8596
+ function register13(parent, getIMClient2, _getAPIClient) {
6993
8597
  const security = parent.command("security").description("Per-conversation encryption and key management");
6994
8598
  security.command("get <conversation-id>").description("Get security settings for a conversation").option("--json", "Output raw JSON response").action(async (convId, opts) => {
6995
8599
  const client = getIMClient2();
@@ -7269,7 +8873,7 @@ function register9(parent, getIMClient2, _getAPIClient) {
7269
8873
  }
7270
8874
 
7271
8875
  // src/commands/community.ts
7272
- var import_node_fs = require("fs");
8876
+ var import_node_fs2 = require("fs");
7273
8877
  function printJson(res, opts) {
7274
8878
  if (opts.json) {
7275
8879
  console.log(JSON.stringify(res, null, 2));
@@ -7296,7 +8900,7 @@ _Next cursor:_ \`${d.nextCursor}\`
7296
8900
  `;
7297
8901
  return t;
7298
8902
  }
7299
- function register10(parent, getIMClient2, _getAPIClient) {
8903
+ function register14(parent, getIMClient2, _getAPIClient) {
7300
8904
  const comm = parent.command("community").description("Evolution community forum \u2014 feed, ask, search, notify");
7301
8905
  comm.command("feed").description("Browse posts (uses hub cache when fresh)").option("-b, --board <id>", "Board: showcase, genelab, helpdesk, ideas, changelog").option("-n, --limit <n>", "Max posts", "15").option("--json", "JSON output").action(async (opts) => {
7302
8906
  const c = getIMClient2();
@@ -7315,7 +8919,7 @@ function register10(parent, getIMClient2, _getAPIClient) {
7315
8919
  process.stdout.write(formatPostsMarkdown(res.data));
7316
8920
  });
7317
8921
  comm.command("ask").description("Post a helpdesk question").argument("<title>", "Title").argument("[body]", "Body (Markdown); omit if using --file").option("-f, --file <path>", "Read body from file").option("--tags <csv>", "Comma-separated tags").option("--json", "JSON output").action(async (title, body, opts) => {
7318
- const content = opts.file ? (0, import_node_fs.readFileSync)(opts.file, "utf8") : body || "(no body)";
8922
+ const content = opts.file ? (0, import_node_fs2.readFileSync)(opts.file, "utf8") : body || "(no body)";
7319
8923
  const tags = opts.tags?.split(",").map((s) => s.trim()).filter(Boolean);
7320
8924
  const c = getIMClient2();
7321
8925
  const res = await c.im.community.ask(title, content, tags);
@@ -7397,8 +9001,8 @@ function register10(parent, getIMClient2, _getAPIClient) {
7397
9001
  }
7398
9002
 
7399
9003
  // src/commands/asset.ts
7400
- var fs2 = __toESM(require("fs"));
7401
- var path2 = __toESM(require("path"));
9004
+ var fs5 = __toESM(require("fs"));
9005
+ var path3 = __toESM(require("path"));
7402
9006
  function resolveWorkspaceId(flag) {
7403
9007
  if (flag) return flag;
7404
9008
  if (typeof process !== "undefined" && process.env?.PRISMER_WORKSPACE_ID) {
@@ -7432,7 +9036,7 @@ function formatBytes(n) {
7432
9036
  if (n == null) return "-";
7433
9037
  return String(n);
7434
9038
  }
7435
- function register11(parent, getIMClient2, _getAPIClient) {
9039
+ function register15(parent, getIMClient2, _getAPIClient) {
7436
9040
  const asset = parent.command("asset").description("Inspect and manage workspace assets (content-addressed)");
7437
9041
  asset.command("list").description("List assets in a workspace").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--filename <glob>", 'filter by filename glob (client-side, e.g. "design-doc*")').option("--mime <type>", 'filter by MIME type prefix (e.g. "application/pdf")').option("--kind <kind>", "filter by row-level kind (e.g. file, image, sandbox-output)").option("--updated-after <iso>", "filter to assets created/updated after this ISO timestamp").option("--task-id <id>", "filter by source task id").option("-n, --limit <n>", "maximum results to return", "50").option("--json", "output raw JSON response").action(async (opts) => {
7438
9042
  const wsId = requireWorkspaceId(opts.workspaceId);
@@ -7592,7 +9196,7 @@ ${rows.length} asset(s) listed.
7592
9196
  });
7593
9197
  asset.command("upload <path>").description("Upload a local file as a workspace asset").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--conversation-id <id>", "pin upload to a conversation (stored in metadata)").option("--kind <kind>", "asset kind label", "user-upload").option("--task-id <id>", "source task id").option("--mime <type>", "override detected MIME type").option("--filename <name>", "override filename").option("--json", "output raw JSON response").action(async (filePath, opts) => {
7594
9198
  const wsId = requireWorkspaceId(opts.workspaceId);
7595
- if (!fs2.existsSync(filePath)) {
9199
+ if (!fs5.existsSync(filePath)) {
7596
9200
  process.stderr.write(`Error: file not found: ${filePath}
7597
9201
  `);
7598
9202
  process.exit(1);
@@ -7622,7 +9226,7 @@ ${rows.length} asset(s) listed.
7622
9226
  process.exit(1);
7623
9227
  }
7624
9228
  const a = res.data;
7625
- process.stdout.write(`Uploaded ${path2.basename(filePath)}
9229
+ process.stdout.write(`Uploaded ${path3.basename(filePath)}
7626
9230
  `);
7627
9231
  process.stdout.write(` ID: ${a.id}
7628
9232
  `);
@@ -7649,7 +9253,7 @@ ${rows.length} asset(s) listed.
7649
9253
  opts.length
7650
9254
  );
7651
9255
  if (opts.out) {
7652
- fs2.writeFileSync(opts.out, bytes);
9256
+ fs5.writeFileSync(opts.out, bytes);
7653
9257
  const range = describeRange(opts.offset, opts.length, bytes.byteLength);
7654
9258
  process.stderr.write(
7655
9259
  `Downloaded ${assetId} -> ${opts.out} (${bytes.byteLength} bytes${range ? `, ${range}` : ""}${truncated ? ", truncated" : ""}${totalSize != null ? `, total=${totalSize}` : ""})
@@ -7797,7 +9401,7 @@ function resolveWorkspaceId2(flag) {
7797
9401
  }
7798
9402
  return void 0;
7799
9403
  }
7800
- function register12(parent, getIMClient2, _getAPIClient) {
9404
+ function register16(parent, getIMClient2, _getAPIClient) {
7801
9405
  const approval = parent.command("approval").description("Submit and manage human approval requests");
7802
9406
  approval.command("request-human").description("Submit a human approval request and stop the current turn").requiredOption("--action <text>", "one-sentence summary of the gated action").requiredOption("--context <text>", "multi-sentence framing for the human").requiredOption("--risk <text>", "what breaks if approved wrongly; what is reversible").option("--options <opt...>", "explicit choice values (defaults to approve/reject if omitted)").option("--task-id <id>", "task to resume when the human decides").option("--conversation-id <id>", "conversation context for the request").option("--workspace-id <id>", "workspace id (defaults to PRISMER_WORKSPACE_ID env)").option("--category <category>", "approval category label", "human-approval").option("--expires-in <seconds>", "expiration window in seconds (default 24h)", parseIntOpt2).option("--json", "output raw JSON response").action(async (opts) => {
7803
9407
  if (!opts.conversationId && !opts.taskId) {
@@ -7875,7 +9479,69 @@ function parseIntOpt2(value) {
7875
9479
  }
7876
9480
 
7877
9481
  // src/commands/agent.ts
7878
- function register13(parent, getIMClient2, _getAPIClient) {
9482
+ var import_node_crypto2 = require("crypto");
9483
+ var import_node_fs3 = require("fs");
9484
+ var import_node_os = require("os");
9485
+ var import_node_path2 = require("path");
9486
+ var import_node_child_process = require("child_process");
9487
+ function resolvePrismerRoot() {
9488
+ return process.env.PRISMER_HOME ? (0, import_node_path2.resolve)(process.env.PRISMER_HOME) : (0, import_node_path2.join)((0, import_node_os.homedir)(), ".prismer");
9489
+ }
9490
+ function readDaemonConfig() {
9491
+ const path6 = (0, import_node_path2.join)(resolvePrismerRoot(), "config.toml");
9492
+ if (!(0, import_node_fs3.existsSync)(path6)) {
9493
+ throw new Error(`${path6} not found. Run \`prismer setup\` first.`);
9494
+ }
9495
+ const raw = (0, import_node_fs3.readFileSync)(path6, "utf8");
9496
+ const idMatch = raw.match(/^daemon_id\s*=\s*"([^"]+)"/m);
9497
+ const keyMatch = raw.match(/^api_key\s*=\s*"([^"]+)"/m);
9498
+ if (!idMatch || !keyMatch) {
9499
+ throw new Error(`config.toml missing daemon_id or api_key (${path6})`);
9500
+ }
9501
+ return { daemonId: idMatch[1], apiKey: keyMatch[1] };
9502
+ }
9503
+ function resolveAgentDir(daemonId, agentId) {
9504
+ return (0, import_node_path2.join)(resolvePrismerRoot(), "devices", daemonId, "agents", agentId);
9505
+ }
9506
+ function collectFiles(root, excludeBasenames) {
9507
+ const out = [];
9508
+ function walk2(dir) {
9509
+ let entries;
9510
+ try {
9511
+ entries = (0, import_node_fs3.readdirSync)(dir);
9512
+ } catch {
9513
+ return;
9514
+ }
9515
+ for (const name of entries) {
9516
+ if (excludeBasenames.has(name)) continue;
9517
+ const full = (0, import_node_path2.join)(dir, name);
9518
+ let st;
9519
+ try {
9520
+ st = (0, import_node_fs3.statSync)(full);
9521
+ } catch {
9522
+ continue;
9523
+ }
9524
+ if (st.isDirectory()) walk2(full);
9525
+ else if (st.isFile()) {
9526
+ const buf = (0, import_node_fs3.readFileSync)(full);
9527
+ const sha = (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
9528
+ out.push({
9529
+ rel: (0, import_node_path2.relative)(root, full).split(import_node_path2.sep).join("/"),
9530
+ sha,
9531
+ size: buf.byteLength
9532
+ });
9533
+ }
9534
+ }
9535
+ }
9536
+ walk2(root);
9537
+ out.sort((a, b) => a.rel.localeCompare(b.rel));
9538
+ return out;
9539
+ }
9540
+ function computeMerkle(files) {
9541
+ const lines = files.map((f) => `${f.rel}:${f.sha}`).join("\n");
9542
+ return (0, import_node_crypto2.createHash)("sha256").update(lines).digest("hex");
9543
+ }
9544
+ function register17(parent, getIMClient2, _getAPIClient) {
7879
9545
  const agent = parent.command("agent").description("Manage agent specs, snapshots, publish, and fork");
7880
9546
  agent.command("spec <agent-id>").description("Read an AgentSpec 4-tuple").option("--workspace-id <id>", "Workspace scope").option("--json", "Output raw JSON response").action(async (agentId, opts) => {
7881
9547
  const res = await getIMClient2().im.agents.spec(agentId, opts.workspaceId);
@@ -7954,6 +9620,244 @@ function register13(parent, getIMClient2, _getAPIClient) {
7954
9620
  }
7955
9621
  });
7956
9622
  });
9623
+ agent.command("export <agent-id>").description("release201/09 \xA79.7 \u2014 quiesce + tar an agent directory for transfer").requiredOption("--output <file>", "Output tar.gz path").option("--force", "Skip cloud pause (use when source device is offline)").option("--json", "Output raw JSON result").action(async (agentId, opts) => {
9624
+ const { daemonId, apiKey } = readDaemonConfig();
9625
+ const agentDir = resolveAgentDir(daemonId, agentId);
9626
+ if (!(0, import_node_fs3.existsSync)(agentDir)) {
9627
+ process.stderr.write(`Error: agent dir not found at ${agentDir}
9628
+ `);
9629
+ process.exit(1);
9630
+ }
9631
+ if (!opts.force) {
9632
+ const res = await getIMClient2().im.agents.pause(agentId);
9633
+ if (!res.ok) {
9634
+ process.stderr.write(
9635
+ `Error: pause failed (${res.error?.message ?? "unknown"}); pass --force to export anyway
9636
+ `
9637
+ );
9638
+ process.exit(1);
9639
+ }
9640
+ }
9641
+ const excludeBasenames = /* @__PURE__ */ new Set(["transfer-manifest.json"]);
9642
+ const files = collectFiles(agentDir, excludeBasenames);
9643
+ const sha256 = computeMerkle(files);
9644
+ const signature = (0, import_node_crypto2.createHmac)("sha256", apiKey).update(sha256).digest("hex");
9645
+ const manifest = {
9646
+ version: 1,
9647
+ agentId,
9648
+ fromDaemonId: daemonId,
9649
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
9650
+ includes: ["profile.json", "skills/**", "memory/**", "outbox/*"],
9651
+ excludes: ["cache/ (daemon-level blob pool)", "local.db (daemon-level)"],
9652
+ sha256,
9653
+ signature
9654
+ };
9655
+ (0, import_node_fs3.writeFileSync)((0, import_node_path2.join)(agentDir, "transfer-manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
9656
+ const parentOfAgents = (0, import_node_path2.join)(resolvePrismerRoot(), "devices", daemonId);
9657
+ const outputAbs = (0, import_node_path2.resolve)(opts.output);
9658
+ try {
9659
+ (0, import_node_child_process.execFileSync)("tar", ["-czf", outputAbs, "-C", parentOfAgents, (0, import_node_path2.join)("agents", agentId)], { stdio: "inherit" });
9660
+ } catch (err) {
9661
+ process.stderr.write(`Error: tar failed: ${err.message}
9662
+ `);
9663
+ process.exit(1);
9664
+ }
9665
+ const result = {
9666
+ agentId,
9667
+ fromDaemonId: daemonId,
9668
+ output: outputAbs,
9669
+ manifestSha256: sha256,
9670
+ filesIncluded: files.length,
9671
+ totalBytes: files.reduce((s, f) => s + f.size, 0),
9672
+ paused: !opts.force
9673
+ };
9674
+ if (opts.json) {
9675
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
9676
+ } else {
9677
+ console.log(`Exported agent ${agentId}`);
9678
+ console.log(` output: ${result.output}`);
9679
+ console.log(` files: ${result.filesIncluded}`);
9680
+ console.log(` total bytes: ${result.totalBytes}`);
9681
+ console.log(` manifestSha: ${result.manifestSha256.slice(0, 16)}\u2026`);
9682
+ console.log(` cloud paused: ${result.paused ? "yes" : "no (--force)"}`);
9683
+ console.log("");
9684
+ console.log("Next: copy the tar.gz to the target device and run:");
9685
+ console.log(` prismer agent import ${result.output}`);
9686
+ }
9687
+ });
9688
+ agent.command("import <tar-file>").description("release201/09 \xA79.7 \u2014 extract + rebind an agent transferred from another device").option("--json", "Output raw JSON result").action(async (tarFile, opts) => {
9689
+ const tarAbs = (0, import_node_path2.resolve)(tarFile);
9690
+ if (!(0, import_node_fs3.existsSync)(tarAbs)) {
9691
+ process.stderr.write(`Error: tar file not found: ${tarAbs}
9692
+ `);
9693
+ process.exit(1);
9694
+ }
9695
+ const { daemonId: toDaemonId, apiKey } = readDaemonConfig();
9696
+ const tmpRoot = (0, import_node_path2.join)(resolvePrismerRoot(), ".transfer-staging", `${Date.now()}`);
9697
+ (0, import_node_fs3.mkdirSync)(tmpRoot, { recursive: true });
9698
+ try {
9699
+ (0, import_node_child_process.execFileSync)("tar", ["-xzf", tarAbs, "-C", tmpRoot], { stdio: "inherit" });
9700
+ } catch (err) {
9701
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9702
+ process.stderr.write(`Error: tar extract failed: ${err.message}
9703
+ `);
9704
+ process.exit(1);
9705
+ }
9706
+ const agentsRoot = (0, import_node_path2.join)(tmpRoot, "agents");
9707
+ if (!(0, import_node_fs3.existsSync)(agentsRoot)) {
9708
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9709
+ process.stderr.write("Error: tar layout invalid \u2014 no agents/ root inside archive\n");
9710
+ process.exit(1);
9711
+ }
9712
+ const entries = (0, import_node_fs3.readdirSync)(agentsRoot);
9713
+ if (entries.length !== 1) {
9714
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9715
+ process.stderr.write(`Error: expected exactly 1 agent in archive, got ${entries.length}
9716
+ `);
9717
+ process.exit(1);
9718
+ }
9719
+ const agentId = entries[0];
9720
+ const stagedAgentDir = (0, import_node_path2.join)(agentsRoot, agentId);
9721
+ const manifestPath = (0, import_node_path2.join)(stagedAgentDir, "transfer-manifest.json");
9722
+ if (!(0, import_node_fs3.existsSync)(manifestPath)) {
9723
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9724
+ process.stderr.write("Error: transfer-manifest.json missing from archive\n");
9725
+ process.exit(1);
9726
+ }
9727
+ let manifest;
9728
+ try {
9729
+ manifest = JSON.parse((0, import_node_fs3.readFileSync)(manifestPath, "utf8"));
9730
+ } catch (err) {
9731
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9732
+ process.stderr.write(`Error: manifest parse failed: ${err.message}
9733
+ `);
9734
+ process.exit(1);
9735
+ }
9736
+ const expectedSig = (0, import_node_crypto2.createHmac)("sha256", apiKey).update(manifest.sha256).digest("hex");
9737
+ if (expectedSig !== manifest.signature) {
9738
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9739
+ process.stderr.write(
9740
+ "Error: manifest signature verification failed \u2014 refusing to import (different workspace, or tampered archive)\n"
9741
+ );
9742
+ process.exit(1);
9743
+ }
9744
+ if (manifest.agentId !== agentId) {
9745
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9746
+ process.stderr.write(
9747
+ `Error: manifest agentId (${manifest.agentId}) != extracted dir name (${agentId})
9748
+ `
9749
+ );
9750
+ process.exit(1);
9751
+ }
9752
+ if (manifest.fromDaemonId === toDaemonId) {
9753
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9754
+ process.stderr.write(
9755
+ `Error: fromDaemonId == toDaemonId (${toDaemonId}); nothing to transfer.
9756
+ `
9757
+ );
9758
+ process.exit(1);
9759
+ }
9760
+ const targetAgentDir = resolveAgentDir(toDaemonId, agentId);
9761
+ if ((0, import_node_fs3.existsSync)(targetAgentDir)) {
9762
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9763
+ process.stderr.write(
9764
+ `Error: target dir already exists at ${targetAgentDir}. Run \`prismer agent remove ${agentId}\` (TODO) or move it aside first.
9765
+ `
9766
+ );
9767
+ process.exit(1);
9768
+ }
9769
+ (0, import_node_fs3.mkdirSync)((0, import_node_path2.join)(resolvePrismerRoot(), "devices", toDaemonId, "agents"), { recursive: true });
9770
+ try {
9771
+ (0, import_node_child_process.execFileSync)("mv", [stagedAgentDir, targetAgentDir]);
9772
+ } catch {
9773
+ try {
9774
+ (0, import_node_child_process.execFileSync)("cp", ["-R", stagedAgentDir, targetAgentDir]);
9775
+ (0, import_node_fs3.rmSync)(stagedAgentDir, { recursive: true, force: true });
9776
+ } catch (err) {
9777
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9778
+ process.stderr.write(`Error: failed to install agent dir: ${err.message}
9779
+ `);
9780
+ process.exit(1);
9781
+ }
9782
+ }
9783
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9784
+ const rebind = await getIMClient2().im.agents.transfer({
9785
+ agentId,
9786
+ fromDaemonId: manifest.fromDaemonId,
9787
+ toDaemonId,
9788
+ manifestSha256: manifest.sha256
9789
+ });
9790
+ if (!rebind.ok || !rebind.data) {
9791
+ process.stderr.write(
9792
+ `Warning: cloud rebind failed (${rebind.error?.message ?? "unknown"}). Agent files are on disk at ${targetAgentDir} but cloud still routes to ${manifest.fromDaemonId}.
9793
+ Manual fix: POST /api/im/agent-bindings/transfer or rebind via the Devices UI.
9794
+ `
9795
+ );
9796
+ process.exit(2);
9797
+ }
9798
+ await getIMClient2().im.agents.resume(agentId);
9799
+ const result = {
9800
+ agentId,
9801
+ fromDaemonId: manifest.fromDaemonId,
9802
+ toDaemonId,
9803
+ targetDir: targetAgentDir,
9804
+ manifestSha256: manifest.sha256,
9805
+ boundDaemonId: rebind.data.boundDaemonId,
9806
+ boundBy: rebind.data.boundBy
9807
+ };
9808
+ if (opts.json) {
9809
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
9810
+ } else {
9811
+ console.log(`Imported agent ${agentId}`);
9812
+ console.log(` from device: ${result.fromDaemonId}`);
9813
+ console.log(` to device: ${result.toDaemonId}`);
9814
+ console.log(` target dir: ${result.targetDir}`);
9815
+ console.log(` cloud bound: ${result.boundDaemonId} (${result.boundBy})`);
9816
+ }
9817
+ });
9818
+ agent.command("cleanup-orphan").description("release201/09 \xA79.7.3 \u2014 list devices/*/agents/* dirs whose cloud binding points elsewhere").option("--delete", "Actually remove orphan dirs (default: dry-run)").option("--json", "Output raw JSON list").action(async (opts) => {
9819
+ const { daemonId } = readDaemonConfig();
9820
+ const devicesRoot = (0, import_node_path2.join)(resolvePrismerRoot(), "devices");
9821
+ if (!(0, import_node_fs3.existsSync)(devicesRoot)) {
9822
+ if (opts.json) process.stdout.write("[]\n");
9823
+ else console.log("No devices/ dir yet.");
9824
+ return;
9825
+ }
9826
+ const orphans = [];
9827
+ for (const did of (0, import_node_fs3.readdirSync)(devicesRoot)) {
9828
+ if (did === daemonId) continue;
9829
+ const agentsRoot = (0, import_node_path2.join)(devicesRoot, did, "agents");
9830
+ if (!(0, import_node_fs3.existsSync)(agentsRoot)) continue;
9831
+ for (const aid of (0, import_node_fs3.readdirSync)(agentsRoot)) {
9832
+ const dir = (0, import_node_path2.join)(agentsRoot, aid);
9833
+ try {
9834
+ const st = (0, import_node_fs3.statSync)(dir);
9835
+ if (!st.isDirectory()) continue;
9836
+ const files = collectFiles(dir, /* @__PURE__ */ new Set());
9837
+ const sizeBytes = files.reduce((s, f) => s + f.size, 0);
9838
+ orphans.push({ daemonId: did, agentId: aid, dir, sizeBytes });
9839
+ } catch {
9840
+ }
9841
+ }
9842
+ }
9843
+ if (opts.delete) {
9844
+ for (const o of orphans) {
9845
+ (0, import_node_fs3.rmSync)(o.dir, { recursive: true, force: true });
9846
+ }
9847
+ }
9848
+ if (opts.json) {
9849
+ process.stdout.write(JSON.stringify({ orphans, deleted: Boolean(opts.delete) }, null, 2) + "\n");
9850
+ } else {
9851
+ if (orphans.length === 0) {
9852
+ console.log("No orphan agent dirs.");
9853
+ } else {
9854
+ console.log(`Found ${orphans.length} orphan agent dir(s)${opts.delete ? " (DELETED)" : " (dry-run; pass --delete to remove)"}:`);
9855
+ for (const o of orphans) {
9856
+ console.log(` ${o.daemonId}/${o.agentId} ${o.sizeBytes} bytes ${o.dir}`);
9857
+ }
9858
+ }
9859
+ }
9860
+ });
7957
9861
  agent.command("fork <pack-id-or-slug>").description("Fork an Agent Pack into a workspace").requiredOption("--workspace-id <id>", "Target workspace id").option("--display-name <name>", "New agent display name").option("--json", "Output raw JSON response").action(async (packId, opts) => {
7958
9862
  const res = await getIMClient2().im.agents.forkPack(packId, {
7959
9863
  targetWorkspaceId: opts.workspaceId,
@@ -7979,32 +9883,168 @@ function printOrExit(res, json, print) {
7979
9883
  print(res.data);
7980
9884
  }
7981
9885
 
9886
+ // src/commands/metric.ts
9887
+ var AGG_FUNCS = ["sum", "count", "avg", "min", "max", "p50", "p95", "p99"];
9888
+ var BUCKETS = ["5m", "1h", "1d"];
9889
+ function splitFqName(fqName) {
9890
+ const i = fqName.lastIndexOf(".");
9891
+ if (i <= 0 || i === fqName.length - 1) {
9892
+ throw new Error(`metric name must be in form "namespace.name" (got "${fqName}")`);
9893
+ }
9894
+ return { namespace: fqName.slice(0, i), name: fqName.slice(i + 1) };
9895
+ }
9896
+ function parseDimFlags(dimArr) {
9897
+ const dims = {};
9898
+ for (const raw of dimArr ?? []) {
9899
+ const i = raw.indexOf("=");
9900
+ if (i <= 0) throw new Error(`--dim "${raw}" must be in form key=value`);
9901
+ const key = raw.slice(0, i);
9902
+ const value = raw.slice(i + 1);
9903
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) dims[key] = Number(value);
9904
+ else if (value === "true" || value === "false") dims[key] = value === "true";
9905
+ else dims[key] = value;
9906
+ }
9907
+ return dims;
9908
+ }
9909
+ async function runEmit(fqName, opts, getIMClient2) {
9910
+ const client = getIMClient2();
9911
+ const { namespace, name } = splitFqName(fqName);
9912
+ const dims = parseDimFlags(opts.dim);
9913
+ if (!dims.workspaceId) {
9914
+ throw new Error("--dim workspaceId=<id> is required (server rejects emits without it)");
9915
+ }
9916
+ let value;
9917
+ if (opts.value !== void 0) {
9918
+ value = /^-?\d+(?:\.\d+)?$/.test(opts.value) ? Number(opts.value) : opts.value;
9919
+ }
9920
+ const input = {
9921
+ namespace,
9922
+ name,
9923
+ ts: opts.ts,
9924
+ value,
9925
+ dims
9926
+ };
9927
+ const res = await client.im.metrics.emit(input);
9928
+ if (opts.json) {
9929
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
9930
+ return;
9931
+ }
9932
+ if (!res.ok) {
9933
+ process.stderr.write(`Error: ${res.error?.message ?? "unknown error"}
9934
+ `);
9935
+ process.exit(1);
9936
+ }
9937
+ process.stdout.write(`emitted ${namespace}.${name}
9938
+ `);
9939
+ }
9940
+ async function runAgg(fqName, opts, getIMClient2) {
9941
+ const client = getIMClient2();
9942
+ const { namespace, name } = splitFqName(fqName);
9943
+ if (!AGG_FUNCS.includes(opts.agg)) {
9944
+ throw new Error(`--agg must be one of ${AGG_FUNCS.join("|")}`);
9945
+ }
9946
+ if (opts.bucket && !BUCKETS.includes(opts.bucket)) {
9947
+ throw new Error(`--bucket must be one of ${BUCKETS.join("|")}`);
9948
+ }
9949
+ const filter = {};
9950
+ for (const raw of (opts.filter ?? "").split(",").filter(Boolean)) {
9951
+ const i = raw.indexOf(":");
9952
+ if (i <= 0) throw new Error(`--filter "${raw}" must be in form key:value`);
9953
+ filter[raw.slice(0, i)] = raw.slice(i + 1);
9954
+ }
9955
+ if (!filter.workspaceId) {
9956
+ throw new Error("--filter must include workspaceId:<id> (cross-workspace queries are admin-only)");
9957
+ }
9958
+ const groupBy = opts.groupBy ? opts.groupBy.split(",").filter(Boolean) : void 0;
9959
+ const res = await client.im.metrics.aggregate({
9960
+ namespace,
9961
+ name,
9962
+ agg: opts.agg,
9963
+ range: opts.range,
9964
+ from: opts.from,
9965
+ to: opts.to,
9966
+ groupBy,
9967
+ filter,
9968
+ bucket: opts.bucket
9969
+ });
9970
+ if (opts.json) {
9971
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
9972
+ return;
9973
+ }
9974
+ if (!res.ok) {
9975
+ process.stderr.write(`Error: ${res.error?.message ?? "unknown error"}
9976
+ `);
9977
+ process.exit(1);
9978
+ }
9979
+ const data = res.data;
9980
+ if (!data) {
9981
+ process.stdout.write("(no data)\n");
9982
+ return;
9983
+ }
9984
+ process.stdout.write(
9985
+ `${data.namespace}.${data.name} ${data.agg} [${data.range.from} \u2192 ${data.range.to}]
9986
+ `
9987
+ );
9988
+ for (const bucket of data.buckets) {
9989
+ const tsLabel = bucket.ts ? `${bucket.ts}` : "(all)";
9990
+ for (const g of bucket.groups) {
9991
+ const keyLabel = Object.entries(g.groupKey).map(([k, v]) => `${k}=${v ?? "\u2205"}`).join(" ");
9992
+ process.stdout.write(` ${tsLabel} ${keyLabel || "(no group)"} \u2192 ${g.value ?? "\u2205"}
9993
+ `);
9994
+ }
9995
+ }
9996
+ }
9997
+ function register18(parent, getIMClient2, _getAPIClient) {
9998
+ const metric = parent.command("metric").description("Emit metric events and query aggregations (release201/11)");
9999
+ metric.command("emit <namespace.name>").description("Emit a single metric event").option("--value <value>", "metric value (number or string)").option("--dim <k=v>", "dimension (repeatable; workspaceId is required)", (val, prev = []) => {
10000
+ prev.push(val);
10001
+ return prev;
10002
+ }).option("--ts <iso>", "business timestamp in ISO 8601 (defaults to now)").option("--json", "output raw JSON response").action(async (fqName, opts) => {
10003
+ try {
10004
+ await runEmit(fqName, opts, getIMClient2);
10005
+ } catch (err) {
10006
+ process.stderr.write(`Error: ${err.message}
10007
+ `);
10008
+ process.exit(1);
10009
+ }
10010
+ });
10011
+ metric.command("agg <namespace.name>").description("Aggregate metric events (release201/11 \xA75)").requiredOption("--agg <fn>", `one of ${AGG_FUNCS.join("|")}`).option("--range <Nh|Nd>", "lookback window (e.g. 24h, 7d)").option("--from <iso>", "window start (ISO, paired with --to)").option("--to <iso>", "window end (ISO, paired with --from)").option("--groupBy <k1,k2>", "csv of dim keys to group by").option("--filter <k1:v1,k2:v2>", "csv k:v filters (workspaceId is required)").option("--bucket <5m|1h|1d>", "timeseries bucket size").option("--json", "output raw JSON response").action(async (fqName, opts) => {
10012
+ try {
10013
+ await runAgg(fqName, opts, getIMClient2);
10014
+ } catch (err) {
10015
+ process.stderr.write(`Error: ${err.message}
10016
+ `);
10017
+ process.exit(1);
10018
+ }
10019
+ });
10020
+ }
10021
+
7982
10022
  // src/daemon.ts
7983
- var fs3 = __toESM(require("fs"));
7984
- var path3 = __toESM(require("path"));
10023
+ var fs6 = __toESM(require("fs"));
10024
+ var path4 = __toESM(require("path"));
7985
10025
  var import_path = require("path");
7986
- var os = __toESM(require("os"));
10026
+ var os2 = __toESM(require("os"));
7987
10027
  var import_os = require("os");
7988
10028
  var http = __toESM(require("http"));
7989
10029
  var import_http = require("http");
7990
10030
  var import_child_process = require("child_process");
7991
10031
  var TOML = __toESM(require("@iarna/toml"));
7992
- var CONFIG_DIR = path3.join(os.homedir(), ".prismer");
7993
- var CONFIG_PATH = path3.join(CONFIG_DIR, "config.toml");
7994
- var PID_PATH = path3.join(CONFIG_DIR, "daemon.pid");
7995
- var PORT_PATH = path3.join(CONFIG_DIR, "daemon.port");
7996
- var CACHE_DIR = path3.join(CONFIG_DIR, "cache");
7997
- var EVOLUTION_CACHE_PATH = path3.join(CACHE_DIR, "evolution.json");
7998
- var OUTBOX_PATH = path3.join(CACHE_DIR, "outbox.json");
10032
+ var CONFIG_DIR = path4.join(os2.homedir(), ".prismer");
10033
+ var CONFIG_PATH = path4.join(CONFIG_DIR, "config.toml");
10034
+ var PID_PATH = path4.join(CONFIG_DIR, "daemon.pid");
10035
+ var PORT_PATH = path4.join(CONFIG_DIR, "daemon.port");
10036
+ var CACHE_DIR = path4.join(CONFIG_DIR, "cache");
10037
+ var EVOLUTION_CACHE_PATH = path4.join(CACHE_DIR, "evolution.json");
10038
+ var OUTBOX_PATH = path4.join(CACHE_DIR, "outbox.json");
7999
10039
  var SYNC_INTERVAL_MS = 6e4;
8000
10040
  var FLUSH_INTERVAL_MS = 3e4;
8001
10041
  var API_TIMEOUT_MS = 1e4;
8002
10042
  var EVENTS_FILE = (0, import_path.join)(CACHE_DIR, "events.json");
8003
10043
  var MAX_EVENTS = 1e3;
8004
10044
  function loadConfig() {
8005
- if (!fs3.existsSync(CONFIG_PATH)) return null;
10045
+ if (!fs6.existsSync(CONFIG_PATH)) return null;
8006
10046
  try {
8007
- const raw = fs3.readFileSync(CONFIG_PATH, "utf-8");
10047
+ const raw = fs6.readFileSync(CONFIG_PATH, "utf-8");
8008
10048
  const parsed = TOML.parse(raw);
8009
10049
  const apiKey = parsed?.default?.api_key || "";
8010
10050
  const baseUrl = parsed?.default?.base_url || "https://prismer.cloud";
@@ -8015,13 +10055,13 @@ function loadConfig() {
8015
10055
  }
8016
10056
  }
8017
10057
  function ensureCacheDir() {
8018
- if (!fs3.existsSync(CACHE_DIR)) {
8019
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
10058
+ if (!fs6.existsSync(CACHE_DIR)) {
10059
+ fs6.mkdirSync(CACHE_DIR, { recursive: true });
8020
10060
  }
8021
10061
  }
8022
10062
  function loadEvents() {
8023
10063
  try {
8024
- return JSON.parse(fs3.readFileSync(EVENTS_FILE, "utf-8"));
10064
+ return JSON.parse(fs6.readFileSync(EVENTS_FILE, "utf-8"));
8025
10065
  } catch {
8026
10066
  return [];
8027
10067
  }
@@ -8030,7 +10070,7 @@ function appendEvent(event) {
8030
10070
  const events = loadEvents();
8031
10071
  events.push(event);
8032
10072
  if (events.length > MAX_EVENTS) events.splice(0, events.length - MAX_EVENTS);
8033
- fs3.writeFileSync(EVENTS_FILE, JSON.stringify(events), { encoding: "utf-8", mode: 384 });
10073
+ fs6.writeFileSync(EVENTS_FILE, JSON.stringify(events), { encoding: "utf-8", mode: 384 });
8034
10074
  }
8035
10075
  function emitSyncEvent(genesCount) {
8036
10076
  if (genesCount > 0) {
@@ -8045,9 +10085,9 @@ function emitSyncEvent(genesCount) {
8045
10085
  }
8046
10086
  }
8047
10087
  function readPid() {
8048
- if (!fs3.existsSync(PID_PATH)) return null;
10088
+ if (!fs6.existsSync(PID_PATH)) return null;
8049
10089
  try {
8050
- const raw = fs3.readFileSync(PID_PATH, "utf-8").trim();
10090
+ const raw = fs6.readFileSync(PID_PATH, "utf-8").trim();
8051
10091
  const pid = parseInt(raw, 10);
8052
10092
  return isNaN(pid) ? null : pid;
8053
10093
  } catch {
@@ -8055,9 +10095,9 @@ function readPid() {
8055
10095
  }
8056
10096
  }
8057
10097
  function readPort() {
8058
- if (!fs3.existsSync(PORT_PATH)) return null;
10098
+ if (!fs6.existsSync(PORT_PATH)) return null;
8059
10099
  try {
8060
- const raw = fs3.readFileSync(PORT_PATH, "utf-8").trim();
10100
+ const raw = fs6.readFileSync(PORT_PATH, "utf-8").trim();
8061
10101
  const port = parseInt(raw, 10);
8062
10102
  return isNaN(port) ? null : port;
8063
10103
  } catch {
@@ -8074,28 +10114,28 @@ function isProcessRunning(pid) {
8074
10114
  }
8075
10115
  function writePid(pid) {
8076
10116
  ensureCacheDir();
8077
- if (!fs3.existsSync(CONFIG_DIR)) {
8078
- fs3.mkdirSync(CONFIG_DIR, { recursive: true });
10117
+ if (!fs6.existsSync(CONFIG_DIR)) {
10118
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
8079
10119
  }
8080
- fs3.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
10120
+ fs6.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
8081
10121
  }
8082
10122
  function writePort(port) {
8083
- if (!fs3.existsSync(CONFIG_DIR)) {
8084
- fs3.mkdirSync(CONFIG_DIR, { recursive: true });
10123
+ if (!fs6.existsSync(CONFIG_DIR)) {
10124
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
8085
10125
  }
8086
- fs3.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
10126
+ fs6.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
8087
10127
  }
8088
10128
  function cleanupPidFiles() {
8089
10129
  try {
8090
- if (fs3.existsSync(PID_PATH)) fs3.unlinkSync(PID_PATH);
10130
+ if (fs6.existsSync(PID_PATH)) fs6.unlinkSync(PID_PATH);
8091
10131
  } catch {
8092
10132
  }
8093
10133
  try {
8094
- if (fs3.existsSync(PORT_PATH)) fs3.unlinkSync(PORT_PATH);
10134
+ if (fs6.existsSync(PORT_PATH)) fs6.unlinkSync(PORT_PATH);
8095
10135
  } catch {
8096
10136
  }
8097
10137
  }
8098
- async function fetchWithTimeout(url, options, timeoutMs = API_TIMEOUT_MS) {
10138
+ async function fetchWithTimeout2(url, options, timeoutMs = API_TIMEOUT_MS) {
8099
10139
  const controller = new AbortController();
8100
10140
  const timer = setTimeout(() => controller.abort(), timeoutMs);
8101
10141
  try {
@@ -8114,9 +10154,9 @@ async function runDaemonProcess() {
8114
10154
  let lastSync = 0;
8115
10155
  let syncCount = 0;
8116
10156
  let evolutionCursor = 0;
8117
- if (fs3.existsSync(EVOLUTION_CACHE_PATH)) {
10157
+ if (fs6.existsSync(EVOLUTION_CACHE_PATH)) {
8118
10158
  try {
8119
- const cached = JSON.parse(fs3.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
10159
+ const cached = JSON.parse(fs6.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
8120
10160
  if (typeof cached?.cursor === "number") evolutionCursor = cached.cursor;
8121
10161
  } catch {
8122
10162
  }
@@ -8124,9 +10164,9 @@ async function runDaemonProcess() {
8124
10164
  const server = (0, import_http.createServer)((req, res) => {
8125
10165
  if (req.method === "GET" && req.url === "/health") {
8126
10166
  let outboxSize = 0;
8127
- if (fs3.existsSync(OUTBOX_PATH)) {
10167
+ if (fs6.existsSync(OUTBOX_PATH)) {
8128
10168
  try {
8129
- const entries = JSON.parse(fs3.readFileSync(OUTBOX_PATH, "utf-8"));
10169
+ const entries = JSON.parse(fs6.readFileSync(OUTBOX_PATH, "utf-8"));
8130
10170
  if (Array.isArray(entries)) outboxSize = entries.length;
8131
10171
  } catch {
8132
10172
  }
@@ -8167,7 +10207,7 @@ async function runDaemonProcess() {
8167
10207
  process.on("SIGTERM", shutdown);
8168
10208
  const doEvolutionSync = async () => {
8169
10209
  try {
8170
- const res = await fetchWithTimeout(
10210
+ const res = await fetchWithTimeout2(
8171
10211
  `${cfg.baseUrl}/api/im/evolution/sync`,
8172
10212
  {
8173
10213
  method: "POST",
@@ -8189,7 +10229,7 @@ async function runDaemonProcess() {
8189
10229
  }
8190
10230
  ensureCacheDir();
8191
10231
  const pulled = data?.data || data;
8192
- fs3.writeFileSync(
10232
+ fs6.writeFileSync(
8193
10233
  EVOLUTION_CACHE_PATH,
8194
10234
  JSON.stringify({ cursor: evolutionCursor, lastSync, data: pulled }, null, 2),
8195
10235
  { encoding: "utf-8", mode: 384 }
@@ -8200,16 +10240,16 @@ async function runDaemonProcess() {
8200
10240
  }
8201
10241
  };
8202
10242
  const doOutboxFlush = async () => {
8203
- if (!fs3.existsSync(OUTBOX_PATH)) return;
10243
+ if (!fs6.existsSync(OUTBOX_PATH)) return;
8204
10244
  let entries = [];
8205
10245
  try {
8206
- entries = JSON.parse(fs3.readFileSync(OUTBOX_PATH, "utf-8"));
10246
+ entries = JSON.parse(fs6.readFileSync(OUTBOX_PATH, "utf-8"));
8207
10247
  if (!Array.isArray(entries) || entries.length === 0) return;
8208
10248
  } catch {
8209
10249
  return;
8210
10250
  }
8211
10251
  try {
8212
- const res = await fetchWithTimeout(
10252
+ const res = await fetchWithTimeout2(
8213
10253
  `${cfg.baseUrl}/api/im/evolution/sync`,
8214
10254
  {
8215
10255
  method: "POST",
@@ -8224,7 +10264,7 @@ async function runDaemonProcess() {
8224
10264
  }
8225
10265
  );
8226
10266
  if (res.ok) {
8227
- fs3.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
10267
+ fs6.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
8228
10268
  }
8229
10269
  } catch {
8230
10270
  }
@@ -8342,7 +10382,7 @@ function resolveNpxPath() {
8342
10382
  } catch {
8343
10383
  for (const p of ["/usr/local/bin/npx", "/opt/homebrew/bin/npx", `${(0, import_os.homedir)()}/.nvm/current/bin/npx`]) {
8344
10384
  try {
8345
- fs3.accessSync(p);
10385
+ fs6.accessSync(p);
8346
10386
  return p;
8347
10387
  } catch {
8348
10388
  }
@@ -8384,8 +10424,8 @@ function installLaunchd() {
8384
10424
  <string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stderr.log")}</string>
8385
10425
  </dict>
8386
10426
  </plist>`;
8387
- fs3.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
8388
- fs3.writeFileSync(plistPath, plist, { mode: 384 });
10427
+ fs6.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
10428
+ fs6.writeFileSync(plistPath, plist, { mode: 384 });
8389
10429
  try {
8390
10430
  (0, import_child_process.execSync)(`launchctl load ${plistPath}`, { stdio: "pipe" });
8391
10431
  console.log("[prismer] Daemon service installed and started (launchd)");
@@ -8401,7 +10441,7 @@ function uninstallLaunchd() {
8401
10441
  } catch {
8402
10442
  }
8403
10443
  try {
8404
- fs3.unlinkSync(plistPath);
10444
+ fs6.unlinkSync(plistPath);
8405
10445
  } catch {
8406
10446
  }
8407
10447
  console.log("[prismer] Daemon service uninstalled (launchd)");
@@ -8426,8 +10466,8 @@ RestartSec=10
8426
10466
  [Install]
8427
10467
  WantedBy=default.target
8428
10468
  `;
8429
- fs3.mkdirSync(serviceDir, { recursive: true });
8430
- fs3.writeFileSync(servicePath, unit, { mode: 420 });
10469
+ fs6.mkdirSync(serviceDir, { recursive: true });
10470
+ fs6.writeFileSync(servicePath, unit, { mode: 420 });
8431
10471
  try {
8432
10472
  (0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
8433
10473
  (0, import_child_process.execSync)("systemctl --user enable prismer-daemon", { stdio: "pipe" });
@@ -8450,7 +10490,7 @@ function uninstallSystemd() {
8450
10490
  }
8451
10491
  const servicePath = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user", "prismer-daemon.service");
8452
10492
  try {
8453
- fs3.unlinkSync(servicePath);
10493
+ fs6.unlinkSync(servicePath);
8454
10494
  } catch {
8455
10495
  }
8456
10496
  try {
@@ -8490,21 +10530,21 @@ if (process.env["PRISMER_DAEMON"] === "1") {
8490
10530
  // src/cli.ts
8491
10531
  var cliVersion = "1.7.2";
8492
10532
  try {
8493
- const pkgPath = path4.join(__dirname, "..", "package.json");
8494
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
10533
+ const pkgPath = path5.join(__dirname, "..", "package.json");
10534
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf8"));
8495
10535
  cliVersion = pkg.version || cliVersion;
8496
10536
  } catch {
8497
10537
  }
8498
- var CONFIG_DIR2 = process.env.PRISMER_HOME ? path4.resolve(process.env.PRISMER_HOME) : path4.join(os2.homedir(), ".prismer");
8499
- var CONFIG_PATH2 = path4.join(CONFIG_DIR2, "config.toml");
10538
+ var CONFIG_DIR2 = process.env.PRISMER_HOME ? path5.resolve(process.env.PRISMER_HOME) : path5.join(os3.homedir(), ".prismer");
10539
+ var CONFIG_PATH2 = path5.join(CONFIG_DIR2, "config.toml");
8500
10540
  function ensureConfigDir() {
8501
- if (!fs4.existsSync(CONFIG_DIR2)) {
8502
- fs4.mkdirSync(CONFIG_DIR2, { recursive: true });
10541
+ if (!fs7.existsSync(CONFIG_DIR2)) {
10542
+ fs7.mkdirSync(CONFIG_DIR2, { recursive: true });
8503
10543
  }
8504
10544
  }
8505
10545
  function readConfig() {
8506
- if (!fs4.existsSync(CONFIG_PATH2)) return {};
8507
- const raw = fs4.readFileSync(CONFIG_PATH2, "utf-8");
10546
+ if (!fs7.existsSync(CONFIG_PATH2)) return {};
10547
+ const raw = fs7.readFileSync(CONFIG_PATH2, "utf-8");
8508
10548
  const parsed = TOML2.parse(raw);
8509
10549
  const flatApiKey = parsed.api_key;
8510
10550
  const flatBaseUrl = parsed.cloud_api_base ?? parsed.base_url;
@@ -8522,7 +10562,7 @@ function readConfig() {
8522
10562
  }
8523
10563
  function writeConfig(config) {
8524
10564
  ensureConfigDir();
8525
- fs4.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
10565
+ fs7.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
8526
10566
  }
8527
10567
  function setNestedValue(obj, dotPath, value) {
8528
10568
  const parts = dotPath.split(".");
@@ -8685,8 +10725,8 @@ async function runSetup(opts, apiKey) {
8685
10725
  return;
8686
10726
  }
8687
10727
  const http2 = require("http");
8688
- const crypto2 = require("crypto");
8689
- const state = crypto2.randomBytes(16).toString("hex");
10728
+ const crypto3 = require("crypto");
10729
+ const state = crypto3.randomBytes(16).toString("hex");
8690
10730
  let resolved = false;
8691
10731
  const server = http2.createServer((req, res) => {
8692
10732
  const url = new URL(req.url, `http://localhost`);
@@ -8855,11 +10895,11 @@ program.command("status").description("Show current config and live info").actio
8855
10895
  });
8856
10896
  var configCmd = program.command("config").description("Manage config file");
8857
10897
  configCmd.command("show").description("Print config file").action(() => {
8858
- if (!fs4.existsSync(CONFIG_PATH2)) {
10898
+ if (!fs7.existsSync(CONFIG_PATH2)) {
8859
10899
  warn('No config file. Run "cloud setup" to create one.');
8860
10900
  return;
8861
10901
  }
8862
- console.log(fs4.readFileSync(CONFIG_PATH2, "utf-8"));
10902
+ console.log(fs7.readFileSync(CONFIG_PATH2, "utf-8"));
8863
10903
  });
8864
10904
  configCmd.command("set <key> <value>").description("Set a config value (e.g. default.base_url)").action((key, value) => {
8865
10905
  const config = readConfig();
@@ -8904,6 +10944,11 @@ register10(program, getIMClient, getAPIClient);
8904
10944
  register11(program, getIMClient, getAPIClient);
8905
10945
  register12(program, getIMClient, getAPIClient);
8906
10946
  register13(program, getIMClient, getAPIClient);
10947
+ register14(program, getIMClient, getAPIClient);
10948
+ register15(program, getIMClient, getAPIClient);
10949
+ register16(program, getIMClient, getAPIClient);
10950
+ register17(program, getIMClient, getAPIClient);
10951
+ register18(program, getIMClient, getAPIClient);
8907
10952
  program.command("send").description("Send a direct message (shortcut for: im send)").argument("<user-id-or-username>", "Target user/agent IM user ID (or username with --by-username)").argument("<message>", "Message content").option("-t, --type <type>", "Message type: text, markdown, code, etc.", "text").option("--reply-to <id>", "Reply to a message ID").option("--conversation-id <id>", "Pin message to a specific conversation/session").option("--asset-id <id>", "Attach a previously uploaded asset (treats type as file)").option("--by-username", "Treat the first argument as a username; resolve to imUserId first").option("--json", "JSON output").action(async (target, message, opts) => {
8908
10953
  const client = getIMClient();
8909
10954
  let userId = target;
@@ -8952,6 +10997,17 @@ program.command("send").description("Send a direct message (shortcut for: im sen
8952
10997
  }
8953
10998
  success(`Message sent (conversation: ${res.data?.conversationId})`);
8954
10999
  });
11000
+ program.command("emit <namespace.name>").description("Emit a metric event (shortcut for: metric emit)").option("--value <value>", "metric value (number or string)").option("--dim <k=v>", "dimension (repeatable; workspaceId is required)", (val, prev = []) => {
11001
+ prev.push(val);
11002
+ return prev;
11003
+ }).option("--ts <iso>", "business timestamp in ISO 8601 (defaults to now)").option("--json", "output raw JSON response").action(async (fqName, opts) => {
11004
+ try {
11005
+ await runEmit(fqName, opts, getIMClient);
11006
+ } catch (err) {
11007
+ errorLine(`Error: ${err.message}`);
11008
+ process.exit(1);
11009
+ }
11010
+ });
8955
11011
  program.command("load").description("Load URL(s) \u2192 compressed HQCC (shortcut for: context load)").argument("<urls...>", "One or more URLs").option("-f, --format <fmt>", "Return format: hqcc, raw, both", "hqcc").option("--json", "JSON output").action(async (urls, opts) => {
8956
11012
  const client = getAPIClient();
8957
11013
  const input = urls.length === 1 ? urls[0] : urls;