@slock-ai/daemon 0.54.2 → 0.55.1

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/index.js CHANGED
@@ -8,7 +8,20 @@ var __export = (target, all) => {
8
8
  // src/index.ts
9
9
  import { Command } from "commander";
10
10
 
11
- // src/output.ts
11
+ // src/core/errors.ts
12
+ var CliError = class extends Error {
13
+ code;
14
+ exitCode;
15
+ suggestedNextAction;
16
+ constructor(options) {
17
+ super(options.message);
18
+ this.name = "CliError";
19
+ this.code = options.code;
20
+ this.exitCode = options.exitCode ?? 1;
21
+ this.cause = options.cause;
22
+ this.suggestedNextAction = options.suggestedNextAction;
23
+ }
24
+ };
12
25
  var CliExit = class extends Error {
13
26
  constructor(exitCode) {
14
27
  super(`CliExit(${exitCode})`);
@@ -16,16 +29,29 @@ var CliExit = class extends Error {
16
29
  this.name = "CliExit";
17
30
  }
18
31
  };
19
- function emit(payload) {
20
- process.stdout.write(JSON.stringify(payload) + "\n");
21
- }
22
- function fail(code, message, options) {
23
- const body = { ok: false, code, message };
24
- if (options?.suggestedNextAction) {
25
- body.suggested_next_action = options.suggestedNextAction;
32
+ var InternalBugError = class extends CliError {
33
+ constructor(cause) {
34
+ const message = cause instanceof Error ? cause.message : String(cause);
35
+ super({
36
+ code: "INTERNAL_BUG",
37
+ message: `Unexpected error: ${message}`,
38
+ cause
39
+ });
40
+ this.name = "InternalBugError";
26
41
  }
27
- process.stderr.write(JSON.stringify(body) + "\n");
28
- throw new CliExit(options?.exitCode ?? 1);
42
+ };
43
+ function cliError(code, message, options = {}) {
44
+ return new CliError({
45
+ code,
46
+ message,
47
+ exitCode: options.exitCode,
48
+ cause: options.cause,
49
+ suggestedNextAction: options.suggestedNextAction
50
+ });
51
+ }
52
+ function toCliError(err) {
53
+ if (err instanceof CliError) return err;
54
+ return new InternalBugError(err);
29
55
  }
30
56
 
31
57
  // src/version.ts
@@ -46,10 +72,430 @@ function readCliVersion(baseUrl = import.meta.url) {
46
72
  );
47
73
  }
48
74
 
75
+ // src/proxy.ts
76
+ import { ProxyAgent } from "undici";
77
+ var fetchDispatcherCache = /* @__PURE__ */ new Map();
78
+ function getDefaultPort(protocol) {
79
+ switch (protocol) {
80
+ case "https:":
81
+ return "443";
82
+ case "http:":
83
+ return "80";
84
+ default:
85
+ return "";
86
+ }
87
+ }
88
+ function hostMatchesNoProxyEntry(hostname3, ruleHost) {
89
+ if (!ruleHost) return false;
90
+ const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
91
+ const normalizedHost = hostname3.toLowerCase();
92
+ return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
93
+ }
94
+ function getProxyUrlForTarget(targetUrl, env) {
95
+ const protocol = new URL(targetUrl).protocol;
96
+ switch (protocol) {
97
+ case "https:":
98
+ return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
99
+ case "http:":
100
+ return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
101
+ default:
102
+ return env.ALL_PROXY || env.all_proxy;
103
+ }
104
+ }
105
+ function shouldBypassProxy(targetUrl, env) {
106
+ const rawNoProxy = env.NO_PROXY || env.no_proxy;
107
+ if (!rawNoProxy) return false;
108
+ const url2 = new URL(targetUrl);
109
+ const hostname3 = url2.hostname.toLowerCase();
110
+ const port = url2.port || getDefaultPort(url2.protocol);
111
+ return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
112
+ if (entry === "*") return true;
113
+ const [ruleHost, rulePort] = entry.split(":", 2);
114
+ if (rulePort && rulePort !== port) return false;
115
+ return hostMatchesNoProxyEntry(hostname3, ruleHost);
116
+ });
117
+ }
118
+ function buildFetchDispatcher(targetUrl, env = process.env) {
119
+ const proxyUrl = getProxyUrlForTarget(targetUrl, env);
120
+ if (!proxyUrl) return void 0;
121
+ if (shouldBypassProxy(targetUrl, env)) return void 0;
122
+ const cached2 = fetchDispatcherCache.get(proxyUrl);
123
+ if (cached2) return cached2;
124
+ const dispatcher = new ProxyAgent(proxyUrl);
125
+ fetchDispatcherCache.set(proxyUrl, dispatcher);
126
+ return dispatcher;
127
+ }
128
+
129
+ // src/transportTrace.ts
130
+ import { randomBytes } from "crypto";
131
+ import { appendFileSync, mkdirSync } from "fs";
132
+ import path from "path";
133
+ var CLI_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
134
+ var TRACE_ID_HEX_LENGTH = 32;
135
+ var SPAN_ID_HEX_LENGTH = 16;
136
+ var traceSinkForTest = null;
137
+ var traceFilePath = null;
138
+ function emitCliTransportNormalizedError(attrs, env = process.env) {
139
+ try {
140
+ traceSinkForTest?.("cli.transport.normalized_error", attrs);
141
+ const traceDir = env[CLI_TRACE_DIR_ENV];
142
+ if (!traceDir) return;
143
+ mkdirSync(traceDir, { recursive: true, mode: 448 });
144
+ if (!traceFilePath) {
145
+ traceFilePath = path.join(
146
+ traceDir,
147
+ `daemon-trace-cli-transport-${safeTimestamp(Date.now())}-${process.pid}-${randomHex(4)}.jsonl`
148
+ );
149
+ }
150
+ appendFileSync(traceFilePath, `${JSON.stringify(spanRecord("cli.transport.normalized_error", attrs))}
151
+ `, {
152
+ encoding: "utf8",
153
+ mode: 384
154
+ });
155
+ } catch {
156
+ }
157
+ }
158
+ function routeFamilyForPath(pathname) {
159
+ const normalized = pathname.split("?")[0] || "/";
160
+ if (normalized === "/internal/agent-api/send") return "agent-api/send";
161
+ if (normalized === "/internal/agent-api/events") return "agent-api/events";
162
+ if (normalized === "/internal/agent-api/receive-ack") return "agent-api/events";
163
+ if (normalized === "/internal/agent-api/tasks/claim") return "tasks/claim";
164
+ if (normalized === "/internal/agent-api/tasks/update-status") return "tasks/update";
165
+ if (normalized === "/internal/agent-api/tasks" || normalized.startsWith("/internal/agent-api/tasks/")) {
166
+ return "tasks";
167
+ }
168
+ if (normalized.startsWith("/internal/agent-api/attachments/")) return "agent-api/attachments";
169
+ if (normalized.startsWith("/api/attachments/")) return "attachments/download";
170
+ if (/^\/internal\/agent-api\/messages\/[^/]+\/reactions$/.test(normalized)) return "agent-api/messages/reactions";
171
+ if (normalized === "/internal/agent-api/server") return "server";
172
+ if (normalized.startsWith("/internal/agent-api/history")) return "agent-api/events";
173
+ if (normalized.startsWith("/internal/agent-api/search")) return "agent-api/events";
174
+ if (normalized.startsWith("/internal/agent-api/channel-members")) return "channel-members";
175
+ if (normalized.startsWith("/internal/agent-api/knowledge")) return "knowledge";
176
+ if (normalized === "/internal/agent-api/profile" || normalized.startsWith("/internal/agent-api/profile/")) return "profile";
177
+ if (normalized === "/internal/agent-api/integrations" || normalized.startsWith("/internal/agent-api/integrations/")) return "integrations";
178
+ if (normalized === "/internal/agent-api/upload") return "attachments/upload";
179
+ if (normalized === "/internal/agent-api/resolve-channel") return "resolve-channel";
180
+ if (normalized === "/internal/agent-api/threads/unfollow") return "threads/unfollow";
181
+ if (normalized === "/internal/agent-api/prepare-action") return "action/prepare";
182
+ if (normalized === "/internal/agent-api/reminders" || normalized.startsWith("/internal/agent-api/reminders/")) return "reminders";
183
+ if (/^\/internal\/agent-api\/channels\/[^/]+\/join$/.test(normalized)) return "channels/join";
184
+ if (/^\/internal\/agent-api\/channels\/[^/]+\/leave$/.test(normalized)) return "channels/leave";
185
+ if (normalized.startsWith("/internal/agent/")) {
186
+ const parts = normalized.split("/").filter(Boolean);
187
+ const firstAfterAgentId = parts[3] ?? "unknown";
188
+ if (firstAfterAgentId === "server") return "server";
189
+ if (firstAfterAgentId === "send") return "agent-api/send";
190
+ if (firstAfterAgentId === "history" || firstAfterAgentId === "search" || firstAfterAgentId === "receive" || firstAfterAgentId === "receive-ack") {
191
+ return "agent-api/events";
192
+ }
193
+ if (firstAfterAgentId === "channel-members") return "channel-members";
194
+ if (firstAfterAgentId === "knowledge") return "knowledge";
195
+ if (firstAfterAgentId === "profile") return "profile";
196
+ if (firstAfterAgentId === "integrations") return "integrations";
197
+ if (firstAfterAgentId === "upload") return "attachments/upload";
198
+ if (firstAfterAgentId === "resolve-channel") return "resolve-channel";
199
+ if (firstAfterAgentId === "threads") return "threads/unfollow";
200
+ if (firstAfterAgentId === "prepare-action") return "action/prepare";
201
+ if (firstAfterAgentId === "tasks") {
202
+ if (normalized.endsWith("/tasks/claim")) return "tasks/claim";
203
+ if (normalized.endsWith("/tasks/update-status")) return "tasks/update";
204
+ return "tasks";
205
+ }
206
+ if (firstAfterAgentId === "reminders") return "reminders";
207
+ if (firstAfterAgentId === "messages" && normalized.endsWith("/reactions")) return "agent-api/messages/reactions";
208
+ if (firstAfterAgentId === "channels" && normalized.endsWith("/join")) return "channels/join";
209
+ if (firstAfterAgentId === "channels" && normalized.endsWith("/leave")) return "channels/leave";
210
+ }
211
+ return "unknown";
212
+ }
213
+ function targetHostClassForUrl(url2) {
214
+ const hostname3 = url2.hostname.toLowerCase();
215
+ if (hostname3 === "127.0.0.1" || hostname3 === "localhost" || hostname3 === "::1") return "local_daemon";
216
+ if (hostname3 === "api.slock.ai") return "api.slock.ai";
217
+ return "custom_server";
218
+ }
219
+ function upstreamLayerForFetchError(url2, err) {
220
+ if (targetHostClassForUrl(url2) === "local_daemon") return "local_daemon_loopback";
221
+ const code = errorCode(err).toUpperCase();
222
+ const message = errorMessage(err).toLowerCase();
223
+ if (code === "ENOTFOUND" || code === "EAI_AGAIN" || message.includes("dns")) return "dns";
224
+ if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || message.includes("socket")) return "tcp";
225
+ if (code.includes("TLS") || message.includes("certificate") || message.includes("tls")) return "tls";
226
+ if (code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT" || message.includes("timeout")) return "read_timeout";
227
+ if (message.includes("proxy")) return "proxy_connect";
228
+ if (message.includes("fly")) return "fly_edge";
229
+ return "unknown";
230
+ }
231
+ function boundedOriginalMessage(err) {
232
+ const message = sanitizeOriginalMessage(errorMessage(err));
233
+ return message || void 0;
234
+ }
235
+ function spanRecord(name, attrs) {
236
+ const now = (/* @__PURE__ */ new Date()).toISOString();
237
+ return {
238
+ type: "span",
239
+ schema_version: 1,
240
+ trace_id: randomHex(TRACE_ID_HEX_LENGTH / 2),
241
+ span_id: randomHex(SPAN_ID_HEX_LENGTH / 2),
242
+ parent_span_id: null,
243
+ name,
244
+ surface: "cli",
245
+ kind: "client",
246
+ status: "error",
247
+ start_time: now,
248
+ end_time: now,
249
+ duration_ms: 0,
250
+ attrs: Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0 && value !== null && value !== "")),
251
+ events: []
252
+ };
253
+ }
254
+ function randomHex(bytes) {
255
+ return randomBytes(bytes).toString("hex");
256
+ }
257
+ function safeTimestamp(timeMs) {
258
+ return new Date(timeMs).toISOString().replace(/[:.]/g, "-");
259
+ }
260
+ function errorCode(err) {
261
+ if (typeof err === "object" && err && "code" in err && typeof err.code === "string") {
262
+ return err.code;
263
+ }
264
+ if (typeof err === "object" && err && "cause" in err) return errorCode(err.cause);
265
+ return "";
266
+ }
267
+ function errorMessage(err) {
268
+ if (err instanceof Error) return err.message;
269
+ return String(err ?? "");
270
+ }
271
+ function sanitizeOriginalMessage(message) {
272
+ const normalized = message.replace(/sk_(?:agent|machine|computer)_[A-Za-z0-9_-]+/g, "sk_[redacted]").replace(/sap_[A-Za-z0-9_-]+/g, "sap_[redacted]").replace(/https?:\/\/\S+/g, "[url]").replace(/\s+/g, " ").trim();
273
+ return normalized.length > 240 ? `${normalized.slice(0, 237)}...` : normalized;
274
+ }
275
+
276
+ // src/client.ts
277
+ var ApiClient = class {
278
+ constructor(ctx) {
279
+ this.ctx = ctx;
280
+ }
281
+ usesAgentApiSurface() {
282
+ return this.ctx.clientMode === "managed-runner" || this.ctx.clientMode === "self-hosted-runner";
283
+ }
284
+ rewriteAgentCredentialPath(pathname) {
285
+ if (!this.usesAgentApiSurface()) return pathname;
286
+ const attachmentDownload = /^\/api\/attachments\/([^/?]+)(.*)$/.exec(pathname);
287
+ if (attachmentDownload) {
288
+ return `/internal/agent-api/attachments/${attachmentDownload[1]}${attachmentDownload[2] ?? ""}`;
289
+ }
290
+ const agentPrefix = `/internal/agent/${encodeURIComponent(this.ctx.agentId)}`;
291
+ if (!pathname.startsWith(agentPrefix)) return pathname;
292
+ const suffix = pathname.slice(agentPrefix.length);
293
+ if (suffix === "/server") return "/internal/agent-api/server";
294
+ if (suffix === "/send") return "/internal/agent-api/send";
295
+ if (suffix.startsWith("/history")) return `/internal/agent-api/history${suffix.slice("/history".length)}`;
296
+ if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
297
+ if (suffix.startsWith("/channel-members")) return `/internal/agent-api/channel-members${suffix.slice("/channel-members".length)}`;
298
+ if (suffix === "/knowledge" || suffix.startsWith("/knowledge?")) {
299
+ return `/internal/agent-api/knowledge${suffix.slice("/knowledge".length)}`;
300
+ }
301
+ if (suffix === "/profile" || suffix.startsWith("/profile/")) return `/internal/agent-api${suffix}`;
302
+ if (suffix === "/integrations" || suffix.startsWith("/integrations/")) return `/internal/agent-api${suffix}`;
303
+ if (suffix === "/upload") return "/internal/agent-api/upload";
304
+ if (suffix === "/resolve-channel") return "/internal/agent-api/resolve-channel";
305
+ if (suffix === "/threads/unfollow") return "/internal/agent-api/threads/unfollow";
306
+ if (suffix === "/prepare-action") return "/internal/agent-api/prepare-action";
307
+ if (suffix === "/tasks" || suffix.startsWith("/tasks?") || suffix.startsWith("/tasks/")) {
308
+ return `/internal/agent-api${suffix}`;
309
+ }
310
+ if (suffix === "/reminders" || suffix.startsWith("/reminders?") || suffix.startsWith("/reminders/")) {
311
+ return `/internal/agent-api${suffix}`;
312
+ }
313
+ if (suffix === "/receive" || suffix.startsWith("/receive?")) {
314
+ return "/internal/agent-api/events?since=latest";
315
+ }
316
+ const reaction = /^\/messages\/([^/]+)\/reactions$/.exec(suffix);
317
+ if (reaction) {
318
+ return `/internal/agent-api/messages/${reaction[1]}/reactions`;
319
+ }
320
+ const channelMembership = /^\/channels\/([^/]+)\/(join|leave)$/.exec(suffix);
321
+ if (channelMembership) {
322
+ return `/internal/agent-api/channels/${channelMembership[1]}/${channelMembership[2]}`;
323
+ }
324
+ return pathname;
325
+ }
326
+ normalizeAgentCredentialResponse(pathname, data) {
327
+ if (!this.usesAgentApiSurface()) return data;
328
+ if (!pathname.includes("/internal/agent-api/events")) return data;
329
+ const value = data;
330
+ if (!Array.isArray(value.events)) return data;
331
+ return { ...data, messages: value.events };
332
+ }
333
+ buildAuthHeaders() {
334
+ const headers = {
335
+ "Authorization": `Bearer ${this.ctx.token}`,
336
+ "X-Agent-Id": this.ctx.agentId,
337
+ "X-Slock-Client": "cli"
338
+ };
339
+ if (this.ctx.serverId) headers["X-Server-Id"] = this.ctx.serverId;
340
+ return headers;
341
+ }
342
+ async parseJsonResponse(res) {
343
+ let data = null;
344
+ let error48 = null;
345
+ let errorCode2 = null;
346
+ const contentType = res.headers.get("content-type") ?? "";
347
+ if (contentType.includes("application/json")) {
348
+ let parseFailed = false;
349
+ const parsed = await res.json().catch(() => {
350
+ parseFailed = true;
351
+ return null;
352
+ });
353
+ if (parseFailed) {
354
+ return {
355
+ ok: false,
356
+ status: res.status,
357
+ data: null,
358
+ error: `Invalid JSON response from server/proxy (HTTP ${res.status})`,
359
+ errorCode: "INVALID_JSON_RESPONSE"
360
+ };
361
+ }
362
+ if (res.ok) {
363
+ data = parsed;
364
+ } else {
365
+ const body = parsed;
366
+ if (res.status === 403 && body?.requiredScope) {
367
+ errorCode2 = "SCOPE_DENIED";
368
+ error48 = `Permission denied. The human has revoked the \`${body.requiredScope}\` capability for this agent under the agent profile's Permissions tab, so this command can't run. If you need it, ask the human to re-enable the corresponding toggle.`;
369
+ } else {
370
+ error48 = body?.error ?? `HTTP ${res.status}`;
371
+ errorCode2 = body?.errorCode ?? body?.code ?? null;
372
+ }
373
+ const suggestedNextAction = body?.suggestedNextAction ?? body?.suggested_next_action;
374
+ return { ok: res.ok, status: res.status, data, error: error48, errorCode: errorCode2, suggestedNextAction };
375
+ }
376
+ } else if (!res.ok) {
377
+ error48 = `HTTP ${res.status}`;
378
+ }
379
+ return { ok: res.ok, status: res.status, data, error: error48, errorCode: errorCode2 };
380
+ }
381
+ async fetchWithTransportTrace(url2, pathname, init) {
382
+ try {
383
+ const res = await fetch(url2, init);
384
+ if (res.status >= 500) {
385
+ this.emitTransportNormalizedError({
386
+ url: url2,
387
+ pathname,
388
+ normalizedCode: "server_5xx",
389
+ responseStarted: true,
390
+ upstreamLayer: "http_status",
391
+ upstreamStatus: res.status
392
+ });
393
+ }
394
+ return res;
395
+ } catch (err) {
396
+ this.emitTransportNormalizedError({
397
+ url: url2,
398
+ pathname,
399
+ normalizedCode: "transport_failure",
400
+ responseStarted: false,
401
+ upstreamLayer: upstreamLayerForFetchError(url2, err),
402
+ originalMessage: boundedOriginalMessage(err)
403
+ });
404
+ throw err;
405
+ }
406
+ }
407
+ emitTransportNormalizedError(input) {
408
+ emitCliTransportNormalizedError({
409
+ producer: "cli",
410
+ normalized_code: input.normalizedCode,
411
+ route_family: routeFamilyForPath(input.pathname),
412
+ response_started: input.responseStarted,
413
+ upstream_layer: input.upstreamLayer,
414
+ ...input.upstreamStatus === void 0 || input.upstreamStatus === null ? {} : { upstream_status: input.upstreamStatus },
415
+ ...input.originalMessage ? { original_message: input.originalMessage } : {},
416
+ ...this.ctx.serverId ? { serverId: this.ctx.serverId } : {},
417
+ agentId: this.ctx.agentId,
418
+ target_host_class: targetHostClassForUrl(input.url)
419
+ });
420
+ }
421
+ async request(method, pathname, body) {
422
+ pathname = this.rewriteAgentCredentialPath(pathname);
423
+ const url2 = new URL(pathname, this.ctx.serverUrl);
424
+ const headers = this.buildAuthHeaders();
425
+ headers["Content-Type"] = "application/json";
426
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
427
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
428
+ }
429
+ const dispatcher = buildFetchDispatcher(url2.toString());
430
+ const init = {
431
+ method,
432
+ headers,
433
+ body: body === void 0 ? void 0 : JSON.stringify(body)
434
+ };
435
+ if (dispatcher) init.dispatcher = dispatcher;
436
+ const res = await this.fetchWithTransportTrace(url2, pathname, init);
437
+ const parsed = await this.parseJsonResponse(res);
438
+ if (parsed.ok && parsed.data !== null) {
439
+ parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
440
+ }
441
+ return parsed;
442
+ }
443
+ // Multipart upload. Caller builds the FormData (file part + any text fields).
444
+ // Content-Type intentionally omitted so fetch sets the correct multipart
445
+ // boundary itself.
446
+ async requestMultipart(method, pathname, form) {
447
+ pathname = this.rewriteAgentCredentialPath(pathname);
448
+ const url2 = new URL(pathname, this.ctx.serverUrl);
449
+ const dispatcher = buildFetchDispatcher(url2.toString());
450
+ const headers = this.buildAuthHeaders();
451
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
452
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
453
+ }
454
+ const init = {
455
+ method,
456
+ headers,
457
+ body: form
458
+ };
459
+ if (dispatcher) init.dispatcher = dispatcher;
460
+ const res = await this.fetchWithTransportTrace(url2, pathname, init);
461
+ return this.parseJsonResponse(res);
462
+ }
463
+ // Returns the raw Response so the caller can stream / save the body.
464
+ // For non-JSON downloads (binary attachments). Caller is responsible for
465
+ // consuming the body. On non-2xx, attempts to surface a JSON error.
466
+ async requestRaw(method, pathname) {
467
+ pathname = this.rewriteAgentCredentialPath(pathname);
468
+ const url2 = new URL(pathname, this.ctx.serverUrl);
469
+ const dispatcher = buildFetchDispatcher(url2.toString());
470
+ const headers = this.buildAuthHeaders();
471
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
472
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
473
+ }
474
+ const init = {
475
+ method,
476
+ headers,
477
+ redirect: "follow"
478
+ };
479
+ if (dispatcher) init.dispatcher = dispatcher;
480
+ const res = await this.fetchWithTransportTrace(url2, pathname, init);
481
+ let error48 = null;
482
+ if (!res.ok) {
483
+ const contentType = res.headers.get("content-type") ?? "";
484
+ if (contentType.includes("application/json")) {
485
+ const parsed = await res.json().catch(() => null);
486
+ error48 = parsed?.error ?? `HTTP ${res.status}`;
487
+ } else {
488
+ error48 = `HTTP ${res.status}`;
489
+ }
490
+ }
491
+ return { ok: res.ok, status: res.status, response: res, error: error48 };
492
+ }
493
+ };
494
+
49
495
  // src/auth/env.ts
50
496
  import fs from "fs";
51
497
  import os from "os";
52
- import path from "path";
498
+ import path2 from "path";
53
499
  var RAW_AGENT_ENV_KEYS = [
54
500
  "SLOCK_AGENT_ID",
55
501
  "SLOCK_SERVER_URL",
@@ -72,13 +518,13 @@ function resolveProfileDir(slug, env = process.env) {
72
518
  return env.SLOCK_PROFILE_DIR;
73
519
  }
74
520
  if (env.SLOCK_HOME) {
75
- return path.join(env.SLOCK_HOME, "profiles", slug);
521
+ return path2.join(env.SLOCK_HOME, "profiles", slug);
76
522
  }
77
523
  const home = env.HOME ?? os.homedir();
78
- return path.join(home, ".slock", "profiles", slug);
524
+ return path2.join(home, ".slock", "profiles", slug);
79
525
  }
80
526
  function resolveProfileCredentialPath(slug, env) {
81
- return path.join(resolveProfileDir(slug, env), "credential.json");
527
+ return path2.join(resolveProfileDir(slug, env), "credential.json");
82
528
  }
83
529
  function readProfileCredential(slug, env) {
84
530
  const filePath = resolveProfileCredentialPath(slug, env);
@@ -216,29 +662,137 @@ function loadAgentContext(env = process.env) {
216
662
  );
217
663
  }
218
664
 
219
- // src/commands/auth/whoami.ts
220
- function registerWhoamiCommand(parent) {
221
- parent.command("whoami").description("Print the agent context resolved from env (token value redacted)").action(() => {
222
- let ctx;
665
+ // src/core/io.ts
666
+ function defaultCliIo() {
667
+ return {
668
+ stdin: process.stdin,
669
+ stdout: process.stdout,
670
+ stderr: process.stderr
671
+ };
672
+ }
673
+
674
+ // src/core/context.ts
675
+ function bootstrapSuggestedNextAction(code) {
676
+ switch (code) {
677
+ case "MISSING_AGENT_ID":
678
+ case "MISSING_SERVER_URL":
679
+ case "MISSING_TOKEN":
680
+ return "Use a Slock profile with `slock --profile <slug> ...`; create one with `slock agent login --server <server-url> --agent <agent-id> --profile-slug <slug>`.";
681
+ case "PROFILE_FILE_UNREADABLE":
682
+ case "PROFILE_FILE_INVALID":
683
+ return "Check the selected Slock profile, or recreate it with `slock agent login --server <server-url> --agent <agent-id> --profile-slug <slug>`.";
684
+ case "TOKEN_FILE_UNREADABLE":
685
+ case "TOKEN_FILE_EMPTY":
686
+ return "Check the daemon-injected token file, or restart the Slock daemon so it can inject a fresh credential.";
687
+ case "MISSING_AGENT_PROXY_URL":
688
+ case "MISSING_AGENT_PROXY_TOKEN":
689
+ case "MULTIPLE_AGENT_PROXY_TOKENS":
690
+ return "Restart the Slock daemon so it can inject a complete local proxy environment, or remove the partial proxy env vars before retrying.";
691
+ default:
692
+ return void 0;
693
+ }
694
+ }
695
+ function createCommandContext(options = {}) {
696
+ const io = options.io ?? defaultCliIo();
697
+ const env = options.env ?? process.env;
698
+ const loadContext = options.loadAgentContext ?? loadAgentContext;
699
+ const createApiClient = options.createApiClient ?? ((agentContext) => new ApiClient(agentContext));
700
+ return {
701
+ io,
702
+ env,
703
+ loadAgentContext() {
704
+ try {
705
+ return loadContext(env);
706
+ } catch (err) {
707
+ if (err instanceof AgentBootstrapError) {
708
+ throw new CliError({
709
+ code: err.code,
710
+ message: err.message,
711
+ cause: err,
712
+ suggestedNextAction: bootstrapSuggestedNextAction(err.code)
713
+ });
714
+ }
715
+ throw err;
716
+ }
717
+ },
718
+ createApiClient
719
+ };
720
+ }
721
+
722
+ // src/core/renderer.ts
723
+ function renderError(io, err) {
724
+ io.stderr.write(`Error: ${err.message}
725
+ `);
726
+ io.stderr.write(`Code: ${err.code}
727
+ `);
728
+ if (err.suggestedNextAction) {
729
+ io.stderr.write(`Next action: ${err.suggestedNextAction}
730
+ `);
731
+ }
732
+ }
733
+ function writeText(io, text) {
734
+ io.stdout.write(text);
735
+ }
736
+ function writeJson(io, payload) {
737
+ io.stdout.write(`${JSON.stringify(payload)}
738
+ `);
739
+ }
740
+
741
+ // src/core/command.ts
742
+ function defineCommand(spec, handler) {
743
+ return { spec, handler };
744
+ }
745
+ function registerCliCommand(parent, command, runtimeOptions = {}) {
746
+ const child = parent.command(command.spec.name).description(command.spec.description);
747
+ for (const arg of command.spec.arguments ?? []) {
748
+ child.argument(arg);
749
+ }
750
+ for (const option of command.spec.options ?? []) {
751
+ if (option.parse) {
752
+ child.option(option.flags, option.description, option.parse);
753
+ } else {
754
+ child.option(option.flags, option.description);
755
+ }
756
+ }
757
+ if (command.spec.helpAfter) {
758
+ child.addHelpText("after", command.spec.helpAfter);
759
+ }
760
+ child.action(async (...args) => {
761
+ const ctx = createCommandContext(runtimeOptions);
223
762
  try {
224
- ctx = loadAgentContext();
763
+ await command.handler(ctx, ...args);
225
764
  } catch (err) {
226
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
227
- throw err;
765
+ const cliError2 = toCliError(err);
766
+ renderError(ctx.io, cliError2);
767
+ throw new CliExit(cliError2.exitCode);
228
768
  }
229
- emit({
769
+ });
770
+ }
771
+
772
+ // src/commands/auth/whoami.ts
773
+ var whoamiCommand = defineCommand(
774
+ {
775
+ name: "whoami",
776
+ description: "Print the agent context resolved from env (token value redacted)"
777
+ },
778
+ (ctx) => {
779
+ const agentContext = ctx.loadAgentContext();
780
+ writeJson(ctx.io, {
230
781
  ok: true,
231
782
  data: {
232
- agentId: ctx.agentId,
233
- serverUrl: ctx.serverUrl,
234
- serverId: ctx.serverId,
235
- clientMode: ctx.clientMode,
236
- secretSource: ctx.secretSource,
237
- ...ctx.profileSlug ? { profileSlug: ctx.profileSlug } : {},
238
- ...ctx.profileCredentialPath ? { profileCredentialPath: ctx.profileCredentialPath } : {}
783
+ agentId: agentContext.agentId,
784
+ serverUrl: agentContext.serverUrl,
785
+ serverId: agentContext.serverId,
786
+ clientMode: agentContext.clientMode,
787
+ secretSource: agentContext.secretSource,
788
+ ...agentContext.profileSlug ? { profileSlug: agentContext.profileSlug } : {},
789
+ ...agentContext.profileCredentialPath ? { profileCredentialPath: agentContext.profileCredentialPath } : {}
239
790
  }
240
791
  });
241
- });
792
+ }
793
+ );
794
+ function registerWhoamiCommand(parent, runtimeOptions) {
795
+ registerCliCommand(parent, whoamiCommand, runtimeOptions);
242
796
  }
243
797
 
244
798
  // src/commands/agent/list.ts
@@ -355,17 +909,26 @@ function describeListResult(reason, serverUrl) {
355
909
  return "You have `manageAgents` on at least one server, but no agents exist on those servers yet. Ask the user to create an agent first (via web UI), then rerun `slock agent list`.";
356
910
  }
357
911
  }
358
- function registerAgentListCommand(parent) {
359
- parent.command("list").description(
360
- "List Slock agents the user can mint credentials for (after a device-code login)."
361
- ).requiredOption("--server <url>", "Slock server base URL, e.g. https://slock.example.com").option("--client-name <label>", "Human-readable label shown on the web approval page").action(async (options) => {
912
+ var agentListCommand = defineCommand(
913
+ {
914
+ name: "list",
915
+ description: "List Slock agents the user can mint credentials for (after a device-code login).",
916
+ options: [
917
+ { flags: "--server <url>", description: "Slock server base URL, e.g. https://slock.example.com" },
918
+ { flags: "--client-name <label>", description: "Human-readable label shown on the web approval page" }
919
+ ]
920
+ },
921
+ async (ctx, options) => {
922
+ if (!options.server?.trim()) {
923
+ throw cliError("INVALID_ARG", "--server is required");
924
+ }
362
925
  let userSession;
363
926
  try {
364
927
  userSession = await runDeviceCodeLogin({
365
928
  serverUrl: options.server,
366
929
  ...options.clientName ? { clientName: options.clientName } : {},
367
930
  onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
368
- process.stderr.write(
931
+ ctx.io.stderr.write(
369
932
  `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
370
933
  `
371
934
  );
@@ -373,7 +936,7 @@ function registerAgentListCommand(parent) {
373
936
  });
374
937
  } catch (err) {
375
938
  if (err instanceof DeviceCodeLoginError) {
376
- fail(err.code, err.message);
939
+ throw cliError(err.code, err.message, { cause: err });
377
940
  }
378
941
  throw err;
379
942
  }
@@ -393,7 +956,7 @@ function registerAgentListCommand(parent) {
393
956
  body = await res.json();
394
957
  } catch {
395
958
  }
396
- fail(
959
+ throw cliError(
397
960
  body?.code ?? `list_failed_${res.status}`,
398
961
  body?.error ?? `Failed to list manageable agents (status ${res.status}).`
399
962
  );
@@ -402,7 +965,7 @@ function registerAgentListCommand(parent) {
402
965
  const agents = payload.data?.agents ?? [];
403
966
  const reason = payload.data?.reason ?? (agents.length > 0 ? "ok" : "no_agents_on_manageable_servers");
404
967
  const suggestedNextAction = describeListResult(reason, options.server);
405
- emit({
968
+ writeJson(ctx.io, {
406
969
  ok: true,
407
970
  data: {
408
971
  agents,
@@ -411,28 +974,46 @@ function registerAgentListCommand(parent) {
411
974
  suggested_next_action: suggestedNextAction
412
975
  }
413
976
  });
414
- });
977
+ }
978
+ );
979
+ function registerAgentListCommand(parent, runtimeOptions = {}) {
980
+ registerCliCommand(parent, agentListCommand, runtimeOptions);
415
981
  }
416
982
 
417
983
  // src/commands/agent/login.ts
418
984
  import { mkdir, stat, writeFile } from "fs/promises";
419
- import path2 from "path";
985
+ import path3 from "path";
420
986
  import { fetch as undiciFetch2 } from "undici";
421
- function registerAgentLoginCommand(parent) {
422
- parent.command("login").description(
423
- "Sign this CLI in as a specific Slock agent via the device-code login grant."
424
- ).requiredOption("--server <url>", "Slock server base URL, e.g. https://slock.example.com").requiredOption("--agent <agentId>", "Agent id to log in as").option("--client-name <label>", "Human-readable label shown on the web approval page").option("--profile-slug <slug>", "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use.").option("--profile-dir <path>", "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)").action(async (options) => {
987
+ var agentLoginCommand = defineCommand(
988
+ {
989
+ name: "login",
990
+ description: "Sign this CLI in as a specific Slock agent via the device-code login grant.",
991
+ options: [
992
+ { flags: "--server <url>", description: "Slock server base URL, e.g. https://slock.example.com" },
993
+ { flags: "--agent <agentId>", description: "Agent id to log in as" },
994
+ { flags: "--client-name <label>", description: "Human-readable label shown on the web approval page" },
995
+ { flags: "--profile-slug <slug>", description: "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use." },
996
+ { flags: "--profile-dir <path>", description: "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)" }
997
+ ]
998
+ },
999
+ async (ctx, options) => {
1000
+ if (!options.server?.trim()) {
1001
+ throw cliError("INVALID_ARG", "--server is required");
1002
+ }
1003
+ if (!options.agent?.trim()) {
1004
+ throw cliError("INVALID_AGENT_ID", "--agent must not be empty.");
1005
+ }
425
1006
  const invalidShape = describeInvalidAgentIdShape(options.agent);
426
1007
  if (invalidShape) {
427
- fail("INVALID_AGENT_ID", invalidShape, {
1008
+ throw cliError("INVALID_AGENT_ID", invalidShape, {
428
1009
  suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
429
1010
  });
430
1011
  }
431
1012
  const profileSlug = options.profileSlug ?? options.agent;
432
1013
  const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
433
- const credentialPath = path2.join(profileDir, "credential.json");
1014
+ const credentialPath = path3.join(profileDir, "credential.json");
434
1015
  if (await profileFileExists(credentialPath)) {
435
- fail(
1016
+ throw cliError(
436
1017
  "PROFILE_ALREADY_EXISTS",
437
1018
  `Profile '${profileSlug}' already has a credential at ${credentialPath}.`,
438
1019
  {
@@ -446,7 +1027,7 @@ function registerAgentLoginCommand(parent) {
446
1027
  serverUrl: options.server,
447
1028
  ...options.clientName ? { clientName: options.clientName } : {},
448
1029
  onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
449
- process.stderr.write(
1030
+ ctx.io.stderr.write(
450
1031
  `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
451
1032
  `
452
1033
  );
@@ -454,7 +1035,7 @@ function registerAgentLoginCommand(parent) {
454
1035
  });
455
1036
  } catch (err) {
456
1037
  if (err instanceof DeviceCodeLoginError) {
457
- fail(err.code, err.message);
1038
+ throw cliError(err.code, err.message, { cause: err });
458
1039
  }
459
1040
  throw err;
460
1041
  }
@@ -473,7 +1054,7 @@ function registerAgentLoginCommand(parent) {
473
1054
  const body = await safeJson2(mintRes);
474
1055
  const code = body?.code ?? `mint_failed_${mintRes.status}`;
475
1056
  const detail = describeMintError(code, options.server);
476
- fail(
1057
+ throw cliError(
477
1058
  code,
478
1059
  detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
479
1060
  detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
@@ -481,7 +1062,7 @@ function registerAgentLoginCommand(parent) {
481
1062
  }
482
1063
  const minted = await mintRes.json();
483
1064
  if (!minted.apiKey || !minted.agentId || !minted.serverId) {
484
- fail(
1065
+ throw cliError(
485
1066
  "mint_response_invalid",
486
1067
  "Server mint response was missing apiKey / agentId / serverId."
487
1068
  );
@@ -506,7 +1087,7 @@ function registerAgentLoginCommand(parent) {
506
1087
  ) + "\n",
507
1088
  { mode: 384 }
508
1089
  );
509
- emit({
1090
+ writeJson(ctx.io, {
510
1091
  ok: true,
511
1092
  data: {
512
1093
  agentId: minted.agentId,
@@ -518,7 +1099,10 @@ function registerAgentLoginCommand(parent) {
518
1099
  credentialPath
519
1100
  }
520
1101
  });
521
- });
1102
+ }
1103
+ );
1104
+ function registerAgentLoginCommand(parent, runtimeOptions = {}) {
1105
+ registerCliCommand(parent, agentLoginCommand, runtimeOptions);
522
1106
  }
523
1107
  async function safeJson2(res) {
524
1108
  try {
@@ -575,32 +1159,56 @@ function describeMintError(code, serverUrl) {
575
1159
  return void 0;
576
1160
  }
577
1161
 
1162
+ // ../shared/src/slockRefs.ts
1163
+ var SLOCK_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
1164
+ var SLOCK_REF_USER_NAME_PATTERN = SLOCK_REF_CHANNEL_NAME_PATTERN;
1165
+ var SLOCK_REF_DM_PEER_PATTERN = String.raw`[\w-]+`;
1166
+ var SLOCK_REF_THREAD_SHORT_ID_PATTERN = String.raw`[\da-f]{6,8}`;
1167
+ var SLOCK_REF_MESSAGE_ID_PATTERN = String.raw`[A-Za-z0-9][A-Za-z0-9-]{1,63}`;
1168
+ var SLOCK_REF_TASK_NUMBER_PATTERN = String.raw`[1-9]\d*`;
1169
+ var USER_RE = new RegExp(String.raw`^@(${SLOCK_REF_USER_NAME_PATTERN})$`, "u");
1170
+ var CHANNEL_RE = new RegExp(String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN})$`, "u");
1171
+ var CHANNEL_THREAD_RE = new RegExp(
1172
+ String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN}):(${SLOCK_REF_THREAD_SHORT_ID_PATTERN})$`,
1173
+ "iu"
1174
+ );
1175
+ var DM_RE = new RegExp(String.raw`^dm:@(${SLOCK_REF_DM_PEER_PATTERN})$`, "iu");
1176
+ var DM_THREAD_RE = new RegExp(
1177
+ String.raw`^dm:@(${SLOCK_REF_DM_PEER_PATTERN}):(${SLOCK_REF_THREAD_SHORT_ID_PATTERN})$`,
1178
+ "iu"
1179
+ );
1180
+ var TASK_RE = new RegExp(String.raw`^task\s+#(${SLOCK_REF_TASK_NUMBER_PATTERN})$`, "iu");
1181
+ var CHANNEL_MESSAGE_RE = new RegExp(
1182
+ String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN})(?::(${SLOCK_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${SLOCK_REF_MESSAGE_ID_PATTERN})$`,
1183
+ "iu"
1184
+ );
1185
+
578
1186
  // ../shared/src/tracing/index.ts
579
1187
  var DEFAULT_TRACE_FLAGS = "00";
580
- var TRACE_ID_HEX_LENGTH = 32;
581
- var SPAN_ID_HEX_LENGTH = 16;
1188
+ var TRACE_ID_HEX_LENGTH2 = 32;
1189
+ var SPAN_ID_HEX_LENGTH2 = 16;
582
1190
  var TRACE_FLAGS_HEX_LENGTH = 2;
583
1191
  var TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
584
1192
  var SPAN_ID_PATTERN = /^[0-9a-f]{16}$/;
585
1193
  var TRACE_FLAGS_PATTERN = /^[0-9a-f]{2}$/;
586
1194
  function isTraceId(value) {
587
- return TRACE_ID_PATTERN.test(value) && value !== "0".repeat(TRACE_ID_HEX_LENGTH);
1195
+ return TRACE_ID_PATTERN.test(value) && value !== "0".repeat(TRACE_ID_HEX_LENGTH2);
588
1196
  }
589
1197
  function isSpanId(value) {
590
- return SPAN_ID_PATTERN.test(value) && value !== "0".repeat(SPAN_ID_HEX_LENGTH);
1198
+ return SPAN_ID_PATTERN.test(value) && value !== "0".repeat(SPAN_ID_HEX_LENGTH2);
591
1199
  }
592
1200
  function isTraceFlags(value) {
593
1201
  return TRACE_FLAGS_PATTERN.test(value);
594
1202
  }
595
1203
  function assertTraceContext(context) {
596
1204
  if (!isTraceId(context.traceId)) {
597
- throw new Error(`Invalid traceId: expected ${TRACE_ID_HEX_LENGTH} lowercase hex chars`);
1205
+ throw new Error(`Invalid traceId: expected ${TRACE_ID_HEX_LENGTH2} lowercase hex chars`);
598
1206
  }
599
1207
  if (!isSpanId(context.spanId)) {
600
- throw new Error(`Invalid spanId: expected ${SPAN_ID_HEX_LENGTH} lowercase hex chars`);
1208
+ throw new Error(`Invalid spanId: expected ${SPAN_ID_HEX_LENGTH2} lowercase hex chars`);
601
1209
  }
602
1210
  if (context.parentSpanId !== null && !isSpanId(context.parentSpanId)) {
603
- throw new Error(`Invalid parentSpanId: expected null or ${SPAN_ID_HEX_LENGTH} lowercase hex chars`);
1211
+ throw new Error(`Invalid parentSpanId: expected null or ${SPAN_ID_HEX_LENGTH2} lowercase hex chars`);
604
1212
  }
605
1213
  if (!isTraceFlags(context.traceFlags)) {
606
1214
  throw new Error(`Invalid traceFlags: expected ${TRACE_FLAGS_HEX_LENGTH} lowercase hex chars`);
@@ -640,19 +1248,19 @@ var NoopActiveSpan = class {
640
1248
  };
641
1249
  var noopTracer = new NoopTracer();
642
1250
  function generateTraceId() {
643
- return randomNonZeroHex(TRACE_ID_HEX_LENGTH);
1251
+ return randomNonZeroHex(TRACE_ID_HEX_LENGTH2);
644
1252
  }
645
1253
  function generateSpanId() {
646
- return randomNonZeroHex(SPAN_ID_HEX_LENGTH);
1254
+ return randomNonZeroHex(SPAN_ID_HEX_LENGTH2);
647
1255
  }
648
1256
  function randomNonZeroHex(length) {
649
- let value = randomHex(length);
1257
+ let value = randomHex2(length);
650
1258
  while (value === "0".repeat(length)) {
651
- value = randomHex(length);
1259
+ value = randomHex2(length);
652
1260
  }
653
1261
  return value;
654
1262
  }
655
- function randomHex(length) {
1263
+ function randomHex2(length) {
656
1264
  const bytes = new Uint8Array(length / 2);
657
1265
  globalThis.crypto.getRandomValues(bytes);
658
1266
  return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -1428,10 +2036,10 @@ function mergeDefs(...defs) {
1428
2036
  function cloneDef(schema) {
1429
2037
  return mergeDefs(schema._zod.def);
1430
2038
  }
1431
- function getElementAtPath(obj, path4) {
1432
- if (!path4)
2039
+ function getElementAtPath(obj, path6) {
2040
+ if (!path6)
1433
2041
  return obj;
1434
- return path4.reduce((acc, key) => acc?.[key], obj);
2042
+ return path6.reduce((acc, key) => acc?.[key], obj);
1435
2043
  }
1436
2044
  function promiseAllObject(promisesObj) {
1437
2045
  const keys = Object.keys(promisesObj);
@@ -1814,11 +2422,11 @@ function aborted(x, startIndex = 0) {
1814
2422
  }
1815
2423
  return false;
1816
2424
  }
1817
- function prefixIssues(path4, issues) {
2425
+ function prefixIssues(path6, issues) {
1818
2426
  return issues.map((iss) => {
1819
2427
  var _a2;
1820
2428
  (_a2 = iss).path ?? (_a2.path = []);
1821
- iss.path.unshift(path4);
2429
+ iss.path.unshift(path6);
1822
2430
  return iss;
1823
2431
  });
1824
2432
  }
@@ -2001,7 +2609,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
2001
2609
  }
2002
2610
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
2003
2611
  const result = { errors: [] };
2004
- const processError = (error49, path4 = []) => {
2612
+ const processError = (error49, path6 = []) => {
2005
2613
  var _a2, _b;
2006
2614
  for (const issue2 of error49.issues) {
2007
2615
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -2011,7 +2619,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
2011
2619
  } else if (issue2.code === "invalid_element") {
2012
2620
  processError({ issues: issue2.issues }, issue2.path);
2013
2621
  } else {
2014
- const fullpath = [...path4, ...issue2.path];
2622
+ const fullpath = [...path6, ...issue2.path];
2015
2623
  if (fullpath.length === 0) {
2016
2624
  result.errors.push(mapper(issue2));
2017
2625
  continue;
@@ -2043,8 +2651,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
2043
2651
  }
2044
2652
  function toDotPath(_path) {
2045
2653
  const segs = [];
2046
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2047
- for (const seg of path4) {
2654
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2655
+ for (const seg of path6) {
2048
2656
  if (typeof seg === "number")
2049
2657
  segs.push(`[${seg}]`);
2050
2658
  else if (typeof seg === "symbol")
@@ -14021,13 +14629,13 @@ function resolveRef(ref, ctx) {
14021
14629
  if (!ref.startsWith("#")) {
14022
14630
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14023
14631
  }
14024
- const path4 = ref.slice(1).split("/").filter(Boolean);
14025
- if (path4.length === 0) {
14632
+ const path6 = ref.slice(1).split("/").filter(Boolean);
14633
+ if (path6.length === 0) {
14026
14634
  return ctx.rootSchema;
14027
14635
  }
14028
14636
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14029
- if (path4[0] === defsKey) {
14030
- const key = path4[1];
14637
+ if (path6[0] === defsKey) {
14638
+ const key = path6[1];
14031
14639
  if (!key || !ctx.defs[key]) {
14032
14640
  throw new Error(`Reference not found: ${ref}`);
14033
14641
  }
@@ -14634,234 +15242,6 @@ var DISPLAY_PLAN_CONFIG = {
14634
15242
  }
14635
15243
  };
14636
15244
 
14637
- // src/proxy.ts
14638
- import { ProxyAgent } from "undici";
14639
- var fetchDispatcherCache = /* @__PURE__ */ new Map();
14640
- function getDefaultPort(protocol) {
14641
- switch (protocol) {
14642
- case "https:":
14643
- return "443";
14644
- case "http:":
14645
- return "80";
14646
- default:
14647
- return "";
14648
- }
14649
- }
14650
- function hostMatchesNoProxyEntry(hostname3, ruleHost) {
14651
- if (!ruleHost) return false;
14652
- const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
14653
- const normalizedHost = hostname3.toLowerCase();
14654
- return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
14655
- }
14656
- function getProxyUrlForTarget(targetUrl, env) {
14657
- const protocol = new URL(targetUrl).protocol;
14658
- switch (protocol) {
14659
- case "https:":
14660
- return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
14661
- case "http:":
14662
- return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
14663
- default:
14664
- return env.ALL_PROXY || env.all_proxy;
14665
- }
14666
- }
14667
- function shouldBypassProxy(targetUrl, env) {
14668
- const rawNoProxy = env.NO_PROXY || env.no_proxy;
14669
- if (!rawNoProxy) return false;
14670
- const url2 = new URL(targetUrl);
14671
- const hostname3 = url2.hostname.toLowerCase();
14672
- const port = url2.port || getDefaultPort(url2.protocol);
14673
- return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
14674
- if (entry === "*") return true;
14675
- const [ruleHost, rulePort] = entry.split(":", 2);
14676
- if (rulePort && rulePort !== port) return false;
14677
- return hostMatchesNoProxyEntry(hostname3, ruleHost);
14678
- });
14679
- }
14680
- function buildFetchDispatcher(targetUrl, env = process.env) {
14681
- const proxyUrl = getProxyUrlForTarget(targetUrl, env);
14682
- if (!proxyUrl) return void 0;
14683
- if (shouldBypassProxy(targetUrl, env)) return void 0;
14684
- const cached2 = fetchDispatcherCache.get(proxyUrl);
14685
- if (cached2) return cached2;
14686
- const dispatcher = new ProxyAgent(proxyUrl);
14687
- fetchDispatcherCache.set(proxyUrl, dispatcher);
14688
- return dispatcher;
14689
- }
14690
-
14691
- // src/client.ts
14692
- var ApiClient = class {
14693
- constructor(ctx) {
14694
- this.ctx = ctx;
14695
- }
14696
- usesAgentApiSurface() {
14697
- return this.ctx.clientMode === "managed-runner" || this.ctx.clientMode === "self-hosted-runner";
14698
- }
14699
- rewriteAgentCredentialPath(pathname) {
14700
- if (!this.usesAgentApiSurface()) return pathname;
14701
- const attachmentDownload = /^\/api\/attachments\/([^/?]+)(.*)$/.exec(pathname);
14702
- if (attachmentDownload) {
14703
- return `/internal/agent-api/attachments/${attachmentDownload[1]}${attachmentDownload[2] ?? ""}`;
14704
- }
14705
- const agentPrefix = `/internal/agent/${encodeURIComponent(this.ctx.agentId)}`;
14706
- if (!pathname.startsWith(agentPrefix)) return pathname;
14707
- const suffix = pathname.slice(agentPrefix.length);
14708
- if (suffix === "/server") return "/internal/agent-api/server";
14709
- if (suffix === "/send") return "/internal/agent-api/send";
14710
- if (suffix.startsWith("/history")) return `/internal/agent-api/history${suffix.slice("/history".length)}`;
14711
- if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
14712
- if (suffix.startsWith("/channel-members")) return `/internal/agent-api/channel-members${suffix.slice("/channel-members".length)}`;
14713
- if (suffix === "/profile" || suffix.startsWith("/profile/")) return `/internal/agent-api${suffix}`;
14714
- if (suffix === "/integrations" || suffix.startsWith("/integrations/")) return `/internal/agent-api${suffix}`;
14715
- if (suffix === "/upload") return "/internal/agent-api/upload";
14716
- if (suffix === "/resolve-channel") return "/internal/agent-api/resolve-channel";
14717
- if (suffix === "/threads/unfollow") return "/internal/agent-api/threads/unfollow";
14718
- if (suffix === "/prepare-action") return "/internal/agent-api/prepare-action";
14719
- if (suffix === "/tasks" || suffix.startsWith("/tasks?") || suffix.startsWith("/tasks/")) {
14720
- return `/internal/agent-api${suffix}`;
14721
- }
14722
- if (suffix === "/reminders" || suffix.startsWith("/reminders?") || suffix.startsWith("/reminders/")) {
14723
- return `/internal/agent-api${suffix}`;
14724
- }
14725
- if (suffix === "/receive" || suffix.startsWith("/receive?")) {
14726
- return "/internal/agent-api/events?since=latest";
14727
- }
14728
- const reaction = /^\/messages\/([^/]+)\/reactions$/.exec(suffix);
14729
- if (reaction) {
14730
- return `/internal/agent-api/messages/${reaction[1]}/reactions`;
14731
- }
14732
- const channelMembership = /^\/channels\/([^/]+)\/(join|leave)$/.exec(suffix);
14733
- if (channelMembership) {
14734
- return `/internal/agent-api/channels/${channelMembership[1]}/${channelMembership[2]}`;
14735
- }
14736
- return pathname;
14737
- }
14738
- normalizeAgentCredentialResponse(pathname, data) {
14739
- if (!this.usesAgentApiSurface()) return data;
14740
- if (!pathname.includes("/internal/agent-api/events")) return data;
14741
- const value = data;
14742
- if (!Array.isArray(value.events)) return data;
14743
- return { ...data, messages: value.events };
14744
- }
14745
- buildAuthHeaders() {
14746
- const headers = {
14747
- "Authorization": `Bearer ${this.ctx.token}`,
14748
- "X-Agent-Id": this.ctx.agentId,
14749
- "X-Slock-Client": "cli"
14750
- };
14751
- if (this.ctx.serverId) headers["X-Server-Id"] = this.ctx.serverId;
14752
- return headers;
14753
- }
14754
- async parseJsonResponse(res) {
14755
- let data = null;
14756
- let error48 = null;
14757
- let errorCode = null;
14758
- const contentType = res.headers.get("content-type") ?? "";
14759
- if (contentType.includes("application/json")) {
14760
- let parseFailed = false;
14761
- const parsed = await res.json().catch(() => {
14762
- parseFailed = true;
14763
- return null;
14764
- });
14765
- if (parseFailed) {
14766
- return {
14767
- ok: false,
14768
- status: res.status,
14769
- data: null,
14770
- error: `Invalid JSON response from server/proxy (HTTP ${res.status})`,
14771
- errorCode: "INVALID_JSON_RESPONSE"
14772
- };
14773
- }
14774
- if (res.ok) {
14775
- data = parsed;
14776
- } else {
14777
- const body = parsed;
14778
- if (res.status === 403 && body?.requiredScope) {
14779
- errorCode = "SCOPE_DENIED";
14780
- error48 = `Permission denied. The human has revoked the \`${body.requiredScope}\` capability for this agent under the agent profile's Permissions tab, so this command can't run. If you need it, ask the human to re-enable the corresponding toggle.`;
14781
- } else {
14782
- error48 = body?.error ?? `HTTP ${res.status}`;
14783
- errorCode = body?.errorCode ?? null;
14784
- }
14785
- }
14786
- } else if (!res.ok) {
14787
- error48 = `HTTP ${res.status}`;
14788
- }
14789
- return { ok: res.ok, status: res.status, data, error: error48, errorCode };
14790
- }
14791
- async request(method, pathname, body) {
14792
- pathname = this.rewriteAgentCredentialPath(pathname);
14793
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14794
- const headers = this.buildAuthHeaders();
14795
- headers["Content-Type"] = "application/json";
14796
- if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14797
- headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14798
- }
14799
- const dispatcher = buildFetchDispatcher(url2);
14800
- const init = {
14801
- method,
14802
- headers,
14803
- body: body === void 0 ? void 0 : JSON.stringify(body)
14804
- };
14805
- if (dispatcher) init.dispatcher = dispatcher;
14806
- const res = await fetch(url2, init);
14807
- const parsed = await this.parseJsonResponse(res);
14808
- if (parsed.ok && parsed.data !== null) {
14809
- parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
14810
- }
14811
- return parsed;
14812
- }
14813
- // Multipart upload. Caller builds the FormData (file part + any text fields).
14814
- // Content-Type intentionally omitted so fetch sets the correct multipart
14815
- // boundary itself.
14816
- async requestMultipart(method, pathname, form) {
14817
- pathname = this.rewriteAgentCredentialPath(pathname);
14818
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14819
- const dispatcher = buildFetchDispatcher(url2);
14820
- const headers = this.buildAuthHeaders();
14821
- if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14822
- headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14823
- }
14824
- const init = {
14825
- method,
14826
- headers,
14827
- body: form
14828
- };
14829
- if (dispatcher) init.dispatcher = dispatcher;
14830
- const res = await fetch(url2, init);
14831
- return this.parseJsonResponse(res);
14832
- }
14833
- // Returns the raw Response so the caller can stream / save the body.
14834
- // For non-JSON downloads (binary attachments). Caller is responsible for
14835
- // consuming the body. On non-2xx, attempts to surface a JSON error.
14836
- async requestRaw(method, pathname) {
14837
- pathname = this.rewriteAgentCredentialPath(pathname);
14838
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14839
- const dispatcher = buildFetchDispatcher(url2);
14840
- const headers = this.buildAuthHeaders();
14841
- if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14842
- headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14843
- }
14844
- const init = {
14845
- method,
14846
- headers,
14847
- redirect: "follow"
14848
- };
14849
- if (dispatcher) init.dispatcher = dispatcher;
14850
- const res = await fetch(url2, init);
14851
- let error48 = null;
14852
- if (!res.ok) {
14853
- const contentType = res.headers.get("content-type") ?? "";
14854
- if (contentType.includes("application/json")) {
14855
- const parsed = await res.json().catch(() => null);
14856
- error48 = parsed?.error ?? `HTTP ${res.status}`;
14857
- } else {
14858
- error48 = `HTTP ${res.status}`;
14859
- }
14860
- }
14861
- return { ok: res.ok, status: res.status, response: res, error: error48 };
14862
- }
14863
- };
14864
-
14865
15245
  // src/commands/action/prepare.ts
14866
15246
  var ACTION_HEREDOC_DELIMITER = "SLOCKACTION";
14867
15247
  var PrepareActionInputError = class extends Error {
@@ -14905,52 +15285,60 @@ async function resolveActionInput(input = process.stdin) {
14905
15285
  );
14906
15286
  }
14907
15287
  }
14908
- function registerActionPrepareCommand(parent) {
14909
- parent.command("prepare").description("Prepare an action card for a human to commit (B-mode quick-commit shortcut)").requiredOption(
14910
- "--target <target>",
14911
- "Channel/DM/thread target to post the card. Same format as slock message send: '#channel', 'dm:@peer', '#channel:shortid'"
14912
- ).action(async (opts) => {
14913
- let ctx;
14914
- try {
14915
- ctx = loadAgentContext();
14916
- } catch (err) {
14917
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
14918
- throw err;
15288
+ var actionPrepareCommand = defineCommand(
15289
+ {
15290
+ name: "prepare",
15291
+ description: "Prepare an action card for a human to commit (B-mode quick-commit shortcut)",
15292
+ options: [
15293
+ {
15294
+ flags: "--target <target>",
15295
+ description: "Channel/DM/thread target to post the card. Same format as slock message send: '#channel', 'dm:@peer', '#channel:shortid'"
15296
+ }
15297
+ ]
15298
+ },
15299
+ async (ctx, opts) => {
15300
+ if (!opts.target?.trim()) {
15301
+ throw cliError("INVALID_ARG", "--target is required");
14919
15302
  }
15303
+ const agentContext = ctx.loadAgentContext();
14920
15304
  let raw;
14921
15305
  try {
14922
- raw = await resolveActionInput();
15306
+ raw = await resolveActionInput(ctx.io.stdin ?? process.stdin);
14923
15307
  } catch (err) {
14924
- if (err instanceof PrepareActionInputError) fail(err.code, err.message);
15308
+ if (err instanceof PrepareActionInputError) throw cliError(err.code, err.message, { cause: err });
14925
15309
  throw err;
14926
15310
  }
14927
15311
  const parsed = actionCardActionSchema.safeParse(raw);
14928
15312
  if (!parsed.success) {
14929
15313
  const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
14930
- fail("INVALID_ACTION", `Action failed validation: ${issues}`);
15314
+ throw cliError("INVALID_ACTION", `Action failed validation: ${issues}`);
14931
15315
  }
14932
15316
  const crossFieldError = validateActionCardAction(parsed.data);
14933
15317
  if (crossFieldError) {
14934
- fail("INVALID_ACTION", `Action failed validation: ${crossFieldError}`);
15318
+ throw cliError("INVALID_ACTION", `Action failed validation: ${crossFieldError}`);
14935
15319
  }
14936
- const client = new ApiClient(ctx);
15320
+ const client = ctx.createApiClient(agentContext);
14937
15321
  const res = await client.request(
14938
15322
  "POST",
14939
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/prepare-action`,
15323
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/prepare-action`,
14940
15324
  { target: opts.target, action: parsed.data }
14941
15325
  );
14942
15326
  if (!res.ok) {
14943
15327
  const code = res.status >= 500 ? "SERVER_5XX" : "PREPARE_FAILED";
14944
- fail(code, res.error ?? `HTTP ${res.status}`);
15328
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
14945
15329
  }
14946
15330
  const data = res.data;
14947
15331
  const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
14948
- process.stdout.write(
15332
+ writeText(
15333
+ ctx.io,
14949
15334
  shortId ? `Action card posted to ${opts.target} as message ${data.messageId} (short ${shortId}). The human can click the action verb to commit.
14950
15335
  ` : `Action card posted to ${opts.target}.
14951
15336
  `
14952
15337
  );
14953
- });
15338
+ }
15339
+ );
15340
+ function registerActionPrepareCommand(parent, runtimeOptions = {}) {
15341
+ registerCliCommand(parent, actionPrepareCommand, runtimeOptions);
14954
15342
  }
14955
15343
 
14956
15344
  // src/commands/server/_format.ts
@@ -15053,29 +15441,38 @@ function formatChannelMembers(data) {
15053
15441
  }
15054
15442
 
15055
15443
  // src/commands/channel/members.ts
15056
- function registerChannelMembersCommand(parent) {
15057
- parent.command("members").description("List agents and humans who are members of a channel, DM, or thread").argument("<target>", "Channel / DM / thread target, e.g. #proj-runtime, dm:@alice, #proj-runtime:abcd1234").action(async (target) => {
15058
- let ctx;
15059
- try {
15060
- ctx = loadAgentContext();
15061
- } catch (err) {
15062
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15063
- throw err;
15064
- }
15065
- const client = new ApiClient(ctx);
15444
+ var channelMembersCommand = defineCommand(
15445
+ {
15446
+ name: "members",
15447
+ description: "List agents and humans who are members of a channel, DM, or thread",
15448
+ arguments: ["<target>"]
15449
+ },
15450
+ async (ctx, target) => {
15066
15451
  const channel = String(target || "").trim();
15067
- if (!channel) fail("MEMBERS_FAILED", "target is required");
15452
+ if (!channel) {
15453
+ throw new CliError({
15454
+ code: "INVALID_ARG",
15455
+ message: "target is required"
15456
+ });
15457
+ }
15458
+ const agentContext = ctx.loadAgentContext();
15459
+ const client = ctx.createApiClient(agentContext);
15068
15460
  const encoded = encodeURIComponent(channel);
15069
15461
  const res = await client.request(
15070
15462
  "GET",
15071
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/channel-members?channel=${encoded}`
15463
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/channel-members?channel=${encoded}`
15072
15464
  );
15073
15465
  if (!res.ok) {
15074
- const code = res.status >= 500 ? "SERVER_5XX" : "MEMBERS_FAILED";
15075
- fail(code, res.error ?? `HTTP ${res.status}`);
15466
+ throw new CliError({
15467
+ code: res.status >= 500 ? "SERVER_5XX" : "MEMBERS_FAILED",
15468
+ message: res.error ?? `HTTP ${res.status}`
15469
+ });
15076
15470
  }
15077
- process.stdout.write(formatChannelMembers(res.data));
15078
- });
15471
+ writeText(ctx.io, formatChannelMembers(res.data));
15472
+ }
15473
+ );
15474
+ function registerChannelMembersCommand(parent, runtimeOptions) {
15475
+ registerCliCommand(parent, channelMembersCommand, runtimeOptions);
15079
15476
  }
15080
15477
 
15081
15478
  // src/commands/channel/leave.ts
@@ -15091,46 +15488,64 @@ function formatLeaveChannelResult(target) {
15091
15488
  function formatAlreadyNotJoined(target) {
15092
15489
  return `Already not joined in ${target}.`;
15093
15490
  }
15094
- function registerChannelLeaveCommand(parent) {
15095
- parent.command("leave").description("Leave a regular channel you have joined").requiredOption("--target <target>", "Regular channel to leave, e.g. '#engineering'").action(async (opts) => {
15096
- const channelName = parseRegularChannelTarget(opts.target);
15491
+ var channelLeaveCommand = defineCommand(
15492
+ {
15493
+ name: "leave",
15494
+ description: "Leave a regular channel you have joined",
15495
+ options: [
15496
+ {
15497
+ flags: "--target <target>",
15498
+ description: "Regular channel to leave, e.g. '#engineering'"
15499
+ }
15500
+ ]
15501
+ },
15502
+ async (ctx, opts) => {
15503
+ const target = opts.target ?? "";
15504
+ const channelName = parseRegularChannelTarget(target);
15097
15505
  if (!channelName) {
15098
- fail("INVALID_TARGET", "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.");
15099
- }
15100
- let ctx;
15101
- try {
15102
- ctx = loadAgentContext();
15103
- } catch (err) {
15104
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15105
- throw err;
15506
+ throw new CliError({
15507
+ code: "INVALID_TARGET",
15508
+ message: "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported."
15509
+ });
15106
15510
  }
15107
- const client = new ApiClient(ctx);
15511
+ const agentContext = ctx.loadAgentContext();
15512
+ const client = ctx.createApiClient(agentContext);
15108
15513
  const infoRes = await client.request(
15109
15514
  "GET",
15110
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
15515
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/server`
15111
15516
  );
15112
15517
  if (!infoRes.ok) {
15113
- const code = infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
15114
- fail(code, infoRes.error ?? `HTTP ${infoRes.status}`);
15518
+ throw new CliError({
15519
+ code: infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED",
15520
+ message: infoRes.error ?? `HTTP ${infoRes.status}`
15521
+ });
15115
15522
  }
15116
15523
  const channel = (infoRes.data?.channels ?? []).find((candidate) => candidate.name === channelName);
15117
15524
  if (!channel) {
15118
- fail("NOT_FOUND", `Channel not found: ${opts.target}`);
15525
+ throw new CliError({
15526
+ code: "NOT_FOUND",
15527
+ message: `Channel not found: ${target}`
15528
+ });
15119
15529
  }
15120
15530
  if (!channel.joined) {
15121
- process.stdout.write(formatAlreadyNotJoined(opts.target) + "\n");
15531
+ writeText(ctx.io, formatAlreadyNotJoined(target) + "\n");
15122
15532
  return;
15123
15533
  }
15124
15534
  const leaveRes = await client.request(
15125
15535
  "POST",
15126
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/channels/${encodeURIComponent(channel.id)}/leave`
15536
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/channels/${encodeURIComponent(channel.id)}/leave`
15127
15537
  );
15128
15538
  if (!leaveRes.ok) {
15129
- const code = leaveRes.status >= 500 ? "SERVER_5XX" : "LEAVE_FAILED";
15130
- fail(code, leaveRes.error ?? `HTTP ${leaveRes.status}`);
15539
+ throw new CliError({
15540
+ code: leaveRes.status >= 500 ? "SERVER_5XX" : "LEAVE_FAILED",
15541
+ message: leaveRes.error ?? `HTTP ${leaveRes.status}`
15542
+ });
15131
15543
  }
15132
- process.stdout.write(formatLeaveChannelResult(opts.target) + "\n");
15133
- });
15544
+ writeText(ctx.io, formatLeaveChannelResult(target) + "\n");
15545
+ }
15546
+ );
15547
+ function registerChannelLeaveCommand(parent, runtimeOptions) {
15548
+ registerCliCommand(parent, channelLeaveCommand, runtimeOptions);
15134
15549
  }
15135
15550
 
15136
15551
  // src/commands/channel/join.ts
@@ -15140,76 +15555,177 @@ function formatJoinChannelResult(target) {
15140
15555
  function formatAlreadyJoined(target) {
15141
15556
  return `Already joined ${target}.`;
15142
15557
  }
15143
- function registerChannelJoinCommand(parent) {
15144
- parent.command("join").description("Join a visible public channel").requiredOption("--target <target>", "Regular channel to join, e.g. '#engineering'").action(async (opts) => {
15145
- const channelName = parseRegularChannelTarget(opts.target);
15558
+ var channelJoinCommand = defineCommand(
15559
+ {
15560
+ name: "join",
15561
+ description: "Join a visible public channel",
15562
+ options: [
15563
+ {
15564
+ flags: "--target <target>",
15565
+ description: "Regular channel to join, e.g. '#engineering'"
15566
+ }
15567
+ ]
15568
+ },
15569
+ async (ctx, opts) => {
15570
+ const target = opts.target ?? "";
15571
+ const channelName = parseRegularChannelTarget(target);
15146
15572
  if (!channelName) {
15147
- fail("INVALID_TARGET", "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.");
15148
- }
15149
- let ctx;
15150
- try {
15151
- ctx = loadAgentContext();
15152
- } catch (err) {
15153
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15154
- throw err;
15573
+ throw new CliError({
15574
+ code: "INVALID_TARGET",
15575
+ message: "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported."
15576
+ });
15155
15577
  }
15156
- const client = new ApiClient(ctx);
15578
+ const agentContext = ctx.loadAgentContext();
15579
+ const client = ctx.createApiClient(agentContext);
15157
15580
  const infoRes = await client.request(
15158
15581
  "GET",
15159
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
15582
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/server`
15160
15583
  );
15161
15584
  if (!infoRes.ok) {
15162
- const code = infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
15163
- fail(code, infoRes.error ?? `HTTP ${infoRes.status}`);
15585
+ throw new CliError({
15586
+ code: infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED",
15587
+ message: infoRes.error ?? `HTTP ${infoRes.status}`
15588
+ });
15164
15589
  }
15165
15590
  const channel = (infoRes.data?.channels ?? []).find((candidate) => candidate.name === channelName);
15166
15591
  if (!channel) {
15167
- fail("NOT_FOUND", `Channel not found: ${opts.target}`);
15592
+ throw new CliError({
15593
+ code: "NOT_FOUND",
15594
+ message: `Channel not found: ${target}`
15595
+ });
15168
15596
  }
15169
15597
  if (channel.joined) {
15170
- process.stdout.write(formatAlreadyJoined(opts.target) + "\n");
15598
+ writeText(ctx.io, formatAlreadyJoined(target) + "\n");
15171
15599
  return;
15172
15600
  }
15173
15601
  const joinRes = await client.request(
15174
15602
  "POST",
15175
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/channels/${encodeURIComponent(channel.id)}/join`
15603
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/channels/${encodeURIComponent(channel.id)}/join`
15176
15604
  );
15177
15605
  if (!joinRes.ok) {
15178
- const code = joinRes.status >= 500 ? "SERVER_5XX" : "JOIN_FAILED";
15179
- fail(code, joinRes.error ?? `HTTP ${joinRes.status}`);
15606
+ throw new CliError({
15607
+ code: joinRes.status >= 500 ? "SERVER_5XX" : "JOIN_FAILED",
15608
+ message: joinRes.error ?? `HTTP ${joinRes.status}`
15609
+ });
15180
15610
  }
15181
- process.stdout.write(formatJoinChannelResult(opts.target) + "\n");
15182
- });
15611
+ writeText(ctx.io, formatJoinChannelResult(target) + "\n");
15612
+ }
15613
+ );
15614
+ function registerChannelJoinCommand(parent, runtimeOptions) {
15615
+ registerCliCommand(parent, channelJoinCommand, runtimeOptions);
15183
15616
  }
15184
15617
 
15185
15618
  // src/commands/server/info.ts
15186
- function registerServerInfoCommand(parent) {
15187
- parent.command("info").description("List channels, agents, and humans on the current server").action(async () => {
15188
- let ctx;
15189
- try {
15190
- ctx = loadAgentContext();
15191
- } catch (err) {
15192
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15193
- throw err;
15194
- }
15195
- const client = new ApiClient(ctx);
15619
+ var serverInfoCommand = defineCommand(
15620
+ {
15621
+ name: "info",
15622
+ description: "List channels, agents, and humans on the current server"
15623
+ },
15624
+ async (ctx) => {
15625
+ const agentContext = ctx.loadAgentContext();
15626
+ const client = ctx.createApiClient(agentContext);
15196
15627
  const res = await client.request(
15197
15628
  "GET",
15198
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
15629
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/server`
15199
15630
  );
15200
15631
  if (!res.ok) {
15201
15632
  const code = res.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
15202
- fail(code, res.error ?? `HTTP ${res.status}`);
15633
+ throw new CliError({
15634
+ code,
15635
+ message: res.error ?? `HTTP ${res.status}`
15636
+ });
15203
15637
  }
15204
15638
  const data = res.data;
15205
15639
  if (data?.runtimeContext) {
15206
15640
  data.runtimeContext = {
15207
15641
  ...data.runtimeContext,
15208
- workspacePath: data.runtimeContext.workspacePath ?? process.env.SLOCK_CURRENT_WORKSPACE_PATH ?? null
15642
+ workspacePath: data.runtimeContext.workspacePath ?? ctx.env.SLOCK_CURRENT_WORKSPACE_PATH ?? null
15209
15643
  };
15210
15644
  }
15211
- process.stdout.write(formatServerInfo(data));
15212
- });
15645
+ writeText(ctx.io, formatServerInfo(data));
15646
+ }
15647
+ );
15648
+ function registerServerInfoCommand(parent, runtimeOptions) {
15649
+ registerCliCommand(parent, serverInfoCommand, runtimeOptions);
15650
+ }
15651
+
15652
+ // src/commands/knowledge/get.ts
15653
+ function buildKnowledgeGetPath(agentId, topic, opts) {
15654
+ const params = new URLSearchParams();
15655
+ params.set("topic", topic);
15656
+ if (opts.reason) params.set("reason", opts.reason);
15657
+ if (opts.turnId) params.set("turn_id", opts.turnId);
15658
+ if (opts.traceId) params.set("trace_id", opts.traceId);
15659
+ return `/internal/agent/${encodeURIComponent(agentId)}/knowledge?${params.toString()}`;
15660
+ }
15661
+ function formatKnowledgeStdout(content) {
15662
+ return content.endsWith("\n") ? content : `${content}
15663
+ `;
15664
+ }
15665
+ function toKnowledgeErrorCode(errorCode2, status) {
15666
+ switch (errorCode2) {
15667
+ case "INVALID_JSON_RESPONSE":
15668
+ case "SCOPE_DENIED":
15669
+ case "knowledge_agent_missing":
15670
+ case "knowledge_internal_error":
15671
+ case "knowledge_not_found":
15672
+ case "knowledge_reason_invalid":
15673
+ case "knowledge_source_invalid":
15674
+ case "knowledge_topic_invalid":
15675
+ case "knowledge_trace_id_invalid":
15676
+ case "knowledge_turn_id_invalid":
15677
+ case "unsupported_capability":
15678
+ return errorCode2;
15679
+ default:
15680
+ return status >= 500 ? "SERVER_5XX" : "KNOWLEDGE_GET_FAILED";
15681
+ }
15682
+ }
15683
+ var knowledgeGetCommand = defineCommand(
15684
+ {
15685
+ name: "get",
15686
+ description: "Fetch an agent knowledge topic from the current server",
15687
+ arguments: ["<topic>"],
15688
+ options: [
15689
+ {
15690
+ flags: "--reason <text>",
15691
+ description: "Optional rationale for fetching this topic (>=12 chars when provided)"
15692
+ },
15693
+ {
15694
+ flags: "--turn-id <id>",
15695
+ description: "Optional turn id for correlation in the knowledge event"
15696
+ },
15697
+ {
15698
+ flags: "--trace-id <id>",
15699
+ description: "Optional trace id for correlation in the knowledge event"
15700
+ }
15701
+ ]
15702
+ },
15703
+ async (ctx, topic, opts) => {
15704
+ const agentContext = ctx.loadAgentContext();
15705
+ const client = ctx.createApiClient(agentContext);
15706
+ const res = await client.request(
15707
+ "GET",
15708
+ buildKnowledgeGetPath(agentContext.agentId, topic, opts)
15709
+ );
15710
+ if (!res.ok) {
15711
+ throw new CliError({
15712
+ code: toKnowledgeErrorCode(res.errorCode, res.status),
15713
+ message: res.error ?? `HTTP ${res.status}`,
15714
+ suggestedNextAction: res.suggestedNextAction ?? (res.errorCode === "knowledge_not_found" ? "Run `slock knowledge get index` to see all available knowledge topics." : void 0)
15715
+ });
15716
+ }
15717
+ const data = res.data;
15718
+ if (!data || data.ok !== true) {
15719
+ throw new CliError({
15720
+ code: "KNOWLEDGE_GET_FAILED",
15721
+ message: "Server returned an unexpected response shape"
15722
+ });
15723
+ }
15724
+ writeText(ctx.io, formatKnowledgeStdout(data.content));
15725
+ }
15726
+ );
15727
+ function registerKnowledgeGetCommand(parent, runtimeOptions) {
15728
+ registerCliCommand(parent, knowledgeGetCommand, runtimeOptions);
15213
15729
  }
15214
15730
 
15215
15731
  // src/commands/thread/unfollow.ts
@@ -15237,31 +15753,43 @@ function parseThreadTarget(target) {
15237
15753
  function formatUnfollowThreadResult(target) {
15238
15754
  return `Unfollowed ${target}. You can still inspect the thread when its parent conversation is visible, but you will no longer receive ordinary thread delivery unless you follow it again or are mentioned.`;
15239
15755
  }
15240
- function registerThreadUnfollowCommand(parent) {
15241
- parent.command("unfollow").description("Stop following a thread you no longer need ordinary delivery for").requiredOption("--target <target>", "Thread target, e.g. '#engineering:abcd1234' or 'dm:@alice:abcd1234'").action(async (opts) => {
15242
- const thread = parseThreadTarget(opts.target);
15756
+ var threadUnfollowCommand = defineCommand(
15757
+ {
15758
+ name: "unfollow",
15759
+ description: "Stop following a thread you no longer need ordinary delivery for",
15760
+ options: [
15761
+ {
15762
+ flags: "--target <target>",
15763
+ description: "Thread target, e.g. '#engineering:abcd1234' or 'dm:@alice:abcd1234'"
15764
+ }
15765
+ ]
15766
+ },
15767
+ async (ctx, opts) => {
15768
+ const thread = parseThreadTarget(opts.target ?? "");
15243
15769
  if (!thread) {
15244
- fail("INVALID_TARGET", "Thread must be a thread target like '#channel:abcd1234', 'dm:@peer:abcd1234', or a thread channel UUID.");
15245
- }
15246
- let ctx;
15247
- try {
15248
- ctx = loadAgentContext();
15249
- } catch (err) {
15250
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15251
- throw err;
15770
+ throw new CliError({
15771
+ code: "INVALID_TARGET",
15772
+ message: "Thread must be a thread target like '#channel:abcd1234', 'dm:@peer:abcd1234', or a thread channel UUID."
15773
+ });
15252
15774
  }
15253
- const client = new ApiClient(ctx);
15775
+ const agentContext = ctx.loadAgentContext();
15776
+ const client = ctx.createApiClient(agentContext);
15254
15777
  const res = await client.request(
15255
15778
  "POST",
15256
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/threads/unfollow`,
15779
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/threads/unfollow`,
15257
15780
  { thread }
15258
15781
  );
15259
15782
  if (!res.ok) {
15260
- const code = res.status >= 500 ? "SERVER_5XX" : "UNFOLLOW_FAILED";
15261
- fail(code, res.error ?? `HTTP ${res.status}`);
15783
+ throw new CliError({
15784
+ code: res.status >= 500 ? "SERVER_5XX" : "UNFOLLOW_FAILED",
15785
+ message: res.error ?? `HTTP ${res.status}`
15786
+ });
15262
15787
  }
15263
- process.stdout.write(formatUnfollowThreadResult(thread) + "\n");
15264
- });
15788
+ writeText(ctx.io, formatUnfollowThreadResult(thread) + "\n");
15789
+ }
15790
+ );
15791
+ function registerThreadUnfollowCommand(parent, runtimeOptions) {
15792
+ registerCliCommand(parent, threadUnfollowCommand, runtimeOptions);
15265
15793
  }
15266
15794
 
15267
15795
  // src/commands/message/_format.ts
@@ -15434,10 +15962,10 @@ ${opts.heldAction} Review the bounded context shown here, then choose one path.$
15434
15962
  // src/commands/message/_continueDraftState.ts
15435
15963
  import fs2 from "fs";
15436
15964
  import os2 from "os";
15437
- import path3 from "path";
15965
+ import path4 from "path";
15438
15966
  var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
15439
15967
  function stateFilePath(agentId) {
15440
- return path3.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
15968
+ return path4.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
15441
15969
  }
15442
15970
  function readState(agentId) {
15443
15971
  const filePath = stateFilePath(agentId);
@@ -15451,7 +15979,7 @@ function readState(agentId) {
15451
15979
  }
15452
15980
  function writeState(agentId, state) {
15453
15981
  const filePath = stateFilePath(agentId);
15454
- fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
15982
+ fs2.mkdirSync(path4.dirname(filePath), { recursive: true });
15455
15983
  fs2.writeFileSync(filePath, JSON.stringify(state), "utf8");
15456
15984
  }
15457
15985
  function getSavedDraft(agentId, target) {
@@ -15603,48 +16131,68 @@ To send the current draft unchanged:
15603
16131
  `
15604
16132
  });
15605
16133
  }
15606
- function registerSendCommand(parent) {
15607
- parent.command("send").description("Send a message to a channel, DM, or thread").argument("[content...]", "Unsupported positional message content. Pipe content to stdin instead.").requiredOption("--target <target>", "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'").option("--send-draft", "Send the current saved draft after reviewing newer messages").option("--anyway", "Escape hatch: send a saved draft even if freshness re-check is still stale").option("--content <content>", "Unsupported. Pipe message content to stdin instead.").option(
15608
- "--attachment-id <id>",
15609
- "Attachment id to link (repeatable). Get one from `slock attachment upload`.",
15610
- (value, prev = []) => prev.concat(value)
15611
- ).action(async (positionalContent, opts) => {
15612
- try {
15613
- rejectArgContent(positionalContent, opts);
15614
- } catch (err) {
15615
- if (err instanceof SendContentError) fail(err.code, err.message);
15616
- throw err;
16134
+ var messageSendCommand = defineCommand(
16135
+ {
16136
+ name: "send",
16137
+ description: "Send a message to a channel, DM, or thread",
16138
+ arguments: ["[content...]"],
16139
+ options: [
16140
+ { flags: "--target <target>", description: "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'" },
16141
+ { flags: "--send-draft", description: "Send the current saved draft after reviewing newer messages" },
16142
+ { flags: "--anyway", description: "Escape hatch: send a saved draft even if freshness re-check is still stale" },
16143
+ { flags: "--content <content>", description: "Unsupported. Pipe message content to stdin instead." },
16144
+ {
16145
+ flags: "--attachment-id <id>",
16146
+ description: "Attachment id to link (repeatable). Get one from `slock attachment upload`.",
16147
+ parse: (value, prev = []) => prev.concat(value)
16148
+ }
16149
+ ]
16150
+ },
16151
+ async (ctx, positionalContent, opts) => {
16152
+ if (!opts.target?.trim()) {
16153
+ throw cliError("INVALID_ARG", "--target is required");
15617
16154
  }
15618
- let ctx;
15619
16155
  try {
15620
- ctx = loadAgentContext();
16156
+ rejectArgContent(positionalContent, opts);
15621
16157
  } catch (err) {
15622
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16158
+ if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
15623
16159
  throw err;
15624
16160
  }
15625
- const client = new ApiClient(ctx);
15626
16161
  try {
15627
16162
  validateDraftSendFlags(opts);
15628
16163
  } catch (err) {
15629
- if (err instanceof SendContentError) fail(err.code, err.message);
16164
+ if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
15630
16165
  throw err;
15631
16166
  }
15632
16167
  let content;
15633
- let outgoingContent;
16168
+ let outgoingContent = "";
15634
16169
  let outgoingAttachmentIds = [];
15635
16170
  let previousDraftReholdCount = 0;
15636
16171
  let seenUpToSeq;
15637
16172
  if (opts.sendDraft) {
15638
- content = await resolveOptionalSendContent();
16173
+ content = await resolveOptionalSendContent(ctx.io.stdin ?? process.stdin);
15639
16174
  try {
15640
16175
  rejectSendDraftStdin(content, opts.target);
15641
16176
  } catch (err) {
15642
- if (err instanceof SendContentError) fail(err.code, err.message);
16177
+ if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
16178
+ throw err;
16179
+ }
16180
+ } else {
16181
+ try {
16182
+ content = await resolveSendContent(ctx.io.stdin ?? process.stdin);
16183
+ } catch (err) {
16184
+ if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
15643
16185
  throw err;
15644
16186
  }
15645
- const savedDraft = getSavedDraft(ctx.agentId, opts.target);
16187
+ outgoingContent = content;
16188
+ outgoingAttachmentIds = opts.attachmentId && opts.attachmentId.length > 0 ? opts.attachmentId : [];
16189
+ }
16190
+ const agentContext = ctx.loadAgentContext();
16191
+ const client = ctx.createApiClient(agentContext);
16192
+ if (opts.sendDraft) {
16193
+ const savedDraft = getSavedDraft(agentContext.agentId, opts.target);
15646
16194
  if (!savedDraft) {
15647
- fail(
16195
+ throw cliError(
15648
16196
  "SEND_DRAFT_NOT_FOUND",
15649
16197
  [
15650
16198
  "No saved draft exists for this target.",
@@ -15661,15 +16209,7 @@ function registerSendCommand(parent) {
15661
16209
  previousDraftReholdCount = savedDraft.reholdCount;
15662
16210
  seenUpToSeq = savedDraft.seenUpToSeq;
15663
16211
  } else {
15664
- try {
15665
- content = await resolveSendContent();
15666
- } catch (err) {
15667
- if (err instanceof SendContentError) fail(err.code, err.message);
15668
- throw err;
15669
- }
15670
- outgoingContent = content;
15671
- outgoingAttachmentIds = opts.attachmentId && opts.attachmentId.length > 0 ? opts.attachmentId : [];
15672
- const previousDraft = getSavedDraft(ctx.agentId, opts.target);
16212
+ const previousDraft = getSavedDraft(agentContext.agentId, opts.target);
15673
16213
  previousDraftReholdCount = previousDraft?.reholdCount ?? 0;
15674
16214
  seenUpToSeq = previousDraft?.seenUpToSeq;
15675
16215
  }
@@ -15692,26 +16232,26 @@ function registerSendCommand(parent) {
15692
16232
  }
15693
16233
  const res = await client.request(
15694
16234
  "POST",
15695
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/send`,
16235
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/send`,
15696
16236
  body
15697
16237
  );
15698
16238
  if (!res.ok) {
15699
16239
  const code = res.status >= 500 ? "SERVER_5XX" : "SEND_FAILED";
15700
- fail(code, res.error ?? `HTTP ${res.status}`);
16240
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
15701
16241
  }
15702
16242
  const data = res.data;
15703
16243
  if (data.state === "held") {
15704
- setSavedDraft(ctx.agentId, opts.target, {
16244
+ setSavedDraft(agentContext.agentId, opts.target, {
15705
16245
  content: outgoingContent,
15706
16246
  attachmentIds: outgoingAttachmentIds,
15707
16247
  savedAt: Date.now(),
15708
16248
  reholdCount: previousDraftReholdCount + 1,
15709
16249
  seenUpToSeq: data.seenUpToSeq
15710
16250
  });
15711
- process.stdout.write(formatHeldSendOutput(opts.target, data));
16251
+ writeText(ctx.io, formatHeldSendOutput(opts.target, data));
15712
16252
  return;
15713
16253
  }
15714
- clearSavedDraft(ctx.agentId, opts.target);
16254
+ clearSavedDraft(agentContext.agentId, opts.target);
15715
16255
  const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
15716
16256
  const replyHint = shortId ? ` (to reply in this message's thread, use target "${opts.target.includes(":") ? opts.target : opts.target + ":" + shortId}")` : "";
15717
16257
  let unreadSection = "";
@@ -15721,24 +16261,28 @@ function registerSendCommand(parent) {
15721
16261
  --- New messages you may have missed ---
15722
16262
  ${formatMessages(data.recentUnread)}`;
15723
16263
  }
15724
- process.stdout.write(`Message sent to ${opts.target}. Message ID: ${data.messageId}${replyHint}${unreadSection}
16264
+ writeText(ctx.io, `Message sent to ${opts.target}. Message ID: ${data.messageId}${replyHint}${unreadSection}
15725
16265
  `);
15726
- });
16266
+ }
16267
+ );
16268
+ function registerSendCommand(parent, runtimeOptions = {}) {
16269
+ registerCliCommand(parent, messageSendCommand, runtimeOptions);
15727
16270
  }
15728
16271
 
15729
16272
  // src/commands/message/_inbox.ts
15730
- async function drainInbox(ctx, opts) {
15731
- const client = new ApiClient(ctx);
16273
+ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
15732
16274
  const agentPath = `/internal/agent/${encodeURIComponent(ctx.agentId)}`;
15733
16275
  const failCode = opts.block ? "WAIT_FAILED" : "CHECK_FAILED";
15734
16276
  const query = [];
15735
16277
  if (opts.block) query.push("block=true");
15736
16278
  if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
15737
- const path4 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
15738
- const res = await client.request("GET", path4);
16279
+ const path6 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
16280
+ const res = await client.request("GET", path6);
15739
16281
  if (!res.ok) {
15740
- const code = res.status >= 500 ? "SERVER_5XX" : failCode;
15741
- fail(code, res.error ?? `HTTP ${res.status}`);
16282
+ throw new CliError({
16283
+ code: res.status >= 500 ? "SERVER_5XX" : failCode,
16284
+ message: res.error ?? `HTTP ${res.status}`
16285
+ });
15742
16286
  }
15743
16287
  const messages = res.data?.messages ?? [];
15744
16288
  if (ctx.clientMode === "managed-runner" || ctx.clientMode === "self-hosted-runner") {
@@ -15754,18 +16298,24 @@ async function drainInbox(ctx, opts) {
15754
16298
  }
15755
16299
 
15756
16300
  // src/commands/message/check.ts
15757
- function registerCheckCommand(parent) {
15758
- parent.command("check").description("Drain the agent inbox (non-blocking). Acks delivered seqs before returning.").action(async () => {
15759
- let ctx;
15760
- try {
15761
- ctx = loadAgentContext();
15762
- } catch (err) {
15763
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15764
- throw err;
15765
- }
15766
- const result = await drainInbox(ctx, { block: false });
15767
- process.stdout.write(formatMessages(result.messages) + "\n");
15768
- });
16301
+ var messageCheckCommand = defineCommand(
16302
+ {
16303
+ name: "check",
16304
+ description: "Drain the agent inbox (non-blocking). Acks delivered seqs before returning."
16305
+ },
16306
+ async (ctx) => {
16307
+ const agentContext = ctx.loadAgentContext();
16308
+ const result = await drainInbox(
16309
+ agentContext,
16310
+ { block: false },
16311
+ ctx.createApiClient(agentContext)
16312
+ );
16313
+ writeText(ctx.io, `${formatMessages(result.messages)}
16314
+ `);
16315
+ }
16316
+ );
16317
+ function registerCheckCommand(parent, runtimeOptions) {
16318
+ registerCliCommand(parent, messageCheckCommand, runtimeOptions);
15769
16319
  }
15770
16320
 
15771
16321
  // src/commands/message/read.ts
@@ -15773,39 +16323,80 @@ function parsePositiveInt(name, raw) {
15773
16323
  if (raw === void 0) return void 0;
15774
16324
  const n = Number(raw);
15775
16325
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
15776
- fail("INVALID_ARG", `--${name} must be a positive integer; got ${raw}`);
16326
+ throw new CliError({
16327
+ code: "INVALID_ARG",
16328
+ message: `--${name} must be a positive integer; got ${raw}`
16329
+ });
15777
16330
  }
15778
16331
  return n;
15779
16332
  }
15780
- function registerReadCommand(parent) {
15781
- parent.command("read").description("Read message history for a channel, DM, or thread").requiredOption("--channel <target>", "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'").option("--before <seq>", "Return messages strictly before this seq (paginate backwards)").option("--after <seq>", "Return messages strictly after this seq (paginate forwards)").option("--around <idOrSeq>", "Center the window on this messageId or seq").option("--limit <n>", "Max messages to return (server default applies if omitted)").action(async (opts) => {
15782
- let ctx;
15783
- try {
15784
- ctx = loadAgentContext();
15785
- } catch (err) {
15786
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15787
- throw err;
15788
- }
15789
- const before = parsePositiveInt("before", opts.before);
15790
- const after = parsePositiveInt("after", opts.after);
15791
- const limit = parsePositiveInt("limit", opts.limit);
15792
- const params = new URLSearchParams();
15793
- params.set("channel", opts.channel);
15794
- if (before !== void 0) params.set("before", String(before));
15795
- if (after !== void 0) params.set("after", String(after));
15796
- if (opts.around !== void 0) params.set("around", opts.around);
15797
- if (limit !== void 0) params.set("limit", String(limit));
15798
- const client = new ApiClient(ctx);
16333
+ function buildReadPath(agentId, opts) {
16334
+ const params = new URLSearchParams();
16335
+ params.set("channel", opts.channel);
16336
+ if (opts.before !== void 0) params.set("before", String(opts.before));
16337
+ if (opts.after !== void 0) params.set("after", String(opts.after));
16338
+ if (opts.around !== void 0) params.set("around", opts.around);
16339
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
16340
+ return `/internal/agent/${encodeURIComponent(agentId)}/history?${params.toString()}`;
16341
+ }
16342
+ function validateReadOpts(opts) {
16343
+ const channel = opts.channel?.trim();
16344
+ if (!channel) {
16345
+ throw new CliError({
16346
+ code: "INVALID_ARG",
16347
+ message: "--channel is required"
16348
+ });
16349
+ }
16350
+ const before = parsePositiveInt("before", opts.before);
16351
+ const after = parsePositiveInt("after", opts.after);
16352
+ const limit = parsePositiveInt("limit", opts.limit);
16353
+ return {
16354
+ channel,
16355
+ ...before !== void 0 ? { before } : {},
16356
+ ...after !== void 0 ? { after } : {},
16357
+ ...opts.around !== void 0 ? { around: opts.around } : {},
16358
+ ...limit !== void 0 ? { limit } : {}
16359
+ };
16360
+ }
16361
+ var messageReadCommand = defineCommand(
16362
+ {
16363
+ name: "read",
16364
+ description: "Read message history for a channel, DM, or thread",
16365
+ options: [
16366
+ { flags: "--channel <target>", description: "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'" },
16367
+ { flags: "--before <seq>", description: "Return messages strictly before this seq (paginate backwards)" },
16368
+ { flags: "--after <seq>", description: "Return messages strictly after this seq (paginate forwards)" },
16369
+ { flags: "--around <idOrSeq>", description: "Center the window on this messageId or seq" },
16370
+ { flags: "--limit <n>", description: "Max messages to return (server default applies if omitted)" }
16371
+ ]
16372
+ },
16373
+ async (ctx, opts) => {
16374
+ const readOpts = validateReadOpts(opts);
16375
+ const agentContext = ctx.loadAgentContext();
16376
+ const client = ctx.createApiClient(agentContext);
15799
16377
  const res = await client.request(
15800
16378
  "GET",
15801
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/history?${params.toString()}`
16379
+ buildReadPath(agentContext.agentId, readOpts)
15802
16380
  );
15803
16381
  if (!res.ok) {
15804
- const code = res.status >= 500 ? "SERVER_5XX" : "READ_FAILED";
15805
- fail(code, res.error ?? `HTTP ${res.status}`);
16382
+ throw new CliError({
16383
+ code: res.status >= 500 ? "SERVER_5XX" : "READ_FAILED",
16384
+ message: res.error ?? `HTTP ${res.status}`
16385
+ });
15806
16386
  }
15807
- process.stdout.write(formatHistory(opts.channel, res.data, { around: opts.around, after, before }) + "\n");
15808
- });
16387
+ writeText(
16388
+ ctx.io,
16389
+ `${formatHistory(readOpts.channel, res.data, {
16390
+ around: readOpts.around,
16391
+ after: readOpts.after,
16392
+ before: readOpts.before
16393
+ })}
16394
+ `
16395
+ );
16396
+ }
16397
+ );
16398
+ function registerReadCommand(parent, runtimeOptions) {
16399
+ registerCliCommand(parent, messageReadCommand, runtimeOptions);
15809
16400
  }
15810
16401
 
15811
16402
  // src/commands/message/search.ts
@@ -15813,56 +16404,116 @@ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
15813
16404
  function normalizeMemberHandleRef(raw) {
15814
16405
  const trimmed = raw.trim();
15815
16406
  if (!trimmed) {
15816
- fail("INVALID_ARG", "--sender must not be empty");
16407
+ throw new CliError({
16408
+ code: "INVALID_ARG",
16409
+ message: "--sender must not be empty"
16410
+ });
15817
16411
  }
15818
16412
  if (UUID_RE2.test(trimmed)) {
15819
- fail("INVALID_ARG", "--sender expects a member handle like @alice, not a UUID");
16413
+ throw new CliError({
16414
+ code: "INVALID_ARG",
16415
+ message: "--sender expects a member handle like @alice, not a UUID"
16416
+ });
15820
16417
  }
15821
16418
  const handle = trimmed.replace(/^@/, "").trim();
15822
16419
  if (!handle) {
15823
- fail("INVALID_ARG", "--sender handle must not be empty");
16420
+ throw new CliError({
16421
+ code: "INVALID_ARG",
16422
+ message: "--sender handle must not be empty"
16423
+ });
15824
16424
  }
15825
16425
  return handle;
15826
16426
  }
15827
- function registerSearchCommand(parent) {
15828
- parent.command("search").description("Search messages across channels the agent can see").requiredOption("--query <q>", "Search query string").option("--channel <target>", "Restrict to a single channel/DM/thread").option("--sender <handle>", "Restrict to messages by sender handle, e.g. @alice").option("--sort <mode>", "Sort results by relevance or recent (default: relevance)").option("--before <iso>", "Only messages before this ISO datetime").option("--after <iso>", "Only messages after this ISO datetime").option("--limit <n>", "Max results (server default applies if omitted)").action(async (opts) => {
15829
- let ctx;
15830
- try {
15831
- ctx = loadAgentContext();
15832
- } catch (err) {
15833
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15834
- throw err;
15835
- }
15836
- let limit;
15837
- if (opts.limit !== void 0) {
15838
- const n = Number(opts.limit);
15839
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
15840
- fail("INVALID_ARG", `--limit must be a positive integer; got ${opts.limit}`);
15841
- }
15842
- limit = n;
15843
- }
15844
- if (opts.sort !== void 0 && opts.sort !== "relevance" && opts.sort !== "recent") {
15845
- fail("INVALID_ARG", `--sort must be "relevance" or "recent"; got ${opts.sort}`);
15846
- }
15847
- const params = new URLSearchParams();
15848
- params.set("q", opts.query);
15849
- if (opts.channel) params.set("channel", opts.channel);
15850
- if (opts.sender) params.set("sender", normalizeMemberHandleRef(opts.sender));
15851
- if (opts.sort) params.set("sort", opts.sort);
15852
- if (opts.before) params.set("before", opts.before);
15853
- if (opts.after) params.set("after", opts.after);
15854
- if (limit !== void 0) params.set("limit", String(limit));
15855
- const client = new ApiClient(ctx);
16427
+ function parsePositiveInt2(name, raw) {
16428
+ if (raw === void 0) return void 0;
16429
+ const n = Number(raw);
16430
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16431
+ throw new CliError({
16432
+ code: "INVALID_ARG",
16433
+ message: `--${name} must be a positive integer; got ${raw}`
16434
+ });
16435
+ }
16436
+ return n;
16437
+ }
16438
+ function normalizeSearchOpts(opts) {
16439
+ const query = opts.query?.trim();
16440
+ if (!query) {
16441
+ throw new CliError({
16442
+ code: "INVALID_ARG",
16443
+ message: "--query is required"
16444
+ });
16445
+ }
16446
+ if (opts.sort !== void 0 && opts.sort !== "relevance" && opts.sort !== "recent") {
16447
+ throw new CliError({
16448
+ code: "INVALID_ARG",
16449
+ message: `--sort must be "relevance" or "recent"; got ${opts.sort}`
16450
+ });
16451
+ }
16452
+ const limit = parsePositiveInt2("limit", opts.limit);
16453
+ return {
16454
+ query,
16455
+ ...opts.channel ? { channel: opts.channel } : {},
16456
+ ...opts.sender ? { sender: normalizeMemberHandleRef(opts.sender) } : {},
16457
+ ...opts.sort ? { sort: opts.sort } : {},
16458
+ ...opts.before ? { before: opts.before } : {},
16459
+ ...opts.after ? { after: opts.after } : {},
16460
+ ...limit !== void 0 ? { limit } : {}
16461
+ };
16462
+ }
16463
+ function buildSearchPath(agentId, opts) {
16464
+ const params = new URLSearchParams();
16465
+ params.set("q", opts.query);
16466
+ if (opts.channel) params.set("channel", opts.channel);
16467
+ if (opts.sender) params.set("sender", opts.sender);
16468
+ if (opts.sort) params.set("sort", opts.sort);
16469
+ if (opts.before) params.set("before", opts.before);
16470
+ if (opts.after) params.set("after", opts.after);
16471
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
16472
+ return `/internal/agent/${encodeURIComponent(agentId)}/search?${params.toString()}`;
16473
+ }
16474
+ function toSearchErrorCode(errorCode2, status) {
16475
+ switch (errorCode2) {
16476
+ case "SCOPE_DENIED":
16477
+ case "INVALID_JSON_RESPONSE":
16478
+ return errorCode2;
16479
+ default:
16480
+ return status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED";
16481
+ }
16482
+ }
16483
+ var messageSearchCommand = defineCommand(
16484
+ {
16485
+ name: "search",
16486
+ description: "Search messages across channels the agent can see",
16487
+ options: [
16488
+ { flags: "--query <q>", description: "Search query string" },
16489
+ { flags: "--channel <target>", description: "Restrict to a single channel/DM/thread" },
16490
+ { flags: "--sender <handle>", description: "Restrict to messages by sender handle, e.g. @alice" },
16491
+ { flags: "--sort <mode>", description: "Sort results by relevance or recent (default: relevance)" },
16492
+ { flags: "--before <iso>", description: "Only messages before this ISO datetime" },
16493
+ { flags: "--after <iso>", description: "Only messages after this ISO datetime" },
16494
+ { flags: "--limit <n>", description: "Max results (server default applies if omitted)" }
16495
+ ]
16496
+ },
16497
+ async (ctx, opts) => {
16498
+ const searchOpts = normalizeSearchOpts(opts);
16499
+ const agentContext = ctx.loadAgentContext();
16500
+ const client = ctx.createApiClient(agentContext);
15856
16501
  const res = await client.request(
15857
16502
  "GET",
15858
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/search?${params.toString()}`
16503
+ buildSearchPath(agentContext.agentId, searchOpts)
15859
16504
  );
15860
16505
  if (!res.ok) {
15861
- const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED");
15862
- fail(code, res.error ?? `HTTP ${res.status}`);
16506
+ throw new CliError({
16507
+ code: toSearchErrorCode(res.errorCode, res.status),
16508
+ message: res.error ?? `HTTP ${res.status}`
16509
+ });
15863
16510
  }
15864
- process.stdout.write(formatSearchResults(opts.query, res.data) + "\n");
15865
- });
16511
+ writeText(ctx.io, `${formatSearchResults(searchOpts.query, res.data)}
16512
+ `);
16513
+ }
16514
+ );
16515
+ function registerSearchCommand(parent, runtimeOptions) {
16516
+ registerCliCommand(parent, messageSearchCommand, runtimeOptions);
15866
16517
  }
15867
16518
 
15868
16519
  // src/commands/message/react.ts
@@ -15873,40 +16524,48 @@ function normalizeReactionEmoji(value) {
15873
16524
  }
15874
16525
  return emoji3;
15875
16526
  }
15876
- function registerReactCommand(parent) {
15877
- parent.command("react").description("Add or remove your reaction on a message").requiredOption("--message-id <id>", "Message UUID to react to").requiredOption("--emoji <emoji>", "Reaction emoji").option("--remove", "Remove your reaction instead of adding it").addHelpText("after", [
15878
- "",
15879
- "Agent guidance:",
15880
- " Use reactions sparingly. Prefer acknowledgement/follow-up signals like \u{1F440}.",
15881
- " Do not auto-react to every merge, deploy, or task completion with celebratory emoji."
15882
- ].join("\n")).action(async (opts) => {
15883
- let ctx;
15884
- try {
15885
- ctx = loadAgentContext();
15886
- } catch (err) {
15887
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15888
- throw err;
16527
+ var messageReactCommand = defineCommand(
16528
+ {
16529
+ name: "react",
16530
+ description: "Add or remove your reaction on a message",
16531
+ options: [
16532
+ { flags: "--message-id <id>", description: "Message UUID to react to" },
16533
+ { flags: "--emoji <emoji>", description: "Reaction emoji" },
16534
+ { flags: "--remove", description: "Remove your reaction instead of adding it" }
16535
+ ],
16536
+ helpAfter: "\nAgent guidance:\n Use this only when a human explicitly asks for a reaction or when a reaction is a clear acknowledgement.\n Do not auto-react to every merge, deploy, task completion, or routine status update.\n"
16537
+ },
16538
+ async (ctx, opts) => {
16539
+ if (!opts.messageId?.trim()) {
16540
+ throw cliError("INVALID_ARG", "--message-id is required");
16541
+ }
16542
+ if (typeof opts.emoji !== "string") {
16543
+ throw cliError("INVALID_ARG", "--emoji is required");
15889
16544
  }
15890
16545
  let emoji3;
15891
16546
  try {
15892
16547
  emoji3 = normalizeReactionEmoji(opts.emoji);
15893
16548
  } catch (err) {
15894
- fail("INVALID_REACTION", err instanceof Error ? err.message : "Invalid reaction emoji");
16549
+ throw cliError("INVALID_REACTION", err instanceof Error ? err.message : "Invalid reaction emoji", { cause: err });
15895
16550
  }
15896
- const client = new ApiClient(ctx);
16551
+ const agentContext = ctx.loadAgentContext();
16552
+ const client = ctx.createApiClient(agentContext);
15897
16553
  const res = await client.request(
15898
16554
  opts.remove ? "DELETE" : "POST",
15899
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/messages/${encodeURIComponent(opts.messageId)}/reactions`,
16555
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/messages/${encodeURIComponent(opts.messageId)}/reactions`,
15900
16556
  { emoji: emoji3 }
15901
16557
  );
15902
16558
  if (!res.ok) {
15903
16559
  const code = res.status >= 500 ? "SERVER_5XX" : "REACT_FAILED";
15904
- fail(code, res.error ?? `HTTP ${res.status}`);
16560
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
15905
16561
  }
15906
16562
  const verb = opts.remove ? "removed from" : "added to";
15907
- process.stdout.write(`Reaction ${emoji3} ${verb} message ${opts.messageId.slice(0, 8)}.
16563
+ writeText(ctx.io, `Reaction ${emoji3} ${verb} message ${opts.messageId.slice(0, 8)}.
15908
16564
  `);
15909
- });
16565
+ }
16566
+ );
16567
+ function registerReactCommand(parent, runtimeOptions = {}) {
16568
+ registerCliCommand(parent, messageReactCommand, runtimeOptions);
15910
16569
  }
15911
16570
 
15912
16571
  // src/commands/attachment/upload.ts
@@ -15995,59 +16654,65 @@ function formatBytes(bytes) {
15995
16654
  }
15996
16655
  return `${unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)}${units[unitIndex]}`;
15997
16656
  }
15998
- function registerAttachmentUploadCommand(parent) {
15999
- parent.command("upload").description(`Upload a local file as an attachment (max ${MAX_ATTACHMENT_UPLOAD_LABEL})`).requiredOption("--path <filepath>", "Absolute path to the local file to upload").option(
16000
- "--channel <target>",
16001
- "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
16002
- ).option("--mime-type <type>", "Explicit MIME type override, e.g. image/png").action(async (opts) => {
16003
- let ctx;
16004
- try {
16005
- ctx = loadAgentContext();
16006
- } catch (err) {
16007
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16008
- throw err;
16657
+ var attachmentUploadCommand = defineCommand(
16658
+ {
16659
+ name: "upload",
16660
+ description: `Upload a local file as an attachment (max ${MAX_ATTACHMENT_UPLOAD_LABEL})`,
16661
+ options: [
16662
+ { flags: "--path <filepath>", description: "Absolute path to the local file to upload" },
16663
+ {
16664
+ flags: "--channel <target>",
16665
+ description: "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
16666
+ },
16667
+ { flags: "--mime-type <type>", description: "Explicit MIME type override, e.g. image/png" }
16668
+ ]
16669
+ },
16670
+ async (ctx, opts) => {
16671
+ if (typeof opts.path !== "string" || opts.path.length === 0) {
16672
+ throw cliError("INVALID_ARG", "--path is required");
16009
16673
  }
16010
16674
  if (!existsSync(opts.path)) {
16011
- fail("INVALID_ARG", `--path does not exist: ${opts.path}`);
16675
+ throw cliError("INVALID_ARG", `--path does not exist: ${opts.path}`);
16012
16676
  }
16013
16677
  const stat2 = statSync(opts.path);
16014
16678
  if (!stat2.isFile()) {
16015
- fail("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
16679
+ throw cliError("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
16016
16680
  }
16017
16681
  try {
16018
16682
  validateUploadFileSize(stat2.size);
16019
16683
  } catch (err) {
16020
- if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
16684
+ if (err instanceof AttachmentUploadArgError) throw cliError(err.code, err.message, { cause: err });
16021
16685
  throw err;
16022
16686
  }
16023
16687
  if (!opts.channel) {
16024
- fail(
16688
+ throw cliError(
16025
16689
  "MISSING_CHANNEL",
16026
16690
  "v0 server requires a channel to attach the upload to. Pass --channel '#name', 'dm:@peer', or a thread target."
16027
16691
  );
16028
16692
  }
16029
- const client = new ApiClient(ctx);
16030
- const agentPath = `/internal/agent/${encodeURIComponent(ctx.agentId)}`;
16031
- const resolved = await client.request(
16032
- "POST",
16033
- `${agentPath}/resolve-channel`,
16034
- { target: opts.channel }
16035
- );
16036
- if (!resolved.ok || !resolved.data?.channelId) {
16037
- const code = resolved.status >= 500 ? "SERVER_5XX" : "RESOLVE_FAILED";
16038
- fail(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
16039
- }
16040
- const channelId = resolved.data.channelId;
16041
16693
  const buffer = readFileSync2(opts.path);
16042
16694
  const filename = basename(opts.path);
16043
16695
  let explicitMimeType;
16044
16696
  try {
16045
16697
  explicitMimeType = normalizeExplicitMimeType(opts.mimeType);
16046
16698
  } catch (err) {
16047
- if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
16699
+ if (err instanceof AttachmentUploadArgError) throw cliError(err.code, err.message, { cause: err });
16048
16700
  throw err;
16049
16701
  }
16050
16702
  const uploadMimeType = inferUploadMimeType(filename, buffer, explicitMimeType);
16703
+ const agentContext = ctx.loadAgentContext();
16704
+ const client = ctx.createApiClient(agentContext);
16705
+ const agentPath = `/internal/agent/${encodeURIComponent(agentContext.agentId)}`;
16706
+ const resolved = await client.request(
16707
+ "POST",
16708
+ `${agentPath}/resolve-channel`,
16709
+ { target: opts.channel }
16710
+ );
16711
+ if (!resolved.ok || !resolved.data?.channelId) {
16712
+ const code = resolved.status >= 500 ? "SERVER_5XX" : "RESOLVE_FAILED";
16713
+ throw cliError(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
16714
+ }
16715
+ const channelId = resolved.data.channelId;
16051
16716
  const blob = new Blob([buffer], { type: uploadMimeType });
16052
16717
  const form = new FormData();
16053
16718
  form.append("file", blob, filename);
@@ -16062,42 +16727,70 @@ function registerAttachmentUploadCommand(parent) {
16062
16727
  );
16063
16728
  if (!res.ok) {
16064
16729
  const code = res.status >= 500 ? "SERVER_5XX" : "UPLOAD_FAILED";
16065
- fail(code, res.error ?? `HTTP ${res.status}`);
16730
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16066
16731
  }
16067
16732
  const d = res.data;
16068
- process.stdout.write(`File uploaded: ${d.filename} (${(d.sizeBytes / 1024).toFixed(1)}KB)
16733
+ writeText(ctx.io, `File uploaded: ${d.filename} (${(d.sizeBytes / 1024).toFixed(1)}KB)
16069
16734
  Attachment ID: ${d.id}
16070
16735
 
16071
16736
  Use this ID with slock message send --attachment-id ${d.id} to include it in a message.
16072
16737
  `);
16073
- });
16738
+ }
16739
+ );
16740
+ function registerAttachmentUploadCommand(parent, runtimeOptions = {}) {
16741
+ registerCliCommand(parent, attachmentUploadCommand, runtimeOptions);
16074
16742
  }
16075
16743
 
16076
16744
  // src/commands/attachment/view.ts
16077
16745
  import { writeFileSync } from "fs";
16078
- function registerAttachmentViewCommand(parent) {
16079
- parent.command("view").description("Download an attachment by id and save it to a local path").requiredOption("--id <attachmentId>", "Attachment UUID").requiredOption("--output <path>", "Local path to write the file to").action(async (opts) => {
16080
- let ctx;
16081
- try {
16082
- ctx = loadAgentContext();
16083
- } catch (err) {
16084
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16085
- throw err;
16086
- }
16087
- const client = new ApiClient(ctx);
16746
+ function validateViewOpts(opts) {
16747
+ const id = opts.id?.trim();
16748
+ const output = opts.output;
16749
+ if (!id) {
16750
+ throw new CliError({
16751
+ code: "INVALID_ARG",
16752
+ message: "--id is required"
16753
+ });
16754
+ }
16755
+ if (!output) {
16756
+ throw new CliError({
16757
+ code: "INVALID_ARG",
16758
+ message: "--output is required"
16759
+ });
16760
+ }
16761
+ return { id, output };
16762
+ }
16763
+ var attachmentViewCommand = defineCommand(
16764
+ {
16765
+ name: "view",
16766
+ description: "Download an attachment by id and save it to a local path",
16767
+ options: [
16768
+ { flags: "--id <attachmentId>", description: "Attachment UUID" },
16769
+ { flags: "--output <path>", description: "Local path to write the file to" }
16770
+ ]
16771
+ },
16772
+ async (ctx, opts) => {
16773
+ const { id, output } = validateViewOpts(opts);
16774
+ const agentContext = ctx.loadAgentContext();
16775
+ const client = ctx.createApiClient(agentContext);
16088
16776
  const res = await client.requestRaw(
16089
16777
  "GET",
16090
- `/api/attachments/${encodeURIComponent(opts.id)}`
16778
+ `/api/attachments/${encodeURIComponent(id)}`
16091
16779
  );
16092
16780
  if (!res.ok) {
16093
- const code = res.status >= 500 ? "SERVER_5XX" : "VIEW_FAILED";
16094
- fail(code, res.error ?? `HTTP ${res.status}`);
16781
+ throw new CliError({
16782
+ code: res.status >= 500 ? "SERVER_5XX" : "VIEW_FAILED",
16783
+ message: res.error ?? `HTTP ${res.status}`
16784
+ });
16095
16785
  }
16096
16786
  const buffer = Buffer.from(await res.response.arrayBuffer());
16097
- writeFileSync(opts.output, buffer);
16098
- process.stdout.write(`Downloaded to: ${opts.output}
16787
+ writeFileSync(output, buffer);
16788
+ writeText(ctx.io, `Downloaded to: ${output}
16099
16789
  `);
16100
- });
16790
+ }
16791
+ );
16792
+ function registerAttachmentViewCommand(parent, runtimeOptions) {
16793
+ registerCliCommand(parent, attachmentViewCommand, runtimeOptions);
16101
16794
  }
16102
16795
 
16103
16796
  // src/commands/task/_format.ts
@@ -16155,188 +16848,288 @@ function formatTaskStatusUpdated(taskNumber, status) {
16155
16848
 
16156
16849
  // src/commands/task/list.ts
16157
16850
  var VALID_STATUSES = /* @__PURE__ */ new Set(["all", "todo", "in_progress", "in_review", "done", "closed"]);
16158
- function registerTaskListCommand(parent) {
16159
- parent.command("list").description("List tasks in a channel").requiredOption("--channel <target>", "Channel target: '#channel'").option("--status <s>", "Filter: all|todo|in_progress|in_review|done (default: server-side)").action(async (opts) => {
16160
- let ctx;
16161
- try {
16162
- ctx = loadAgentContext();
16163
- } catch (err) {
16164
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16165
- throw err;
16166
- }
16167
- if (opts.status && !VALID_STATUSES.has(opts.status)) {
16168
- fail("INVALID_ARG", `--status must be one of ${Array.from(VALID_STATUSES).join("|")}; got ${opts.status}`);
16169
- }
16170
- const params = new URLSearchParams();
16171
- params.set("channel", opts.channel);
16172
- if (opts.status) params.set("status", opts.status);
16173
- const client = new ApiClient(ctx);
16851
+ function buildTaskListPath(agentId, opts) {
16852
+ const params = new URLSearchParams();
16853
+ params.set("channel", opts.channel);
16854
+ if (opts.status) params.set("status", opts.status);
16855
+ return `/internal/agent/${encodeURIComponent(agentId)}/tasks?${params.toString()}`;
16856
+ }
16857
+ function validateListOpts(opts) {
16858
+ const channel = opts.channel?.trim();
16859
+ if (!channel) {
16860
+ throw new CliError({
16861
+ code: "INVALID_ARG",
16862
+ message: "--channel is required"
16863
+ });
16864
+ }
16865
+ if (opts.status && !VALID_STATUSES.has(opts.status)) {
16866
+ throw new CliError({
16867
+ code: "INVALID_ARG",
16868
+ message: `--status must be one of ${Array.from(VALID_STATUSES).join("|")}; got ${opts.status}`
16869
+ });
16870
+ }
16871
+ return {
16872
+ channel,
16873
+ ...opts.status ? { status: opts.status } : {}
16874
+ };
16875
+ }
16876
+ var taskListCommand = defineCommand(
16877
+ {
16878
+ name: "list",
16879
+ description: "List tasks in a channel",
16880
+ options: [
16881
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
16882
+ { flags: "--status <s>", description: "Filter: all|todo|in_progress|in_review|done (default: server-side)" }
16883
+ ]
16884
+ },
16885
+ async (ctx, opts) => {
16886
+ const listOpts = validateListOpts(opts);
16887
+ const agentContext = ctx.loadAgentContext();
16888
+ const client = ctx.createApiClient(agentContext);
16174
16889
  const res = await client.request(
16175
16890
  "GET",
16176
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks?${params.toString()}`
16891
+ buildTaskListPath(agentContext.agentId, listOpts)
16177
16892
  );
16178
16893
  if (!res.ok) {
16179
- const code = res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED";
16180
- fail(code, res.error ?? `HTTP ${res.status}`);
16894
+ throw new CliError({
16895
+ code: res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED",
16896
+ message: res.error ?? `HTTP ${res.status}`
16897
+ });
16181
16898
  }
16182
- process.stdout.write(formatTaskList(opts.channel, res.data, opts.status) + "\n");
16183
- });
16899
+ writeText(ctx.io, `${formatTaskList(listOpts.channel, res.data, listOpts.status)}
16900
+ `);
16901
+ }
16902
+ );
16903
+ function registerTaskListCommand(parent, runtimeOptions) {
16904
+ registerCliCommand(parent, taskListCommand, runtimeOptions);
16184
16905
  }
16185
16906
 
16186
16907
  // src/commands/task/create.ts
16187
- function registerTaskCreateCommand(parent) {
16188
- parent.command("create").description("Create one or more tasks in a channel").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption(
16189
- "--title <title>",
16190
- "Task title (repeatable for batch create)",
16191
- (value, prev = []) => prev.concat(value)
16192
- ).action(async (opts) => {
16193
- let ctx;
16194
- try {
16195
- ctx = loadAgentContext();
16196
- } catch (err) {
16197
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16198
- throw err;
16908
+ var taskCreateCommand = defineCommand(
16909
+ {
16910
+ name: "create",
16911
+ description: "Create one or more tasks in a channel",
16912
+ options: [
16913
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
16914
+ {
16915
+ flags: "--title <title>",
16916
+ description: "Task title (repeatable for batch create)",
16917
+ parse: (value, prev = []) => prev.concat(value)
16918
+ }
16919
+ ]
16920
+ },
16921
+ async (ctx, opts) => {
16922
+ if (!opts.channel?.trim()) {
16923
+ throw cliError("INVALID_ARG", "--channel is required");
16199
16924
  }
16200
16925
  const titles = opts.title ?? [];
16201
- if (titles.length === 0) fail("INVALID_ARG", "--title is required (at least one)");
16202
- const client = new ApiClient(ctx);
16926
+ if (titles.length === 0) throw cliError("INVALID_ARG", "--title is required (at least one)");
16927
+ const agentContext = ctx.loadAgentContext();
16928
+ const client = ctx.createApiClient(agentContext);
16203
16929
  const res = await client.request(
16204
16930
  "POST",
16205
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks`,
16931
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks`,
16206
16932
  { channel: opts.channel, tasks: titles.map((title) => ({ title })) }
16207
16933
  );
16208
16934
  if (!res.ok) {
16209
16935
  const code = res.status >= 500 ? "SERVER_5XX" : "CREATE_FAILED";
16210
- fail(code, res.error ?? `HTTP ${res.status}`);
16936
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16211
16937
  }
16212
- process.stdout.write(formatTasksCreated(opts.channel, res.data) + "\n");
16213
- });
16938
+ writeText(ctx.io, formatTasksCreated(opts.channel, res.data) + "\n");
16939
+ }
16940
+ );
16941
+ function registerTaskCreateCommand(parent, runtimeOptions = {}) {
16942
+ registerCliCommand(parent, taskCreateCommand, runtimeOptions);
16214
16943
  }
16215
16944
 
16216
16945
  // src/commands/task/claim.ts
16217
- function registerTaskClaimCommand(parent) {
16218
- parent.command("claim").description("Claim one or more tasks (by task number or message id)").requiredOption("--channel <target>", "Channel target: '#channel'").option(
16219
- "--number <n>",
16220
- "Task number to claim (repeatable)",
16221
- (value, prev = []) => prev.concat(value)
16222
- ).option(
16223
- "--message-id <id>",
16224
- "Message id (full or short) to claim (repeatable)",
16225
- (value, prev = []) => prev.concat(value)
16226
- ).action(async (opts) => {
16227
- let ctx;
16228
- try {
16229
- ctx = loadAgentContext();
16230
- } catch (err) {
16231
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16232
- throw err;
16946
+ var taskClaimCommand = defineCommand(
16947
+ {
16948
+ name: "claim",
16949
+ description: "Claim one or more tasks (by task number or message id)",
16950
+ options: [
16951
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
16952
+ {
16953
+ flags: "--number <n>",
16954
+ description: "Task number to claim (repeatable)",
16955
+ parse: (value, prev = []) => prev.concat(value)
16956
+ },
16957
+ {
16958
+ flags: "--message-id <id>",
16959
+ description: "Message id (full or short) to claim (repeatable)",
16960
+ parse: (value, prev = []) => prev.concat(value)
16961
+ }
16962
+ ]
16963
+ },
16964
+ async (ctx, opts) => {
16965
+ if (!opts.channel?.trim()) {
16966
+ throw cliError("INVALID_ARG", "--channel is required");
16233
16967
  }
16234
16968
  const numbers = (opts.number ?? []).map((raw) => {
16235
16969
  const n = Number(raw);
16236
16970
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16237
- fail("INVALID_ARG", `--number must be a positive integer; got ${raw}`);
16971
+ throw cliError("INVALID_ARG", `--number must be a positive integer; got ${raw}`);
16238
16972
  }
16239
16973
  return n;
16240
16974
  });
16241
16975
  const messageIds = opts.messageId ?? [];
16242
16976
  if (numbers.length === 0 && messageIds.length === 0) {
16243
- fail("INVALID_ARG", "Provide at least one --number or --message-id");
16977
+ throw cliError("INVALID_ARG", "Provide at least one --number or --message-id");
16244
16978
  }
16245
16979
  const body = { channel: opts.channel };
16246
16980
  if (numbers.length > 0) body.task_numbers = numbers;
16247
16981
  if (messageIds.length > 0) body.message_ids = messageIds;
16248
- const client = new ApiClient(ctx);
16982
+ const agentContext = ctx.loadAgentContext();
16983
+ const client = ctx.createApiClient(agentContext);
16249
16984
  const res = await client.request(
16250
16985
  "POST",
16251
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks/claim`,
16986
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/claim`,
16252
16987
  body
16253
16988
  );
16254
16989
  if (!res.ok) {
16255
16990
  const code = res.status >= 500 ? "SERVER_5XX" : "CLAIM_FAILED";
16256
- fail(code, res.error ?? `HTTP ${res.status}`);
16991
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16257
16992
  }
16258
16993
  if (isFreshnessHeldResponse(res.data)) {
16259
- process.stdout.write(formatFreshnessHoldOutput(opts.channel, res.data, {
16994
+ writeText(ctx.io, formatFreshnessHoldOutput(opts.channel, res.data, {
16260
16995
  heldAction: "Your task claim was not applied.",
16261
16996
  draftInstructions: "After reviewing the newer context, rerun the task claim command if it is still correct.\n"
16262
16997
  }));
16263
16998
  return;
16264
16999
  }
16265
- process.stdout.write(formatClaimResults(opts.channel, res.data) + "\n");
16266
- });
17000
+ writeText(ctx.io, formatClaimResults(opts.channel, res.data) + "\n");
17001
+ }
17002
+ );
17003
+ function registerTaskClaimCommand(parent, runtimeOptions = {}) {
17004
+ registerCliCommand(parent, taskClaimCommand, runtimeOptions);
16267
17005
  }
16268
17006
 
16269
17007
  // src/commands/task/unclaim.ts
16270
- function registerTaskUnclaimCommand(parent) {
16271
- parent.command("unclaim").description("Release a previously-claimed task").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption("--number <n>", "Task number to unclaim").action(async (opts) => {
16272
- let ctx;
16273
- try {
16274
- ctx = loadAgentContext();
16275
- } catch (err) {
16276
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16277
- throw err;
16278
- }
16279
- const n = Number(opts.number);
16280
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16281
- fail("INVALID_ARG", `--number must be a positive integer; got ${opts.number}`);
16282
- }
16283
- const client = new ApiClient(ctx);
17008
+ function parseTaskNumber(raw) {
17009
+ const n = Number(raw);
17010
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
17011
+ throw new CliError({
17012
+ code: "INVALID_ARG",
17013
+ message: `--number must be a positive integer; got ${raw}`
17014
+ });
17015
+ }
17016
+ return n;
17017
+ }
17018
+ function validateUnclaimOpts(opts) {
17019
+ const channel = opts.channel?.trim();
17020
+ if (!channel) {
17021
+ throw new CliError({
17022
+ code: "INVALID_ARG",
17023
+ message: "--channel is required"
17024
+ });
17025
+ }
17026
+ return { channel, taskNumber: parseTaskNumber(opts.number) };
17027
+ }
17028
+ var taskUnclaimCommand = defineCommand(
17029
+ {
17030
+ name: "unclaim",
17031
+ description: "Release a previously-claimed task",
17032
+ options: [
17033
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
17034
+ { flags: "--number <n>", description: "Task number to unclaim" }
17035
+ ]
17036
+ },
17037
+ async (ctx, opts) => {
17038
+ const { channel, taskNumber } = validateUnclaimOpts(opts);
17039
+ const agentContext = ctx.loadAgentContext();
17040
+ const client = ctx.createApiClient(agentContext);
16284
17041
  const res = await client.request(
16285
17042
  "POST",
16286
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks/unclaim`,
16287
- { channel: opts.channel, task_number: n }
17043
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/unclaim`,
17044
+ { channel, task_number: taskNumber }
16288
17045
  );
16289
17046
  if (!res.ok) {
16290
- const code = res.status >= 500 ? "SERVER_5XX" : "UNCLAIM_FAILED";
16291
- fail(code, res.error ?? `HTTP ${res.status}`);
17047
+ throw new CliError({
17048
+ code: res.status >= 500 ? "SERVER_5XX" : "UNCLAIM_FAILED",
17049
+ message: res.error ?? `HTTP ${res.status}`
17050
+ });
16292
17051
  }
16293
- process.stdout.write(formatTaskUnclaimed(n) + "\n");
16294
- });
17052
+ writeText(ctx.io, `${formatTaskUnclaimed(taskNumber)}
17053
+ `);
17054
+ }
17055
+ );
17056
+ function registerTaskUnclaimCommand(parent, runtimeOptions) {
17057
+ registerCliCommand(parent, taskUnclaimCommand, runtimeOptions);
16295
17058
  }
16296
17059
 
16297
17060
  // src/commands/task/update.ts
16298
17061
  var STATUSES = ["todo", "in_progress", "in_review", "done", "closed"];
16299
- function registerTaskUpdateCommand(parent) {
16300
- parent.command("update").description("Update task status").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption("--number <n>", "Task number to update").requiredOption(
16301
- "--status <status>",
16302
- `New status. One of: ${STATUSES.join(", ")}`
16303
- ).action(async (opts) => {
16304
- let ctx;
16305
- try {
16306
- ctx = loadAgentContext();
16307
- } catch (err) {
16308
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16309
- throw err;
16310
- }
16311
- const n = Number(opts.number);
16312
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16313
- fail("INVALID_ARG", `--number must be a positive integer; got ${opts.number}`);
16314
- }
16315
- if (!STATUSES.includes(opts.status)) {
16316
- fail(
16317
- "INVALID_ARG",
16318
- `--status must be one of: ${STATUSES.join(", ")}; got ${opts.status}`
16319
- );
16320
- }
16321
- const client = new ApiClient(ctx);
17062
+ function parseTaskNumber2(raw) {
17063
+ const n = Number(raw);
17064
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
17065
+ throw new CliError({
17066
+ code: "INVALID_ARG",
17067
+ message: `--number must be a positive integer; got ${raw}`
17068
+ });
17069
+ }
17070
+ return n;
17071
+ }
17072
+ function parseStatus(raw) {
17073
+ if (!raw || !STATUSES.includes(raw)) {
17074
+ throw new CliError({
17075
+ code: "INVALID_ARG",
17076
+ message: `--status must be one of: ${STATUSES.join(", ")}; got ${raw}`
17077
+ });
17078
+ }
17079
+ return raw;
17080
+ }
17081
+ function validateUpdateOpts(opts) {
17082
+ const channel = opts.channel?.trim();
17083
+ if (!channel) {
17084
+ throw new CliError({
17085
+ code: "INVALID_ARG",
17086
+ message: "--channel is required"
17087
+ });
17088
+ }
17089
+ return {
17090
+ channel,
17091
+ taskNumber: parseTaskNumber2(opts.number),
17092
+ status: parseStatus(opts.status)
17093
+ };
17094
+ }
17095
+ var taskUpdateCommand = defineCommand(
17096
+ {
17097
+ name: "update",
17098
+ description: "Update task status",
17099
+ options: [
17100
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
17101
+ { flags: "--number <n>", description: "Task number to update" },
17102
+ { flags: "--status <status>", description: `New status. One of: ${STATUSES.join(", ")}` }
17103
+ ]
17104
+ },
17105
+ async (ctx, opts) => {
17106
+ const { channel, taskNumber, status } = validateUpdateOpts(opts);
17107
+ const agentContext = ctx.loadAgentContext();
17108
+ const client = ctx.createApiClient(agentContext);
16322
17109
  const res = await client.request(
16323
17110
  "POST",
16324
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks/update-status`,
16325
- { channel: opts.channel, task_number: n, status: opts.status }
17111
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/update-status`,
17112
+ { channel, task_number: taskNumber, status }
16326
17113
  );
16327
17114
  if (!res.ok) {
16328
- const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
16329
- fail(code, res.error ?? `HTTP ${res.status}`);
17115
+ throw new CliError({
17116
+ code: res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED",
17117
+ message: res.error ?? `HTTP ${res.status}`
17118
+ });
16330
17119
  }
16331
17120
  if (isFreshnessHeldResponse(res.data)) {
16332
- process.stdout.write(formatFreshnessHoldOutput(opts.channel, res.data, {
17121
+ writeText(ctx.io, formatFreshnessHoldOutput(channel, res.data, {
16333
17122
  heldAction: "Your task status update was not applied.",
16334
17123
  draftInstructions: "After reviewing the newer context, rerun the task update command if it is still correct.\n"
16335
17124
  }));
16336
17125
  return;
16337
17126
  }
16338
- process.stdout.write(formatTaskStatusUpdated(n, opts.status) + "\n");
16339
- });
17127
+ writeText(ctx.io, `${formatTaskStatusUpdated(taskNumber, status)}
17128
+ `);
17129
+ }
17130
+ );
17131
+ function registerTaskUpdateCommand(parent, runtimeOptions) {
17132
+ registerCliCommand(parent, taskUpdateCommand, runtimeOptions);
16340
17133
  }
16341
17134
 
16342
17135
  // src/commands/profile/_format.ts
@@ -16403,39 +17196,57 @@ function normalizeTarget(target) {
16403
17196
  if (target === void 0) return null;
16404
17197
  const trimmed = target.trim();
16405
17198
  if (!trimmed) {
16406
- fail("INVALID_ARG", "profile target must not be empty");
17199
+ throw new CliError({
17200
+ code: "INVALID_ARG",
17201
+ message: "profile target must not be empty"
17202
+ });
16407
17203
  }
16408
17204
  if (!trimmed.startsWith("@")) {
16409
- fail("INVALID_ARG", "profile target must start with @");
17205
+ throw new CliError({
17206
+ code: "INVALID_ARG",
17207
+ message: "profile target must start with @"
17208
+ });
16410
17209
  }
16411
17210
  return trimmed;
16412
17211
  }
16413
- function registerProfileShowCommand(parent) {
16414
- parent.command("show").description("Show a profile. Omit the target to show your own profile.").argument("[target]", "Handle like @alice; omit to show your own profile").option("--json", "Emit machine-readable JSON").action(async (target, opts) => {
16415
- let ctx;
16416
- try {
16417
- ctx = loadAgentContext();
16418
- } catch (err) {
16419
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16420
- throw err;
16421
- }
17212
+ function buildProfileShowPath(agentId, target) {
17213
+ const base = `/internal/agent/${encodeURIComponent(agentId)}/profile`;
17214
+ if (!target) return base;
17215
+ const params = new URLSearchParams();
17216
+ params.set("target", target);
17217
+ return `${base}?${params.toString()}`;
17218
+ }
17219
+ var profileShowCommand = defineCommand(
17220
+ {
17221
+ name: "show",
17222
+ description: "Show a profile. Omit the target to show your own profile.",
17223
+ arguments: ["[target]"],
17224
+ options: [{ flags: "--json", description: "Emit machine-readable JSON" }]
17225
+ },
17226
+ async (ctx, target, opts = {}) => {
17227
+ const agentContext = ctx.loadAgentContext();
17228
+ const client = ctx.createApiClient(agentContext);
16422
17229
  const normalizedTarget = normalizeTarget(target);
16423
- const params = new URLSearchParams();
16424
- if (normalizedTarget) params.set("target", normalizedTarget);
16425
- const client = new ApiClient(ctx);
16426
- const pathname = params.size > 0 ? `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile?${params.toString()}` : `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile`;
16427
- const res = await client.request("GET", pathname);
17230
+ const res = await client.request(
17231
+ "GET",
17232
+ buildProfileShowPath(agentContext.agentId, normalizedTarget)
17233
+ );
16428
17234
  if (!res.ok || !res.data) {
16429
- const code = res.status >= 500 ? "SERVER_5XX" : "PROFILE_SHOW_FAILED";
16430
- fail(code, res.error ?? `HTTP ${res.status}`);
17235
+ throw new CliError({
17236
+ code: res.status >= 500 ? "SERVER_5XX" : "PROFILE_SHOW_FAILED",
17237
+ message: res.error ?? `HTTP ${res.status}`
17238
+ });
16431
17239
  }
16432
17240
  if (opts.json) {
16433
- emit({ ok: true, data: res.data });
17241
+ writeJson(ctx.io, { ok: true, data: res.data });
16434
17242
  return;
16435
17243
  }
16436
- process.stdout.write(`${formatProfile(res.data)}
17244
+ writeText(ctx.io, `${formatProfile(res.data)}
16437
17245
  `);
16438
- });
17246
+ }
17247
+ );
17248
+ function registerProfileShowCommand(parent, runtimeOptions) {
17249
+ registerCliCommand(parent, profileShowCommand, runtimeOptions);
16439
17250
  }
16440
17251
 
16441
17252
  // src/commands/profile/update.ts
@@ -16477,14 +17288,14 @@ function inferImageMimeType(filename, buffer) {
16477
17288
  }
16478
17289
  function readAvatarFile(avatarFile) {
16479
17290
  if (!existsSync2(avatarFile)) {
16480
- fail("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
17291
+ throw cliError("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
16481
17292
  }
16482
17293
  const stat2 = statSync2(avatarFile);
16483
17294
  if (!stat2.isFile()) {
16484
- fail("PROFILE_FILE_NOT_FOUND", `Avatar file is not a regular file: ${avatarFile}`);
17295
+ throw cliError("PROFILE_FILE_NOT_FOUND", `Avatar file is not a regular file: ${avatarFile}`);
16485
17296
  }
16486
17297
  if (stat2.size > MAX_PROFILE_AVATAR_BYTES) {
16487
- fail(
17298
+ throw cliError(
16488
17299
  "PROFILE_AVATAR_TOO_LARGE",
16489
17300
  `Avatar file is ${stat2.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
16490
17301
  );
@@ -16493,7 +17304,7 @@ function readAvatarFile(avatarFile) {
16493
17304
  const filename = basename2(avatarFile);
16494
17305
  const mimeType = inferImageMimeType(filename, buffer);
16495
17306
  if (!mimeType || !PROFILE_AVATAR_MIME_TYPES.has(mimeType)) {
16496
- fail(
17307
+ throw cliError(
16497
17308
  "PROFILE_AVATAR_BAD_FORMAT",
16498
17309
  "Avatar must be a JPEG, PNG, GIF, or WebP image"
16499
17310
  );
@@ -16503,31 +17314,35 @@ function readAvatarFile(avatarFile) {
16503
17314
  function normalizeAvatarUrl(avatarUrl) {
16504
17315
  const trimmed = avatarUrl.trim();
16505
17316
  if (trimmed.length === 0) {
16506
- fail("INVALID_ARG", "--avatar-url must not be empty");
17317
+ throw cliError("INVALID_ARG", "--avatar-url must not be empty");
16507
17318
  }
16508
17319
  if (!trimmed.startsWith("pixel:")) {
16509
- fail("INVALID_ARG", "--avatar-url currently supports only pixel avatar URLs; use --avatar-file for image uploads");
17320
+ throw cliError("INVALID_ARG", "--avatar-url currently supports only pixel avatar URLs; use --avatar-file for image uploads");
16510
17321
  }
16511
17322
  return trimmed;
16512
17323
  }
16513
- function registerProfileUpdateCommand(parent) {
16514
- parent.command("update").description("Update your own profile").option("--avatar-file <path>", "Path to a local image file to use as your avatar").option("--avatar-url <value>", "Set a pixel avatar URL such as pixel:random:<seed>").option("--display-name <name>", "Set your display name (non-empty)").option("--description <text>", "Set your profile description (non-empty)").option("--json", "Emit machine-readable JSON").action(async (opts) => {
16515
- let ctx;
16516
- try {
16517
- ctx = loadAgentContext();
16518
- } catch (err) {
16519
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16520
- throw err;
16521
- }
17324
+ var profileUpdateCommand = defineCommand(
17325
+ {
17326
+ name: "update",
17327
+ description: "Update your own profile",
17328
+ options: [
17329
+ { flags: "--avatar-file <path>", description: "Path to a local image file to use as your avatar" },
17330
+ { flags: "--avatar-url <value>", description: "Set a pixel avatar URL such as pixel:random:<seed>" },
17331
+ { flags: "--display-name <name>", description: "Set your display name (non-empty)" },
17332
+ { flags: "--description <text>", description: "Set your profile description (non-empty)" },
17333
+ { flags: "--json", description: "Emit machine-readable JSON" }
17334
+ ]
17335
+ },
17336
+ async (ctx, opts) => {
16522
17337
  const hasAvatar = opts.avatarFile !== void 0;
16523
17338
  const hasAvatarUrl = opts.avatarUrl !== void 0;
16524
17339
  const hasDisplayName = opts.displayName !== void 0;
16525
17340
  const hasDescription = opts.description !== void 0;
16526
17341
  if (!hasAvatar && !hasAvatarUrl && !hasDisplayName && !hasDescription) {
16527
- fail("INVALID_ARG", "Provide at least one of --avatar-file, --avatar-url, --display-name, or --description");
17342
+ throw cliError("INVALID_ARG", "Provide at least one of --avatar-file, --avatar-url, --display-name, or --description");
16528
17343
  }
16529
17344
  if (hasAvatar && hasAvatarUrl) {
16530
- fail("INVALID_ARG", "Use either --avatar-file or --avatar-url, not both");
17345
+ throw cliError("INVALID_ARG", "Use either --avatar-file or --avatar-url, not both");
16531
17346
  }
16532
17347
  let normalizedAvatarUrl;
16533
17348
  if (hasAvatarUrl) {
@@ -16537,21 +17352,23 @@ function registerProfileUpdateCommand(parent) {
16537
17352
  if (hasDisplayName) {
16538
17353
  trimmedDisplayName = opts.displayName.trim();
16539
17354
  if (trimmedDisplayName.length === 0) {
16540
- fail("INVALID_ARG", "--display-name must not be empty");
17355
+ throw cliError("INVALID_ARG", "--display-name must not be empty");
16541
17356
  }
16542
17357
  if (trimmedDisplayName.length > MAX_PROFILE_DISPLAY_NAME_LENGTH) {
16543
- fail("INVALID_ARG", `--display-name must be at most ${MAX_PROFILE_DISPLAY_NAME_LENGTH} characters`);
17358
+ throw cliError("INVALID_ARG", `--display-name must be at most ${MAX_PROFILE_DISPLAY_NAME_LENGTH} characters`);
16544
17359
  }
16545
17360
  }
16546
17361
  if (hasDescription) {
16547
17362
  if (opts.description.length === 0) {
16548
- fail("INVALID_ARG", "--description must not be empty");
17363
+ throw cliError("INVALID_ARG", "--description must not be empty");
16549
17364
  }
16550
17365
  if (opts.description.length > MAX_PROFILE_DESCRIPTION_LENGTH) {
16551
- fail("INVALID_ARG", `--description must be at most ${MAX_PROFILE_DESCRIPTION_LENGTH} characters`);
17366
+ throw cliError("INVALID_ARG", `--description must be at most ${MAX_PROFILE_DESCRIPTION_LENGTH} characters`);
16552
17367
  }
16553
17368
  }
16554
- const client = new ApiClient(ctx);
17369
+ const avatar = hasAvatar ? readAvatarFile(opts.avatarFile) : null;
17370
+ const agentContext = ctx.loadAgentContext();
17371
+ const client = ctx.createApiClient(agentContext);
16555
17372
  let latestProfile = null;
16556
17373
  if (hasAvatarUrl || hasDisplayName || hasDescription) {
16557
17374
  const body = {};
@@ -16566,41 +17383,43 @@ function registerProfileUpdateCommand(parent) {
16566
17383
  }
16567
17384
  const res = await client.request(
16568
17385
  "POST",
16569
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile`,
17386
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/profile`,
16570
17387
  body
16571
17388
  );
16572
17389
  if (!res.ok || !res.data) {
16573
17390
  const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
16574
- fail(code, res.error ?? `HTTP ${res.status}`);
17391
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16575
17392
  }
16576
17393
  latestProfile = res.data;
16577
17394
  }
16578
17395
  if (hasAvatar) {
16579
- const avatar = readAvatarFile(opts.avatarFile);
16580
17396
  const form = new FormData();
16581
17397
  const avatarBytes = Uint8Array.from(avatar.buffer);
16582
17398
  form.append("avatar", new Blob([avatarBytes], { type: avatar.mimeType }), avatar.filename);
16583
17399
  const res = await client.requestMultipart(
16584
17400
  "POST",
16585
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile/avatar`,
17401
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/profile/avatar`,
16586
17402
  form
16587
17403
  );
16588
17404
  if (!res.ok || !res.data) {
16589
17405
  const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
16590
- fail(code, res.error ?? `HTTP ${res.status}`);
17406
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16591
17407
  }
16592
17408
  latestProfile = res.data;
16593
17409
  }
16594
17410
  if (!latestProfile) {
16595
- fail("PROFILE_UPDATE_FAILED", "No profile returned from server");
17411
+ throw cliError("PROFILE_UPDATE_FAILED", "No profile returned from server");
16596
17412
  }
16597
17413
  if (opts.json) {
16598
- emit({ ok: true, data: latestProfile });
17414
+ writeJson(ctx.io, { ok: true, data: latestProfile });
16599
17415
  return;
16600
17416
  }
16601
- process.stdout.write(`${formatProfile(latestProfile)}
17417
+ writeText(ctx.io, `${formatProfile(latestProfile)}
16602
17418
  `);
16603
- });
17419
+ }
17420
+ );
17421
+ function registerProfileUpdateCommand(parent, runtimeOptions = {}) {
17422
+ registerCliCommand(parent, profileUpdateCommand, runtimeOptions);
16604
17423
  }
16605
17424
 
16606
17425
  // src/commands/integration/_format.ts
@@ -16631,6 +17450,10 @@ function formatIntegrationList(data) {
16631
17450
  lines.push(` id: ${service.id}`);
16632
17451
  lines.push(` status: ${active ? "active login" : "not logged in"}`);
16633
17452
  lines.push(` return URL: ${formatMaybe(service.returnUrl)}`);
17453
+ if (service.agentManifestUrl) {
17454
+ lines.push(` agent behavior manifest: ${service.agentManifestUrl}`);
17455
+ lines.push(` local CLI env: slock integration env --service ${JSON.stringify(service.clientId)}`);
17456
+ }
16634
17457
  if (service.homepageUrl) lines.push(` homepage: ${service.homepageUrl}`);
16635
17458
  if (service.description) lines.push(` description: ${service.description}`);
16636
17459
  if (!active) lines.push(` next: slock integration login --service ${JSON.stringify(service.clientId)}`);
@@ -16647,6 +17470,10 @@ function formatIntegrationList(data) {
16647
17470
  lines.push(` grant id: ${login.id}`);
16648
17471
  lines.push(` scopes: ${login.scopes.length > 0 ? login.scopes.join(", ") : "-"}`);
16649
17472
  lines.push(` return URL: ${formatMaybe(login.returnUrl)}`);
17473
+ if (login.agentManifestUrl) {
17474
+ lines.push(` agent behavior manifest: ${login.agentManifestUrl}`);
17475
+ lines.push(` local CLI env: slock integration env --service ${JSON.stringify(login.clientId)}`);
17476
+ }
16650
17477
  lines.push(` created: ${login.createdAt}`);
16651
17478
  }
16652
17479
  }
@@ -16659,10 +17486,14 @@ function formatIntegrationLogin(data) {
16659
17486
  `service: ${data.service.clientId}`,
16660
17487
  `id: ${data.service.id}`,
16661
17488
  `scopes: ${data.scopes.length > 0 ? data.scopes.join(", ") : "-"}`,
16662
- `return URL: ${formatMaybe(data.service.returnUrl)}`,
16663
- "complete: this agent login is configured in Slock; no human OAuth is required",
16664
- "identity: run `slock profile show` if the service or human asks for your Slock Agent identity card"
17489
+ `return URL: ${formatMaybe(data.service.returnUrl)}`
16665
17490
  ];
17491
+ if (data.service.agentManifestUrl) {
17492
+ lines.push(`agent behavior manifest: ${data.service.agentManifestUrl}`);
17493
+ lines.push(`local CLI env: slock integration env --service ${JSON.stringify(data.service.clientId)}`);
17494
+ }
17495
+ lines.push("complete: this agent login is configured in Slock; no human OAuth is required");
17496
+ lines.push("identity: run `slock profile show` if the service or human asks for your Slock Agent identity card");
16666
17497
  const agentAppUrl = buildAgentAppUrl(data.service.returnUrl, data.requestId);
16667
17498
  if (agentAppUrl) {
16668
17499
  lines.push(`app URL: ${agentAppUrl}`);
@@ -16674,31 +17505,33 @@ function formatIntegrationLogin(data) {
16674
17505
  }
16675
17506
 
16676
17507
  // src/commands/integration/list.ts
16677
- function registerIntegrationListCommand(parent) {
16678
- parent.command("list").description("List registered third-party services and this agent's active logins").option("--json", "Emit machine-readable JSON").action(async (opts) => {
16679
- let ctx;
16680
- try {
16681
- ctx = loadAgentContext();
16682
- } catch (err) {
16683
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16684
- throw err;
16685
- }
16686
- const client = new ApiClient(ctx);
17508
+ var integrationListCommand = defineCommand(
17509
+ {
17510
+ name: "list",
17511
+ description: "List registered third-party services and this agent's active logins",
17512
+ options: [{ flags: "--json", description: "Emit machine-readable JSON" }]
17513
+ },
17514
+ async (ctx, opts) => {
17515
+ const agentContext = ctx.loadAgentContext();
17516
+ const client = ctx.createApiClient(agentContext);
16687
17517
  const res = await client.request(
16688
17518
  "GET",
16689
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/integrations`
17519
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/integrations`
16690
17520
  );
16691
17521
  if (!res.ok || !res.data) {
16692
17522
  const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LIST_FAILED";
16693
- fail(code, res.error ?? `HTTP ${res.status}`);
17523
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16694
17524
  }
16695
17525
  if (opts.json) {
16696
- emit({ ok: true, data: res.data });
17526
+ writeJson(ctx.io, { ok: true, data: res.data });
16697
17527
  return;
16698
17528
  }
16699
- process.stdout.write(`${formatIntegrationList(res.data)}
17529
+ writeText(ctx.io, `${formatIntegrationList(res.data)}
16700
17530
  `);
16701
- });
17531
+ }
17532
+ );
17533
+ function registerIntegrationListCommand(parent, runtimeOptions = {}) {
17534
+ registerCliCommand(parent, integrationListCommand, runtimeOptions);
16702
17535
  }
16703
17536
 
16704
17537
  // src/commands/integration/login.ts
@@ -16708,46 +17541,389 @@ function normalizeScopes(raw) {
16708
17541
  raw.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
16709
17542
  )).sort();
16710
17543
  if (scopes.length === 0) {
16711
- fail("INVALID_ARG", "--scope must include at least one non-empty scope");
17544
+ throw cliError("INVALID_ARG", "--scope must include at least one non-empty scope");
16712
17545
  }
16713
17546
  return scopes;
16714
17547
  }
16715
- function registerIntegrationLoginCommand(parent) {
16716
- parent.command("login").description("Provision or reuse this agent's login for a registered service").requiredOption("--service <id>", "Registered service id, client id, or exact service name").option("--scope <scope>", "Requested scope; can be repeated or comma-separated", (value, previous = []) => {
16717
- previous.push(value);
16718
- return previous;
16719
- }).option("--json", "Emit machine-readable JSON").action(async (opts) => {
16720
- let ctx;
16721
- try {
16722
- ctx = loadAgentContext();
16723
- } catch (err) {
16724
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16725
- throw err;
16726
- }
16727
- const service = opts.service.trim();
17548
+ var integrationLoginCommand = defineCommand(
17549
+ {
17550
+ name: "login",
17551
+ description: "Provision or reuse this agent's login for a registered service",
17552
+ options: [
17553
+ { flags: "--service <id>", description: "Registered service id, client id, or exact service name" },
17554
+ {
17555
+ flags: "--scope <scope>",
17556
+ description: "Requested scope; can be repeated or comma-separated",
17557
+ parse: (value, previous = []) => {
17558
+ previous.push(value);
17559
+ return previous;
17560
+ }
17561
+ },
17562
+ { flags: "--json", description: "Emit machine-readable JSON" }
17563
+ ]
17564
+ },
17565
+ async (ctx, opts) => {
17566
+ const service = opts.service?.trim() ?? "";
16728
17567
  if (!service) {
16729
- fail("INVALID_ARG", "--service must not be empty");
17568
+ throw cliError("INVALID_ARG", "--service is required");
16730
17569
  }
16731
- const client = new ApiClient(ctx);
17570
+ const scopes = normalizeScopes(opts.scope);
17571
+ const agentContext = ctx.loadAgentContext();
17572
+ const client = ctx.createApiClient(agentContext);
16732
17573
  const res = await client.request(
16733
17574
  "POST",
16734
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/integrations/login`,
17575
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/integrations/login`,
16735
17576
  {
16736
17577
  service,
16737
- scopes: normalizeScopes(opts.scope)
17578
+ scopes
16738
17579
  }
16739
17580
  );
16740
17581
  if (!res.ok || !res.data) {
16741
17582
  const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LOGIN_FAILED";
16742
- fail(code, res.error ?? `HTTP ${res.status}`);
17583
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16743
17584
  }
16744
17585
  if (opts.json) {
16745
- emit({ ok: true, data: res.data });
17586
+ writeJson(ctx.io, { ok: true, data: res.data });
16746
17587
  return;
16747
17588
  }
16748
- process.stdout.write(`${formatIntegrationLogin(res.data)}
17589
+ writeText(ctx.io, `${formatIntegrationLogin(res.data)}
16749
17590
  `);
17591
+ }
17592
+ );
17593
+ function registerIntegrationLoginCommand(parent, runtimeOptions = {}) {
17594
+ registerCliCommand(parent, integrationLoginCommand, runtimeOptions);
17595
+ }
17596
+
17597
+ // src/commands/integration/manifest.ts
17598
+ import fs3 from "fs";
17599
+ import os3 from "os";
17600
+ import path5 from "path";
17601
+ var AGENT_MANIFEST_MAX_BYTES = 64 * 1024;
17602
+ function isRecord(value) {
17603
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
17604
+ }
17605
+ function requireUrl(value, field) {
17606
+ if (typeof value !== "string" || !value.trim()) {
17607
+ throw new Error(`${field} must be a non-empty URL string`);
17608
+ }
17609
+ let parsed;
17610
+ try {
17611
+ parsed = new URL(value);
17612
+ } catch {
17613
+ throw new Error(`${field} must be a valid URL`);
17614
+ }
17615
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
17616
+ throw new Error(`${field} must use http or https`);
17617
+ }
17618
+ if (parsed.username || parsed.password) {
17619
+ throw new Error(`${field} must not include credentials`);
17620
+ }
17621
+ return parsed.toString();
17622
+ }
17623
+ function requireCommand(value) {
17624
+ if (typeof value !== "string" || !value.trim()) {
17625
+ throw new Error("execution.command must be a non-empty string for local_cli manifests");
17626
+ }
17627
+ const command = value.trim();
17628
+ if (command.includes("/") || command.includes("\\") || /\s/.test(command)) {
17629
+ throw new Error("execution.command must be a bare command name, not a path or shell fragment");
17630
+ }
17631
+ return command;
17632
+ }
17633
+ function validateAgentManifestV0(value) {
17634
+ if (!isRecord(value)) throw new Error("manifest must be a JSON object");
17635
+ if (value.schema !== "slock-agent-manifest.v0") {
17636
+ throw new Error("manifest schema must be slock-agent-manifest.v0");
17637
+ }
17638
+ const execution = value.execution;
17639
+ if (!isRecord(execution)) throw new Error("execution must be an object");
17640
+ if (execution.mode !== "local_cli" && execution.mode !== "http_api") {
17641
+ throw new Error("execution.mode must be local_cli or http_api");
17642
+ }
17643
+ const credentialBoundary = value.credential_boundary;
17644
+ if (credentialBoundary !== void 0 && !isRecord(credentialBoundary)) {
17645
+ throw new Error("credential_boundary must be an object when present");
17646
+ }
17647
+ if (isRecord(credentialBoundary) && credentialBoundary.storage !== "per_agent_home" && credentialBoundary.storage !== "slock_managed_token") {
17648
+ throw new Error("credential_boundary.storage must be per_agent_home or slock_managed_token");
17649
+ }
17650
+ const credentialStorage = isRecord(credentialBoundary) && (credentialBoundary.storage === "per_agent_home" || credentialBoundary.storage === "slock_managed_token") ? credentialBoundary.storage : void 0;
17651
+ const forbidUserHome = isRecord(credentialBoundary) && credentialBoundary.forbid_user_home === true;
17652
+ if (value.context_check !== void 0 && !isRecord(value.context_check)) {
17653
+ throw new Error("context_check must be an object when present");
17654
+ }
17655
+ return {
17656
+ schema: value.schema,
17657
+ service: typeof value.service === "string" && value.service.trim() ? value.service.trim() : void 0,
17658
+ docs_url: value.docs_url === void 0 ? void 0 : requireUrl(value.docs_url, "docs_url"),
17659
+ execution: {
17660
+ mode: execution.mode,
17661
+ command: execution.mode === "local_cli" ? requireCommand(execution.command) : void 0,
17662
+ base_url: execution.base_url === void 0 ? void 0 : requireUrl(execution.base_url, "execution.base_url")
17663
+ },
17664
+ credential_boundary: credentialStorage ? {
17665
+ storage: credentialStorage,
17666
+ forbid_user_home: forbidUserHome
17667
+ } : void 0,
17668
+ context_check: value.context_check
17669
+ };
17670
+ }
17671
+ async function fetchAgentManifest(url2) {
17672
+ const parsed = new URL(url2);
17673
+ if (parsed.protocol !== "https:") {
17674
+ throw new Error("agent behavior manifest URL must use HTTPS");
17675
+ }
17676
+ if (parsed.username || parsed.password) {
17677
+ throw new Error("agent behavior manifest URL must not include credentials");
17678
+ }
17679
+ const response = await fetch(parsed, {
17680
+ headers: { accept: "application/json" },
17681
+ redirect: "follow"
16750
17682
  });
17683
+ const finalUrl = new URL(response.url);
17684
+ if (finalUrl.protocol !== "https:" || finalUrl.username || finalUrl.password) {
17685
+ throw new Error("manifest redirects must remain on credential-free HTTPS URLs");
17686
+ }
17687
+ if (!response.ok) {
17688
+ throw new Error(`manifest fetch failed with HTTP ${response.status}`);
17689
+ }
17690
+ const contentType = response.headers.get("content-type") ?? "";
17691
+ if (contentType && !contentType.includes("application/json")) {
17692
+ throw new Error("manifest response must be application/json");
17693
+ }
17694
+ const contentLength = Number(response.headers.get("content-length") ?? "");
17695
+ if (Number.isFinite(contentLength) && contentLength > AGENT_MANIFEST_MAX_BYTES) {
17696
+ throw new Error("manifest response is too large");
17697
+ }
17698
+ const body = Buffer.from(await response.arrayBuffer());
17699
+ if (body.byteLength > AGENT_MANIFEST_MAX_BYTES) {
17700
+ throw new Error("manifest response is too large");
17701
+ }
17702
+ const raw = body.toString("utf8");
17703
+ let parsedJson;
17704
+ try {
17705
+ parsedJson = JSON.parse(raw);
17706
+ } catch (err) {
17707
+ throw new Error(`manifest response is not valid JSON: ${err.message}`);
17708
+ }
17709
+ return validateAgentManifestV0(parsedJson);
17710
+ }
17711
+ function sanitizePathSegment(value) {
17712
+ const segment = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
17713
+ return segment || "service";
17714
+ }
17715
+ function resolveSlockHome(env) {
17716
+ if (env.SLOCK_HOME?.trim()) return path5.resolve(env.SLOCK_HOME);
17717
+ return path5.join(env.HOME ?? os3.homedir(), ".slock");
17718
+ }
17719
+ function buildLocalCliProfileEnv(input) {
17720
+ if (input.manifest.execution.mode !== "local_cli" || !input.manifest.execution.command) {
17721
+ throw new Error("manifest is not a local_cli integration");
17722
+ }
17723
+ if (input.manifest.credential_boundary?.storage !== "per_agent_home") {
17724
+ throw new Error("manifest does not request per_agent_home credential storage");
17725
+ }
17726
+ if (input.manifest.credential_boundary.forbid_user_home !== true) {
17727
+ throw new Error("manifest must set credential_boundary.forbid_user_home=true for local_cli isolation");
17728
+ }
17729
+ const root = resolveSlockHome(input.env ?? process.env);
17730
+ const profileHome = path5.join(
17731
+ root,
17732
+ "integration-profiles",
17733
+ sanitizePathSegment(input.ctx.serverId ?? "server"),
17734
+ sanitizePathSegment(input.ctx.agentId),
17735
+ sanitizePathSegment(input.serviceId)
17736
+ );
17737
+ fs3.mkdirSync(profileHome, { recursive: true, mode: 448 });
17738
+ fs3.chmodSync(profileHome, 448);
17739
+ return {
17740
+ serviceId: input.serviceId,
17741
+ command: input.manifest.execution.command,
17742
+ profileHome,
17743
+ env: {
17744
+ SLOCK_INTEGRATION_SERVICE: input.serviceId,
17745
+ SLOCK_INTEGRATION_PROFILE_HOME: profileHome,
17746
+ HOME: profileHome,
17747
+ XDG_CONFIG_HOME: path5.join(profileHome, ".config"),
17748
+ XDG_CACHE_HOME: path5.join(profileHome, ".cache"),
17749
+ XDG_DATA_HOME: path5.join(profileHome, ".local", "share"),
17750
+ XDG_STATE_HOME: path5.join(profileHome, ".local", "state")
17751
+ }
17752
+ };
17753
+ }
17754
+ function shellQuote(value) {
17755
+ return `'${value.replace(/'/g, "'\\''")}'`;
17756
+ }
17757
+ function formatShellExports(profile) {
17758
+ const lines = [
17759
+ `# Slock integration profile for ${profile.serviceId}`,
17760
+ `# command: ${profile.command}`,
17761
+ "# This only exports env; Slock does not execute manifest commands.",
17762
+ "# Apply these exports before invoking the local CLI yourself."
17763
+ ];
17764
+ for (const [key, value] of Object.entries(profile.env)) {
17765
+ lines.push(`export ${key}=${shellQuote(value)}`);
17766
+ }
17767
+ return lines.join("\n");
17768
+ }
17769
+
17770
+ // src/commands/integration/env.ts
17771
+ function normalizeService(value) {
17772
+ return value.trim().toLowerCase();
17773
+ }
17774
+ function findService(data, service) {
17775
+ const normalized = normalizeService(service);
17776
+ if (!normalized) return null;
17777
+ return data.services.find(
17778
+ (candidate) => candidate.id === service || normalizeService(candidate.clientId) === normalized || normalizeService(candidate.name) === normalized
17779
+ ) ?? null;
17780
+ }
17781
+ function buildIntegrationEnvListPath(agentId) {
17782
+ return `/internal/agent/${encodeURIComponent(agentId)}/integrations`;
17783
+ }
17784
+ function describeNoLocalEnv(manifest) {
17785
+ if (manifest.execution.mode !== "local_cli") {
17786
+ return "manifest execution mode is not local_cli; no local CLI env is required";
17787
+ }
17788
+ if (!manifest.credential_boundary) {
17789
+ return "manifest does not request a Slock-managed local environment";
17790
+ }
17791
+ if (manifest.credential_boundary.storage === "slock_managed_token") {
17792
+ return "manifest uses Slock-managed token storage; no local HOME/XDG exports are required";
17793
+ }
17794
+ return "manifest does not request local CLI env exports";
17795
+ }
17796
+ var IntegrationEnvError = class extends Error {
17797
+ constructor(code, message) {
17798
+ super(message);
17799
+ this.code = code;
17800
+ this.name = "IntegrationEnvError";
17801
+ }
17802
+ };
17803
+ async function resolveIntegrationEnv(input) {
17804
+ if (!input.service.agentManifestUrl) {
17805
+ throw new IntegrationEnvError(
17806
+ "INTEGRATION_MANIFEST_MISSING",
17807
+ `${input.service.name} does not expose an agent behavior manifest`
17808
+ );
17809
+ }
17810
+ const fetchManifestImpl = input.fetchManifest ?? fetchAgentManifest;
17811
+ let manifest;
17812
+ try {
17813
+ manifest = await fetchManifestImpl(input.service.agentManifestUrl);
17814
+ } catch (err) {
17815
+ throw new IntegrationEnvError("INTEGRATION_MANIFEST_INVALID", err.message);
17816
+ }
17817
+ if (manifest.execution.mode !== "local_cli" || manifest.credential_boundary?.storage !== "per_agent_home") {
17818
+ return {
17819
+ kind: "no-local-env",
17820
+ service: input.service,
17821
+ manifestUrl: input.service.agentManifestUrl,
17822
+ manifest,
17823
+ message: describeNoLocalEnv(manifest)
17824
+ };
17825
+ }
17826
+ try {
17827
+ return {
17828
+ kind: "local-env",
17829
+ service: input.service,
17830
+ manifestUrl: input.service.agentManifestUrl,
17831
+ profile: buildLocalCliProfileEnv({
17832
+ ctx: input.ctx,
17833
+ serviceId: input.service.clientId,
17834
+ manifest,
17835
+ env: input.env
17836
+ })
17837
+ };
17838
+ } catch (err) {
17839
+ throw new IntegrationEnvError("INTEGRATION_MANIFEST_UNSUPPORTED", err.message);
17840
+ }
17841
+ }
17842
+ function formatNoLocalEnv(input) {
17843
+ return [
17844
+ `# Slock integration profile for ${input.service}`,
17845
+ `# manifest: ${input.manifestUrl}`,
17846
+ "# No local CLI environment exports are required by this manifest.",
17847
+ `# ${input.message}`,
17848
+ "# Slock did not set HOME/XDG exports and did not execute manifest commands."
17849
+ ].join("\n");
17850
+ }
17851
+ var integrationEnvCommand = defineCommand(
17852
+ {
17853
+ name: "env",
17854
+ description: "Print per-agent local environment for a manifest-backed local CLI integration",
17855
+ options: [
17856
+ { flags: "--service <id>", description: "Registered service id, client id, or exact service name" },
17857
+ { flags: "--json", description: "Emit machine-readable JSON" }
17858
+ ]
17859
+ },
17860
+ async (cmdCtx, opts) => {
17861
+ const serviceQuery = opts.service?.trim() ?? "";
17862
+ if (!serviceQuery) {
17863
+ throw cliError("INVALID_ARG", "--service must not be empty");
17864
+ }
17865
+ const ctx = cmdCtx.loadAgentContext();
17866
+ const client = cmdCtx.createApiClient(ctx);
17867
+ const res = await client.request("GET", buildIntegrationEnvListPath(ctx.agentId));
17868
+ if (!res.ok || !res.data) {
17869
+ const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LIST_FAILED";
17870
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17871
+ }
17872
+ const integrations = res.data;
17873
+ const service = findService(integrations, serviceQuery);
17874
+ if (!service) {
17875
+ throw cliError("INTEGRATION_NOT_FOUND", `No registered integration matched ${serviceQuery}`);
17876
+ }
17877
+ let resolution;
17878
+ try {
17879
+ resolution = await resolveIntegrationEnv({ ctx, service });
17880
+ } catch (err) {
17881
+ if (err instanceof IntegrationEnvError) throw cliError(err.code, err.message, { cause: err });
17882
+ throw err;
17883
+ }
17884
+ if (resolution.kind === "no-local-env") {
17885
+ if (opts.json) {
17886
+ writeJson(cmdCtx.io, {
17887
+ ok: true,
17888
+ data: {
17889
+ service: resolution.service.clientId,
17890
+ manifestUrl: resolution.manifestUrl,
17891
+ requiresLocalEnv: false,
17892
+ command: resolution.manifest.execution.command ?? null,
17893
+ env: {},
17894
+ message: resolution.message
17895
+ }
17896
+ });
17897
+ return;
17898
+ }
17899
+ writeText(cmdCtx.io, `${formatNoLocalEnv({
17900
+ service: resolution.service.clientId,
17901
+ manifestUrl: resolution.manifestUrl,
17902
+ message: resolution.message
17903
+ })}
17904
+ `);
17905
+ return;
17906
+ }
17907
+ if (opts.json) {
17908
+ writeJson(cmdCtx.io, {
17909
+ ok: true,
17910
+ data: {
17911
+ service: resolution.service.clientId,
17912
+ manifestUrl: resolution.manifestUrl,
17913
+ requiresLocalEnv: true,
17914
+ command: resolution.profile.command,
17915
+ profileHome: resolution.profile.profileHome,
17916
+ env: resolution.profile.env
17917
+ }
17918
+ });
17919
+ return;
17920
+ }
17921
+ writeText(cmdCtx.io, `${formatShellExports(resolution.profile)}
17922
+ `);
17923
+ }
17924
+ );
17925
+ function registerIntegrationEnvCommand(parent, runtimeOptions = {}) {
17926
+ registerCliCommand(parent, integrationEnvCommand, runtimeOptions);
16751
17927
  }
16752
17928
 
16753
17929
  // src/commands/reminder/_format.ts
@@ -16849,66 +18025,62 @@ function buildScheduleBody(opts, now = () => Intl.DateTimeFormat().resolvedOptio
16849
18025
  }
16850
18026
  return { body };
16851
18027
  }
16852
- function registerReminderScheduleCommand(parent) {
16853
- parent.command("schedule").description("Schedule a reminder that fires at a future time").requiredOption("--title <t>", "Short description of what the reminder is about").option(
16854
- "--delay-seconds <n>",
16855
- "Preferred for relative times. Fires this many seconds from now (server-computed, timezone-safe)"
16856
- ).option(
16857
- "--fire-at <iso>",
16858
- "ISO-8601 UTC timestamp, e.g. 2026-04-21T09:00:00Z. Use only for absolute calendar times"
16859
- ).option(
16860
- "--repeat <rule>",
16861
- "Recurrence rule: every:15m | every:2h | every:1d | daily@09:00 | weekly:mon,fri@09:00"
16862
- ).option(
16863
- "--channel <ref>",
16864
- "Optional channel to post a receipt message in (e.g. #general, dm:@alice)."
16865
- ).requiredOption(
16866
- "--msg-id <id>",
16867
- "Message id this reminder is anchored to. Required for agent-created reminders."
16868
- ).action(async (opts) => {
16869
- let ctx;
16870
- try {
16871
- ctx = loadAgentContext();
16872
- } catch (err) {
16873
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16874
- throw err;
18028
+ var reminderScheduleCommand = defineCommand(
18029
+ {
18030
+ name: "schedule",
18031
+ description: "Schedule a reminder that fires at a future time",
18032
+ options: [
18033
+ { flags: "--title <t>", description: "Short description of what the reminder is about" },
18034
+ { flags: "--delay-seconds <n>", description: "Preferred for relative times. Fires this many seconds from now (server-computed, timezone-safe)" },
18035
+ { flags: "--fire-at <iso>", description: "ISO-8601 UTC timestamp, e.g. 2026-04-21T09:00:00Z. Use only for absolute calendar times" },
18036
+ { flags: "--repeat <rule>", description: "Recurrence rule: every:15m | every:2h | every:1d | daily@09:00 | weekly:mon,fri@09:00" },
18037
+ { flags: "--channel <ref>", description: "Optional channel to post a receipt message in (e.g. #general, dm:@alice)." },
18038
+ { flags: "--msg-id <id>", description: "Message id this reminder is anchored to. Required for agent-created reminders." }
18039
+ ]
18040
+ },
18041
+ async (ctx, opts) => {
18042
+ if (!opts.title?.trim()) {
18043
+ throw cliError("INVALID_ARG", "--title is required");
16875
18044
  }
16876
18045
  const built = buildScheduleBody(opts);
16877
- if (built.error) fail(built.error.code, built.error.message);
16878
- const client = new ApiClient(ctx);
18046
+ if (built.error) throw cliError(built.error.code, built.error.message);
18047
+ const agentContext = ctx.loadAgentContext();
18048
+ const client = ctx.createApiClient(agentContext);
16879
18049
  const res = await client.request(
16880
18050
  "POST",
16881
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders`,
18051
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders`,
16882
18052
  built.body
16883
18053
  );
16884
18054
  if (!res.ok || !res.data?.reminder) {
16885
18055
  const code = res.status >= 500 ? "SERVER_5XX" : "SCHEDULE_FAILED";
16886
- fail(code, res.error ?? `HTTP ${res.status}`);
18056
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16887
18057
  }
16888
- process.stdout.write(
18058
+ writeText(
18059
+ ctx.io,
16889
18060
  formatReminderScheduled(res.data.reminder, res.data.warning ?? null) + "\n"
16890
18061
  );
16891
- });
18062
+ }
18063
+ );
18064
+ function registerReminderScheduleCommand(parent, runtimeOptions = {}) {
18065
+ registerCliCommand(parent, reminderScheduleCommand, runtimeOptions);
16892
18066
  }
16893
18067
 
16894
18068
  // src/commands/reminder/list.ts
16895
18069
  var VALID_STATUSES2 = /* @__PURE__ */ new Set(["scheduled", "fired", "canceled"]);
16896
- function registerReminderListCommand(parent) {
16897
- parent.command("list").description("List your own reminders (defaults to scheduled and fired)").option("--all", "Include canceled reminders").option(
16898
- "--status <s>",
16899
- "Comma-separated statuses (scheduled,fired,canceled). Default: scheduled,fired"
16900
- ).action(async (opts) => {
16901
- let ctx;
16902
- try {
16903
- ctx = loadAgentContext();
16904
- } catch (err) {
16905
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16906
- throw err;
16907
- }
18070
+ var reminderListCommand = defineCommand(
18071
+ {
18072
+ name: "list",
18073
+ description: "List your own reminders (defaults to scheduled and fired)",
18074
+ options: [
18075
+ { flags: "--all", description: "Include canceled reminders" },
18076
+ { flags: "--status <s>", description: "Comma-separated statuses (scheduled,fired,canceled). Default: scheduled,fired" }
18077
+ ]
18078
+ },
18079
+ async (ctx, opts) => {
16908
18080
  const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled,fired";
16909
18081
  for (const s of statusRaw.split(",").map((x) => x.trim()).filter(Boolean)) {
16910
18082
  if (!VALID_STATUSES2.has(s)) {
16911
- fail("INVALID_ARG", `--status entries must be one of ${Array.from(VALID_STATUSES2).join("|")}; got ${s}`);
18083
+ throw cliError("INVALID_ARG", `--status entries must be one of ${Array.from(VALID_STATUSES2).join("|")}; got ${s}`);
16912
18084
  }
16913
18085
  }
16914
18086
  const params = new URLSearchParams();
@@ -16917,23 +18089,27 @@ function registerReminderListCommand(parent) {
16917
18089
  } else {
16918
18090
  params.set("status", statusRaw);
16919
18091
  }
16920
- const client = new ApiClient(ctx);
18092
+ const agentContext = ctx.loadAgentContext();
18093
+ const client = ctx.createApiClient(agentContext);
16921
18094
  const res = await client.request(
16922
18095
  "GET",
16923
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders?${params.toString()}`
18096
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders?${params.toString()}`
16924
18097
  );
16925
18098
  if (!res.ok) {
16926
18099
  const code = res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED";
16927
- fail(code, res.error ?? `HTTP ${res.status}`);
18100
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16928
18101
  }
16929
- process.stdout.write(formatReminderList(res.data?.reminders ?? []) + "\n");
16930
- });
18102
+ writeText(ctx.io, formatReminderList(res.data?.reminders ?? []) + "\n");
18103
+ }
18104
+ );
18105
+ function registerReminderListCommand(parent, runtimeOptions = {}) {
18106
+ registerCliCommand(parent, reminderListCommand, runtimeOptions);
16931
18107
  }
16932
18108
 
16933
18109
  // src/commands/reminder/_resolve.ts
16934
18110
  async function resolveReminderId(client, agentId, id, opts) {
16935
18111
  const trimmed = id.trim();
16936
- if (!trimmed) fail("INVALID_ARG", "--id is required");
18112
+ if (!trimmed) throw cliError("INVALID_ARG", "--id is required");
16937
18113
  if (trimmed.length >= 32) return trimmed;
16938
18114
  const params = new URLSearchParams();
16939
18115
  if (opts.all) {
@@ -16948,47 +18124,49 @@ async function resolveReminderId(client, agentId, id, opts) {
16948
18124
  );
16949
18125
  if (!res.ok) {
16950
18126
  const code = res.status >= 500 ? "SERVER_5XX" : opts.failureCode;
16951
- fail(code, res.error ?? `HTTP ${res.status}`);
18127
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16952
18128
  }
16953
18129
  const matches = (res.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(trimmed));
16954
18130
  if (matches.length === 0) {
16955
18131
  const scope = opts.all ? "reminder" : `${opts.statuses?.join("/") ?? "active"} reminder`;
16956
- fail("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
18132
+ throw cliError("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
16957
18133
  }
16958
18134
  if (matches.length > 1) {
16959
- fail("AMBIGUOUS", `Ambiguous id prefix '${trimmed}' matches ${matches.length} reminders; pass a longer id.`);
18135
+ throw cliError("AMBIGUOUS", `Ambiguous id prefix '${trimmed}' matches ${matches.length} reminders; pass a longer id.`);
16960
18136
  }
16961
18137
  return matches[0].reminderId;
16962
18138
  }
16963
18139
 
16964
18140
  // src/commands/reminder/cancel.ts
16965
- function registerReminderCancelCommand(parent) {
16966
- parent.command("cancel").description("Cancel a scheduled reminder by id (full uuid or 8-char prefix)").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
16967
- let ctx;
16968
- try {
16969
- ctx = loadAgentContext();
16970
- } catch (err) {
16971
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16972
- throw err;
16973
- }
18141
+ var reminderCancelCommand = defineCommand(
18142
+ {
18143
+ name: "cancel",
18144
+ description: "Cancel a scheduled reminder by id (full uuid or 8-char prefix)",
18145
+ options: [{ flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" }]
18146
+ },
18147
+ async (ctx, opts) => {
16974
18148
  if (!opts.id || opts.id.trim().length === 0) {
16975
- fail("INVALID_ARG", "--id is required");
18149
+ throw cliError("INVALID_ARG", "--id is required");
16976
18150
  }
16977
- const client = new ApiClient(ctx);
16978
- const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
18151
+ const agentContext = ctx.loadAgentContext();
18152
+ const client = ctx.createApiClient(agentContext);
18153
+ const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
16979
18154
  statuses: ["scheduled", "fired"],
16980
18155
  failureCode: "CANCEL_FAILED"
16981
18156
  });
16982
18157
  const res = await client.request(
16983
18158
  "DELETE",
16984
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`
18159
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}`
16985
18160
  );
16986
18161
  if (!res.ok || !res.data?.reminder) {
16987
18162
  const code = res.status >= 500 ? "SERVER_5XX" : "CANCEL_FAILED";
16988
- fail(code, res.error ?? `HTTP ${res.status}`);
18163
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16989
18164
  }
16990
- process.stdout.write(formatReminderCanceled(res.data.reminder) + "\n");
16991
- });
18165
+ writeText(ctx.io, formatReminderCanceled(res.data.reminder) + "\n");
18166
+ }
18167
+ );
18168
+ function registerReminderCancelCommand(parent, runtimeOptions = {}) {
18169
+ registerCliCommand(parent, reminderCancelCommand, runtimeOptions);
16992
18170
  }
16993
18171
 
16994
18172
  // src/commands/reminder/_duration.ts
@@ -17005,57 +18183,75 @@ function parseDurationSeconds(input) {
17005
18183
  }
17006
18184
 
17007
18185
  // src/commands/reminder/snooze.ts
17008
- function registerReminderSnoozeCommand(parent) {
17009
- parent.command("snooze").description("Snooze a scheduled or fired reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").requiredOption("--by <duration>", "Snooze duration, e.g. 30m, 2h, 1d").action(async (opts) => {
17010
- let ctx;
17011
- try {
17012
- ctx = loadAgentContext();
17013
- } catch (err) {
17014
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17015
- throw err;
18186
+ var reminderSnoozeCommand = defineCommand(
18187
+ {
18188
+ name: "snooze",
18189
+ description: "Snooze a scheduled or fired reminder",
18190
+ options: [
18191
+ { flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" },
18192
+ { flags: "--by <duration>", description: "Snooze duration, e.g. 30m, 2h, 1d" }
18193
+ ]
18194
+ },
18195
+ async (ctx, opts) => {
18196
+ if (!opts.id?.trim()) {
18197
+ throw cliError("INVALID_ARG", "--id is required");
18198
+ }
18199
+ if (!opts.by?.trim()) {
18200
+ throw cliError("INVALID_ARG", "--by is required");
17016
18201
  }
17017
18202
  const delaySeconds = parseDurationSeconds(opts.by);
17018
18203
  if (delaySeconds == null) {
17019
- fail("INVALID_ARG", "--by must be a positive duration like 30m, 2h, or 1d");
18204
+ throw cliError("INVALID_ARG", "--by must be a positive duration like 30m, 2h, or 1d");
17020
18205
  }
17021
- const client = new ApiClient(ctx);
17022
- const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
18206
+ const agentContext = ctx.loadAgentContext();
18207
+ const client = ctx.createApiClient(agentContext);
18208
+ const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
17023
18209
  statuses: ["scheduled", "fired"],
17024
18210
  failureCode: "SNOOZE_FAILED"
17025
18211
  });
17026
18212
  const res = await client.request(
17027
18213
  "POST",
17028
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
18214
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
17029
18215
  { delaySeconds }
17030
18216
  );
17031
18217
  if (!res.ok || !res.data?.reminder) {
17032
18218
  const code = res.status >= 500 ? "SERVER_5XX" : "SNOOZE_FAILED";
17033
- fail(code, res.error ?? `HTTP ${res.status}`);
18219
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17034
18220
  }
17035
- process.stdout.write(formatReminderSnoozed(res.data.reminder) + "\n");
17036
- });
18221
+ writeText(ctx.io, formatReminderSnoozed(res.data.reminder) + "\n");
18222
+ }
18223
+ );
18224
+ function registerReminderSnoozeCommand(parent, runtimeOptions = {}) {
18225
+ registerCliCommand(parent, reminderSnoozeCommand, runtimeOptions);
17037
18226
  }
17038
18227
 
17039
18228
  // src/commands/reminder/update.ts
17040
- function registerReminderUpdateCommand(parent) {
17041
- parent.command("update").description("Update one field on a scheduled reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").option("--fire-at <iso>", "New absolute next fire time").option("--in <duration>", "New relative next fire time, e.g. 30m, 2h").option("--cadence <rule>", "New recurrence rule: every:15m | daily@09:00 | weekly:mon,fri@09:00").option("--title <text>", "New reminder title").action(async (opts) => {
17042
- let ctx;
17043
- try {
17044
- ctx = loadAgentContext();
17045
- } catch (err) {
17046
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17047
- throw err;
18229
+ var reminderUpdateCommand = defineCommand(
18230
+ {
18231
+ name: "update",
18232
+ description: "Update one field on a scheduled reminder",
18233
+ options: [
18234
+ { flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" },
18235
+ { flags: "--fire-at <iso>", description: "New absolute next fire time" },
18236
+ { flags: "--in <duration>", description: "New relative next fire time, e.g. 30m, 2h" },
18237
+ { flags: "--cadence <rule>", description: "New recurrence rule: every:15m | daily@09:00 | weekly:mon,fri@09:00" },
18238
+ { flags: "--title <text>", description: "New reminder title" }
18239
+ ]
18240
+ },
18241
+ async (ctx, opts) => {
18242
+ if (!opts.id?.trim()) {
18243
+ throw cliError("INVALID_ARG", "--id is required");
17048
18244
  }
17049
18245
  const mutationCount = [opts.fireAt, opts.in, opts.cadence, opts.title].filter((x) => x !== void 0 && x !== null).length;
17050
18246
  if (mutationCount !== 1) {
17051
- fail("INVALID_ARG", "Pass exactly one of --fire-at, --in, --cadence, or --title");
18247
+ throw cliError("INVALID_ARG", "Pass exactly one of --fire-at, --in, --cadence, or --title");
17052
18248
  }
17053
18249
  const body = {};
17054
18250
  if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
17055
18251
  if (opts.in !== void 0) {
17056
18252
  const delaySeconds = parseDurationSeconds(opts.in);
17057
18253
  if (delaySeconds == null) {
17058
- fail("INVALID_ARG", "--in must be a positive duration like 30m, 2h, or 1d");
18254
+ throw cliError("INVALID_ARG", "--in must be a positive duration like 30m, 2h, or 1d");
17059
18255
  }
17060
18256
  body.delaySeconds = delaySeconds;
17061
18257
  }
@@ -17064,49 +18260,58 @@ function registerReminderUpdateCommand(parent) {
17064
18260
  body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
17065
18261
  }
17066
18262
  if (opts.title !== void 0) body.title = opts.title;
17067
- const client = new ApiClient(ctx);
17068
- const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
18263
+ const agentContext = ctx.loadAgentContext();
18264
+ const client = ctx.createApiClient(agentContext);
18265
+ const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
17069
18266
  all: true,
17070
18267
  failureCode: "UPDATE_FAILED"
17071
18268
  });
17072
18269
  const res = await client.request(
17073
18270
  "PATCH",
17074
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`,
18271
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}`,
17075
18272
  body
17076
18273
  );
17077
18274
  if (!res.ok || !res.data?.reminder) {
17078
18275
  const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
17079
- fail(code, res.error ?? `HTTP ${res.status}`);
18276
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17080
18277
  }
17081
- process.stdout.write(formatReminderUpdated(res.data.reminder, res.data.warning ?? null) + "\n");
17082
- });
18278
+ writeText(ctx.io, formatReminderUpdated(res.data.reminder, res.data.warning ?? null) + "\n");
18279
+ }
18280
+ );
18281
+ function registerReminderUpdateCommand(parent, runtimeOptions = {}) {
18282
+ registerCliCommand(parent, reminderUpdateCommand, runtimeOptions);
17083
18283
  }
17084
18284
 
17085
18285
  // src/commands/reminder/log.ts
17086
- function registerReminderLogCommand(parent) {
17087
- parent.command("log").description("Show lifecycle events for one reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
17088
- let ctx;
17089
- try {
17090
- ctx = loadAgentContext();
17091
- } catch (err) {
17092
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17093
- throw err;
18286
+ var reminderLogCommand = defineCommand(
18287
+ {
18288
+ name: "log",
18289
+ description: "Show lifecycle events for one reminder",
18290
+ options: [{ flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" }]
18291
+ },
18292
+ async (ctx, opts) => {
18293
+ if (!opts.id?.trim()) {
18294
+ throw cliError("INVALID_ARG", "--id is required");
17094
18295
  }
17095
- const client = new ApiClient(ctx);
17096
- const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
18296
+ const agentContext = ctx.loadAgentContext();
18297
+ const client = ctx.createApiClient(agentContext);
18298
+ const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
17097
18299
  all: true,
17098
18300
  failureCode: "LOG_FAILED"
17099
18301
  });
17100
18302
  const res = await client.request(
17101
18303
  "GET",
17102
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
18304
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
17103
18305
  );
17104
18306
  if (!res.ok || !res.data?.events) {
17105
18307
  const code = res.status >= 500 ? "SERVER_5XX" : "LOG_FAILED";
17106
- fail(code, res.error ?? `HTTP ${res.status}`);
18308
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17107
18309
  }
17108
- process.stdout.write(formatReminderLog(res.data.events) + "\n");
17109
- });
18310
+ writeText(ctx.io, formatReminderLog(res.data.events) + "\n");
18311
+ }
18312
+ );
18313
+ function registerReminderLogCommand(parent, runtimeOptions = {}) {
18314
+ registerCliCommand(parent, reminderLogCommand, runtimeOptions);
17110
18315
  }
17111
18316
 
17112
18317
  // src/index.ts
@@ -17136,6 +18341,8 @@ var threadCmd = program.command("thread").description("Thread attention operatio
17136
18341
  registerThreadUnfollowCommand(threadCmd);
17137
18342
  var serverCmd = program.command("server").description("Server / workspace introspection");
17138
18343
  registerServerInfoCommand(serverCmd);
18344
+ var knowledgeCmd = program.command("knowledge").description("Agent knowledge retrieval (canonical Slock product knowledge topics)");
18345
+ registerKnowledgeGetCommand(knowledgeCmd);
17139
18346
  var messageCmd = program.command("message").description("Message operations");
17140
18347
  registerSendCommand(messageCmd);
17141
18348
  registerCheckCommand(messageCmd);
@@ -17157,6 +18364,7 @@ registerProfileUpdateCommand(profileCmd);
17157
18364
  var integrationCmd = program.command("integration").description("Third-party service integration operations");
17158
18365
  registerIntegrationListCommand(integrationCmd);
17159
18366
  registerIntegrationLoginCommand(integrationCmd);
18367
+ registerIntegrationEnvCommand(integrationCmd);
17160
18368
  var reminderCmd = program.command("reminder").description("Reminder operations");
17161
18369
  registerReminderScheduleCommand(reminderCmd);
17162
18370
  registerReminderListCommand(reminderCmd);