@slock-ai/daemon 0.54.2 → 0.55.0

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
@@ -46,6 +46,237 @@ function readCliVersion(baseUrl = import.meta.url) {
46
46
  );
47
47
  }
48
48
 
49
+ // src/proxy.ts
50
+ import { ProxyAgent } from "undici";
51
+ var fetchDispatcherCache = /* @__PURE__ */ new Map();
52
+ function getDefaultPort(protocol) {
53
+ switch (protocol) {
54
+ case "https:":
55
+ return "443";
56
+ case "http:":
57
+ return "80";
58
+ default:
59
+ return "";
60
+ }
61
+ }
62
+ function hostMatchesNoProxyEntry(hostname3, ruleHost) {
63
+ if (!ruleHost) return false;
64
+ const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
65
+ const normalizedHost = hostname3.toLowerCase();
66
+ return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
67
+ }
68
+ function getProxyUrlForTarget(targetUrl, env) {
69
+ const protocol = new URL(targetUrl).protocol;
70
+ switch (protocol) {
71
+ case "https:":
72
+ return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
73
+ case "http:":
74
+ return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
75
+ default:
76
+ return env.ALL_PROXY || env.all_proxy;
77
+ }
78
+ }
79
+ function shouldBypassProxy(targetUrl, env) {
80
+ const rawNoProxy = env.NO_PROXY || env.no_proxy;
81
+ if (!rawNoProxy) return false;
82
+ const url2 = new URL(targetUrl);
83
+ const hostname3 = url2.hostname.toLowerCase();
84
+ const port = url2.port || getDefaultPort(url2.protocol);
85
+ return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
86
+ if (entry === "*") return true;
87
+ const [ruleHost, rulePort] = entry.split(":", 2);
88
+ if (rulePort && rulePort !== port) return false;
89
+ return hostMatchesNoProxyEntry(hostname3, ruleHost);
90
+ });
91
+ }
92
+ function buildFetchDispatcher(targetUrl, env = process.env) {
93
+ const proxyUrl = getProxyUrlForTarget(targetUrl, env);
94
+ if (!proxyUrl) return void 0;
95
+ if (shouldBypassProxy(targetUrl, env)) return void 0;
96
+ const cached2 = fetchDispatcherCache.get(proxyUrl);
97
+ if (cached2) return cached2;
98
+ const dispatcher = new ProxyAgent(proxyUrl);
99
+ fetchDispatcherCache.set(proxyUrl, dispatcher);
100
+ return dispatcher;
101
+ }
102
+
103
+ // src/client.ts
104
+ var ApiClient = class {
105
+ constructor(ctx) {
106
+ this.ctx = ctx;
107
+ }
108
+ usesAgentApiSurface() {
109
+ return this.ctx.clientMode === "managed-runner" || this.ctx.clientMode === "self-hosted-runner";
110
+ }
111
+ rewriteAgentCredentialPath(pathname) {
112
+ if (!this.usesAgentApiSurface()) return pathname;
113
+ const attachmentDownload = /^\/api\/attachments\/([^/?]+)(.*)$/.exec(pathname);
114
+ if (attachmentDownload) {
115
+ return `/internal/agent-api/attachments/${attachmentDownload[1]}${attachmentDownload[2] ?? ""}`;
116
+ }
117
+ const agentPrefix = `/internal/agent/${encodeURIComponent(this.ctx.agentId)}`;
118
+ if (!pathname.startsWith(agentPrefix)) return pathname;
119
+ const suffix = pathname.slice(agentPrefix.length);
120
+ if (suffix === "/server") return "/internal/agent-api/server";
121
+ if (suffix === "/send") return "/internal/agent-api/send";
122
+ if (suffix.startsWith("/history")) return `/internal/agent-api/history${suffix.slice("/history".length)}`;
123
+ if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
124
+ if (suffix.startsWith("/channel-members")) return `/internal/agent-api/channel-members${suffix.slice("/channel-members".length)}`;
125
+ if (suffix === "/knowledge" || suffix.startsWith("/knowledge?")) {
126
+ return `/internal/agent-api/knowledge${suffix.slice("/knowledge".length)}`;
127
+ }
128
+ if (suffix === "/profile" || suffix.startsWith("/profile/")) return `/internal/agent-api${suffix}`;
129
+ if (suffix === "/integrations" || suffix.startsWith("/integrations/")) return `/internal/agent-api${suffix}`;
130
+ if (suffix === "/upload") return "/internal/agent-api/upload";
131
+ if (suffix === "/resolve-channel") return "/internal/agent-api/resolve-channel";
132
+ if (suffix === "/threads/unfollow") return "/internal/agent-api/threads/unfollow";
133
+ if (suffix === "/prepare-action") return "/internal/agent-api/prepare-action";
134
+ if (suffix === "/tasks" || suffix.startsWith("/tasks?") || suffix.startsWith("/tasks/")) {
135
+ return `/internal/agent-api${suffix}`;
136
+ }
137
+ if (suffix === "/reminders" || suffix.startsWith("/reminders?") || suffix.startsWith("/reminders/")) {
138
+ return `/internal/agent-api${suffix}`;
139
+ }
140
+ if (suffix === "/receive" || suffix.startsWith("/receive?")) {
141
+ return "/internal/agent-api/events?since=latest";
142
+ }
143
+ const reaction = /^\/messages\/([^/]+)\/reactions$/.exec(suffix);
144
+ if (reaction) {
145
+ return `/internal/agent-api/messages/${reaction[1]}/reactions`;
146
+ }
147
+ const channelMembership = /^\/channels\/([^/]+)\/(join|leave)$/.exec(suffix);
148
+ if (channelMembership) {
149
+ return `/internal/agent-api/channels/${channelMembership[1]}/${channelMembership[2]}`;
150
+ }
151
+ return pathname;
152
+ }
153
+ normalizeAgentCredentialResponse(pathname, data) {
154
+ if (!this.usesAgentApiSurface()) return data;
155
+ if (!pathname.includes("/internal/agent-api/events")) return data;
156
+ const value = data;
157
+ if (!Array.isArray(value.events)) return data;
158
+ return { ...data, messages: value.events };
159
+ }
160
+ buildAuthHeaders() {
161
+ const headers = {
162
+ "Authorization": `Bearer ${this.ctx.token}`,
163
+ "X-Agent-Id": this.ctx.agentId,
164
+ "X-Slock-Client": "cli"
165
+ };
166
+ if (this.ctx.serverId) headers["X-Server-Id"] = this.ctx.serverId;
167
+ return headers;
168
+ }
169
+ async parseJsonResponse(res) {
170
+ let data = null;
171
+ let error48 = null;
172
+ let errorCode = null;
173
+ const contentType = res.headers.get("content-type") ?? "";
174
+ if (contentType.includes("application/json")) {
175
+ let parseFailed = false;
176
+ const parsed = await res.json().catch(() => {
177
+ parseFailed = true;
178
+ return null;
179
+ });
180
+ if (parseFailed) {
181
+ return {
182
+ ok: false,
183
+ status: res.status,
184
+ data: null,
185
+ error: `Invalid JSON response from server/proxy (HTTP ${res.status})`,
186
+ errorCode: "INVALID_JSON_RESPONSE"
187
+ };
188
+ }
189
+ if (res.ok) {
190
+ data = parsed;
191
+ } else {
192
+ const body = parsed;
193
+ if (res.status === 403 && body?.requiredScope) {
194
+ errorCode = "SCOPE_DENIED";
195
+ 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
+ } else {
197
+ error48 = body?.error ?? `HTTP ${res.status}`;
198
+ errorCode = body?.errorCode ?? null;
199
+ }
200
+ }
201
+ } else if (!res.ok) {
202
+ error48 = `HTTP ${res.status}`;
203
+ }
204
+ return { ok: res.ok, status: res.status, data, error: error48, errorCode };
205
+ }
206
+ async request(method, pathname, body) {
207
+ pathname = this.rewriteAgentCredentialPath(pathname);
208
+ const url2 = new URL(pathname, this.ctx.serverUrl).toString();
209
+ const headers = this.buildAuthHeaders();
210
+ headers["Content-Type"] = "application/json";
211
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
212
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
213
+ }
214
+ const dispatcher = buildFetchDispatcher(url2);
215
+ const init = {
216
+ method,
217
+ headers,
218
+ body: body === void 0 ? void 0 : JSON.stringify(body)
219
+ };
220
+ if (dispatcher) init.dispatcher = dispatcher;
221
+ const res = await fetch(url2, init);
222
+ const parsed = await this.parseJsonResponse(res);
223
+ if (parsed.ok && parsed.data !== null) {
224
+ parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
225
+ }
226
+ return parsed;
227
+ }
228
+ // Multipart upload. Caller builds the FormData (file part + any text fields).
229
+ // Content-Type intentionally omitted so fetch sets the correct multipart
230
+ // boundary itself.
231
+ async requestMultipart(method, pathname, form) {
232
+ pathname = this.rewriteAgentCredentialPath(pathname);
233
+ const url2 = new URL(pathname, this.ctx.serverUrl).toString();
234
+ const dispatcher = buildFetchDispatcher(url2);
235
+ const headers = this.buildAuthHeaders();
236
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
237
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
238
+ }
239
+ const init = {
240
+ method,
241
+ headers,
242
+ body: form
243
+ };
244
+ if (dispatcher) init.dispatcher = dispatcher;
245
+ const res = await fetch(url2, init);
246
+ return this.parseJsonResponse(res);
247
+ }
248
+ // Returns the raw Response so the caller can stream / save the body.
249
+ // For non-JSON downloads (binary attachments). Caller is responsible for
250
+ // consuming the body. On non-2xx, attempts to surface a JSON error.
251
+ async requestRaw(method, pathname) {
252
+ pathname = this.rewriteAgentCredentialPath(pathname);
253
+ const url2 = new URL(pathname, this.ctx.serverUrl).toString();
254
+ const dispatcher = buildFetchDispatcher(url2);
255
+ const headers = this.buildAuthHeaders();
256
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
257
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
258
+ }
259
+ const init = {
260
+ method,
261
+ headers,
262
+ redirect: "follow"
263
+ };
264
+ if (dispatcher) init.dispatcher = dispatcher;
265
+ const res = await fetch(url2, init);
266
+ let error48 = null;
267
+ if (!res.ok) {
268
+ const contentType = res.headers.get("content-type") ?? "";
269
+ if (contentType.includes("application/json")) {
270
+ const parsed = await res.json().catch(() => null);
271
+ error48 = parsed?.error ?? `HTTP ${res.status}`;
272
+ } else {
273
+ error48 = `HTTP ${res.status}`;
274
+ }
275
+ }
276
+ return { ok: res.ok, status: res.status, response: res, error: error48 };
277
+ }
278
+ };
279
+
49
280
  // src/auth/env.ts
50
281
  import fs from "fs";
51
282
  import os from "os";
@@ -216,44 +447,156 @@ function loadAgentContext(env = process.env) {
216
447
  );
217
448
  }
218
449
 
219
- // src/commands/auth/whoami.ts
220
- function registerWhoamiCommand(parent) {
221
- parent.command("whoami").description("Print the agent context resolved from env (token value redacted)").action(() => {
222
- let ctx;
223
- try {
224
- ctx = loadAgentContext();
225
- } catch (err) {
226
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
227
- throw err;
228
- }
229
- emit({
230
- ok: true,
231
- data: {
232
- agentId: ctx.agentId,
233
- serverUrl: ctx.serverUrl,
234
- serverId: ctx.serverId,
235
- clientMode: ctx.clientMode,
236
- secretSource: ctx.secretSource,
237
- ...ctx.profileSlug ? { profileSlug: ctx.profileSlug } : {},
238
- ...ctx.profileCredentialPath ? { profileCredentialPath: ctx.profileCredentialPath } : {}
239
- }
240
- });
241
- });
450
+ // src/core/io.ts
451
+ function defaultCliIo() {
452
+ return {
453
+ stdout: process.stdout,
454
+ stderr: process.stderr
455
+ };
242
456
  }
243
457
 
244
- // src/commands/agent/list.ts
245
- import { fetch as undiciFetch } from "undici";
246
-
247
- // src/agentLogin/deviceAuthClient.ts
248
- import { fetch as fetch2 } from "undici";
249
- var DeviceCodeLoginError = class extends Error {
250
- constructor(code, message) {
251
- super(message);
252
- this.code = code;
253
- this.name = "DeviceCodeLoginError";
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;
254
470
  }
255
471
  };
256
- var ACTIONABLE_ERROR_MESSAGES = {
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";
481
+ }
482
+ };
483
+ function toCliError(err) {
484
+ if (err instanceof CliError) return err;
485
+ return new InternalBugError(err);
486
+ }
487
+
488
+ // src/core/context.ts
489
+ function createCommandContext(options = {}) {
490
+ const io = options.io ?? defaultCliIo();
491
+ const env = options.env ?? process.env;
492
+ const loadContext = options.loadAgentContext ?? loadAgentContext;
493
+ const createApiClient = options.createApiClient ?? ((agentContext) => new ApiClient(agentContext));
494
+ return {
495
+ io,
496
+ env,
497
+ loadAgentContext() {
498
+ try {
499
+ return loadContext(env);
500
+ } catch (err) {
501
+ if (err instanceof AgentBootstrapError) {
502
+ throw new CliError({
503
+ code: err.code,
504
+ message: err.message,
505
+ cause: err
506
+ });
507
+ }
508
+ throw err;
509
+ }
510
+ },
511
+ createApiClient
512
+ };
513
+ }
514
+
515
+ // src/core/renderer.ts
516
+ function renderError(io, err) {
517
+ const body = {
518
+ ok: false,
519
+ code: err.code,
520
+ message: err.message
521
+ };
522
+ if (err.suggestedNextAction) {
523
+ body.suggested_next_action = err.suggestedNextAction;
524
+ }
525
+ io.stderr.write(`${JSON.stringify(body)}
526
+ `);
527
+ }
528
+ function writeText(io, text) {
529
+ io.stdout.write(text);
530
+ }
531
+ function writeJson(io, payload) {
532
+ io.stdout.write(`${JSON.stringify(payload)}
533
+ `);
534
+ }
535
+
536
+ // src/core/command.ts
537
+ function defineCommand(spec, handler) {
538
+ return { spec, handler };
539
+ }
540
+ function registerCliCommand(parent, command, runtimeOptions = {}) {
541
+ const child = parent.command(command.spec.name).description(command.spec.description);
542
+ for (const arg of command.spec.arguments ?? []) {
543
+ child.argument(arg);
544
+ }
545
+ for (const option of command.spec.options ?? []) {
546
+ child.option(option.flags, option.description);
547
+ }
548
+ child.action(async (...args) => {
549
+ const ctx = createCommandContext(runtimeOptions);
550
+ try {
551
+ await command.handler(ctx, ...args);
552
+ } catch (err) {
553
+ const cliError = toCliError(err);
554
+ renderError(ctx.io, cliError);
555
+ throw new CliExit(cliError.exitCode);
556
+ }
557
+ });
558
+ }
559
+
560
+ // src/commands/auth/whoami.ts
561
+ var whoamiCommand = defineCommand(
562
+ {
563
+ name: "whoami",
564
+ description: "Print the agent context resolved from env (token value redacted)",
565
+ rationale: "denied"
566
+ },
567
+ (ctx) => {
568
+ const agentContext = ctx.loadAgentContext();
569
+ writeJson(ctx.io, {
570
+ ok: true,
571
+ data: {
572
+ agentId: agentContext.agentId,
573
+ serverUrl: agentContext.serverUrl,
574
+ serverId: agentContext.serverId,
575
+ clientMode: agentContext.clientMode,
576
+ secretSource: agentContext.secretSource,
577
+ ...agentContext.profileSlug ? { profileSlug: agentContext.profileSlug } : {},
578
+ ...agentContext.profileCredentialPath ? { profileCredentialPath: agentContext.profileCredentialPath } : {}
579
+ }
580
+ });
581
+ }
582
+ );
583
+ function registerWhoamiCommand(parent, runtimeOptions) {
584
+ registerCliCommand(parent, whoamiCommand, runtimeOptions);
585
+ }
586
+
587
+ // src/commands/agent/list.ts
588
+ import { fetch as undiciFetch } from "undici";
589
+
590
+ // src/agentLogin/deviceAuthClient.ts
591
+ import { fetch as fetch2 } from "undici";
592
+ var DeviceCodeLoginError = class extends Error {
593
+ constructor(code, message) {
594
+ super(message);
595
+ this.code = code;
596
+ this.name = "DeviceCodeLoginError";
597
+ }
598
+ };
599
+ var ACTIONABLE_ERROR_MESSAGES = {
257
600
  device_login_disabled: "Device login is not enabled on this Slock server. Ask an admin to set SLOCK_DEVICE_LOGIN_ENABLED=true.",
258
601
  device_code_required: "Internal CLI bug: device_code was missing from the poll request.",
259
602
  user_code_required: "Internal CLI bug: user_code was missing from the approve request.",
@@ -575,6 +918,30 @@ function describeMintError(code, serverUrl) {
575
918
  return void 0;
576
919
  }
577
920
 
921
+ // ../shared/src/slockRefs.ts
922
+ var SLOCK_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
923
+ var SLOCK_REF_USER_NAME_PATTERN = SLOCK_REF_CHANNEL_NAME_PATTERN;
924
+ var SLOCK_REF_DM_PEER_PATTERN = String.raw`[\w-]+`;
925
+ var SLOCK_REF_THREAD_SHORT_ID_PATTERN = String.raw`[\da-f]{6,8}`;
926
+ var SLOCK_REF_MESSAGE_ID_PATTERN = String.raw`[A-Za-z0-9][A-Za-z0-9-]{1,63}`;
927
+ var SLOCK_REF_TASK_NUMBER_PATTERN = String.raw`[1-9]\d*`;
928
+ var USER_RE = new RegExp(String.raw`^@(${SLOCK_REF_USER_NAME_PATTERN})$`, "u");
929
+ var CHANNEL_RE = new RegExp(String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN})$`, "u");
930
+ var CHANNEL_THREAD_RE = new RegExp(
931
+ String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN}):(${SLOCK_REF_THREAD_SHORT_ID_PATTERN})$`,
932
+ "iu"
933
+ );
934
+ var DM_RE = new RegExp(String.raw`^dm:@(${SLOCK_REF_DM_PEER_PATTERN})$`, "iu");
935
+ var DM_THREAD_RE = new RegExp(
936
+ String.raw`^dm:@(${SLOCK_REF_DM_PEER_PATTERN}):(${SLOCK_REF_THREAD_SHORT_ID_PATTERN})$`,
937
+ "iu"
938
+ );
939
+ var TASK_RE = new RegExp(String.raw`^task\s+#(${SLOCK_REF_TASK_NUMBER_PATTERN})$`, "iu");
940
+ var CHANNEL_MESSAGE_RE = new RegExp(
941
+ String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN})(?::(${SLOCK_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${SLOCK_REF_MESSAGE_ID_PATTERN})$`,
942
+ "iu"
943
+ );
944
+
578
945
  // ../shared/src/tracing/index.ts
579
946
  var DEFAULT_TRACE_FLAGS = "00";
580
947
  var TRACE_ID_HEX_LENGTH = 32;
@@ -14481,384 +14848,156 @@ var channelAddMemberOperationSchema = external_exports.object({
14481
14848
  channel: idOrHandleSchema,
14482
14849
  /** Same resolution rule as `channelCreateOperationSchema.initialHumans`. */
14483
14850
  humans: external_exports.array(idOrHandleSchema).max(64).optional(),
14484
- /** Same resolution rule as `channelCreateOperationSchema.initialAgents`. */
14485
- agents: external_exports.array(idOrHandleSchema).max(64).optional(),
14486
- draftHint: draftHintSchema
14487
- });
14488
- var actionCardActionSchema = external_exports.discriminatedUnion("type", [
14489
- channelCreateOperationSchema,
14490
- agentCreateOperationSchema,
14491
- channelAddMemberOperationSchema
14492
- ]);
14493
- function validateActionCardAction(action) {
14494
- if (action.type === "agent:create") {
14495
- if (action.suggestedComputer && action.requiredComputer) {
14496
- return "agent:create must include only one of suggestedComputer or requiredComputer";
14497
- }
14498
- }
14499
- if (action.type === "channel:add_member") {
14500
- const total = (action.humans?.length ?? 0) + (action.agents?.length ?? 0);
14501
- if (total === 0) {
14502
- return "channel:add_member must include at least one human or agent";
14503
- }
14504
- }
14505
- return null;
14506
- }
14507
-
14508
- // ../shared/src/translationLanguages.ts
14509
- var SUPPORTED_TRANSLATION_LANGUAGE_CODES = [
14510
- "en",
14511
- "zh-cn",
14512
- "zh-tw",
14513
- "ja",
14514
- "ko",
14515
- "es",
14516
- "fr",
14517
- "de",
14518
- "pt-br"
14519
- ];
14520
- var SUPPORTED_TRANSLATION_LANGUAGE_SET = new Set(
14521
- SUPPORTED_TRANSLATION_LANGUAGE_CODES
14522
- );
14523
-
14524
- // ../shared/src/testing/failpoints.ts
14525
- var NoopFailpointRegistry = class {
14526
- get enabled() {
14527
- return false;
14528
- }
14529
- isEnabled() {
14530
- return false;
14531
- }
14532
- configure() {
14533
- }
14534
- clear() {
14535
- }
14536
- getTrace() {
14537
- return [];
14538
- }
14539
- hit(_key, _context, fallback) {
14540
- return fallback ? fallback() : void 0;
14541
- }
14542
- };
14543
- var noopFailpointRegistry = new NoopFailpointRegistry();
14544
-
14545
- // ../shared/src/serverPermissions.ts
14546
- var EMPTY_SERVER_CAPABILITIES = Object.freeze({
14547
- manageServer: false,
14548
- manageChannels: false,
14549
- manageAgents: false,
14550
- manageMachines: false,
14551
- manageMembers: false,
14552
- changeMemberRoles: false,
14553
- manageBilling: false,
14554
- joinPublicChannels: false
14555
- });
14556
- var SERVER_CAPABILITY_MATRIX = {
14557
- owner: Object.freeze({
14558
- manageServer: true,
14559
- manageChannels: true,
14560
- manageAgents: true,
14561
- manageMachines: true,
14562
- manageMembers: true,
14563
- changeMemberRoles: true,
14564
- manageBilling: true,
14565
- joinPublicChannels: true
14566
- }),
14567
- admin: Object.freeze({
14568
- manageServer: true,
14569
- manageChannels: true,
14570
- manageAgents: true,
14571
- manageMachines: true,
14572
- manageMembers: true,
14573
- changeMemberRoles: true,
14574
- manageBilling: false,
14575
- joinPublicChannels: true
14576
- }),
14577
- member: Object.freeze({
14578
- manageServer: false,
14579
- manageChannels: false,
14580
- manageAgents: false,
14581
- manageMachines: false,
14582
- manageMembers: false,
14583
- changeMemberRoles: false,
14584
- manageBilling: false,
14585
- joinPublicChannels: true
14586
- })
14587
- };
14588
-
14589
- // ../shared/src/index.ts
14590
- var RUNTIMES = [
14591
- { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
14592
- { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
14593
- { id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
14594
- { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
14595
- { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
14596
- { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
14597
- { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
14598
- { id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
14599
- ];
14600
- function getRuntimeDisplayName(id) {
14601
- return RUNTIMES.find((r) => r.id === id)?.displayName ?? id;
14602
- }
14603
- var PLAN_CONFIG = {
14604
- free: {
14605
- displayName: "Hobby",
14606
- limits: { maxMachines: 2, maxAgents: 5, maxChannels: 5, messageHistoryDays: 30, includedAgents: 5 },
14607
- comingSoon: false,
14608
- price: 0,
14609
- extraAgentPrice: 0
14610
- },
14611
- founder: {
14612
- displayName: "Founder",
14613
- limits: { maxMachines: -1, maxAgents: -1, maxChannels: -1, messageHistoryDays: -1, includedAgents: -1 },
14614
- comingSoon: false,
14615
- price: 0,
14616
- extraAgentPrice: 0
14617
- }
14618
- };
14619
- var DISPLAY_PLAN_CONFIG = {
14620
- free: PLAN_CONFIG.free,
14621
- pro: {
14622
- displayName: "Team",
14623
- limits: { maxMachines: 8, maxAgents: 40, maxChannels: 20, messageHistoryDays: -1, includedAgents: 40 },
14624
- comingSoon: true,
14625
- price: 20,
14626
- extraAgentPrice: 0
14627
- },
14628
- max: {
14629
- displayName: "Business",
14630
- limits: { maxMachines: 40, maxAgents: 200, maxChannels: -1, messageHistoryDays: -1, includedAgents: 200 },
14631
- comingSoon: true,
14632
- price: 200,
14633
- extraAgentPrice: 0
14634
- }
14635
- };
14636
-
14637
- // src/proxy.ts
14638
- import { ProxyAgent } from "undici";
14639
- var fetchDispatcherCache = /* @__PURE__ */ new Map();
14640
- function getDefaultPort(protocol) {
14641
- switch (protocol) {
14642
- case "https:":
14643
- return "443";
14644
- case "http:":
14645
- return "80";
14646
- default:
14647
- return "";
14648
- }
14649
- }
14650
- function hostMatchesNoProxyEntry(hostname3, ruleHost) {
14651
- if (!ruleHost) return false;
14652
- const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
14653
- const normalizedHost = hostname3.toLowerCase();
14654
- return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
14655
- }
14656
- function getProxyUrlForTarget(targetUrl, env) {
14657
- const protocol = new URL(targetUrl).protocol;
14658
- switch (protocol) {
14659
- case "https:":
14660
- return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
14661
- case "http:":
14662
- return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
14663
- default:
14664
- return env.ALL_PROXY || env.all_proxy;
14665
- }
14666
- }
14667
- function shouldBypassProxy(targetUrl, env) {
14668
- const rawNoProxy = env.NO_PROXY || env.no_proxy;
14669
- if (!rawNoProxy) return false;
14670
- const url2 = new URL(targetUrl);
14671
- const hostname3 = url2.hostname.toLowerCase();
14672
- const port = url2.port || getDefaultPort(url2.protocol);
14673
- return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
14674
- if (entry === "*") return true;
14675
- const [ruleHost, rulePort] = entry.split(":", 2);
14676
- if (rulePort && rulePort !== port) return false;
14677
- return hostMatchesNoProxyEntry(hostname3, ruleHost);
14678
- });
14679
- }
14680
- function buildFetchDispatcher(targetUrl, env = process.env) {
14681
- const proxyUrl = getProxyUrlForTarget(targetUrl, env);
14682
- if (!proxyUrl) return void 0;
14683
- if (shouldBypassProxy(targetUrl, env)) return void 0;
14684
- const cached2 = fetchDispatcherCache.get(proxyUrl);
14685
- if (cached2) return cached2;
14686
- const dispatcher = new ProxyAgent(proxyUrl);
14687
- fetchDispatcherCache.set(proxyUrl, dispatcher);
14688
- return dispatcher;
14689
- }
14690
-
14691
- // src/client.ts
14692
- var ApiClient = class {
14693
- constructor(ctx) {
14694
- this.ctx = ctx;
14695
- }
14696
- usesAgentApiSurface() {
14697
- return this.ctx.clientMode === "managed-runner" || this.ctx.clientMode === "self-hosted-runner";
14698
- }
14699
- rewriteAgentCredentialPath(pathname) {
14700
- if (!this.usesAgentApiSurface()) return pathname;
14701
- const attachmentDownload = /^\/api\/attachments\/([^/?]+)(.*)$/.exec(pathname);
14702
- if (attachmentDownload) {
14703
- return `/internal/agent-api/attachments/${attachmentDownload[1]}${attachmentDownload[2] ?? ""}`;
14704
- }
14705
- const agentPrefix = `/internal/agent/${encodeURIComponent(this.ctx.agentId)}`;
14706
- if (!pathname.startsWith(agentPrefix)) return pathname;
14707
- const suffix = pathname.slice(agentPrefix.length);
14708
- if (suffix === "/server") return "/internal/agent-api/server";
14709
- if (suffix === "/send") return "/internal/agent-api/send";
14710
- if (suffix.startsWith("/history")) return `/internal/agent-api/history${suffix.slice("/history".length)}`;
14711
- if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
14712
- if (suffix.startsWith("/channel-members")) return `/internal/agent-api/channel-members${suffix.slice("/channel-members".length)}`;
14713
- if (suffix === "/profile" || suffix.startsWith("/profile/")) return `/internal/agent-api${suffix}`;
14714
- if (suffix === "/integrations" || suffix.startsWith("/integrations/")) return `/internal/agent-api${suffix}`;
14715
- if (suffix === "/upload") return "/internal/agent-api/upload";
14716
- if (suffix === "/resolve-channel") return "/internal/agent-api/resolve-channel";
14717
- if (suffix === "/threads/unfollow") return "/internal/agent-api/threads/unfollow";
14718
- if (suffix === "/prepare-action") return "/internal/agent-api/prepare-action";
14719
- if (suffix === "/tasks" || suffix.startsWith("/tasks?") || suffix.startsWith("/tasks/")) {
14720
- return `/internal/agent-api${suffix}`;
14721
- }
14722
- if (suffix === "/reminders" || suffix.startsWith("/reminders?") || suffix.startsWith("/reminders/")) {
14723
- return `/internal/agent-api${suffix}`;
14724
- }
14725
- if (suffix === "/receive" || suffix.startsWith("/receive?")) {
14726
- return "/internal/agent-api/events?since=latest";
14727
- }
14728
- const reaction = /^\/messages\/([^/]+)\/reactions$/.exec(suffix);
14729
- if (reaction) {
14730
- return `/internal/agent-api/messages/${reaction[1]}/reactions`;
14731
- }
14732
- const channelMembership = /^\/channels\/([^/]+)\/(join|leave)$/.exec(suffix);
14733
- if (channelMembership) {
14734
- return `/internal/agent-api/channels/${channelMembership[1]}/${channelMembership[2]}`;
14735
- }
14736
- return pathname;
14737
- }
14738
- normalizeAgentCredentialResponse(pathname, data) {
14739
- if (!this.usesAgentApiSurface()) return data;
14740
- if (!pathname.includes("/internal/agent-api/events")) return data;
14741
- const value = data;
14742
- if (!Array.isArray(value.events)) return data;
14743
- return { ...data, messages: value.events };
14744
- }
14745
- buildAuthHeaders() {
14746
- const headers = {
14747
- "Authorization": `Bearer ${this.ctx.token}`,
14748
- "X-Agent-Id": this.ctx.agentId,
14749
- "X-Slock-Client": "cli"
14750
- };
14751
- if (this.ctx.serverId) headers["X-Server-Id"] = this.ctx.serverId;
14752
- return headers;
14753
- }
14754
- async parseJsonResponse(res) {
14755
- let data = null;
14756
- let error48 = null;
14757
- let errorCode = null;
14758
- const contentType = res.headers.get("content-type") ?? "";
14759
- if (contentType.includes("application/json")) {
14760
- let parseFailed = false;
14761
- const parsed = await res.json().catch(() => {
14762
- parseFailed = true;
14763
- return null;
14764
- });
14765
- if (parseFailed) {
14766
- return {
14767
- ok: false,
14768
- status: res.status,
14769
- data: null,
14770
- error: `Invalid JSON response from server/proxy (HTTP ${res.status})`,
14771
- errorCode: "INVALID_JSON_RESPONSE"
14772
- };
14773
- }
14774
- if (res.ok) {
14775
- data = parsed;
14776
- } else {
14777
- const body = parsed;
14778
- if (res.status === 403 && body?.requiredScope) {
14779
- errorCode = "SCOPE_DENIED";
14780
- error48 = `Permission denied. The human has revoked the \`${body.requiredScope}\` capability for this agent under the agent profile's Permissions tab, so this command can't run. If you need it, ask the human to re-enable the corresponding toggle.`;
14781
- } else {
14782
- error48 = body?.error ?? `HTTP ${res.status}`;
14783
- errorCode = body?.errorCode ?? null;
14784
- }
14785
- }
14786
- } else if (!res.ok) {
14787
- error48 = `HTTP ${res.status}`;
14788
- }
14789
- return { ok: res.ok, status: res.status, data, error: error48, errorCode };
14790
- }
14791
- async request(method, pathname, body) {
14792
- pathname = this.rewriteAgentCredentialPath(pathname);
14793
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14794
- const headers = this.buildAuthHeaders();
14795
- headers["Content-Type"] = "application/json";
14796
- if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14797
- headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14798
- }
14799
- const dispatcher = buildFetchDispatcher(url2);
14800
- const init = {
14801
- method,
14802
- headers,
14803
- body: body === void 0 ? void 0 : JSON.stringify(body)
14804
- };
14805
- if (dispatcher) init.dispatcher = dispatcher;
14806
- const res = await fetch(url2, init);
14807
- const parsed = await this.parseJsonResponse(res);
14808
- if (parsed.ok && parsed.data !== null) {
14809
- parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
14851
+ /** Same resolution rule as `channelCreateOperationSchema.initialAgents`. */
14852
+ agents: external_exports.array(idOrHandleSchema).max(64).optional(),
14853
+ draftHint: draftHintSchema
14854
+ });
14855
+ var actionCardActionSchema = external_exports.discriminatedUnion("type", [
14856
+ channelCreateOperationSchema,
14857
+ agentCreateOperationSchema,
14858
+ channelAddMemberOperationSchema
14859
+ ]);
14860
+ function validateActionCardAction(action) {
14861
+ if (action.type === "agent:create") {
14862
+ if (action.suggestedComputer && action.requiredComputer) {
14863
+ return "agent:create must include only one of suggestedComputer or requiredComputer";
14810
14864
  }
14811
- return parsed;
14812
14865
  }
14813
- // Multipart upload. Caller builds the FormData (file part + any text fields).
14814
- // Content-Type intentionally omitted so fetch sets the correct multipart
14815
- // boundary itself.
14816
- async requestMultipart(method, pathname, form) {
14817
- pathname = this.rewriteAgentCredentialPath(pathname);
14818
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14819
- const dispatcher = buildFetchDispatcher(url2);
14820
- const headers = this.buildAuthHeaders();
14821
- if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14822
- headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14866
+ if (action.type === "channel:add_member") {
14867
+ const total = (action.humans?.length ?? 0) + (action.agents?.length ?? 0);
14868
+ if (total === 0) {
14869
+ return "channel:add_member must include at least one human or agent";
14823
14870
  }
14824
- const init = {
14825
- method,
14826
- headers,
14827
- body: form
14828
- };
14829
- if (dispatcher) init.dispatcher = dispatcher;
14830
- const res = await fetch(url2, init);
14831
- return this.parseJsonResponse(res);
14832
14871
  }
14833
- // Returns the raw Response so the caller can stream / save the body.
14834
- // For non-JSON downloads (binary attachments). Caller is responsible for
14835
- // consuming the body. On non-2xx, attempts to surface a JSON error.
14836
- async requestRaw(method, pathname) {
14837
- pathname = this.rewriteAgentCredentialPath(pathname);
14838
- const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14839
- const dispatcher = buildFetchDispatcher(url2);
14840
- const headers = this.buildAuthHeaders();
14841
- if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14842
- headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14843
- }
14844
- const init = {
14845
- method,
14846
- headers,
14847
- redirect: "follow"
14848
- };
14849
- if (dispatcher) init.dispatcher = dispatcher;
14850
- const res = await fetch(url2, init);
14851
- let error48 = null;
14852
- if (!res.ok) {
14853
- const contentType = res.headers.get("content-type") ?? "";
14854
- if (contentType.includes("application/json")) {
14855
- const parsed = await res.json().catch(() => null);
14856
- error48 = parsed?.error ?? `HTTP ${res.status}`;
14857
- } else {
14858
- error48 = `HTTP ${res.status}`;
14859
- }
14860
- }
14861
- return { ok: res.ok, status: res.status, response: res, error: error48 };
14872
+ return null;
14873
+ }
14874
+
14875
+ // ../shared/src/translationLanguages.ts
14876
+ var SUPPORTED_TRANSLATION_LANGUAGE_CODES = [
14877
+ "en",
14878
+ "zh-cn",
14879
+ "zh-tw",
14880
+ "ja",
14881
+ "ko",
14882
+ "es",
14883
+ "fr",
14884
+ "de",
14885
+ "pt-br"
14886
+ ];
14887
+ var SUPPORTED_TRANSLATION_LANGUAGE_SET = new Set(
14888
+ SUPPORTED_TRANSLATION_LANGUAGE_CODES
14889
+ );
14890
+
14891
+ // ../shared/src/testing/failpoints.ts
14892
+ var NoopFailpointRegistry = class {
14893
+ get enabled() {
14894
+ return false;
14895
+ }
14896
+ isEnabled() {
14897
+ return false;
14898
+ }
14899
+ configure() {
14900
+ }
14901
+ clear() {
14902
+ }
14903
+ getTrace() {
14904
+ return [];
14905
+ }
14906
+ hit(_key, _context, fallback) {
14907
+ return fallback ? fallback() : void 0;
14908
+ }
14909
+ };
14910
+ var noopFailpointRegistry = new NoopFailpointRegistry();
14911
+
14912
+ // ../shared/src/serverPermissions.ts
14913
+ var EMPTY_SERVER_CAPABILITIES = Object.freeze({
14914
+ manageServer: false,
14915
+ manageChannels: false,
14916
+ manageAgents: false,
14917
+ manageMachines: false,
14918
+ manageMembers: false,
14919
+ changeMemberRoles: false,
14920
+ manageBilling: false,
14921
+ joinPublicChannels: false
14922
+ });
14923
+ var SERVER_CAPABILITY_MATRIX = {
14924
+ owner: Object.freeze({
14925
+ manageServer: true,
14926
+ manageChannels: true,
14927
+ manageAgents: true,
14928
+ manageMachines: true,
14929
+ manageMembers: true,
14930
+ changeMemberRoles: true,
14931
+ manageBilling: true,
14932
+ joinPublicChannels: true
14933
+ }),
14934
+ admin: Object.freeze({
14935
+ manageServer: true,
14936
+ manageChannels: true,
14937
+ manageAgents: true,
14938
+ manageMachines: true,
14939
+ manageMembers: true,
14940
+ changeMemberRoles: true,
14941
+ manageBilling: false,
14942
+ joinPublicChannels: true
14943
+ }),
14944
+ member: Object.freeze({
14945
+ manageServer: false,
14946
+ manageChannels: false,
14947
+ manageAgents: false,
14948
+ manageMachines: false,
14949
+ manageMembers: false,
14950
+ changeMemberRoles: false,
14951
+ manageBilling: false,
14952
+ joinPublicChannels: true
14953
+ })
14954
+ };
14955
+
14956
+ // ../shared/src/index.ts
14957
+ var RUNTIMES = [
14958
+ { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
14959
+ { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
14960
+ { id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
14961
+ { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
14962
+ { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
14963
+ { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
14964
+ { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
14965
+ { id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
14966
+ ];
14967
+ function getRuntimeDisplayName(id) {
14968
+ return RUNTIMES.find((r) => r.id === id)?.displayName ?? id;
14969
+ }
14970
+ var PLAN_CONFIG = {
14971
+ free: {
14972
+ displayName: "Hobby",
14973
+ limits: { maxMachines: 2, maxAgents: 5, maxChannels: 5, messageHistoryDays: 30, includedAgents: 5 },
14974
+ comingSoon: false,
14975
+ price: 0,
14976
+ extraAgentPrice: 0
14977
+ },
14978
+ founder: {
14979
+ displayName: "Founder",
14980
+ limits: { maxMachines: -1, maxAgents: -1, maxChannels: -1, messageHistoryDays: -1, includedAgents: -1 },
14981
+ comingSoon: false,
14982
+ price: 0,
14983
+ extraAgentPrice: 0
14984
+ }
14985
+ };
14986
+ var DISPLAY_PLAN_CONFIG = {
14987
+ free: PLAN_CONFIG.free,
14988
+ pro: {
14989
+ displayName: "Team",
14990
+ limits: { maxMachines: 8, maxAgents: 40, maxChannels: 20, messageHistoryDays: -1, includedAgents: 40 },
14991
+ comingSoon: true,
14992
+ price: 20,
14993
+ extraAgentPrice: 0
14994
+ },
14995
+ max: {
14996
+ displayName: "Business",
14997
+ limits: { maxMachines: 40, maxAgents: 200, maxChannels: -1, messageHistoryDays: -1, includedAgents: 200 },
14998
+ comingSoon: true,
14999
+ price: 200,
15000
+ extraAgentPrice: 0
14862
15001
  }
14863
15002
  };
14864
15003
 
@@ -15053,29 +15192,39 @@ function formatChannelMembers(data) {
15053
15192
  }
15054
15193
 
15055
15194
  // src/commands/channel/members.ts
15056
- function registerChannelMembersCommand(parent) {
15057
- parent.command("members").description("List agents and humans who are members of a channel, DM, or thread").argument("<target>", "Channel / DM / thread target, e.g. #proj-runtime, dm:@alice, #proj-runtime:abcd1234").action(async (target) => {
15058
- let ctx;
15059
- try {
15060
- ctx = loadAgentContext();
15061
- } catch (err) {
15062
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15063
- throw err;
15064
- }
15065
- const client = new ApiClient(ctx);
15195
+ var channelMembersCommand = defineCommand(
15196
+ {
15197
+ name: "members",
15198
+ description: "List agents and humans who are members of a channel, DM, or thread",
15199
+ rationale: "denied",
15200
+ arguments: ["<target>"]
15201
+ },
15202
+ async (ctx, target) => {
15203
+ const agentContext = ctx.loadAgentContext();
15204
+ const client = ctx.createApiClient(agentContext);
15066
15205
  const channel = String(target || "").trim();
15067
- if (!channel) fail("MEMBERS_FAILED", "target is required");
15206
+ if (!channel) {
15207
+ throw new CliError({
15208
+ code: "INVALID_ARG",
15209
+ message: "target is required"
15210
+ });
15211
+ }
15068
15212
  const encoded = encodeURIComponent(channel);
15069
15213
  const res = await client.request(
15070
15214
  "GET",
15071
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/channel-members?channel=${encoded}`
15215
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/channel-members?channel=${encoded}`
15072
15216
  );
15073
15217
  if (!res.ok) {
15074
- const code = res.status >= 500 ? "SERVER_5XX" : "MEMBERS_FAILED";
15075
- fail(code, res.error ?? `HTTP ${res.status}`);
15218
+ throw new CliError({
15219
+ code: res.status >= 500 ? "SERVER_5XX" : "MEMBERS_FAILED",
15220
+ message: res.error ?? `HTTP ${res.status}`
15221
+ });
15076
15222
  }
15077
- process.stdout.write(formatChannelMembers(res.data));
15078
- });
15223
+ writeText(ctx.io, formatChannelMembers(res.data));
15224
+ }
15225
+ );
15226
+ function registerChannelMembersCommand(parent, runtimeOptions) {
15227
+ registerCliCommand(parent, channelMembersCommand, runtimeOptions);
15079
15228
  }
15080
15229
 
15081
15230
  // src/commands/channel/leave.ts
@@ -15091,46 +15240,65 @@ function formatLeaveChannelResult(target) {
15091
15240
  function formatAlreadyNotJoined(target) {
15092
15241
  return `Already not joined in ${target}.`;
15093
15242
  }
15094
- function registerChannelLeaveCommand(parent) {
15095
- parent.command("leave").description("Leave a regular channel you have joined").requiredOption("--target <target>", "Regular channel to leave, e.g. '#engineering'").action(async (opts) => {
15096
- const channelName = parseRegularChannelTarget(opts.target);
15243
+ var channelLeaveCommand = defineCommand(
15244
+ {
15245
+ name: "leave",
15246
+ description: "Leave a regular channel you have joined",
15247
+ rationale: "required",
15248
+ options: [
15249
+ {
15250
+ flags: "--target <target>",
15251
+ description: "Regular channel to leave, e.g. '#engineering'"
15252
+ }
15253
+ ]
15254
+ },
15255
+ async (ctx, opts) => {
15256
+ const target = opts.target ?? "";
15257
+ const channelName = parseRegularChannelTarget(target);
15097
15258
  if (!channelName) {
15098
- fail("INVALID_TARGET", "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.");
15099
- }
15100
- let ctx;
15101
- try {
15102
- ctx = loadAgentContext();
15103
- } catch (err) {
15104
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15105
- throw err;
15259
+ throw new CliError({
15260
+ code: "INVALID_TARGET",
15261
+ message: "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported."
15262
+ });
15106
15263
  }
15107
- const client = new ApiClient(ctx);
15264
+ const agentContext = ctx.loadAgentContext();
15265
+ const client = ctx.createApiClient(agentContext);
15108
15266
  const infoRes = await client.request(
15109
15267
  "GET",
15110
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
15268
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/server`
15111
15269
  );
15112
15270
  if (!infoRes.ok) {
15113
- const code = infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
15114
- fail(code, infoRes.error ?? `HTTP ${infoRes.status}`);
15271
+ throw new CliError({
15272
+ code: infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED",
15273
+ message: infoRes.error ?? `HTTP ${infoRes.status}`
15274
+ });
15115
15275
  }
15116
15276
  const channel = (infoRes.data?.channels ?? []).find((candidate) => candidate.name === channelName);
15117
15277
  if (!channel) {
15118
- fail("NOT_FOUND", `Channel not found: ${opts.target}`);
15278
+ throw new CliError({
15279
+ code: "NOT_FOUND",
15280
+ message: `Channel not found: ${target}`
15281
+ });
15119
15282
  }
15120
15283
  if (!channel.joined) {
15121
- process.stdout.write(formatAlreadyNotJoined(opts.target) + "\n");
15284
+ writeText(ctx.io, formatAlreadyNotJoined(target) + "\n");
15122
15285
  return;
15123
15286
  }
15124
15287
  const leaveRes = await client.request(
15125
15288
  "POST",
15126
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/channels/${encodeURIComponent(channel.id)}/leave`
15289
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/channels/${encodeURIComponent(channel.id)}/leave`
15127
15290
  );
15128
15291
  if (!leaveRes.ok) {
15129
- const code = leaveRes.status >= 500 ? "SERVER_5XX" : "LEAVE_FAILED";
15130
- fail(code, leaveRes.error ?? `HTTP ${leaveRes.status}`);
15292
+ throw new CliError({
15293
+ code: leaveRes.status >= 500 ? "SERVER_5XX" : "LEAVE_FAILED",
15294
+ message: leaveRes.error ?? `HTTP ${leaveRes.status}`
15295
+ });
15131
15296
  }
15132
- process.stdout.write(formatLeaveChannelResult(opts.target) + "\n");
15133
- });
15297
+ writeText(ctx.io, formatLeaveChannelResult(target) + "\n");
15298
+ }
15299
+ );
15300
+ function registerChannelLeaveCommand(parent, runtimeOptions) {
15301
+ registerCliCommand(parent, channelLeaveCommand, runtimeOptions);
15134
15302
  }
15135
15303
 
15136
15304
  // src/commands/channel/join.ts
@@ -15140,76 +15308,179 @@ function formatJoinChannelResult(target) {
15140
15308
  function formatAlreadyJoined(target) {
15141
15309
  return `Already joined ${target}.`;
15142
15310
  }
15143
- function registerChannelJoinCommand(parent) {
15144
- parent.command("join").description("Join a visible public channel").requiredOption("--target <target>", "Regular channel to join, e.g. '#engineering'").action(async (opts) => {
15145
- const channelName = parseRegularChannelTarget(opts.target);
15311
+ var channelJoinCommand = defineCommand(
15312
+ {
15313
+ name: "join",
15314
+ description: "Join a visible public channel",
15315
+ rationale: "required",
15316
+ options: [
15317
+ {
15318
+ flags: "--target <target>",
15319
+ description: "Regular channel to join, e.g. '#engineering'"
15320
+ }
15321
+ ]
15322
+ },
15323
+ async (ctx, opts) => {
15324
+ const target = opts.target ?? "";
15325
+ const channelName = parseRegularChannelTarget(target);
15146
15326
  if (!channelName) {
15147
- fail("INVALID_TARGET", "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.");
15148
- }
15149
- let ctx;
15150
- try {
15151
- ctx = loadAgentContext();
15152
- } catch (err) {
15153
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15154
- throw err;
15327
+ throw new CliError({
15328
+ code: "INVALID_TARGET",
15329
+ message: "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported."
15330
+ });
15155
15331
  }
15156
- const client = new ApiClient(ctx);
15332
+ const agentContext = ctx.loadAgentContext();
15333
+ const client = ctx.createApiClient(agentContext);
15157
15334
  const infoRes = await client.request(
15158
15335
  "GET",
15159
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
15336
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/server`
15160
15337
  );
15161
15338
  if (!infoRes.ok) {
15162
- const code = infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
15163
- fail(code, infoRes.error ?? `HTTP ${infoRes.status}`);
15339
+ throw new CliError({
15340
+ code: infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED",
15341
+ message: infoRes.error ?? `HTTP ${infoRes.status}`
15342
+ });
15164
15343
  }
15165
15344
  const channel = (infoRes.data?.channels ?? []).find((candidate) => candidate.name === channelName);
15166
15345
  if (!channel) {
15167
- fail("NOT_FOUND", `Channel not found: ${opts.target}`);
15346
+ throw new CliError({
15347
+ code: "NOT_FOUND",
15348
+ message: `Channel not found: ${target}`
15349
+ });
15168
15350
  }
15169
15351
  if (channel.joined) {
15170
- process.stdout.write(formatAlreadyJoined(opts.target) + "\n");
15352
+ writeText(ctx.io, formatAlreadyJoined(target) + "\n");
15171
15353
  return;
15172
15354
  }
15173
15355
  const joinRes = await client.request(
15174
15356
  "POST",
15175
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/channels/${encodeURIComponent(channel.id)}/join`
15357
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/channels/${encodeURIComponent(channel.id)}/join`
15176
15358
  );
15177
15359
  if (!joinRes.ok) {
15178
- const code = joinRes.status >= 500 ? "SERVER_5XX" : "JOIN_FAILED";
15179
- fail(code, joinRes.error ?? `HTTP ${joinRes.status}`);
15180
- }
15181
- process.stdout.write(formatJoinChannelResult(opts.target) + "\n");
15182
- });
15183
- }
15184
-
15185
- // src/commands/server/info.ts
15186
- function registerServerInfoCommand(parent) {
15187
- parent.command("info").description("List channels, agents, and humans on the current server").action(async () => {
15188
- let ctx;
15189
- try {
15190
- ctx = loadAgentContext();
15191
- } catch (err) {
15192
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15193
- throw err;
15360
+ throw new CliError({
15361
+ code: joinRes.status >= 500 ? "SERVER_5XX" : "JOIN_FAILED",
15362
+ message: joinRes.error ?? `HTTP ${joinRes.status}`
15363
+ });
15194
15364
  }
15195
- const client = new ApiClient(ctx);
15365
+ writeText(ctx.io, formatJoinChannelResult(target) + "\n");
15366
+ }
15367
+ );
15368
+ function registerChannelJoinCommand(parent, runtimeOptions) {
15369
+ registerCliCommand(parent, channelJoinCommand, runtimeOptions);
15370
+ }
15371
+
15372
+ // src/commands/server/info.ts
15373
+ var serverInfoCommand = defineCommand(
15374
+ {
15375
+ name: "info",
15376
+ description: "List channels, agents, and humans on the current server",
15377
+ rationale: "denied"
15378
+ },
15379
+ async (ctx) => {
15380
+ const agentContext = ctx.loadAgentContext();
15381
+ const client = ctx.createApiClient(agentContext);
15196
15382
  const res = await client.request(
15197
15383
  "GET",
15198
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
15384
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/server`
15199
15385
  );
15200
15386
  if (!res.ok) {
15201
15387
  const code = res.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
15202
- fail(code, res.error ?? `HTTP ${res.status}`);
15388
+ throw new CliError({
15389
+ code,
15390
+ message: res.error ?? `HTTP ${res.status}`
15391
+ });
15203
15392
  }
15204
15393
  const data = res.data;
15205
15394
  if (data?.runtimeContext) {
15206
15395
  data.runtimeContext = {
15207
15396
  ...data.runtimeContext,
15208
- workspacePath: data.runtimeContext.workspacePath ?? process.env.SLOCK_CURRENT_WORKSPACE_PATH ?? null
15397
+ workspacePath: data.runtimeContext.workspacePath ?? ctx.env.SLOCK_CURRENT_WORKSPACE_PATH ?? null
15209
15398
  };
15210
15399
  }
15211
- process.stdout.write(formatServerInfo(data));
15212
- });
15400
+ writeText(ctx.io, formatServerInfo(data));
15401
+ }
15402
+ );
15403
+ function registerServerInfoCommand(parent, runtimeOptions) {
15404
+ registerCliCommand(parent, serverInfoCommand, runtimeOptions);
15405
+ }
15406
+
15407
+ // src/commands/knowledge/get.ts
15408
+ function buildKnowledgeGetPath(agentId, topic, opts) {
15409
+ const params = new URLSearchParams();
15410
+ params.set("topic", topic);
15411
+ if (opts.reason) params.set("reason", opts.reason);
15412
+ if (opts.turnId) params.set("turn_id", opts.turnId);
15413
+ if (opts.traceId) params.set("trace_id", opts.traceId);
15414
+ return `/internal/agent/${encodeURIComponent(agentId)}/knowledge?${params.toString()}`;
15415
+ }
15416
+ function formatKnowledgeStdout(content) {
15417
+ return content.endsWith("\n") ? content : `${content}
15418
+ `;
15419
+ }
15420
+ function toKnowledgeErrorCode(errorCode, status) {
15421
+ switch (errorCode) {
15422
+ case "INVALID_JSON_RESPONSE":
15423
+ case "SCOPE_DENIED":
15424
+ case "knowledge_agent_missing":
15425
+ case "knowledge_internal_error":
15426
+ case "knowledge_not_found":
15427
+ case "knowledge_reason_invalid":
15428
+ case "knowledge_source_invalid":
15429
+ case "knowledge_topic_invalid":
15430
+ case "knowledge_trace_id_invalid":
15431
+ case "knowledge_turn_id_invalid":
15432
+ case "unsupported_capability":
15433
+ return errorCode;
15434
+ default:
15435
+ return status >= 500 ? "SERVER_5XX" : "KNOWLEDGE_GET_FAILED";
15436
+ }
15437
+ }
15438
+ var knowledgeGetCommand = defineCommand(
15439
+ {
15440
+ name: "get",
15441
+ description: "Fetch an agent knowledge topic from the current server",
15442
+ rationale: "optional",
15443
+ arguments: ["<topic>"],
15444
+ options: [
15445
+ {
15446
+ flags: "--reason <text>",
15447
+ description: "Optional rationale for fetching this topic (>=12 chars when provided)"
15448
+ },
15449
+ {
15450
+ flags: "--turn-id <id>",
15451
+ description: "Optional turn id for correlation in the knowledge event"
15452
+ },
15453
+ {
15454
+ flags: "--trace-id <id>",
15455
+ description: "Optional trace id for correlation in the knowledge event"
15456
+ }
15457
+ ]
15458
+ },
15459
+ async (ctx, topic, opts) => {
15460
+ const agentContext = ctx.loadAgentContext();
15461
+ const client = ctx.createApiClient(agentContext);
15462
+ const res = await client.request(
15463
+ "GET",
15464
+ buildKnowledgeGetPath(agentContext.agentId, topic, opts)
15465
+ );
15466
+ if (!res.ok) {
15467
+ throw new CliError({
15468
+ code: toKnowledgeErrorCode(res.errorCode, res.status),
15469
+ message: res.error ?? `HTTP ${res.status}`
15470
+ });
15471
+ }
15472
+ const data = res.data;
15473
+ if (!data || data.ok !== true) {
15474
+ throw new CliError({
15475
+ code: "KNOWLEDGE_GET_FAILED",
15476
+ message: "Server returned an unexpected response shape"
15477
+ });
15478
+ }
15479
+ writeText(ctx.io, formatKnowledgeStdout(data.content));
15480
+ }
15481
+ );
15482
+ function registerKnowledgeGetCommand(parent, runtimeOptions) {
15483
+ registerCliCommand(parent, knowledgeGetCommand, runtimeOptions);
15213
15484
  }
15214
15485
 
15215
15486
  // src/commands/thread/unfollow.ts
@@ -15237,31 +15508,44 @@ function parseThreadTarget(target) {
15237
15508
  function formatUnfollowThreadResult(target) {
15238
15509
  return `Unfollowed ${target}. You can still inspect the thread when its parent conversation is visible, but you will no longer receive ordinary thread delivery unless you follow it again or are mentioned.`;
15239
15510
  }
15240
- function registerThreadUnfollowCommand(parent) {
15241
- parent.command("unfollow").description("Stop following a thread you no longer need ordinary delivery for").requiredOption("--target <target>", "Thread target, e.g. '#engineering:abcd1234' or 'dm:@alice:abcd1234'").action(async (opts) => {
15242
- const thread = parseThreadTarget(opts.target);
15511
+ var threadUnfollowCommand = defineCommand(
15512
+ {
15513
+ name: "unfollow",
15514
+ description: "Stop following a thread you no longer need ordinary delivery for",
15515
+ rationale: "required",
15516
+ options: [
15517
+ {
15518
+ flags: "--target <target>",
15519
+ description: "Thread target, e.g. '#engineering:abcd1234' or 'dm:@alice:abcd1234'"
15520
+ }
15521
+ ]
15522
+ },
15523
+ async (ctx, opts) => {
15524
+ const thread = parseThreadTarget(opts.target ?? "");
15243
15525
  if (!thread) {
15244
- fail("INVALID_TARGET", "Thread must be a thread target like '#channel:abcd1234', 'dm:@peer:abcd1234', or a thread channel UUID.");
15245
- }
15246
- let ctx;
15247
- try {
15248
- ctx = loadAgentContext();
15249
- } catch (err) {
15250
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15251
- throw err;
15526
+ throw new CliError({
15527
+ code: "INVALID_TARGET",
15528
+ message: "Thread must be a thread target like '#channel:abcd1234', 'dm:@peer:abcd1234', or a thread channel UUID."
15529
+ });
15252
15530
  }
15253
- const client = new ApiClient(ctx);
15531
+ const agentContext = ctx.loadAgentContext();
15532
+ const client = ctx.createApiClient(agentContext);
15254
15533
  const res = await client.request(
15255
15534
  "POST",
15256
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/threads/unfollow`,
15535
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/threads/unfollow`,
15257
15536
  { thread }
15258
15537
  );
15259
15538
  if (!res.ok) {
15260
- const code = res.status >= 500 ? "SERVER_5XX" : "UNFOLLOW_FAILED";
15261
- fail(code, res.error ?? `HTTP ${res.status}`);
15539
+ throw new CliError({
15540
+ code: res.status >= 500 ? "SERVER_5XX" : "UNFOLLOW_FAILED",
15541
+ message: res.error ?? `HTTP ${res.status}`
15542
+ });
15262
15543
  }
15263
- process.stdout.write(formatUnfollowThreadResult(thread) + "\n");
15264
- });
15544
+ writeText(ctx.io, formatUnfollowThreadResult(thread) + "\n");
15545
+ }
15546
+ );
15547
+ function registerThreadUnfollowCommand(parent, runtimeOptions) {
15548
+ registerCliCommand(parent, threadUnfollowCommand, runtimeOptions);
15265
15549
  }
15266
15550
 
15267
15551
  // src/commands/message/_format.ts
@@ -15727,8 +16011,7 @@ ${formatMessages(data.recentUnread)}`;
15727
16011
  }
15728
16012
 
15729
16013
  // src/commands/message/_inbox.ts
15730
- async function drainInbox(ctx, opts) {
15731
- const client = new ApiClient(ctx);
16014
+ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
15732
16015
  const agentPath = `/internal/agent/${encodeURIComponent(ctx.agentId)}`;
15733
16016
  const failCode = opts.block ? "WAIT_FAILED" : "CHECK_FAILED";
15734
16017
  const query = [];
@@ -15737,8 +16020,10 @@ async function drainInbox(ctx, opts) {
15737
16020
  const path4 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
15738
16021
  const res = await client.request("GET", path4);
15739
16022
  if (!res.ok) {
15740
- const code = res.status >= 500 ? "SERVER_5XX" : failCode;
15741
- fail(code, res.error ?? `HTTP ${res.status}`);
16023
+ throw new CliError({
16024
+ code: res.status >= 500 ? "SERVER_5XX" : failCode,
16025
+ message: res.error ?? `HTTP ${res.status}`
16026
+ });
15742
16027
  }
15743
16028
  const messages = res.data?.messages ?? [];
15744
16029
  if (ctx.clientMode === "managed-runner" || ctx.clientMode === "self-hosted-runner") {
@@ -15754,18 +16039,25 @@ async function drainInbox(ctx, opts) {
15754
16039
  }
15755
16040
 
15756
16041
  // src/commands/message/check.ts
15757
- function registerCheckCommand(parent) {
15758
- parent.command("check").description("Drain the agent inbox (non-blocking). Acks delivered seqs before returning.").action(async () => {
15759
- let ctx;
15760
- try {
15761
- ctx = loadAgentContext();
15762
- } catch (err) {
15763
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15764
- throw err;
15765
- }
15766
- const result = await drainInbox(ctx, { block: false });
15767
- process.stdout.write(formatMessages(result.messages) + "\n");
15768
- });
16042
+ var messageCheckCommand = defineCommand(
16043
+ {
16044
+ name: "check",
16045
+ description: "Drain the agent inbox (non-blocking). Acks delivered seqs before returning.",
16046
+ rationale: "denied"
16047
+ },
16048
+ async (ctx) => {
16049
+ const agentContext = ctx.loadAgentContext();
16050
+ const result = await drainInbox(
16051
+ agentContext,
16052
+ { block: false },
16053
+ ctx.createApiClient(agentContext)
16054
+ );
16055
+ writeText(ctx.io, `${formatMessages(result.messages)}
16056
+ `);
16057
+ }
16058
+ );
16059
+ function registerCheckCommand(parent, runtimeOptions) {
16060
+ registerCliCommand(parent, messageCheckCommand, runtimeOptions);
15769
16061
  }
15770
16062
 
15771
16063
  // src/commands/message/read.ts
@@ -15773,39 +16065,81 @@ function parsePositiveInt(name, raw) {
15773
16065
  if (raw === void 0) return void 0;
15774
16066
  const n = Number(raw);
15775
16067
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
15776
- fail("INVALID_ARG", `--${name} must be a positive integer; got ${raw}`);
16068
+ throw new CliError({
16069
+ code: "INVALID_ARG",
16070
+ message: `--${name} must be a positive integer; got ${raw}`
16071
+ });
15777
16072
  }
15778
16073
  return n;
15779
16074
  }
15780
- function registerReadCommand(parent) {
15781
- parent.command("read").description("Read message history for a channel, DM, or thread").requiredOption("--channel <target>", "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'").option("--before <seq>", "Return messages strictly before this seq (paginate backwards)").option("--after <seq>", "Return messages strictly after this seq (paginate forwards)").option("--around <idOrSeq>", "Center the window on this messageId or seq").option("--limit <n>", "Max messages to return (server default applies if omitted)").action(async (opts) => {
15782
- let ctx;
15783
- try {
15784
- ctx = loadAgentContext();
15785
- } catch (err) {
15786
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15787
- throw err;
15788
- }
15789
- const before = parsePositiveInt("before", opts.before);
15790
- const after = parsePositiveInt("after", opts.after);
15791
- const limit = parsePositiveInt("limit", opts.limit);
15792
- const params = new URLSearchParams();
15793
- params.set("channel", opts.channel);
15794
- if (before !== void 0) params.set("before", String(before));
15795
- if (after !== void 0) params.set("after", String(after));
15796
- if (opts.around !== void 0) params.set("around", opts.around);
15797
- if (limit !== void 0) params.set("limit", String(limit));
15798
- const client = new ApiClient(ctx);
16075
+ function buildReadPath(agentId, opts) {
16076
+ const params = new URLSearchParams();
16077
+ params.set("channel", opts.channel);
16078
+ if (opts.before !== void 0) params.set("before", String(opts.before));
16079
+ if (opts.after !== void 0) params.set("after", String(opts.after));
16080
+ if (opts.around !== void 0) params.set("around", opts.around);
16081
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
16082
+ return `/internal/agent/${encodeURIComponent(agentId)}/history?${params.toString()}`;
16083
+ }
16084
+ function validateReadOpts(opts) {
16085
+ const channel = opts.channel?.trim();
16086
+ if (!channel) {
16087
+ throw new CliError({
16088
+ code: "INVALID_ARG",
16089
+ message: "--channel is required"
16090
+ });
16091
+ }
16092
+ const before = parsePositiveInt("before", opts.before);
16093
+ const after = parsePositiveInt("after", opts.after);
16094
+ const limit = parsePositiveInt("limit", opts.limit);
16095
+ return {
16096
+ channel,
16097
+ ...before !== void 0 ? { before } : {},
16098
+ ...after !== void 0 ? { after } : {},
16099
+ ...opts.around !== void 0 ? { around: opts.around } : {},
16100
+ ...limit !== void 0 ? { limit } : {}
16101
+ };
16102
+ }
16103
+ var messageReadCommand = defineCommand(
16104
+ {
16105
+ name: "read",
16106
+ description: "Read message history for a channel, DM, or thread",
16107
+ rationale: "denied",
16108
+ options: [
16109
+ { flags: "--channel <target>", description: "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'" },
16110
+ { flags: "--before <seq>", description: "Return messages strictly before this seq (paginate backwards)" },
16111
+ { flags: "--after <seq>", description: "Return messages strictly after this seq (paginate forwards)" },
16112
+ { flags: "--around <idOrSeq>", description: "Center the window on this messageId or seq" },
16113
+ { flags: "--limit <n>", description: "Max messages to return (server default applies if omitted)" }
16114
+ ]
16115
+ },
16116
+ async (ctx, opts) => {
16117
+ const readOpts = validateReadOpts(opts);
16118
+ const agentContext = ctx.loadAgentContext();
16119
+ const client = ctx.createApiClient(agentContext);
15799
16120
  const res = await client.request(
15800
16121
  "GET",
15801
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/history?${params.toString()}`
16122
+ buildReadPath(agentContext.agentId, readOpts)
15802
16123
  );
15803
16124
  if (!res.ok) {
15804
- const code = res.status >= 500 ? "SERVER_5XX" : "READ_FAILED";
15805
- fail(code, res.error ?? `HTTP ${res.status}`);
16125
+ throw new CliError({
16126
+ code: res.status >= 500 ? "SERVER_5XX" : "READ_FAILED",
16127
+ message: res.error ?? `HTTP ${res.status}`
16128
+ });
15806
16129
  }
15807
- process.stdout.write(formatHistory(opts.channel, res.data, { around: opts.around, after, before }) + "\n");
15808
- });
16130
+ writeText(
16131
+ ctx.io,
16132
+ `${formatHistory(readOpts.channel, res.data, {
16133
+ around: readOpts.around,
16134
+ after: readOpts.after,
16135
+ before: readOpts.before
16136
+ })}
16137
+ `
16138
+ );
16139
+ }
16140
+ );
16141
+ function registerReadCommand(parent, runtimeOptions) {
16142
+ registerCliCommand(parent, messageReadCommand, runtimeOptions);
15809
16143
  }
15810
16144
 
15811
16145
  // src/commands/message/search.ts
@@ -15813,56 +16147,117 @@ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
15813
16147
  function normalizeMemberHandleRef(raw) {
15814
16148
  const trimmed = raw.trim();
15815
16149
  if (!trimmed) {
15816
- fail("INVALID_ARG", "--sender must not be empty");
16150
+ throw new CliError({
16151
+ code: "INVALID_ARG",
16152
+ message: "--sender must not be empty"
16153
+ });
15817
16154
  }
15818
16155
  if (UUID_RE2.test(trimmed)) {
15819
- fail("INVALID_ARG", "--sender expects a member handle like @alice, not a UUID");
16156
+ throw new CliError({
16157
+ code: "INVALID_ARG",
16158
+ message: "--sender expects a member handle like @alice, not a UUID"
16159
+ });
15820
16160
  }
15821
16161
  const handle = trimmed.replace(/^@/, "").trim();
15822
16162
  if (!handle) {
15823
- fail("INVALID_ARG", "--sender handle must not be empty");
16163
+ throw new CliError({
16164
+ code: "INVALID_ARG",
16165
+ message: "--sender handle must not be empty"
16166
+ });
15824
16167
  }
15825
16168
  return handle;
15826
16169
  }
15827
- function registerSearchCommand(parent) {
15828
- parent.command("search").description("Search messages across channels the agent can see").requiredOption("--query <q>", "Search query string").option("--channel <target>", "Restrict to a single channel/DM/thread").option("--sender <handle>", "Restrict to messages by sender handle, e.g. @alice").option("--sort <mode>", "Sort results by relevance or recent (default: relevance)").option("--before <iso>", "Only messages before this ISO datetime").option("--after <iso>", "Only messages after this ISO datetime").option("--limit <n>", "Max results (server default applies if omitted)").action(async (opts) => {
15829
- let ctx;
15830
- try {
15831
- ctx = loadAgentContext();
15832
- } catch (err) {
15833
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
15834
- throw err;
15835
- }
15836
- let limit;
15837
- if (opts.limit !== void 0) {
15838
- const n = Number(opts.limit);
15839
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
15840
- fail("INVALID_ARG", `--limit must be a positive integer; got ${opts.limit}`);
15841
- }
15842
- limit = n;
15843
- }
15844
- if (opts.sort !== void 0 && opts.sort !== "relevance" && opts.sort !== "recent") {
15845
- fail("INVALID_ARG", `--sort must be "relevance" or "recent"; got ${opts.sort}`);
15846
- }
15847
- const params = new URLSearchParams();
15848
- params.set("q", opts.query);
15849
- if (opts.channel) params.set("channel", opts.channel);
15850
- if (opts.sender) params.set("sender", normalizeMemberHandleRef(opts.sender));
15851
- if (opts.sort) params.set("sort", opts.sort);
15852
- if (opts.before) params.set("before", opts.before);
15853
- if (opts.after) params.set("after", opts.after);
15854
- if (limit !== void 0) params.set("limit", String(limit));
15855
- const client = new ApiClient(ctx);
16170
+ function parsePositiveInt2(name, raw) {
16171
+ if (raw === void 0) return void 0;
16172
+ const n = Number(raw);
16173
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16174
+ throw new CliError({
16175
+ code: "INVALID_ARG",
16176
+ message: `--${name} must be a positive integer; got ${raw}`
16177
+ });
16178
+ }
16179
+ return n;
16180
+ }
16181
+ function normalizeSearchOpts(opts) {
16182
+ const query = opts.query?.trim();
16183
+ if (!query) {
16184
+ throw new CliError({
16185
+ code: "INVALID_ARG",
16186
+ message: "--query is required"
16187
+ });
16188
+ }
16189
+ if (opts.sort !== void 0 && opts.sort !== "relevance" && opts.sort !== "recent") {
16190
+ throw new CliError({
16191
+ code: "INVALID_ARG",
16192
+ message: `--sort must be "relevance" or "recent"; got ${opts.sort}`
16193
+ });
16194
+ }
16195
+ const limit = parsePositiveInt2("limit", opts.limit);
16196
+ return {
16197
+ query,
16198
+ ...opts.channel ? { channel: opts.channel } : {},
16199
+ ...opts.sender ? { sender: normalizeMemberHandleRef(opts.sender) } : {},
16200
+ ...opts.sort ? { sort: opts.sort } : {},
16201
+ ...opts.before ? { before: opts.before } : {},
16202
+ ...opts.after ? { after: opts.after } : {},
16203
+ ...limit !== void 0 ? { limit } : {}
16204
+ };
16205
+ }
16206
+ function buildSearchPath(agentId, opts) {
16207
+ const params = new URLSearchParams();
16208
+ params.set("q", opts.query);
16209
+ if (opts.channel) params.set("channel", opts.channel);
16210
+ if (opts.sender) params.set("sender", opts.sender);
16211
+ if (opts.sort) params.set("sort", opts.sort);
16212
+ if (opts.before) params.set("before", opts.before);
16213
+ if (opts.after) params.set("after", opts.after);
16214
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
16215
+ return `/internal/agent/${encodeURIComponent(agentId)}/search?${params.toString()}`;
16216
+ }
16217
+ function toSearchErrorCode(errorCode, status) {
16218
+ switch (errorCode) {
16219
+ case "SCOPE_DENIED":
16220
+ case "INVALID_JSON_RESPONSE":
16221
+ return errorCode;
16222
+ default:
16223
+ return status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED";
16224
+ }
16225
+ }
16226
+ var messageSearchCommand = defineCommand(
16227
+ {
16228
+ name: "search",
16229
+ description: "Search messages across channels the agent can see",
16230
+ rationale: "denied",
16231
+ options: [
16232
+ { flags: "--query <q>", description: "Search query string" },
16233
+ { flags: "--channel <target>", description: "Restrict to a single channel/DM/thread" },
16234
+ { flags: "--sender <handle>", description: "Restrict to messages by sender handle, e.g. @alice" },
16235
+ { flags: "--sort <mode>", description: "Sort results by relevance or recent (default: relevance)" },
16236
+ { flags: "--before <iso>", description: "Only messages before this ISO datetime" },
16237
+ { flags: "--after <iso>", description: "Only messages after this ISO datetime" },
16238
+ { flags: "--limit <n>", description: "Max results (server default applies if omitted)" }
16239
+ ]
16240
+ },
16241
+ async (ctx, opts) => {
16242
+ const searchOpts = normalizeSearchOpts(opts);
16243
+ const agentContext = ctx.loadAgentContext();
16244
+ const client = ctx.createApiClient(agentContext);
15856
16245
  const res = await client.request(
15857
16246
  "GET",
15858
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/search?${params.toString()}`
16247
+ buildSearchPath(agentContext.agentId, searchOpts)
15859
16248
  );
15860
16249
  if (!res.ok) {
15861
- const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED");
15862
- fail(code, res.error ?? `HTTP ${res.status}`);
16250
+ throw new CliError({
16251
+ code: toSearchErrorCode(res.errorCode, res.status),
16252
+ message: res.error ?? `HTTP ${res.status}`
16253
+ });
15863
16254
  }
15864
- process.stdout.write(formatSearchResults(opts.query, res.data) + "\n");
15865
- });
16255
+ writeText(ctx.io, `${formatSearchResults(searchOpts.query, res.data)}
16256
+ `);
16257
+ }
16258
+ );
16259
+ function registerSearchCommand(parent, runtimeOptions) {
16260
+ registerCliCommand(parent, messageSearchCommand, runtimeOptions);
15866
16261
  }
15867
16262
 
15868
16263
  // src/commands/message/react.ts
@@ -16075,29 +16470,55 @@ Use this ID with slock message send --attachment-id ${d.id} to include it in a m
16075
16470
 
16076
16471
  // src/commands/attachment/view.ts
16077
16472
  import { writeFileSync } from "fs";
16078
- function registerAttachmentViewCommand(parent) {
16079
- parent.command("view").description("Download an attachment by id and save it to a local path").requiredOption("--id <attachmentId>", "Attachment UUID").requiredOption("--output <path>", "Local path to write the file to").action(async (opts) => {
16080
- let ctx;
16081
- try {
16082
- ctx = loadAgentContext();
16083
- } catch (err) {
16084
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16085
- throw err;
16086
- }
16087
- const client = new ApiClient(ctx);
16473
+ function validateViewOpts(opts) {
16474
+ const id = opts.id?.trim();
16475
+ const output = opts.output;
16476
+ if (!id) {
16477
+ throw new CliError({
16478
+ code: "INVALID_ARG",
16479
+ message: "--id is required"
16480
+ });
16481
+ }
16482
+ if (!output) {
16483
+ throw new CliError({
16484
+ code: "INVALID_ARG",
16485
+ message: "--output is required"
16486
+ });
16487
+ }
16488
+ return { id, output };
16489
+ }
16490
+ var attachmentViewCommand = defineCommand(
16491
+ {
16492
+ name: "view",
16493
+ description: "Download an attachment by id and save it to a local path",
16494
+ rationale: "denied",
16495
+ options: [
16496
+ { flags: "--id <attachmentId>", description: "Attachment UUID" },
16497
+ { flags: "--output <path>", description: "Local path to write the file to" }
16498
+ ]
16499
+ },
16500
+ async (ctx, opts) => {
16501
+ const { id, output } = validateViewOpts(opts);
16502
+ const agentContext = ctx.loadAgentContext();
16503
+ const client = ctx.createApiClient(agentContext);
16088
16504
  const res = await client.requestRaw(
16089
16505
  "GET",
16090
- `/api/attachments/${encodeURIComponent(opts.id)}`
16506
+ `/api/attachments/${encodeURIComponent(id)}`
16091
16507
  );
16092
16508
  if (!res.ok) {
16093
- const code = res.status >= 500 ? "SERVER_5XX" : "VIEW_FAILED";
16094
- fail(code, res.error ?? `HTTP ${res.status}`);
16509
+ throw new CliError({
16510
+ code: res.status >= 500 ? "SERVER_5XX" : "VIEW_FAILED",
16511
+ message: res.error ?? `HTTP ${res.status}`
16512
+ });
16095
16513
  }
16096
16514
  const buffer = Buffer.from(await res.response.arrayBuffer());
16097
- writeFileSync(opts.output, buffer);
16098
- process.stdout.write(`Downloaded to: ${opts.output}
16515
+ writeFileSync(output, buffer);
16516
+ writeText(ctx.io, `Downloaded to: ${output}
16099
16517
  `);
16100
- });
16518
+ }
16519
+ );
16520
+ function registerAttachmentViewCommand(parent, runtimeOptions) {
16521
+ registerCliCommand(parent, attachmentViewCommand, runtimeOptions);
16101
16522
  }
16102
16523
 
16103
16524
  // src/commands/task/_format.ts
@@ -16155,32 +16576,61 @@ function formatTaskStatusUpdated(taskNumber, status) {
16155
16576
 
16156
16577
  // src/commands/task/list.ts
16157
16578
  var VALID_STATUSES = /* @__PURE__ */ new Set(["all", "todo", "in_progress", "in_review", "done", "closed"]);
16158
- function registerTaskListCommand(parent) {
16159
- parent.command("list").description("List tasks in a channel").requiredOption("--channel <target>", "Channel target: '#channel'").option("--status <s>", "Filter: all|todo|in_progress|in_review|done (default: server-side)").action(async (opts) => {
16160
- let ctx;
16161
- try {
16162
- ctx = loadAgentContext();
16163
- } catch (err) {
16164
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16165
- throw err;
16166
- }
16167
- if (opts.status && !VALID_STATUSES.has(opts.status)) {
16168
- fail("INVALID_ARG", `--status must be one of ${Array.from(VALID_STATUSES).join("|")}; got ${opts.status}`);
16169
- }
16170
- const params = new URLSearchParams();
16171
- params.set("channel", opts.channel);
16172
- if (opts.status) params.set("status", opts.status);
16173
- const client = new ApiClient(ctx);
16579
+ function buildTaskListPath(agentId, opts) {
16580
+ const params = new URLSearchParams();
16581
+ params.set("channel", opts.channel);
16582
+ if (opts.status) params.set("status", opts.status);
16583
+ return `/internal/agent/${encodeURIComponent(agentId)}/tasks?${params.toString()}`;
16584
+ }
16585
+ function validateListOpts(opts) {
16586
+ const channel = opts.channel?.trim();
16587
+ if (!channel) {
16588
+ throw new CliError({
16589
+ code: "INVALID_ARG",
16590
+ message: "--channel is required"
16591
+ });
16592
+ }
16593
+ if (opts.status && !VALID_STATUSES.has(opts.status)) {
16594
+ throw new CliError({
16595
+ code: "INVALID_ARG",
16596
+ message: `--status must be one of ${Array.from(VALID_STATUSES).join("|")}; got ${opts.status}`
16597
+ });
16598
+ }
16599
+ return {
16600
+ channel,
16601
+ ...opts.status ? { status: opts.status } : {}
16602
+ };
16603
+ }
16604
+ var taskListCommand = defineCommand(
16605
+ {
16606
+ name: "list",
16607
+ description: "List tasks in a channel",
16608
+ rationale: "denied",
16609
+ options: [
16610
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
16611
+ { flags: "--status <s>", description: "Filter: all|todo|in_progress|in_review|done (default: server-side)" }
16612
+ ]
16613
+ },
16614
+ async (ctx, opts) => {
16615
+ const listOpts = validateListOpts(opts);
16616
+ const agentContext = ctx.loadAgentContext();
16617
+ const client = ctx.createApiClient(agentContext);
16174
16618
  const res = await client.request(
16175
16619
  "GET",
16176
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks?${params.toString()}`
16620
+ buildTaskListPath(agentContext.agentId, listOpts)
16177
16621
  );
16178
16622
  if (!res.ok) {
16179
- const code = res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED";
16180
- fail(code, res.error ?? `HTTP ${res.status}`);
16623
+ throw new CliError({
16624
+ code: res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED",
16625
+ message: res.error ?? `HTTP ${res.status}`
16626
+ });
16181
16627
  }
16182
- process.stdout.write(formatTaskList(opts.channel, res.data, opts.status) + "\n");
16183
- });
16628
+ writeText(ctx.io, `${formatTaskList(listOpts.channel, res.data, listOpts.status)}
16629
+ `);
16630
+ }
16631
+ );
16632
+ function registerTaskListCommand(parent, runtimeOptions) {
16633
+ registerCliCommand(parent, taskListCommand, runtimeOptions);
16184
16634
  }
16185
16635
 
16186
16636
  // src/commands/task/create.ts
@@ -16267,76 +16717,133 @@ function registerTaskClaimCommand(parent) {
16267
16717
  }
16268
16718
 
16269
16719
  // src/commands/task/unclaim.ts
16270
- function registerTaskUnclaimCommand(parent) {
16271
- parent.command("unclaim").description("Release a previously-claimed task").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption("--number <n>", "Task number to unclaim").action(async (opts) => {
16272
- let ctx;
16273
- try {
16274
- ctx = loadAgentContext();
16275
- } catch (err) {
16276
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16277
- throw err;
16278
- }
16279
- const n = Number(opts.number);
16280
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16281
- fail("INVALID_ARG", `--number must be a positive integer; got ${opts.number}`);
16282
- }
16283
- const client = new ApiClient(ctx);
16720
+ function parseTaskNumber(raw) {
16721
+ const n = Number(raw);
16722
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16723
+ throw new CliError({
16724
+ code: "INVALID_ARG",
16725
+ message: `--number must be a positive integer; got ${raw}`
16726
+ });
16727
+ }
16728
+ return n;
16729
+ }
16730
+ function validateUnclaimOpts(opts) {
16731
+ const channel = opts.channel?.trim();
16732
+ if (!channel) {
16733
+ throw new CliError({
16734
+ code: "INVALID_ARG",
16735
+ message: "--channel is required"
16736
+ });
16737
+ }
16738
+ return { channel, taskNumber: parseTaskNumber(opts.number) };
16739
+ }
16740
+ var taskUnclaimCommand = defineCommand(
16741
+ {
16742
+ name: "unclaim",
16743
+ description: "Release a previously-claimed task",
16744
+ rationale: "required",
16745
+ options: [
16746
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
16747
+ { flags: "--number <n>", description: "Task number to unclaim" }
16748
+ ]
16749
+ },
16750
+ async (ctx, opts) => {
16751
+ const { channel, taskNumber } = validateUnclaimOpts(opts);
16752
+ const agentContext = ctx.loadAgentContext();
16753
+ const client = ctx.createApiClient(agentContext);
16284
16754
  const res = await client.request(
16285
16755
  "POST",
16286
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks/unclaim`,
16287
- { channel: opts.channel, task_number: n }
16756
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/unclaim`,
16757
+ { channel, task_number: taskNumber }
16288
16758
  );
16289
16759
  if (!res.ok) {
16290
- const code = res.status >= 500 ? "SERVER_5XX" : "UNCLAIM_FAILED";
16291
- fail(code, res.error ?? `HTTP ${res.status}`);
16760
+ throw new CliError({
16761
+ code: res.status >= 500 ? "SERVER_5XX" : "UNCLAIM_FAILED",
16762
+ message: res.error ?? `HTTP ${res.status}`
16763
+ });
16292
16764
  }
16293
- process.stdout.write(formatTaskUnclaimed(n) + "\n");
16294
- });
16765
+ writeText(ctx.io, `${formatTaskUnclaimed(taskNumber)}
16766
+ `);
16767
+ }
16768
+ );
16769
+ function registerTaskUnclaimCommand(parent, runtimeOptions) {
16770
+ registerCliCommand(parent, taskUnclaimCommand, runtimeOptions);
16295
16771
  }
16296
16772
 
16297
16773
  // src/commands/task/update.ts
16298
16774
  var STATUSES = ["todo", "in_progress", "in_review", "done", "closed"];
16299
- function registerTaskUpdateCommand(parent) {
16300
- parent.command("update").description("Update task status").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption("--number <n>", "Task number to update").requiredOption(
16301
- "--status <status>",
16302
- `New status. One of: ${STATUSES.join(", ")}`
16303
- ).action(async (opts) => {
16304
- let ctx;
16305
- try {
16306
- ctx = loadAgentContext();
16307
- } catch (err) {
16308
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16309
- throw err;
16310
- }
16311
- const n = Number(opts.number);
16312
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16313
- fail("INVALID_ARG", `--number must be a positive integer; got ${opts.number}`);
16314
- }
16315
- if (!STATUSES.includes(opts.status)) {
16316
- fail(
16317
- "INVALID_ARG",
16318
- `--status must be one of: ${STATUSES.join(", ")}; got ${opts.status}`
16319
- );
16320
- }
16321
- const client = new ApiClient(ctx);
16775
+ function parseTaskNumber2(raw) {
16776
+ const n = Number(raw);
16777
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
16778
+ throw new CliError({
16779
+ code: "INVALID_ARG",
16780
+ message: `--number must be a positive integer; got ${raw}`
16781
+ });
16782
+ }
16783
+ return n;
16784
+ }
16785
+ function parseStatus(raw) {
16786
+ if (!raw || !STATUSES.includes(raw)) {
16787
+ throw new CliError({
16788
+ code: "INVALID_ARG",
16789
+ message: `--status must be one of: ${STATUSES.join(", ")}; got ${raw}`
16790
+ });
16791
+ }
16792
+ return raw;
16793
+ }
16794
+ function validateUpdateOpts(opts) {
16795
+ const channel = opts.channel?.trim();
16796
+ if (!channel) {
16797
+ throw new CliError({
16798
+ code: "INVALID_ARG",
16799
+ message: "--channel is required"
16800
+ });
16801
+ }
16802
+ return {
16803
+ channel,
16804
+ taskNumber: parseTaskNumber2(opts.number),
16805
+ status: parseStatus(opts.status)
16806
+ };
16807
+ }
16808
+ var taskUpdateCommand = defineCommand(
16809
+ {
16810
+ name: "update",
16811
+ description: "Update task status",
16812
+ rationale: "required",
16813
+ options: [
16814
+ { flags: "--channel <target>", description: "Channel target: '#channel'" },
16815
+ { flags: "--number <n>", description: "Task number to update" },
16816
+ { flags: "--status <status>", description: `New status. One of: ${STATUSES.join(", ")}` }
16817
+ ]
16818
+ },
16819
+ async (ctx, opts) => {
16820
+ const { channel, taskNumber, status } = validateUpdateOpts(opts);
16821
+ const agentContext = ctx.loadAgentContext();
16822
+ const client = ctx.createApiClient(agentContext);
16322
16823
  const res = await client.request(
16323
16824
  "POST",
16324
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/tasks/update-status`,
16325
- { channel: opts.channel, task_number: n, status: opts.status }
16825
+ `/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/update-status`,
16826
+ { channel, task_number: taskNumber, status }
16326
16827
  );
16327
16828
  if (!res.ok) {
16328
- const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
16329
- fail(code, res.error ?? `HTTP ${res.status}`);
16829
+ throw new CliError({
16830
+ code: res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED",
16831
+ message: res.error ?? `HTTP ${res.status}`
16832
+ });
16330
16833
  }
16331
16834
  if (isFreshnessHeldResponse(res.data)) {
16332
- process.stdout.write(formatFreshnessHoldOutput(opts.channel, res.data, {
16835
+ writeText(ctx.io, formatFreshnessHoldOutput(channel, res.data, {
16333
16836
  heldAction: "Your task status update was not applied.",
16334
16837
  draftInstructions: "After reviewing the newer context, rerun the task update command if it is still correct.\n"
16335
16838
  }));
16336
16839
  return;
16337
16840
  }
16338
- process.stdout.write(formatTaskStatusUpdated(n, opts.status) + "\n");
16339
- });
16841
+ writeText(ctx.io, `${formatTaskStatusUpdated(taskNumber, status)}
16842
+ `);
16843
+ }
16844
+ );
16845
+ function registerTaskUpdateCommand(parent, runtimeOptions) {
16846
+ registerCliCommand(parent, taskUpdateCommand, runtimeOptions);
16340
16847
  }
16341
16848
 
16342
16849
  // src/commands/profile/_format.ts
@@ -16403,39 +16910,58 @@ function normalizeTarget(target) {
16403
16910
  if (target === void 0) return null;
16404
16911
  const trimmed = target.trim();
16405
16912
  if (!trimmed) {
16406
- fail("INVALID_ARG", "profile target must not be empty");
16913
+ throw new CliError({
16914
+ code: "INVALID_ARG",
16915
+ message: "profile target must not be empty"
16916
+ });
16407
16917
  }
16408
16918
  if (!trimmed.startsWith("@")) {
16409
- fail("INVALID_ARG", "profile target must start with @");
16919
+ throw new CliError({
16920
+ code: "INVALID_ARG",
16921
+ message: "profile target must start with @"
16922
+ });
16410
16923
  }
16411
16924
  return trimmed;
16412
16925
  }
16413
- function registerProfileShowCommand(parent) {
16414
- parent.command("show").description("Show a profile. Omit the target to show your own profile.").argument("[target]", "Handle like @alice; omit to show your own profile").option("--json", "Emit machine-readable JSON").action(async (target, opts) => {
16415
- let ctx;
16416
- try {
16417
- ctx = loadAgentContext();
16418
- } catch (err) {
16419
- if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16420
- throw err;
16421
- }
16926
+ function buildProfileShowPath(agentId, target) {
16927
+ const base = `/internal/agent/${encodeURIComponent(agentId)}/profile`;
16928
+ if (!target) return base;
16929
+ const params = new URLSearchParams();
16930
+ params.set("target", target);
16931
+ return `${base}?${params.toString()}`;
16932
+ }
16933
+ var profileShowCommand = defineCommand(
16934
+ {
16935
+ name: "show",
16936
+ description: "Show a profile. Omit the target to show your own profile.",
16937
+ rationale: "denied",
16938
+ arguments: ["[target]"],
16939
+ options: [{ flags: "--json", description: "Emit machine-readable JSON" }]
16940
+ },
16941
+ async (ctx, target, opts = {}) => {
16942
+ const agentContext = ctx.loadAgentContext();
16943
+ const client = ctx.createApiClient(agentContext);
16422
16944
  const normalizedTarget = normalizeTarget(target);
16423
- const params = new URLSearchParams();
16424
- if (normalizedTarget) params.set("target", normalizedTarget);
16425
- const client = new ApiClient(ctx);
16426
- const pathname = params.size > 0 ? `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile?${params.toString()}` : `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile`;
16427
- const res = await client.request("GET", pathname);
16945
+ const res = await client.request(
16946
+ "GET",
16947
+ buildProfileShowPath(agentContext.agentId, normalizedTarget)
16948
+ );
16428
16949
  if (!res.ok || !res.data) {
16429
- const code = res.status >= 500 ? "SERVER_5XX" : "PROFILE_SHOW_FAILED";
16430
- fail(code, res.error ?? `HTTP ${res.status}`);
16950
+ throw new CliError({
16951
+ code: res.status >= 500 ? "SERVER_5XX" : "PROFILE_SHOW_FAILED",
16952
+ message: res.error ?? `HTTP ${res.status}`
16953
+ });
16431
16954
  }
16432
16955
  if (opts.json) {
16433
- emit({ ok: true, data: res.data });
16956
+ writeJson(ctx.io, { ok: true, data: res.data });
16434
16957
  return;
16435
16958
  }
16436
- process.stdout.write(`${formatProfile(res.data)}
16959
+ writeText(ctx.io, `${formatProfile(res.data)}
16437
16960
  `);
16438
- });
16961
+ }
16962
+ );
16963
+ function registerProfileShowCommand(parent, runtimeOptions) {
16964
+ registerCliCommand(parent, profileShowCommand, runtimeOptions);
16439
16965
  }
16440
16966
 
16441
16967
  // src/commands/profile/update.ts
@@ -17136,6 +17662,8 @@ var threadCmd = program.command("thread").description("Thread attention operatio
17136
17662
  registerThreadUnfollowCommand(threadCmd);
17137
17663
  var serverCmd = program.command("server").description("Server / workspace introspection");
17138
17664
  registerServerInfoCommand(serverCmd);
17665
+ var knowledgeCmd = program.command("knowledge").description("Agent knowledge retrieval (canonical Slock product knowledge topics)");
17666
+ registerKnowledgeGetCommand(knowledgeCmd);
17139
17667
  var messageCmd = program.command("message").description("Message operations");
17140
17668
  registerSendCommand(messageCmd);
17141
17669
  registerCheckCommand(messageCmd);