@slock-ai/daemon 0.53.2 → 0.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -19,13 +19,47 @@ var CliExit = class extends Error {
19
19
  function emit(payload) {
20
20
  process.stdout.write(JSON.stringify(payload) + "\n");
21
21
  }
22
- function fail(code, message, exitCode = 1) {
23
- process.stderr.write(JSON.stringify({ ok: false, code, message }) + "\n");
24
- throw new CliExit(exitCode);
22
+ function fail(code, message, options) {
23
+ const body = { ok: false, code, message };
24
+ if (options?.suggestedNextAction) {
25
+ body.suggested_next_action = options.suggestedNextAction;
26
+ }
27
+ process.stderr.write(JSON.stringify(body) + "\n");
28
+ throw new CliExit(options?.exitCode ?? 1);
29
+ }
30
+
31
+ // src/version.ts
32
+ import { readFileSync } from "fs";
33
+ var FALLBACK_VERSION = "0.0.0";
34
+ function readVersionFrom(candidate) {
35
+ try {
36
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
37
+ return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function readCliVersion(baseUrl = import.meta.url) {
43
+ return (
44
+ // Built package and daemon-bundled CLI: dist/package.json travels with dist/index.js.
45
+ readVersionFrom(new URL("./package.json", baseUrl)) ?? readVersionFrom(new URL("../package.json", baseUrl)) ?? FALLBACK_VERSION
46
+ );
25
47
  }
26
48
 
27
49
  // src/auth/env.ts
28
50
  import fs from "fs";
51
+ import os from "os";
52
+ import path from "path";
53
+ var RAW_AGENT_ENV_KEYS = [
54
+ "SLOCK_AGENT_ID",
55
+ "SLOCK_SERVER_URL",
56
+ "SLOCK_SERVER_ID",
57
+ "SLOCK_AGENT_PROXY_URL",
58
+ "SLOCK_AGENT_PROXY_TOKEN",
59
+ "SLOCK_AGENT_PROXY_TOKEN_FILE",
60
+ "SLOCK_AGENT_TOKEN_FILE",
61
+ "SLOCK_AGENT_TOKEN"
62
+ ];
29
63
  var AgentBootstrapError = class extends Error {
30
64
  constructor(code, message) {
31
65
  super(message);
@@ -33,6 +67,47 @@ var AgentBootstrapError = class extends Error {
33
67
  this.name = "AgentBootstrapError";
34
68
  }
35
69
  };
70
+ function resolveProfileDir(slug, env = process.env) {
71
+ if (env.SLOCK_PROFILE_DIR) {
72
+ return env.SLOCK_PROFILE_DIR;
73
+ }
74
+ if (env.SLOCK_HOME) {
75
+ return path.join(env.SLOCK_HOME, "profiles", slug);
76
+ }
77
+ const home = env.HOME ?? os.homedir();
78
+ return path.join(home, ".slock", "profiles", slug);
79
+ }
80
+ function resolveProfileCredentialPath(slug, env) {
81
+ return path.join(resolveProfileDir(slug, env), "credential.json");
82
+ }
83
+ function readProfileCredential(slug, env) {
84
+ const filePath = resolveProfileCredentialPath(slug, env);
85
+ let raw;
86
+ try {
87
+ raw = fs.readFileSync(filePath, "utf-8");
88
+ } catch (err) {
89
+ throw new AgentBootstrapError(
90
+ "PROFILE_FILE_UNREADABLE",
91
+ `SLOCK_PROFILE=${slug} resolved to ${filePath}, which could not be read: ${err.message}. Run \`slock agent login\` to mint a credential.`
92
+ );
93
+ }
94
+ let parsed;
95
+ try {
96
+ parsed = JSON.parse(raw);
97
+ } catch (err) {
98
+ throw new AgentBootstrapError(
99
+ "PROFILE_FILE_INVALID",
100
+ `${filePath} is not valid JSON: ${err.message}`
101
+ );
102
+ }
103
+ if (!parsed || typeof parsed !== "object" || typeof parsed.apiKey !== "string" || typeof parsed.agentId !== "string" || typeof parsed.serverUrl !== "string") {
104
+ throw new AgentBootstrapError(
105
+ "PROFILE_FILE_INVALID",
106
+ `${filePath} is missing required fields (apiKey, agentId, serverUrl).`
107
+ );
108
+ }
109
+ return { filePath, data: parsed };
110
+ }
36
111
  function readTokenFromFile(filePath) {
37
112
  let raw;
38
113
  try {
@@ -53,10 +128,32 @@ function readTokenFromFile(filePath) {
53
128
  return token;
54
129
  }
55
130
  function loadAgentContext(env = process.env) {
131
+ const activeCapabilities = env.SLOCK_AGENT_ACTIVE_CAPABILITIES ? env.SLOCK_AGENT_ACTIVE_CAPABILITIES.split(",").map((cap) => cap.trim()).filter(Boolean) : null;
132
+ const profileSlug = env.SLOCK_PROFILE;
133
+ if (profileSlug) {
134
+ const shadowed = RAW_AGENT_ENV_KEYS.filter((k) => env[k]);
135
+ if (shadowed.length > 0) {
136
+ process.stderr.write(
137
+ `slock: SLOCK_PROFILE=${profileSlug} active; ignoring ${shadowed.join(", ")} from env.
138
+ `
139
+ );
140
+ }
141
+ const { filePath, data } = readProfileCredential(profileSlug, env);
142
+ return {
143
+ agentId: data.agentId,
144
+ serverUrl: data.serverUrl,
145
+ serverId: data.serverId ?? null,
146
+ token: data.apiKey,
147
+ clientMode: "self-hosted-runner",
148
+ secretSource: "profile-credential-file",
149
+ activeCapabilities,
150
+ profileSlug,
151
+ profileCredentialPath: filePath
152
+ };
153
+ }
56
154
  const agentId = env.SLOCK_AGENT_ID;
57
155
  const serverUrl = env.SLOCK_SERVER_URL;
58
156
  const serverId = env.SLOCK_SERVER_ID ?? null;
59
- const activeCapabilities = env.SLOCK_AGENT_ACTIVE_CAPABILITIES ? env.SLOCK_AGENT_ACTIVE_CAPABILITIES.split(",").map((cap) => cap.trim()).filter(Boolean) : null;
60
157
  if (!agentId) throw new AgentBootstrapError("MISSING_AGENT_ID", "SLOCK_AGENT_ID is required");
61
158
  if (!serverUrl) throw new AgentBootstrapError("MISSING_SERVER_URL", "SLOCK_SERVER_URL is required");
62
159
  const agentProxyUrl = env.SLOCK_AGENT_PROXY_URL;
@@ -89,18 +186,6 @@ function loadAgentContext(env = process.env) {
89
186
  activeCapabilities
90
187
  };
91
188
  }
92
- const agentCredentialFile = env.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
93
- if (agentCredentialFile) {
94
- return {
95
- agentId,
96
- serverUrl,
97
- serverId,
98
- token: readTokenFromFile(agentCredentialFile),
99
- clientMode: "self-hosted-runner",
100
- secretSource: "agent-credential-file",
101
- activeCapabilities
102
- };
103
- }
104
189
  const tokenFile = env.SLOCK_AGENT_TOKEN_FILE;
105
190
  if (tokenFile) {
106
191
  return {
@@ -127,7 +212,7 @@ function loadAgentContext(env = process.env) {
127
212
  }
128
213
  throw new AgentBootstrapError(
129
214
  "MISSING_TOKEN",
130
- "Neither SLOCK_AGENT_PROXY_TOKEN_FILE, SLOCK_AGENT_PROXY_TOKEN, SLOCK_AGENT_CREDENTIAL_KEY_FILE, SLOCK_AGENT_TOKEN_FILE nor SLOCK_AGENT_TOKEN is set. The daemon should inject one of these when spawning the agent process."
215
+ "Neither SLOCK_AGENT_PROXY_TOKEN_FILE, SLOCK_AGENT_PROXY_TOKEN, SLOCK_AGENT_TOKEN_FILE nor SLOCK_AGENT_TOKEN is set. The daemon should inject one of these when spawning the agent process."
131
216
  );
132
217
  }
133
218
 
@@ -148,12 +233,348 @@ function registerWhoamiCommand(parent) {
148
233
  serverUrl: ctx.serverUrl,
149
234
  serverId: ctx.serverId,
150
235
  clientMode: ctx.clientMode,
151
- secretSource: ctx.secretSource
236
+ secretSource: ctx.secretSource,
237
+ ...ctx.profileSlug ? { profileSlug: ctx.profileSlug } : {},
238
+ ...ctx.profileCredentialPath ? { profileCredentialPath: ctx.profileCredentialPath } : {}
152
239
  }
153
240
  });
154
241
  });
155
242
  }
156
243
 
244
+ // src/commands/agent/list.ts
245
+ import { fetch as undiciFetch } from "undici";
246
+
247
+ // src/agentLogin/deviceAuthClient.ts
248
+ import { fetch as fetch2 } from "undici";
249
+ var DeviceCodeLoginError = class extends Error {
250
+ constructor(code, message) {
251
+ super(message);
252
+ this.code = code;
253
+ this.name = "DeviceCodeLoginError";
254
+ }
255
+ };
256
+ var ACTIONABLE_ERROR_MESSAGES = {
257
+ device_login_disabled: "Device login is not enabled on this Slock server. Ask an admin to set SLOCK_DEVICE_LOGIN_ENABLED=true.",
258
+ device_code_required: "Internal CLI bug: device_code was missing from the poll request.",
259
+ user_code_required: "Internal CLI bug: user_code was missing from the approve request.",
260
+ authorization_pending: "Still waiting for you to approve the login on the web page.",
261
+ expired_token: "The login code expired. Run `slock agent login` again to start a new flow.",
262
+ access_denied: "You denied the login request in the web approval page.",
263
+ device_code_consumed: "This login code has already been used. Run `slock agent login` again.",
264
+ device_code_invalid: "Unknown / malformed device code. Run `slock agent login` again to start a fresh flow."
265
+ };
266
+ function describeDeviceCodeLoginError(code) {
267
+ return ACTIONABLE_ERROR_MESSAGES[code] ?? `Device login failed (code: ${code}).`;
268
+ }
269
+ async function runDeviceCodeLogin(options) {
270
+ const httpFetch = options.fetchImpl ?? fetch2;
271
+ const base = options.serverUrl.replace(/\/+$/, "");
272
+ const authorizeRes = await httpFetch(`${base}/api/auth/device/authorize`, {
273
+ method: "POST",
274
+ headers: { "content-type": "application/json" },
275
+ body: JSON.stringify({
276
+ ...options.clientName ? { clientName: options.clientName } : {}
277
+ })
278
+ });
279
+ if (!authorizeRes.ok) {
280
+ const payload = await safeJson(authorizeRes);
281
+ throw new DeviceCodeLoginError(
282
+ typeof payload?.code === "string" ? payload.code : "authorize_failed",
283
+ describeDeviceCodeLoginError(typeof payload?.code === "string" ? payload.code : "authorize_failed")
284
+ );
285
+ }
286
+ const authorizeBody = await authorizeRes.json();
287
+ if (!authorizeBody.deviceCode || !authorizeBody.userCode || !authorizeBody.verificationUri) {
288
+ throw new DeviceCodeLoginError(
289
+ "authorize_response_invalid",
290
+ "Server's authorize response was missing deviceCode / userCode / verificationUri."
291
+ );
292
+ }
293
+ const verificationUri = authorizeBody.verificationUri.startsWith("http") ? authorizeBody.verificationUri : `${base}${authorizeBody.verificationUri}`;
294
+ await options.onUserAction({
295
+ verificationUri,
296
+ userCode: authorizeBody.userCode,
297
+ expiresInSeconds: authorizeBody.expiresIn ?? 0
298
+ });
299
+ const serverIntervalMs = (authorizeBody.interval ?? 5) * 1e3;
300
+ const pollIntervalMs = options.pollIntervalOverrideMs ?? serverIntervalMs;
301
+ const deadlineMs = Date.now() + Math.max(1, authorizeBody.expiresIn ?? 600) * 1e3;
302
+ while (Date.now() < deadlineMs) {
303
+ const tokenRes = await httpFetch(`${base}/api/auth/device/token`, {
304
+ method: "POST",
305
+ headers: { "content-type": "application/json" },
306
+ body: JSON.stringify({ deviceCode: authorizeBody.deviceCode })
307
+ });
308
+ if (tokenRes.ok) {
309
+ const tokenBody = await tokenRes.json();
310
+ if (!tokenBody.accessToken || !tokenBody.refreshToken || !tokenBody.userId) {
311
+ throw new DeviceCodeLoginError(
312
+ "token_response_invalid",
313
+ "Server's token response was missing accessToken / refreshToken / userId."
314
+ );
315
+ }
316
+ return {
317
+ accessToken: tokenBody.accessToken,
318
+ refreshToken: tokenBody.refreshToken,
319
+ userId: tokenBody.userId
320
+ };
321
+ }
322
+ const tokenError = await safeJson(tokenRes);
323
+ const code = typeof tokenError?.code === "string" ? tokenError.code : "token_failed";
324
+ if (code === "authorization_pending") {
325
+ await delay(pollIntervalMs);
326
+ continue;
327
+ }
328
+ throw new DeviceCodeLoginError(code, describeDeviceCodeLoginError(code));
329
+ }
330
+ throw new DeviceCodeLoginError(
331
+ "expired_token",
332
+ describeDeviceCodeLoginError("expired_token")
333
+ );
334
+ }
335
+ async function safeJson(res) {
336
+ try {
337
+ return await res.json();
338
+ } catch {
339
+ return null;
340
+ }
341
+ }
342
+ function delay(ms) {
343
+ if (ms <= 0) return Promise.resolve();
344
+ return new Promise((resolve) => setTimeout(resolve, ms));
345
+ }
346
+
347
+ // src/commands/agent/list.ts
348
+ function describeListResult(reason, serverUrl) {
349
+ switch (reason) {
350
+ case "ok":
351
+ return `Ask the user which agent to bind to this machine, then run \`slock agent login --server ${serverUrl} --agent <id>\` with the selected agent id.`;
352
+ case "no_manageable_server":
353
+ return "You are logged in but don't have `manageAgents` on any server you're a member of. Ask a server owner or admin to grant the capability, then rerun `slock agent list`.";
354
+ case "no_agents_on_manageable_servers":
355
+ return "You have `manageAgents` on at least one server, but no agents exist on those servers yet. Ask the user to create an agent first (via web UI), then rerun `slock agent list`.";
356
+ }
357
+ }
358
+ function registerAgentListCommand(parent) {
359
+ parent.command("list").description(
360
+ "List Slock agents the user can mint credentials for (after a device-code login)."
361
+ ).requiredOption("--server <url>", "Slock server base URL, e.g. https://slock.example.com").option("--client-name <label>", "Human-readable label shown on the web approval page").action(async (options) => {
362
+ let userSession;
363
+ try {
364
+ userSession = await runDeviceCodeLogin({
365
+ serverUrl: options.server,
366
+ ...options.clientName ? { clientName: options.clientName } : {},
367
+ onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
368
+ process.stderr.write(
369
+ `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
370
+ `
371
+ );
372
+ }
373
+ });
374
+ } catch (err) {
375
+ if (err instanceof DeviceCodeLoginError) {
376
+ fail(err.code, err.message);
377
+ }
378
+ throw err;
379
+ }
380
+ const res = await undiciFetch(
381
+ `${options.server.replace(/\/+$/, "")}/api/agents/manageable`,
382
+ {
383
+ method: "GET",
384
+ headers: {
385
+ "content-type": "application/json",
386
+ authorization: `Bearer ${userSession.accessToken}`
387
+ }
388
+ }
389
+ );
390
+ if (!res.ok) {
391
+ let body = null;
392
+ try {
393
+ body = await res.json();
394
+ } catch {
395
+ }
396
+ fail(
397
+ body?.code ?? `list_failed_${res.status}`,
398
+ body?.error ?? `Failed to list manageable agents (status ${res.status}).`
399
+ );
400
+ }
401
+ const payload = await res.json();
402
+ const agents = payload.data?.agents ?? [];
403
+ const reason = payload.data?.reason ?? (agents.length > 0 ? "ok" : "no_agents_on_manageable_servers");
404
+ const suggestedNextAction = describeListResult(reason, options.server);
405
+ emit({
406
+ ok: true,
407
+ data: {
408
+ agents,
409
+ reason,
410
+ ...typeof payload.data?.manageable_server_count === "number" ? { manageable_server_count: payload.data.manageable_server_count } : {},
411
+ suggested_next_action: suggestedNextAction
412
+ }
413
+ });
414
+ });
415
+ }
416
+
417
+ // src/commands/agent/login.ts
418
+ import { mkdir, stat, writeFile } from "fs/promises";
419
+ import path2 from "path";
420
+ import { fetch as undiciFetch2 } from "undici";
421
+ function registerAgentLoginCommand(parent) {
422
+ parent.command("login").description(
423
+ "Sign this CLI in as a specific Slock agent via the device-code login grant."
424
+ ).requiredOption("--server <url>", "Slock server base URL, e.g. https://slock.example.com").requiredOption("--agent <agentId>", "Agent id to log in as").option("--client-name <label>", "Human-readable label shown on the web approval page").option("--profile-slug <slug>", "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use.").option("--profile-dir <path>", "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)").action(async (options) => {
425
+ const invalidShape = describeInvalidAgentIdShape(options.agent);
426
+ if (invalidShape) {
427
+ fail("INVALID_AGENT_ID", invalidShape, {
428
+ suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
429
+ });
430
+ }
431
+ const profileSlug = options.profileSlug ?? options.agent;
432
+ const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
433
+ const credentialPath = path2.join(profileDir, "credential.json");
434
+ if (await profileFileExists(credentialPath)) {
435
+ fail(
436
+ "PROFILE_ALREADY_EXISTS",
437
+ `Profile '${profileSlug}' already has a credential at ${credentialPath}.`,
438
+ {
439
+ suggestedNextAction: `Use a different \`--profile-slug <slug>\` to mint a coexistent credential, OR manually delete ${credentialPath} and rerun login. Note: the existing sk_agent_* on the server is NOT revoked by deleting the local file \u2014 it remains valid until it expires or is explicitly revoked from the agent settings UI.`
440
+ }
441
+ );
442
+ }
443
+ let userSession;
444
+ try {
445
+ userSession = await runDeviceCodeLogin({
446
+ serverUrl: options.server,
447
+ ...options.clientName ? { clientName: options.clientName } : {},
448
+ onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
449
+ process.stderr.write(
450
+ `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
451
+ `
452
+ );
453
+ }
454
+ });
455
+ } catch (err) {
456
+ if (err instanceof DeviceCodeLoginError) {
457
+ fail(err.code, err.message);
458
+ }
459
+ throw err;
460
+ }
461
+ const mintRes = await undiciFetch2(
462
+ `${options.server.replace(/\/+$/, "")}/api/agents/${encodeURIComponent(options.agent)}/credentials`,
463
+ {
464
+ method: "POST",
465
+ headers: {
466
+ "content-type": "application/json",
467
+ authorization: `Bearer ${userSession.accessToken}`
468
+ },
469
+ body: JSON.stringify({})
470
+ }
471
+ );
472
+ if (!mintRes.ok) {
473
+ const body = await safeJson2(mintRes);
474
+ const code = body?.code ?? `mint_failed_${mintRes.status}`;
475
+ const detail = describeMintError(code, options.server);
476
+ fail(
477
+ code,
478
+ detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
479
+ detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
480
+ );
481
+ }
482
+ const minted = await mintRes.json();
483
+ if (!minted.apiKey || !minted.agentId || !minted.serverId) {
484
+ fail(
485
+ "mint_response_invalid",
486
+ "Server mint response was missing apiKey / agentId / serverId."
487
+ );
488
+ }
489
+ await mkdir(profileDir, { recursive: true, mode: 448 });
490
+ await writeFile(
491
+ credentialPath,
492
+ JSON.stringify(
493
+ {
494
+ schemaVersion: 1,
495
+ serverUrl: options.server,
496
+ agentId: minted.agentId,
497
+ agentName: minted.agentName,
498
+ serverId: minted.serverId,
499
+ credentialId: minted.credentialId,
500
+ scopes: minted.scopes,
501
+ apiKey: minted.apiKey,
502
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
503
+ },
504
+ null,
505
+ 2
506
+ ) + "\n",
507
+ { mode: 384 }
508
+ );
509
+ emit({
510
+ ok: true,
511
+ data: {
512
+ agentId: minted.agentId,
513
+ agentName: minted.agentName,
514
+ serverId: minted.serverId,
515
+ credentialId: minted.credentialId,
516
+ scopes: minted.scopes,
517
+ profileSlug,
518
+ credentialPath
519
+ }
520
+ });
521
+ });
522
+ }
523
+ async function safeJson2(res) {
524
+ try {
525
+ return await res.json();
526
+ } catch {
527
+ return null;
528
+ }
529
+ }
530
+ async function profileFileExists(filePath) {
531
+ try {
532
+ await stat(filePath);
533
+ return true;
534
+ } catch {
535
+ return false;
536
+ }
537
+ }
538
+ function describeInvalidAgentIdShape(input) {
539
+ const trimmed = input.trim();
540
+ if (trimmed.length === 0) {
541
+ return "--agent must not be empty.";
542
+ }
543
+ if (trimmed.startsWith("@")) {
544
+ return "--agent expects an agent id (an opaque server-issued identifier), not an @handle.";
545
+ }
546
+ if (trimmed.startsWith("#")) {
547
+ return "--agent expects an agent id, not a #channel name.";
548
+ }
549
+ if (/^https?:\/\//i.test(trimmed) || trimmed.includes("/")) {
550
+ return "--agent expects an agent id, not a URL or path.";
551
+ }
552
+ return null;
553
+ }
554
+ function describeMintError(code, serverUrl) {
555
+ switch (code) {
556
+ case "device_login_disabled":
557
+ return { message: describeDeviceCodeLoginError(code) ?? code };
558
+ case "agent_missing":
559
+ return {
560
+ message: "Agent id is not known on this server, or the user you approved with isn't a member of the agent's server.",
561
+ suggestedNextAction: `Run \`slock agent list --server ${serverUrl}\` to see manageable agents, then rerun login with --agent <id>.`
562
+ };
563
+ case "insufficient_role":
564
+ return {
565
+ message: "The user you approved with isn't a server owner or admin on the agent's server, so they can't mint agent credentials.",
566
+ suggestedNextAction: "Ask a server owner or admin to grant you the `manageAgents` capability on this server, then rerun login."
567
+ };
568
+ case "scopes_invalid":
569
+ case "scopes_empty":
570
+ case "name_invalid":
571
+ return {
572
+ message: "Invalid request body for the agent credential mint. Re-run with default flags."
573
+ };
574
+ }
575
+ return void 0;
576
+ }
577
+
157
578
  // ../shared/src/tracing/index.ts
158
579
  var DEFAULT_TRACE_FLAGS = "00";
159
580
  var TRACE_ID_HEX_LENGTH = 32;
@@ -1007,10 +1428,10 @@ function mergeDefs(...defs) {
1007
1428
  function cloneDef(schema) {
1008
1429
  return mergeDefs(schema._zod.def);
1009
1430
  }
1010
- function getElementAtPath(obj, path2) {
1011
- if (!path2)
1431
+ function getElementAtPath(obj, path4) {
1432
+ if (!path4)
1012
1433
  return obj;
1013
- return path2.reduce((acc, key) => acc?.[key], obj);
1434
+ return path4.reduce((acc, key) => acc?.[key], obj);
1014
1435
  }
1015
1436
  function promiseAllObject(promisesObj) {
1016
1437
  const keys = Object.keys(promisesObj);
@@ -1393,11 +1814,11 @@ function aborted(x, startIndex = 0) {
1393
1814
  }
1394
1815
  return false;
1395
1816
  }
1396
- function prefixIssues(path2, issues) {
1817
+ function prefixIssues(path4, issues) {
1397
1818
  return issues.map((iss) => {
1398
1819
  var _a2;
1399
1820
  (_a2 = iss).path ?? (_a2.path = []);
1400
- iss.path.unshift(path2);
1821
+ iss.path.unshift(path4);
1401
1822
  return iss;
1402
1823
  });
1403
1824
  }
@@ -1580,7 +2001,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
1580
2001
  }
1581
2002
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
1582
2003
  const result = { errors: [] };
1583
- const processError = (error49, path2 = []) => {
2004
+ const processError = (error49, path4 = []) => {
1584
2005
  var _a2, _b;
1585
2006
  for (const issue2 of error49.issues) {
1586
2007
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1590,7 +2011,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1590
2011
  } else if (issue2.code === "invalid_element") {
1591
2012
  processError({ issues: issue2.issues }, issue2.path);
1592
2013
  } else {
1593
- const fullpath = [...path2, ...issue2.path];
2014
+ const fullpath = [...path4, ...issue2.path];
1594
2015
  if (fullpath.length === 0) {
1595
2016
  result.errors.push(mapper(issue2));
1596
2017
  continue;
@@ -1622,8 +2043,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1622
2043
  }
1623
2044
  function toDotPath(_path) {
1624
2045
  const segs = [];
1625
- const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1626
- for (const seg of path2) {
2046
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2047
+ for (const seg of path4) {
1627
2048
  if (typeof seg === "number")
1628
2049
  segs.push(`[${seg}]`);
1629
2050
  else if (typeof seg === "symbol")
@@ -13600,13 +14021,13 @@ function resolveRef(ref, ctx) {
13600
14021
  if (!ref.startsWith("#")) {
13601
14022
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
13602
14023
  }
13603
- const path2 = ref.slice(1).split("/").filter(Boolean);
13604
- if (path2.length === 0) {
14024
+ const path4 = ref.slice(1).split("/").filter(Boolean);
14025
+ if (path4.length === 0) {
13605
14026
  return ctx.rootSchema;
13606
14027
  }
13607
14028
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
13608
- if (path2[0] === defsKey) {
13609
- const key = path2[1];
14029
+ if (path4[0] === defsKey) {
14030
+ const key = path4[1];
13610
14031
  if (!key || !ctx.defs[key]) {
13611
14032
  throw new Error(`Reference not found: ${ref}`);
13612
14033
  }
@@ -14038,13 +14459,16 @@ var agentCreateOperationSchema = external_exports.object({
14038
14459
  name: external_exports.string().trim().min(1).max(60),
14039
14460
  description: external_exports.string().trim().max(500).optional(),
14040
14461
  /**
14041
- * Agent can only suggest semantic intent (name + description). Technical
14042
- * configuration (which computer / runtime / model / reasoning effort) is
14043
- * a user prerogative the human picks those in the create dialog when
14044
- * they click "Create Agent" on the card. Per stdrc 2026-05-10
14045
- * #proj-approval msg=ae4ecedd: "为什么 computer、runtime 和 model 还是
14046
- * 帮用户选了?" — don't let the agent prefill those.
14462
+ * Optional computer placement contract. Agents may only set this when the
14463
+ * human request is explicitly computer-bound; server prepare resolves the
14464
+ * name/UUID and stores the UUID-only form. `suggestedComputer` preselects
14465
+ * the dialog when available; `requiredComputer` prevents silent fallback to
14466
+ * any other computer.
14467
+ *
14468
+ * Runtime / model / reasoning effort remain human-picked technical fields.
14047
14469
  */
14470
+ suggestedComputer: idOrHandleSchema.optional(),
14471
+ requiredComputer: idOrHandleSchema.optional(),
14048
14472
  draftHint: draftHintSchema
14049
14473
  });
14050
14474
  var channelAddMemberOperationSchema = external_exports.object({
@@ -14067,6 +14491,11 @@ var actionCardActionSchema = external_exports.discriminatedUnion("type", [
14067
14491
  channelAddMemberOperationSchema
14068
14492
  ]);
14069
14493
  function validateActionCardAction(action) {
14494
+ if (action.type === "agent:create") {
14495
+ if (action.suggestedComputer && action.requiredComputer) {
14496
+ return "agent:create must include only one of suggestedComputer or requiredComputer";
14497
+ }
14498
+ }
14070
14499
  if (action.type === "channel:add_member") {
14071
14500
  const total = (action.humans?.length ?? 0) + (action.agents?.length ?? 0);
14072
14501
  if (total === 0) {
@@ -15004,11 +15433,11 @@ ${opts.heldAction} Review the bounded context shown here, then choose one path.$
15004
15433
 
15005
15434
  // src/commands/message/_continueDraftState.ts
15006
15435
  import fs2 from "fs";
15007
- import os from "os";
15008
- import path from "path";
15436
+ import os2 from "os";
15437
+ import path3 from "path";
15009
15438
  var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
15010
15439
  function stateFilePath(agentId) {
15011
- return path.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
15440
+ return path3.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
15012
15441
  }
15013
15442
  function readState(agentId) {
15014
15443
  const filePath = stateFilePath(agentId);
@@ -15022,7 +15451,7 @@ function readState(agentId) {
15022
15451
  }
15023
15452
  function writeState(agentId, state) {
15024
15453
  const filePath = stateFilePath(agentId);
15025
- fs2.mkdirSync(path.dirname(filePath), { recursive: true });
15454
+ fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
15026
15455
  fs2.writeFileSync(filePath, JSON.stringify(state), "utf8");
15027
15456
  }
15028
15457
  function getSavedDraft(agentId, target) {
@@ -15305,8 +15734,8 @@ async function drainInbox(ctx, opts) {
15305
15734
  const query = [];
15306
15735
  if (opts.block) query.push("block=true");
15307
15736
  if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
15308
- const path2 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
15309
- const res = await client.request("GET", path2);
15737
+ const path4 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
15738
+ const res = await client.request("GET", path4);
15310
15739
  if (!res.ok) {
15311
15740
  const code = res.status >= 500 ? "SERVER_5XX" : failCode;
15312
15741
  fail(code, res.error ?? `HTTP ${res.status}`);
@@ -15481,7 +15910,7 @@ function registerReactCommand(parent) {
15481
15910
  }
15482
15911
 
15483
15912
  // src/commands/attachment/upload.ts
15484
- import { existsSync, statSync, readFileSync } from "fs";
15913
+ import { existsSync, statSync, readFileSync as readFileSync2 } from "fs";
15485
15914
  import { basename } from "path";
15486
15915
  var MAX_ATTACHMENT_UPLOAD_BYTES = 50 * 1024 * 1024;
15487
15916
  var MAX_ATTACHMENT_UPLOAD_LABEL = "50MB";
@@ -15581,12 +16010,12 @@ function registerAttachmentUploadCommand(parent) {
15581
16010
  if (!existsSync(opts.path)) {
15582
16011
  fail("INVALID_ARG", `--path does not exist: ${opts.path}`);
15583
16012
  }
15584
- const stat = statSync(opts.path);
15585
- if (!stat.isFile()) {
16013
+ const stat2 = statSync(opts.path);
16014
+ if (!stat2.isFile()) {
15586
16015
  fail("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
15587
16016
  }
15588
16017
  try {
15589
- validateUploadFileSize(stat.size);
16018
+ validateUploadFileSize(stat2.size);
15590
16019
  } catch (err) {
15591
16020
  if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
15592
16021
  throw err;
@@ -15609,7 +16038,7 @@ function registerAttachmentUploadCommand(parent) {
15609
16038
  fail(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
15610
16039
  }
15611
16040
  const channelId = resolved.data.channelId;
15612
- const buffer = readFileSync(opts.path);
16041
+ const buffer = readFileSync2(opts.path);
15613
16042
  const filename = basename(opts.path);
15614
16043
  let explicitMimeType;
15615
16044
  try {
@@ -16011,7 +16440,7 @@ function registerProfileShowCommand(parent) {
16011
16440
 
16012
16441
  // src/commands/profile/update.ts
16013
16442
  import { basename as basename2 } from "path";
16014
- import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
16443
+ import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
16015
16444
  var MAX_PROFILE_AVATAR_BYTES = 2 * 1024 * 1024;
16016
16445
  var PROFILE_AVATAR_MIME_TYPES = /* @__PURE__ */ new Set([
16017
16446
  "image/jpeg",
@@ -16050,17 +16479,17 @@ function readAvatarFile(avatarFile) {
16050
16479
  if (!existsSync2(avatarFile)) {
16051
16480
  fail("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
16052
16481
  }
16053
- const stat = statSync2(avatarFile);
16054
- if (!stat.isFile()) {
16482
+ const stat2 = statSync2(avatarFile);
16483
+ if (!stat2.isFile()) {
16055
16484
  fail("PROFILE_FILE_NOT_FOUND", `Avatar file is not a regular file: ${avatarFile}`);
16056
16485
  }
16057
- if (stat.size > MAX_PROFILE_AVATAR_BYTES) {
16486
+ if (stat2.size > MAX_PROFILE_AVATAR_BYTES) {
16058
16487
  fail(
16059
16488
  "PROFILE_AVATAR_TOO_LARGE",
16060
- `Avatar file is ${stat.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
16489
+ `Avatar file is ${stat2.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
16061
16490
  );
16062
16491
  }
16063
- const buffer = readFileSync2(avatarFile);
16492
+ const buffer = readFileSync3(avatarFile);
16064
16493
  const filename = basename2(avatarFile);
16065
16494
  const mimeType = inferImageMimeType(filename, buffer);
16066
16495
  if (!mimeType || !PROFILE_AVATAR_MIME_TYPES.has(mimeType)) {
@@ -16683,10 +17112,22 @@ function registerReminderLogCommand(parent) {
16683
17112
  // src/index.ts
16684
17113
  var program = new Command();
16685
17114
  program.name("slock").description(
16686
- "Agent-facing execution interface for Slock. Invoked by daemon-spawned agent processes \u2014 not a user-facing CLI product."
16687
- ).version("0.0.1");
17115
+ "Agent-facing CLI for Slock. Two entry shapes: (A) self-managed agent via `slock agent login --profile-slug <slug>` to create a profile, then `slock --profile <slug>` (or SLOCK_PROFILE=<slug>) to use it; (B) daemon-injected runner, where the local `slock` wrapper sets the SLOCK_AGENT_* env vars for you."
17116
+ ).option(
17117
+ "-p, --profile <slug>",
17118
+ "Use the credential at $SLOCK_HOME/profiles/<slug>/credential.json (or ~/.slock/profiles/<slug>/credential.json when SLOCK_HOME is unset). Equivalent to setting SLOCK_PROFILE=<slug>. To create a new profile, use `slock agent login --profile-slug <slug>`."
17119
+ ).version(readCliVersion());
17120
+ program.hook("preAction", () => {
17121
+ const opts = program.opts();
17122
+ if (opts.profile) {
17123
+ process.env.SLOCK_PROFILE = opts.profile;
17124
+ }
17125
+ });
16688
17126
  var authCmd = program.command("auth").description("Auth introspection");
16689
17127
  registerWhoamiCommand(authCmd);
17128
+ var agentCmd = program.command("agent").description("Self-managed agent onboarding (device-code login \u2192 sk_agent_* mint \u2192 ~/.slock/profiles/<slug>/credential.json)");
17129
+ registerAgentLoginCommand(agentCmd);
17130
+ registerAgentListCommand(agentCmd);
16690
17131
  var channelCmd = program.command("channel").description("Channel membership operations");
16691
17132
  registerChannelMembersCommand(channelCmd);
16692
17133
  registerChannelJoinCommand(channelCmd);