@slock-ai/daemon 0.55.0 → 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
@@ -100,6 +126,153 @@ function buildFetchDispatcher(targetUrl, env = process.env) {
100
126
  return dispatcher;
101
127
  }
102
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
+
103
276
  // src/client.ts
104
277
  var ApiClient = class {
105
278
  constructor(ctx) {
@@ -169,7 +342,7 @@ var ApiClient = class {
169
342
  async parseJsonResponse(res) {
170
343
  let data = null;
171
344
  let error48 = null;
172
- let errorCode = null;
345
+ let errorCode2 = null;
173
346
  const contentType = res.headers.get("content-type") ?? "";
174
347
  if (contentType.includes("application/json")) {
175
348
  let parseFailed = false;
@@ -191,34 +364,76 @@ var ApiClient = class {
191
364
  } else {
192
365
  const body = parsed;
193
366
  if (res.status === 403 && body?.requiredScope) {
194
- errorCode = "SCOPE_DENIED";
367
+ errorCode2 = "SCOPE_DENIED";
195
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.`;
196
369
  } else {
197
370
  error48 = body?.error ?? `HTTP ${res.status}`;
198
- errorCode = body?.errorCode ?? null;
371
+ errorCode2 = body?.errorCode ?? body?.code ?? null;
199
372
  }
373
+ const suggestedNextAction = body?.suggestedNextAction ?? body?.suggested_next_action;
374
+ return { ok: res.ok, status: res.status, data, error: error48, errorCode: errorCode2, suggestedNextAction };
200
375
  }
201
376
  } else if (!res.ok) {
202
377
  error48 = `HTTP ${res.status}`;
203
378
  }
204
- return { ok: res.ok, status: res.status, data, error: error48, errorCode };
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
+ });
205
420
  }
206
421
  async request(method, pathname, body) {
207
422
  pathname = this.rewriteAgentCredentialPath(pathname);
208
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
423
+ const url2 = new URL(pathname, this.ctx.serverUrl);
209
424
  const headers = this.buildAuthHeaders();
210
425
  headers["Content-Type"] = "application/json";
211
426
  if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
212
427
  headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
213
428
  }
214
- const dispatcher = buildFetchDispatcher(url2);
429
+ const dispatcher = buildFetchDispatcher(url2.toString());
215
430
  const init = {
216
431
  method,
217
432
  headers,
218
433
  body: body === void 0 ? void 0 : JSON.stringify(body)
219
434
  };
220
435
  if (dispatcher) init.dispatcher = dispatcher;
221
- const res = await fetch(url2, init);
436
+ const res = await this.fetchWithTransportTrace(url2, pathname, init);
222
437
  const parsed = await this.parseJsonResponse(res);
223
438
  if (parsed.ok && parsed.data !== null) {
224
439
  parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
@@ -230,8 +445,8 @@ var ApiClient = class {
230
445
  // boundary itself.
231
446
  async requestMultipart(method, pathname, form) {
232
447
  pathname = this.rewriteAgentCredentialPath(pathname);
233
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
234
- const dispatcher = buildFetchDispatcher(url2);
448
+ const url2 = new URL(pathname, this.ctx.serverUrl);
449
+ const dispatcher = buildFetchDispatcher(url2.toString());
235
450
  const headers = this.buildAuthHeaders();
236
451
  if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
237
452
  headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
@@ -242,7 +457,7 @@ var ApiClient = class {
242
457
  body: form
243
458
  };
244
459
  if (dispatcher) init.dispatcher = dispatcher;
245
- const res = await fetch(url2, init);
460
+ const res = await this.fetchWithTransportTrace(url2, pathname, init);
246
461
  return this.parseJsonResponse(res);
247
462
  }
248
463
  // Returns the raw Response so the caller can stream / save the body.
@@ -250,8 +465,8 @@ var ApiClient = class {
250
465
  // consuming the body. On non-2xx, attempts to surface a JSON error.
251
466
  async requestRaw(method, pathname) {
252
467
  pathname = this.rewriteAgentCredentialPath(pathname);
253
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
254
- const dispatcher = buildFetchDispatcher(url2);
468
+ const url2 = new URL(pathname, this.ctx.serverUrl);
469
+ const dispatcher = buildFetchDispatcher(url2.toString());
255
470
  const headers = this.buildAuthHeaders();
256
471
  if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
257
472
  headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
@@ -262,7 +477,7 @@ var ApiClient = class {
262
477
  redirect: "follow"
263
478
  };
264
479
  if (dispatcher) init.dispatcher = dispatcher;
265
- const res = await fetch(url2, init);
480
+ const res = await this.fetchWithTransportTrace(url2, pathname, init);
266
481
  let error48 = null;
267
482
  if (!res.ok) {
268
483
  const contentType = res.headers.get("content-type") ?? "";
@@ -280,7 +495,7 @@ var ApiClient = class {
280
495
  // src/auth/env.ts
281
496
  import fs from "fs";
282
497
  import os from "os";
283
- import path from "path";
498
+ import path2 from "path";
284
499
  var RAW_AGENT_ENV_KEYS = [
285
500
  "SLOCK_AGENT_ID",
286
501
  "SLOCK_SERVER_URL",
@@ -303,13 +518,13 @@ function resolveProfileDir(slug, env = process.env) {
303
518
  return env.SLOCK_PROFILE_DIR;
304
519
  }
305
520
  if (env.SLOCK_HOME) {
306
- return path.join(env.SLOCK_HOME, "profiles", slug);
521
+ return path2.join(env.SLOCK_HOME, "profiles", slug);
307
522
  }
308
523
  const home = env.HOME ?? os.homedir();
309
- return path.join(home, ".slock", "profiles", slug);
524
+ return path2.join(home, ".slock", "profiles", slug);
310
525
  }
311
526
  function resolveProfileCredentialPath(slug, env) {
312
- return path.join(resolveProfileDir(slug, env), "credential.json");
527
+ return path2.join(resolveProfileDir(slug, env), "credential.json");
313
528
  }
314
529
  function readProfileCredential(slug, env) {
315
530
  const filePath = resolveProfileCredentialPath(slug, env);
@@ -450,42 +665,33 @@ function loadAgentContext(env = process.env) {
450
665
  // src/core/io.ts
451
666
  function defaultCliIo() {
452
667
  return {
668
+ stdin: process.stdin,
453
669
  stdout: process.stdout,
454
670
  stderr: process.stderr
455
671
  };
456
672
  }
457
673
 
458
- // src/core/errors.ts
459
- var CliError = class extends Error {
460
- code;
461
- exitCode;
462
- suggestedNextAction;
463
- constructor(options) {
464
- super(options.message);
465
- this.name = "CliError";
466
- this.code = options.code;
467
- this.exitCode = options.exitCode ?? 1;
468
- this.cause = options.cause;
469
- this.suggestedNextAction = options.suggestedNextAction;
470
- }
471
- };
472
- var InternalBugError = class extends CliError {
473
- constructor(cause) {
474
- const message = cause instanceof Error ? cause.message : String(cause);
475
- super({
476
- code: "INTERNAL_BUG",
477
- message: `Unexpected error: ${message}`,
478
- cause
479
- });
480
- this.name = "InternalBugError";
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;
481
693
  }
482
- };
483
- function toCliError(err) {
484
- if (err instanceof CliError) return err;
485
- return new InternalBugError(err);
486
694
  }
487
-
488
- // src/core/context.ts
489
695
  function createCommandContext(options = {}) {
490
696
  const io = options.io ?? defaultCliIo();
491
697
  const env = options.env ?? process.env;
@@ -502,7 +708,8 @@ function createCommandContext(options = {}) {
502
708
  throw new CliError({
503
709
  code: err.code,
504
710
  message: err.message,
505
- cause: err
711
+ cause: err,
712
+ suggestedNextAction: bootstrapSuggestedNextAction(err.code)
506
713
  });
507
714
  }
508
715
  throw err;
@@ -514,16 +721,14 @@ function createCommandContext(options = {}) {
514
721
 
515
722
  // src/core/renderer.ts
516
723
  function renderError(io, err) {
517
- const body = {
518
- ok: false,
519
- code: err.code,
520
- message: err.message
521
- };
724
+ io.stderr.write(`Error: ${err.message}
725
+ `);
726
+ io.stderr.write(`Code: ${err.code}
727
+ `);
522
728
  if (err.suggestedNextAction) {
523
- body.suggested_next_action = err.suggestedNextAction;
524
- }
525
- io.stderr.write(`${JSON.stringify(body)}
729
+ io.stderr.write(`Next action: ${err.suggestedNextAction}
526
730
  `);
731
+ }
527
732
  }
528
733
  function writeText(io, text) {
529
734
  io.stdout.write(text);
@@ -543,16 +748,23 @@ function registerCliCommand(parent, command, runtimeOptions = {}) {
543
748
  child.argument(arg);
544
749
  }
545
750
  for (const option of command.spec.options ?? []) {
546
- child.option(option.flags, option.description);
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);
547
759
  }
548
760
  child.action(async (...args) => {
549
761
  const ctx = createCommandContext(runtimeOptions);
550
762
  try {
551
763
  await command.handler(ctx, ...args);
552
764
  } catch (err) {
553
- const cliError = toCliError(err);
554
- renderError(ctx.io, cliError);
555
- throw new CliExit(cliError.exitCode);
765
+ const cliError2 = toCliError(err);
766
+ renderError(ctx.io, cliError2);
767
+ throw new CliExit(cliError2.exitCode);
556
768
  }
557
769
  });
558
770
  }
@@ -561,8 +773,7 @@ function registerCliCommand(parent, command, runtimeOptions = {}) {
561
773
  var whoamiCommand = defineCommand(
562
774
  {
563
775
  name: "whoami",
564
- description: "Print the agent context resolved from env (token value redacted)",
565
- rationale: "denied"
776
+ description: "Print the agent context resolved from env (token value redacted)"
566
777
  },
567
778
  (ctx) => {
568
779
  const agentContext = ctx.loadAgentContext();
@@ -698,17 +909,26 @@ function describeListResult(reason, serverUrl) {
698
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`.";
699
910
  }
700
911
  }
701
- function registerAgentListCommand(parent) {
702
- parent.command("list").description(
703
- "List Slock agents the user can mint credentials for (after a device-code login)."
704
- ).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
+ }
705
925
  let userSession;
706
926
  try {
707
927
  userSession = await runDeviceCodeLogin({
708
928
  serverUrl: options.server,
709
929
  ...options.clientName ? { clientName: options.clientName } : {},
710
930
  onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
711
- process.stderr.write(
931
+ ctx.io.stderr.write(
712
932
  `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
713
933
  `
714
934
  );
@@ -716,7 +936,7 @@ function registerAgentListCommand(parent) {
716
936
  });
717
937
  } catch (err) {
718
938
  if (err instanceof DeviceCodeLoginError) {
719
- fail(err.code, err.message);
939
+ throw cliError(err.code, err.message, { cause: err });
720
940
  }
721
941
  throw err;
722
942
  }
@@ -736,7 +956,7 @@ function registerAgentListCommand(parent) {
736
956
  body = await res.json();
737
957
  } catch {
738
958
  }
739
- fail(
959
+ throw cliError(
740
960
  body?.code ?? `list_failed_${res.status}`,
741
961
  body?.error ?? `Failed to list manageable agents (status ${res.status}).`
742
962
  );
@@ -745,7 +965,7 @@ function registerAgentListCommand(parent) {
745
965
  const agents = payload.data?.agents ?? [];
746
966
  const reason = payload.data?.reason ?? (agents.length > 0 ? "ok" : "no_agents_on_manageable_servers");
747
967
  const suggestedNextAction = describeListResult(reason, options.server);
748
- emit({
968
+ writeJson(ctx.io, {
749
969
  ok: true,
750
970
  data: {
751
971
  agents,
@@ -754,28 +974,46 @@ function registerAgentListCommand(parent) {
754
974
  suggested_next_action: suggestedNextAction
755
975
  }
756
976
  });
757
- });
977
+ }
978
+ );
979
+ function registerAgentListCommand(parent, runtimeOptions = {}) {
980
+ registerCliCommand(parent, agentListCommand, runtimeOptions);
758
981
  }
759
982
 
760
983
  // src/commands/agent/login.ts
761
984
  import { mkdir, stat, writeFile } from "fs/promises";
762
- import path2 from "path";
985
+ import path3 from "path";
763
986
  import { fetch as undiciFetch2 } from "undici";
764
- function registerAgentLoginCommand(parent) {
765
- parent.command("login").description(
766
- "Sign this CLI in as a specific Slock agent via the device-code login grant."
767
- ).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
+ }
768
1006
  const invalidShape = describeInvalidAgentIdShape(options.agent);
769
1007
  if (invalidShape) {
770
- fail("INVALID_AGENT_ID", invalidShape, {
1008
+ throw cliError("INVALID_AGENT_ID", invalidShape, {
771
1009
  suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
772
1010
  });
773
1011
  }
774
1012
  const profileSlug = options.profileSlug ?? options.agent;
775
1013
  const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
776
- const credentialPath = path2.join(profileDir, "credential.json");
1014
+ const credentialPath = path3.join(profileDir, "credential.json");
777
1015
  if (await profileFileExists(credentialPath)) {
778
- fail(
1016
+ throw cliError(
779
1017
  "PROFILE_ALREADY_EXISTS",
780
1018
  `Profile '${profileSlug}' already has a credential at ${credentialPath}.`,
781
1019
  {
@@ -789,7 +1027,7 @@ function registerAgentLoginCommand(parent) {
789
1027
  serverUrl: options.server,
790
1028
  ...options.clientName ? { clientName: options.clientName } : {},
791
1029
  onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
792
- process.stderr.write(
1030
+ ctx.io.stderr.write(
793
1031
  `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
794
1032
  `
795
1033
  );
@@ -797,7 +1035,7 @@ function registerAgentLoginCommand(parent) {
797
1035
  });
798
1036
  } catch (err) {
799
1037
  if (err instanceof DeviceCodeLoginError) {
800
- fail(err.code, err.message);
1038
+ throw cliError(err.code, err.message, { cause: err });
801
1039
  }
802
1040
  throw err;
803
1041
  }
@@ -816,7 +1054,7 @@ function registerAgentLoginCommand(parent) {
816
1054
  const body = await safeJson2(mintRes);
817
1055
  const code = body?.code ?? `mint_failed_${mintRes.status}`;
818
1056
  const detail = describeMintError(code, options.server);
819
- fail(
1057
+ throw cliError(
820
1058
  code,
821
1059
  detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
822
1060
  detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
@@ -824,7 +1062,7 @@ function registerAgentLoginCommand(parent) {
824
1062
  }
825
1063
  const minted = await mintRes.json();
826
1064
  if (!minted.apiKey || !minted.agentId || !minted.serverId) {
827
- fail(
1065
+ throw cliError(
828
1066
  "mint_response_invalid",
829
1067
  "Server mint response was missing apiKey / agentId / serverId."
830
1068
  );
@@ -849,7 +1087,7 @@ function registerAgentLoginCommand(parent) {
849
1087
  ) + "\n",
850
1088
  { mode: 384 }
851
1089
  );
852
- emit({
1090
+ writeJson(ctx.io, {
853
1091
  ok: true,
854
1092
  data: {
855
1093
  agentId: minted.agentId,
@@ -861,7 +1099,10 @@ function registerAgentLoginCommand(parent) {
861
1099
  credentialPath
862
1100
  }
863
1101
  });
864
- });
1102
+ }
1103
+ );
1104
+ function registerAgentLoginCommand(parent, runtimeOptions = {}) {
1105
+ registerCliCommand(parent, agentLoginCommand, runtimeOptions);
865
1106
  }
866
1107
  async function safeJson2(res) {
867
1108
  try {
@@ -944,30 +1185,30 @@ var CHANNEL_MESSAGE_RE = new RegExp(
944
1185
 
945
1186
  // ../shared/src/tracing/index.ts
946
1187
  var DEFAULT_TRACE_FLAGS = "00";
947
- var TRACE_ID_HEX_LENGTH = 32;
948
- var SPAN_ID_HEX_LENGTH = 16;
1188
+ var TRACE_ID_HEX_LENGTH2 = 32;
1189
+ var SPAN_ID_HEX_LENGTH2 = 16;
949
1190
  var TRACE_FLAGS_HEX_LENGTH = 2;
950
1191
  var TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
951
1192
  var SPAN_ID_PATTERN = /^[0-9a-f]{16}$/;
952
1193
  var TRACE_FLAGS_PATTERN = /^[0-9a-f]{2}$/;
953
1194
  function isTraceId(value) {
954
- 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);
955
1196
  }
956
1197
  function isSpanId(value) {
957
- 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);
958
1199
  }
959
1200
  function isTraceFlags(value) {
960
1201
  return TRACE_FLAGS_PATTERN.test(value);
961
1202
  }
962
1203
  function assertTraceContext(context) {
963
1204
  if (!isTraceId(context.traceId)) {
964
- 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`);
965
1206
  }
966
1207
  if (!isSpanId(context.spanId)) {
967
- 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`);
968
1209
  }
969
1210
  if (context.parentSpanId !== null && !isSpanId(context.parentSpanId)) {
970
- 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`);
971
1212
  }
972
1213
  if (!isTraceFlags(context.traceFlags)) {
973
1214
  throw new Error(`Invalid traceFlags: expected ${TRACE_FLAGS_HEX_LENGTH} lowercase hex chars`);
@@ -1007,19 +1248,19 @@ var NoopActiveSpan = class {
1007
1248
  };
1008
1249
  var noopTracer = new NoopTracer();
1009
1250
  function generateTraceId() {
1010
- return randomNonZeroHex(TRACE_ID_HEX_LENGTH);
1251
+ return randomNonZeroHex(TRACE_ID_HEX_LENGTH2);
1011
1252
  }
1012
1253
  function generateSpanId() {
1013
- return randomNonZeroHex(SPAN_ID_HEX_LENGTH);
1254
+ return randomNonZeroHex(SPAN_ID_HEX_LENGTH2);
1014
1255
  }
1015
1256
  function randomNonZeroHex(length) {
1016
- let value = randomHex(length);
1257
+ let value = randomHex2(length);
1017
1258
  while (value === "0".repeat(length)) {
1018
- value = randomHex(length);
1259
+ value = randomHex2(length);
1019
1260
  }
1020
1261
  return value;
1021
1262
  }
1022
- function randomHex(length) {
1263
+ function randomHex2(length) {
1023
1264
  const bytes = new Uint8Array(length / 2);
1024
1265
  globalThis.crypto.getRandomValues(bytes);
1025
1266
  return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -1795,10 +2036,10 @@ function mergeDefs(...defs) {
1795
2036
  function cloneDef(schema) {
1796
2037
  return mergeDefs(schema._zod.def);
1797
2038
  }
1798
- function getElementAtPath(obj, path4) {
1799
- if (!path4)
2039
+ function getElementAtPath(obj, path6) {
2040
+ if (!path6)
1800
2041
  return obj;
1801
- return path4.reduce((acc, key) => acc?.[key], obj);
2042
+ return path6.reduce((acc, key) => acc?.[key], obj);
1802
2043
  }
1803
2044
  function promiseAllObject(promisesObj) {
1804
2045
  const keys = Object.keys(promisesObj);
@@ -2181,11 +2422,11 @@ function aborted(x, startIndex = 0) {
2181
2422
  }
2182
2423
  return false;
2183
2424
  }
2184
- function prefixIssues(path4, issues) {
2425
+ function prefixIssues(path6, issues) {
2185
2426
  return issues.map((iss) => {
2186
2427
  var _a2;
2187
2428
  (_a2 = iss).path ?? (_a2.path = []);
2188
- iss.path.unshift(path4);
2429
+ iss.path.unshift(path6);
2189
2430
  return iss;
2190
2431
  });
2191
2432
  }
@@ -2368,7 +2609,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
2368
2609
  }
2369
2610
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
2370
2611
  const result = { errors: [] };
2371
- const processError = (error49, path4 = []) => {
2612
+ const processError = (error49, path6 = []) => {
2372
2613
  var _a2, _b;
2373
2614
  for (const issue2 of error49.issues) {
2374
2615
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -2378,7 +2619,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
2378
2619
  } else if (issue2.code === "invalid_element") {
2379
2620
  processError({ issues: issue2.issues }, issue2.path);
2380
2621
  } else {
2381
- const fullpath = [...path4, ...issue2.path];
2622
+ const fullpath = [...path6, ...issue2.path];
2382
2623
  if (fullpath.length === 0) {
2383
2624
  result.errors.push(mapper(issue2));
2384
2625
  continue;
@@ -2410,8 +2651,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
2410
2651
  }
2411
2652
  function toDotPath(_path) {
2412
2653
  const segs = [];
2413
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2414
- for (const seg of path4) {
2654
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2655
+ for (const seg of path6) {
2415
2656
  if (typeof seg === "number")
2416
2657
  segs.push(`[${seg}]`);
2417
2658
  else if (typeof seg === "symbol")
@@ -14388,13 +14629,13 @@ function resolveRef(ref, ctx) {
14388
14629
  if (!ref.startsWith("#")) {
14389
14630
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14390
14631
  }
14391
- const path4 = ref.slice(1).split("/").filter(Boolean);
14392
- if (path4.length === 0) {
14632
+ const path6 = ref.slice(1).split("/").filter(Boolean);
14633
+ if (path6.length === 0) {
14393
14634
  return ctx.rootSchema;
14394
14635
  }
14395
14636
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14396
- if (path4[0] === defsKey) {
14397
- const key = path4[1];
14637
+ if (path6[0] === defsKey) {
14638
+ const key = path6[1];
14398
14639
  if (!key || !ctx.defs[key]) {
14399
14640
  throw new Error(`Reference not found: ${ref}`);
14400
14641
  }
@@ -15044,52 +15285,60 @@ async function resolveActionInput(input = process.stdin) {
15044
15285
  );
15045
15286
  }
15046
15287
  }
15047
- function registerActionPrepareCommand(parent) {
15048
- parent.command("prepare").description("Prepare an action card for a human to commit (B-mode quick-commit shortcut)").requiredOption(
15049
- "--target <target>",
15050
- "Channel/DM/thread target to post the card. Same format as slock message send: '#channel', 'dm:@peer', '#channel:shortid'"
15051
- ).action(async (opts) => {
15052
- let ctx;
15053
- try {
15054
- ctx = loadAgentContext();
15055
- } catch (err) {
15056
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15057
- 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");
15058
15302
  }
15303
+ const agentContext = ctx.loadAgentContext();
15059
15304
  let raw;
15060
15305
  try {
15061
- raw = await resolveActionInput();
15306
+ raw = await resolveActionInput(ctx.io.stdin ?? process.stdin);
15062
15307
  } catch (err) {
15063
- if (err instanceof PrepareActionInputError) fail(err.code, err.message);
15308
+ if (err instanceof PrepareActionInputError) throw cliError(err.code, err.message, { cause: err });
15064
15309
  throw err;
15065
15310
  }
15066
15311
  const parsed = actionCardActionSchema.safeParse(raw);
15067
15312
  if (!parsed.success) {
15068
15313
  const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
15069
- fail("INVALID_ACTION", `Action failed validation: ${issues}`);
15314
+ throw cliError("INVALID_ACTION", `Action failed validation: ${issues}`);
15070
15315
  }
15071
15316
  const crossFieldError = validateActionCardAction(parsed.data);
15072
15317
  if (crossFieldError) {
15073
- fail("INVALID_ACTION", `Action failed validation: ${crossFieldError}`);
15318
+ throw cliError("INVALID_ACTION", `Action failed validation: ${crossFieldError}`);
15074
15319
  }
15075
- const client = new ApiClient(ctx);
15320
+ const client = ctx.createApiClient(agentContext);
15076
15321
  const res = await client.request(
15077
15322
  "POST",
15078
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/prepare-action`,
15323
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/prepare-action`,
15079
15324
  { target: opts.target, action: parsed.data }
15080
15325
  );
15081
15326
  if (!res.ok) {
15082
15327
  const code = res.status >= 500 ? "SERVER_5XX" : "PREPARE_FAILED";
15083
- fail(code, res.error ?? `HTTP ${res.status}`);
15328
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
15084
15329
  }
15085
15330
  const data = res.data;
15086
15331
  const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
15087
- process.stdout.write(
15332
+ writeText(
15333
+ ctx.io,
15088
15334
  shortId ? `Action card posted to ${opts.target} as message ${data.messageId} (short ${shortId}). The human can click the action verb to commit.
15089
15335
  ` : `Action card posted to ${opts.target}.
15090
15336
  `
15091
15337
  );
15092
- });
15338
+ }
15339
+ );
15340
+ function registerActionPrepareCommand(parent, runtimeOptions = {}) {
15341
+ registerCliCommand(parent, actionPrepareCommand, runtimeOptions);
15093
15342
  }
15094
15343
 
15095
15344
  // src/commands/server/_format.ts
@@ -15196,12 +15445,9 @@ var channelMembersCommand = defineCommand(
15196
15445
  {
15197
15446
  name: "members",
15198
15447
  description: "List agents and humans who are members of a channel, DM, or thread",
15199
- rationale: "denied",
15200
15448
  arguments: ["<target>"]
15201
15449
  },
15202
15450
  async (ctx, target) => {
15203
- const agentContext = ctx.loadAgentContext();
15204
- const client = ctx.createApiClient(agentContext);
15205
15451
  const channel = String(target || "").trim();
15206
15452
  if (!channel) {
15207
15453
  throw new CliError({
@@ -15209,6 +15455,8 @@ var channelMembersCommand = defineCommand(
15209
15455
  message: "target is required"
15210
15456
  });
15211
15457
  }
15458
+ const agentContext = ctx.loadAgentContext();
15459
+ const client = ctx.createApiClient(agentContext);
15212
15460
  const encoded = encodeURIComponent(channel);
15213
15461
  const res = await client.request(
15214
15462
  "GET",
@@ -15244,7 +15492,6 @@ var channelLeaveCommand = defineCommand(
15244
15492
  {
15245
15493
  name: "leave",
15246
15494
  description: "Leave a regular channel you have joined",
15247
- rationale: "required",
15248
15495
  options: [
15249
15496
  {
15250
15497
  flags: "--target <target>",
@@ -15312,7 +15559,6 @@ var channelJoinCommand = defineCommand(
15312
15559
  {
15313
15560
  name: "join",
15314
15561
  description: "Join a visible public channel",
15315
- rationale: "required",
15316
15562
  options: [
15317
15563
  {
15318
15564
  flags: "--target <target>",
@@ -15373,8 +15619,7 @@ function registerChannelJoinCommand(parent, runtimeOptions) {
15373
15619
  var serverInfoCommand = defineCommand(
15374
15620
  {
15375
15621
  name: "info",
15376
- description: "List channels, agents, and humans on the current server",
15377
- rationale: "denied"
15622
+ description: "List channels, agents, and humans on the current server"
15378
15623
  },
15379
15624
  async (ctx) => {
15380
15625
  const agentContext = ctx.loadAgentContext();
@@ -15417,8 +15662,8 @@ function formatKnowledgeStdout(content) {
15417
15662
  return content.endsWith("\n") ? content : `${content}
15418
15663
  `;
15419
15664
  }
15420
- function toKnowledgeErrorCode(errorCode, status) {
15421
- switch (errorCode) {
15665
+ function toKnowledgeErrorCode(errorCode2, status) {
15666
+ switch (errorCode2) {
15422
15667
  case "INVALID_JSON_RESPONSE":
15423
15668
  case "SCOPE_DENIED":
15424
15669
  case "knowledge_agent_missing":
@@ -15430,7 +15675,7 @@ function toKnowledgeErrorCode(errorCode, status) {
15430
15675
  case "knowledge_trace_id_invalid":
15431
15676
  case "knowledge_turn_id_invalid":
15432
15677
  case "unsupported_capability":
15433
- return errorCode;
15678
+ return errorCode2;
15434
15679
  default:
15435
15680
  return status >= 500 ? "SERVER_5XX" : "KNOWLEDGE_GET_FAILED";
15436
15681
  }
@@ -15439,7 +15684,6 @@ var knowledgeGetCommand = defineCommand(
15439
15684
  {
15440
15685
  name: "get",
15441
15686
  description: "Fetch an agent knowledge topic from the current server",
15442
- rationale: "optional",
15443
15687
  arguments: ["<topic>"],
15444
15688
  options: [
15445
15689
  {
@@ -15466,7 +15710,8 @@ var knowledgeGetCommand = defineCommand(
15466
15710
  if (!res.ok) {
15467
15711
  throw new CliError({
15468
15712
  code: toKnowledgeErrorCode(res.errorCode, res.status),
15469
- message: res.error ?? `HTTP ${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)
15470
15715
  });
15471
15716
  }
15472
15717
  const data = res.data;
@@ -15512,7 +15757,6 @@ var threadUnfollowCommand = defineCommand(
15512
15757
  {
15513
15758
  name: "unfollow",
15514
15759
  description: "Stop following a thread you no longer need ordinary delivery for",
15515
- rationale: "required",
15516
15760
  options: [
15517
15761
  {
15518
15762
  flags: "--target <target>",
@@ -15718,10 +15962,10 @@ ${opts.heldAction} Review the bounded context shown here, then choose one path.$
15718
15962
  // src/commands/message/_continueDraftState.ts
15719
15963
  import fs2 from "fs";
15720
15964
  import os2 from "os";
15721
- import path3 from "path";
15965
+ import path4 from "path";
15722
15966
  var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
15723
15967
  function stateFilePath(agentId) {
15724
- 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");
15725
15969
  }
15726
15970
  function readState(agentId) {
15727
15971
  const filePath = stateFilePath(agentId);
@@ -15735,7 +15979,7 @@ function readState(agentId) {
15735
15979
  }
15736
15980
  function writeState(agentId, state) {
15737
15981
  const filePath = stateFilePath(agentId);
15738
- fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
15982
+ fs2.mkdirSync(path4.dirname(filePath), { recursive: true });
15739
15983
  fs2.writeFileSync(filePath, JSON.stringify(state), "utf8");
15740
15984
  }
15741
15985
  function getSavedDraft(agentId, target) {
@@ -15887,48 +16131,68 @@ To send the current draft unchanged:
15887
16131
  `
15888
16132
  });
15889
16133
  }
15890
- function registerSendCommand(parent) {
15891
- 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(
15892
- "--attachment-id <id>",
15893
- "Attachment id to link (repeatable). Get one from `slock attachment upload`.",
15894
- (value, prev = []) => prev.concat(value)
15895
- ).action(async (positionalContent, opts) => {
15896
- try {
15897
- rejectArgContent(positionalContent, opts);
15898
- } catch (err) {
15899
- if (err instanceof SendContentError) fail(err.code, err.message);
15900
- 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");
15901
16154
  }
15902
- let ctx;
15903
16155
  try {
15904
- ctx = loadAgentContext();
16156
+ rejectArgContent(positionalContent, opts);
15905
16157
  } catch (err) {
15906
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16158
+ if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
15907
16159
  throw err;
15908
16160
  }
15909
- const client = new ApiClient(ctx);
15910
16161
  try {
15911
16162
  validateDraftSendFlags(opts);
15912
16163
  } catch (err) {
15913
- if (err instanceof SendContentError) fail(err.code, err.message);
16164
+ if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
15914
16165
  throw err;
15915
16166
  }
15916
16167
  let content;
15917
- let outgoingContent;
16168
+ let outgoingContent = "";
15918
16169
  let outgoingAttachmentIds = [];
15919
16170
  let previousDraftReholdCount = 0;
15920
16171
  let seenUpToSeq;
15921
16172
  if (opts.sendDraft) {
15922
- content = await resolveOptionalSendContent();
16173
+ content = await resolveOptionalSendContent(ctx.io.stdin ?? process.stdin);
15923
16174
  try {
15924
16175
  rejectSendDraftStdin(content, opts.target);
15925
16176
  } catch (err) {
15926
- 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 });
15927
16185
  throw err;
15928
16186
  }
15929
- 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);
15930
16194
  if (!savedDraft) {
15931
- fail(
16195
+ throw cliError(
15932
16196
  "SEND_DRAFT_NOT_FOUND",
15933
16197
  [
15934
16198
  "No saved draft exists for this target.",
@@ -15945,15 +16209,7 @@ function registerSendCommand(parent) {
15945
16209
  previousDraftReholdCount = savedDraft.reholdCount;
15946
16210
  seenUpToSeq = savedDraft.seenUpToSeq;
15947
16211
  } else {
15948
- try {
15949
- content = await resolveSendContent();
15950
- } catch (err) {
15951
- if (err instanceof SendContentError) fail(err.code, err.message);
15952
- throw err;
15953
- }
15954
- outgoingContent = content;
15955
- outgoingAttachmentIds = opts.attachmentId && opts.attachmentId.length > 0 ? opts.attachmentId : [];
15956
- const previousDraft = getSavedDraft(ctx.agentId, opts.target);
16212
+ const previousDraft = getSavedDraft(agentContext.agentId, opts.target);
15957
16213
  previousDraftReholdCount = previousDraft?.reholdCount ?? 0;
15958
16214
  seenUpToSeq = previousDraft?.seenUpToSeq;
15959
16215
  }
@@ -15976,26 +16232,26 @@ function registerSendCommand(parent) {
15976
16232
  }
15977
16233
  const res = await client.request(
15978
16234
  "POST",
15979
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/send`,
16235
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/send`,
15980
16236
  body
15981
16237
  );
15982
16238
  if (!res.ok) {
15983
16239
  const code = res.status >= 500 ? "SERVER_5XX" : "SEND_FAILED";
15984
- fail(code, res.error ?? `HTTP ${res.status}`);
16240
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
15985
16241
  }
15986
16242
  const data = res.data;
15987
16243
  if (data.state === "held") {
15988
- setSavedDraft(ctx.agentId, opts.target, {
16244
+ setSavedDraft(agentContext.agentId, opts.target, {
15989
16245
  content: outgoingContent,
15990
16246
  attachmentIds: outgoingAttachmentIds,
15991
16247
  savedAt: Date.now(),
15992
16248
  reholdCount: previousDraftReholdCount + 1,
15993
16249
  seenUpToSeq: data.seenUpToSeq
15994
16250
  });
15995
- process.stdout.write(formatHeldSendOutput(opts.target, data));
16251
+ writeText(ctx.io, formatHeldSendOutput(opts.target, data));
15996
16252
  return;
15997
16253
  }
15998
- clearSavedDraft(ctx.agentId, opts.target);
16254
+ clearSavedDraft(agentContext.agentId, opts.target);
15999
16255
  const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
16000
16256
  const replyHint = shortId ? ` (to reply in this message's thread, use target "${opts.target.includes(":") ? opts.target : opts.target + ":" + shortId}")` : "";
16001
16257
  let unreadSection = "";
@@ -16005,9 +16261,12 @@ function registerSendCommand(parent) {
16005
16261
  --- New messages you may have missed ---
16006
16262
  ${formatMessages(data.recentUnread)}`;
16007
16263
  }
16008
- 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}
16009
16265
  `);
16010
- });
16266
+ }
16267
+ );
16268
+ function registerSendCommand(parent, runtimeOptions = {}) {
16269
+ registerCliCommand(parent, messageSendCommand, runtimeOptions);
16011
16270
  }
16012
16271
 
16013
16272
  // src/commands/message/_inbox.ts
@@ -16017,8 +16276,8 @@ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
16017
16276
  const query = [];
16018
16277
  if (opts.block) query.push("block=true");
16019
16278
  if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
16020
- const path4 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
16021
- 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);
16022
16281
  if (!res.ok) {
16023
16282
  throw new CliError({
16024
16283
  code: res.status >= 500 ? "SERVER_5XX" : failCode,
@@ -16042,8 +16301,7 @@ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
16042
16301
  var messageCheckCommand = defineCommand(
16043
16302
  {
16044
16303
  name: "check",
16045
- description: "Drain the agent inbox (non-blocking). Acks delivered seqs before returning.",
16046
- rationale: "denied"
16304
+ description: "Drain the agent inbox (non-blocking). Acks delivered seqs before returning."
16047
16305
  },
16048
16306
  async (ctx) => {
16049
16307
  const agentContext = ctx.loadAgentContext();
@@ -16104,7 +16362,6 @@ var messageReadCommand = defineCommand(
16104
16362
  {
16105
16363
  name: "read",
16106
16364
  description: "Read message history for a channel, DM, or thread",
16107
- rationale: "denied",
16108
16365
  options: [
16109
16366
  { flags: "--channel <target>", description: "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'" },
16110
16367
  { flags: "--before <seq>", description: "Return messages strictly before this seq (paginate backwards)" },
@@ -16214,11 +16471,11 @@ function buildSearchPath(agentId, opts) {
16214
16471
  if (opts.limit !== void 0) params.set("limit", String(opts.limit));
16215
16472
  return `/internal/agent/${encodeURIComponent(agentId)}/search?${params.toString()}`;
16216
16473
  }
16217
- function toSearchErrorCode(errorCode, status) {
16218
- switch (errorCode) {
16474
+ function toSearchErrorCode(errorCode2, status) {
16475
+ switch (errorCode2) {
16219
16476
  case "SCOPE_DENIED":
16220
16477
  case "INVALID_JSON_RESPONSE":
16221
- return errorCode;
16478
+ return errorCode2;
16222
16479
  default:
16223
16480
  return status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED";
16224
16481
  }
@@ -16227,7 +16484,6 @@ var messageSearchCommand = defineCommand(
16227
16484
  {
16228
16485
  name: "search",
16229
16486
  description: "Search messages across channels the agent can see",
16230
- rationale: "denied",
16231
16487
  options: [
16232
16488
  { flags: "--query <q>", description: "Search query string" },
16233
16489
  { flags: "--channel <target>", description: "Restrict to a single channel/DM/thread" },
@@ -16268,40 +16524,48 @@ function normalizeReactionEmoji(value) {
16268
16524
  }
16269
16525
  return emoji3;
16270
16526
  }
16271
- function registerReactCommand(parent) {
16272
- 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", [
16273
- "",
16274
- "Agent guidance:",
16275
- " Use reactions sparingly. Prefer acknowledgement/follow-up signals like \u{1F440}.",
16276
- " Do not auto-react to every merge, deploy, or task completion with celebratory emoji."
16277
- ].join("\n")).action(async (opts) => {
16278
- let ctx;
16279
- try {
16280
- ctx = loadAgentContext();
16281
- } catch (err) {
16282
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16283
- 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");
16284
16544
  }
16285
16545
  let emoji3;
16286
16546
  try {
16287
16547
  emoji3 = normalizeReactionEmoji(opts.emoji);
16288
16548
  } catch (err) {
16289
- 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 });
16290
16550
  }
16291
- const client = new ApiClient(ctx);
16551
+ const agentContext = ctx.loadAgentContext();
16552
+ const client = ctx.createApiClient(agentContext);
16292
16553
  const res = await client.request(
16293
16554
  opts.remove ? "DELETE" : "POST",
16294
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/messages/${encodeURIComponent(opts.messageId)}/reactions`,
16555
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/messages/${encodeURIComponent(opts.messageId)}/reactions`,
16295
16556
  { emoji: emoji3 }
16296
16557
  );
16297
16558
  if (!res.ok) {
16298
16559
  const code = res.status >= 500 ? "SERVER_5XX" : "REACT_FAILED";
16299
- fail(code, res.error ?? `HTTP ${res.status}`);
16560
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16300
16561
  }
16301
16562
  const verb = opts.remove ? "removed from" : "added to";
16302
- 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)}.
16303
16564
  `);
16304
- });
16565
+ }
16566
+ );
16567
+ function registerReactCommand(parent, runtimeOptions = {}) {
16568
+ registerCliCommand(parent, messageReactCommand, runtimeOptions);
16305
16569
  }
16306
16570
 
16307
16571
  // src/commands/attachment/upload.ts
@@ -16390,59 +16654,65 @@ function formatBytes(bytes) {
16390
16654
  }
16391
16655
  return `${unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)}${units[unitIndex]}`;
16392
16656
  }
16393
- function registerAttachmentUploadCommand(parent) {
16394
- 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(
16395
- "--channel <target>",
16396
- "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
16397
- ).option("--mime-type <type>", "Explicit MIME type override, e.g. image/png").action(async (opts) => {
16398
- let ctx;
16399
- try {
16400
- ctx = loadAgentContext();
16401
- } catch (err) {
16402
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16403
- 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");
16404
16673
  }
16405
16674
  if (!existsSync(opts.path)) {
16406
- fail("INVALID_ARG", `--path does not exist: ${opts.path}`);
16675
+ throw cliError("INVALID_ARG", `--path does not exist: ${opts.path}`);
16407
16676
  }
16408
16677
  const stat2 = statSync(opts.path);
16409
16678
  if (!stat2.isFile()) {
16410
- fail("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
16679
+ throw cliError("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
16411
16680
  }
16412
16681
  try {
16413
16682
  validateUploadFileSize(stat2.size);
16414
16683
  } catch (err) {
16415
- if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
16684
+ if (err instanceof AttachmentUploadArgError) throw cliError(err.code, err.message, { cause: err });
16416
16685
  throw err;
16417
16686
  }
16418
16687
  if (!opts.channel) {
16419
- fail(
16688
+ throw cliError(
16420
16689
  "MISSING_CHANNEL",
16421
16690
  "v0 server requires a channel to attach the upload to. Pass --channel '#name', 'dm:@peer', or a thread target."
16422
16691
  );
16423
16692
  }
16424
- const client = new ApiClient(ctx);
16425
- const agentPath = `/internal/agent/${encodeURIComponent(ctx.agentId)}`;
16426
- const resolved = await client.request(
16427
- "POST",
16428
- `${agentPath}/resolve-channel`,
16429
- { target: opts.channel }
16430
- );
16431
- if (!resolved.ok || !resolved.data?.channelId) {
16432
- const code = resolved.status >= 500 ? "SERVER_5XX" : "RESOLVE_FAILED";
16433
- fail(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
16434
- }
16435
- const channelId = resolved.data.channelId;
16436
16693
  const buffer = readFileSync2(opts.path);
16437
16694
  const filename = basename(opts.path);
16438
16695
  let explicitMimeType;
16439
16696
  try {
16440
16697
  explicitMimeType = normalizeExplicitMimeType(opts.mimeType);
16441
16698
  } catch (err) {
16442
- if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
16699
+ if (err instanceof AttachmentUploadArgError) throw cliError(err.code, err.message, { cause: err });
16443
16700
  throw err;
16444
16701
  }
16445
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;
16446
16716
  const blob = new Blob([buffer], { type: uploadMimeType });
16447
16717
  const form = new FormData();
16448
16718
  form.append("file", blob, filename);
@@ -16457,15 +16727,18 @@ function registerAttachmentUploadCommand(parent) {
16457
16727
  );
16458
16728
  if (!res.ok) {
16459
16729
  const code = res.status >= 500 ? "SERVER_5XX" : "UPLOAD_FAILED";
16460
- fail(code, res.error ?? `HTTP ${res.status}`);
16730
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16461
16731
  }
16462
16732
  const d = res.data;
16463
- 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)
16464
16734
  Attachment ID: ${d.id}
16465
16735
 
16466
16736
  Use this ID with slock message send --attachment-id ${d.id} to include it in a message.
16467
16737
  `);
16468
- });
16738
+ }
16739
+ );
16740
+ function registerAttachmentUploadCommand(parent, runtimeOptions = {}) {
16741
+ registerCliCommand(parent, attachmentUploadCommand, runtimeOptions);
16469
16742
  }
16470
16743
 
16471
16744
  // src/commands/attachment/view.ts
@@ -16491,7 +16764,6 @@ var attachmentViewCommand = defineCommand(
16491
16764
  {
16492
16765
  name: "view",
16493
16766
  description: "Download an attachment by id and save it to a local path",
16494
- rationale: "denied",
16495
16767
  options: [
16496
16768
  { flags: "--id <attachmentId>", description: "Attachment UUID" },
16497
16769
  { flags: "--output <path>", description: "Local path to write the file to" }
@@ -16605,7 +16877,6 @@ var taskListCommand = defineCommand(
16605
16877
  {
16606
16878
  name: "list",
16607
16879
  description: "List tasks in a channel",
16608
- rationale: "denied",
16609
16880
  options: [
16610
16881
  { flags: "--channel <target>", description: "Channel target: '#channel'" },
16611
16882
  { flags: "--status <s>", description: "Filter: all|todo|in_progress|in_review|done (default: server-side)" }
@@ -16634,86 +16905,103 @@ function registerTaskListCommand(parent, runtimeOptions) {
16634
16905
  }
16635
16906
 
16636
16907
  // src/commands/task/create.ts
16637
- function registerTaskCreateCommand(parent) {
16638
- parent.command("create").description("Create one or more tasks in a channel").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption(
16639
- "--title <title>",
16640
- "Task title (repeatable for batch create)",
16641
- (value, prev = []) => prev.concat(value)
16642
- ).action(async (opts) => {
16643
- let ctx;
16644
- try {
16645
- ctx = loadAgentContext();
16646
- } catch (err) {
16647
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16648
- 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");
16649
16924
  }
16650
16925
  const titles = opts.title ?? [];
16651
- if (titles.length === 0) fail("INVALID_ARG", "--title is required (at least one)");
16652
- 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);
16653
16929
  const res = await client.request(
16654
16930
  "POST",
16655
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks`,
16931
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks`,
16656
16932
  { channel: opts.channel, tasks: titles.map((title) => ({ title })) }
16657
16933
  );
16658
16934
  if (!res.ok) {
16659
16935
  const code = res.status >= 500 ? "SERVER_5XX" : "CREATE_FAILED";
16660
- fail(code, res.error ?? `HTTP ${res.status}`);
16936
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16661
16937
  }
16662
- process.stdout.write(formatTasksCreated(opts.channel, res.data) + "\n");
16663
- });
16938
+ writeText(ctx.io, formatTasksCreated(opts.channel, res.data) + "\n");
16939
+ }
16940
+ );
16941
+ function registerTaskCreateCommand(parent, runtimeOptions = {}) {
16942
+ registerCliCommand(parent, taskCreateCommand, runtimeOptions);
16664
16943
  }
16665
16944
 
16666
16945
  // src/commands/task/claim.ts
16667
- function registerTaskClaimCommand(parent) {
16668
- parent.command("claim").description("Claim one or more tasks (by task number or message id)").requiredOption("--channel <target>", "Channel target: '#channel'").option(
16669
- "--number <n>",
16670
- "Task number to claim (repeatable)",
16671
- (value, prev = []) => prev.concat(value)
16672
- ).option(
16673
- "--message-id <id>",
16674
- "Message id (full or short) to claim (repeatable)",
16675
- (value, prev = []) => prev.concat(value)
16676
- ).action(async (opts) => {
16677
- let ctx;
16678
- try {
16679
- ctx = loadAgentContext();
16680
- } catch (err) {
16681
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16682
- 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");
16683
16967
  }
16684
16968
  const numbers = (opts.number ?? []).map((raw) => {
16685
16969
  const n = Number(raw);
16686
16970
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16687
- fail("INVALID_ARG", `--number must be a positive integer; got ${raw}`);
16971
+ throw cliError("INVALID_ARG", `--number must be a positive integer; got ${raw}`);
16688
16972
  }
16689
16973
  return n;
16690
16974
  });
16691
16975
  const messageIds = opts.messageId ?? [];
16692
16976
  if (numbers.length === 0 && messageIds.length === 0) {
16693
- fail("INVALID_ARG", "Provide at least one --number or --message-id");
16977
+ throw cliError("INVALID_ARG", "Provide at least one --number or --message-id");
16694
16978
  }
16695
16979
  const body = { channel: opts.channel };
16696
16980
  if (numbers.length > 0) body.task_numbers = numbers;
16697
16981
  if (messageIds.length > 0) body.message_ids = messageIds;
16698
- const client = new ApiClient(ctx);
16982
+ const agentContext = ctx.loadAgentContext();
16983
+ const client = ctx.createApiClient(agentContext);
16699
16984
  const res = await client.request(
16700
16985
  "POST",
16701
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks/claim`,
16986
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/claim`,
16702
16987
  body
16703
16988
  );
16704
16989
  if (!res.ok) {
16705
16990
  const code = res.status >= 500 ? "SERVER_5XX" : "CLAIM_FAILED";
16706
- fail(code, res.error ?? `HTTP ${res.status}`);
16991
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
16707
16992
  }
16708
16993
  if (isFreshnessHeldResponse(res.data)) {
16709
- process.stdout.write(formatFreshnessHoldOutput(opts.channel, res.data, {
16994
+ writeText(ctx.io, formatFreshnessHoldOutput(opts.channel, res.data, {
16710
16995
  heldAction: "Your task claim was not applied.",
16711
16996
  draftInstructions: "After reviewing the newer context, rerun the task claim command if it is still correct.\n"
16712
16997
  }));
16713
16998
  return;
16714
16999
  }
16715
- process.stdout.write(formatClaimResults(opts.channel, res.data) + "\n");
16716
- });
17000
+ writeText(ctx.io, formatClaimResults(opts.channel, res.data) + "\n");
17001
+ }
17002
+ );
17003
+ function registerTaskClaimCommand(parent, runtimeOptions = {}) {
17004
+ registerCliCommand(parent, taskClaimCommand, runtimeOptions);
16717
17005
  }
16718
17006
 
16719
17007
  // src/commands/task/unclaim.ts
@@ -16741,7 +17029,6 @@ var taskUnclaimCommand = defineCommand(
16741
17029
  {
16742
17030
  name: "unclaim",
16743
17031
  description: "Release a previously-claimed task",
16744
- rationale: "required",
16745
17032
  options: [
16746
17033
  { flags: "--channel <target>", description: "Channel target: '#channel'" },
16747
17034
  { flags: "--number <n>", description: "Task number to unclaim" }
@@ -16809,7 +17096,6 @@ var taskUpdateCommand = defineCommand(
16809
17096
  {
16810
17097
  name: "update",
16811
17098
  description: "Update task status",
16812
- rationale: "required",
16813
17099
  options: [
16814
17100
  { flags: "--channel <target>", description: "Channel target: '#channel'" },
16815
17101
  { flags: "--number <n>", description: "Task number to update" },
@@ -16934,7 +17220,6 @@ var profileShowCommand = defineCommand(
16934
17220
  {
16935
17221
  name: "show",
16936
17222
  description: "Show a profile. Omit the target to show your own profile.",
16937
- rationale: "denied",
16938
17223
  arguments: ["[target]"],
16939
17224
  options: [{ flags: "--json", description: "Emit machine-readable JSON" }]
16940
17225
  },
@@ -17003,14 +17288,14 @@ function inferImageMimeType(filename, buffer) {
17003
17288
  }
17004
17289
  function readAvatarFile(avatarFile) {
17005
17290
  if (!existsSync2(avatarFile)) {
17006
- fail("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
17291
+ throw cliError("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
17007
17292
  }
17008
17293
  const stat2 = statSync2(avatarFile);
17009
17294
  if (!stat2.isFile()) {
17010
- 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}`);
17011
17296
  }
17012
17297
  if (stat2.size > MAX_PROFILE_AVATAR_BYTES) {
17013
- fail(
17298
+ throw cliError(
17014
17299
  "PROFILE_AVATAR_TOO_LARGE",
17015
17300
  `Avatar file is ${stat2.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
17016
17301
  );
@@ -17019,7 +17304,7 @@ function readAvatarFile(avatarFile) {
17019
17304
  const filename = basename2(avatarFile);
17020
17305
  const mimeType = inferImageMimeType(filename, buffer);
17021
17306
  if (!mimeType || !PROFILE_AVATAR_MIME_TYPES.has(mimeType)) {
17022
- fail(
17307
+ throw cliError(
17023
17308
  "PROFILE_AVATAR_BAD_FORMAT",
17024
17309
  "Avatar must be a JPEG, PNG, GIF, or WebP image"
17025
17310
  );
@@ -17029,31 +17314,35 @@ function readAvatarFile(avatarFile) {
17029
17314
  function normalizeAvatarUrl(avatarUrl) {
17030
17315
  const trimmed = avatarUrl.trim();
17031
17316
  if (trimmed.length === 0) {
17032
- fail("INVALID_ARG", "--avatar-url must not be empty");
17317
+ throw cliError("INVALID_ARG", "--avatar-url must not be empty");
17033
17318
  }
17034
17319
  if (!trimmed.startsWith("pixel:")) {
17035
- 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");
17036
17321
  }
17037
17322
  return trimmed;
17038
17323
  }
17039
- function registerProfileUpdateCommand(parent) {
17040
- 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) => {
17041
- let ctx;
17042
- try {
17043
- ctx = loadAgentContext();
17044
- } catch (err) {
17045
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17046
- throw err;
17047
- }
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) => {
17048
17337
  const hasAvatar = opts.avatarFile !== void 0;
17049
17338
  const hasAvatarUrl = opts.avatarUrl !== void 0;
17050
17339
  const hasDisplayName = opts.displayName !== void 0;
17051
17340
  const hasDescription = opts.description !== void 0;
17052
17341
  if (!hasAvatar && !hasAvatarUrl && !hasDisplayName && !hasDescription) {
17053
- 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");
17054
17343
  }
17055
17344
  if (hasAvatar && hasAvatarUrl) {
17056
- 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");
17057
17346
  }
17058
17347
  let normalizedAvatarUrl;
17059
17348
  if (hasAvatarUrl) {
@@ -17063,21 +17352,23 @@ function registerProfileUpdateCommand(parent) {
17063
17352
  if (hasDisplayName) {
17064
17353
  trimmedDisplayName = opts.displayName.trim();
17065
17354
  if (trimmedDisplayName.length === 0) {
17066
- fail("INVALID_ARG", "--display-name must not be empty");
17355
+ throw cliError("INVALID_ARG", "--display-name must not be empty");
17067
17356
  }
17068
17357
  if (trimmedDisplayName.length > MAX_PROFILE_DISPLAY_NAME_LENGTH) {
17069
- 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`);
17070
17359
  }
17071
17360
  }
17072
17361
  if (hasDescription) {
17073
17362
  if (opts.description.length === 0) {
17074
- fail("INVALID_ARG", "--description must not be empty");
17363
+ throw cliError("INVALID_ARG", "--description must not be empty");
17075
17364
  }
17076
17365
  if (opts.description.length > MAX_PROFILE_DESCRIPTION_LENGTH) {
17077
- 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`);
17078
17367
  }
17079
17368
  }
17080
- const client = new ApiClient(ctx);
17369
+ const avatar = hasAvatar ? readAvatarFile(opts.avatarFile) : null;
17370
+ const agentContext = ctx.loadAgentContext();
17371
+ const client = ctx.createApiClient(agentContext);
17081
17372
  let latestProfile = null;
17082
17373
  if (hasAvatarUrl || hasDisplayName || hasDescription) {
17083
17374
  const body = {};
@@ -17092,41 +17383,43 @@ function registerProfileUpdateCommand(parent) {
17092
17383
  }
17093
17384
  const res = await client.request(
17094
17385
  "POST",
17095
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile`,
17386
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/profile`,
17096
17387
  body
17097
17388
  );
17098
17389
  if (!res.ok || !res.data) {
17099
17390
  const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
17100
- fail(code, res.error ?? `HTTP ${res.status}`);
17391
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17101
17392
  }
17102
17393
  latestProfile = res.data;
17103
17394
  }
17104
17395
  if (hasAvatar) {
17105
- const avatar = readAvatarFile(opts.avatarFile);
17106
17396
  const form = new FormData();
17107
17397
  const avatarBytes = Uint8Array.from(avatar.buffer);
17108
17398
  form.append("avatar", new Blob([avatarBytes], { type: avatar.mimeType }), avatar.filename);
17109
17399
  const res = await client.requestMultipart(
17110
17400
  "POST",
17111
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile/avatar`,
17401
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/profile/avatar`,
17112
17402
  form
17113
17403
  );
17114
17404
  if (!res.ok || !res.data) {
17115
17405
  const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
17116
- fail(code, res.error ?? `HTTP ${res.status}`);
17406
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17117
17407
  }
17118
17408
  latestProfile = res.data;
17119
17409
  }
17120
17410
  if (!latestProfile) {
17121
- fail("PROFILE_UPDATE_FAILED", "No profile returned from server");
17411
+ throw cliError("PROFILE_UPDATE_FAILED", "No profile returned from server");
17122
17412
  }
17123
17413
  if (opts.json) {
17124
- emit({ ok: true, data: latestProfile });
17414
+ writeJson(ctx.io, { ok: true, data: latestProfile });
17125
17415
  return;
17126
17416
  }
17127
- process.stdout.write(`${formatProfile(latestProfile)}
17417
+ writeText(ctx.io, `${formatProfile(latestProfile)}
17128
17418
  `);
17129
- });
17419
+ }
17420
+ );
17421
+ function registerProfileUpdateCommand(parent, runtimeOptions = {}) {
17422
+ registerCliCommand(parent, profileUpdateCommand, runtimeOptions);
17130
17423
  }
17131
17424
 
17132
17425
  // src/commands/integration/_format.ts
@@ -17157,6 +17450,10 @@ function formatIntegrationList(data) {
17157
17450
  lines.push(` id: ${service.id}`);
17158
17451
  lines.push(` status: ${active ? "active login" : "not logged in"}`);
17159
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
+ }
17160
17457
  if (service.homepageUrl) lines.push(` homepage: ${service.homepageUrl}`);
17161
17458
  if (service.description) lines.push(` description: ${service.description}`);
17162
17459
  if (!active) lines.push(` next: slock integration login --service ${JSON.stringify(service.clientId)}`);
@@ -17173,6 +17470,10 @@ function formatIntegrationList(data) {
17173
17470
  lines.push(` grant id: ${login.id}`);
17174
17471
  lines.push(` scopes: ${login.scopes.length > 0 ? login.scopes.join(", ") : "-"}`);
17175
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
+ }
17176
17477
  lines.push(` created: ${login.createdAt}`);
17177
17478
  }
17178
17479
  }
@@ -17185,10 +17486,14 @@ function formatIntegrationLogin(data) {
17185
17486
  `service: ${data.service.clientId}`,
17186
17487
  `id: ${data.service.id}`,
17187
17488
  `scopes: ${data.scopes.length > 0 ? data.scopes.join(", ") : "-"}`,
17188
- `return URL: ${formatMaybe(data.service.returnUrl)}`,
17189
- "complete: this agent login is configured in Slock; no human OAuth is required",
17190
- "identity: run `slock profile show` if the service or human asks for your Slock Agent identity card"
17489
+ `return URL: ${formatMaybe(data.service.returnUrl)}`
17191
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");
17192
17497
  const agentAppUrl = buildAgentAppUrl(data.service.returnUrl, data.requestId);
17193
17498
  if (agentAppUrl) {
17194
17499
  lines.push(`app URL: ${agentAppUrl}`);
@@ -17200,31 +17505,33 @@ function formatIntegrationLogin(data) {
17200
17505
  }
17201
17506
 
17202
17507
  // src/commands/integration/list.ts
17203
- function registerIntegrationListCommand(parent) {
17204
- parent.command("list").description("List registered third-party services and this agent's active logins").option("--json", "Emit machine-readable JSON").action(async (opts) => {
17205
- let ctx;
17206
- try {
17207
- ctx = loadAgentContext();
17208
- } catch (err) {
17209
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17210
- throw err;
17211
- }
17212
- 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);
17213
17517
  const res = await client.request(
17214
17518
  "GET",
17215
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/integrations`
17519
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/integrations`
17216
17520
  );
17217
17521
  if (!res.ok || !res.data) {
17218
17522
  const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LIST_FAILED";
17219
- fail(code, res.error ?? `HTTP ${res.status}`);
17523
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17220
17524
  }
17221
17525
  if (opts.json) {
17222
- emit({ ok: true, data: res.data });
17526
+ writeJson(ctx.io, { ok: true, data: res.data });
17223
17527
  return;
17224
17528
  }
17225
- process.stdout.write(`${formatIntegrationList(res.data)}
17529
+ writeText(ctx.io, `${formatIntegrationList(res.data)}
17226
17530
  `);
17227
- });
17531
+ }
17532
+ );
17533
+ function registerIntegrationListCommand(parent, runtimeOptions = {}) {
17534
+ registerCliCommand(parent, integrationListCommand, runtimeOptions);
17228
17535
  }
17229
17536
 
17230
17537
  // src/commands/integration/login.ts
@@ -17234,46 +17541,389 @@ function normalizeScopes(raw) {
17234
17541
  raw.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
17235
17542
  )).sort();
17236
17543
  if (scopes.length === 0) {
17237
- 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");
17238
17545
  }
17239
17546
  return scopes;
17240
17547
  }
17241
- function registerIntegrationLoginCommand(parent) {
17242
- 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 = []) => {
17243
- previous.push(value);
17244
- return previous;
17245
- }).option("--json", "Emit machine-readable JSON").action(async (opts) => {
17246
- let ctx;
17247
- try {
17248
- ctx = loadAgentContext();
17249
- } catch (err) {
17250
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17251
- throw err;
17252
- }
17253
- 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() ?? "";
17254
17567
  if (!service) {
17255
- fail("INVALID_ARG", "--service must not be empty");
17568
+ throw cliError("INVALID_ARG", "--service is required");
17256
17569
  }
17257
- const client = new ApiClient(ctx);
17570
+ const scopes = normalizeScopes(opts.scope);
17571
+ const agentContext = ctx.loadAgentContext();
17572
+ const client = ctx.createApiClient(agentContext);
17258
17573
  const res = await client.request(
17259
17574
  "POST",
17260
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/integrations/login`,
17575
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/integrations/login`,
17261
17576
  {
17262
17577
  service,
17263
- scopes: normalizeScopes(opts.scope)
17578
+ scopes
17264
17579
  }
17265
17580
  );
17266
17581
  if (!res.ok || !res.data) {
17267
17582
  const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LOGIN_FAILED";
17268
- fail(code, res.error ?? `HTTP ${res.status}`);
17583
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17269
17584
  }
17270
17585
  if (opts.json) {
17271
- emit({ ok: true, data: res.data });
17586
+ writeJson(ctx.io, { ok: true, data: res.data });
17272
17587
  return;
17273
17588
  }
17274
- process.stdout.write(`${formatIntegrationLogin(res.data)}
17589
+ writeText(ctx.io, `${formatIntegrationLogin(res.data)}
17275
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"
17276
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);
17277
17927
  }
17278
17928
 
17279
17929
  // src/commands/reminder/_format.ts
@@ -17375,66 +18025,62 @@ function buildScheduleBody(opts, now = () => Intl.DateTimeFormat().resolvedOptio
17375
18025
  }
17376
18026
  return { body };
17377
18027
  }
17378
- function registerReminderScheduleCommand(parent) {
17379
- 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(
17380
- "--delay-seconds <n>",
17381
- "Preferred for relative times. Fires this many seconds from now (server-computed, timezone-safe)"
17382
- ).option(
17383
- "--fire-at <iso>",
17384
- "ISO-8601 UTC timestamp, e.g. 2026-04-21T09:00:00Z. Use only for absolute calendar times"
17385
- ).option(
17386
- "--repeat <rule>",
17387
- "Recurrence rule: every:15m | every:2h | every:1d | daily@09:00 | weekly:mon,fri@09:00"
17388
- ).option(
17389
- "--channel <ref>",
17390
- "Optional channel to post a receipt message in (e.g. #general, dm:@alice)."
17391
- ).requiredOption(
17392
- "--msg-id <id>",
17393
- "Message id this reminder is anchored to. Required for agent-created reminders."
17394
- ).action(async (opts) => {
17395
- let ctx;
17396
- try {
17397
- ctx = loadAgentContext();
17398
- } catch (err) {
17399
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17400
- 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");
17401
18044
  }
17402
18045
  const built = buildScheduleBody(opts);
17403
- if (built.error) fail(built.error.code, built.error.message);
17404
- 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);
17405
18049
  const res = await client.request(
17406
18050
  "POST",
17407
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders`,
18051
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders`,
17408
18052
  built.body
17409
18053
  );
17410
18054
  if (!res.ok || !res.data?.reminder) {
17411
18055
  const code = res.status >= 500 ? "SERVER_5XX" : "SCHEDULE_FAILED";
17412
- fail(code, res.error ?? `HTTP ${res.status}`);
18056
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17413
18057
  }
17414
- process.stdout.write(
18058
+ writeText(
18059
+ ctx.io,
17415
18060
  formatReminderScheduled(res.data.reminder, res.data.warning ?? null) + "\n"
17416
18061
  );
17417
- });
18062
+ }
18063
+ );
18064
+ function registerReminderScheduleCommand(parent, runtimeOptions = {}) {
18065
+ registerCliCommand(parent, reminderScheduleCommand, runtimeOptions);
17418
18066
  }
17419
18067
 
17420
18068
  // src/commands/reminder/list.ts
17421
18069
  var VALID_STATUSES2 = /* @__PURE__ */ new Set(["scheduled", "fired", "canceled"]);
17422
- function registerReminderListCommand(parent) {
17423
- parent.command("list").description("List your own reminders (defaults to scheduled and fired)").option("--all", "Include canceled reminders").option(
17424
- "--status <s>",
17425
- "Comma-separated statuses (scheduled,fired,canceled). Default: scheduled,fired"
17426
- ).action(async (opts) => {
17427
- let ctx;
17428
- try {
17429
- ctx = loadAgentContext();
17430
- } catch (err) {
17431
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17432
- throw err;
17433
- }
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) => {
17434
18080
  const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled,fired";
17435
18081
  for (const s of statusRaw.split(",").map((x) => x.trim()).filter(Boolean)) {
17436
18082
  if (!VALID_STATUSES2.has(s)) {
17437
- 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}`);
17438
18084
  }
17439
18085
  }
17440
18086
  const params = new URLSearchParams();
@@ -17443,23 +18089,27 @@ function registerReminderListCommand(parent) {
17443
18089
  } else {
17444
18090
  params.set("status", statusRaw);
17445
18091
  }
17446
- const client = new ApiClient(ctx);
18092
+ const agentContext = ctx.loadAgentContext();
18093
+ const client = ctx.createApiClient(agentContext);
17447
18094
  const res = await client.request(
17448
18095
  "GET",
17449
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders?${params.toString()}`
18096
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders?${params.toString()}`
17450
18097
  );
17451
18098
  if (!res.ok) {
17452
18099
  const code = res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED";
17453
- fail(code, res.error ?? `HTTP ${res.status}`);
18100
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17454
18101
  }
17455
- process.stdout.write(formatReminderList(res.data?.reminders ?? []) + "\n");
17456
- });
18102
+ writeText(ctx.io, formatReminderList(res.data?.reminders ?? []) + "\n");
18103
+ }
18104
+ );
18105
+ function registerReminderListCommand(parent, runtimeOptions = {}) {
18106
+ registerCliCommand(parent, reminderListCommand, runtimeOptions);
17457
18107
  }
17458
18108
 
17459
18109
  // src/commands/reminder/_resolve.ts
17460
18110
  async function resolveReminderId(client, agentId, id, opts) {
17461
18111
  const trimmed = id.trim();
17462
- if (!trimmed) fail("INVALID_ARG", "--id is required");
18112
+ if (!trimmed) throw cliError("INVALID_ARG", "--id is required");
17463
18113
  if (trimmed.length >= 32) return trimmed;
17464
18114
  const params = new URLSearchParams();
17465
18115
  if (opts.all) {
@@ -17474,47 +18124,49 @@ async function resolveReminderId(client, agentId, id, opts) {
17474
18124
  );
17475
18125
  if (!res.ok) {
17476
18126
  const code = res.status >= 500 ? "SERVER_5XX" : opts.failureCode;
17477
- fail(code, res.error ?? `HTTP ${res.status}`);
18127
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17478
18128
  }
17479
18129
  const matches = (res.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(trimmed));
17480
18130
  if (matches.length === 0) {
17481
18131
  const scope = opts.all ? "reminder" : `${opts.statuses?.join("/") ?? "active"} reminder`;
17482
- fail("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
18132
+ throw cliError("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
17483
18133
  }
17484
18134
  if (matches.length > 1) {
17485
- 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.`);
17486
18136
  }
17487
18137
  return matches[0].reminderId;
17488
18138
  }
17489
18139
 
17490
18140
  // src/commands/reminder/cancel.ts
17491
- function registerReminderCancelCommand(parent) {
17492
- 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) => {
17493
- let ctx;
17494
- try {
17495
- ctx = loadAgentContext();
17496
- } catch (err) {
17497
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17498
- throw err;
17499
- }
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) => {
17500
18148
  if (!opts.id || opts.id.trim().length === 0) {
17501
- fail("INVALID_ARG", "--id is required");
18149
+ throw cliError("INVALID_ARG", "--id is required");
17502
18150
  }
17503
- const client = new ApiClient(ctx);
17504
- 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, {
17505
18154
  statuses: ["scheduled", "fired"],
17506
18155
  failureCode: "CANCEL_FAILED"
17507
18156
  });
17508
18157
  const res = await client.request(
17509
18158
  "DELETE",
17510
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`
18159
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}`
17511
18160
  );
17512
18161
  if (!res.ok || !res.data?.reminder) {
17513
18162
  const code = res.status >= 500 ? "SERVER_5XX" : "CANCEL_FAILED";
17514
- fail(code, res.error ?? `HTTP ${res.status}`);
18163
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17515
18164
  }
17516
- process.stdout.write(formatReminderCanceled(res.data.reminder) + "\n");
17517
- });
18165
+ writeText(ctx.io, formatReminderCanceled(res.data.reminder) + "\n");
18166
+ }
18167
+ );
18168
+ function registerReminderCancelCommand(parent, runtimeOptions = {}) {
18169
+ registerCliCommand(parent, reminderCancelCommand, runtimeOptions);
17518
18170
  }
17519
18171
 
17520
18172
  // src/commands/reminder/_duration.ts
@@ -17531,57 +18183,75 @@ function parseDurationSeconds(input) {
17531
18183
  }
17532
18184
 
17533
18185
  // src/commands/reminder/snooze.ts
17534
- function registerReminderSnoozeCommand(parent) {
17535
- 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) => {
17536
- let ctx;
17537
- try {
17538
- ctx = loadAgentContext();
17539
- } catch (err) {
17540
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17541
- 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");
17542
18201
  }
17543
18202
  const delaySeconds = parseDurationSeconds(opts.by);
17544
18203
  if (delaySeconds == null) {
17545
- 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");
17546
18205
  }
17547
- const client = new ApiClient(ctx);
17548
- 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, {
17549
18209
  statuses: ["scheduled", "fired"],
17550
18210
  failureCode: "SNOOZE_FAILED"
17551
18211
  });
17552
18212
  const res = await client.request(
17553
18213
  "POST",
17554
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
18214
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
17555
18215
  { delaySeconds }
17556
18216
  );
17557
18217
  if (!res.ok || !res.data?.reminder) {
17558
18218
  const code = res.status >= 500 ? "SERVER_5XX" : "SNOOZE_FAILED";
17559
- fail(code, res.error ?? `HTTP ${res.status}`);
18219
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17560
18220
  }
17561
- process.stdout.write(formatReminderSnoozed(res.data.reminder) + "\n");
17562
- });
18221
+ writeText(ctx.io, formatReminderSnoozed(res.data.reminder) + "\n");
18222
+ }
18223
+ );
18224
+ function registerReminderSnoozeCommand(parent, runtimeOptions = {}) {
18225
+ registerCliCommand(parent, reminderSnoozeCommand, runtimeOptions);
17563
18226
  }
17564
18227
 
17565
18228
  // src/commands/reminder/update.ts
17566
- function registerReminderUpdateCommand(parent) {
17567
- 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) => {
17568
- let ctx;
17569
- try {
17570
- ctx = loadAgentContext();
17571
- } catch (err) {
17572
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17573
- 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");
17574
18244
  }
17575
18245
  const mutationCount = [opts.fireAt, opts.in, opts.cadence, opts.title].filter((x) => x !== void 0 && x !== null).length;
17576
18246
  if (mutationCount !== 1) {
17577
- 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");
17578
18248
  }
17579
18249
  const body = {};
17580
18250
  if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
17581
18251
  if (opts.in !== void 0) {
17582
18252
  const delaySeconds = parseDurationSeconds(opts.in);
17583
18253
  if (delaySeconds == null) {
17584
- 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");
17585
18255
  }
17586
18256
  body.delaySeconds = delaySeconds;
17587
18257
  }
@@ -17590,49 +18260,58 @@ function registerReminderUpdateCommand(parent) {
17590
18260
  body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
17591
18261
  }
17592
18262
  if (opts.title !== void 0) body.title = opts.title;
17593
- const client = new ApiClient(ctx);
17594
- 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, {
17595
18266
  all: true,
17596
18267
  failureCode: "UPDATE_FAILED"
17597
18268
  });
17598
18269
  const res = await client.request(
17599
18270
  "PATCH",
17600
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`,
18271
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}`,
17601
18272
  body
17602
18273
  );
17603
18274
  if (!res.ok || !res.data?.reminder) {
17604
18275
  const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
17605
- fail(code, res.error ?? `HTTP ${res.status}`);
18276
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17606
18277
  }
17607
- process.stdout.write(formatReminderUpdated(res.data.reminder, res.data.warning ?? null) + "\n");
17608
- });
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);
17609
18283
  }
17610
18284
 
17611
18285
  // src/commands/reminder/log.ts
17612
- function registerReminderLogCommand(parent) {
17613
- parent.command("log").description("Show lifecycle events for one reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
17614
- let ctx;
17615
- try {
17616
- ctx = loadAgentContext();
17617
- } catch (err) {
17618
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
17619
- 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");
17620
18295
  }
17621
- const client = new ApiClient(ctx);
17622
- 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, {
17623
18299
  all: true,
17624
18300
  failureCode: "LOG_FAILED"
17625
18301
  });
17626
18302
  const res = await client.request(
17627
18303
  "GET",
17628
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
18304
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
17629
18305
  );
17630
18306
  if (!res.ok || !res.data?.events) {
17631
18307
  const code = res.status >= 500 ? "SERVER_5XX" : "LOG_FAILED";
17632
- fail(code, res.error ?? `HTTP ${res.status}`);
18308
+ throw cliError(code, res.error ?? `HTTP ${res.status}`);
17633
18309
  }
17634
- process.stdout.write(formatReminderLog(res.data.events) + "\n");
17635
- });
18310
+ writeText(ctx.io, formatReminderLog(res.data.events) + "\n");
18311
+ }
18312
+ );
18313
+ function registerReminderLogCommand(parent, runtimeOptions = {}) {
18314
+ registerCliCommand(parent, reminderLogCommand, runtimeOptions);
17636
18315
  }
17637
18316
 
17638
18317
  // src/index.ts
@@ -17685,6 +18364,7 @@ registerProfileUpdateCommand(profileCmd);
17685
18364
  var integrationCmd = program.command("integration").description("Third-party service integration operations");
17686
18365
  registerIntegrationListCommand(integrationCmd);
17687
18366
  registerIntegrationLoginCommand(integrationCmd);
18367
+ registerIntegrationEnvCommand(integrationCmd);
17688
18368
  var reminderCmd = program.command("reminder").description("Reminder operations");
17689
18369
  registerReminderScheduleCommand(reminderCmd);
17690
18370
  registerReminderListCommand(reminderCmd);