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