@slock-ai/daemon 0.55.0 → 0.55.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6Q3U5STT.js → chunk-QQRU3GA6.js} +186 -27
- package/dist/cli/index.js +1160 -480
- 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
|
|
@@ -100,6 +126,153 @@ function buildFetchDispatcher(targetUrl, env = process.env) {
|
|
|
100
126
|
return dispatcher;
|
|
101
127
|
}
|
|
102
128
|
|
|
129
|
+
// src/transportTrace.ts
|
|
130
|
+
import { randomBytes } from "crypto";
|
|
131
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
132
|
+
import path from "path";
|
|
133
|
+
var CLI_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
|
|
134
|
+
var TRACE_ID_HEX_LENGTH = 32;
|
|
135
|
+
var SPAN_ID_HEX_LENGTH = 16;
|
|
136
|
+
var traceSinkForTest = null;
|
|
137
|
+
var traceFilePath = null;
|
|
138
|
+
function emitCliTransportNormalizedError(attrs, env = process.env) {
|
|
139
|
+
try {
|
|
140
|
+
traceSinkForTest?.("cli.transport.normalized_error", attrs);
|
|
141
|
+
const traceDir = env[CLI_TRACE_DIR_ENV];
|
|
142
|
+
if (!traceDir) return;
|
|
143
|
+
mkdirSync(traceDir, { recursive: true, mode: 448 });
|
|
144
|
+
if (!traceFilePath) {
|
|
145
|
+
traceFilePath = path.join(
|
|
146
|
+
traceDir,
|
|
147
|
+
`daemon-trace-cli-transport-${safeTimestamp(Date.now())}-${process.pid}-${randomHex(4)}.jsonl`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
appendFileSync(traceFilePath, `${JSON.stringify(spanRecord("cli.transport.normalized_error", attrs))}
|
|
151
|
+
`, {
|
|
152
|
+
encoding: "utf8",
|
|
153
|
+
mode: 384
|
|
154
|
+
});
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function routeFamilyForPath(pathname) {
|
|
159
|
+
const normalized = pathname.split("?")[0] || "/";
|
|
160
|
+
if (normalized === "/internal/agent-api/send") return "agent-api/send";
|
|
161
|
+
if (normalized === "/internal/agent-api/events") return "agent-api/events";
|
|
162
|
+
if (normalized === "/internal/agent-api/receive-ack") return "agent-api/events";
|
|
163
|
+
if (normalized === "/internal/agent-api/tasks/claim") return "tasks/claim";
|
|
164
|
+
if (normalized === "/internal/agent-api/tasks/update-status") return "tasks/update";
|
|
165
|
+
if (normalized === "/internal/agent-api/tasks" || normalized.startsWith("/internal/agent-api/tasks/")) {
|
|
166
|
+
return "tasks";
|
|
167
|
+
}
|
|
168
|
+
if (normalized.startsWith("/internal/agent-api/attachments/")) return "agent-api/attachments";
|
|
169
|
+
if (normalized.startsWith("/api/attachments/")) return "attachments/download";
|
|
170
|
+
if (/^\/internal\/agent-api\/messages\/[^/]+\/reactions$/.test(normalized)) return "agent-api/messages/reactions";
|
|
171
|
+
if (normalized === "/internal/agent-api/server") return "server";
|
|
172
|
+
if (normalized.startsWith("/internal/agent-api/history")) return "agent-api/events";
|
|
173
|
+
if (normalized.startsWith("/internal/agent-api/search")) return "agent-api/events";
|
|
174
|
+
if (normalized.startsWith("/internal/agent-api/channel-members")) return "channel-members";
|
|
175
|
+
if (normalized.startsWith("/internal/agent-api/knowledge")) return "knowledge";
|
|
176
|
+
if (normalized === "/internal/agent-api/profile" || normalized.startsWith("/internal/agent-api/profile/")) return "profile";
|
|
177
|
+
if (normalized === "/internal/agent-api/integrations" || normalized.startsWith("/internal/agent-api/integrations/")) return "integrations";
|
|
178
|
+
if (normalized === "/internal/agent-api/upload") return "attachments/upload";
|
|
179
|
+
if (normalized === "/internal/agent-api/resolve-channel") return "resolve-channel";
|
|
180
|
+
if (normalized === "/internal/agent-api/threads/unfollow") return "threads/unfollow";
|
|
181
|
+
if (normalized === "/internal/agent-api/prepare-action") return "action/prepare";
|
|
182
|
+
if (normalized === "/internal/agent-api/reminders" || normalized.startsWith("/internal/agent-api/reminders/")) return "reminders";
|
|
183
|
+
if (/^\/internal\/agent-api\/channels\/[^/]+\/join$/.test(normalized)) return "channels/join";
|
|
184
|
+
if (/^\/internal\/agent-api\/channels\/[^/]+\/leave$/.test(normalized)) return "channels/leave";
|
|
185
|
+
if (normalized.startsWith("/internal/agent/")) {
|
|
186
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
187
|
+
const firstAfterAgentId = parts[3] ?? "unknown";
|
|
188
|
+
if (firstAfterAgentId === "server") return "server";
|
|
189
|
+
if (firstAfterAgentId === "send") return "agent-api/send";
|
|
190
|
+
if (firstAfterAgentId === "history" || firstAfterAgentId === "search" || firstAfterAgentId === "receive" || firstAfterAgentId === "receive-ack") {
|
|
191
|
+
return "agent-api/events";
|
|
192
|
+
}
|
|
193
|
+
if (firstAfterAgentId === "channel-members") return "channel-members";
|
|
194
|
+
if (firstAfterAgentId === "knowledge") return "knowledge";
|
|
195
|
+
if (firstAfterAgentId === "profile") return "profile";
|
|
196
|
+
if (firstAfterAgentId === "integrations") return "integrations";
|
|
197
|
+
if (firstAfterAgentId === "upload") return "attachments/upload";
|
|
198
|
+
if (firstAfterAgentId === "resolve-channel") return "resolve-channel";
|
|
199
|
+
if (firstAfterAgentId === "threads") return "threads/unfollow";
|
|
200
|
+
if (firstAfterAgentId === "prepare-action") return "action/prepare";
|
|
201
|
+
if (firstAfterAgentId === "tasks") {
|
|
202
|
+
if (normalized.endsWith("/tasks/claim")) return "tasks/claim";
|
|
203
|
+
if (normalized.endsWith("/tasks/update-status")) return "tasks/update";
|
|
204
|
+
return "tasks";
|
|
205
|
+
}
|
|
206
|
+
if (firstAfterAgentId === "reminders") return "reminders";
|
|
207
|
+
if (firstAfterAgentId === "messages" && normalized.endsWith("/reactions")) return "agent-api/messages/reactions";
|
|
208
|
+
if (firstAfterAgentId === "channels" && normalized.endsWith("/join")) return "channels/join";
|
|
209
|
+
if (firstAfterAgentId === "channels" && normalized.endsWith("/leave")) return "channels/leave";
|
|
210
|
+
}
|
|
211
|
+
return "unknown";
|
|
212
|
+
}
|
|
213
|
+
function targetHostClassForUrl(url2) {
|
|
214
|
+
const hostname3 = url2.hostname.toLowerCase();
|
|
215
|
+
if (hostname3 === "127.0.0.1" || hostname3 === "localhost" || hostname3 === "::1") return "local_daemon";
|
|
216
|
+
if (hostname3 === "api.slock.ai") return "api.slock.ai";
|
|
217
|
+
return "custom_server";
|
|
218
|
+
}
|
|
219
|
+
function upstreamLayerForFetchError(url2, err) {
|
|
220
|
+
if (targetHostClassForUrl(url2) === "local_daemon") return "local_daemon_loopback";
|
|
221
|
+
const code = errorCode(err).toUpperCase();
|
|
222
|
+
const message = errorMessage(err).toLowerCase();
|
|
223
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN" || message.includes("dns")) return "dns";
|
|
224
|
+
if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || message.includes("socket")) return "tcp";
|
|
225
|
+
if (code.includes("TLS") || message.includes("certificate") || message.includes("tls")) return "tls";
|
|
226
|
+
if (code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT" || message.includes("timeout")) return "read_timeout";
|
|
227
|
+
if (message.includes("proxy")) return "proxy_connect";
|
|
228
|
+
if (message.includes("fly")) return "fly_edge";
|
|
229
|
+
return "unknown";
|
|
230
|
+
}
|
|
231
|
+
function boundedOriginalMessage(err) {
|
|
232
|
+
const message = sanitizeOriginalMessage(errorMessage(err));
|
|
233
|
+
return message || void 0;
|
|
234
|
+
}
|
|
235
|
+
function spanRecord(name, attrs) {
|
|
236
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
237
|
+
return {
|
|
238
|
+
type: "span",
|
|
239
|
+
schema_version: 1,
|
|
240
|
+
trace_id: randomHex(TRACE_ID_HEX_LENGTH / 2),
|
|
241
|
+
span_id: randomHex(SPAN_ID_HEX_LENGTH / 2),
|
|
242
|
+
parent_span_id: null,
|
|
243
|
+
name,
|
|
244
|
+
surface: "cli",
|
|
245
|
+
kind: "client",
|
|
246
|
+
status: "error",
|
|
247
|
+
start_time: now,
|
|
248
|
+
end_time: now,
|
|
249
|
+
duration_ms: 0,
|
|
250
|
+
attrs: Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0 && value !== null && value !== "")),
|
|
251
|
+
events: []
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function randomHex(bytes) {
|
|
255
|
+
return randomBytes(bytes).toString("hex");
|
|
256
|
+
}
|
|
257
|
+
function safeTimestamp(timeMs) {
|
|
258
|
+
return new Date(timeMs).toISOString().replace(/[:.]/g, "-");
|
|
259
|
+
}
|
|
260
|
+
function errorCode(err) {
|
|
261
|
+
if (typeof err === "object" && err && "code" in err && typeof err.code === "string") {
|
|
262
|
+
return err.code;
|
|
263
|
+
}
|
|
264
|
+
if (typeof err === "object" && err && "cause" in err) return errorCode(err.cause);
|
|
265
|
+
return "";
|
|
266
|
+
}
|
|
267
|
+
function errorMessage(err) {
|
|
268
|
+
if (err instanceof Error) return err.message;
|
|
269
|
+
return String(err ?? "");
|
|
270
|
+
}
|
|
271
|
+
function sanitizeOriginalMessage(message) {
|
|
272
|
+
const normalized = message.replace(/sk_(?:agent|machine|computer)_[A-Za-z0-9_-]+/g, "sk_[redacted]").replace(/sap_[A-Za-z0-9_-]+/g, "sap_[redacted]").replace(/https?:\/\/\S+/g, "[url]").replace(/\s+/g, " ").trim();
|
|
273
|
+
return normalized.length > 240 ? `${normalized.slice(0, 237)}...` : normalized;
|
|
274
|
+
}
|
|
275
|
+
|
|
103
276
|
// src/client.ts
|
|
104
277
|
var ApiClient = class {
|
|
105
278
|
constructor(ctx) {
|
|
@@ -169,7 +342,7 @@ var ApiClient = class {
|
|
|
169
342
|
async parseJsonResponse(res) {
|
|
170
343
|
let data = null;
|
|
171
344
|
let error48 = null;
|
|
172
|
-
let
|
|
345
|
+
let errorCode2 = null;
|
|
173
346
|
const contentType = res.headers.get("content-type") ?? "";
|
|
174
347
|
if (contentType.includes("application/json")) {
|
|
175
348
|
let parseFailed = false;
|
|
@@ -191,34 +364,76 @@ var ApiClient = class {
|
|
|
191
364
|
} else {
|
|
192
365
|
const body = parsed;
|
|
193
366
|
if (res.status === 403 && body?.requiredScope) {
|
|
194
|
-
|
|
367
|
+
errorCode2 = "SCOPE_DENIED";
|
|
195
368
|
error48 = `Permission denied. The human has revoked the \`${body.requiredScope}\` capability for this agent under the agent profile's Permissions tab, so this command can't run. If you need it, ask the human to re-enable the corresponding toggle.`;
|
|
196
369
|
} else {
|
|
197
370
|
error48 = body?.error ?? `HTTP ${res.status}`;
|
|
198
|
-
|
|
371
|
+
errorCode2 = body?.errorCode ?? body?.code ?? null;
|
|
199
372
|
}
|
|
373
|
+
const suggestedNextAction = body?.suggestedNextAction ?? body?.suggested_next_action;
|
|
374
|
+
return { ok: res.ok, status: res.status, data, error: error48, errorCode: errorCode2, suggestedNextAction };
|
|
200
375
|
}
|
|
201
376
|
} else if (!res.ok) {
|
|
202
377
|
error48 = `HTTP ${res.status}`;
|
|
203
378
|
}
|
|
204
|
-
return { ok: res.ok, status: res.status, data, error: error48, errorCode };
|
|
379
|
+
return { ok: res.ok, status: res.status, data, error: error48, errorCode: errorCode2 };
|
|
380
|
+
}
|
|
381
|
+
async fetchWithTransportTrace(url2, pathname, init) {
|
|
382
|
+
try {
|
|
383
|
+
const res = await fetch(url2, init);
|
|
384
|
+
if (res.status >= 500) {
|
|
385
|
+
this.emitTransportNormalizedError({
|
|
386
|
+
url: url2,
|
|
387
|
+
pathname,
|
|
388
|
+
normalizedCode: "server_5xx",
|
|
389
|
+
responseStarted: true,
|
|
390
|
+
upstreamLayer: "http_status",
|
|
391
|
+
upstreamStatus: res.status
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return res;
|
|
395
|
+
} catch (err) {
|
|
396
|
+
this.emitTransportNormalizedError({
|
|
397
|
+
url: url2,
|
|
398
|
+
pathname,
|
|
399
|
+
normalizedCode: "transport_failure",
|
|
400
|
+
responseStarted: false,
|
|
401
|
+
upstreamLayer: upstreamLayerForFetchError(url2, err),
|
|
402
|
+
originalMessage: boundedOriginalMessage(err)
|
|
403
|
+
});
|
|
404
|
+
throw err;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
emitTransportNormalizedError(input) {
|
|
408
|
+
emitCliTransportNormalizedError({
|
|
409
|
+
producer: "cli",
|
|
410
|
+
normalized_code: input.normalizedCode,
|
|
411
|
+
route_family: routeFamilyForPath(input.pathname),
|
|
412
|
+
response_started: input.responseStarted,
|
|
413
|
+
upstream_layer: input.upstreamLayer,
|
|
414
|
+
...input.upstreamStatus === void 0 || input.upstreamStatus === null ? {} : { upstream_status: input.upstreamStatus },
|
|
415
|
+
...input.originalMessage ? { original_message: input.originalMessage } : {},
|
|
416
|
+
...this.ctx.serverId ? { serverId: this.ctx.serverId } : {},
|
|
417
|
+
agentId: this.ctx.agentId,
|
|
418
|
+
target_host_class: targetHostClassForUrl(input.url)
|
|
419
|
+
});
|
|
205
420
|
}
|
|
206
421
|
async request(method, pathname, body) {
|
|
207
422
|
pathname = this.rewriteAgentCredentialPath(pathname);
|
|
208
|
-
const url2 = new URL(pathname, this.ctx.serverUrl)
|
|
423
|
+
const url2 = new URL(pathname, this.ctx.serverUrl);
|
|
209
424
|
const headers = this.buildAuthHeaders();
|
|
210
425
|
headers["Content-Type"] = "application/json";
|
|
211
426
|
if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
|
|
212
427
|
headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
|
|
213
428
|
}
|
|
214
|
-
const dispatcher = buildFetchDispatcher(url2);
|
|
429
|
+
const dispatcher = buildFetchDispatcher(url2.toString());
|
|
215
430
|
const init = {
|
|
216
431
|
method,
|
|
217
432
|
headers,
|
|
218
433
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
219
434
|
};
|
|
220
435
|
if (dispatcher) init.dispatcher = dispatcher;
|
|
221
|
-
const res = await
|
|
436
|
+
const res = await this.fetchWithTransportTrace(url2, pathname, init);
|
|
222
437
|
const parsed = await this.parseJsonResponse(res);
|
|
223
438
|
if (parsed.ok && parsed.data !== null) {
|
|
224
439
|
parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
|
|
@@ -230,8 +445,8 @@ var ApiClient = class {
|
|
|
230
445
|
// boundary itself.
|
|
231
446
|
async requestMultipart(method, pathname, form) {
|
|
232
447
|
pathname = this.rewriteAgentCredentialPath(pathname);
|
|
233
|
-
const url2 = new URL(pathname, this.ctx.serverUrl)
|
|
234
|
-
const dispatcher = buildFetchDispatcher(url2);
|
|
448
|
+
const url2 = new URL(pathname, this.ctx.serverUrl);
|
|
449
|
+
const dispatcher = buildFetchDispatcher(url2.toString());
|
|
235
450
|
const headers = this.buildAuthHeaders();
|
|
236
451
|
if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
|
|
237
452
|
headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
|
|
@@ -242,7 +457,7 @@ var ApiClient = class {
|
|
|
242
457
|
body: form
|
|
243
458
|
};
|
|
244
459
|
if (dispatcher) init.dispatcher = dispatcher;
|
|
245
|
-
const res = await
|
|
460
|
+
const res = await this.fetchWithTransportTrace(url2, pathname, init);
|
|
246
461
|
return this.parseJsonResponse(res);
|
|
247
462
|
}
|
|
248
463
|
// Returns the raw Response so the caller can stream / save the body.
|
|
@@ -250,8 +465,8 @@ var ApiClient = class {
|
|
|
250
465
|
// consuming the body. On non-2xx, attempts to surface a JSON error.
|
|
251
466
|
async requestRaw(method, pathname) {
|
|
252
467
|
pathname = this.rewriteAgentCredentialPath(pathname);
|
|
253
|
-
const url2 = new URL(pathname, this.ctx.serverUrl)
|
|
254
|
-
const dispatcher = buildFetchDispatcher(url2);
|
|
468
|
+
const url2 = new URL(pathname, this.ctx.serverUrl);
|
|
469
|
+
const dispatcher = buildFetchDispatcher(url2.toString());
|
|
255
470
|
const headers = this.buildAuthHeaders();
|
|
256
471
|
if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
|
|
257
472
|
headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
|
|
@@ -262,7 +477,7 @@ var ApiClient = class {
|
|
|
262
477
|
redirect: "follow"
|
|
263
478
|
};
|
|
264
479
|
if (dispatcher) init.dispatcher = dispatcher;
|
|
265
|
-
const res = await
|
|
480
|
+
const res = await this.fetchWithTransportTrace(url2, pathname, init);
|
|
266
481
|
let error48 = null;
|
|
267
482
|
if (!res.ok) {
|
|
268
483
|
const contentType = res.headers.get("content-type") ?? "";
|
|
@@ -280,7 +495,7 @@ var ApiClient = class {
|
|
|
280
495
|
// src/auth/env.ts
|
|
281
496
|
import fs from "fs";
|
|
282
497
|
import os from "os";
|
|
283
|
-
import
|
|
498
|
+
import path2 from "path";
|
|
284
499
|
var RAW_AGENT_ENV_KEYS = [
|
|
285
500
|
"SLOCK_AGENT_ID",
|
|
286
501
|
"SLOCK_SERVER_URL",
|
|
@@ -303,13 +518,13 @@ function resolveProfileDir(slug, env = process.env) {
|
|
|
303
518
|
return env.SLOCK_PROFILE_DIR;
|
|
304
519
|
}
|
|
305
520
|
if (env.SLOCK_HOME) {
|
|
306
|
-
return
|
|
521
|
+
return path2.join(env.SLOCK_HOME, "profiles", slug);
|
|
307
522
|
}
|
|
308
523
|
const home = env.HOME ?? os.homedir();
|
|
309
|
-
return
|
|
524
|
+
return path2.join(home, ".slock", "profiles", slug);
|
|
310
525
|
}
|
|
311
526
|
function resolveProfileCredentialPath(slug, env) {
|
|
312
|
-
return
|
|
527
|
+
return path2.join(resolveProfileDir(slug, env), "credential.json");
|
|
313
528
|
}
|
|
314
529
|
function readProfileCredential(slug, env) {
|
|
315
530
|
const filePath = resolveProfileCredentialPath(slug, env);
|
|
@@ -450,42 +665,33 @@ function loadAgentContext(env = process.env) {
|
|
|
450
665
|
// src/core/io.ts
|
|
451
666
|
function defaultCliIo() {
|
|
452
667
|
return {
|
|
668
|
+
stdin: process.stdin,
|
|
453
669
|
stdout: process.stdout,
|
|
454
670
|
stderr: process.stderr
|
|
455
671
|
};
|
|
456
672
|
}
|
|
457
673
|
|
|
458
|
-
// src/core/
|
|
459
|
-
|
|
460
|
-
code
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
message: `Unexpected error: ${message}`,
|
|
478
|
-
cause
|
|
479
|
-
});
|
|
480
|
-
this.name = "InternalBugError";
|
|
674
|
+
// src/core/context.ts
|
|
675
|
+
function bootstrapSuggestedNextAction(code) {
|
|
676
|
+
switch (code) {
|
|
677
|
+
case "MISSING_AGENT_ID":
|
|
678
|
+
case "MISSING_SERVER_URL":
|
|
679
|
+
case "MISSING_TOKEN":
|
|
680
|
+
return "Use a Slock profile with `slock --profile <slug> ...`; create one with `slock agent login --server <server-url> --agent <agent-id> --profile-slug <slug>`.";
|
|
681
|
+
case "PROFILE_FILE_UNREADABLE":
|
|
682
|
+
case "PROFILE_FILE_INVALID":
|
|
683
|
+
return "Check the selected Slock profile, or recreate it with `slock agent login --server <server-url> --agent <agent-id> --profile-slug <slug>`.";
|
|
684
|
+
case "TOKEN_FILE_UNREADABLE":
|
|
685
|
+
case "TOKEN_FILE_EMPTY":
|
|
686
|
+
return "Check the daemon-injected token file, or restart the Slock daemon so it can inject a fresh credential.";
|
|
687
|
+
case "MISSING_AGENT_PROXY_URL":
|
|
688
|
+
case "MISSING_AGENT_PROXY_TOKEN":
|
|
689
|
+
case "MULTIPLE_AGENT_PROXY_TOKENS":
|
|
690
|
+
return "Restart the Slock daemon so it can inject a complete local proxy environment, or remove the partial proxy env vars before retrying.";
|
|
691
|
+
default:
|
|
692
|
+
return void 0;
|
|
481
693
|
}
|
|
482
|
-
};
|
|
483
|
-
function toCliError(err) {
|
|
484
|
-
if (err instanceof CliError) return err;
|
|
485
|
-
return new InternalBugError(err);
|
|
486
694
|
}
|
|
487
|
-
|
|
488
|
-
// src/core/context.ts
|
|
489
695
|
function createCommandContext(options = {}) {
|
|
490
696
|
const io = options.io ?? defaultCliIo();
|
|
491
697
|
const env = options.env ?? process.env;
|
|
@@ -502,7 +708,8 @@ function createCommandContext(options = {}) {
|
|
|
502
708
|
throw new CliError({
|
|
503
709
|
code: err.code,
|
|
504
710
|
message: err.message,
|
|
505
|
-
cause: err
|
|
711
|
+
cause: err,
|
|
712
|
+
suggestedNextAction: bootstrapSuggestedNextAction(err.code)
|
|
506
713
|
});
|
|
507
714
|
}
|
|
508
715
|
throw err;
|
|
@@ -514,16 +721,14 @@ function createCommandContext(options = {}) {
|
|
|
514
721
|
|
|
515
722
|
// src/core/renderer.ts
|
|
516
723
|
function renderError(io, err) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
};
|
|
724
|
+
io.stderr.write(`Error: ${err.message}
|
|
725
|
+
`);
|
|
726
|
+
io.stderr.write(`Code: ${err.code}
|
|
727
|
+
`);
|
|
522
728
|
if (err.suggestedNextAction) {
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
io.stderr.write(`${JSON.stringify(body)}
|
|
729
|
+
io.stderr.write(`Next action: ${err.suggestedNextAction}
|
|
526
730
|
`);
|
|
731
|
+
}
|
|
527
732
|
}
|
|
528
733
|
function writeText(io, text) {
|
|
529
734
|
io.stdout.write(text);
|
|
@@ -543,16 +748,23 @@ function registerCliCommand(parent, command, runtimeOptions = {}) {
|
|
|
543
748
|
child.argument(arg);
|
|
544
749
|
}
|
|
545
750
|
for (const option of command.spec.options ?? []) {
|
|
546
|
-
|
|
751
|
+
if (option.parse) {
|
|
752
|
+
child.option(option.flags, option.description, option.parse);
|
|
753
|
+
} else {
|
|
754
|
+
child.option(option.flags, option.description);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (command.spec.helpAfter) {
|
|
758
|
+
child.addHelpText("after", command.spec.helpAfter);
|
|
547
759
|
}
|
|
548
760
|
child.action(async (...args) => {
|
|
549
761
|
const ctx = createCommandContext(runtimeOptions);
|
|
550
762
|
try {
|
|
551
763
|
await command.handler(ctx, ...args);
|
|
552
764
|
} catch (err) {
|
|
553
|
-
const
|
|
554
|
-
renderError(ctx.io,
|
|
555
|
-
throw new CliExit(
|
|
765
|
+
const cliError2 = toCliError(err);
|
|
766
|
+
renderError(ctx.io, cliError2);
|
|
767
|
+
throw new CliExit(cliError2.exitCode);
|
|
556
768
|
}
|
|
557
769
|
});
|
|
558
770
|
}
|
|
@@ -561,8 +773,7 @@ function registerCliCommand(parent, command, runtimeOptions = {}) {
|
|
|
561
773
|
var whoamiCommand = defineCommand(
|
|
562
774
|
{
|
|
563
775
|
name: "whoami",
|
|
564
|
-
description: "Print the agent context resolved from env (token value redacted)"
|
|
565
|
-
rationale: "denied"
|
|
776
|
+
description: "Print the agent context resolved from env (token value redacted)"
|
|
566
777
|
},
|
|
567
778
|
(ctx) => {
|
|
568
779
|
const agentContext = ctx.loadAgentContext();
|
|
@@ -698,17 +909,26 @@ function describeListResult(reason, serverUrl) {
|
|
|
698
909
|
return "You have `manageAgents` on at least one server, but no agents exist on those servers yet. Ask the user to create an agent first (via web UI), then rerun `slock agent list`.";
|
|
699
910
|
}
|
|
700
911
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
912
|
+
var agentListCommand = defineCommand(
|
|
913
|
+
{
|
|
914
|
+
name: "list",
|
|
915
|
+
description: "List Slock agents the user can mint credentials for (after a device-code login).",
|
|
916
|
+
options: [
|
|
917
|
+
{ flags: "--server <url>", description: "Slock server base URL, e.g. https://slock.example.com" },
|
|
918
|
+
{ flags: "--client-name <label>", description: "Human-readable label shown on the web approval page" }
|
|
919
|
+
]
|
|
920
|
+
},
|
|
921
|
+
async (ctx, options) => {
|
|
922
|
+
if (!options.server?.trim()) {
|
|
923
|
+
throw cliError("INVALID_ARG", "--server is required");
|
|
924
|
+
}
|
|
705
925
|
let userSession;
|
|
706
926
|
try {
|
|
707
927
|
userSession = await runDeviceCodeLogin({
|
|
708
928
|
serverUrl: options.server,
|
|
709
929
|
...options.clientName ? { clientName: options.clientName } : {},
|
|
710
930
|
onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
|
|
711
|
-
|
|
931
|
+
ctx.io.stderr.write(
|
|
712
932
|
`Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
|
|
713
933
|
`
|
|
714
934
|
);
|
|
@@ -716,7 +936,7 @@ function registerAgentListCommand(parent) {
|
|
|
716
936
|
});
|
|
717
937
|
} catch (err) {
|
|
718
938
|
if (err instanceof DeviceCodeLoginError) {
|
|
719
|
-
|
|
939
|
+
throw cliError(err.code, err.message, { cause: err });
|
|
720
940
|
}
|
|
721
941
|
throw err;
|
|
722
942
|
}
|
|
@@ -736,7 +956,7 @@ function registerAgentListCommand(parent) {
|
|
|
736
956
|
body = await res.json();
|
|
737
957
|
} catch {
|
|
738
958
|
}
|
|
739
|
-
|
|
959
|
+
throw cliError(
|
|
740
960
|
body?.code ?? `list_failed_${res.status}`,
|
|
741
961
|
body?.error ?? `Failed to list manageable agents (status ${res.status}).`
|
|
742
962
|
);
|
|
@@ -745,7 +965,7 @@ function registerAgentListCommand(parent) {
|
|
|
745
965
|
const agents = payload.data?.agents ?? [];
|
|
746
966
|
const reason = payload.data?.reason ?? (agents.length > 0 ? "ok" : "no_agents_on_manageable_servers");
|
|
747
967
|
const suggestedNextAction = describeListResult(reason, options.server);
|
|
748
|
-
|
|
968
|
+
writeJson(ctx.io, {
|
|
749
969
|
ok: true,
|
|
750
970
|
data: {
|
|
751
971
|
agents,
|
|
@@ -754,28 +974,46 @@ function registerAgentListCommand(parent) {
|
|
|
754
974
|
suggested_next_action: suggestedNextAction
|
|
755
975
|
}
|
|
756
976
|
});
|
|
757
|
-
}
|
|
977
|
+
}
|
|
978
|
+
);
|
|
979
|
+
function registerAgentListCommand(parent, runtimeOptions = {}) {
|
|
980
|
+
registerCliCommand(parent, agentListCommand, runtimeOptions);
|
|
758
981
|
}
|
|
759
982
|
|
|
760
983
|
// src/commands/agent/login.ts
|
|
761
984
|
import { mkdir, stat, writeFile } from "fs/promises";
|
|
762
|
-
import
|
|
985
|
+
import path3 from "path";
|
|
763
986
|
import { fetch as undiciFetch2 } from "undici";
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
"
|
|
767
|
-
|
|
987
|
+
var agentLoginCommand = defineCommand(
|
|
988
|
+
{
|
|
989
|
+
name: "login",
|
|
990
|
+
description: "Sign this CLI in as a specific Slock agent via the device-code login grant.",
|
|
991
|
+
options: [
|
|
992
|
+
{ flags: "--server <url>", description: "Slock server base URL, e.g. https://slock.example.com" },
|
|
993
|
+
{ flags: "--agent <agentId>", description: "Agent id to log in as" },
|
|
994
|
+
{ flags: "--client-name <label>", description: "Human-readable label shown on the web approval page" },
|
|
995
|
+
{ flags: "--profile-slug <slug>", description: "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use." },
|
|
996
|
+
{ flags: "--profile-dir <path>", description: "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)" }
|
|
997
|
+
]
|
|
998
|
+
},
|
|
999
|
+
async (ctx, options) => {
|
|
1000
|
+
if (!options.server?.trim()) {
|
|
1001
|
+
throw cliError("INVALID_ARG", "--server is required");
|
|
1002
|
+
}
|
|
1003
|
+
if (!options.agent?.trim()) {
|
|
1004
|
+
throw cliError("INVALID_AGENT_ID", "--agent must not be empty.");
|
|
1005
|
+
}
|
|
768
1006
|
const invalidShape = describeInvalidAgentIdShape(options.agent);
|
|
769
1007
|
if (invalidShape) {
|
|
770
|
-
|
|
1008
|
+
throw cliError("INVALID_AGENT_ID", invalidShape, {
|
|
771
1009
|
suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
|
|
772
1010
|
});
|
|
773
1011
|
}
|
|
774
1012
|
const profileSlug = options.profileSlug ?? options.agent;
|
|
775
1013
|
const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
|
|
776
|
-
const credentialPath =
|
|
1014
|
+
const credentialPath = path3.join(profileDir, "credential.json");
|
|
777
1015
|
if (await profileFileExists(credentialPath)) {
|
|
778
|
-
|
|
1016
|
+
throw cliError(
|
|
779
1017
|
"PROFILE_ALREADY_EXISTS",
|
|
780
1018
|
`Profile '${profileSlug}' already has a credential at ${credentialPath}.`,
|
|
781
1019
|
{
|
|
@@ -789,7 +1027,7 @@ function registerAgentLoginCommand(parent) {
|
|
|
789
1027
|
serverUrl: options.server,
|
|
790
1028
|
...options.clientName ? { clientName: options.clientName } : {},
|
|
791
1029
|
onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
|
|
792
|
-
|
|
1030
|
+
ctx.io.stderr.write(
|
|
793
1031
|
`Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
|
|
794
1032
|
`
|
|
795
1033
|
);
|
|
@@ -797,7 +1035,7 @@ function registerAgentLoginCommand(parent) {
|
|
|
797
1035
|
});
|
|
798
1036
|
} catch (err) {
|
|
799
1037
|
if (err instanceof DeviceCodeLoginError) {
|
|
800
|
-
|
|
1038
|
+
throw cliError(err.code, err.message, { cause: err });
|
|
801
1039
|
}
|
|
802
1040
|
throw err;
|
|
803
1041
|
}
|
|
@@ -816,7 +1054,7 @@ function registerAgentLoginCommand(parent) {
|
|
|
816
1054
|
const body = await safeJson2(mintRes);
|
|
817
1055
|
const code = body?.code ?? `mint_failed_${mintRes.status}`;
|
|
818
1056
|
const detail = describeMintError(code, options.server);
|
|
819
|
-
|
|
1057
|
+
throw cliError(
|
|
820
1058
|
code,
|
|
821
1059
|
detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
|
|
822
1060
|
detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
|
|
@@ -824,7 +1062,7 @@ function registerAgentLoginCommand(parent) {
|
|
|
824
1062
|
}
|
|
825
1063
|
const minted = await mintRes.json();
|
|
826
1064
|
if (!minted.apiKey || !minted.agentId || !minted.serverId) {
|
|
827
|
-
|
|
1065
|
+
throw cliError(
|
|
828
1066
|
"mint_response_invalid",
|
|
829
1067
|
"Server mint response was missing apiKey / agentId / serverId."
|
|
830
1068
|
);
|
|
@@ -849,7 +1087,7 @@ function registerAgentLoginCommand(parent) {
|
|
|
849
1087
|
) + "\n",
|
|
850
1088
|
{ mode: 384 }
|
|
851
1089
|
);
|
|
852
|
-
|
|
1090
|
+
writeJson(ctx.io, {
|
|
853
1091
|
ok: true,
|
|
854
1092
|
data: {
|
|
855
1093
|
agentId: minted.agentId,
|
|
@@ -861,7 +1099,10 @@ function registerAgentLoginCommand(parent) {
|
|
|
861
1099
|
credentialPath
|
|
862
1100
|
}
|
|
863
1101
|
});
|
|
864
|
-
}
|
|
1102
|
+
}
|
|
1103
|
+
);
|
|
1104
|
+
function registerAgentLoginCommand(parent, runtimeOptions = {}) {
|
|
1105
|
+
registerCliCommand(parent, agentLoginCommand, runtimeOptions);
|
|
865
1106
|
}
|
|
866
1107
|
async function safeJson2(res) {
|
|
867
1108
|
try {
|
|
@@ -944,30 +1185,30 @@ var CHANNEL_MESSAGE_RE = new RegExp(
|
|
|
944
1185
|
|
|
945
1186
|
// ../shared/src/tracing/index.ts
|
|
946
1187
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
947
|
-
var
|
|
948
|
-
var
|
|
1188
|
+
var TRACE_ID_HEX_LENGTH2 = 32;
|
|
1189
|
+
var SPAN_ID_HEX_LENGTH2 = 16;
|
|
949
1190
|
var TRACE_FLAGS_HEX_LENGTH = 2;
|
|
950
1191
|
var TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
|
|
951
1192
|
var SPAN_ID_PATTERN = /^[0-9a-f]{16}$/;
|
|
952
1193
|
var TRACE_FLAGS_PATTERN = /^[0-9a-f]{2}$/;
|
|
953
1194
|
function isTraceId(value) {
|
|
954
|
-
return TRACE_ID_PATTERN.test(value) && value !== "0".repeat(
|
|
1195
|
+
return TRACE_ID_PATTERN.test(value) && value !== "0".repeat(TRACE_ID_HEX_LENGTH2);
|
|
955
1196
|
}
|
|
956
1197
|
function isSpanId(value) {
|
|
957
|
-
return SPAN_ID_PATTERN.test(value) && value !== "0".repeat(
|
|
1198
|
+
return SPAN_ID_PATTERN.test(value) && value !== "0".repeat(SPAN_ID_HEX_LENGTH2);
|
|
958
1199
|
}
|
|
959
1200
|
function isTraceFlags(value) {
|
|
960
1201
|
return TRACE_FLAGS_PATTERN.test(value);
|
|
961
1202
|
}
|
|
962
1203
|
function assertTraceContext(context) {
|
|
963
1204
|
if (!isTraceId(context.traceId)) {
|
|
964
|
-
throw new Error(`Invalid traceId: expected ${
|
|
1205
|
+
throw new Error(`Invalid traceId: expected ${TRACE_ID_HEX_LENGTH2} lowercase hex chars`);
|
|
965
1206
|
}
|
|
966
1207
|
if (!isSpanId(context.spanId)) {
|
|
967
|
-
throw new Error(`Invalid spanId: expected ${
|
|
1208
|
+
throw new Error(`Invalid spanId: expected ${SPAN_ID_HEX_LENGTH2} lowercase hex chars`);
|
|
968
1209
|
}
|
|
969
1210
|
if (context.parentSpanId !== null && !isSpanId(context.parentSpanId)) {
|
|
970
|
-
throw new Error(`Invalid parentSpanId: expected null or ${
|
|
1211
|
+
throw new Error(`Invalid parentSpanId: expected null or ${SPAN_ID_HEX_LENGTH2} lowercase hex chars`);
|
|
971
1212
|
}
|
|
972
1213
|
if (!isTraceFlags(context.traceFlags)) {
|
|
973
1214
|
throw new Error(`Invalid traceFlags: expected ${TRACE_FLAGS_HEX_LENGTH} lowercase hex chars`);
|
|
@@ -1007,19 +1248,19 @@ var NoopActiveSpan = class {
|
|
|
1007
1248
|
};
|
|
1008
1249
|
var noopTracer = new NoopTracer();
|
|
1009
1250
|
function generateTraceId() {
|
|
1010
|
-
return randomNonZeroHex(
|
|
1251
|
+
return randomNonZeroHex(TRACE_ID_HEX_LENGTH2);
|
|
1011
1252
|
}
|
|
1012
1253
|
function generateSpanId() {
|
|
1013
|
-
return randomNonZeroHex(
|
|
1254
|
+
return randomNonZeroHex(SPAN_ID_HEX_LENGTH2);
|
|
1014
1255
|
}
|
|
1015
1256
|
function randomNonZeroHex(length) {
|
|
1016
|
-
let value =
|
|
1257
|
+
let value = randomHex2(length);
|
|
1017
1258
|
while (value === "0".repeat(length)) {
|
|
1018
|
-
value =
|
|
1259
|
+
value = randomHex2(length);
|
|
1019
1260
|
}
|
|
1020
1261
|
return value;
|
|
1021
1262
|
}
|
|
1022
|
-
function
|
|
1263
|
+
function randomHex2(length) {
|
|
1023
1264
|
const bytes = new Uint8Array(length / 2);
|
|
1024
1265
|
globalThis.crypto.getRandomValues(bytes);
|
|
1025
1266
|
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -1795,10 +2036,10 @@ function mergeDefs(...defs) {
|
|
|
1795
2036
|
function cloneDef(schema) {
|
|
1796
2037
|
return mergeDefs(schema._zod.def);
|
|
1797
2038
|
}
|
|
1798
|
-
function getElementAtPath(obj,
|
|
1799
|
-
if (!
|
|
2039
|
+
function getElementAtPath(obj, path6) {
|
|
2040
|
+
if (!path6)
|
|
1800
2041
|
return obj;
|
|
1801
|
-
return
|
|
2042
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
1802
2043
|
}
|
|
1803
2044
|
function promiseAllObject(promisesObj) {
|
|
1804
2045
|
const keys = Object.keys(promisesObj);
|
|
@@ -2181,11 +2422,11 @@ function aborted(x, startIndex = 0) {
|
|
|
2181
2422
|
}
|
|
2182
2423
|
return false;
|
|
2183
2424
|
}
|
|
2184
|
-
function prefixIssues(
|
|
2425
|
+
function prefixIssues(path6, issues) {
|
|
2185
2426
|
return issues.map((iss) => {
|
|
2186
2427
|
var _a2;
|
|
2187
2428
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
2188
|
-
iss.path.unshift(
|
|
2429
|
+
iss.path.unshift(path6);
|
|
2189
2430
|
return iss;
|
|
2190
2431
|
});
|
|
2191
2432
|
}
|
|
@@ -2368,7 +2609,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
2368
2609
|
}
|
|
2369
2610
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
2370
2611
|
const result = { errors: [] };
|
|
2371
|
-
const processError = (error49,
|
|
2612
|
+
const processError = (error49, path6 = []) => {
|
|
2372
2613
|
var _a2, _b;
|
|
2373
2614
|
for (const issue2 of error49.issues) {
|
|
2374
2615
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -2378,7 +2619,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
2378
2619
|
} else if (issue2.code === "invalid_element") {
|
|
2379
2620
|
processError({ issues: issue2.issues }, issue2.path);
|
|
2380
2621
|
} else {
|
|
2381
|
-
const fullpath = [...
|
|
2622
|
+
const fullpath = [...path6, ...issue2.path];
|
|
2382
2623
|
if (fullpath.length === 0) {
|
|
2383
2624
|
result.errors.push(mapper(issue2));
|
|
2384
2625
|
continue;
|
|
@@ -2410,8 +2651,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
2410
2651
|
}
|
|
2411
2652
|
function toDotPath(_path) {
|
|
2412
2653
|
const segs = [];
|
|
2413
|
-
const
|
|
2414
|
-
for (const seg of
|
|
2654
|
+
const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2655
|
+
for (const seg of path6) {
|
|
2415
2656
|
if (typeof seg === "number")
|
|
2416
2657
|
segs.push(`[${seg}]`);
|
|
2417
2658
|
else if (typeof seg === "symbol")
|
|
@@ -14388,13 +14629,13 @@ function resolveRef(ref, ctx) {
|
|
|
14388
14629
|
if (!ref.startsWith("#")) {
|
|
14389
14630
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
14390
14631
|
}
|
|
14391
|
-
const
|
|
14392
|
-
if (
|
|
14632
|
+
const path6 = ref.slice(1).split("/").filter(Boolean);
|
|
14633
|
+
if (path6.length === 0) {
|
|
14393
14634
|
return ctx.rootSchema;
|
|
14394
14635
|
}
|
|
14395
14636
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14396
|
-
if (
|
|
14397
|
-
const key =
|
|
14637
|
+
if (path6[0] === defsKey) {
|
|
14638
|
+
const key = path6[1];
|
|
14398
14639
|
if (!key || !ctx.defs[key]) {
|
|
14399
14640
|
throw new Error(`Reference not found: ${ref}`);
|
|
14400
14641
|
}
|
|
@@ -15044,52 +15285,60 @@ async function resolveActionInput(input = process.stdin) {
|
|
|
15044
15285
|
);
|
|
15045
15286
|
}
|
|
15046
15287
|
}
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
"
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15288
|
+
var actionPrepareCommand = defineCommand(
|
|
15289
|
+
{
|
|
15290
|
+
name: "prepare",
|
|
15291
|
+
description: "Prepare an action card for a human to commit (B-mode quick-commit shortcut)",
|
|
15292
|
+
options: [
|
|
15293
|
+
{
|
|
15294
|
+
flags: "--target <target>",
|
|
15295
|
+
description: "Channel/DM/thread target to post the card. Same format as slock message send: '#channel', 'dm:@peer', '#channel:shortid'"
|
|
15296
|
+
}
|
|
15297
|
+
]
|
|
15298
|
+
},
|
|
15299
|
+
async (ctx, opts) => {
|
|
15300
|
+
if (!opts.target?.trim()) {
|
|
15301
|
+
throw cliError("INVALID_ARG", "--target is required");
|
|
15058
15302
|
}
|
|
15303
|
+
const agentContext = ctx.loadAgentContext();
|
|
15059
15304
|
let raw;
|
|
15060
15305
|
try {
|
|
15061
|
-
raw = await resolveActionInput();
|
|
15306
|
+
raw = await resolveActionInput(ctx.io.stdin ?? process.stdin);
|
|
15062
15307
|
} catch (err) {
|
|
15063
|
-
if (err instanceof PrepareActionInputError)
|
|
15308
|
+
if (err instanceof PrepareActionInputError) throw cliError(err.code, err.message, { cause: err });
|
|
15064
15309
|
throw err;
|
|
15065
15310
|
}
|
|
15066
15311
|
const parsed = actionCardActionSchema.safeParse(raw);
|
|
15067
15312
|
if (!parsed.success) {
|
|
15068
15313
|
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
15069
|
-
|
|
15314
|
+
throw cliError("INVALID_ACTION", `Action failed validation: ${issues}`);
|
|
15070
15315
|
}
|
|
15071
15316
|
const crossFieldError = validateActionCardAction(parsed.data);
|
|
15072
15317
|
if (crossFieldError) {
|
|
15073
|
-
|
|
15318
|
+
throw cliError("INVALID_ACTION", `Action failed validation: ${crossFieldError}`);
|
|
15074
15319
|
}
|
|
15075
|
-
const client =
|
|
15320
|
+
const client = ctx.createApiClient(agentContext);
|
|
15076
15321
|
const res = await client.request(
|
|
15077
15322
|
"POST",
|
|
15078
|
-
`/internal/agent/${encodeURIComponent(
|
|
15323
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/prepare-action`,
|
|
15079
15324
|
{ target: opts.target, action: parsed.data }
|
|
15080
15325
|
);
|
|
15081
15326
|
if (!res.ok) {
|
|
15082
15327
|
const code = res.status >= 500 ? "SERVER_5XX" : "PREPARE_FAILED";
|
|
15083
|
-
|
|
15328
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
15084
15329
|
}
|
|
15085
15330
|
const data = res.data;
|
|
15086
15331
|
const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
|
|
15087
|
-
|
|
15332
|
+
writeText(
|
|
15333
|
+
ctx.io,
|
|
15088
15334
|
shortId ? `Action card posted to ${opts.target} as message ${data.messageId} (short ${shortId}). The human can click the action verb to commit.
|
|
15089
15335
|
` : `Action card posted to ${opts.target}.
|
|
15090
15336
|
`
|
|
15091
15337
|
);
|
|
15092
|
-
}
|
|
15338
|
+
}
|
|
15339
|
+
);
|
|
15340
|
+
function registerActionPrepareCommand(parent, runtimeOptions = {}) {
|
|
15341
|
+
registerCliCommand(parent, actionPrepareCommand, runtimeOptions);
|
|
15093
15342
|
}
|
|
15094
15343
|
|
|
15095
15344
|
// src/commands/server/_format.ts
|
|
@@ -15196,12 +15445,9 @@ var channelMembersCommand = defineCommand(
|
|
|
15196
15445
|
{
|
|
15197
15446
|
name: "members",
|
|
15198
15447
|
description: "List agents and humans who are members of a channel, DM, or thread",
|
|
15199
|
-
rationale: "denied",
|
|
15200
15448
|
arguments: ["<target>"]
|
|
15201
15449
|
},
|
|
15202
15450
|
async (ctx, target) => {
|
|
15203
|
-
const agentContext = ctx.loadAgentContext();
|
|
15204
|
-
const client = ctx.createApiClient(agentContext);
|
|
15205
15451
|
const channel = String(target || "").trim();
|
|
15206
15452
|
if (!channel) {
|
|
15207
15453
|
throw new CliError({
|
|
@@ -15209,6 +15455,8 @@ var channelMembersCommand = defineCommand(
|
|
|
15209
15455
|
message: "target is required"
|
|
15210
15456
|
});
|
|
15211
15457
|
}
|
|
15458
|
+
const agentContext = ctx.loadAgentContext();
|
|
15459
|
+
const client = ctx.createApiClient(agentContext);
|
|
15212
15460
|
const encoded = encodeURIComponent(channel);
|
|
15213
15461
|
const res = await client.request(
|
|
15214
15462
|
"GET",
|
|
@@ -15244,7 +15492,6 @@ var channelLeaveCommand = defineCommand(
|
|
|
15244
15492
|
{
|
|
15245
15493
|
name: "leave",
|
|
15246
15494
|
description: "Leave a regular channel you have joined",
|
|
15247
|
-
rationale: "required",
|
|
15248
15495
|
options: [
|
|
15249
15496
|
{
|
|
15250
15497
|
flags: "--target <target>",
|
|
@@ -15312,7 +15559,6 @@ var channelJoinCommand = defineCommand(
|
|
|
15312
15559
|
{
|
|
15313
15560
|
name: "join",
|
|
15314
15561
|
description: "Join a visible public channel",
|
|
15315
|
-
rationale: "required",
|
|
15316
15562
|
options: [
|
|
15317
15563
|
{
|
|
15318
15564
|
flags: "--target <target>",
|
|
@@ -15373,8 +15619,7 @@ function registerChannelJoinCommand(parent, runtimeOptions) {
|
|
|
15373
15619
|
var serverInfoCommand = defineCommand(
|
|
15374
15620
|
{
|
|
15375
15621
|
name: "info",
|
|
15376
|
-
description: "List channels, agents, and humans on the current server"
|
|
15377
|
-
rationale: "denied"
|
|
15622
|
+
description: "List channels, agents, and humans on the current server"
|
|
15378
15623
|
},
|
|
15379
15624
|
async (ctx) => {
|
|
15380
15625
|
const agentContext = ctx.loadAgentContext();
|
|
@@ -15417,8 +15662,8 @@ function formatKnowledgeStdout(content) {
|
|
|
15417
15662
|
return content.endsWith("\n") ? content : `${content}
|
|
15418
15663
|
`;
|
|
15419
15664
|
}
|
|
15420
|
-
function toKnowledgeErrorCode(
|
|
15421
|
-
switch (
|
|
15665
|
+
function toKnowledgeErrorCode(errorCode2, status) {
|
|
15666
|
+
switch (errorCode2) {
|
|
15422
15667
|
case "INVALID_JSON_RESPONSE":
|
|
15423
15668
|
case "SCOPE_DENIED":
|
|
15424
15669
|
case "knowledge_agent_missing":
|
|
@@ -15430,7 +15675,7 @@ function toKnowledgeErrorCode(errorCode, status) {
|
|
|
15430
15675
|
case "knowledge_trace_id_invalid":
|
|
15431
15676
|
case "knowledge_turn_id_invalid":
|
|
15432
15677
|
case "unsupported_capability":
|
|
15433
|
-
return
|
|
15678
|
+
return errorCode2;
|
|
15434
15679
|
default:
|
|
15435
15680
|
return status >= 500 ? "SERVER_5XX" : "KNOWLEDGE_GET_FAILED";
|
|
15436
15681
|
}
|
|
@@ -15439,7 +15684,6 @@ var knowledgeGetCommand = defineCommand(
|
|
|
15439
15684
|
{
|
|
15440
15685
|
name: "get",
|
|
15441
15686
|
description: "Fetch an agent knowledge topic from the current server",
|
|
15442
|
-
rationale: "optional",
|
|
15443
15687
|
arguments: ["<topic>"],
|
|
15444
15688
|
options: [
|
|
15445
15689
|
{
|
|
@@ -15466,7 +15710,8 @@ var knowledgeGetCommand = defineCommand(
|
|
|
15466
15710
|
if (!res.ok) {
|
|
15467
15711
|
throw new CliError({
|
|
15468
15712
|
code: toKnowledgeErrorCode(res.errorCode, res.status),
|
|
15469
|
-
message: res.error ?? `HTTP ${res.status}
|
|
15713
|
+
message: res.error ?? `HTTP ${res.status}`,
|
|
15714
|
+
suggestedNextAction: res.suggestedNextAction ?? (res.errorCode === "knowledge_not_found" ? "Run `slock knowledge get index` to see all available knowledge topics." : void 0)
|
|
15470
15715
|
});
|
|
15471
15716
|
}
|
|
15472
15717
|
const data = res.data;
|
|
@@ -15512,7 +15757,6 @@ var threadUnfollowCommand = defineCommand(
|
|
|
15512
15757
|
{
|
|
15513
15758
|
name: "unfollow",
|
|
15514
15759
|
description: "Stop following a thread you no longer need ordinary delivery for",
|
|
15515
|
-
rationale: "required",
|
|
15516
15760
|
options: [
|
|
15517
15761
|
{
|
|
15518
15762
|
flags: "--target <target>",
|
|
@@ -15718,10 +15962,10 @@ ${opts.heldAction} Review the bounded context shown here, then choose one path.$
|
|
|
15718
15962
|
// src/commands/message/_continueDraftState.ts
|
|
15719
15963
|
import fs2 from "fs";
|
|
15720
15964
|
import os2 from "os";
|
|
15721
|
-
import
|
|
15965
|
+
import path4 from "path";
|
|
15722
15966
|
var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
|
|
15723
15967
|
function stateFilePath(agentId) {
|
|
15724
|
-
return
|
|
15968
|
+
return path4.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
|
|
15725
15969
|
}
|
|
15726
15970
|
function readState(agentId) {
|
|
15727
15971
|
const filePath = stateFilePath(agentId);
|
|
@@ -15735,7 +15979,7 @@ function readState(agentId) {
|
|
|
15735
15979
|
}
|
|
15736
15980
|
function writeState(agentId, state) {
|
|
15737
15981
|
const filePath = stateFilePath(agentId);
|
|
15738
|
-
fs2.mkdirSync(
|
|
15982
|
+
fs2.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
15739
15983
|
fs2.writeFileSync(filePath, JSON.stringify(state), "utf8");
|
|
15740
15984
|
}
|
|
15741
15985
|
function getSavedDraft(agentId, target) {
|
|
@@ -15887,48 +16131,68 @@ To send the current draft unchanged:
|
|
|
15887
16131
|
`
|
|
15888
16132
|
});
|
|
15889
16133
|
}
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
"
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
16134
|
+
var messageSendCommand = defineCommand(
|
|
16135
|
+
{
|
|
16136
|
+
name: "send",
|
|
16137
|
+
description: "Send a message to a channel, DM, or thread",
|
|
16138
|
+
arguments: ["[content...]"],
|
|
16139
|
+
options: [
|
|
16140
|
+
{ flags: "--target <target>", description: "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'" },
|
|
16141
|
+
{ flags: "--send-draft", description: "Send the current saved draft after reviewing newer messages" },
|
|
16142
|
+
{ flags: "--anyway", description: "Escape hatch: send a saved draft even if freshness re-check is still stale" },
|
|
16143
|
+
{ flags: "--content <content>", description: "Unsupported. Pipe message content to stdin instead." },
|
|
16144
|
+
{
|
|
16145
|
+
flags: "--attachment-id <id>",
|
|
16146
|
+
description: "Attachment id to link (repeatable). Get one from `slock attachment upload`.",
|
|
16147
|
+
parse: (value, prev = []) => prev.concat(value)
|
|
16148
|
+
}
|
|
16149
|
+
]
|
|
16150
|
+
},
|
|
16151
|
+
async (ctx, positionalContent, opts) => {
|
|
16152
|
+
if (!opts.target?.trim()) {
|
|
16153
|
+
throw cliError("INVALID_ARG", "--target is required");
|
|
15901
16154
|
}
|
|
15902
|
-
let ctx;
|
|
15903
16155
|
try {
|
|
15904
|
-
|
|
16156
|
+
rejectArgContent(positionalContent, opts);
|
|
15905
16157
|
} catch (err) {
|
|
15906
|
-
if (err instanceof
|
|
16158
|
+
if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
|
|
15907
16159
|
throw err;
|
|
15908
16160
|
}
|
|
15909
|
-
const client = new ApiClient(ctx);
|
|
15910
16161
|
try {
|
|
15911
16162
|
validateDraftSendFlags(opts);
|
|
15912
16163
|
} catch (err) {
|
|
15913
|
-
if (err instanceof SendContentError)
|
|
16164
|
+
if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
|
|
15914
16165
|
throw err;
|
|
15915
16166
|
}
|
|
15916
16167
|
let content;
|
|
15917
|
-
let outgoingContent;
|
|
16168
|
+
let outgoingContent = "";
|
|
15918
16169
|
let outgoingAttachmentIds = [];
|
|
15919
16170
|
let previousDraftReholdCount = 0;
|
|
15920
16171
|
let seenUpToSeq;
|
|
15921
16172
|
if (opts.sendDraft) {
|
|
15922
|
-
content = await resolveOptionalSendContent();
|
|
16173
|
+
content = await resolveOptionalSendContent(ctx.io.stdin ?? process.stdin);
|
|
15923
16174
|
try {
|
|
15924
16175
|
rejectSendDraftStdin(content, opts.target);
|
|
15925
16176
|
} catch (err) {
|
|
15926
|
-
if (err instanceof SendContentError)
|
|
16177
|
+
if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
|
|
16178
|
+
throw err;
|
|
16179
|
+
}
|
|
16180
|
+
} else {
|
|
16181
|
+
try {
|
|
16182
|
+
content = await resolveSendContent(ctx.io.stdin ?? process.stdin);
|
|
16183
|
+
} catch (err) {
|
|
16184
|
+
if (err instanceof SendContentError) throw cliError(err.code, err.message, { cause: err });
|
|
15927
16185
|
throw err;
|
|
15928
16186
|
}
|
|
15929
|
-
|
|
16187
|
+
outgoingContent = content;
|
|
16188
|
+
outgoingAttachmentIds = opts.attachmentId && opts.attachmentId.length > 0 ? opts.attachmentId : [];
|
|
16189
|
+
}
|
|
16190
|
+
const agentContext = ctx.loadAgentContext();
|
|
16191
|
+
const client = ctx.createApiClient(agentContext);
|
|
16192
|
+
if (opts.sendDraft) {
|
|
16193
|
+
const savedDraft = getSavedDraft(agentContext.agentId, opts.target);
|
|
15930
16194
|
if (!savedDraft) {
|
|
15931
|
-
|
|
16195
|
+
throw cliError(
|
|
15932
16196
|
"SEND_DRAFT_NOT_FOUND",
|
|
15933
16197
|
[
|
|
15934
16198
|
"No saved draft exists for this target.",
|
|
@@ -15945,15 +16209,7 @@ function registerSendCommand(parent) {
|
|
|
15945
16209
|
previousDraftReholdCount = savedDraft.reholdCount;
|
|
15946
16210
|
seenUpToSeq = savedDraft.seenUpToSeq;
|
|
15947
16211
|
} else {
|
|
15948
|
-
|
|
15949
|
-
content = await resolveSendContent();
|
|
15950
|
-
} catch (err) {
|
|
15951
|
-
if (err instanceof SendContentError) fail(err.code, err.message);
|
|
15952
|
-
throw err;
|
|
15953
|
-
}
|
|
15954
|
-
outgoingContent = content;
|
|
15955
|
-
outgoingAttachmentIds = opts.attachmentId && opts.attachmentId.length > 0 ? opts.attachmentId : [];
|
|
15956
|
-
const previousDraft = getSavedDraft(ctx.agentId, opts.target);
|
|
16212
|
+
const previousDraft = getSavedDraft(agentContext.agentId, opts.target);
|
|
15957
16213
|
previousDraftReholdCount = previousDraft?.reholdCount ?? 0;
|
|
15958
16214
|
seenUpToSeq = previousDraft?.seenUpToSeq;
|
|
15959
16215
|
}
|
|
@@ -15976,26 +16232,26 @@ function registerSendCommand(parent) {
|
|
|
15976
16232
|
}
|
|
15977
16233
|
const res = await client.request(
|
|
15978
16234
|
"POST",
|
|
15979
|
-
`/internal/agent/${encodeURIComponent(
|
|
16235
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/send`,
|
|
15980
16236
|
body
|
|
15981
16237
|
);
|
|
15982
16238
|
if (!res.ok) {
|
|
15983
16239
|
const code = res.status >= 500 ? "SERVER_5XX" : "SEND_FAILED";
|
|
15984
|
-
|
|
16240
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
15985
16241
|
}
|
|
15986
16242
|
const data = res.data;
|
|
15987
16243
|
if (data.state === "held") {
|
|
15988
|
-
setSavedDraft(
|
|
16244
|
+
setSavedDraft(agentContext.agentId, opts.target, {
|
|
15989
16245
|
content: outgoingContent,
|
|
15990
16246
|
attachmentIds: outgoingAttachmentIds,
|
|
15991
16247
|
savedAt: Date.now(),
|
|
15992
16248
|
reholdCount: previousDraftReholdCount + 1,
|
|
15993
16249
|
seenUpToSeq: data.seenUpToSeq
|
|
15994
16250
|
});
|
|
15995
|
-
|
|
16251
|
+
writeText(ctx.io, formatHeldSendOutput(opts.target, data));
|
|
15996
16252
|
return;
|
|
15997
16253
|
}
|
|
15998
|
-
clearSavedDraft(
|
|
16254
|
+
clearSavedDraft(agentContext.agentId, opts.target);
|
|
15999
16255
|
const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
|
|
16000
16256
|
const replyHint = shortId ? ` (to reply in this message's thread, use target "${opts.target.includes(":") ? opts.target : opts.target + ":" + shortId}")` : "";
|
|
16001
16257
|
let unreadSection = "";
|
|
@@ -16005,9 +16261,12 @@ function registerSendCommand(parent) {
|
|
|
16005
16261
|
--- New messages you may have missed ---
|
|
16006
16262
|
${formatMessages(data.recentUnread)}`;
|
|
16007
16263
|
}
|
|
16008
|
-
|
|
16264
|
+
writeText(ctx.io, `Message sent to ${opts.target}. Message ID: ${data.messageId}${replyHint}${unreadSection}
|
|
16009
16265
|
`);
|
|
16010
|
-
}
|
|
16266
|
+
}
|
|
16267
|
+
);
|
|
16268
|
+
function registerSendCommand(parent, runtimeOptions = {}) {
|
|
16269
|
+
registerCliCommand(parent, messageSendCommand, runtimeOptions);
|
|
16011
16270
|
}
|
|
16012
16271
|
|
|
16013
16272
|
// src/commands/message/_inbox.ts
|
|
@@ -16017,8 +16276,8 @@ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
|
|
|
16017
16276
|
const query = [];
|
|
16018
16277
|
if (opts.block) query.push("block=true");
|
|
16019
16278
|
if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
|
|
16020
|
-
const
|
|
16021
|
-
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);
|
|
16022
16281
|
if (!res.ok) {
|
|
16023
16282
|
throw new CliError({
|
|
16024
16283
|
code: res.status >= 500 ? "SERVER_5XX" : failCode,
|
|
@@ -16042,8 +16301,7 @@ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
|
|
|
16042
16301
|
var messageCheckCommand = defineCommand(
|
|
16043
16302
|
{
|
|
16044
16303
|
name: "check",
|
|
16045
|
-
description: "Drain the agent inbox (non-blocking). Acks delivered seqs before returning."
|
|
16046
|
-
rationale: "denied"
|
|
16304
|
+
description: "Drain the agent inbox (non-blocking). Acks delivered seqs before returning."
|
|
16047
16305
|
},
|
|
16048
16306
|
async (ctx) => {
|
|
16049
16307
|
const agentContext = ctx.loadAgentContext();
|
|
@@ -16104,7 +16362,6 @@ var messageReadCommand = defineCommand(
|
|
|
16104
16362
|
{
|
|
16105
16363
|
name: "read",
|
|
16106
16364
|
description: "Read message history for a channel, DM, or thread",
|
|
16107
|
-
rationale: "denied",
|
|
16108
16365
|
options: [
|
|
16109
16366
|
{ flags: "--channel <target>", description: "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'" },
|
|
16110
16367
|
{ flags: "--before <seq>", description: "Return messages strictly before this seq (paginate backwards)" },
|
|
@@ -16214,11 +16471,11 @@ function buildSearchPath(agentId, opts) {
|
|
|
16214
16471
|
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
16215
16472
|
return `/internal/agent/${encodeURIComponent(agentId)}/search?${params.toString()}`;
|
|
16216
16473
|
}
|
|
16217
|
-
function toSearchErrorCode(
|
|
16218
|
-
switch (
|
|
16474
|
+
function toSearchErrorCode(errorCode2, status) {
|
|
16475
|
+
switch (errorCode2) {
|
|
16219
16476
|
case "SCOPE_DENIED":
|
|
16220
16477
|
case "INVALID_JSON_RESPONSE":
|
|
16221
|
-
return
|
|
16478
|
+
return errorCode2;
|
|
16222
16479
|
default:
|
|
16223
16480
|
return status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED";
|
|
16224
16481
|
}
|
|
@@ -16227,7 +16484,6 @@ var messageSearchCommand = defineCommand(
|
|
|
16227
16484
|
{
|
|
16228
16485
|
name: "search",
|
|
16229
16486
|
description: "Search messages across channels the agent can see",
|
|
16230
|
-
rationale: "denied",
|
|
16231
16487
|
options: [
|
|
16232
16488
|
{ flags: "--query <q>", description: "Search query string" },
|
|
16233
16489
|
{ flags: "--channel <target>", description: "Restrict to a single channel/DM/thread" },
|
|
@@ -16268,40 +16524,48 @@ function normalizeReactionEmoji(value) {
|
|
|
16268
16524
|
}
|
|
16269
16525
|
return emoji3;
|
|
16270
16526
|
}
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
"",
|
|
16274
|
-
"
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16527
|
+
var messageReactCommand = defineCommand(
|
|
16528
|
+
{
|
|
16529
|
+
name: "react",
|
|
16530
|
+
description: "Add or remove your reaction on a message",
|
|
16531
|
+
options: [
|
|
16532
|
+
{ flags: "--message-id <id>", description: "Message UUID to react to" },
|
|
16533
|
+
{ flags: "--emoji <emoji>", description: "Reaction emoji" },
|
|
16534
|
+
{ flags: "--remove", description: "Remove your reaction instead of adding it" }
|
|
16535
|
+
],
|
|
16536
|
+
helpAfter: "\nAgent guidance:\n Use this only when a human explicitly asks for a reaction or when a reaction is a clear acknowledgement.\n Do not auto-react to every merge, deploy, task completion, or routine status update.\n"
|
|
16537
|
+
},
|
|
16538
|
+
async (ctx, opts) => {
|
|
16539
|
+
if (!opts.messageId?.trim()) {
|
|
16540
|
+
throw cliError("INVALID_ARG", "--message-id is required");
|
|
16541
|
+
}
|
|
16542
|
+
if (typeof opts.emoji !== "string") {
|
|
16543
|
+
throw cliError("INVALID_ARG", "--emoji is required");
|
|
16284
16544
|
}
|
|
16285
16545
|
let emoji3;
|
|
16286
16546
|
try {
|
|
16287
16547
|
emoji3 = normalizeReactionEmoji(opts.emoji);
|
|
16288
16548
|
} catch (err) {
|
|
16289
|
-
|
|
16549
|
+
throw cliError("INVALID_REACTION", err instanceof Error ? err.message : "Invalid reaction emoji", { cause: err });
|
|
16290
16550
|
}
|
|
16291
|
-
const
|
|
16551
|
+
const agentContext = ctx.loadAgentContext();
|
|
16552
|
+
const client = ctx.createApiClient(agentContext);
|
|
16292
16553
|
const res = await client.request(
|
|
16293
16554
|
opts.remove ? "DELETE" : "POST",
|
|
16294
|
-
`/internal/agent/${encodeURIComponent(
|
|
16555
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/messages/${encodeURIComponent(opts.messageId)}/reactions`,
|
|
16295
16556
|
{ emoji: emoji3 }
|
|
16296
16557
|
);
|
|
16297
16558
|
if (!res.ok) {
|
|
16298
16559
|
const code = res.status >= 500 ? "SERVER_5XX" : "REACT_FAILED";
|
|
16299
|
-
|
|
16560
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
16300
16561
|
}
|
|
16301
16562
|
const verb = opts.remove ? "removed from" : "added to";
|
|
16302
|
-
|
|
16563
|
+
writeText(ctx.io, `Reaction ${emoji3} ${verb} message ${opts.messageId.slice(0, 8)}.
|
|
16303
16564
|
`);
|
|
16304
|
-
}
|
|
16565
|
+
}
|
|
16566
|
+
);
|
|
16567
|
+
function registerReactCommand(parent, runtimeOptions = {}) {
|
|
16568
|
+
registerCliCommand(parent, messageReactCommand, runtimeOptions);
|
|
16305
16569
|
}
|
|
16306
16570
|
|
|
16307
16571
|
// src/commands/attachment/upload.ts
|
|
@@ -16390,59 +16654,65 @@ function formatBytes(bytes) {
|
|
|
16390
16654
|
}
|
|
16391
16655
|
return `${unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)}${units[unitIndex]}`;
|
|
16392
16656
|
}
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16657
|
+
var attachmentUploadCommand = defineCommand(
|
|
16658
|
+
{
|
|
16659
|
+
name: "upload",
|
|
16660
|
+
description: `Upload a local file as an attachment (max ${MAX_ATTACHMENT_UPLOAD_LABEL})`,
|
|
16661
|
+
options: [
|
|
16662
|
+
{ flags: "--path <filepath>", description: "Absolute path to the local file to upload" },
|
|
16663
|
+
{
|
|
16664
|
+
flags: "--channel <target>",
|
|
16665
|
+
description: "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
|
|
16666
|
+
},
|
|
16667
|
+
{ flags: "--mime-type <type>", description: "Explicit MIME type override, e.g. image/png" }
|
|
16668
|
+
]
|
|
16669
|
+
},
|
|
16670
|
+
async (ctx, opts) => {
|
|
16671
|
+
if (typeof opts.path !== "string" || opts.path.length === 0) {
|
|
16672
|
+
throw cliError("INVALID_ARG", "--path is required");
|
|
16404
16673
|
}
|
|
16405
16674
|
if (!existsSync(opts.path)) {
|
|
16406
|
-
|
|
16675
|
+
throw cliError("INVALID_ARG", `--path does not exist: ${opts.path}`);
|
|
16407
16676
|
}
|
|
16408
16677
|
const stat2 = statSync(opts.path);
|
|
16409
16678
|
if (!stat2.isFile()) {
|
|
16410
|
-
|
|
16679
|
+
throw cliError("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
|
|
16411
16680
|
}
|
|
16412
16681
|
try {
|
|
16413
16682
|
validateUploadFileSize(stat2.size);
|
|
16414
16683
|
} catch (err) {
|
|
16415
|
-
if (err instanceof AttachmentUploadArgError)
|
|
16684
|
+
if (err instanceof AttachmentUploadArgError) throw cliError(err.code, err.message, { cause: err });
|
|
16416
16685
|
throw err;
|
|
16417
16686
|
}
|
|
16418
16687
|
if (!opts.channel) {
|
|
16419
|
-
|
|
16688
|
+
throw cliError(
|
|
16420
16689
|
"MISSING_CHANNEL",
|
|
16421
16690
|
"v0 server requires a channel to attach the upload to. Pass --channel '#name', 'dm:@peer', or a thread target."
|
|
16422
16691
|
);
|
|
16423
16692
|
}
|
|
16424
|
-
const client = new ApiClient(ctx);
|
|
16425
|
-
const agentPath = `/internal/agent/${encodeURIComponent(ctx.agentId)}`;
|
|
16426
|
-
const resolved = await client.request(
|
|
16427
|
-
"POST",
|
|
16428
|
-
`${agentPath}/resolve-channel`,
|
|
16429
|
-
{ target: opts.channel }
|
|
16430
|
-
);
|
|
16431
|
-
if (!resolved.ok || !resolved.data?.channelId) {
|
|
16432
|
-
const code = resolved.status >= 500 ? "SERVER_5XX" : "RESOLVE_FAILED";
|
|
16433
|
-
fail(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
|
|
16434
|
-
}
|
|
16435
|
-
const channelId = resolved.data.channelId;
|
|
16436
16693
|
const buffer = readFileSync2(opts.path);
|
|
16437
16694
|
const filename = basename(opts.path);
|
|
16438
16695
|
let explicitMimeType;
|
|
16439
16696
|
try {
|
|
16440
16697
|
explicitMimeType = normalizeExplicitMimeType(opts.mimeType);
|
|
16441
16698
|
} catch (err) {
|
|
16442
|
-
if (err instanceof AttachmentUploadArgError)
|
|
16699
|
+
if (err instanceof AttachmentUploadArgError) throw cliError(err.code, err.message, { cause: err });
|
|
16443
16700
|
throw err;
|
|
16444
16701
|
}
|
|
16445
16702
|
const uploadMimeType = inferUploadMimeType(filename, buffer, explicitMimeType);
|
|
16703
|
+
const agentContext = ctx.loadAgentContext();
|
|
16704
|
+
const client = ctx.createApiClient(agentContext);
|
|
16705
|
+
const agentPath = `/internal/agent/${encodeURIComponent(agentContext.agentId)}`;
|
|
16706
|
+
const resolved = await client.request(
|
|
16707
|
+
"POST",
|
|
16708
|
+
`${agentPath}/resolve-channel`,
|
|
16709
|
+
{ target: opts.channel }
|
|
16710
|
+
);
|
|
16711
|
+
if (!resolved.ok || !resolved.data?.channelId) {
|
|
16712
|
+
const code = resolved.status >= 500 ? "SERVER_5XX" : "RESOLVE_FAILED";
|
|
16713
|
+
throw cliError(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
|
|
16714
|
+
}
|
|
16715
|
+
const channelId = resolved.data.channelId;
|
|
16446
16716
|
const blob = new Blob([buffer], { type: uploadMimeType });
|
|
16447
16717
|
const form = new FormData();
|
|
16448
16718
|
form.append("file", blob, filename);
|
|
@@ -16457,15 +16727,18 @@ function registerAttachmentUploadCommand(parent) {
|
|
|
16457
16727
|
);
|
|
16458
16728
|
if (!res.ok) {
|
|
16459
16729
|
const code = res.status >= 500 ? "SERVER_5XX" : "UPLOAD_FAILED";
|
|
16460
|
-
|
|
16730
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
16461
16731
|
}
|
|
16462
16732
|
const d = res.data;
|
|
16463
|
-
|
|
16733
|
+
writeText(ctx.io, `File uploaded: ${d.filename} (${(d.sizeBytes / 1024).toFixed(1)}KB)
|
|
16464
16734
|
Attachment ID: ${d.id}
|
|
16465
16735
|
|
|
16466
16736
|
Use this ID with slock message send --attachment-id ${d.id} to include it in a message.
|
|
16467
16737
|
`);
|
|
16468
|
-
}
|
|
16738
|
+
}
|
|
16739
|
+
);
|
|
16740
|
+
function registerAttachmentUploadCommand(parent, runtimeOptions = {}) {
|
|
16741
|
+
registerCliCommand(parent, attachmentUploadCommand, runtimeOptions);
|
|
16469
16742
|
}
|
|
16470
16743
|
|
|
16471
16744
|
// src/commands/attachment/view.ts
|
|
@@ -16491,7 +16764,6 @@ var attachmentViewCommand = defineCommand(
|
|
|
16491
16764
|
{
|
|
16492
16765
|
name: "view",
|
|
16493
16766
|
description: "Download an attachment by id and save it to a local path",
|
|
16494
|
-
rationale: "denied",
|
|
16495
16767
|
options: [
|
|
16496
16768
|
{ flags: "--id <attachmentId>", description: "Attachment UUID" },
|
|
16497
16769
|
{ flags: "--output <path>", description: "Local path to write the file to" }
|
|
@@ -16605,7 +16877,6 @@ var taskListCommand = defineCommand(
|
|
|
16605
16877
|
{
|
|
16606
16878
|
name: "list",
|
|
16607
16879
|
description: "List tasks in a channel",
|
|
16608
|
-
rationale: "denied",
|
|
16609
16880
|
options: [
|
|
16610
16881
|
{ flags: "--channel <target>", description: "Channel target: '#channel'" },
|
|
16611
16882
|
{ flags: "--status <s>", description: "Filter: all|todo|in_progress|in_review|done (default: server-side)" }
|
|
@@ -16634,86 +16905,103 @@ function registerTaskListCommand(parent, runtimeOptions) {
|
|
|
16634
16905
|
}
|
|
16635
16906
|
|
|
16636
16907
|
// src/commands/task/create.ts
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
"
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16908
|
+
var taskCreateCommand = defineCommand(
|
|
16909
|
+
{
|
|
16910
|
+
name: "create",
|
|
16911
|
+
description: "Create one or more tasks in a channel",
|
|
16912
|
+
options: [
|
|
16913
|
+
{ flags: "--channel <target>", description: "Channel target: '#channel'" },
|
|
16914
|
+
{
|
|
16915
|
+
flags: "--title <title>",
|
|
16916
|
+
description: "Task title (repeatable for batch create)",
|
|
16917
|
+
parse: (value, prev = []) => prev.concat(value)
|
|
16918
|
+
}
|
|
16919
|
+
]
|
|
16920
|
+
},
|
|
16921
|
+
async (ctx, opts) => {
|
|
16922
|
+
if (!opts.channel?.trim()) {
|
|
16923
|
+
throw cliError("INVALID_ARG", "--channel is required");
|
|
16649
16924
|
}
|
|
16650
16925
|
const titles = opts.title ?? [];
|
|
16651
|
-
if (titles.length === 0)
|
|
16652
|
-
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);
|
|
16653
16929
|
const res = await client.request(
|
|
16654
16930
|
"POST",
|
|
16655
|
-
`/internal/agent/${encodeURIComponent(
|
|
16931
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks`,
|
|
16656
16932
|
{ channel: opts.channel, tasks: titles.map((title) => ({ title })) }
|
|
16657
16933
|
);
|
|
16658
16934
|
if (!res.ok) {
|
|
16659
16935
|
const code = res.status >= 500 ? "SERVER_5XX" : "CREATE_FAILED";
|
|
16660
|
-
|
|
16936
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
16661
16937
|
}
|
|
16662
|
-
|
|
16663
|
-
}
|
|
16938
|
+
writeText(ctx.io, formatTasksCreated(opts.channel, res.data) + "\n");
|
|
16939
|
+
}
|
|
16940
|
+
);
|
|
16941
|
+
function registerTaskCreateCommand(parent, runtimeOptions = {}) {
|
|
16942
|
+
registerCliCommand(parent, taskCreateCommand, runtimeOptions);
|
|
16664
16943
|
}
|
|
16665
16944
|
|
|
16666
16945
|
// src/commands/task/claim.ts
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
"
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16946
|
+
var taskClaimCommand = defineCommand(
|
|
16947
|
+
{
|
|
16948
|
+
name: "claim",
|
|
16949
|
+
description: "Claim one or more tasks (by task number or message id)",
|
|
16950
|
+
options: [
|
|
16951
|
+
{ flags: "--channel <target>", description: "Channel target: '#channel'" },
|
|
16952
|
+
{
|
|
16953
|
+
flags: "--number <n>",
|
|
16954
|
+
description: "Task number to claim (repeatable)",
|
|
16955
|
+
parse: (value, prev = []) => prev.concat(value)
|
|
16956
|
+
},
|
|
16957
|
+
{
|
|
16958
|
+
flags: "--message-id <id>",
|
|
16959
|
+
description: "Message id (full or short) to claim (repeatable)",
|
|
16960
|
+
parse: (value, prev = []) => prev.concat(value)
|
|
16961
|
+
}
|
|
16962
|
+
]
|
|
16963
|
+
},
|
|
16964
|
+
async (ctx, opts) => {
|
|
16965
|
+
if (!opts.channel?.trim()) {
|
|
16966
|
+
throw cliError("INVALID_ARG", "--channel is required");
|
|
16683
16967
|
}
|
|
16684
16968
|
const numbers = (opts.number ?? []).map((raw) => {
|
|
16685
16969
|
const n = Number(raw);
|
|
16686
16970
|
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
16687
|
-
|
|
16971
|
+
throw cliError("INVALID_ARG", `--number must be a positive integer; got ${raw}`);
|
|
16688
16972
|
}
|
|
16689
16973
|
return n;
|
|
16690
16974
|
});
|
|
16691
16975
|
const messageIds = opts.messageId ?? [];
|
|
16692
16976
|
if (numbers.length === 0 && messageIds.length === 0) {
|
|
16693
|
-
|
|
16977
|
+
throw cliError("INVALID_ARG", "Provide at least one --number or --message-id");
|
|
16694
16978
|
}
|
|
16695
16979
|
const body = { channel: opts.channel };
|
|
16696
16980
|
if (numbers.length > 0) body.task_numbers = numbers;
|
|
16697
16981
|
if (messageIds.length > 0) body.message_ids = messageIds;
|
|
16698
|
-
const
|
|
16982
|
+
const agentContext = ctx.loadAgentContext();
|
|
16983
|
+
const client = ctx.createApiClient(agentContext);
|
|
16699
16984
|
const res = await client.request(
|
|
16700
16985
|
"POST",
|
|
16701
|
-
`/internal/agent/${encodeURIComponent(
|
|
16986
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/tasks/claim`,
|
|
16702
16987
|
body
|
|
16703
16988
|
);
|
|
16704
16989
|
if (!res.ok) {
|
|
16705
16990
|
const code = res.status >= 500 ? "SERVER_5XX" : "CLAIM_FAILED";
|
|
16706
|
-
|
|
16991
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
16707
16992
|
}
|
|
16708
16993
|
if (isFreshnessHeldResponse(res.data)) {
|
|
16709
|
-
|
|
16994
|
+
writeText(ctx.io, formatFreshnessHoldOutput(opts.channel, res.data, {
|
|
16710
16995
|
heldAction: "Your task claim was not applied.",
|
|
16711
16996
|
draftInstructions: "After reviewing the newer context, rerun the task claim command if it is still correct.\n"
|
|
16712
16997
|
}));
|
|
16713
16998
|
return;
|
|
16714
16999
|
}
|
|
16715
|
-
|
|
16716
|
-
}
|
|
17000
|
+
writeText(ctx.io, formatClaimResults(opts.channel, res.data) + "\n");
|
|
17001
|
+
}
|
|
17002
|
+
);
|
|
17003
|
+
function registerTaskClaimCommand(parent, runtimeOptions = {}) {
|
|
17004
|
+
registerCliCommand(parent, taskClaimCommand, runtimeOptions);
|
|
16717
17005
|
}
|
|
16718
17006
|
|
|
16719
17007
|
// src/commands/task/unclaim.ts
|
|
@@ -16741,7 +17029,6 @@ var taskUnclaimCommand = defineCommand(
|
|
|
16741
17029
|
{
|
|
16742
17030
|
name: "unclaim",
|
|
16743
17031
|
description: "Release a previously-claimed task",
|
|
16744
|
-
rationale: "required",
|
|
16745
17032
|
options: [
|
|
16746
17033
|
{ flags: "--channel <target>", description: "Channel target: '#channel'" },
|
|
16747
17034
|
{ flags: "--number <n>", description: "Task number to unclaim" }
|
|
@@ -16809,7 +17096,6 @@ var taskUpdateCommand = defineCommand(
|
|
|
16809
17096
|
{
|
|
16810
17097
|
name: "update",
|
|
16811
17098
|
description: "Update task status",
|
|
16812
|
-
rationale: "required",
|
|
16813
17099
|
options: [
|
|
16814
17100
|
{ flags: "--channel <target>", description: "Channel target: '#channel'" },
|
|
16815
17101
|
{ flags: "--number <n>", description: "Task number to update" },
|
|
@@ -16934,7 +17220,6 @@ var profileShowCommand = defineCommand(
|
|
|
16934
17220
|
{
|
|
16935
17221
|
name: "show",
|
|
16936
17222
|
description: "Show a profile. Omit the target to show your own profile.",
|
|
16937
|
-
rationale: "denied",
|
|
16938
17223
|
arguments: ["[target]"],
|
|
16939
17224
|
options: [{ flags: "--json", description: "Emit machine-readable JSON" }]
|
|
16940
17225
|
},
|
|
@@ -17003,14 +17288,14 @@ function inferImageMimeType(filename, buffer) {
|
|
|
17003
17288
|
}
|
|
17004
17289
|
function readAvatarFile(avatarFile) {
|
|
17005
17290
|
if (!existsSync2(avatarFile)) {
|
|
17006
|
-
|
|
17291
|
+
throw cliError("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
|
|
17007
17292
|
}
|
|
17008
17293
|
const stat2 = statSync2(avatarFile);
|
|
17009
17294
|
if (!stat2.isFile()) {
|
|
17010
|
-
|
|
17295
|
+
throw cliError("PROFILE_FILE_NOT_FOUND", `Avatar file is not a regular file: ${avatarFile}`);
|
|
17011
17296
|
}
|
|
17012
17297
|
if (stat2.size > MAX_PROFILE_AVATAR_BYTES) {
|
|
17013
|
-
|
|
17298
|
+
throw cliError(
|
|
17014
17299
|
"PROFILE_AVATAR_TOO_LARGE",
|
|
17015
17300
|
`Avatar file is ${stat2.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
|
|
17016
17301
|
);
|
|
@@ -17019,7 +17304,7 @@ function readAvatarFile(avatarFile) {
|
|
|
17019
17304
|
const filename = basename2(avatarFile);
|
|
17020
17305
|
const mimeType = inferImageMimeType(filename, buffer);
|
|
17021
17306
|
if (!mimeType || !PROFILE_AVATAR_MIME_TYPES.has(mimeType)) {
|
|
17022
|
-
|
|
17307
|
+
throw cliError(
|
|
17023
17308
|
"PROFILE_AVATAR_BAD_FORMAT",
|
|
17024
17309
|
"Avatar must be a JPEG, PNG, GIF, or WebP image"
|
|
17025
17310
|
);
|
|
@@ -17029,31 +17314,35 @@ function readAvatarFile(avatarFile) {
|
|
|
17029
17314
|
function normalizeAvatarUrl(avatarUrl) {
|
|
17030
17315
|
const trimmed = avatarUrl.trim();
|
|
17031
17316
|
if (trimmed.length === 0) {
|
|
17032
|
-
|
|
17317
|
+
throw cliError("INVALID_ARG", "--avatar-url must not be empty");
|
|
17033
17318
|
}
|
|
17034
17319
|
if (!trimmed.startsWith("pixel:")) {
|
|
17035
|
-
|
|
17320
|
+
throw cliError("INVALID_ARG", "--avatar-url currently supports only pixel avatar URLs; use --avatar-file for image uploads");
|
|
17036
17321
|
}
|
|
17037
17322
|
return trimmed;
|
|
17038
17323
|
}
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17324
|
+
var profileUpdateCommand = defineCommand(
|
|
17325
|
+
{
|
|
17326
|
+
name: "update",
|
|
17327
|
+
description: "Update your own profile",
|
|
17328
|
+
options: [
|
|
17329
|
+
{ flags: "--avatar-file <path>", description: "Path to a local image file to use as your avatar" },
|
|
17330
|
+
{ flags: "--avatar-url <value>", description: "Set a pixel avatar URL such as pixel:random:<seed>" },
|
|
17331
|
+
{ flags: "--display-name <name>", description: "Set your display name (non-empty)" },
|
|
17332
|
+
{ flags: "--description <text>", description: "Set your profile description (non-empty)" },
|
|
17333
|
+
{ flags: "--json", description: "Emit machine-readable JSON" }
|
|
17334
|
+
]
|
|
17335
|
+
},
|
|
17336
|
+
async (ctx, opts) => {
|
|
17048
17337
|
const hasAvatar = opts.avatarFile !== void 0;
|
|
17049
17338
|
const hasAvatarUrl = opts.avatarUrl !== void 0;
|
|
17050
17339
|
const hasDisplayName = opts.displayName !== void 0;
|
|
17051
17340
|
const hasDescription = opts.description !== void 0;
|
|
17052
17341
|
if (!hasAvatar && !hasAvatarUrl && !hasDisplayName && !hasDescription) {
|
|
17053
|
-
|
|
17342
|
+
throw cliError("INVALID_ARG", "Provide at least one of --avatar-file, --avatar-url, --display-name, or --description");
|
|
17054
17343
|
}
|
|
17055
17344
|
if (hasAvatar && hasAvatarUrl) {
|
|
17056
|
-
|
|
17345
|
+
throw cliError("INVALID_ARG", "Use either --avatar-file or --avatar-url, not both");
|
|
17057
17346
|
}
|
|
17058
17347
|
let normalizedAvatarUrl;
|
|
17059
17348
|
if (hasAvatarUrl) {
|
|
@@ -17063,21 +17352,23 @@ function registerProfileUpdateCommand(parent) {
|
|
|
17063
17352
|
if (hasDisplayName) {
|
|
17064
17353
|
trimmedDisplayName = opts.displayName.trim();
|
|
17065
17354
|
if (trimmedDisplayName.length === 0) {
|
|
17066
|
-
|
|
17355
|
+
throw cliError("INVALID_ARG", "--display-name must not be empty");
|
|
17067
17356
|
}
|
|
17068
17357
|
if (trimmedDisplayName.length > MAX_PROFILE_DISPLAY_NAME_LENGTH) {
|
|
17069
|
-
|
|
17358
|
+
throw cliError("INVALID_ARG", `--display-name must be at most ${MAX_PROFILE_DISPLAY_NAME_LENGTH} characters`);
|
|
17070
17359
|
}
|
|
17071
17360
|
}
|
|
17072
17361
|
if (hasDescription) {
|
|
17073
17362
|
if (opts.description.length === 0) {
|
|
17074
|
-
|
|
17363
|
+
throw cliError("INVALID_ARG", "--description must not be empty");
|
|
17075
17364
|
}
|
|
17076
17365
|
if (opts.description.length > MAX_PROFILE_DESCRIPTION_LENGTH) {
|
|
17077
|
-
|
|
17366
|
+
throw cliError("INVALID_ARG", `--description must be at most ${MAX_PROFILE_DESCRIPTION_LENGTH} characters`);
|
|
17078
17367
|
}
|
|
17079
17368
|
}
|
|
17080
|
-
const
|
|
17369
|
+
const avatar = hasAvatar ? readAvatarFile(opts.avatarFile) : null;
|
|
17370
|
+
const agentContext = ctx.loadAgentContext();
|
|
17371
|
+
const client = ctx.createApiClient(agentContext);
|
|
17081
17372
|
let latestProfile = null;
|
|
17082
17373
|
if (hasAvatarUrl || hasDisplayName || hasDescription) {
|
|
17083
17374
|
const body = {};
|
|
@@ -17092,41 +17383,43 @@ function registerProfileUpdateCommand(parent) {
|
|
|
17092
17383
|
}
|
|
17093
17384
|
const res = await client.request(
|
|
17094
17385
|
"POST",
|
|
17095
|
-
`/internal/agent/${encodeURIComponent(
|
|
17386
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/profile`,
|
|
17096
17387
|
body
|
|
17097
17388
|
);
|
|
17098
17389
|
if (!res.ok || !res.data) {
|
|
17099
17390
|
const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
|
|
17100
|
-
|
|
17391
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17101
17392
|
}
|
|
17102
17393
|
latestProfile = res.data;
|
|
17103
17394
|
}
|
|
17104
17395
|
if (hasAvatar) {
|
|
17105
|
-
const avatar = readAvatarFile(opts.avatarFile);
|
|
17106
17396
|
const form = new FormData();
|
|
17107
17397
|
const avatarBytes = Uint8Array.from(avatar.buffer);
|
|
17108
17398
|
form.append("avatar", new Blob([avatarBytes], { type: avatar.mimeType }), avatar.filename);
|
|
17109
17399
|
const res = await client.requestMultipart(
|
|
17110
17400
|
"POST",
|
|
17111
|
-
`/internal/agent/${encodeURIComponent(
|
|
17401
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/profile/avatar`,
|
|
17112
17402
|
form
|
|
17113
17403
|
);
|
|
17114
17404
|
if (!res.ok || !res.data) {
|
|
17115
17405
|
const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
|
|
17116
|
-
|
|
17406
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17117
17407
|
}
|
|
17118
17408
|
latestProfile = res.data;
|
|
17119
17409
|
}
|
|
17120
17410
|
if (!latestProfile) {
|
|
17121
|
-
|
|
17411
|
+
throw cliError("PROFILE_UPDATE_FAILED", "No profile returned from server");
|
|
17122
17412
|
}
|
|
17123
17413
|
if (opts.json) {
|
|
17124
|
-
|
|
17414
|
+
writeJson(ctx.io, { ok: true, data: latestProfile });
|
|
17125
17415
|
return;
|
|
17126
17416
|
}
|
|
17127
|
-
|
|
17417
|
+
writeText(ctx.io, `${formatProfile(latestProfile)}
|
|
17128
17418
|
`);
|
|
17129
|
-
}
|
|
17419
|
+
}
|
|
17420
|
+
);
|
|
17421
|
+
function registerProfileUpdateCommand(parent, runtimeOptions = {}) {
|
|
17422
|
+
registerCliCommand(parent, profileUpdateCommand, runtimeOptions);
|
|
17130
17423
|
}
|
|
17131
17424
|
|
|
17132
17425
|
// src/commands/integration/_format.ts
|
|
@@ -17157,6 +17450,10 @@ function formatIntegrationList(data) {
|
|
|
17157
17450
|
lines.push(` id: ${service.id}`);
|
|
17158
17451
|
lines.push(` status: ${active ? "active login" : "not logged in"}`);
|
|
17159
17452
|
lines.push(` return URL: ${formatMaybe(service.returnUrl)}`);
|
|
17453
|
+
if (service.agentManifestUrl) {
|
|
17454
|
+
lines.push(` agent behavior manifest: ${service.agentManifestUrl}`);
|
|
17455
|
+
lines.push(` local CLI env: slock integration env --service ${JSON.stringify(service.clientId)}`);
|
|
17456
|
+
}
|
|
17160
17457
|
if (service.homepageUrl) lines.push(` homepage: ${service.homepageUrl}`);
|
|
17161
17458
|
if (service.description) lines.push(` description: ${service.description}`);
|
|
17162
17459
|
if (!active) lines.push(` next: slock integration login --service ${JSON.stringify(service.clientId)}`);
|
|
@@ -17173,6 +17470,10 @@ function formatIntegrationList(data) {
|
|
|
17173
17470
|
lines.push(` grant id: ${login.id}`);
|
|
17174
17471
|
lines.push(` scopes: ${login.scopes.length > 0 ? login.scopes.join(", ") : "-"}`);
|
|
17175
17472
|
lines.push(` return URL: ${formatMaybe(login.returnUrl)}`);
|
|
17473
|
+
if (login.agentManifestUrl) {
|
|
17474
|
+
lines.push(` agent behavior manifest: ${login.agentManifestUrl}`);
|
|
17475
|
+
lines.push(` local CLI env: slock integration env --service ${JSON.stringify(login.clientId)}`);
|
|
17476
|
+
}
|
|
17176
17477
|
lines.push(` created: ${login.createdAt}`);
|
|
17177
17478
|
}
|
|
17178
17479
|
}
|
|
@@ -17185,10 +17486,14 @@ function formatIntegrationLogin(data) {
|
|
|
17185
17486
|
`service: ${data.service.clientId}`,
|
|
17186
17487
|
`id: ${data.service.id}`,
|
|
17187
17488
|
`scopes: ${data.scopes.length > 0 ? data.scopes.join(", ") : "-"}`,
|
|
17188
|
-
`return URL: ${formatMaybe(data.service.returnUrl)}
|
|
17189
|
-
"complete: this agent login is configured in Slock; no human OAuth is required",
|
|
17190
|
-
"identity: run `slock profile show` if the service or human asks for your Slock Agent identity card"
|
|
17489
|
+
`return URL: ${formatMaybe(data.service.returnUrl)}`
|
|
17191
17490
|
];
|
|
17491
|
+
if (data.service.agentManifestUrl) {
|
|
17492
|
+
lines.push(`agent behavior manifest: ${data.service.agentManifestUrl}`);
|
|
17493
|
+
lines.push(`local CLI env: slock integration env --service ${JSON.stringify(data.service.clientId)}`);
|
|
17494
|
+
}
|
|
17495
|
+
lines.push("complete: this agent login is configured in Slock; no human OAuth is required");
|
|
17496
|
+
lines.push("identity: run `slock profile show` if the service or human asks for your Slock Agent identity card");
|
|
17192
17497
|
const agentAppUrl = buildAgentAppUrl(data.service.returnUrl, data.requestId);
|
|
17193
17498
|
if (agentAppUrl) {
|
|
17194
17499
|
lines.push(`app URL: ${agentAppUrl}`);
|
|
@@ -17200,31 +17505,33 @@ function formatIntegrationLogin(data) {
|
|
|
17200
17505
|
}
|
|
17201
17506
|
|
|
17202
17507
|
// src/commands/integration/list.ts
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
const client = new ApiClient(ctx);
|
|
17508
|
+
var integrationListCommand = defineCommand(
|
|
17509
|
+
{
|
|
17510
|
+
name: "list",
|
|
17511
|
+
description: "List registered third-party services and this agent's active logins",
|
|
17512
|
+
options: [{ flags: "--json", description: "Emit machine-readable JSON" }]
|
|
17513
|
+
},
|
|
17514
|
+
async (ctx, opts) => {
|
|
17515
|
+
const agentContext = ctx.loadAgentContext();
|
|
17516
|
+
const client = ctx.createApiClient(agentContext);
|
|
17213
17517
|
const res = await client.request(
|
|
17214
17518
|
"GET",
|
|
17215
|
-
`/internal/agent/${encodeURIComponent(
|
|
17519
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/integrations`
|
|
17216
17520
|
);
|
|
17217
17521
|
if (!res.ok || !res.data) {
|
|
17218
17522
|
const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LIST_FAILED";
|
|
17219
|
-
|
|
17523
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17220
17524
|
}
|
|
17221
17525
|
if (opts.json) {
|
|
17222
|
-
|
|
17526
|
+
writeJson(ctx.io, { ok: true, data: res.data });
|
|
17223
17527
|
return;
|
|
17224
17528
|
}
|
|
17225
|
-
|
|
17529
|
+
writeText(ctx.io, `${formatIntegrationList(res.data)}
|
|
17226
17530
|
`);
|
|
17227
|
-
}
|
|
17531
|
+
}
|
|
17532
|
+
);
|
|
17533
|
+
function registerIntegrationListCommand(parent, runtimeOptions = {}) {
|
|
17534
|
+
registerCliCommand(parent, integrationListCommand, runtimeOptions);
|
|
17228
17535
|
}
|
|
17229
17536
|
|
|
17230
17537
|
// src/commands/integration/login.ts
|
|
@@ -17234,46 +17541,389 @@ function normalizeScopes(raw) {
|
|
|
17234
17541
|
raw.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
|
|
17235
17542
|
)).sort();
|
|
17236
17543
|
if (scopes.length === 0) {
|
|
17237
|
-
|
|
17544
|
+
throw cliError("INVALID_ARG", "--scope must include at least one non-empty scope");
|
|
17238
17545
|
}
|
|
17239
17546
|
return scopes;
|
|
17240
17547
|
}
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17548
|
+
var integrationLoginCommand = defineCommand(
|
|
17549
|
+
{
|
|
17550
|
+
name: "login",
|
|
17551
|
+
description: "Provision or reuse this agent's login for a registered service",
|
|
17552
|
+
options: [
|
|
17553
|
+
{ flags: "--service <id>", description: "Registered service id, client id, or exact service name" },
|
|
17554
|
+
{
|
|
17555
|
+
flags: "--scope <scope>",
|
|
17556
|
+
description: "Requested scope; can be repeated or comma-separated",
|
|
17557
|
+
parse: (value, previous = []) => {
|
|
17558
|
+
previous.push(value);
|
|
17559
|
+
return previous;
|
|
17560
|
+
}
|
|
17561
|
+
},
|
|
17562
|
+
{ flags: "--json", description: "Emit machine-readable JSON" }
|
|
17563
|
+
]
|
|
17564
|
+
},
|
|
17565
|
+
async (ctx, opts) => {
|
|
17566
|
+
const service = opts.service?.trim() ?? "";
|
|
17254
17567
|
if (!service) {
|
|
17255
|
-
|
|
17568
|
+
throw cliError("INVALID_ARG", "--service is required");
|
|
17256
17569
|
}
|
|
17257
|
-
const
|
|
17570
|
+
const scopes = normalizeScopes(opts.scope);
|
|
17571
|
+
const agentContext = ctx.loadAgentContext();
|
|
17572
|
+
const client = ctx.createApiClient(agentContext);
|
|
17258
17573
|
const res = await client.request(
|
|
17259
17574
|
"POST",
|
|
17260
|
-
`/internal/agent/${encodeURIComponent(
|
|
17575
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/integrations/login`,
|
|
17261
17576
|
{
|
|
17262
17577
|
service,
|
|
17263
|
-
scopes
|
|
17578
|
+
scopes
|
|
17264
17579
|
}
|
|
17265
17580
|
);
|
|
17266
17581
|
if (!res.ok || !res.data) {
|
|
17267
17582
|
const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LOGIN_FAILED";
|
|
17268
|
-
|
|
17583
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17269
17584
|
}
|
|
17270
17585
|
if (opts.json) {
|
|
17271
|
-
|
|
17586
|
+
writeJson(ctx.io, { ok: true, data: res.data });
|
|
17272
17587
|
return;
|
|
17273
17588
|
}
|
|
17274
|
-
|
|
17589
|
+
writeText(ctx.io, `${formatIntegrationLogin(res.data)}
|
|
17275
17590
|
`);
|
|
17591
|
+
}
|
|
17592
|
+
);
|
|
17593
|
+
function registerIntegrationLoginCommand(parent, runtimeOptions = {}) {
|
|
17594
|
+
registerCliCommand(parent, integrationLoginCommand, runtimeOptions);
|
|
17595
|
+
}
|
|
17596
|
+
|
|
17597
|
+
// src/commands/integration/manifest.ts
|
|
17598
|
+
import fs3 from "fs";
|
|
17599
|
+
import os3 from "os";
|
|
17600
|
+
import path5 from "path";
|
|
17601
|
+
var AGENT_MANIFEST_MAX_BYTES = 64 * 1024;
|
|
17602
|
+
function isRecord(value) {
|
|
17603
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
17604
|
+
}
|
|
17605
|
+
function requireUrl(value, field) {
|
|
17606
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
17607
|
+
throw new Error(`${field} must be a non-empty URL string`);
|
|
17608
|
+
}
|
|
17609
|
+
let parsed;
|
|
17610
|
+
try {
|
|
17611
|
+
parsed = new URL(value);
|
|
17612
|
+
} catch {
|
|
17613
|
+
throw new Error(`${field} must be a valid URL`);
|
|
17614
|
+
}
|
|
17615
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
17616
|
+
throw new Error(`${field} must use http or https`);
|
|
17617
|
+
}
|
|
17618
|
+
if (parsed.username || parsed.password) {
|
|
17619
|
+
throw new Error(`${field} must not include credentials`);
|
|
17620
|
+
}
|
|
17621
|
+
return parsed.toString();
|
|
17622
|
+
}
|
|
17623
|
+
function requireCommand(value) {
|
|
17624
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
17625
|
+
throw new Error("execution.command must be a non-empty string for local_cli manifests");
|
|
17626
|
+
}
|
|
17627
|
+
const command = value.trim();
|
|
17628
|
+
if (command.includes("/") || command.includes("\\") || /\s/.test(command)) {
|
|
17629
|
+
throw new Error("execution.command must be a bare command name, not a path or shell fragment");
|
|
17630
|
+
}
|
|
17631
|
+
return command;
|
|
17632
|
+
}
|
|
17633
|
+
function validateAgentManifestV0(value) {
|
|
17634
|
+
if (!isRecord(value)) throw new Error("manifest must be a JSON object");
|
|
17635
|
+
if (value.schema !== "slock-agent-manifest.v0") {
|
|
17636
|
+
throw new Error("manifest schema must be slock-agent-manifest.v0");
|
|
17637
|
+
}
|
|
17638
|
+
const execution = value.execution;
|
|
17639
|
+
if (!isRecord(execution)) throw new Error("execution must be an object");
|
|
17640
|
+
if (execution.mode !== "local_cli" && execution.mode !== "http_api") {
|
|
17641
|
+
throw new Error("execution.mode must be local_cli or http_api");
|
|
17642
|
+
}
|
|
17643
|
+
const credentialBoundary = value.credential_boundary;
|
|
17644
|
+
if (credentialBoundary !== void 0 && !isRecord(credentialBoundary)) {
|
|
17645
|
+
throw new Error("credential_boundary must be an object when present");
|
|
17646
|
+
}
|
|
17647
|
+
if (isRecord(credentialBoundary) && credentialBoundary.storage !== "per_agent_home" && credentialBoundary.storage !== "slock_managed_token") {
|
|
17648
|
+
throw new Error("credential_boundary.storage must be per_agent_home or slock_managed_token");
|
|
17649
|
+
}
|
|
17650
|
+
const credentialStorage = isRecord(credentialBoundary) && (credentialBoundary.storage === "per_agent_home" || credentialBoundary.storage === "slock_managed_token") ? credentialBoundary.storage : void 0;
|
|
17651
|
+
const forbidUserHome = isRecord(credentialBoundary) && credentialBoundary.forbid_user_home === true;
|
|
17652
|
+
if (value.context_check !== void 0 && !isRecord(value.context_check)) {
|
|
17653
|
+
throw new Error("context_check must be an object when present");
|
|
17654
|
+
}
|
|
17655
|
+
return {
|
|
17656
|
+
schema: value.schema,
|
|
17657
|
+
service: typeof value.service === "string" && value.service.trim() ? value.service.trim() : void 0,
|
|
17658
|
+
docs_url: value.docs_url === void 0 ? void 0 : requireUrl(value.docs_url, "docs_url"),
|
|
17659
|
+
execution: {
|
|
17660
|
+
mode: execution.mode,
|
|
17661
|
+
command: execution.mode === "local_cli" ? requireCommand(execution.command) : void 0,
|
|
17662
|
+
base_url: execution.base_url === void 0 ? void 0 : requireUrl(execution.base_url, "execution.base_url")
|
|
17663
|
+
},
|
|
17664
|
+
credential_boundary: credentialStorage ? {
|
|
17665
|
+
storage: credentialStorage,
|
|
17666
|
+
forbid_user_home: forbidUserHome
|
|
17667
|
+
} : void 0,
|
|
17668
|
+
context_check: value.context_check
|
|
17669
|
+
};
|
|
17670
|
+
}
|
|
17671
|
+
async function fetchAgentManifest(url2) {
|
|
17672
|
+
const parsed = new URL(url2);
|
|
17673
|
+
if (parsed.protocol !== "https:") {
|
|
17674
|
+
throw new Error("agent behavior manifest URL must use HTTPS");
|
|
17675
|
+
}
|
|
17676
|
+
if (parsed.username || parsed.password) {
|
|
17677
|
+
throw new Error("agent behavior manifest URL must not include credentials");
|
|
17678
|
+
}
|
|
17679
|
+
const response = await fetch(parsed, {
|
|
17680
|
+
headers: { accept: "application/json" },
|
|
17681
|
+
redirect: "follow"
|
|
17276
17682
|
});
|
|
17683
|
+
const finalUrl = new URL(response.url);
|
|
17684
|
+
if (finalUrl.protocol !== "https:" || finalUrl.username || finalUrl.password) {
|
|
17685
|
+
throw new Error("manifest redirects must remain on credential-free HTTPS URLs");
|
|
17686
|
+
}
|
|
17687
|
+
if (!response.ok) {
|
|
17688
|
+
throw new Error(`manifest fetch failed with HTTP ${response.status}`);
|
|
17689
|
+
}
|
|
17690
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
17691
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
17692
|
+
throw new Error("manifest response must be application/json");
|
|
17693
|
+
}
|
|
17694
|
+
const contentLength = Number(response.headers.get("content-length") ?? "");
|
|
17695
|
+
if (Number.isFinite(contentLength) && contentLength > AGENT_MANIFEST_MAX_BYTES) {
|
|
17696
|
+
throw new Error("manifest response is too large");
|
|
17697
|
+
}
|
|
17698
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
17699
|
+
if (body.byteLength > AGENT_MANIFEST_MAX_BYTES) {
|
|
17700
|
+
throw new Error("manifest response is too large");
|
|
17701
|
+
}
|
|
17702
|
+
const raw = body.toString("utf8");
|
|
17703
|
+
let parsedJson;
|
|
17704
|
+
try {
|
|
17705
|
+
parsedJson = JSON.parse(raw);
|
|
17706
|
+
} catch (err) {
|
|
17707
|
+
throw new Error(`manifest response is not valid JSON: ${err.message}`);
|
|
17708
|
+
}
|
|
17709
|
+
return validateAgentManifestV0(parsedJson);
|
|
17710
|
+
}
|
|
17711
|
+
function sanitizePathSegment(value) {
|
|
17712
|
+
const segment = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
17713
|
+
return segment || "service";
|
|
17714
|
+
}
|
|
17715
|
+
function resolveSlockHome(env) {
|
|
17716
|
+
if (env.SLOCK_HOME?.trim()) return path5.resolve(env.SLOCK_HOME);
|
|
17717
|
+
return path5.join(env.HOME ?? os3.homedir(), ".slock");
|
|
17718
|
+
}
|
|
17719
|
+
function buildLocalCliProfileEnv(input) {
|
|
17720
|
+
if (input.manifest.execution.mode !== "local_cli" || !input.manifest.execution.command) {
|
|
17721
|
+
throw new Error("manifest is not a local_cli integration");
|
|
17722
|
+
}
|
|
17723
|
+
if (input.manifest.credential_boundary?.storage !== "per_agent_home") {
|
|
17724
|
+
throw new Error("manifest does not request per_agent_home credential storage");
|
|
17725
|
+
}
|
|
17726
|
+
if (input.manifest.credential_boundary.forbid_user_home !== true) {
|
|
17727
|
+
throw new Error("manifest must set credential_boundary.forbid_user_home=true for local_cli isolation");
|
|
17728
|
+
}
|
|
17729
|
+
const root = resolveSlockHome(input.env ?? process.env);
|
|
17730
|
+
const profileHome = path5.join(
|
|
17731
|
+
root,
|
|
17732
|
+
"integration-profiles",
|
|
17733
|
+
sanitizePathSegment(input.ctx.serverId ?? "server"),
|
|
17734
|
+
sanitizePathSegment(input.ctx.agentId),
|
|
17735
|
+
sanitizePathSegment(input.serviceId)
|
|
17736
|
+
);
|
|
17737
|
+
fs3.mkdirSync(profileHome, { recursive: true, mode: 448 });
|
|
17738
|
+
fs3.chmodSync(profileHome, 448);
|
|
17739
|
+
return {
|
|
17740
|
+
serviceId: input.serviceId,
|
|
17741
|
+
command: input.manifest.execution.command,
|
|
17742
|
+
profileHome,
|
|
17743
|
+
env: {
|
|
17744
|
+
SLOCK_INTEGRATION_SERVICE: input.serviceId,
|
|
17745
|
+
SLOCK_INTEGRATION_PROFILE_HOME: profileHome,
|
|
17746
|
+
HOME: profileHome,
|
|
17747
|
+
XDG_CONFIG_HOME: path5.join(profileHome, ".config"),
|
|
17748
|
+
XDG_CACHE_HOME: path5.join(profileHome, ".cache"),
|
|
17749
|
+
XDG_DATA_HOME: path5.join(profileHome, ".local", "share"),
|
|
17750
|
+
XDG_STATE_HOME: path5.join(profileHome, ".local", "state")
|
|
17751
|
+
}
|
|
17752
|
+
};
|
|
17753
|
+
}
|
|
17754
|
+
function shellQuote(value) {
|
|
17755
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
17756
|
+
}
|
|
17757
|
+
function formatShellExports(profile) {
|
|
17758
|
+
const lines = [
|
|
17759
|
+
`# Slock integration profile for ${profile.serviceId}`,
|
|
17760
|
+
`# command: ${profile.command}`,
|
|
17761
|
+
"# This only exports env; Slock does not execute manifest commands.",
|
|
17762
|
+
"# Apply these exports before invoking the local CLI yourself."
|
|
17763
|
+
];
|
|
17764
|
+
for (const [key, value] of Object.entries(profile.env)) {
|
|
17765
|
+
lines.push(`export ${key}=${shellQuote(value)}`);
|
|
17766
|
+
}
|
|
17767
|
+
return lines.join("\n");
|
|
17768
|
+
}
|
|
17769
|
+
|
|
17770
|
+
// src/commands/integration/env.ts
|
|
17771
|
+
function normalizeService(value) {
|
|
17772
|
+
return value.trim().toLowerCase();
|
|
17773
|
+
}
|
|
17774
|
+
function findService(data, service) {
|
|
17775
|
+
const normalized = normalizeService(service);
|
|
17776
|
+
if (!normalized) return null;
|
|
17777
|
+
return data.services.find(
|
|
17778
|
+
(candidate) => candidate.id === service || normalizeService(candidate.clientId) === normalized || normalizeService(candidate.name) === normalized
|
|
17779
|
+
) ?? null;
|
|
17780
|
+
}
|
|
17781
|
+
function buildIntegrationEnvListPath(agentId) {
|
|
17782
|
+
return `/internal/agent/${encodeURIComponent(agentId)}/integrations`;
|
|
17783
|
+
}
|
|
17784
|
+
function describeNoLocalEnv(manifest) {
|
|
17785
|
+
if (manifest.execution.mode !== "local_cli") {
|
|
17786
|
+
return "manifest execution mode is not local_cli; no local CLI env is required";
|
|
17787
|
+
}
|
|
17788
|
+
if (!manifest.credential_boundary) {
|
|
17789
|
+
return "manifest does not request a Slock-managed local environment";
|
|
17790
|
+
}
|
|
17791
|
+
if (manifest.credential_boundary.storage === "slock_managed_token") {
|
|
17792
|
+
return "manifest uses Slock-managed token storage; no local HOME/XDG exports are required";
|
|
17793
|
+
}
|
|
17794
|
+
return "manifest does not request local CLI env exports";
|
|
17795
|
+
}
|
|
17796
|
+
var IntegrationEnvError = class extends Error {
|
|
17797
|
+
constructor(code, message) {
|
|
17798
|
+
super(message);
|
|
17799
|
+
this.code = code;
|
|
17800
|
+
this.name = "IntegrationEnvError";
|
|
17801
|
+
}
|
|
17802
|
+
};
|
|
17803
|
+
async function resolveIntegrationEnv(input) {
|
|
17804
|
+
if (!input.service.agentManifestUrl) {
|
|
17805
|
+
throw new IntegrationEnvError(
|
|
17806
|
+
"INTEGRATION_MANIFEST_MISSING",
|
|
17807
|
+
`${input.service.name} does not expose an agent behavior manifest`
|
|
17808
|
+
);
|
|
17809
|
+
}
|
|
17810
|
+
const fetchManifestImpl = input.fetchManifest ?? fetchAgentManifest;
|
|
17811
|
+
let manifest;
|
|
17812
|
+
try {
|
|
17813
|
+
manifest = await fetchManifestImpl(input.service.agentManifestUrl);
|
|
17814
|
+
} catch (err) {
|
|
17815
|
+
throw new IntegrationEnvError("INTEGRATION_MANIFEST_INVALID", err.message);
|
|
17816
|
+
}
|
|
17817
|
+
if (manifest.execution.mode !== "local_cli" || manifest.credential_boundary?.storage !== "per_agent_home") {
|
|
17818
|
+
return {
|
|
17819
|
+
kind: "no-local-env",
|
|
17820
|
+
service: input.service,
|
|
17821
|
+
manifestUrl: input.service.agentManifestUrl,
|
|
17822
|
+
manifest,
|
|
17823
|
+
message: describeNoLocalEnv(manifest)
|
|
17824
|
+
};
|
|
17825
|
+
}
|
|
17826
|
+
try {
|
|
17827
|
+
return {
|
|
17828
|
+
kind: "local-env",
|
|
17829
|
+
service: input.service,
|
|
17830
|
+
manifestUrl: input.service.agentManifestUrl,
|
|
17831
|
+
profile: buildLocalCliProfileEnv({
|
|
17832
|
+
ctx: input.ctx,
|
|
17833
|
+
serviceId: input.service.clientId,
|
|
17834
|
+
manifest,
|
|
17835
|
+
env: input.env
|
|
17836
|
+
})
|
|
17837
|
+
};
|
|
17838
|
+
} catch (err) {
|
|
17839
|
+
throw new IntegrationEnvError("INTEGRATION_MANIFEST_UNSUPPORTED", err.message);
|
|
17840
|
+
}
|
|
17841
|
+
}
|
|
17842
|
+
function formatNoLocalEnv(input) {
|
|
17843
|
+
return [
|
|
17844
|
+
`# Slock integration profile for ${input.service}`,
|
|
17845
|
+
`# manifest: ${input.manifestUrl}`,
|
|
17846
|
+
"# No local CLI environment exports are required by this manifest.",
|
|
17847
|
+
`# ${input.message}`,
|
|
17848
|
+
"# Slock did not set HOME/XDG exports and did not execute manifest commands."
|
|
17849
|
+
].join("\n");
|
|
17850
|
+
}
|
|
17851
|
+
var integrationEnvCommand = defineCommand(
|
|
17852
|
+
{
|
|
17853
|
+
name: "env",
|
|
17854
|
+
description: "Print per-agent local environment for a manifest-backed local CLI integration",
|
|
17855
|
+
options: [
|
|
17856
|
+
{ flags: "--service <id>", description: "Registered service id, client id, or exact service name" },
|
|
17857
|
+
{ flags: "--json", description: "Emit machine-readable JSON" }
|
|
17858
|
+
]
|
|
17859
|
+
},
|
|
17860
|
+
async (cmdCtx, opts) => {
|
|
17861
|
+
const serviceQuery = opts.service?.trim() ?? "";
|
|
17862
|
+
if (!serviceQuery) {
|
|
17863
|
+
throw cliError("INVALID_ARG", "--service must not be empty");
|
|
17864
|
+
}
|
|
17865
|
+
const ctx = cmdCtx.loadAgentContext();
|
|
17866
|
+
const client = cmdCtx.createApiClient(ctx);
|
|
17867
|
+
const res = await client.request("GET", buildIntegrationEnvListPath(ctx.agentId));
|
|
17868
|
+
if (!res.ok || !res.data) {
|
|
17869
|
+
const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LIST_FAILED";
|
|
17870
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17871
|
+
}
|
|
17872
|
+
const integrations = res.data;
|
|
17873
|
+
const service = findService(integrations, serviceQuery);
|
|
17874
|
+
if (!service) {
|
|
17875
|
+
throw cliError("INTEGRATION_NOT_FOUND", `No registered integration matched ${serviceQuery}`);
|
|
17876
|
+
}
|
|
17877
|
+
let resolution;
|
|
17878
|
+
try {
|
|
17879
|
+
resolution = await resolveIntegrationEnv({ ctx, service });
|
|
17880
|
+
} catch (err) {
|
|
17881
|
+
if (err instanceof IntegrationEnvError) throw cliError(err.code, err.message, { cause: err });
|
|
17882
|
+
throw err;
|
|
17883
|
+
}
|
|
17884
|
+
if (resolution.kind === "no-local-env") {
|
|
17885
|
+
if (opts.json) {
|
|
17886
|
+
writeJson(cmdCtx.io, {
|
|
17887
|
+
ok: true,
|
|
17888
|
+
data: {
|
|
17889
|
+
service: resolution.service.clientId,
|
|
17890
|
+
manifestUrl: resolution.manifestUrl,
|
|
17891
|
+
requiresLocalEnv: false,
|
|
17892
|
+
command: resolution.manifest.execution.command ?? null,
|
|
17893
|
+
env: {},
|
|
17894
|
+
message: resolution.message
|
|
17895
|
+
}
|
|
17896
|
+
});
|
|
17897
|
+
return;
|
|
17898
|
+
}
|
|
17899
|
+
writeText(cmdCtx.io, `${formatNoLocalEnv({
|
|
17900
|
+
service: resolution.service.clientId,
|
|
17901
|
+
manifestUrl: resolution.manifestUrl,
|
|
17902
|
+
message: resolution.message
|
|
17903
|
+
})}
|
|
17904
|
+
`);
|
|
17905
|
+
return;
|
|
17906
|
+
}
|
|
17907
|
+
if (opts.json) {
|
|
17908
|
+
writeJson(cmdCtx.io, {
|
|
17909
|
+
ok: true,
|
|
17910
|
+
data: {
|
|
17911
|
+
service: resolution.service.clientId,
|
|
17912
|
+
manifestUrl: resolution.manifestUrl,
|
|
17913
|
+
requiresLocalEnv: true,
|
|
17914
|
+
command: resolution.profile.command,
|
|
17915
|
+
profileHome: resolution.profile.profileHome,
|
|
17916
|
+
env: resolution.profile.env
|
|
17917
|
+
}
|
|
17918
|
+
});
|
|
17919
|
+
return;
|
|
17920
|
+
}
|
|
17921
|
+
writeText(cmdCtx.io, `${formatShellExports(resolution.profile)}
|
|
17922
|
+
`);
|
|
17923
|
+
}
|
|
17924
|
+
);
|
|
17925
|
+
function registerIntegrationEnvCommand(parent, runtimeOptions = {}) {
|
|
17926
|
+
registerCliCommand(parent, integrationEnvCommand, runtimeOptions);
|
|
17277
17927
|
}
|
|
17278
17928
|
|
|
17279
17929
|
// src/commands/reminder/_format.ts
|
|
@@ -17375,66 +18025,62 @@ function buildScheduleBody(opts, now = () => Intl.DateTimeFormat().resolvedOptio
|
|
|
17375
18025
|
}
|
|
17376
18026
|
return { body };
|
|
17377
18027
|
}
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
"
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
)
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
).action(async (opts) => {
|
|
17395
|
-
let ctx;
|
|
17396
|
-
try {
|
|
17397
|
-
ctx = loadAgentContext();
|
|
17398
|
-
} catch (err) {
|
|
17399
|
-
if (err instanceof AgentBootstrapError) fail(err.code, err.message);
|
|
17400
|
-
throw err;
|
|
18028
|
+
var reminderScheduleCommand = defineCommand(
|
|
18029
|
+
{
|
|
18030
|
+
name: "schedule",
|
|
18031
|
+
description: "Schedule a reminder that fires at a future time",
|
|
18032
|
+
options: [
|
|
18033
|
+
{ flags: "--title <t>", description: "Short description of what the reminder is about" },
|
|
18034
|
+
{ flags: "--delay-seconds <n>", description: "Preferred for relative times. Fires this many seconds from now (server-computed, timezone-safe)" },
|
|
18035
|
+
{ flags: "--fire-at <iso>", description: "ISO-8601 UTC timestamp, e.g. 2026-04-21T09:00:00Z. Use only for absolute calendar times" },
|
|
18036
|
+
{ flags: "--repeat <rule>", description: "Recurrence rule: every:15m | every:2h | every:1d | daily@09:00 | weekly:mon,fri@09:00" },
|
|
18037
|
+
{ flags: "--channel <ref>", description: "Optional channel to post a receipt message in (e.g. #general, dm:@alice)." },
|
|
18038
|
+
{ flags: "--msg-id <id>", description: "Message id this reminder is anchored to. Required for agent-created reminders." }
|
|
18039
|
+
]
|
|
18040
|
+
},
|
|
18041
|
+
async (ctx, opts) => {
|
|
18042
|
+
if (!opts.title?.trim()) {
|
|
18043
|
+
throw cliError("INVALID_ARG", "--title is required");
|
|
17401
18044
|
}
|
|
17402
18045
|
const built = buildScheduleBody(opts);
|
|
17403
|
-
if (built.error)
|
|
17404
|
-
const
|
|
18046
|
+
if (built.error) throw cliError(built.error.code, built.error.message);
|
|
18047
|
+
const agentContext = ctx.loadAgentContext();
|
|
18048
|
+
const client = ctx.createApiClient(agentContext);
|
|
17405
18049
|
const res = await client.request(
|
|
17406
18050
|
"POST",
|
|
17407
|
-
`/internal/agent/${encodeURIComponent(
|
|
18051
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders`,
|
|
17408
18052
|
built.body
|
|
17409
18053
|
);
|
|
17410
18054
|
if (!res.ok || !res.data?.reminder) {
|
|
17411
18055
|
const code = res.status >= 500 ? "SERVER_5XX" : "SCHEDULE_FAILED";
|
|
17412
|
-
|
|
18056
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17413
18057
|
}
|
|
17414
|
-
|
|
18058
|
+
writeText(
|
|
18059
|
+
ctx.io,
|
|
17415
18060
|
formatReminderScheduled(res.data.reminder, res.data.warning ?? null) + "\n"
|
|
17416
18061
|
);
|
|
17417
|
-
}
|
|
18062
|
+
}
|
|
18063
|
+
);
|
|
18064
|
+
function registerReminderScheduleCommand(parent, runtimeOptions = {}) {
|
|
18065
|
+
registerCliCommand(parent, reminderScheduleCommand, runtimeOptions);
|
|
17418
18066
|
}
|
|
17419
18067
|
|
|
17420
18068
|
// src/commands/reminder/list.ts
|
|
17421
18069
|
var VALID_STATUSES2 = /* @__PURE__ */ new Set(["scheduled", "fired", "canceled"]);
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
"
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
throw err;
|
|
17433
|
-
}
|
|
18070
|
+
var reminderListCommand = defineCommand(
|
|
18071
|
+
{
|
|
18072
|
+
name: "list",
|
|
18073
|
+
description: "List your own reminders (defaults to scheduled and fired)",
|
|
18074
|
+
options: [
|
|
18075
|
+
{ flags: "--all", description: "Include canceled reminders" },
|
|
18076
|
+
{ flags: "--status <s>", description: "Comma-separated statuses (scheduled,fired,canceled). Default: scheduled,fired" }
|
|
18077
|
+
]
|
|
18078
|
+
},
|
|
18079
|
+
async (ctx, opts) => {
|
|
17434
18080
|
const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled,fired";
|
|
17435
18081
|
for (const s of statusRaw.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
17436
18082
|
if (!VALID_STATUSES2.has(s)) {
|
|
17437
|
-
|
|
18083
|
+
throw cliError("INVALID_ARG", `--status entries must be one of ${Array.from(VALID_STATUSES2).join("|")}; got ${s}`);
|
|
17438
18084
|
}
|
|
17439
18085
|
}
|
|
17440
18086
|
const params = new URLSearchParams();
|
|
@@ -17443,23 +18089,27 @@ function registerReminderListCommand(parent) {
|
|
|
17443
18089
|
} else {
|
|
17444
18090
|
params.set("status", statusRaw);
|
|
17445
18091
|
}
|
|
17446
|
-
const
|
|
18092
|
+
const agentContext = ctx.loadAgentContext();
|
|
18093
|
+
const client = ctx.createApiClient(agentContext);
|
|
17447
18094
|
const res = await client.request(
|
|
17448
18095
|
"GET",
|
|
17449
|
-
`/internal/agent/${encodeURIComponent(
|
|
18096
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders?${params.toString()}`
|
|
17450
18097
|
);
|
|
17451
18098
|
if (!res.ok) {
|
|
17452
18099
|
const code = res.status >= 500 ? "SERVER_5XX" : "LIST_FAILED";
|
|
17453
|
-
|
|
18100
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17454
18101
|
}
|
|
17455
|
-
|
|
17456
|
-
}
|
|
18102
|
+
writeText(ctx.io, formatReminderList(res.data?.reminders ?? []) + "\n");
|
|
18103
|
+
}
|
|
18104
|
+
);
|
|
18105
|
+
function registerReminderListCommand(parent, runtimeOptions = {}) {
|
|
18106
|
+
registerCliCommand(parent, reminderListCommand, runtimeOptions);
|
|
17457
18107
|
}
|
|
17458
18108
|
|
|
17459
18109
|
// src/commands/reminder/_resolve.ts
|
|
17460
18110
|
async function resolveReminderId(client, agentId, id, opts) {
|
|
17461
18111
|
const trimmed = id.trim();
|
|
17462
|
-
if (!trimmed)
|
|
18112
|
+
if (!trimmed) throw cliError("INVALID_ARG", "--id is required");
|
|
17463
18113
|
if (trimmed.length >= 32) return trimmed;
|
|
17464
18114
|
const params = new URLSearchParams();
|
|
17465
18115
|
if (opts.all) {
|
|
@@ -17474,47 +18124,49 @@ async function resolveReminderId(client, agentId, id, opts) {
|
|
|
17474
18124
|
);
|
|
17475
18125
|
if (!res.ok) {
|
|
17476
18126
|
const code = res.status >= 500 ? "SERVER_5XX" : opts.failureCode;
|
|
17477
|
-
|
|
18127
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17478
18128
|
}
|
|
17479
18129
|
const matches = (res.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(trimmed));
|
|
17480
18130
|
if (matches.length === 0) {
|
|
17481
18131
|
const scope = opts.all ? "reminder" : `${opts.statuses?.join("/") ?? "active"} reminder`;
|
|
17482
|
-
|
|
18132
|
+
throw cliError("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
|
|
17483
18133
|
}
|
|
17484
18134
|
if (matches.length > 1) {
|
|
17485
|
-
|
|
18135
|
+
throw cliError("AMBIGUOUS", `Ambiguous id prefix '${trimmed}' matches ${matches.length} reminders; pass a longer id.`);
|
|
17486
18136
|
}
|
|
17487
18137
|
return matches[0].reminderId;
|
|
17488
18138
|
}
|
|
17489
18139
|
|
|
17490
18140
|
// src/commands/reminder/cancel.ts
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
throw err;
|
|
17499
|
-
}
|
|
18141
|
+
var reminderCancelCommand = defineCommand(
|
|
18142
|
+
{
|
|
18143
|
+
name: "cancel",
|
|
18144
|
+
description: "Cancel a scheduled reminder by id (full uuid or 8-char prefix)",
|
|
18145
|
+
options: [{ flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" }]
|
|
18146
|
+
},
|
|
18147
|
+
async (ctx, opts) => {
|
|
17500
18148
|
if (!opts.id || opts.id.trim().length === 0) {
|
|
17501
|
-
|
|
18149
|
+
throw cliError("INVALID_ARG", "--id is required");
|
|
17502
18150
|
}
|
|
17503
|
-
const
|
|
17504
|
-
const
|
|
18151
|
+
const agentContext = ctx.loadAgentContext();
|
|
18152
|
+
const client = ctx.createApiClient(agentContext);
|
|
18153
|
+
const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
|
|
17505
18154
|
statuses: ["scheduled", "fired"],
|
|
17506
18155
|
failureCode: "CANCEL_FAILED"
|
|
17507
18156
|
});
|
|
17508
18157
|
const res = await client.request(
|
|
17509
18158
|
"DELETE",
|
|
17510
|
-
`/internal/agent/${encodeURIComponent(
|
|
18159
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}`
|
|
17511
18160
|
);
|
|
17512
18161
|
if (!res.ok || !res.data?.reminder) {
|
|
17513
18162
|
const code = res.status >= 500 ? "SERVER_5XX" : "CANCEL_FAILED";
|
|
17514
|
-
|
|
18163
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17515
18164
|
}
|
|
17516
|
-
|
|
17517
|
-
}
|
|
18165
|
+
writeText(ctx.io, formatReminderCanceled(res.data.reminder) + "\n");
|
|
18166
|
+
}
|
|
18167
|
+
);
|
|
18168
|
+
function registerReminderCancelCommand(parent, runtimeOptions = {}) {
|
|
18169
|
+
registerCliCommand(parent, reminderCancelCommand, runtimeOptions);
|
|
17518
18170
|
}
|
|
17519
18171
|
|
|
17520
18172
|
// src/commands/reminder/_duration.ts
|
|
@@ -17531,57 +18183,75 @@ function parseDurationSeconds(input) {
|
|
|
17531
18183
|
}
|
|
17532
18184
|
|
|
17533
18185
|
// src/commands/reminder/snooze.ts
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
18186
|
+
var reminderSnoozeCommand = defineCommand(
|
|
18187
|
+
{
|
|
18188
|
+
name: "snooze",
|
|
18189
|
+
description: "Snooze a scheduled or fired reminder",
|
|
18190
|
+
options: [
|
|
18191
|
+
{ flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" },
|
|
18192
|
+
{ flags: "--by <duration>", description: "Snooze duration, e.g. 30m, 2h, 1d" }
|
|
18193
|
+
]
|
|
18194
|
+
},
|
|
18195
|
+
async (ctx, opts) => {
|
|
18196
|
+
if (!opts.id?.trim()) {
|
|
18197
|
+
throw cliError("INVALID_ARG", "--id is required");
|
|
18198
|
+
}
|
|
18199
|
+
if (!opts.by?.trim()) {
|
|
18200
|
+
throw cliError("INVALID_ARG", "--by is required");
|
|
17542
18201
|
}
|
|
17543
18202
|
const delaySeconds = parseDurationSeconds(opts.by);
|
|
17544
18203
|
if (delaySeconds == null) {
|
|
17545
|
-
|
|
18204
|
+
throw cliError("INVALID_ARG", "--by must be a positive duration like 30m, 2h, or 1d");
|
|
17546
18205
|
}
|
|
17547
|
-
const
|
|
17548
|
-
const
|
|
18206
|
+
const agentContext = ctx.loadAgentContext();
|
|
18207
|
+
const client = ctx.createApiClient(agentContext);
|
|
18208
|
+
const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
|
|
17549
18209
|
statuses: ["scheduled", "fired"],
|
|
17550
18210
|
failureCode: "SNOOZE_FAILED"
|
|
17551
18211
|
});
|
|
17552
18212
|
const res = await client.request(
|
|
17553
18213
|
"POST",
|
|
17554
|
-
`/internal/agent/${encodeURIComponent(
|
|
18214
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
|
|
17555
18215
|
{ delaySeconds }
|
|
17556
18216
|
);
|
|
17557
18217
|
if (!res.ok || !res.data?.reminder) {
|
|
17558
18218
|
const code = res.status >= 500 ? "SERVER_5XX" : "SNOOZE_FAILED";
|
|
17559
|
-
|
|
18219
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17560
18220
|
}
|
|
17561
|
-
|
|
17562
|
-
}
|
|
18221
|
+
writeText(ctx.io, formatReminderSnoozed(res.data.reminder) + "\n");
|
|
18222
|
+
}
|
|
18223
|
+
);
|
|
18224
|
+
function registerReminderSnoozeCommand(parent, runtimeOptions = {}) {
|
|
18225
|
+
registerCliCommand(parent, reminderSnoozeCommand, runtimeOptions);
|
|
17563
18226
|
}
|
|
17564
18227
|
|
|
17565
18228
|
// src/commands/reminder/update.ts
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17570
|
-
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
|
|
18229
|
+
var reminderUpdateCommand = defineCommand(
|
|
18230
|
+
{
|
|
18231
|
+
name: "update",
|
|
18232
|
+
description: "Update one field on a scheduled reminder",
|
|
18233
|
+
options: [
|
|
18234
|
+
{ flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" },
|
|
18235
|
+
{ flags: "--fire-at <iso>", description: "New absolute next fire time" },
|
|
18236
|
+
{ flags: "--in <duration>", description: "New relative next fire time, e.g. 30m, 2h" },
|
|
18237
|
+
{ flags: "--cadence <rule>", description: "New recurrence rule: every:15m | daily@09:00 | weekly:mon,fri@09:00" },
|
|
18238
|
+
{ flags: "--title <text>", description: "New reminder title" }
|
|
18239
|
+
]
|
|
18240
|
+
},
|
|
18241
|
+
async (ctx, opts) => {
|
|
18242
|
+
if (!opts.id?.trim()) {
|
|
18243
|
+
throw cliError("INVALID_ARG", "--id is required");
|
|
17574
18244
|
}
|
|
17575
18245
|
const mutationCount = [opts.fireAt, opts.in, opts.cadence, opts.title].filter((x) => x !== void 0 && x !== null).length;
|
|
17576
18246
|
if (mutationCount !== 1) {
|
|
17577
|
-
|
|
18247
|
+
throw cliError("INVALID_ARG", "Pass exactly one of --fire-at, --in, --cadence, or --title");
|
|
17578
18248
|
}
|
|
17579
18249
|
const body = {};
|
|
17580
18250
|
if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
|
|
17581
18251
|
if (opts.in !== void 0) {
|
|
17582
18252
|
const delaySeconds = parseDurationSeconds(opts.in);
|
|
17583
18253
|
if (delaySeconds == null) {
|
|
17584
|
-
|
|
18254
|
+
throw cliError("INVALID_ARG", "--in must be a positive duration like 30m, 2h, or 1d");
|
|
17585
18255
|
}
|
|
17586
18256
|
body.delaySeconds = delaySeconds;
|
|
17587
18257
|
}
|
|
@@ -17590,49 +18260,58 @@ function registerReminderUpdateCommand(parent) {
|
|
|
17590
18260
|
body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
17591
18261
|
}
|
|
17592
18262
|
if (opts.title !== void 0) body.title = opts.title;
|
|
17593
|
-
const
|
|
17594
|
-
const
|
|
18263
|
+
const agentContext = ctx.loadAgentContext();
|
|
18264
|
+
const client = ctx.createApiClient(agentContext);
|
|
18265
|
+
const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
|
|
17595
18266
|
all: true,
|
|
17596
18267
|
failureCode: "UPDATE_FAILED"
|
|
17597
18268
|
});
|
|
17598
18269
|
const res = await client.request(
|
|
17599
18270
|
"PATCH",
|
|
17600
|
-
`/internal/agent/${encodeURIComponent(
|
|
18271
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}`,
|
|
17601
18272
|
body
|
|
17602
18273
|
);
|
|
17603
18274
|
if (!res.ok || !res.data?.reminder) {
|
|
17604
18275
|
const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
|
|
17605
|
-
|
|
18276
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17606
18277
|
}
|
|
17607
|
-
|
|
17608
|
-
}
|
|
18278
|
+
writeText(ctx.io, formatReminderUpdated(res.data.reminder, res.data.warning ?? null) + "\n");
|
|
18279
|
+
}
|
|
18280
|
+
);
|
|
18281
|
+
function registerReminderUpdateCommand(parent, runtimeOptions = {}) {
|
|
18282
|
+
registerCliCommand(parent, reminderUpdateCommand, runtimeOptions);
|
|
17609
18283
|
}
|
|
17610
18284
|
|
|
17611
18285
|
// src/commands/reminder/log.ts
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
18286
|
+
var reminderLogCommand = defineCommand(
|
|
18287
|
+
{
|
|
18288
|
+
name: "log",
|
|
18289
|
+
description: "Show lifecycle events for one reminder",
|
|
18290
|
+
options: [{ flags: "--id <id>", description: "Reminder id (full uuid or short prefix)" }]
|
|
18291
|
+
},
|
|
18292
|
+
async (ctx, opts) => {
|
|
18293
|
+
if (!opts.id?.trim()) {
|
|
18294
|
+
throw cliError("INVALID_ARG", "--id is required");
|
|
17620
18295
|
}
|
|
17621
|
-
const
|
|
17622
|
-
const
|
|
18296
|
+
const agentContext = ctx.loadAgentContext();
|
|
18297
|
+
const client = ctx.createApiClient(agentContext);
|
|
18298
|
+
const fullId = await resolveReminderId(client, agentContext.agentId, opts.id, {
|
|
17623
18299
|
all: true,
|
|
17624
18300
|
failureCode: "LOG_FAILED"
|
|
17625
18301
|
});
|
|
17626
18302
|
const res = await client.request(
|
|
17627
18303
|
"GET",
|
|
17628
|
-
`/internal/agent/${encodeURIComponent(
|
|
18304
|
+
`/internal/agent/${encodeURIComponent(agentContext.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
|
|
17629
18305
|
);
|
|
17630
18306
|
if (!res.ok || !res.data?.events) {
|
|
17631
18307
|
const code = res.status >= 500 ? "SERVER_5XX" : "LOG_FAILED";
|
|
17632
|
-
|
|
18308
|
+
throw cliError(code, res.error ?? `HTTP ${res.status}`);
|
|
17633
18309
|
}
|
|
17634
|
-
|
|
17635
|
-
}
|
|
18310
|
+
writeText(ctx.io, formatReminderLog(res.data.events) + "\n");
|
|
18311
|
+
}
|
|
18312
|
+
);
|
|
18313
|
+
function registerReminderLogCommand(parent, runtimeOptions = {}) {
|
|
18314
|
+
registerCliCommand(parent, reminderLogCommand, runtimeOptions);
|
|
17636
18315
|
}
|
|
17637
18316
|
|
|
17638
18317
|
// src/index.ts
|
|
@@ -17685,6 +18364,7 @@ registerProfileUpdateCommand(profileCmd);
|
|
|
17685
18364
|
var integrationCmd = program.command("integration").description("Third-party service integration operations");
|
|
17686
18365
|
registerIntegrationListCommand(integrationCmd);
|
|
17687
18366
|
registerIntegrationLoginCommand(integrationCmd);
|
|
18367
|
+
registerIntegrationEnvCommand(integrationCmd);
|
|
17688
18368
|
var reminderCmd = program.command("reminder").description("Reminder operations");
|
|
17689
18369
|
registerReminderScheduleCommand(reminderCmd);
|
|
17690
18370
|
registerReminderListCommand(reminderCmd);
|