@prismer/sdk 2.0.6 → 2.0.8

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((resolve6, 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
+ resolve6();
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((resolve6, 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: resolve6, 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);
@@ -3552,6 +3868,19 @@ var FilesClient = class {
3552
3868
  */
3553
3869
  async sendFile(conversationId, input, opts) {
3554
3870
  const uploaded = await this.upload(input, opts);
3871
+ const outboxDir = process.env.PRISMER_OUTBOX_DIR;
3872
+ if (outboxDir) {
3873
+ try {
3874
+ const fsMod = await import("fs");
3875
+ const pathMod = await import("path");
3876
+ const srcPath = typeof input === "string" ? input : input.path;
3877
+ if (srcPath && typeof srcPath === "string") {
3878
+ await fsMod.promises.mkdir(outboxDir, { recursive: true });
3879
+ fsMod.cpSync(srcPath, pathMod.join(outboxDir, pathMod.basename(srcPath)));
3880
+ }
3881
+ } catch {
3882
+ }
3883
+ }
3555
3884
  const msgRes = await this._r("POST", `/api/im/messages/${conversationId}`, {
3556
3885
  content: opts?.content || uploaded.fileName,
3557
3886
  type: "file",
@@ -3760,6 +4089,36 @@ var IMRealtimeClient = class {
3760
4089
  };
3761
4090
  }
3762
4091
  };
4092
+ var SkillsDraftClient = class {
4093
+ constructor(_r) {
4094
+ this._r = _r;
4095
+ }
4096
+ /** Create a draft skill from a manifest v1 payload. */
4097
+ async create(input) {
4098
+ return this._r("POST", "/api/im/skills/draft", input);
4099
+ }
4100
+ /** Apply incremental file ops to a draft. */
4101
+ async patch(id, input) {
4102
+ return this._r("PATCH", `/api/im/skills/${id}/draft`, input);
4103
+ }
4104
+ /** Request a regenerate session for a draft. */
4105
+ async regenerate(id, input) {
4106
+ return this._r("POST", `/api/im/skills/${id}/draft/regenerate`, input);
4107
+ }
4108
+ /** Fetch a single draft's manifest + revision history. */
4109
+ async show(id) {
4110
+ return this._r("GET", `/api/im/skills/draft/${id}`);
4111
+ }
4112
+ /** List drafts for a workspace (most-recently-updated first). */
4113
+ async list(workspaceId) {
4114
+ return this._r("GET", "/api/im/skills/drafts", void 0, { workspaceId });
4115
+ }
4116
+ };
4117
+ var SkillsClient = class {
4118
+ constructor(_r) {
4119
+ this.draft = new SkillsDraftClient(_r);
4120
+ }
4121
+ };
3763
4122
  var IMClient = class {
3764
4123
  constructor(request2, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
3765
4124
  this._request = request2;
@@ -3773,16 +4132,22 @@ var IMClient = class {
3773
4132
  this.credits = new CreditsClient(request2);
3774
4133
  this.workspace = new WorkspaceClient(request2);
3775
4134
  this.workspaces = new WorkspacesClient(request2);
4135
+ this.invites = new InvitesClient(request2);
4136
+ this.projects = new ProjectsClient(request2);
3776
4137
  this.workspaceFiles = new WorkspaceFilesClient(request2);
3777
4138
  this.assets = new AssetsClient(request2, wsBase, fetchFn, getAuthHeaders);
3778
4139
  this.runtimeInstallations = new RuntimeInstallationsClient(request2);
3779
4140
  this.tasks = new TasksClient(request2);
4141
+ this.criteriaTemplates = new CriteriaTemplatesClient(request2);
3780
4142
  this.memory = new MemoryClient(request2);
3781
4143
  this.knowledge = new KnowledgeLinkClient(request2);
4144
+ this.metrics = new MetricsClient(request2);
3782
4145
  this.identity = new IdentityClient(request2);
3783
4146
  this.security = new SecurityClient(request2);
3784
4147
  this.agents = new AgentsClient(request2);
3785
4148
  this.evolution = new EvolutionClient(request2);
4149
+ this.skills = new SkillsClient(request2);
4150
+ this.studio = new StudioClient(request2);
3786
4151
  this.community = new CommunityHub(request2, communityHubConfig ?? void 0);
3787
4152
  this.files = new FilesClient(request2, wsBase, fetchFn, getAuthHeaders);
3788
4153
  this.realtime = new IMRealtimeClient(wsBase, fetchFn);
@@ -3812,8 +4177,8 @@ var IMClient = class {
3812
4177
  * 'POST', '/api/im/approvals', { category, title, context, options },
3813
4178
  * );
3814
4179
  */
3815
- async request(method, path5, body, query) {
3816
- return this._request(method, path5, body, query);
4180
+ async request(method, path6, body, query) {
4181
+ return this._request(method, path6, body, query);
3817
4182
  }
3818
4183
  };
3819
4184
  var PrismerClient = class {
@@ -3832,6 +4197,7 @@ var PrismerClient = class {
3832
4197
  this.timeout = config.timeout || 3e4;
3833
4198
  this.fetchFn = config.fetch || fetch;
3834
4199
  this.imAgent = config.imAgent;
4200
+ this.imWorkspace = config.imWorkspace;
3835
4201
  if (config.identity) {
3836
4202
  if (config.identity === "auto" && this.apiKey) {
3837
4203
  this._identityReady = import_aip_sdk.AIPIdentity.fromApiKey(this.apiKey).then((id) => {
@@ -3865,20 +4231,20 @@ var PrismerClient = class {
3865
4231
  ) : (m, p, b, q, opts) => this._request(m, p, b, q, opts);
3866
4232
  if (config.identity) {
3867
4233
  const baseRequest = imRequest;
3868
- imRequest = (method, path5, body, query, opts) => {
3869
- if (method === "POST" && path5.includes("/messages") && body) {
4234
+ imRequest = (method, path6, body, query, opts) => {
4235
+ if (method === "POST" && path6.includes("/messages") && body) {
3870
4236
  const b = body;
3871
4237
  if (!b.signature && !b.skipSigning) {
3872
4238
  const ready = this._identityReady || Promise.resolve();
3873
4239
  return ready.then(() => {
3874
4240
  if (this._identity) {
3875
- return this._signAndSend(baseRequest, method, path5, b, query, opts);
4241
+ return this._signAndSend(baseRequest, method, path6, b, query, opts);
3876
4242
  }
3877
- return baseRequest(method, path5, body, query, opts);
4243
+ return baseRequest(method, path6, body, query, opts);
3878
4244
  });
3879
4245
  }
3880
4246
  }
3881
- return baseRequest(method, path5, body, query, opts);
4247
+ return baseRequest(method, path6, body, query, opts);
3882
4248
  };
3883
4249
  }
3884
4250
  this.im = new IMClient(
@@ -3890,6 +4256,8 @@ var PrismerClient = class {
3890
4256
  config.community ?? null
3891
4257
  );
3892
4258
  this.workspaces = this.im.workspaces;
4259
+ this.invites = this.im.invites;
4260
+ this.projects = this.im.projects;
3893
4261
  this.workspaceFiles = this.im.workspaceFiles;
3894
4262
  this.assets = this.im.assets;
3895
4263
  this.evolution = this.im.evolution;
@@ -3900,9 +4268,9 @@ var PrismerClient = class {
3900
4268
  return this._identity;
3901
4269
  }
3902
4270
  /** Auto-sign a message body and send (v1.8.0 S1) */
3903
- async _signAndSend(baseRequest, method, path5, body, query, opts) {
4271
+ async _signAndSend(baseRequest, method, path6, body, query, opts) {
3904
4272
  if (this._identityReady) await this._identityReady;
3905
- if (!this._identity) return baseRequest(method, path5, body, query, opts);
4273
+ if (!this._identity) return baseRequest(method, path6, body, query, opts);
3906
4274
  const content = body.content || "";
3907
4275
  const contentHashBytes = new Uint8Array(
3908
4276
  await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
@@ -3912,7 +4280,7 @@ var PrismerClient = class {
3912
4280
  const payload = `1|${this._identity.did}|${body.type || "text"}|${timestamp}|${contentHash}`;
3913
4281
  const payloadBytes = new TextEncoder().encode(payload);
3914
4282
  const signature = await this._identity.sign(payloadBytes);
3915
- return baseRequest(method, path5, {
4283
+ return baseRequest(method, path6, {
3916
4284
  ...body,
3917
4285
  secVersion: 1,
3918
4286
  senderDid: this._identity.did,
@@ -3926,6 +4294,7 @@ var PrismerClient = class {
3926
4294
  const headers = {};
3927
4295
  if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
3928
4296
  if (this.imAgent) headers["X-IM-Agent"] = this.imAgent;
4297
+ if (this.imWorkspace) headers["X-IM-Workspace"] = this.imWorkspace;
3929
4298
  return headers;
3930
4299
  }
3931
4300
  /**
@@ -3977,11 +4346,11 @@ var PrismerClient = class {
3977
4346
  // --------------------------------------------------------------------------
3978
4347
  // Internal request helper
3979
4348
  // --------------------------------------------------------------------------
3980
- async _request(method, path5, body, query, opts, _isRetry) {
4349
+ async _request(method, path6, body, query, opts, _isRetry) {
3981
4350
  const controller = new AbortController();
3982
4351
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3983
4352
  try {
3984
- let url = `${this.baseUrl}${path5}`;
4353
+ let url = `${this.baseUrl}${path6}`;
3985
4354
  if (query && Object.keys(query).length > 0) {
3986
4355
  url += "?" + new URLSearchParams(query).toString();
3987
4356
  }
@@ -3992,6 +4361,9 @@ var PrismerClient = class {
3992
4361
  if (this.imAgent) {
3993
4362
  headers["X-IM-Agent"] = this.imAgent;
3994
4363
  }
4364
+ if (this.imWorkspace) {
4365
+ headers["X-IM-Workspace"] = this.imWorkspace;
4366
+ }
3995
4367
  if (opts?.headers) {
3996
4368
  for (const [k, v] of Object.entries(opts.headers)) headers[k] = v;
3997
4369
  }
@@ -4002,12 +4374,12 @@ var PrismerClient = class {
4002
4374
  }
4003
4375
  const response = await this.fetchFn(url, init);
4004
4376
  const data = await response.json();
4005
- if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path5.includes("/token/refresh")) {
4377
+ if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path6.includes("/token/refresh")) {
4006
4378
  try {
4007
4379
  const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, void 0, true);
4008
4380
  if (refreshRes?.ok && refreshRes?.data?.token) {
4009
4381
  this.apiKey = refreshRes.data.token;
4010
- return this._request(method, path5, body, query, opts, true);
4382
+ return this._request(method, path6, body, query, opts, true);
4011
4383
  }
4012
4384
  } catch {
4013
4385
  }
@@ -5360,7 +5732,7 @@ function register3(parent, getIMClient2, _getAPIClient) {
5360
5732
  const maxIterations = 30;
5361
5733
  let lastStatus;
5362
5734
  for (let i = 0; i < maxIterations; i++) {
5363
- await new Promise((resolve3) => setTimeout(resolve3, 2e3));
5735
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
5364
5736
  if (!opts.json) process.stdout.write(".");
5365
5737
  const statusRes = await client.im.evolution.getReportStatus(traceId);
5366
5738
  if (!statusRes.ok) break;
@@ -5742,15 +6114,162 @@ function register3(parent, getIMClient2, _getAPIClient) {
5742
6114
  });
5743
6115
  }
5744
6116
 
6117
+ // src/commands/task.ts
6118
+ var import_node_fs = require("fs");
6119
+ var import_node_path2 = require("path");
6120
+ var import_node_crypto = require("crypto");
6121
+
6122
+ // src/commands/deliver-proxy.ts
6123
+ var import_node_path = require("path");
6124
+ function detectDeliverProxy(opts) {
6125
+ const taskId = (opts?.taskId || opts?.runId || "").trim() || process.env.PRISMER_TASK_ID || process.env.PRISMER_RUN_ID || "";
6126
+ if (!taskId) return null;
6127
+ const port = (opts?.daemonPort || "").trim() || (process.env.PRISMER_DAEMON_PORT ?? "3210").trim() || "3210";
6128
+ const ctx = {
6129
+ daemonUrl: `http://127.0.0.1:${port}`,
6130
+ taskId
6131
+ };
6132
+ const agentUsername = process.env.PRISMER_AGENT_USERNAME;
6133
+ if (agentUsername) ctx.agentUsername = agentUsername;
6134
+ const conversationId = (opts?.conversationId || "").trim() || process.env.PRISMER_CONVERSATION_ID;
6135
+ if (conversationId) ctx.conversationId = conversationId;
6136
+ return ctx;
6137
+ }
6138
+ async function proxyDeliver(ctx, filePath, mode, conversationId, messageId) {
6139
+ const abs = (0, import_node_path.resolve)(filePath);
6140
+ const body = {
6141
+ taskId: ctx.taskId,
6142
+ path: abs,
6143
+ mode
6144
+ };
6145
+ const conv = conversationId ?? ctx.conversationId;
6146
+ if (mode === "send") {
6147
+ if (!conv) {
6148
+ return { ok: false, status: 400, error: "conversationId is required for send mode" };
6149
+ }
6150
+ body.conversationId = conv;
6151
+ }
6152
+ if (mode === "message-attach") {
6153
+ if (!conv) {
6154
+ return { ok: false, status: 400, error: "conversationId is required for message-attach mode" };
6155
+ }
6156
+ if (!messageId) {
6157
+ return { ok: false, status: 400, error: "messageId is required for message-attach mode" };
6158
+ }
6159
+ body.conversationId = conv;
6160
+ body.messageId = messageId;
6161
+ }
6162
+ if (ctx.agentUsername) body.agentUsername = ctx.agentUsername;
6163
+ let res;
6164
+ try {
6165
+ res = await fetch(`${ctx.daemonUrl}/local/deliver`, {
6166
+ method: "POST",
6167
+ headers: { "Content-Type": "application/json" },
6168
+ body: JSON.stringify(body)
6169
+ });
6170
+ } catch (err) {
6171
+ return {
6172
+ ok: false,
6173
+ status: 0,
6174
+ error: `daemon unreachable at ${ctx.daemonUrl}/local/deliver: ${err instanceof Error ? err.message : String(err)}`
6175
+ };
6176
+ }
6177
+ let parsed = {};
6178
+ try {
6179
+ parsed = await res.json();
6180
+ } catch {
6181
+ }
6182
+ if (res.ok && parsed.ok) {
6183
+ const out = { ok: true, status: res.status };
6184
+ if (parsed.assetId) out.assetId = parsed.assetId;
6185
+ return out;
6186
+ }
6187
+ return {
6188
+ ok: false,
6189
+ status: res.status,
6190
+ error: parsed.error ?? `daemon returned ${res.status}`
6191
+ };
6192
+ }
6193
+
5745
6194
  // src/commands/task.ts
5746
6195
  var TASK_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running", "review", "completed", "failed", "cancelled"]);
5747
6196
  var TASK_PRIORITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "urgent"]);
5748
6197
  var TASK_KINDS = /* @__PURE__ */ new Set(["work_item", "goal"]);
6198
+ function assertNotRunId(id) {
6199
+ if (typeof id === "string" && id.startsWith("run_")) {
6200
+ process.stderr.write(
6201
+ `Error: '${id}' is a run id (run_\u2026); a chat reply doesn't need 'cloud task' ops \u2014 the platform closes the turn from your reply.
6202
+ `
6203
+ );
6204
+ process.exit(1);
6205
+ }
6206
+ }
5749
6207
  function parseTaskStatus(raw) {
5750
6208
  if (!raw) return void 0;
5751
6209
  if (TASK_STATUSES.has(raw)) return raw;
5752
6210
  throw new Error(`Invalid task status "${raw}".`);
5753
6211
  }
6212
+ function normalizeProjectForCreate(raw) {
6213
+ const value = raw ?? process.env.PRISMER_ACTIVE_PROJECT_ID;
6214
+ if (!value) return void 0;
6215
+ const trimmed = value.trim();
6216
+ if (!trimmed || trimmed === "all") return void 0;
6217
+ if (trimmed === "__unscoped" || trimmed === "_unscoped" || trimmed === "none" || trimmed === "null") return null;
6218
+ return trimmed;
6219
+ }
6220
+ function normalizeProjectForList(raw) {
6221
+ const value = raw ?? process.env.PRISMER_ACTIVE_PROJECT_ID;
6222
+ if (!value) return void 0;
6223
+ const trimmed = value.trim();
6224
+ if (!trimmed) return void 0;
6225
+ if (trimmed === "_unscoped" || trimmed === "none" || trimmed === "null") return "__unscoped";
6226
+ return trimmed;
6227
+ }
6228
+ async function runReviewCheckpoint(taskId, toStatus) {
6229
+ const port = process.env.PRISMER_DAEMON_PORT ?? "3210";
6230
+ const url = `http://127.0.0.1:${port}/v1/checkpoints/pre_status_change`;
6231
+ let response;
6232
+ try {
6233
+ response = await fetch(url, {
6234
+ method: "POST",
6235
+ headers: { "Content-Type": "application/json" },
6236
+ body: JSON.stringify({ taskId, toStatus })
6237
+ });
6238
+ } catch (err) {
6239
+ return {
6240
+ ok: false,
6241
+ indeterminate: true,
6242
+ message: `daemon unreachable at ${url}: ${err instanceof Error ? err.message : String(err)}`
6243
+ };
6244
+ }
6245
+ let body;
6246
+ try {
6247
+ body = await response.json();
6248
+ } catch {
6249
+ body = {};
6250
+ }
6251
+ if (response.status === 200 && body.ok === true) {
6252
+ return { ok: true };
6253
+ }
6254
+ if (response.status === 409 && Array.isArray(body.pendingFiles)) {
6255
+ return {
6256
+ ok: false,
6257
+ pendingFiles: body.pendingFiles,
6258
+ message: body.error?.message ?? "pending_attach"
6259
+ };
6260
+ }
6261
+ if (response.status === 503) {
6262
+ return {
6263
+ ok: false,
6264
+ indeterminate: true,
6265
+ message: body.error?.message ?? `daemon returned 503`
6266
+ };
6267
+ }
6268
+ return {
6269
+ ok: false,
6270
+ message: body.error?.message ?? `daemon returned ${response.status}`
6271
+ };
6272
+ }
5754
6273
  async function resolveAssigneeId(client, name) {
5755
6274
  const needle = name.trim().toLowerCase();
5756
6275
  const normalized = needle.replace(/^@/, "");
@@ -5773,7 +6292,7 @@ async function resolveAssigneeId(client, name) {
5773
6292
  }
5774
6293
  function register4(parent, getIMClient2, _getAPIClient) {
5775
6294
  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) => {
6295
+ 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
6296
  const client = getIMClient2();
5778
6297
  try {
5779
6298
  if (opts.priority && !TASK_PRIORITIES.has(opts.priority)) {
@@ -5817,6 +6336,8 @@ function register4(parent, getIMClient2, _getAPIClient) {
5817
6336
  };
5818
6337
  if (assigneeId) createOpts.assigneeId = assigneeId;
5819
6338
  if (opts.conversationId) createOpts.conversationId = opts.conversationId;
6339
+ const projectId = normalizeProjectForCreate(opts.project);
6340
+ if (projectId !== void 0) createOpts.projectId = projectId;
5820
6341
  if (opts.scheduleAt) {
5821
6342
  createOpts.scheduleType = "once";
5822
6343
  createOpts.scheduleAt = opts.scheduleAt;
@@ -5858,12 +6379,13 @@ function register4(parent, getIMClient2, _getAPIClient) {
5858
6379
  process.exit(1);
5859
6380
  }
5860
6381
  });
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) => {
6382
+ 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
6383
  const client = getIMClient2();
5863
6384
  try {
5864
6385
  const res = await client.im.tasks.list({
5865
6386
  status: parseTaskStatus(opts.status),
5866
6387
  capability: opts.capability,
6388
+ projectId: normalizeProjectForList(opts.project),
5867
6389
  limit: parseInt(opts.limit, 10)
5868
6390
  });
5869
6391
  if (opts.json) {
@@ -5884,9 +6406,9 @@ function register4(parent, getIMClient2, _getAPIClient) {
5884
6406
  const statusW = 12;
5885
6407
  const titleW = 40;
5886
6408
  const header = "ID".padEnd(idW) + "STATUS".padEnd(statusW) + "TITLE";
5887
- const sep = "-".repeat(idW + statusW + titleW);
6409
+ const sep3 = "-".repeat(idW + statusW + titleW);
5888
6410
  process.stdout.write(header + "\n");
5889
- process.stdout.write(sep + "\n");
6411
+ process.stdout.write(sep3 + "\n");
5890
6412
  for (const t of tasks) {
5891
6413
  const title = t.title.length > titleW ? t.title.slice(0, titleW - 3) + "..." : t.title;
5892
6414
  process.stdout.write(
@@ -5895,6 +6417,33 @@ function register4(parent, getIMClient2, _getAPIClient) {
5895
6417
  }
5896
6418
  process.stdout.write(`
5897
6419
  ${tasks.length} task(s) listed.
6420
+ `);
6421
+ } catch (err) {
6422
+ const message = err instanceof Error ? err.message : String(err);
6423
+ process.stderr.write(`Error: ${message}
6424
+ `);
6425
+ process.exit(1);
6426
+ }
6427
+ });
6428
+ 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) => {
6429
+ assertNotRunId(taskId);
6430
+ const client = getIMClient2();
6431
+ try {
6432
+ if (!opts.unscoped && !projectId) {
6433
+ throw new Error("Provide <project-id> or pass --unscoped.");
6434
+ }
6435
+ const targetProjectId = opts.unscoped ? null : projectId.trim();
6436
+ const res = await client.im.tasks.moveProject(taskId, targetProjectId);
6437
+ if (opts.json) {
6438
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6439
+ return;
6440
+ }
6441
+ if (!res.ok) {
6442
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6443
+ `);
6444
+ process.exit(1);
6445
+ }
6446
+ process.stdout.write(`Task ${taskId} project: ${targetProjectId ?? "(workspace-level)"}
5898
6447
  `);
5899
6448
  } catch (err) {
5900
6449
  const message = err instanceof Error ? err.message : String(err);
@@ -5904,6 +6453,7 @@ ${tasks.length} task(s) listed.
5904
6453
  }
5905
6454
  });
5906
6455
  task.command("get <task-id>").description("Get task details and logs").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6456
+ assertNotRunId(taskId);
5907
6457
  const client = getIMClient2();
5908
6458
  try {
5909
6459
  const res = await client.im.tasks.get(taskId);
@@ -5968,6 +6518,7 @@ Logs (${logs.length}):
5968
6518
  }
5969
6519
  });
5970
6520
  task.command("claim <task-id>").description("Claim a pending task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6521
+ assertNotRunId(taskId);
5971
6522
  const client = getIMClient2();
5972
6523
  try {
5973
6524
  const res = await client.im.tasks.claim(taskId);
@@ -5997,9 +6548,38 @@ Logs (${logs.length}):
5997
6548
  process.exit(1);
5998
6549
  }
5999
6550
  });
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) => {
6551
+ 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) => {
6552
+ assertNotRunId(taskId);
6001
6553
  const client = getIMClient2();
6002
6554
  try {
6555
+ if (opts.status === "review" && !opts.skipCheckpoint) {
6556
+ const checkpoint = await runReviewCheckpoint(taskId, opts.status);
6557
+ if (!checkpoint.ok) {
6558
+ if (checkpoint.pendingFiles && checkpoint.pendingFiles.length > 0) {
6559
+ process.stderr.write(
6560
+ `[checkpoint] artifacts/ \u542B ${checkpoint.pendingFiles.length} \u4E2A\u672A attach \u6587\u4EF6:
6561
+ `
6562
+ );
6563
+ for (const f of checkpoint.pendingFiles) {
6564
+ const sizeKb = (f.sizeBytes / 1024).toFixed(1);
6565
+ process.stderr.write(` - ${f.path} (sha256=${f.sha256.slice(0, 12)}\u2026, ${sizeKb}KB)
6566
+ `);
6567
+ }
6568
+ process.stderr.write(
6569
+ "\u8BF7 attach \u5B83\u4EEC\u518D retry status change, \u6216\u52A0 --skip-checkpoint \u663E\u5F0F\u5FFD\u7565\n"
6570
+ );
6571
+ } else if (checkpoint.indeterminate) {
6572
+ process.stderr.write(
6573
+ `[checkpoint] daemon unreachable or cloud lookup failed (${checkpoint.message}); pass --skip-checkpoint to bypass
6574
+ `
6575
+ );
6576
+ } else {
6577
+ process.stderr.write(`[checkpoint] failed: ${checkpoint.message ?? "unknown"}
6578
+ `);
6579
+ }
6580
+ process.exit(12);
6581
+ }
6582
+ }
6003
6583
  const res = await client.im.tasks.update(taskId, {
6004
6584
  title: opts.title,
6005
6585
  description: opts.description,
@@ -6038,6 +6618,7 @@ Logs (${logs.length}):
6038
6618
  }
6039
6619
  });
6040
6620
  task.command("complete <task-id>").description("Mark a task as complete").option("--result <result>", "result or output of the task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6621
+ assertNotRunId(taskId);
6041
6622
  const client = getIMClient2();
6042
6623
  try {
6043
6624
  const res = await client.im.tasks.complete(taskId, {
@@ -6072,6 +6653,7 @@ Logs (${logs.length}):
6072
6653
  }
6073
6654
  });
6074
6655
  task.command("fail <task-id>").description("Mark a task as failed").requiredOption("--error <error>", "error message describing why the task failed").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6656
+ assertNotRunId(taskId);
6075
6657
  const client = getIMClient2();
6076
6658
  try {
6077
6659
  const res = await client.im.tasks.fail(taskId, opts.error);
@@ -6104,6 +6686,7 @@ Logs (${logs.length}):
6104
6686
  }
6105
6687
  });
6106
6688
  task.command("approve <task-id>").description("Approve a completed task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6689
+ assertNotRunId(taskId);
6107
6690
  const client = getIMClient2();
6108
6691
  try {
6109
6692
  const res = await client.im.tasks.approve(taskId);
@@ -6134,6 +6717,7 @@ Logs (${logs.length}):
6134
6717
  }
6135
6718
  });
6136
6719
  task.command("reject <task-id>").description("Reject a task").requiredOption("--reason <reason>", "reason for rejection").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6720
+ assertNotRunId(taskId);
6137
6721
  const client = getIMClient2();
6138
6722
  try {
6139
6723
  const res = await client.im.tasks.reject(taskId, opts.reason);
@@ -6163,28 +6747,463 @@ Logs (${logs.length}):
6163
6747
  process.exit(1);
6164
6748
  }
6165
6749
  });
6166
- task.command("cancel <task-id>").description("Cancel a task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6167
- const client = getIMClient2();
6168
- try {
6169
- const res = await client.im.tasks.cancel(taskId);
6750
+ 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("--run-id <id>", "dispatch task id (alias for --task; from <execution_context>; env fallback PRISMER_TASK_ID)").option("--daemon-port <port>", "daemon local-server port (env fallback PRISMER_DAEMON_PORT, default 3210)").option("--json", "output raw JSON response").action(async (filePath, opts) => {
6751
+ const targetTaskId = opts.task ?? opts.runId ?? process.env.PRISMER_TASK_ID;
6752
+ if (!targetTaskId) {
6753
+ process.stderr.write("Error: --task is required (or set PRISMER_TASK_ID env)\n");
6754
+ process.exit(1);
6755
+ }
6756
+ assertNotRunId(targetTaskId);
6757
+ const proxy = detectDeliverProxy({
6758
+ taskId: targetTaskId,
6759
+ daemonPort: opts.daemonPort
6760
+ });
6761
+ if (proxy) {
6762
+ const result = await proxyDeliver(proxy, filePath, "task-attach");
6763
+ if (!result.ok) {
6764
+ process.stderr.write(`Error: ${result.error ?? "task attach failed"}
6765
+ `);
6766
+ process.exit(1);
6767
+ }
6170
6768
  if (opts.json) {
6171
- process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6769
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
6172
6770
  return;
6173
6771
  }
6174
- if (!res.ok) {
6175
- process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6772
+ process.stdout.write(`Attached to task ${targetTaskId} (assetId: ${result.assetId ?? "-"})
6773
+ `);
6774
+ return;
6775
+ }
6776
+ const client = getIMClient2();
6777
+ try {
6778
+ const getRes = await client.im.tasks.get(targetTaskId);
6779
+ if (!getRes.ok || !getRes.data) {
6780
+ process.stderr.write(`Error: task ${targetTaskId} not found: ${getRes.error?.message ?? "unknown"}
6176
6781
  `);
6177
6782
  process.exit(1);
6178
6783
  }
6179
- const t = res.data;
6180
- process.stdout.write(`Task cancelled
6181
-
6182
- `);
6183
- process.stdout.write(`ID: ${t.id}
6184
- `);
6185
- process.stdout.write(`Title: ${t.title}
6784
+ const wsId = getRes.data.task?.workspaceId;
6785
+ if (!wsId) {
6786
+ process.stderr.write(`Error: task ${targetTaskId} has no workspaceId; cannot upload
6186
6787
  `);
6187
- process.stdout.write(`Status: ${t.status}
6788
+ process.exit(1);
6789
+ }
6790
+ let bytes;
6791
+ try {
6792
+ bytes = await import_node_fs.promises.readFile(filePath);
6793
+ } catch (err) {
6794
+ process.stderr.write(`Error: cannot read ${filePath}: ${err instanceof Error ? err.message : String(err)}
6795
+ `);
6796
+ process.exit(1);
6797
+ }
6798
+ const contentHash = (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
6799
+ const displayName = opts.name ?? (0, import_node_path2.basename)(filePath);
6800
+ const uploadRes = await client.im.assets.upload(bytes, {
6801
+ workspaceId: wsId,
6802
+ sourceTaskId: targetTaskId,
6803
+ kind: "agent-output",
6804
+ fileName: displayName,
6805
+ metadata: {
6806
+ // boundKind stamped via metadata so service-side cloud code can
6807
+ // mirror onto the column. Service POST handler reads this and
6808
+ // populates IMAsset.boundKind='task-bound'.
6809
+ boundKind: "task-bound",
6810
+ filename: displayName,
6811
+ attachedBy: "cloud-task-attach"
6812
+ }
6813
+ });
6814
+ if (opts.json) {
6815
+ process.stdout.write(JSON.stringify(uploadRes, null, 2) + "\n");
6816
+ return;
6817
+ }
6818
+ if (!uploadRes.ok || !uploadRes.data) {
6819
+ process.stderr.write(`Error: attach failed: ${uploadRes.error?.message ?? "unknown"}
6820
+ `);
6821
+ process.exit(1);
6822
+ }
6823
+ const dedupHit = Boolean(uploadRes.meta?.dedup);
6824
+ const asset = uploadRes.data;
6825
+ process.stdout.write(`Attached ${displayName}
6826
+
6827
+ `);
6828
+ process.stdout.write(`AssetId: ${asset.id}
6829
+ `);
6830
+ process.stdout.write(`Task: ${targetTaskId}
6831
+ `);
6832
+ process.stdout.write(`ContentHash: ${contentHash}
6833
+ `);
6834
+ process.stdout.write(`SizeBytes: ${bytes.length}
6835
+ `);
6836
+ process.stdout.write(`Dedup: ${dedupHit ? "true (existing IMAsset row)" : "false (created)"}
6837
+ `);
6838
+ if (asset.cdnUrl) process.stdout.write(`CDN URL: ${asset.cdnUrl}
6839
+ `);
6840
+ } catch (err) {
6841
+ const message = err instanceof Error ? err.message : String(err);
6842
+ process.stderr.write(`Error: ${message}
6843
+ `);
6844
+ process.exit(1);
6845
+ }
6846
+ });
6847
+ task.command("cancel <task-id>").description("Cancel a task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6848
+ assertNotRunId(taskId);
6849
+ const client = getIMClient2();
6850
+ try {
6851
+ const res = await client.im.tasks.cancel(taskId);
6852
+ if (opts.json) {
6853
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6854
+ return;
6855
+ }
6856
+ if (!res.ok) {
6857
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6858
+ `);
6859
+ process.exit(1);
6860
+ }
6861
+ const t = res.data;
6862
+ process.stdout.write(`Task cancelled
6863
+
6864
+ `);
6865
+ process.stdout.write(`ID: ${t.id}
6866
+ `);
6867
+ process.stdout.write(`Title: ${t.title}
6868
+ `);
6869
+ process.stdout.write(`Status: ${t.status}
6870
+ `);
6871
+ } catch (err) {
6872
+ const message = err instanceof Error ? err.message : String(err);
6873
+ process.stderr.write(`Error: ${message}
6874
+ `);
6875
+ process.exit(1);
6876
+ }
6877
+ });
6878
+ 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) => {
6879
+ assertNotRunId(taskId);
6880
+ const client = getIMClient2();
6881
+ try {
6882
+ let md = "";
6883
+ if (opts.file) {
6884
+ md = await import_node_fs.promises.readFile(opts.file, "utf-8");
6885
+ } else if (opts.markdown) {
6886
+ md = opts.markdown;
6887
+ } else {
6888
+ throw new Error("one of --file or --markdown is required");
6889
+ }
6890
+ const res = await client.im.tasks.spec.set(taskId, { markdown: md });
6891
+ if (opts.json) {
6892
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6893
+ return;
6894
+ }
6895
+ if (!res.ok) {
6896
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6897
+ `);
6898
+ process.exit(1);
6899
+ }
6900
+ const v = res.data;
6901
+ process.stdout.write(`SPEC.md saved (revision ${v.revision})
6902
+ `);
6903
+ } catch (err) {
6904
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6905
+ `);
6906
+ process.exit(1);
6907
+ }
6908
+ });
6909
+ task.command("spec-show <task-id>").description("Print SPEC.md content for a task").option("--json", "output raw JSON response").action(async (taskId, opts) => {
6910
+ assertNotRunId(taskId);
6911
+ const client = getIMClient2();
6912
+ try {
6913
+ const res = await client.im.tasks.spec.get(taskId);
6914
+ if (opts.json) {
6915
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6916
+ return;
6917
+ }
6918
+ if (!res.ok) {
6919
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6920
+ `);
6921
+ process.exit(1);
6922
+ }
6923
+ const v = res.data;
6924
+ process.stdout.write(v.markdown);
6925
+ if (!v.markdown.endsWith("\n")) process.stdout.write("\n");
6926
+ process.stdout.write(`# revision ${v.revision}
6927
+ `);
6928
+ } catch (err) {
6929
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6930
+ `);
6931
+ process.exit(1);
6932
+ }
6933
+ });
6934
+ 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) => {
6935
+ assertNotRunId(taskId);
6936
+ const client = getIMClient2();
6937
+ try {
6938
+ const res = await client.im.tasks.todo.add(taskId, {
6939
+ text: text.join(" "),
6940
+ depth: opts.depth
6941
+ });
6942
+ if (opts.json) {
6943
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6944
+ return;
6945
+ }
6946
+ if (!res.ok) {
6947
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6948
+ `);
6949
+ process.exit(1);
6950
+ }
6951
+ const v = res.data;
6952
+ process.stdout.write(`TODO item added \u2014 ${v.doneCount}/${v.totalCount} (rev ${v.revision})
6953
+ `);
6954
+ } catch (err) {
6955
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6956
+ `);
6957
+ process.exit(1);
6958
+ }
6959
+ });
6960
+ 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) => {
6961
+ assertNotRunId(taskId);
6962
+ const client = getIMClient2();
6963
+ try {
6964
+ const idx = parseInt(indexRaw, 10);
6965
+ if (!Number.isFinite(idx) || idx < 0) throw new Error("index must be a non-negative integer");
6966
+ const res = await client.im.tasks.todo.toggle(taskId, idx, true);
6967
+ if (opts.json) {
6968
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6969
+ return;
6970
+ }
6971
+ if (!res.ok) {
6972
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
6973
+ `);
6974
+ process.exit(1);
6975
+ }
6976
+ const v = res.data;
6977
+ process.stdout.write(
6978
+ `TODO[${idx}] \u2192 done. progress ${v.doneCount}/${v.totalCount} (${Math.round(v.progressPct * 100)}%)
6979
+ `
6980
+ );
6981
+ } catch (err) {
6982
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6983
+ `);
6984
+ process.exit(1);
6985
+ }
6986
+ });
6987
+ task.command("todo-uncheck <task-id> <index>").description("Un-tick a TODO item").option("--json", "output raw JSON response").action(async (taskId, indexRaw, opts) => {
6988
+ assertNotRunId(taskId);
6989
+ const client = getIMClient2();
6990
+ try {
6991
+ const idx = parseInt(indexRaw, 10);
6992
+ if (!Number.isFinite(idx) || idx < 0) throw new Error("index must be a non-negative integer");
6993
+ const res = await client.im.tasks.todo.toggle(taskId, idx, false);
6994
+ if (opts.json) {
6995
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6996
+ return;
6997
+ }
6998
+ if (!res.ok) {
6999
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7000
+ `);
7001
+ process.exit(1);
7002
+ }
7003
+ const v = res.data;
7004
+ process.stdout.write(`TODO[${idx}] \u2192 pending. progress ${v.doneCount}/${v.totalCount}
7005
+ `);
7006
+ } catch (err) {
7007
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7008
+ `);
7009
+ process.exit(1);
7010
+ }
7011
+ });
7012
+ 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) => {
7013
+ assertNotRunId(taskId);
7014
+ const client = getIMClient2();
7015
+ try {
7016
+ const res = await client.im.tasks.todo.list(taskId);
7017
+ if (opts.json) {
7018
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7019
+ return;
7020
+ }
7021
+ if (!res.ok) {
7022
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7023
+ `);
7024
+ process.exit(1);
7025
+ }
7026
+ const v = res.data;
7027
+ process.stdout.write(
7028
+ `TODO progress: ${v.doneCount}/${v.totalCount} (${Math.round(v.progressPct * 100)}%) \xB7 rev ${v.revision}
7029
+ `
7030
+ );
7031
+ for (const it of v.items) {
7032
+ const tick = it.status === "done" ? "\u2611" : "\u2610";
7033
+ const indent = " ".repeat(it.depth);
7034
+ process.stdout.write(` ${indent}${tick} [${it.index}] ${it.text}
7035
+ `);
7036
+ }
7037
+ } catch (err) {
7038
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7039
+ `);
7040
+ process.exit(1);
7041
+ }
7042
+ });
7043
+ 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) => {
7044
+ assertNotRunId(taskId);
7045
+ const client = getIMClient2();
7046
+ try {
7047
+ const res = await client.im.tasks.getAcceptance(taskId);
7048
+ if (opts.json) {
7049
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7050
+ return;
7051
+ }
7052
+ if (!res.ok) {
7053
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7054
+ `);
7055
+ process.exit(1);
7056
+ }
7057
+ const v = res.data;
7058
+ process.stdout.write(`Acceptance for ${taskId}: ${v.overall} (${v.completedCount}/${v.totalCount})
7059
+ `);
7060
+ for (const c of v.criteria) {
7061
+ const mark = c.status === "passed" ? "\u2713" : c.status === "failed" ? "\u2717" : c.status === "n/a" ? "\xB7" : "\u25EF";
7062
+ const req = c.required === false ? " [optional]" : "";
7063
+ const ver = c.verifierAgentId ? ` @${c.verifierAgentId}` : "";
7064
+ process.stdout.write(` ${mark} [${c.verifyMode}${ver}] ${c.expectation}${req} (${c.status}) \u2014 ${c.id}
7065
+ `);
7066
+ }
7067
+ } catch (err) {
7068
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7069
+ `);
7070
+ process.exit(1);
7071
+ }
7072
+ });
7073
+ task.command("add-criterion <task-id>").description("Add an acceptance criterion to a task (rev 2 \u2014 verifyMode + expectation)").requiredOption(
7074
+ "--mode <mode>",
7075
+ "verifyMode: qualitative | quantitative | agent-self-check | manual"
7076
+ ).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) => {
7077
+ assertNotRunId(taskId);
7078
+ const client = getIMClient2();
7079
+ try {
7080
+ const VALID = ["qualitative", "quantitative", "agent-self-check", "manual"];
7081
+ if (!VALID.includes(opts.mode)) {
7082
+ throw new Error(`--mode must be one of ${VALID.join(" | ")}`);
7083
+ }
7084
+ const res = await client.im.tasks.criteria.add(taskId, {
7085
+ verifyMode: opts.mode,
7086
+ expectation: opts.expectation,
7087
+ verifierAgentId: opts.verifierAgent ?? null,
7088
+ weight: opts.weight ?? 1,
7089
+ required: !opts.optional
7090
+ });
7091
+ if (opts.json) {
7092
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7093
+ return;
7094
+ }
7095
+ if (!res.ok) {
7096
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7097
+ `);
7098
+ process.exit(1);
7099
+ }
7100
+ const { criterion } = res.data;
7101
+ process.stdout.write(`Added criterion ${criterion.id} to task ${taskId}
7102
+ `);
7103
+ } catch (err) {
7104
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7105
+ `);
7106
+ process.exit(1);
7107
+ }
7108
+ });
7109
+ task.command("verify <task-id>").description(
7110
+ "Assignee self-check: list all agent-self-check criteria + mark them passed (run before status=review)"
7111
+ ).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) => {
7112
+ assertNotRunId(taskId);
7113
+ const client = getIMClient2();
7114
+ try {
7115
+ const view = await client.im.tasks.getAcceptance(taskId);
7116
+ if (!view.ok) {
7117
+ process.stderr.write(`Error: ${view.error?.message || "Unknown error"}
7118
+ `);
7119
+ process.exit(1);
7120
+ }
7121
+ const v = view.data;
7122
+ const targets = v.criteria.filter(
7123
+ (c) => c.verifyMode === "agent-self-check" && c.status === "pending"
7124
+ );
7125
+ if (targets.length === 0) {
7126
+ process.stdout.write("No pending agent-self-check criteria.\n");
7127
+ return;
7128
+ }
7129
+ const results = [];
7130
+ let anyFailed = false;
7131
+ for (const c of targets) {
7132
+ const r = await client.im.tasks.criteria.verify(taskId, c.id, {
7133
+ outcome: "passed",
7134
+ note: opts.note
7135
+ });
7136
+ results.push({ id: c.id, outcome: r.ok ? "passed" : "error" });
7137
+ if (!r.ok) anyFailed = true;
7138
+ }
7139
+ if (opts.json) {
7140
+ process.stdout.write(JSON.stringify({ ok: !anyFailed, results }, null, 2) + "\n");
7141
+ return;
7142
+ }
7143
+ for (const r of results) {
7144
+ process.stdout.write(` ${r.outcome === "passed" ? "\u2713" : "\u2717"} ${r.id}
7145
+ `);
7146
+ }
7147
+ if (anyFailed) process.exit(1);
7148
+ } catch (err) {
7149
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7150
+ `);
7151
+ process.exit(1);
7152
+ }
7153
+ });
7154
+ task.command("verify-criterion <task-id> <criterion-id>").description(
7155
+ "Report verify outcome for one criterion. Used by reviewers (manual), verifier agents, and assignees (self-check)."
7156
+ ).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) => {
7157
+ assertNotRunId(taskId);
7158
+ const client = getIMClient2();
7159
+ try {
7160
+ const VALID = ["passed", "failed", "n/a", "waived"];
7161
+ if (!VALID.includes(opts.outcome)) {
7162
+ throw new Error(`--outcome must be one of ${VALID.join(" | ")}`);
7163
+ }
7164
+ if (opts.outcome === "waived" && !opts.waiveReason) {
7165
+ throw new Error("--waive-reason is required when --outcome waived");
7166
+ }
7167
+ const outcome = opts.outcome;
7168
+ const res = await client.im.tasks.criteria.verify(taskId, criterionId, {
7169
+ outcome,
7170
+ note: opts.note,
7171
+ evidenceRefs: opts.evidence,
7172
+ waiveReason: opts.waiveReason
7173
+ });
7174
+ if (opts.json) {
7175
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7176
+ return;
7177
+ }
7178
+ if (!res.ok) {
7179
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7180
+ `);
7181
+ process.exit(1);
7182
+ }
7183
+ process.stdout.write(`Criterion ${criterionId} \u2192 ${opts.outcome}
7184
+ `);
7185
+ } catch (err) {
7186
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
7187
+ `);
7188
+ process.exit(1);
7189
+ }
7190
+ });
7191
+ 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) => {
7192
+ assertNotRunId(taskId);
7193
+ const client = getIMClient2();
7194
+ try {
7195
+ const res = await client.im.tasks.applyTemplate(taskId, opts.template);
7196
+ if (opts.json) {
7197
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7198
+ return;
7199
+ }
7200
+ if (!res.ok) {
7201
+ process.stderr.write(`Error: ${res.error?.message || "Unknown error"}
7202
+ `);
7203
+ process.exit(1);
7204
+ }
7205
+ const v = res.data;
7206
+ process.stdout.write(`Applied template ${opts.template} (${v.totalCount} criteria total)
6188
7207
  `);
6189
7208
  } catch (err) {
6190
7209
  const message = err instanceof Error ? err.message : String(err);
@@ -6496,7 +7515,7 @@ function printFileTable(files) {
6496
7515
  const idLen = Math.max(2, ...files.map((f) => f.id.length));
6497
7516
  const scopeLen = Math.max(5, ...files.map((f) => f.scope.length));
6498
7517
  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)}`;
7518
+ const row = (id, scope, path6) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path6.padEnd(pathLen)}`;
6500
7519
  process.stdout.write(row("ID", "SCOPE", "PATH") + "\n");
6501
7520
  process.stdout.write(`${"-".repeat(idLen)} ${"-".repeat(scopeLen)} ${"-".repeat(pathLen)}
6502
7521
  `);
@@ -6679,88 +7698,544 @@ ${result.content}
6679
7698
  `);
6680
7699
  }
6681
7700
  } catch (err) {
6682
- const message = err instanceof Error ? err.message : String(err);
6683
- process.stderr.write(`Error: ${message}
7701
+ const message = err instanceof Error ? err.message : String(err);
7702
+ process.stderr.write(`Error: ${message}
7703
+ `);
7704
+ process.exit(1);
7705
+ }
7706
+ });
7707
+ skill.command("uninstall <slug>").description("Uninstall a skill").option("--no-local", "cloud-only uninstall, do not remove local files").option("--json", "output raw JSON response").action(async (slug, opts) => {
7708
+ const client = getIMClient2();
7709
+ try {
7710
+ let res;
7711
+ if (!opts.local) {
7712
+ res = await client.im.evolution.uninstallSkill(slug);
7713
+ } else {
7714
+ res = await client.im.evolution.uninstallSkillLocal(slug);
7715
+ }
7716
+ if (opts.json) {
7717
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7718
+ return;
7719
+ }
7720
+ const result = res;
7721
+ if (result?.ok === false) {
7722
+ process.stderr.write(`Uninstall failed.
7723
+ `);
7724
+ process.exit(1);
7725
+ }
7726
+ process.stdout.write(`Uninstalled: ${slug}
7727
+ `);
7728
+ const removedPaths = result?.data?.removedPaths ?? [];
7729
+ if (removedPaths.length > 0) {
7730
+ process.stdout.write("Local files removed:\n");
7731
+ for (const p of removedPaths) {
7732
+ process.stdout.write(` ${p}
7733
+ `);
7734
+ }
7735
+ } else if (!opts.local) {
7736
+ process.stdout.write("Cloud-only uninstall complete (no local files removed).\n");
7737
+ }
7738
+ } catch (err) {
7739
+ const message = err instanceof Error ? err.message : String(err);
7740
+ process.stderr.write(`Error: ${message}
7741
+ `);
7742
+ process.exit(1);
7743
+ }
7744
+ });
7745
+ skill.command("sync").description("Re-sync all installed skills to local filesystem").option("--platform <platform>", "target platform: claude-code, openclaw, opencode, or all", "all").option("--json", "output raw JSON response").action(async (opts) => {
7746
+ const client = getIMClient2();
7747
+ try {
7748
+ const platforms = parsePlatforms(opts.platform);
7749
+ const res = await client.im.evolution.syncSkillsLocal({ platforms });
7750
+ if (opts.json) {
7751
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7752
+ return;
7753
+ }
7754
+ const result = res;
7755
+ const synced = result?.synced ?? 0;
7756
+ const failed = result?.failed ?? 0;
7757
+ process.stdout.write(`Synced: ${synced} skill(s)`);
7758
+ if (failed > 0) {
7759
+ process.stdout.write(`, failed: ${failed}`);
7760
+ }
7761
+ process.stdout.write("\n");
7762
+ const paths = result?.paths ?? [];
7763
+ if (paths.length > 0) {
7764
+ process.stdout.write("Files written:\n");
7765
+ for (const p of paths) {
7766
+ process.stdout.write(` ${p}
7767
+ `);
7768
+ }
7769
+ }
7770
+ } catch (err) {
7771
+ const message = err instanceof Error ? err.message : String(err);
7772
+ process.stderr.write(`Error: ${message}
7773
+ `);
7774
+ process.exit(1);
7775
+ }
7776
+ });
7777
+ }
7778
+
7779
+ // src/commands/skill-draft.ts
7780
+ var fs3 = __toESM(require("fs"));
7781
+ function register7(parent, getIMClient2, _getAPIClient) {
7782
+ const skill = parent.commands.find((c) => c.name() === "skill") ?? parent.command("skill");
7783
+ const draft = skill.command("draft").description("Author / patch / regenerate / show / list skill drafts (release201/07)");
7784
+ 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) => {
7785
+ if (!opts.manifest) {
7786
+ process.stderr.write("Error: --manifest <path-to-manifest.json> is required\n");
7787
+ process.exit(1);
7788
+ }
7789
+ let manifest;
7790
+ try {
7791
+ manifest = JSON.parse(fs3.readFileSync(opts.manifest, "utf-8"));
7792
+ } catch (err) {
7793
+ process.stderr.write(`Error: failed to read manifest ${opts.manifest}: ${err.message}
7794
+ `);
7795
+ process.exit(1);
7796
+ }
7797
+ const slug = opts.slug ?? manifest.slug;
7798
+ const workspaceId = opts.workspace ?? manifest.workspaceId;
7799
+ const ownerAgentId = opts.ownerAgent ?? manifest.ownerAgentId;
7800
+ if (!slug || !manifest.name || !manifest.description) {
7801
+ process.stderr.write("Error: manifest must include slug, name, description\n");
7802
+ process.exit(1);
7803
+ }
7804
+ if (!workspaceId) {
7805
+ process.stderr.write("Error: --workspace <id> or manifest.workspaceId is required\n");
7806
+ process.exit(1);
7807
+ }
7808
+ if (!Array.isArray(manifest.files) || manifest.files.length < 2) {
7809
+ process.stderr.write("Error: manifest.files[] must include at least SKILL.md + skill.json\n");
7810
+ process.exit(1);
7811
+ }
7812
+ const client = getIMClient2();
7813
+ try {
7814
+ const res = await client.im.skills.draft.create({
7815
+ slug,
7816
+ name: manifest.name,
7817
+ description: manifest.description,
7818
+ workspaceId,
7819
+ ownerAgentId,
7820
+ files: manifest.files,
7821
+ metadata: manifest.metadata
7822
+ });
7823
+ if (opts.json) {
7824
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7825
+ return;
7826
+ }
7827
+ if (!res.ok) {
7828
+ process.stderr.write(`Error: ${res.error ?? "create failed"}
7829
+ `);
7830
+ process.exit(1);
7831
+ }
7832
+ const data = res.data ?? res;
7833
+ process.stdout.write(`Draft submitted.
7834
+ `);
7835
+ process.stdout.write(` id: ${data.id}
7836
+ `);
7837
+ process.stdout.write(` slug: ${data.slug}
7838
+ `);
7839
+ process.stdout.write(` manifest revision: ${data.manifestRevision}
7840
+ `);
7841
+ if (data.reviewTaskId) {
7842
+ process.stdout.write(` review task: ${data.reviewTaskId}
7843
+ `);
7844
+ }
7845
+ const warnings = data.validationWarnings ?? [];
7846
+ if (warnings.length > 0) {
7847
+ process.stdout.write(` warnings:
7848
+ `);
7849
+ for (const w of warnings) {
7850
+ process.stdout.write(` [${w.gate}] ${w.message}
7851
+ `);
7852
+ }
7853
+ }
7854
+ } catch (err) {
7855
+ process.stderr.write(`Error: ${err.message}
7856
+ `);
7857
+ process.exit(1);
7858
+ }
7859
+ });
7860
+ 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) => {
7861
+ if (!opts.file) {
7862
+ process.stderr.write("Error: --file <path> is required\n");
7863
+ process.exit(1);
7864
+ }
7865
+ if (!["add", "update", "delete"].includes(opts.op)) {
7866
+ process.stderr.write(`Error: --op must be add | update | delete (got ${opts.op})
7867
+ `);
7868
+ process.exit(1);
7869
+ }
7870
+ let content;
7871
+ if (opts.op !== "delete") {
7872
+ if (opts.contentFile) {
7873
+ try {
7874
+ content = fs3.readFileSync(opts.contentFile, "utf-8");
7875
+ } catch (err) {
7876
+ process.stderr.write(`Error: failed to read --content-file: ${err.message}
7877
+ `);
7878
+ process.exit(1);
7879
+ }
7880
+ } else if (opts.content !== void 0) {
7881
+ content = opts.content;
7882
+ } else {
7883
+ process.stderr.write("Error: --content or --content-file is required for add/update\n");
7884
+ process.exit(1);
7885
+ }
7886
+ }
7887
+ const client = getIMClient2();
7888
+ try {
7889
+ const res = await client.im.skills.draft.patch(draftId, {
7890
+ files: [{ path: opts.file, op: opts.op, content }],
7891
+ reason: opts.reason
7892
+ });
7893
+ if (opts.json) {
7894
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7895
+ return;
7896
+ }
7897
+ if (!res.ok) {
7898
+ process.stderr.write(`Error: ${res.error ?? "patch failed"}
7899
+ `);
7900
+ process.exit(1);
7901
+ }
7902
+ const data = res.data ?? res;
7903
+ process.stdout.write(`Patch applied.
7904
+ `);
7905
+ process.stdout.write(` id: ${data.id}
7906
+ `);
7907
+ process.stdout.write(` manifest revision: ${data.manifestRevision}
7908
+ `);
7909
+ } catch (err) {
7910
+ process.stderr.write(`Error: ${err.message}
7911
+ `);
7912
+ process.exit(1);
7913
+ }
7914
+ });
7915
+ 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) => {
7916
+ if (!opts.reason) {
7917
+ process.stderr.write("Error: --reason <text> is required\n");
7918
+ process.exit(1);
7919
+ }
7920
+ const client = getIMClient2();
7921
+ try {
7922
+ const res = await client.im.skills.draft.regenerate(draftId, { reason: opts.reason });
7923
+ if (opts.json) {
7924
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7925
+ return;
7926
+ }
7927
+ if (!res.ok) {
7928
+ process.stderr.write(`Error: ${res.error ?? "regenerate failed"}
7929
+ `);
7930
+ process.exit(1);
7931
+ }
7932
+ const data = res.data ?? res;
7933
+ process.stdout.write(`Regenerate requested.
7934
+ `);
7935
+ process.stdout.write(` id: ${data.id}
7936
+ `);
7937
+ process.stdout.write(` session id: ${data.sessionId}
7938
+ `);
7939
+ } catch (err) {
7940
+ process.stderr.write(`Error: ${err.message}
7941
+ `);
7942
+ process.exit(1);
7943
+ }
7944
+ });
7945
+ draft.command("show <draft-id>").description("Show a draft's manifest + revision history").option("--json", "Output raw JSON response").action(async (draftId, opts) => {
7946
+ const client = getIMClient2();
7947
+ try {
7948
+ const res = await client.im.skills.draft.show(draftId);
7949
+ if (opts.json) {
7950
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7951
+ return;
7952
+ }
7953
+ if (!res.ok) {
7954
+ process.stderr.write(`Error: ${res.error ?? "show failed"}
7955
+ `);
7956
+ process.exit(1);
7957
+ }
7958
+ const data = res.data ?? res;
7959
+ process.stdout.write(`Draft ${data.id}
7960
+ `);
7961
+ process.stdout.write(` slug: ${data.slug}
7962
+ `);
7963
+ process.stdout.write(` name: ${data.name}
7964
+ `);
7965
+ process.stdout.write(` status: ${data.status}
7966
+ `);
7967
+ process.stdout.write(` workspace: ${data.workspaceId}
7968
+ `);
7969
+ process.stdout.write(` ownerAgent: ${data.ownerAgentId}
7970
+ `);
7971
+ process.stdout.write(` revision: ${data.manifestRevision}
7972
+ `);
7973
+ process.stdout.write(` license: ${data.license}
7974
+ `);
7975
+ process.stdout.write(` compatibility: ${(data.compatibility ?? []).join(", ")}
7976
+ `);
7977
+ process.stdout.write(` files:
7978
+ `);
7979
+ for (const f of data.files ?? []) {
7980
+ process.stdout.write(` ${f.path} (${f.size}B, sha256:${(f.sha256 ?? "").slice(0, 8)}\u2026)
7981
+ `);
7982
+ }
7983
+ } catch (err) {
7984
+ process.stderr.write(`Error: ${err.message}
7985
+ `);
7986
+ process.exit(1);
7987
+ }
7988
+ });
7989
+ draft.command("list").description("List drafts in a workspace").option("--workspace <id>", "Workspace id (required)").option("--json", "Output raw JSON response").action(async (opts) => {
7990
+ if (!opts.workspace) {
7991
+ process.stderr.write("Error: --workspace <id> is required\n");
7992
+ process.exit(1);
7993
+ }
7994
+ const client = getIMClient2();
7995
+ try {
7996
+ const res = await client.im.skills.draft.list(opts.workspace);
7997
+ if (opts.json) {
7998
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
7999
+ return;
8000
+ }
8001
+ if (!res.ok) {
8002
+ process.stderr.write(`Error: ${res.error ?? "list failed"}
8003
+ `);
8004
+ process.exit(1);
8005
+ }
8006
+ const drafts = res.data ?? [];
8007
+ if (drafts.length === 0) {
8008
+ process.stdout.write("No drafts in this workspace.\n");
8009
+ return;
8010
+ }
8011
+ process.stdout.write(`Drafts (${drafts.length}):
8012
+ `);
8013
+ for (const d of drafts) {
8014
+ process.stdout.write(` ${d.id} ${d.slug} ${(d.name ?? "").slice(0, 32)} rev:${(d.contentManifestRevision ?? "").slice(0, 8)}\u2026 updated:${d.updatedAt}
8015
+ `);
8016
+ }
8017
+ } catch (err) {
8018
+ process.stderr.write(`Error: ${err.message}
8019
+ `);
8020
+ process.exit(1);
8021
+ }
8022
+ });
8023
+ }
8024
+
8025
+ // src/commands/code-grep.ts
8026
+ var fs4 = __toESM(require("fs"));
8027
+ var path2 = __toESM(require("path"));
8028
+ var crypto2 = __toESM(require("crypto"));
8029
+ var os = __toESM(require("os"));
8030
+ var DEFAULT_MAX_MATCHES = 50;
8031
+ var SCRATCH_DIR_GLOB = path2.join(os.homedir(), ".prismer", "agents");
8032
+ function isPathWhitelisted(repo) {
8033
+ const abs = path2.resolve(repo);
8034
+ const workspaceRoot = process.env.PRISMER_WORKSPACE_ROOT;
8035
+ if (workspaceRoot) {
8036
+ const wsAbs = path2.resolve(workspaceRoot);
8037
+ if (abs === wsAbs || abs.startsWith(wsAbs + path2.sep)) return true;
8038
+ }
8039
+ if (abs.startsWith(SCRATCH_DIR_GLOB + path2.sep)) {
8040
+ const rel = abs.slice(SCRATCH_DIR_GLOB.length + 1);
8041
+ const parts = rel.split(path2.sep);
8042
+ if (parts.length >= 2 && parts[1] === "scratch") return true;
8043
+ }
8044
+ return false;
8045
+ }
8046
+ function walk(dir, glob, acc) {
8047
+ let entries;
8048
+ try {
8049
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
8050
+ } catch {
8051
+ return;
8052
+ }
8053
+ for (const e of entries) {
8054
+ const full = path2.join(dir, e.name);
8055
+ if (e.isDirectory()) {
8056
+ if (e.name === "node_modules" || e.name === ".git" || e.name === "dist" || e.name === "build") continue;
8057
+ walk(full, glob, acc);
8058
+ } else if (e.isFile()) {
8059
+ if (!glob || matchGlob(e.name, glob)) acc.push(full);
8060
+ }
8061
+ }
8062
+ }
8063
+ function matchGlob(name, glob) {
8064
+ if (glob.startsWith("*.")) {
8065
+ return name.endsWith(glob.slice(1));
8066
+ }
8067
+ return name === glob;
8068
+ }
8069
+ function register8(parent, _getIMClient, _getAPIClient) {
8070
+ const code = parent.command("code").description("Local code-source helpers for skill-authoring (release201/07)");
8071
+ 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) => {
8072
+ if (!opts.repo) {
8073
+ process.stderr.write("Error: --repo <abs-path> is required\n");
8074
+ process.exit(1);
8075
+ }
8076
+ if (!isPathWhitelisted(opts.repo)) {
8077
+ process.stderr.write(
8078
+ `Error: --repo path ${opts.repo} is not whitelisted; only PRISMER_WORKSPACE_ROOT or ~/.prismer/agents/<id>/scratch are allowed
8079
+ `
8080
+ );
8081
+ process.exit(1);
8082
+ }
8083
+ const maxMatches = parseInt(opts.maxMatches, 10) || DEFAULT_MAX_MATCHES;
8084
+ let re;
8085
+ try {
8086
+ re = new RegExp(pattern);
8087
+ } catch (err) {
8088
+ process.stderr.write(`Error: invalid regex pattern: ${err.message}
6684
8089
  `);
6685
8090
  process.exit(1);
6686
8091
  }
6687
- });
6688
- skill.command("uninstall <slug>").description("Uninstall a skill").option("--no-local", "cloud-only uninstall, do not remove local files").option("--json", "output raw JSON response").action(async (slug, opts) => {
6689
- const client = getIMClient2();
6690
- try {
6691
- let res;
6692
- if (!opts.local) {
6693
- res = await client.im.evolution.uninstallSkill(slug);
6694
- } else {
6695
- res = await client.im.evolution.uninstallSkillLocal(slug);
8092
+ const files = [];
8093
+ walk(opts.repo, opts.glob, files);
8094
+ const results = [];
8095
+ for (const file of files) {
8096
+ if (results.length >= maxMatches) break;
8097
+ let body;
8098
+ try {
8099
+ body = fs4.readFileSync(file, "utf-8");
8100
+ } catch {
8101
+ continue;
6696
8102
  }
6697
- if (opts.json) {
6698
- process.stdout.write(JSON.stringify(res, null, 2) + "\n");
6699
- return;
8103
+ const lines = body.split("\n");
8104
+ for (let i = 0; i < lines.length; i++) {
8105
+ if (re.test(lines[i])) {
8106
+ const startLine = Math.max(0, i - 1);
8107
+ const endLine = Math.min(lines.length, i + 4);
8108
+ const snippet = lines.slice(startLine, endLine).join("\n");
8109
+ const sha256 = crypto2.createHash("sha256").update(snippet).digest("hex");
8110
+ results.push({
8111
+ path: path2.relative(opts.repo, file),
8112
+ line: i + 1,
8113
+ snippet,
8114
+ sha256
8115
+ });
8116
+ if (results.length >= maxMatches) break;
8117
+ }
6700
8118
  }
6701
- const result = res;
6702
- if (result?.ok === false) {
6703
- process.stderr.write(`Uninstall failed.
6704
- `);
6705
- process.exit(1);
8119
+ }
8120
+ process.stdout.write(JSON.stringify(results, null, 2) + "\n");
8121
+ });
8122
+ }
8123
+
8124
+ // src/commands/service-introspect.ts
8125
+ var FETCH_TIMEOUT_MS = 1e4;
8126
+ async function fetchWithTimeout(url, init = {}) {
8127
+ const ctrl = new AbortController();
8128
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
8129
+ try {
8130
+ return await fetch(url, { ...init, signal: ctrl.signal });
8131
+ } finally {
8132
+ clearTimeout(timer);
8133
+ }
8134
+ }
8135
+ async function probeOpenApi(url) {
8136
+ const base = url.replace(/\/$/, "");
8137
+ const candidates = [
8138
+ `${base}/openapi.json`,
8139
+ `${base}/swagger.json`,
8140
+ `${base}/api/openapi.json`,
8141
+ `${base}/.well-known/openapi.json`
8142
+ ];
8143
+ for (const candidate of candidates) {
8144
+ try {
8145
+ const res = await fetchWithTimeout(candidate, { headers: { Accept: "application/json" } });
8146
+ if (res.ok) {
8147
+ const spec = await res.json();
8148
+ if (spec && (spec.openapi || spec.swagger)) {
8149
+ return { found: true, spec, specUrl: candidate };
8150
+ }
6706
8151
  }
6707
- process.stdout.write(`Uninstalled: ${slug}
6708
- `);
6709
- const removedPaths = result?.data?.removedPaths ?? [];
6710
- if (removedPaths.length > 0) {
6711
- process.stdout.write("Local files removed:\n");
6712
- for (const p of removedPaths) {
6713
- process.stdout.write(` ${p}
6714
- `);
8152
+ } catch {
8153
+ }
8154
+ }
8155
+ return { found: false };
8156
+ }
8157
+ async function probeMcp(url) {
8158
+ try {
8159
+ const res = await fetchWithTimeout(url, {
8160
+ method: "POST",
8161
+ headers: { "Content-Type": "application/json" },
8162
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" })
8163
+ });
8164
+ if (!res.ok) return { found: false };
8165
+ const body = await res.json();
8166
+ if (body?.result?.tools) {
8167
+ let resources = [];
8168
+ try {
8169
+ const r = await fetchWithTimeout(url, {
8170
+ method: "POST",
8171
+ headers: { "Content-Type": "application/json" },
8172
+ body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "resources/list" })
8173
+ });
8174
+ if (r.ok) {
8175
+ const rb = await r.json();
8176
+ if (Array.isArray(rb?.result?.resources)) resources = rb.result.resources;
6715
8177
  }
6716
- } else if (!opts.local) {
6717
- process.stdout.write("Cloud-only uninstall complete (no local files removed).\n");
8178
+ } catch {
6718
8179
  }
6719
- } catch (err) {
6720
- const message = err instanceof Error ? err.message : String(err);
6721
- process.stderr.write(`Error: ${message}
8180
+ return { found: true, tools: body.result.tools, resources };
8181
+ }
8182
+ } catch {
8183
+ }
8184
+ return { found: false };
8185
+ }
8186
+ function register9(parent, _getIMClient, _getAPIClient) {
8187
+ const svc = parent.command("service").description("Local service-endpoint helpers for skill-authoring (release201/07)");
8188
+ 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) => {
8189
+ const protocol = opts.protocol ?? "auto";
8190
+ if (protocol !== "auto" && protocol !== "mcp" && protocol !== "openapi") {
8191
+ process.stderr.write(`Error: --protocol must be auto | mcp | openapi (got ${protocol})
6722
8192
  `);
6723
8193
  process.exit(1);
6724
8194
  }
6725
- });
6726
- skill.command("sync").description("Re-sync all installed skills to local filesystem").option("--platform <platform>", "target platform: claude-code, openclaw, opencode, or all", "all").option("--json", "output raw JSON response").action(async (opts) => {
6727
- const client = getIMClient2();
6728
- try {
6729
- const platforms = parsePlatforms(opts.platform);
6730
- const res = await client.im.evolution.syncSkillsLocal({ platforms });
6731
- if (opts.json) {
6732
- process.stdout.write(JSON.stringify(res, null, 2) + "\n");
8195
+ const result = { kind: "unknown", tools: [], endpoints: [], probedAt: (/* @__PURE__ */ new Date()).toISOString() };
8196
+ if (protocol === "mcp" || protocol === "auto") {
8197
+ const mcp = await probeMcp(url);
8198
+ if (mcp.found) {
8199
+ result.kind = "mcp";
8200
+ result.tools = mcp.tools ?? [];
8201
+ result.resources = mcp.resources ?? [];
8202
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
6733
8203
  return;
6734
8204
  }
6735
- const result = res;
6736
- const synced = result?.synced ?? 0;
6737
- const failed = result?.failed ?? 0;
6738
- process.stdout.write(`Synced: ${synced} skill(s)`);
6739
- if (failed > 0) {
6740
- process.stdout.write(`, failed: ${failed}`);
6741
- }
6742
- process.stdout.write("\n");
6743
- const paths = result?.paths ?? [];
6744
- if (paths.length > 0) {
6745
- process.stdout.write("Files written:\n");
6746
- for (const p of paths) {
6747
- process.stdout.write(` ${p}
6748
- `);
8205
+ }
8206
+ if (protocol === "openapi" || protocol === "auto") {
8207
+ const oa = await probeOpenApi(url);
8208
+ if (oa.found) {
8209
+ result.kind = "openapi";
8210
+ result.specUrl = oa.specUrl;
8211
+ const paths = oa.spec?.paths ?? {};
8212
+ for (const p of Object.keys(paths)) {
8213
+ for (const method of Object.keys(paths[p])) {
8214
+ if (["get", "post", "put", "delete", "patch"].includes(method.toLowerCase())) {
8215
+ const op = paths[p][method];
8216
+ result.endpoints.push({
8217
+ path: p,
8218
+ method: method.toUpperCase(),
8219
+ operationId: op?.operationId ?? null,
8220
+ summary: op?.summary ?? null
8221
+ });
8222
+ }
8223
+ }
6749
8224
  }
8225
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
8226
+ return;
6750
8227
  }
6751
- } catch (err) {
6752
- const message = err instanceof Error ? err.message : String(err);
6753
- process.stderr.write(`Error: ${message}
6754
- `);
6755
- process.exit(1);
6756
8228
  }
8229
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
6757
8230
  });
6758
8231
  }
6759
8232
 
6760
8233
  // src/commands/files.ts
6761
- function register7(parent, getIMClient2, _getAPIClient) {
8234
+ function register10(parent, getIMClient2, _getAPIClient) {
6762
8235
  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) => {
8236
+ file.command("upload <path>").description(
8237
+ "Upload bytes only \u2192 returns an upload ID + CDN URL. NOT a delivery: nothing reaches the user. To deliver an artifact use `cloud deliver <path>`."
8238
+ ).option("--mime <type>", "Override MIME type (e.g. image/png)").option("--json", "Output raw JSON response").action(async (filePath, opts) => {
6764
8239
  const client = getIMClient2();
6765
8240
  try {
6766
8241
  const uploadOpts = {};
@@ -6780,13 +8255,39 @@ function register7(parent, getIMClient2, _getAPIClient) {
6780
8255
  `);
6781
8256
  process.stdout.write(`MIME: ${res.mimeType}
6782
8257
  `);
8258
+ process.stdout.write(
8259
+ "Note: this only uploaded bytes \u2014 nothing was delivered to the user. To deliver an artifact, run `cloud deliver <abs-path>`.\n"
8260
+ );
6783
8261
  } catch (err) {
6784
8262
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
6785
8263
  `);
6786
8264
  process.exit(1);
6787
8265
  }
6788
8266
  });
6789
- file.command("send <conversation-id> <path>").description("Upload a file and send it as a message in a conversation").option("-c, --content <text>", "Optional text caption to accompany the file").option("--mime <type>", "Override MIME type").option("--json", "Output raw JSON response").action(async (conversationId, filePath, opts) => {
8267
+ file.command("send <conversation-id> <path>").description(
8268
+ "Send a file as its OWN standalone message (ad-hoc sharing). For a final deliverable use `cloud deliver` instead \u2014 do not run both on the same file (double-delivery)."
8269
+ ).option("-c, --content <text>", "Optional text caption to accompany the file").option("--mime <type>", "Override MIME type").option("--run-id <id>", "dispatch run/task id (from <execution_context>; env fallback PRISMER_TASK_ID/RUN_ID)").option("--conversation-id <id>", "conversation id (alternative source; positional <conversation-id> takes precedence)").option("--daemon-port <port>", "daemon local-server port (env fallback PRISMER_DAEMON_PORT, default 3210)").option("--json", "Output raw JSON response").action(async (conversationIdArg, filePath, opts) => {
8270
+ const conversationId = conversationIdArg || opts.conversationId || "";
8271
+ const proxy = detectDeliverProxy({
8272
+ runId: opts.runId,
8273
+ conversationId,
8274
+ daemonPort: opts.daemonPort
8275
+ });
8276
+ if (proxy) {
8277
+ const result = await proxyDeliver(proxy, filePath, "send", conversationId);
8278
+ if (!result.ok) {
8279
+ process.stderr.write(`Error: ${result.error ?? "delivery failed"}
8280
+ `);
8281
+ process.exit(1);
8282
+ }
8283
+ if (opts.json) {
8284
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
8285
+ return;
8286
+ }
8287
+ process.stdout.write(`File sent to conversation ${conversationId} (assetId: ${result.assetId ?? "-"})
8288
+ `);
8289
+ return;
8290
+ }
6790
8291
  const client = getIMClient2();
6791
8292
  try {
6792
8293
  const sendOpts = {};
@@ -6872,8 +8373,43 @@ function register7(parent, getIMClient2, _getAPIClient) {
6872
8373
  }
6873
8374
 
6874
8375
  // 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");
8376
+ function fail(message) {
8377
+ process.stderr.write(`Error: ${message}
8378
+ `);
8379
+ process.exit(1);
8380
+ }
8381
+ function emitJson(data, json, fallback) {
8382
+ if (json) {
8383
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
8384
+ return;
8385
+ }
8386
+ fallback();
8387
+ }
8388
+ function parseAdminMemberRole(raw) {
8389
+ if (raw === "admin" || raw === "member") return raw;
8390
+ if (raw === void 0) return "member";
8391
+ if (raw === "owner") {
8392
+ fail("--role=owner is not allowed via CLI; use ownership transfer (v2.1+ RFC, release201/16 \xA75.2)");
8393
+ }
8394
+ fail("--role must be admin|member");
8395
+ }
8396
+ function printMemberList(items) {
8397
+ if (items.length === 0) {
8398
+ process.stdout.write("No members in this workspace.\n");
8399
+ return;
8400
+ }
8401
+ process.stdout.write(
8402
+ "ID".padEnd(28) + "ROLE".padEnd(10) + "IM_USER_ID".padEnd(38) + "JOINED\n"
8403
+ );
8404
+ for (const m of items) {
8405
+ process.stdout.write(
8406
+ `${m.id.padEnd(28)}${m.role.padEnd(10)}${m.memberImUserId.padEnd(38)}${m.joinedAt}
8407
+ `
8408
+ );
8409
+ }
8410
+ }
8411
+ function register11(parent, getIMClient2, _getAPIClient) {
8412
+ const workspace = parent.command("workspace").description("Workspace management \u2014 init, groups, agent assignment, and members (release201/16)");
6877
8413
  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
8414
  const client = getIMClient2();
6879
8415
  try {
@@ -6934,62 +8470,299 @@ function register8(parent, getIMClient2, _getAPIClient) {
6934
8470
  process.exit(1);
6935
8471
  }
6936
8472
  });
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) => {
8473
+ 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) => {
8474
+ const client = getIMClient2();
8475
+ try {
8476
+ const res = await client.im.workspace.addAgent(workspaceId, agentId);
8477
+ if (!res.ok) {
8478
+ process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
8479
+ `);
8480
+ process.exit(1);
8481
+ }
8482
+ if (opts.json) {
8483
+ process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
8484
+ return;
8485
+ }
8486
+ process.stdout.write(`Agent ${agentId} added to workspace ${workspaceId}.
8487
+ `);
8488
+ } catch (err) {
8489
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8490
+ `);
8491
+ process.exit(1);
8492
+ }
8493
+ });
8494
+ workspace.command("agents <workspace-id>").description("List agents in a workspace").option("--json", "Output raw JSON response").action(async (workspaceId, opts) => {
8495
+ const client = getIMClient2();
8496
+ try {
8497
+ const res = await client.im.workspace.listAgents(workspaceId);
8498
+ if (!res.ok) {
8499
+ process.stderr.write(`Error: ${res.error?.message || JSON.stringify(res.error)}
8500
+ `);
8501
+ process.exit(1);
8502
+ }
8503
+ const agents = res.data || [];
8504
+ if (opts.json) {
8505
+ process.stdout.write(JSON.stringify(agents, null, 2) + "\n");
8506
+ return;
8507
+ }
8508
+ if (agents.length === 0) {
8509
+ process.stdout.write("No agents in this workspace.\n");
8510
+ return;
8511
+ }
8512
+ process.stdout.write("Agent ID".padEnd(36) + "Type".padEnd(14) + "Name\n");
8513
+ for (const a of agents) {
8514
+ process.stdout.write(
8515
+ `${(a.agentId || a.id || "").padEnd(36)}${(a.agentType || "").padEnd(14)}${a.name || a.displayName || ""}
8516
+ `
8517
+ );
8518
+ }
8519
+ } catch (err) {
8520
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
8521
+ `);
8522
+ process.exit(1);
8523
+ }
8524
+ });
8525
+ const member = workspace.command("member").description("Workspace membership management (release201/16)");
8526
+ member.command("list <workspaceId>").description("List members of a workspace (any member can read)").option("--json", "Output JSON").action(async (workspaceId, opts) => {
8527
+ const client = getIMClient2();
8528
+ const res = await client.workspaces.members.list(workspaceId);
8529
+ if (!res.ok || !res.data) fail(res.error?.message || "list members failed");
8530
+ emitJson(res.data, opts.json === true, () => printMemberList(res.data));
8531
+ });
8532
+ 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) => {
8533
+ const role = parseAdminMemberRole(opts.role);
8534
+ const client = getIMClient2();
8535
+ const res = await client.workspaces.members.add(workspaceId, {
8536
+ memberImUserId: opts.user.trim(),
8537
+ role
8538
+ });
8539
+ if (!res.ok || !res.data) fail(res.error?.message || "add member failed");
8540
+ emitJson(res.data, opts.json === true, () => {
8541
+ const m = res.data;
8542
+ process.stdout.write(`Member added: ${m.memberImUserId} \u2192 ${m.role} (${m.id})
8543
+ `);
8544
+ });
8545
+ });
8546
+ 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) => {
8547
+ const role = parseAdminMemberRole(opts.role);
8548
+ const client = getIMClient2();
8549
+ const res = await client.workspaces.members.update(workspaceId, memberId, { role });
8550
+ if (!res.ok || !res.data) fail(res.error?.message || "update member failed");
8551
+ emitJson(res.data, opts.json === true, () => {
8552
+ const m = res.data;
8553
+ process.stdout.write(`Member ${m.id} role \u2192 ${m.role}
8554
+ `);
8555
+ });
8556
+ });
8557
+ 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) => {
8558
+ const client = getIMClient2();
8559
+ const res = await client.workspaces.members.remove(workspaceId, memberId);
8560
+ if (!res.ok || !res.data) fail(res.error?.message || "remove member failed");
8561
+ emitJson(res.data, opts.json === true, () => {
8562
+ const r = res.data;
8563
+ process.stdout.write(
8564
+ `Member removed: ${r.removed.memberImUserId} (${r.removed.id}); project memberships cascaded: ${r.projectMembershipsRemoved}
8565
+ `
8566
+ );
8567
+ });
8568
+ });
8569
+ }
8570
+
8571
+ // src/commands/project.ts
8572
+ function fail2(message) {
8573
+ process.stderr.write(`Error: ${message}
8574
+ `);
8575
+ process.exit(1);
8576
+ }
8577
+ function emit(data, json, lines) {
8578
+ if (json) {
8579
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
8580
+ return;
8581
+ }
8582
+ lines();
8583
+ }
8584
+ function parsePrincipal(raw) {
8585
+ const idx = raw.indexOf(":");
8586
+ if (idx <= 0) fail2("--principal must be `user:<id>` or `agent:<id>`");
8587
+ const kind = raw.slice(0, idx);
8588
+ const id = raw.slice(idx + 1).trim();
8589
+ if (kind !== "user" && kind !== "agent") fail2("principal kind must be user|agent");
8590
+ if (!id) fail2("principal id must be non-empty");
8591
+ return { kind, id };
8592
+ }
8593
+ function parseRole(raw) {
8594
+ if (raw === void 0) return void 0;
8595
+ if (raw === "owner" || raw === "contributor" || raw === "observer") return raw;
8596
+ fail2("--role must be owner|contributor|observer");
8597
+ }
8598
+ function printProjectList(items) {
8599
+ if (items.length === 0) {
8600
+ process.stdout.write("No projects found.\n");
8601
+ return;
8602
+ }
8603
+ process.stdout.write(
8604
+ "ID".padEnd(28) + "SLUG".padEnd(20) + "STATUS".padEnd(10) + "MEMBERS".padEnd(10) + "NAME\n"
8605
+ );
8606
+ for (const p of items) {
8607
+ process.stdout.write(
8608
+ `${p.id.padEnd(28)}${p.slug.padEnd(20)}${p.status.padEnd(10)}${String(p.memberCount).padEnd(10)}${p.name}
8609
+ `
8610
+ );
8611
+ }
8612
+ }
8613
+ function printProject(p, heading = "Project") {
8614
+ process.stdout.write(`${heading}: ${p.name}
8615
+ `);
8616
+ process.stdout.write(` id ${p.id}
8617
+ `);
8618
+ process.stdout.write(` slug ${p.slug}
8619
+ `);
8620
+ process.stdout.write(` workspaceId ${p.workspaceId}
8621
+ `);
8622
+ process.stdout.write(` status ${p.status}
8623
+ `);
8624
+ process.stdout.write(` owner ${p.ownerUserId}
8625
+ `);
8626
+ if ("memberCount" in p) process.stdout.write(` members ${p.memberCount}
8627
+ `);
8628
+ if (p.description) process.stdout.write(` description ${p.description}
8629
+ `);
8630
+ if (p.archivedAt) process.stdout.write(` archivedAt ${p.archivedAt}
8631
+ `);
8632
+ process.stdout.write(` createdAt ${p.createdAt}
8633
+ `);
8634
+ process.stdout.write(` updatedAt ${p.updatedAt}
8635
+ `);
8636
+ }
8637
+ function printMemberList2(items) {
8638
+ if (items.length === 0) {
8639
+ process.stdout.write("No members in this project.\n");
8640
+ return;
8641
+ }
8642
+ process.stdout.write(
8643
+ "ID".padEnd(28) + "KIND".padEnd(8) + "PRINCIPAL".padEnd(38) + "ROLE".padEnd(14) + "JOINED\n"
8644
+ );
8645
+ for (const m of items) {
8646
+ process.stdout.write(
8647
+ `${m.id.padEnd(28)}${m.principalKind.padEnd(8)}${m.principalId.padEnd(38)}${m.role.padEnd(14)}${m.joinedAt}
8648
+ `
8649
+ );
8650
+ }
8651
+ }
8652
+ function register12(parent, getIMClient2, _getAPIClient) {
8653
+ const project = parent.command("project").description("Project scope (release201/09) \u2014 CRUD + membership management");
8654
+ 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) => {
8655
+ if (opts.status && opts.status !== "active" && opts.status !== "archived") {
8656
+ fail2("--status must be active|archived");
8657
+ }
8658
+ const client = getIMClient2();
8659
+ const res = await client.projects.list({
8660
+ workspaceId: opts.workspace,
8661
+ status: opts.status,
8662
+ search: opts.search,
8663
+ limit: opts.limit,
8664
+ offset: opts.offset
8665
+ });
8666
+ if (!res.ok || !res.data) fail2(res.error?.message || "list failed");
8667
+ const data = res.data;
8668
+ emit(data, opts.json === true, () => {
8669
+ printProjectList(data.items);
8670
+ process.stdout.write(`
8671
+ Total: ${data.total}
8672
+ `);
8673
+ });
8674
+ });
8675
+ 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) => {
8676
+ const client = getIMClient2();
8677
+ const res = await client.projects.create({
8678
+ workspaceId: opts.workspace,
8679
+ slug: opts.slug,
8680
+ name: opts.name,
8681
+ description: opts.description ?? null
8682
+ });
8683
+ if (!res.ok || !res.data) fail2(res.error?.message || "create failed");
8684
+ emit(res.data, opts.json === true, () => printProject(res.data, "Project created"));
8685
+ });
8686
+ project.command("show <projectId>").description("Show a project (with member count)").option("--json", "Output JSON").action(async (projectId, opts) => {
8687
+ const client = getIMClient2();
8688
+ const res = await client.projects.get(projectId);
8689
+ if (!res.ok || !res.data) fail2(res.error?.message || "show failed");
8690
+ emit(res.data, opts.json === true, () => printProject(res.data));
8691
+ });
8692
+ 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) => {
8693
+ if (opts.archive && opts.unarchive) fail2("--archive and --unarchive are mutually exclusive");
8694
+ const patch = {};
8695
+ if (opts.name !== void 0) patch.name = opts.name;
8696
+ if (opts.description !== void 0) patch.description = opts.description === "" ? null : opts.description;
8697
+ if (opts.archive) patch.status = "archived";
8698
+ if (opts.unarchive) patch.status = "active";
8699
+ if (Object.keys(patch).length === 0) fail2("Nothing to update \u2014 pass --name / --description / --archive / --unarchive");
6938
8700
  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)}
8701
+ const res = await client.projects.update(projectId, patch);
8702
+ if (!res.ok || !res.data) fail2(res.error?.message || "update failed");
8703
+ emit(res.data, opts.json === true, () => printProject(res.data, "Project updated"));
8704
+ });
8705
+ 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) => {
8706
+ const cascade = opts.cascade ?? "archive";
8707
+ if (cascade !== "archive" && cascade !== "null" && cascade !== "hard") {
8708
+ fail2("--cascade must be archive|null|hard");
8709
+ }
8710
+ const client = getIMClient2();
8711
+ const res = await client.projects.delete(projectId, { cascade });
8712
+ if (!res.ok) fail2(res.error?.message || "delete failed");
8713
+ emit(res.data, opts.json === true, () => {
8714
+ if (res.data) {
8715
+ printProject(res.data, "Project archived");
8716
+ } else {
8717
+ process.stdout.write(`Project ${projectId} archived.
6943
8718
  `);
6944
- process.exit(1);
6945
- }
6946
- if (opts.json) {
6947
- process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
6948
- return;
6949
8719
  }
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)}
8720
+ });
8721
+ });
8722
+ const members = project.command("members").description("Manage project memberships");
8723
+ members.command("list <projectId>").description("List members of a project").option("--json", "Output JSON").action(async (projectId, opts) => {
8724
+ const client = getIMClient2();
8725
+ const res = await client.projects.members.list(projectId);
8726
+ if (!res.ok || !res.data) fail2(res.error?.message || "list members failed");
8727
+ emit(res.data, opts.json === true, () => printMemberList2(res.data));
8728
+ });
8729
+ 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) => {
8730
+ const { kind, id } = parsePrincipal(opts.principal);
8731
+ const role = parseRole(opts.role);
8732
+ const client = getIMClient2();
8733
+ const res = await client.projects.members.add(projectId, { principalKind: kind, principalId: id, role });
8734
+ if (!res.ok || !res.data) fail2(res.error?.message || "add member failed");
8735
+ emit(res.data, opts.json === true, () => {
8736
+ const m = res.data;
8737
+ process.stdout.write(`Member added: ${m.principalKind}:${m.principalId} \u2192 ${m.role} (${m.id})
6954
8738
  `);
6955
- process.exit(1);
6956
- }
8739
+ });
6957
8740
  });
6958
- workspace.command("agents <workspace-id>").description("List agents in a workspace").option("--json", "Output raw JSON response").action(async (workspaceId, opts) => {
8741
+ 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) => {
8742
+ const role = parseRole(opts.role);
8743
+ if (!role) fail2("--role is required");
6959
8744
  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)}
8745
+ const res = await client.projects.members.update(projectId, membershipId, { role });
8746
+ if (!res.ok || !res.data) fail2(res.error?.message || "update member failed");
8747
+ emit(res.data, opts.json === true, () => {
8748
+ const m = res.data;
8749
+ process.stdout.write(`Member ${m.id} role \u2192 ${m.role}
6964
8750
  `);
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)}
8751
+ });
8752
+ });
8753
+ members.command("remove <projectId> <membershipId>").description("Remove a member from the project").option("--json", "Output JSON").action(async (projectId, membershipId, opts) => {
8754
+ const client = getIMClient2();
8755
+ const res = await client.projects.members.remove(projectId, membershipId);
8756
+ if (!res.ok) fail2(res.error?.message || "remove member failed");
8757
+ emit({ ok: true }, opts.json === true, () => {
8758
+ process.stdout.write(`Membership ${membershipId} removed.
6985
8759
  `);
6986
- process.exit(1);
6987
- }
8760
+ });
6988
8761
  });
6989
8762
  }
6990
8763
 
6991
8764
  // src/commands/security.ts
6992
- function register9(parent, getIMClient2, _getAPIClient) {
8765
+ function register13(parent, getIMClient2, _getAPIClient) {
6993
8766
  const security = parent.command("security").description("Per-conversation encryption and key management");
6994
8767
  security.command("get <conversation-id>").description("Get security settings for a conversation").option("--json", "Output raw JSON response").action(async (convId, opts) => {
6995
8768
  const client = getIMClient2();
@@ -7269,7 +9042,7 @@ function register9(parent, getIMClient2, _getAPIClient) {
7269
9042
  }
7270
9043
 
7271
9044
  // src/commands/community.ts
7272
- var import_node_fs = require("fs");
9045
+ var import_node_fs2 = require("fs");
7273
9046
  function printJson(res, opts) {
7274
9047
  if (opts.json) {
7275
9048
  console.log(JSON.stringify(res, null, 2));
@@ -7296,7 +9069,7 @@ _Next cursor:_ \`${d.nextCursor}\`
7296
9069
  `;
7297
9070
  return t;
7298
9071
  }
7299
- function register10(parent, getIMClient2, _getAPIClient) {
9072
+ function register14(parent, getIMClient2, _getAPIClient) {
7300
9073
  const comm = parent.command("community").description("Evolution community forum \u2014 feed, ask, search, notify");
7301
9074
  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
9075
  const c = getIMClient2();
@@ -7315,7 +9088,7 @@ function register10(parent, getIMClient2, _getAPIClient) {
7315
9088
  process.stdout.write(formatPostsMarkdown(res.data));
7316
9089
  });
7317
9090
  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)";
9091
+ const content = opts.file ? (0, import_node_fs2.readFileSync)(opts.file, "utf8") : body || "(no body)";
7319
9092
  const tags = opts.tags?.split(",").map((s) => s.trim()).filter(Boolean);
7320
9093
  const c = getIMClient2();
7321
9094
  const res = await c.im.community.ask(title, content, tags);
@@ -7397,8 +9170,8 @@ function register10(parent, getIMClient2, _getAPIClient) {
7397
9170
  }
7398
9171
 
7399
9172
  // src/commands/asset.ts
7400
- var fs2 = __toESM(require("fs"));
7401
- var path2 = __toESM(require("path"));
9173
+ var fs5 = __toESM(require("fs"));
9174
+ var path3 = __toESM(require("path"));
7402
9175
  function resolveWorkspaceId(flag) {
7403
9176
  if (flag) return flag;
7404
9177
  if (typeof process !== "undefined" && process.env?.PRISMER_WORKSPACE_ID) {
@@ -7432,7 +9205,7 @@ function formatBytes(n) {
7432
9205
  if (n == null) return "-";
7433
9206
  return String(n);
7434
9207
  }
7435
- function register11(parent, getIMClient2, _getAPIClient) {
9208
+ function register15(parent, getIMClient2, _getAPIClient) {
7436
9209
  const asset = parent.command("asset").description("Inspect and manage workspace assets (content-addressed)");
7437
9210
  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
9211
  const wsId = requireWorkspaceId(opts.workspaceId);
@@ -7592,7 +9365,7 @@ ${rows.length} asset(s) listed.
7592
9365
  });
7593
9366
  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
9367
  const wsId = requireWorkspaceId(opts.workspaceId);
7595
- if (!fs2.existsSync(filePath)) {
9368
+ if (!fs5.existsSync(filePath)) {
7596
9369
  process.stderr.write(`Error: file not found: ${filePath}
7597
9370
  `);
7598
9371
  process.exit(1);
@@ -7622,7 +9395,7 @@ ${rows.length} asset(s) listed.
7622
9395
  process.exit(1);
7623
9396
  }
7624
9397
  const a = res.data;
7625
- process.stdout.write(`Uploaded ${path2.basename(filePath)}
9398
+ process.stdout.write(`Uploaded ${path3.basename(filePath)}
7626
9399
  `);
7627
9400
  process.stdout.write(` ID: ${a.id}
7628
9401
  `);
@@ -7649,7 +9422,7 @@ ${rows.length} asset(s) listed.
7649
9422
  opts.length
7650
9423
  );
7651
9424
  if (opts.out) {
7652
- fs2.writeFileSync(opts.out, bytes);
9425
+ fs5.writeFileSync(opts.out, bytes);
7653
9426
  const range = describeRange(opts.offset, opts.length, bytes.byteLength);
7654
9427
  process.stderr.write(
7655
9428
  `Downloaded ${assetId} -> ${opts.out} (${bytes.byteLength} bytes${range ? `, ${range}` : ""}${truncated ? ", truncated" : ""}${totalSize != null ? `, total=${totalSize}` : ""})
@@ -7797,7 +9570,7 @@ function resolveWorkspaceId2(flag) {
7797
9570
  }
7798
9571
  return void 0;
7799
9572
  }
7800
- function register12(parent, getIMClient2, _getAPIClient) {
9573
+ function register16(parent, getIMClient2, _getAPIClient) {
7801
9574
  const approval = parent.command("approval").description("Submit and manage human approval requests");
7802
9575
  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
9576
  if (!opts.conversationId && !opts.taskId) {
@@ -7875,7 +9648,69 @@ function parseIntOpt2(value) {
7875
9648
  }
7876
9649
 
7877
9650
  // src/commands/agent.ts
7878
- function register13(parent, getIMClient2, _getAPIClient) {
9651
+ var import_node_crypto2 = require("crypto");
9652
+ var import_node_fs3 = require("fs");
9653
+ var import_node_os = require("os");
9654
+ var import_node_path3 = require("path");
9655
+ var import_node_child_process = require("child_process");
9656
+ function resolvePrismerRoot() {
9657
+ return process.env.PRISMER_HOME ? (0, import_node_path3.resolve)(process.env.PRISMER_HOME) : (0, import_node_path3.join)((0, import_node_os.homedir)(), ".prismer");
9658
+ }
9659
+ function readDaemonConfig() {
9660
+ const path6 = (0, import_node_path3.join)(resolvePrismerRoot(), "config.toml");
9661
+ if (!(0, import_node_fs3.existsSync)(path6)) {
9662
+ throw new Error(`${path6} not found. Run \`prismer setup\` first.`);
9663
+ }
9664
+ const raw = (0, import_node_fs3.readFileSync)(path6, "utf8");
9665
+ const idMatch = raw.match(/^daemon_id\s*=\s*"([^"]+)"/m);
9666
+ const keyMatch = raw.match(/^api_key\s*=\s*"([^"]+)"/m);
9667
+ if (!idMatch || !keyMatch) {
9668
+ throw new Error(`config.toml missing daemon_id or api_key (${path6})`);
9669
+ }
9670
+ return { daemonId: idMatch[1], apiKey: keyMatch[1] };
9671
+ }
9672
+ function resolveAgentDir(daemonId, agentId) {
9673
+ return (0, import_node_path3.join)(resolvePrismerRoot(), "devices", daemonId, "agents", agentId);
9674
+ }
9675
+ function collectFiles(root, excludeBasenames) {
9676
+ const out = [];
9677
+ function walk2(dir) {
9678
+ let entries;
9679
+ try {
9680
+ entries = (0, import_node_fs3.readdirSync)(dir);
9681
+ } catch {
9682
+ return;
9683
+ }
9684
+ for (const name of entries) {
9685
+ if (excludeBasenames.has(name)) continue;
9686
+ const full = (0, import_node_path3.join)(dir, name);
9687
+ let st;
9688
+ try {
9689
+ st = (0, import_node_fs3.statSync)(full);
9690
+ } catch {
9691
+ continue;
9692
+ }
9693
+ if (st.isDirectory()) walk2(full);
9694
+ else if (st.isFile()) {
9695
+ const buf = (0, import_node_fs3.readFileSync)(full);
9696
+ const sha = (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
9697
+ out.push({
9698
+ rel: (0, import_node_path3.relative)(root, full).split(import_node_path3.sep).join("/"),
9699
+ sha,
9700
+ size: buf.byteLength
9701
+ });
9702
+ }
9703
+ }
9704
+ }
9705
+ walk2(root);
9706
+ out.sort((a, b) => a.rel.localeCompare(b.rel));
9707
+ return out;
9708
+ }
9709
+ function computeMerkle(files) {
9710
+ const lines = files.map((f) => `${f.rel}:${f.sha}`).join("\n");
9711
+ return (0, import_node_crypto2.createHash)("sha256").update(lines).digest("hex");
9712
+ }
9713
+ function register17(parent, getIMClient2, _getAPIClient) {
7879
9714
  const agent = parent.command("agent").description("Manage agent specs, snapshots, publish, and fork");
7880
9715
  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
9716
  const res = await getIMClient2().im.agents.spec(agentId, opts.workspaceId);
@@ -7954,6 +9789,244 @@ function register13(parent, getIMClient2, _getAPIClient) {
7954
9789
  }
7955
9790
  });
7956
9791
  });
9792
+ 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) => {
9793
+ const { daemonId, apiKey } = readDaemonConfig();
9794
+ const agentDir = resolveAgentDir(daemonId, agentId);
9795
+ if (!(0, import_node_fs3.existsSync)(agentDir)) {
9796
+ process.stderr.write(`Error: agent dir not found at ${agentDir}
9797
+ `);
9798
+ process.exit(1);
9799
+ }
9800
+ if (!opts.force) {
9801
+ const res = await getIMClient2().im.agents.pause(agentId);
9802
+ if (!res.ok) {
9803
+ process.stderr.write(
9804
+ `Error: pause failed (${res.error?.message ?? "unknown"}); pass --force to export anyway
9805
+ `
9806
+ );
9807
+ process.exit(1);
9808
+ }
9809
+ }
9810
+ const excludeBasenames = /* @__PURE__ */ new Set(["transfer-manifest.json"]);
9811
+ const files = collectFiles(agentDir, excludeBasenames);
9812
+ const sha256 = computeMerkle(files);
9813
+ const signature = (0, import_node_crypto2.createHmac)("sha256", apiKey).update(sha256).digest("hex");
9814
+ const manifest = {
9815
+ version: 1,
9816
+ agentId,
9817
+ fromDaemonId: daemonId,
9818
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
9819
+ includes: ["profile.json", "skills/**", "memory/**", "outbox/*"],
9820
+ excludes: ["cache/ (daemon-level blob pool)", "local.db (daemon-level)"],
9821
+ sha256,
9822
+ signature
9823
+ };
9824
+ (0, import_node_fs3.writeFileSync)((0, import_node_path3.join)(agentDir, "transfer-manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
9825
+ const parentOfAgents = (0, import_node_path3.join)(resolvePrismerRoot(), "devices", daemonId);
9826
+ const outputAbs = (0, import_node_path3.resolve)(opts.output);
9827
+ try {
9828
+ (0, import_node_child_process.execFileSync)("tar", ["-czf", outputAbs, "-C", parentOfAgents, (0, import_node_path3.join)("agents", agentId)], { stdio: "inherit" });
9829
+ } catch (err) {
9830
+ process.stderr.write(`Error: tar failed: ${err.message}
9831
+ `);
9832
+ process.exit(1);
9833
+ }
9834
+ const result = {
9835
+ agentId,
9836
+ fromDaemonId: daemonId,
9837
+ output: outputAbs,
9838
+ manifestSha256: sha256,
9839
+ filesIncluded: files.length,
9840
+ totalBytes: files.reduce((s, f) => s + f.size, 0),
9841
+ paused: !opts.force
9842
+ };
9843
+ if (opts.json) {
9844
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
9845
+ } else {
9846
+ console.log(`Exported agent ${agentId}`);
9847
+ console.log(` output: ${result.output}`);
9848
+ console.log(` files: ${result.filesIncluded}`);
9849
+ console.log(` total bytes: ${result.totalBytes}`);
9850
+ console.log(` manifestSha: ${result.manifestSha256.slice(0, 16)}\u2026`);
9851
+ console.log(` cloud paused: ${result.paused ? "yes" : "no (--force)"}`);
9852
+ console.log("");
9853
+ console.log("Next: copy the tar.gz to the target device and run:");
9854
+ console.log(` prismer agent import ${result.output}`);
9855
+ }
9856
+ });
9857
+ 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) => {
9858
+ const tarAbs = (0, import_node_path3.resolve)(tarFile);
9859
+ if (!(0, import_node_fs3.existsSync)(tarAbs)) {
9860
+ process.stderr.write(`Error: tar file not found: ${tarAbs}
9861
+ `);
9862
+ process.exit(1);
9863
+ }
9864
+ const { daemonId: toDaemonId, apiKey } = readDaemonConfig();
9865
+ const tmpRoot = (0, import_node_path3.join)(resolvePrismerRoot(), ".transfer-staging", `${Date.now()}`);
9866
+ (0, import_node_fs3.mkdirSync)(tmpRoot, { recursive: true });
9867
+ try {
9868
+ (0, import_node_child_process.execFileSync)("tar", ["-xzf", tarAbs, "-C", tmpRoot], { stdio: "inherit" });
9869
+ } catch (err) {
9870
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9871
+ process.stderr.write(`Error: tar extract failed: ${err.message}
9872
+ `);
9873
+ process.exit(1);
9874
+ }
9875
+ const agentsRoot = (0, import_node_path3.join)(tmpRoot, "agents");
9876
+ if (!(0, import_node_fs3.existsSync)(agentsRoot)) {
9877
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9878
+ process.stderr.write("Error: tar layout invalid \u2014 no agents/ root inside archive\n");
9879
+ process.exit(1);
9880
+ }
9881
+ const entries = (0, import_node_fs3.readdirSync)(agentsRoot);
9882
+ if (entries.length !== 1) {
9883
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9884
+ process.stderr.write(`Error: expected exactly 1 agent in archive, got ${entries.length}
9885
+ `);
9886
+ process.exit(1);
9887
+ }
9888
+ const agentId = entries[0];
9889
+ const stagedAgentDir = (0, import_node_path3.join)(agentsRoot, agentId);
9890
+ const manifestPath = (0, import_node_path3.join)(stagedAgentDir, "transfer-manifest.json");
9891
+ if (!(0, import_node_fs3.existsSync)(manifestPath)) {
9892
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9893
+ process.stderr.write("Error: transfer-manifest.json missing from archive\n");
9894
+ process.exit(1);
9895
+ }
9896
+ let manifest;
9897
+ try {
9898
+ manifest = JSON.parse((0, import_node_fs3.readFileSync)(manifestPath, "utf8"));
9899
+ } catch (err) {
9900
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9901
+ process.stderr.write(`Error: manifest parse failed: ${err.message}
9902
+ `);
9903
+ process.exit(1);
9904
+ }
9905
+ const expectedSig = (0, import_node_crypto2.createHmac)("sha256", apiKey).update(manifest.sha256).digest("hex");
9906
+ if (expectedSig !== manifest.signature) {
9907
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9908
+ process.stderr.write(
9909
+ "Error: manifest signature verification failed \u2014 refusing to import (different workspace, or tampered archive)\n"
9910
+ );
9911
+ process.exit(1);
9912
+ }
9913
+ if (manifest.agentId !== agentId) {
9914
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9915
+ process.stderr.write(
9916
+ `Error: manifest agentId (${manifest.agentId}) != extracted dir name (${agentId})
9917
+ `
9918
+ );
9919
+ process.exit(1);
9920
+ }
9921
+ if (manifest.fromDaemonId === toDaemonId) {
9922
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9923
+ process.stderr.write(
9924
+ `Error: fromDaemonId == toDaemonId (${toDaemonId}); nothing to transfer.
9925
+ `
9926
+ );
9927
+ process.exit(1);
9928
+ }
9929
+ const targetAgentDir = resolveAgentDir(toDaemonId, agentId);
9930
+ if ((0, import_node_fs3.existsSync)(targetAgentDir)) {
9931
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9932
+ process.stderr.write(
9933
+ `Error: target dir already exists at ${targetAgentDir}. Run \`prismer agent remove ${agentId}\` (TODO) or move it aside first.
9934
+ `
9935
+ );
9936
+ process.exit(1);
9937
+ }
9938
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.join)(resolvePrismerRoot(), "devices", toDaemonId, "agents"), { recursive: true });
9939
+ try {
9940
+ (0, import_node_child_process.execFileSync)("mv", [stagedAgentDir, targetAgentDir]);
9941
+ } catch {
9942
+ try {
9943
+ (0, import_node_child_process.execFileSync)("cp", ["-R", stagedAgentDir, targetAgentDir]);
9944
+ (0, import_node_fs3.rmSync)(stagedAgentDir, { recursive: true, force: true });
9945
+ } catch (err) {
9946
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9947
+ process.stderr.write(`Error: failed to install agent dir: ${err.message}
9948
+ `);
9949
+ process.exit(1);
9950
+ }
9951
+ }
9952
+ (0, import_node_fs3.rmSync)(tmpRoot, { recursive: true, force: true });
9953
+ const rebind = await getIMClient2().im.agents.transfer({
9954
+ agentId,
9955
+ fromDaemonId: manifest.fromDaemonId,
9956
+ toDaemonId,
9957
+ manifestSha256: manifest.sha256
9958
+ });
9959
+ if (!rebind.ok || !rebind.data) {
9960
+ process.stderr.write(
9961
+ `Warning: cloud rebind failed (${rebind.error?.message ?? "unknown"}). Agent files are on disk at ${targetAgentDir} but cloud still routes to ${manifest.fromDaemonId}.
9962
+ Manual fix: POST /api/im/agent-bindings/transfer or rebind via the Devices UI.
9963
+ `
9964
+ );
9965
+ process.exit(2);
9966
+ }
9967
+ await getIMClient2().im.agents.resume(agentId);
9968
+ const result = {
9969
+ agentId,
9970
+ fromDaemonId: manifest.fromDaemonId,
9971
+ toDaemonId,
9972
+ targetDir: targetAgentDir,
9973
+ manifestSha256: manifest.sha256,
9974
+ boundDaemonId: rebind.data.boundDaemonId,
9975
+ boundBy: rebind.data.boundBy
9976
+ };
9977
+ if (opts.json) {
9978
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
9979
+ } else {
9980
+ console.log(`Imported agent ${agentId}`);
9981
+ console.log(` from device: ${result.fromDaemonId}`);
9982
+ console.log(` to device: ${result.toDaemonId}`);
9983
+ console.log(` target dir: ${result.targetDir}`);
9984
+ console.log(` cloud bound: ${result.boundDaemonId} (${result.boundBy})`);
9985
+ }
9986
+ });
9987
+ 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) => {
9988
+ const { daemonId } = readDaemonConfig();
9989
+ const devicesRoot = (0, import_node_path3.join)(resolvePrismerRoot(), "devices");
9990
+ if (!(0, import_node_fs3.existsSync)(devicesRoot)) {
9991
+ if (opts.json) process.stdout.write("[]\n");
9992
+ else console.log("No devices/ dir yet.");
9993
+ return;
9994
+ }
9995
+ const orphans = [];
9996
+ for (const did of (0, import_node_fs3.readdirSync)(devicesRoot)) {
9997
+ if (did === daemonId) continue;
9998
+ const agentsRoot = (0, import_node_path3.join)(devicesRoot, did, "agents");
9999
+ if (!(0, import_node_fs3.existsSync)(agentsRoot)) continue;
10000
+ for (const aid of (0, import_node_fs3.readdirSync)(agentsRoot)) {
10001
+ const dir = (0, import_node_path3.join)(agentsRoot, aid);
10002
+ try {
10003
+ const st = (0, import_node_fs3.statSync)(dir);
10004
+ if (!st.isDirectory()) continue;
10005
+ const files = collectFiles(dir, /* @__PURE__ */ new Set());
10006
+ const sizeBytes = files.reduce((s, f) => s + f.size, 0);
10007
+ orphans.push({ daemonId: did, agentId: aid, dir, sizeBytes });
10008
+ } catch {
10009
+ }
10010
+ }
10011
+ }
10012
+ if (opts.delete) {
10013
+ for (const o of orphans) {
10014
+ (0, import_node_fs3.rmSync)(o.dir, { recursive: true, force: true });
10015
+ }
10016
+ }
10017
+ if (opts.json) {
10018
+ process.stdout.write(JSON.stringify({ orphans, deleted: Boolean(opts.delete) }, null, 2) + "\n");
10019
+ } else {
10020
+ if (orphans.length === 0) {
10021
+ console.log("No orphan agent dirs.");
10022
+ } else {
10023
+ console.log(`Found ${orphans.length} orphan agent dir(s)${opts.delete ? " (DELETED)" : " (dry-run; pass --delete to remove)"}:`);
10024
+ for (const o of orphans) {
10025
+ console.log(` ${o.daemonId}/${o.agentId} ${o.sizeBytes} bytes ${o.dir}`);
10026
+ }
10027
+ }
10028
+ }
10029
+ });
7957
10030
  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
10031
  const res = await getIMClient2().im.agents.forkPack(packId, {
7959
10032
  targetWorkspaceId: opts.workspaceId,
@@ -7979,32 +10052,168 @@ function printOrExit(res, json, print) {
7979
10052
  print(res.data);
7980
10053
  }
7981
10054
 
10055
+ // src/commands/metric.ts
10056
+ var AGG_FUNCS = ["sum", "count", "avg", "min", "max", "p50", "p95", "p99"];
10057
+ var BUCKETS = ["5m", "1h", "1d"];
10058
+ function splitFqName(fqName) {
10059
+ const i = fqName.lastIndexOf(".");
10060
+ if (i <= 0 || i === fqName.length - 1) {
10061
+ throw new Error(`metric name must be in form "namespace.name" (got "${fqName}")`);
10062
+ }
10063
+ return { namespace: fqName.slice(0, i), name: fqName.slice(i + 1) };
10064
+ }
10065
+ function parseDimFlags(dimArr) {
10066
+ const dims = {};
10067
+ for (const raw of dimArr ?? []) {
10068
+ const i = raw.indexOf("=");
10069
+ if (i <= 0) throw new Error(`--dim "${raw}" must be in form key=value`);
10070
+ const key = raw.slice(0, i);
10071
+ const value = raw.slice(i + 1);
10072
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) dims[key] = Number(value);
10073
+ else if (value === "true" || value === "false") dims[key] = value === "true";
10074
+ else dims[key] = value;
10075
+ }
10076
+ return dims;
10077
+ }
10078
+ async function runEmit(fqName, opts, getIMClient2) {
10079
+ const client = getIMClient2();
10080
+ const { namespace, name } = splitFqName(fqName);
10081
+ const dims = parseDimFlags(opts.dim);
10082
+ if (!dims.workspaceId) {
10083
+ throw new Error("--dim workspaceId=<id> is required (server rejects emits without it)");
10084
+ }
10085
+ let value;
10086
+ if (opts.value !== void 0) {
10087
+ value = /^-?\d+(?:\.\d+)?$/.test(opts.value) ? Number(opts.value) : opts.value;
10088
+ }
10089
+ const input = {
10090
+ namespace,
10091
+ name,
10092
+ ts: opts.ts,
10093
+ value,
10094
+ dims
10095
+ };
10096
+ const res = await client.im.metrics.emit(input);
10097
+ if (opts.json) {
10098
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
10099
+ return;
10100
+ }
10101
+ if (!res.ok) {
10102
+ process.stderr.write(`Error: ${res.error?.message ?? "unknown error"}
10103
+ `);
10104
+ process.exit(1);
10105
+ }
10106
+ process.stdout.write(`emitted ${namespace}.${name}
10107
+ `);
10108
+ }
10109
+ async function runAgg(fqName, opts, getIMClient2) {
10110
+ const client = getIMClient2();
10111
+ const { namespace, name } = splitFqName(fqName);
10112
+ if (!AGG_FUNCS.includes(opts.agg)) {
10113
+ throw new Error(`--agg must be one of ${AGG_FUNCS.join("|")}`);
10114
+ }
10115
+ if (opts.bucket && !BUCKETS.includes(opts.bucket)) {
10116
+ throw new Error(`--bucket must be one of ${BUCKETS.join("|")}`);
10117
+ }
10118
+ const filter = {};
10119
+ for (const raw of (opts.filter ?? "").split(",").filter(Boolean)) {
10120
+ const i = raw.indexOf(":");
10121
+ if (i <= 0) throw new Error(`--filter "${raw}" must be in form key:value`);
10122
+ filter[raw.slice(0, i)] = raw.slice(i + 1);
10123
+ }
10124
+ if (!filter.workspaceId) {
10125
+ throw new Error("--filter must include workspaceId:<id> (cross-workspace queries are admin-only)");
10126
+ }
10127
+ const groupBy = opts.groupBy ? opts.groupBy.split(",").filter(Boolean) : void 0;
10128
+ const res = await client.im.metrics.aggregate({
10129
+ namespace,
10130
+ name,
10131
+ agg: opts.agg,
10132
+ range: opts.range,
10133
+ from: opts.from,
10134
+ to: opts.to,
10135
+ groupBy,
10136
+ filter,
10137
+ bucket: opts.bucket
10138
+ });
10139
+ if (opts.json) {
10140
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
10141
+ return;
10142
+ }
10143
+ if (!res.ok) {
10144
+ process.stderr.write(`Error: ${res.error?.message ?? "unknown error"}
10145
+ `);
10146
+ process.exit(1);
10147
+ }
10148
+ const data = res.data;
10149
+ if (!data) {
10150
+ process.stdout.write("(no data)\n");
10151
+ return;
10152
+ }
10153
+ process.stdout.write(
10154
+ `${data.namespace}.${data.name} ${data.agg} [${data.range.from} \u2192 ${data.range.to}]
10155
+ `
10156
+ );
10157
+ for (const bucket of data.buckets) {
10158
+ const tsLabel = bucket.ts ? `${bucket.ts}` : "(all)";
10159
+ for (const g of bucket.groups) {
10160
+ const keyLabel = Object.entries(g.groupKey).map(([k, v]) => `${k}=${v ?? "\u2205"}`).join(" ");
10161
+ process.stdout.write(` ${tsLabel} ${keyLabel || "(no group)"} \u2192 ${g.value ?? "\u2205"}
10162
+ `);
10163
+ }
10164
+ }
10165
+ }
10166
+ function register18(parent, getIMClient2, _getAPIClient) {
10167
+ const metric = parent.command("metric").description("Emit metric events and query aggregations (release201/11)");
10168
+ 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 = []) => {
10169
+ prev.push(val);
10170
+ return prev;
10171
+ }).option("--ts <iso>", "business timestamp in ISO 8601 (defaults to now)").option("--json", "output raw JSON response").action(async (fqName, opts) => {
10172
+ try {
10173
+ await runEmit(fqName, opts, getIMClient2);
10174
+ } catch (err) {
10175
+ process.stderr.write(`Error: ${err.message}
10176
+ `);
10177
+ process.exit(1);
10178
+ }
10179
+ });
10180
+ 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) => {
10181
+ try {
10182
+ await runAgg(fqName, opts, getIMClient2);
10183
+ } catch (err) {
10184
+ process.stderr.write(`Error: ${err.message}
10185
+ `);
10186
+ process.exit(1);
10187
+ }
10188
+ });
10189
+ }
10190
+
7982
10191
  // src/daemon.ts
7983
- var fs3 = __toESM(require("fs"));
7984
- var path3 = __toESM(require("path"));
10192
+ var fs6 = __toESM(require("fs"));
10193
+ var path4 = __toESM(require("path"));
7985
10194
  var import_path = require("path");
7986
- var os = __toESM(require("os"));
10195
+ var os2 = __toESM(require("os"));
7987
10196
  var import_os = require("os");
7988
10197
  var http = __toESM(require("http"));
7989
10198
  var import_http = require("http");
7990
10199
  var import_child_process = require("child_process");
7991
10200
  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");
10201
+ var CONFIG_DIR = path4.join(os2.homedir(), ".prismer");
10202
+ var CONFIG_PATH = path4.join(CONFIG_DIR, "config.toml");
10203
+ var PID_PATH = path4.join(CONFIG_DIR, "daemon.pid");
10204
+ var PORT_PATH = path4.join(CONFIG_DIR, "daemon.port");
10205
+ var CACHE_DIR = path4.join(CONFIG_DIR, "cache");
10206
+ var EVOLUTION_CACHE_PATH = path4.join(CACHE_DIR, "evolution.json");
10207
+ var OUTBOX_PATH = path4.join(CACHE_DIR, "outbox.json");
7999
10208
  var SYNC_INTERVAL_MS = 6e4;
8000
10209
  var FLUSH_INTERVAL_MS = 3e4;
8001
10210
  var API_TIMEOUT_MS = 1e4;
8002
10211
  var EVENTS_FILE = (0, import_path.join)(CACHE_DIR, "events.json");
8003
10212
  var MAX_EVENTS = 1e3;
8004
10213
  function loadConfig() {
8005
- if (!fs3.existsSync(CONFIG_PATH)) return null;
10214
+ if (!fs6.existsSync(CONFIG_PATH)) return null;
8006
10215
  try {
8007
- const raw = fs3.readFileSync(CONFIG_PATH, "utf-8");
10216
+ const raw = fs6.readFileSync(CONFIG_PATH, "utf-8");
8008
10217
  const parsed = TOML.parse(raw);
8009
10218
  const apiKey = parsed?.default?.api_key || "";
8010
10219
  const baseUrl = parsed?.default?.base_url || "https://prismer.cloud";
@@ -8015,13 +10224,13 @@ function loadConfig() {
8015
10224
  }
8016
10225
  }
8017
10226
  function ensureCacheDir() {
8018
- if (!fs3.existsSync(CACHE_DIR)) {
8019
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
10227
+ if (!fs6.existsSync(CACHE_DIR)) {
10228
+ fs6.mkdirSync(CACHE_DIR, { recursive: true });
8020
10229
  }
8021
10230
  }
8022
10231
  function loadEvents() {
8023
10232
  try {
8024
- return JSON.parse(fs3.readFileSync(EVENTS_FILE, "utf-8"));
10233
+ return JSON.parse(fs6.readFileSync(EVENTS_FILE, "utf-8"));
8025
10234
  } catch {
8026
10235
  return [];
8027
10236
  }
@@ -8030,7 +10239,7 @@ function appendEvent(event) {
8030
10239
  const events = loadEvents();
8031
10240
  events.push(event);
8032
10241
  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 });
10242
+ fs6.writeFileSync(EVENTS_FILE, JSON.stringify(events), { encoding: "utf-8", mode: 384 });
8034
10243
  }
8035
10244
  function emitSyncEvent(genesCount) {
8036
10245
  if (genesCount > 0) {
@@ -8045,9 +10254,9 @@ function emitSyncEvent(genesCount) {
8045
10254
  }
8046
10255
  }
8047
10256
  function readPid() {
8048
- if (!fs3.existsSync(PID_PATH)) return null;
10257
+ if (!fs6.existsSync(PID_PATH)) return null;
8049
10258
  try {
8050
- const raw = fs3.readFileSync(PID_PATH, "utf-8").trim();
10259
+ const raw = fs6.readFileSync(PID_PATH, "utf-8").trim();
8051
10260
  const pid = parseInt(raw, 10);
8052
10261
  return isNaN(pid) ? null : pid;
8053
10262
  } catch {
@@ -8055,9 +10264,9 @@ function readPid() {
8055
10264
  }
8056
10265
  }
8057
10266
  function readPort() {
8058
- if (!fs3.existsSync(PORT_PATH)) return null;
10267
+ if (!fs6.existsSync(PORT_PATH)) return null;
8059
10268
  try {
8060
- const raw = fs3.readFileSync(PORT_PATH, "utf-8").trim();
10269
+ const raw = fs6.readFileSync(PORT_PATH, "utf-8").trim();
8061
10270
  const port = parseInt(raw, 10);
8062
10271
  return isNaN(port) ? null : port;
8063
10272
  } catch {
@@ -8074,28 +10283,28 @@ function isProcessRunning(pid) {
8074
10283
  }
8075
10284
  function writePid(pid) {
8076
10285
  ensureCacheDir();
8077
- if (!fs3.existsSync(CONFIG_DIR)) {
8078
- fs3.mkdirSync(CONFIG_DIR, { recursive: true });
10286
+ if (!fs6.existsSync(CONFIG_DIR)) {
10287
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
8079
10288
  }
8080
- fs3.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
10289
+ fs6.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
8081
10290
  }
8082
10291
  function writePort(port) {
8083
- if (!fs3.existsSync(CONFIG_DIR)) {
8084
- fs3.mkdirSync(CONFIG_DIR, { recursive: true });
10292
+ if (!fs6.existsSync(CONFIG_DIR)) {
10293
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
8085
10294
  }
8086
- fs3.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
10295
+ fs6.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
8087
10296
  }
8088
10297
  function cleanupPidFiles() {
8089
10298
  try {
8090
- if (fs3.existsSync(PID_PATH)) fs3.unlinkSync(PID_PATH);
10299
+ if (fs6.existsSync(PID_PATH)) fs6.unlinkSync(PID_PATH);
8091
10300
  } catch {
8092
10301
  }
8093
10302
  try {
8094
- if (fs3.existsSync(PORT_PATH)) fs3.unlinkSync(PORT_PATH);
10303
+ if (fs6.existsSync(PORT_PATH)) fs6.unlinkSync(PORT_PATH);
8095
10304
  } catch {
8096
10305
  }
8097
10306
  }
8098
- async function fetchWithTimeout(url, options, timeoutMs = API_TIMEOUT_MS) {
10307
+ async function fetchWithTimeout2(url, options, timeoutMs = API_TIMEOUT_MS) {
8099
10308
  const controller = new AbortController();
8100
10309
  const timer = setTimeout(() => controller.abort(), timeoutMs);
8101
10310
  try {
@@ -8114,9 +10323,9 @@ async function runDaemonProcess() {
8114
10323
  let lastSync = 0;
8115
10324
  let syncCount = 0;
8116
10325
  let evolutionCursor = 0;
8117
- if (fs3.existsSync(EVOLUTION_CACHE_PATH)) {
10326
+ if (fs6.existsSync(EVOLUTION_CACHE_PATH)) {
8118
10327
  try {
8119
- const cached = JSON.parse(fs3.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
10328
+ const cached = JSON.parse(fs6.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
8120
10329
  if (typeof cached?.cursor === "number") evolutionCursor = cached.cursor;
8121
10330
  } catch {
8122
10331
  }
@@ -8124,9 +10333,9 @@ async function runDaemonProcess() {
8124
10333
  const server = (0, import_http.createServer)((req, res) => {
8125
10334
  if (req.method === "GET" && req.url === "/health") {
8126
10335
  let outboxSize = 0;
8127
- if (fs3.existsSync(OUTBOX_PATH)) {
10336
+ if (fs6.existsSync(OUTBOX_PATH)) {
8128
10337
  try {
8129
- const entries = JSON.parse(fs3.readFileSync(OUTBOX_PATH, "utf-8"));
10338
+ const entries = JSON.parse(fs6.readFileSync(OUTBOX_PATH, "utf-8"));
8130
10339
  if (Array.isArray(entries)) outboxSize = entries.length;
8131
10340
  } catch {
8132
10341
  }
@@ -8167,7 +10376,7 @@ async function runDaemonProcess() {
8167
10376
  process.on("SIGTERM", shutdown);
8168
10377
  const doEvolutionSync = async () => {
8169
10378
  try {
8170
- const res = await fetchWithTimeout(
10379
+ const res = await fetchWithTimeout2(
8171
10380
  `${cfg.baseUrl}/api/im/evolution/sync`,
8172
10381
  {
8173
10382
  method: "POST",
@@ -8189,7 +10398,7 @@ async function runDaemonProcess() {
8189
10398
  }
8190
10399
  ensureCacheDir();
8191
10400
  const pulled = data?.data || data;
8192
- fs3.writeFileSync(
10401
+ fs6.writeFileSync(
8193
10402
  EVOLUTION_CACHE_PATH,
8194
10403
  JSON.stringify({ cursor: evolutionCursor, lastSync, data: pulled }, null, 2),
8195
10404
  { encoding: "utf-8", mode: 384 }
@@ -8200,16 +10409,16 @@ async function runDaemonProcess() {
8200
10409
  }
8201
10410
  };
8202
10411
  const doOutboxFlush = async () => {
8203
- if (!fs3.existsSync(OUTBOX_PATH)) return;
10412
+ if (!fs6.existsSync(OUTBOX_PATH)) return;
8204
10413
  let entries = [];
8205
10414
  try {
8206
- entries = JSON.parse(fs3.readFileSync(OUTBOX_PATH, "utf-8"));
10415
+ entries = JSON.parse(fs6.readFileSync(OUTBOX_PATH, "utf-8"));
8207
10416
  if (!Array.isArray(entries) || entries.length === 0) return;
8208
10417
  } catch {
8209
10418
  return;
8210
10419
  }
8211
10420
  try {
8212
- const res = await fetchWithTimeout(
10421
+ const res = await fetchWithTimeout2(
8213
10422
  `${cfg.baseUrl}/api/im/evolution/sync`,
8214
10423
  {
8215
10424
  method: "POST",
@@ -8224,7 +10433,7 @@ async function runDaemonProcess() {
8224
10433
  }
8225
10434
  );
8226
10435
  if (res.ok) {
8227
- fs3.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
10436
+ fs6.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
8228
10437
  }
8229
10438
  } catch {
8230
10439
  }
@@ -8342,7 +10551,7 @@ function resolveNpxPath() {
8342
10551
  } catch {
8343
10552
  for (const p of ["/usr/local/bin/npx", "/opt/homebrew/bin/npx", `${(0, import_os.homedir)()}/.nvm/current/bin/npx`]) {
8344
10553
  try {
8345
- fs3.accessSync(p);
10554
+ fs6.accessSync(p);
8346
10555
  return p;
8347
10556
  } catch {
8348
10557
  }
@@ -8384,8 +10593,8 @@ function installLaunchd() {
8384
10593
  <string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stderr.log")}</string>
8385
10594
  </dict>
8386
10595
  </plist>`;
8387
- fs3.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
8388
- fs3.writeFileSync(plistPath, plist, { mode: 384 });
10596
+ fs6.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
10597
+ fs6.writeFileSync(plistPath, plist, { mode: 384 });
8389
10598
  try {
8390
10599
  (0, import_child_process.execSync)(`launchctl load ${plistPath}`, { stdio: "pipe" });
8391
10600
  console.log("[prismer] Daemon service installed and started (launchd)");
@@ -8401,7 +10610,7 @@ function uninstallLaunchd() {
8401
10610
  } catch {
8402
10611
  }
8403
10612
  try {
8404
- fs3.unlinkSync(plistPath);
10613
+ fs6.unlinkSync(plistPath);
8405
10614
  } catch {
8406
10615
  }
8407
10616
  console.log("[prismer] Daemon service uninstalled (launchd)");
@@ -8426,8 +10635,8 @@ RestartSec=10
8426
10635
  [Install]
8427
10636
  WantedBy=default.target
8428
10637
  `;
8429
- fs3.mkdirSync(serviceDir, { recursive: true });
8430
- fs3.writeFileSync(servicePath, unit, { mode: 420 });
10638
+ fs6.mkdirSync(serviceDir, { recursive: true });
10639
+ fs6.writeFileSync(servicePath, unit, { mode: 420 });
8431
10640
  try {
8432
10641
  (0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
8433
10642
  (0, import_child_process.execSync)("systemctl --user enable prismer-daemon", { stdio: "pipe" });
@@ -8450,7 +10659,7 @@ function uninstallSystemd() {
8450
10659
  }
8451
10660
  const servicePath = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user", "prismer-daemon.service");
8452
10661
  try {
8453
- fs3.unlinkSync(servicePath);
10662
+ fs6.unlinkSync(servicePath);
8454
10663
  } catch {
8455
10664
  }
8456
10665
  try {
@@ -8490,21 +10699,21 @@ if (process.env["PRISMER_DAEMON"] === "1") {
8490
10699
  // src/cli.ts
8491
10700
  var cliVersion = "1.7.2";
8492
10701
  try {
8493
- const pkgPath = path4.join(__dirname, "..", "package.json");
8494
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
10702
+ const pkgPath = path5.join(__dirname, "..", "package.json");
10703
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf8"));
8495
10704
  cliVersion = pkg.version || cliVersion;
8496
10705
  } catch {
8497
10706
  }
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");
10707
+ var CONFIG_DIR2 = process.env.PRISMER_HOME ? path5.resolve(process.env.PRISMER_HOME) : path5.join(os3.homedir(), ".prismer");
10708
+ var CONFIG_PATH2 = path5.join(CONFIG_DIR2, "config.toml");
8500
10709
  function ensureConfigDir() {
8501
- if (!fs4.existsSync(CONFIG_DIR2)) {
8502
- fs4.mkdirSync(CONFIG_DIR2, { recursive: true });
10710
+ if (!fs7.existsSync(CONFIG_DIR2)) {
10711
+ fs7.mkdirSync(CONFIG_DIR2, { recursive: true });
8503
10712
  }
8504
10713
  }
8505
10714
  function readConfig() {
8506
- if (!fs4.existsSync(CONFIG_PATH2)) return {};
8507
- const raw = fs4.readFileSync(CONFIG_PATH2, "utf-8");
10715
+ if (!fs7.existsSync(CONFIG_PATH2)) return {};
10716
+ const raw = fs7.readFileSync(CONFIG_PATH2, "utf-8");
8508
10717
  const parsed = TOML2.parse(raw);
8509
10718
  const flatApiKey = parsed.api_key;
8510
10719
  const flatBaseUrl = parsed.cloud_api_base ?? parsed.base_url;
@@ -8522,7 +10731,7 @@ function readConfig() {
8522
10731
  }
8523
10732
  function writeConfig(config) {
8524
10733
  ensureConfigDir();
8525
- fs4.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
10734
+ fs7.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
8526
10735
  }
8527
10736
  function setNestedValue(obj, dotPath, value) {
8528
10737
  const parts = dotPath.split(".");
@@ -8538,16 +10747,32 @@ function getIMClient() {
8538
10747
  const cfg = readConfig();
8539
10748
  const env = cfg?.default?.environment || "production";
8540
10749
  const baseUrl = cfg?.default?.base_url || "";
10750
+ const agentUsername = process.env.PRISMER_AGENT_USERNAME;
10751
+ const imAgentOpt = agentUsername ? { imAgent: agentUsername } : {};
10752
+ const workspaceId = process.env.PRISMER_WORKSPACE_ID;
10753
+ const imWorkspaceOpt = workspaceId ? { imWorkspace: workspaceId } : {};
8541
10754
  const imToken = cfg?.auth?.im_token;
8542
10755
  if (imToken) {
8543
- return new PrismerClient({ apiKey: imToken, environment: env, ...baseUrl ? { baseUrl } : {} });
10756
+ return new PrismerClient({
10757
+ apiKey: imToken,
10758
+ environment: env,
10759
+ ...baseUrl ? { baseUrl } : {},
10760
+ ...imAgentOpt,
10761
+ ...imWorkspaceOpt
10762
+ });
8544
10763
  }
8545
10764
  const apiKey = cfg?.default?.api_key;
8546
10765
  if (!apiKey) {
8547
10766
  errorLine('No credentials. Run "cloud setup" first (or "cloud setup --agent" / "cloud register <username>" for IM-JWT path).');
8548
10767
  process.exit(1);
8549
10768
  }
8550
- return new PrismerClient({ apiKey, environment: env, ...baseUrl ? { baseUrl } : {} });
10769
+ return new PrismerClient({
10770
+ apiKey,
10771
+ environment: env,
10772
+ ...baseUrl ? { baseUrl } : {},
10773
+ ...imAgentOpt,
10774
+ ...imWorkspaceOpt
10775
+ });
8551
10776
  }
8552
10777
  function getAPIClient() {
8553
10778
  const cfg = readConfig();
@@ -8558,7 +10783,11 @@ function getAPIClient() {
8558
10783
  }
8559
10784
  const env = cfg?.default?.environment || "production";
8560
10785
  const baseUrl = cfg?.default?.base_url || "";
8561
- return new PrismerClient({ apiKey, environment: env, ...baseUrl ? { baseUrl } : {} });
10786
+ const agentUsername = process.env.PRISMER_AGENT_USERNAME;
10787
+ const imAgentOpt = agentUsername ? { imAgent: agentUsername } : {};
10788
+ const workspaceId = process.env.PRISMER_WORKSPACE_ID;
10789
+ const imWorkspaceOpt = workspaceId ? { imWorkspace: workspaceId } : {};
10790
+ return new PrismerClient({ apiKey, environment: env, ...baseUrl ? { baseUrl } : {}, ...imAgentOpt, ...imWorkspaceOpt });
8562
10791
  }
8563
10792
  var program = new import_commander.Command();
8564
10793
  program.name("cloud").description("Prismer Cloud SDK CLI").version(cliVersion);
@@ -8685,8 +10914,8 @@ async function runSetup(opts, apiKey) {
8685
10914
  return;
8686
10915
  }
8687
10916
  const http2 = require("http");
8688
- const crypto2 = require("crypto");
8689
- const state = crypto2.randomBytes(16).toString("hex");
10917
+ const crypto3 = require("crypto");
10918
+ const state = crypto3.randomBytes(16).toString("hex");
8690
10919
  let resolved = false;
8691
10920
  const server = http2.createServer((req, res) => {
8692
10921
  const url = new URL(req.url, `http://localhost`);
@@ -8855,11 +11084,11 @@ program.command("status").description("Show current config and live info").actio
8855
11084
  });
8856
11085
  var configCmd = program.command("config").description("Manage config file");
8857
11086
  configCmd.command("show").description("Print config file").action(() => {
8858
- if (!fs4.existsSync(CONFIG_PATH2)) {
11087
+ if (!fs7.existsSync(CONFIG_PATH2)) {
8859
11088
  warn('No config file. Run "cloud setup" to create one.');
8860
11089
  return;
8861
11090
  }
8862
- console.log(fs4.readFileSync(CONFIG_PATH2, "utf-8"));
11091
+ console.log(fs7.readFileSync(CONFIG_PATH2, "utf-8"));
8863
11092
  });
8864
11093
  configCmd.command("set <key> <value>").description("Set a config value (e.g. default.base_url)").action((key, value) => {
8865
11094
  const config = readConfig();
@@ -8904,6 +11133,11 @@ register10(program, getIMClient, getAPIClient);
8904
11133
  register11(program, getIMClient, getAPIClient);
8905
11134
  register12(program, getIMClient, getAPIClient);
8906
11135
  register13(program, getIMClient, getAPIClient);
11136
+ register14(program, getIMClient, getAPIClient);
11137
+ register15(program, getIMClient, getAPIClient);
11138
+ register16(program, getIMClient, getAPIClient);
11139
+ register17(program, getIMClient, getAPIClient);
11140
+ register18(program, getIMClient, getAPIClient);
8907
11141
  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
11142
  const client = getIMClient();
8909
11143
  let userId = target;
@@ -8952,6 +11186,70 @@ program.command("send").description("Send a direct message (shortcut for: im sen
8952
11186
  }
8953
11187
  success(`Message sent (conversation: ${res.data?.conversationId})`);
8954
11188
  });
11189
+ program.command("deliver <path>").description("Attach a file you wrote to your current reply (in-container explicit delivery)").option("--run-id <id>", "dispatch run/task id (from <execution_context>; env fallback PRISMER_TASK_ID/RUN_ID)").option("--conversation-id <id>", "conversation id (from <execution_context>; env fallback PRISMER_CONVERSATION_ID)").option("--daemon-port <port>", "daemon local-server port (env fallback PRISMER_DAEMON_PORT, default 3210)").option("--json", "JSON output").action(async (filePath, opts) => {
11190
+ const proxy = detectDeliverProxy({
11191
+ runId: opts.runId,
11192
+ conversationId: opts.conversationId,
11193
+ daemonPort: opts.daemonPort
11194
+ });
11195
+ if (!proxy) {
11196
+ errorLine(
11197
+ "cloud deliver only works inside a daemon dispatch (no PRISMER_TASK_ID/PRISMER_RUN_ID, and no --run-id flag). On hermes, pass --run-id <id> (and --conversation-id <id>) copied from <execution_context>. Outside a dispatch, use `cloud file send <conversationId> <path>`."
11198
+ );
11199
+ process.exit(1);
11200
+ }
11201
+ const result = await proxyDeliver(proxy, filePath, "attach");
11202
+ if (!result.ok) {
11203
+ errorLine(`Delivery failed: ${result.error ?? `daemon returned ${result.status}`}`);
11204
+ process.exit(1);
11205
+ }
11206
+ if (opts.json) {
11207
+ console.log(JSON.stringify(result, null, 2));
11208
+ return;
11209
+ }
11210
+ success(`Attached to your reply (assetId: ${result.assetId ?? "-"})`);
11211
+ });
11212
+ program.command("attach <messageId> <path>").description("Attach a file to a message you ALREADY sent (by its messageId)").option("--run-id <id>", "dispatch run/task id (from <execution_context>; env fallback PRISMER_TASK_ID/RUN_ID)").option("--conversation-id <id>", "conversation id of the target message (from <execution_context>; env fallback PRISMER_CONVERSATION_ID)").option("--daemon-port <port>", "daemon local-server port (env fallback PRISMER_DAEMON_PORT, default 3210)").option("--json", "JSON output").action(async (messageId, filePath, opts) => {
11213
+ const proxy = detectDeliverProxy({
11214
+ runId: opts.runId,
11215
+ conversationId: opts.conversationId,
11216
+ daemonPort: opts.daemonPort
11217
+ });
11218
+ if (!proxy) {
11219
+ errorLine(
11220
+ "cloud attach only works inside a daemon dispatch (no PRISMER_TASK_ID/PRISMER_RUN_ID, and no --run-id flag). On hermes, pass --run-id <id> and --conversation-id <id> copied from <execution_context>."
11221
+ );
11222
+ process.exit(1);
11223
+ }
11224
+ const conversationId = (opts.conversationId || proxy.conversationId || "").trim();
11225
+ if (!conversationId) {
11226
+ errorLine(
11227
+ "cloud attach needs the conversation id of the target message. Set PRISMER_CONVERSATION_ID or pass --conversation-id <id> (copy it from <execution_context>)."
11228
+ );
11229
+ process.exit(1);
11230
+ }
11231
+ const result = await proxyDeliver(proxy, filePath, "message-attach", conversationId, messageId);
11232
+ if (!result.ok) {
11233
+ errorLine(`Attach failed: ${result.error ?? `daemon returned ${result.status}`}`);
11234
+ process.exit(1);
11235
+ }
11236
+ if (opts.json) {
11237
+ console.log(JSON.stringify(result, null, 2));
11238
+ return;
11239
+ }
11240
+ success(`Attached to message ${messageId} (assetId: ${result.assetId ?? "-"})`);
11241
+ });
11242
+ 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 = []) => {
11243
+ prev.push(val);
11244
+ return prev;
11245
+ }).option("--ts <iso>", "business timestamp in ISO 8601 (defaults to now)").option("--json", "output raw JSON response").action(async (fqName, opts) => {
11246
+ try {
11247
+ await runEmit(fqName, opts, getIMClient);
11248
+ } catch (err) {
11249
+ errorLine(`Error: ${err.message}`);
11250
+ process.exit(1);
11251
+ }
11252
+ });
8955
11253
  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
11254
  const client = getAPIClient();
8957
11255
  const input = urls.length === 1 ? urls[0] : urls;