@slock-ai/daemon 0.41.1-alpha.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -159,6 +159,7 @@ var ApiClient = class {
159
159
  async parseJsonResponse(res) {
160
160
  let data = null;
161
161
  let error = null;
162
+ let errorCode = null;
162
163
  const contentType = res.headers.get("content-type") ?? "";
163
164
  if (contentType.includes("application/json")) {
164
165
  const parsed = await res.json().catch(() => null);
@@ -166,11 +167,12 @@ var ApiClient = class {
166
167
  data = parsed;
167
168
  } else {
168
169
  error = parsed?.error ?? `HTTP ${res.status}`;
170
+ errorCode = parsed?.errorCode ?? null;
169
171
  }
170
172
  } else if (!res.ok) {
171
173
  error = `HTTP ${res.status}`;
172
174
  }
173
- return { ok: res.ok, status: res.status, data, error };
175
+ return { ok: res.ok, status: res.status, data, error, errorCode };
174
176
  }
175
177
  async request(method, pathname, body) {
176
178
  const url = new URL(pathname, this.ctx.serverUrl).toString();
@@ -229,11 +231,32 @@ var ApiClient = class {
229
231
  };
230
232
 
231
233
  // src/commands/server/_format.ts
234
+ function formatRuntimeContext(ctx) {
235
+ if (!ctx) return "";
236
+ const lines = [
237
+ "### Current Runtime",
238
+ "Authoritative context for this agent process. Do not infer computer identity from hostname or cwd when this section is present."
239
+ ];
240
+ if (ctx.agentId) lines.push(`- Agent ID: ${ctx.agentId}`);
241
+ if (ctx.serverId) lines.push(`- Server ID: ${ctx.serverId}`);
242
+ if (ctx.machineName || ctx.machineId) {
243
+ const label = ctx.machineName && ctx.machineId ? `${ctx.machineName} (${ctx.machineId})` : ctx.machineName || ctx.machineId;
244
+ lines.push(`- Computer: ${label}`);
245
+ }
246
+ if (ctx.machineHostname) lines.push(`- Hostname: ${ctx.machineHostname}`);
247
+ if (ctx.machineOs) lines.push(`- OS: ${ctx.machineOs}`);
248
+ if (ctx.daemonVersion) lines.push(`- Daemon: v${ctx.daemonVersion}`);
249
+ if (ctx.workspacePath) lines.push(`- Workspace: ${ctx.workspacePath}`);
250
+ return lines.length > 2 ? `${lines.join("\n")}
251
+
252
+ ` : "";
253
+ }
232
254
  function formatServerInfo(data) {
233
255
  let text = "## Server\n\n";
234
256
  const channels = data.channels ?? [];
235
257
  const agents = data.agents ?? [];
236
258
  const humans = data.humans ?? [];
259
+ text += formatRuntimeContext(data.runtimeContext);
237
260
  text += "### Channels\n";
238
261
  text += 'Visible public channels may appear even when `joined=false`. Use `slock message read --channel "#name"` to inspect them. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a channel you have joined, use `slock channel leave --target "#name"`. To stop following a thread, use `slock thread unfollow --target "#name:shortid"`.\n';
239
262
  if (channels.length > 0) {
@@ -348,7 +371,14 @@ function registerServerInfoCommand(parent) {
348
371
  const code = res.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
349
372
  fail(code, res.error ?? `HTTP ${res.status}`);
350
373
  }
351
- process.stdout.write(formatServerInfo(res.data));
374
+ const data = res.data;
375
+ if (data?.runtimeContext) {
376
+ data.runtimeContext = {
377
+ ...data.runtimeContext,
378
+ workspacePath: data.runtimeContext.workspacePath ?? process.env.SLOCK_CURRENT_WORKSPACE_PATH ?? null
379
+ };
380
+ }
381
+ process.stdout.write(formatServerInfo(data));
352
382
  });
353
383
  }
354
384
 
@@ -826,11 +856,67 @@ function registerSearchCommand(parent) {
826
856
  import { existsSync, statSync, readFileSync } from "fs";
827
857
  import { basename } from "path";
828
858
  var MAX_BYTES = 10 * 1024 * 1024;
859
+ var FILENAME_MIME_MAP = {
860
+ ".jpg": "image/jpeg",
861
+ ".jpeg": "image/jpeg",
862
+ ".png": "image/png",
863
+ ".gif": "image/gif",
864
+ ".webp": "image/webp",
865
+ ".pdf": "application/pdf",
866
+ ".txt": "text/plain",
867
+ ".md": "text/markdown",
868
+ ".json": "application/json",
869
+ ".csv": "text/csv"
870
+ };
871
+ var MIME_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;
872
+ var AttachmentUploadArgError = class extends Error {
873
+ constructor(code, message) {
874
+ super(message);
875
+ this.code = code;
876
+ this.name = "AttachmentUploadArgError";
877
+ }
878
+ };
879
+ function inferMimeTypeFromFilename(filename) {
880
+ const index = filename.lastIndexOf(".");
881
+ const ext = index >= 0 ? filename.slice(index).toLowerCase() : "";
882
+ return FILENAME_MIME_MAP[ext] || null;
883
+ }
884
+ function inferMimeTypeFromBuffer(buffer) {
885
+ if (buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
886
+ return "image/png";
887
+ }
888
+ if (buffer.length >= 3 && buffer.subarray(0, 3).equals(Buffer.from([255, 216, 255]))) {
889
+ return "image/jpeg";
890
+ }
891
+ if (buffer.length >= 6) {
892
+ const header = buffer.subarray(0, 6).toString("ascii");
893
+ if (header === "GIF87a" || header === "GIF89a") return "image/gif";
894
+ }
895
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
896
+ return "image/webp";
897
+ }
898
+ return null;
899
+ }
900
+ function normalizeExplicitMimeType(mimeType) {
901
+ const normalized = mimeType?.trim().toLowerCase();
902
+ if (!normalized) return null;
903
+ if (!MIME_TYPE_RE.test(normalized)) {
904
+ throw new AttachmentUploadArgError(
905
+ "INVALID_ARG",
906
+ `--mime-type must look like type/subtype, got: ${mimeType}`
907
+ );
908
+ }
909
+ return normalized;
910
+ }
911
+ function inferUploadMimeType(filename, buffer, explicitMimeType) {
912
+ const explicit = normalizeExplicitMimeType(explicitMimeType);
913
+ return explicit || inferMimeTypeFromBuffer(buffer) || inferMimeTypeFromFilename(filename) || "application/octet-stream";
914
+ }
829
915
  function registerAttachmentUploadCommand(parent) {
830
916
  parent.command("upload").description("Upload a local file as an attachment (max 10MB)").requiredOption("--path <filepath>", "Absolute path to the local file to upload").option(
831
917
  "--channel <target>",
832
918
  "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
833
- ).action(async (opts) => {
919
+ ).option("--mime-type <type>", "Explicit MIME type override, e.g. image/png").action(async (opts) => {
834
920
  let ctx;
835
921
  try {
836
922
  ctx = loadAgentContext();
@@ -871,10 +957,21 @@ function registerAttachmentUploadCommand(parent) {
871
957
  const channelId = resolved.data.channelId;
872
958
  const buffer = readFileSync(opts.path);
873
959
  const filename = basename(opts.path);
874
- const blob = new Blob([buffer]);
960
+ let explicitMimeType;
961
+ try {
962
+ explicitMimeType = normalizeExplicitMimeType(opts.mimeType);
963
+ } catch (err) {
964
+ if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
965
+ throw err;
966
+ }
967
+ const uploadMimeType = inferUploadMimeType(filename, buffer, explicitMimeType);
968
+ const blob = new Blob([buffer], { type: uploadMimeType });
875
969
  const form = new FormData();
876
970
  form.append("file", blob, filename);
877
971
  form.append("channelId", channelId);
972
+ if (explicitMimeType) {
973
+ form.append("mimeType", explicitMimeType);
974
+ }
878
975
  const res = await client.requestMultipart(
879
976
  "POST",
880
977
  `${agentPath}/upload`,
@@ -1145,6 +1242,394 @@ function registerTaskUpdateCommand(parent) {
1145
1242
  });
1146
1243
  }
1147
1244
 
1245
+ // ../shared/src/tracing/index.ts
1246
+ var DEFAULT_TRACE_FLAGS = "00";
1247
+ var TRACE_ID_HEX_LENGTH = 32;
1248
+ var SPAN_ID_HEX_LENGTH = 16;
1249
+ var TRACE_FLAGS_HEX_LENGTH = 2;
1250
+ var TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
1251
+ var SPAN_ID_PATTERN = /^[0-9a-f]{16}$/;
1252
+ var TRACE_FLAGS_PATTERN = /^[0-9a-f]{2}$/;
1253
+ function isTraceId(value) {
1254
+ return TRACE_ID_PATTERN.test(value) && value !== "0".repeat(TRACE_ID_HEX_LENGTH);
1255
+ }
1256
+ function isSpanId(value) {
1257
+ return SPAN_ID_PATTERN.test(value) && value !== "0".repeat(SPAN_ID_HEX_LENGTH);
1258
+ }
1259
+ function isTraceFlags(value) {
1260
+ return TRACE_FLAGS_PATTERN.test(value);
1261
+ }
1262
+ function assertTraceContext(context) {
1263
+ if (!isTraceId(context.traceId)) {
1264
+ throw new Error(`Invalid traceId: expected ${TRACE_ID_HEX_LENGTH} lowercase hex chars`);
1265
+ }
1266
+ if (!isSpanId(context.spanId)) {
1267
+ throw new Error(`Invalid spanId: expected ${SPAN_ID_HEX_LENGTH} lowercase hex chars`);
1268
+ }
1269
+ if (context.parentSpanId !== null && !isSpanId(context.parentSpanId)) {
1270
+ throw new Error(`Invalid parentSpanId: expected null or ${SPAN_ID_HEX_LENGTH} lowercase hex chars`);
1271
+ }
1272
+ if (!isTraceFlags(context.traceFlags)) {
1273
+ throw new Error(`Invalid traceFlags: expected ${TRACE_FLAGS_HEX_LENGTH} lowercase hex chars`);
1274
+ }
1275
+ }
1276
+ function createTraceContext({
1277
+ parent = null,
1278
+ traceId,
1279
+ spanId,
1280
+ traceFlags,
1281
+ traceIdGenerator = generateTraceId,
1282
+ spanIdGenerator = generateSpanId
1283
+ } = {}) {
1284
+ const context = {
1285
+ traceId: traceId ?? parent?.traceId ?? traceIdGenerator(),
1286
+ spanId: spanId ?? spanIdGenerator(),
1287
+ parentSpanId: parent?.spanId ?? null,
1288
+ traceFlags: traceFlags ?? parent?.traceFlags ?? DEFAULT_TRACE_FLAGS
1289
+ };
1290
+ assertTraceContext(context);
1291
+ return context;
1292
+ }
1293
+ var NoopTracer = class {
1294
+ startSpan(_name, options) {
1295
+ return new NoopActiveSpan(createTraceContext({ parent: options.parent ?? null }));
1296
+ }
1297
+ };
1298
+ var NoopActiveSpan = class {
1299
+ context;
1300
+ constructor(context) {
1301
+ this.context = context;
1302
+ }
1303
+ addEvent() {
1304
+ }
1305
+ end() {
1306
+ }
1307
+ };
1308
+ var noopTracer = new NoopTracer();
1309
+ function generateTraceId() {
1310
+ return randomNonZeroHex(TRACE_ID_HEX_LENGTH);
1311
+ }
1312
+ function generateSpanId() {
1313
+ return randomNonZeroHex(SPAN_ID_HEX_LENGTH);
1314
+ }
1315
+ function randomNonZeroHex(length) {
1316
+ let value = randomHex(length);
1317
+ while (value === "0".repeat(length)) {
1318
+ value = randomHex(length);
1319
+ }
1320
+ return value;
1321
+ }
1322
+ function randomHex(length) {
1323
+ const bytes = new Uint8Array(length / 2);
1324
+ globalThis.crypto.getRandomValues(bytes);
1325
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1326
+ }
1327
+
1328
+ // ../shared/src/testing/failpoints.ts
1329
+ var NoopFailpointRegistry = class {
1330
+ get enabled() {
1331
+ return false;
1332
+ }
1333
+ isEnabled() {
1334
+ return false;
1335
+ }
1336
+ configure() {
1337
+ }
1338
+ clear() {
1339
+ }
1340
+ getTrace() {
1341
+ return [];
1342
+ }
1343
+ hit(_key, _context, fallback) {
1344
+ return fallback ? fallback() : void 0;
1345
+ }
1346
+ };
1347
+ var noopFailpointRegistry = new NoopFailpointRegistry();
1348
+
1349
+ // ../shared/src/serverPermissions.ts
1350
+ var EMPTY_SERVER_CAPABILITIES = Object.freeze({
1351
+ manageServer: false,
1352
+ manageChannels: false,
1353
+ manageAgents: false,
1354
+ manageMachines: false,
1355
+ manageMembers: false,
1356
+ changeMemberRoles: false,
1357
+ manageBilling: false,
1358
+ joinPublicChannels: false
1359
+ });
1360
+ var SERVER_CAPABILITY_MATRIX = {
1361
+ owner: Object.freeze({
1362
+ manageServer: true,
1363
+ manageChannels: true,
1364
+ manageAgents: true,
1365
+ manageMachines: true,
1366
+ manageMembers: true,
1367
+ changeMemberRoles: true,
1368
+ manageBilling: true,
1369
+ joinPublicChannels: true
1370
+ }),
1371
+ admin: Object.freeze({
1372
+ manageServer: true,
1373
+ manageChannels: true,
1374
+ manageAgents: true,
1375
+ manageMachines: true,
1376
+ manageMembers: true,
1377
+ changeMemberRoles: true,
1378
+ manageBilling: false,
1379
+ joinPublicChannels: true
1380
+ }),
1381
+ member: Object.freeze({
1382
+ manageServer: false,
1383
+ manageChannels: false,
1384
+ manageAgents: false,
1385
+ manageMachines: false,
1386
+ manageMembers: false,
1387
+ changeMemberRoles: false,
1388
+ manageBilling: false,
1389
+ joinPublicChannels: true
1390
+ })
1391
+ };
1392
+
1393
+ // ../shared/src/index.ts
1394
+ var RUNTIMES = [
1395
+ { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
1396
+ { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
1397
+ { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
1398
+ { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
1399
+ { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
1400
+ { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true }
1401
+ ];
1402
+ function getRuntimeDisplayName(id) {
1403
+ return RUNTIMES.find((r) => r.id === id)?.displayName ?? id;
1404
+ }
1405
+ var PLAN_CONFIG = {
1406
+ free: {
1407
+ displayName: "Hobby",
1408
+ limits: { maxMachines: 2, maxAgents: 5, maxChannels: 5, messageHistoryDays: 30, includedAgents: 5 },
1409
+ comingSoon: false,
1410
+ price: 0,
1411
+ extraAgentPrice: 0
1412
+ },
1413
+ founder: {
1414
+ displayName: "Founder",
1415
+ limits: { maxMachines: -1, maxAgents: -1, maxChannels: -1, messageHistoryDays: -1, includedAgents: -1 },
1416
+ comingSoon: false,
1417
+ price: 0,
1418
+ extraAgentPrice: 0
1419
+ }
1420
+ };
1421
+ var DISPLAY_PLAN_CONFIG = {
1422
+ free: PLAN_CONFIG.free,
1423
+ pro: {
1424
+ displayName: "Team",
1425
+ limits: { maxMachines: 8, maxAgents: 40, maxChannels: 20, messageHistoryDays: -1, includedAgents: 40 },
1426
+ comingSoon: true,
1427
+ price: 20,
1428
+ extraAgentPrice: 0
1429
+ },
1430
+ max: {
1431
+ displayName: "Business",
1432
+ limits: { maxMachines: 40, maxAgents: 200, maxChannels: -1, messageHistoryDays: -1, includedAgents: 200 },
1433
+ comingSoon: true,
1434
+ price: 200,
1435
+ extraAgentPrice: 0
1436
+ }
1437
+ };
1438
+
1439
+ // src/commands/profile/_format.ts
1440
+ function formatCreatedAgents(createdAgents) {
1441
+ if (createdAgents.length === 0) {
1442
+ return ["- Created Agents: none"];
1443
+ }
1444
+ return [
1445
+ `- Created Agents (${createdAgents.length}):`,
1446
+ ...createdAgents.map((createdAgent) => ` - @${createdAgent.name} (${getRuntimeDisplayName(createdAgent.runtime)}, ${createdAgent.status})`)
1447
+ ];
1448
+ }
1449
+ function formatHumanProfile(profile) {
1450
+ const lines = [
1451
+ "## Profile",
1452
+ "",
1453
+ "- Type: human",
1454
+ `- Handle: @${profile.name}`,
1455
+ `- Display Name: ${profile.displayName ?? "(none)"}`,
1456
+ `- Description: ${profile.description ?? "(none)"}`,
1457
+ `- Membership: ${profile.membershipStatus}`
1458
+ ];
1459
+ if (profile.role) lines.push(`- Role: ${profile.role}`);
1460
+ if (profile.joinedAt) lines.push(`- Joined: ${profile.joinedAt}`);
1461
+ if (profile.email) lines.push(`- Email: ${profile.email}`);
1462
+ return [...lines, ...formatCreatedAgents(profile.createdAgents)].join("\n");
1463
+ }
1464
+ function formatCreator(profile) {
1465
+ if (!profile.creator) return null;
1466
+ return profile.creator.displayName ? `${profile.creator.displayName} (@${profile.creator.name})` : `@${profile.creator.name}`;
1467
+ }
1468
+ function formatAgentProfile(profile) {
1469
+ const lines = [
1470
+ "## Profile",
1471
+ "",
1472
+ "- Type: agent",
1473
+ `- Handle: @${profile.name}`,
1474
+ `- Display Name: ${profile.displayName ?? "(none)"}`,
1475
+ `- Description: ${profile.description ?? "(none)"}`,
1476
+ `- Status: ${profile.status}`,
1477
+ `- Runtime: ${getRuntimeDisplayName(profile.runtime)}`,
1478
+ `- Model: ${profile.model}`,
1479
+ `- Reasoning: ${profile.reasoningEffort ?? "medium"}`
1480
+ ];
1481
+ if (profile.executionMode) lines.push(`- Execution: ${profile.executionMode}`);
1482
+ if (profile.computerName || profile.computerId) {
1483
+ const label = profile.computerName && profile.computerId ? `${profile.computerName} (${profile.computerId})` : profile.computerName ?? profile.computerId;
1484
+ lines.push(`- Computer: ${label}`);
1485
+ }
1486
+ if (profile.computerHostname) lines.push(`- Hostname: ${profile.computerHostname}`);
1487
+ if (profile.daemonVersion) lines.push(`- Daemon: v${profile.daemonVersion}`);
1488
+ lines.push(`- Created: ${profile.createdAt}`);
1489
+ if (profile.deletedAt) lines.push(`- Deleted At: ${profile.deletedAt}`);
1490
+ const creator = formatCreator(profile);
1491
+ if (creator) lines.push(`- Creator: ${creator}`);
1492
+ return [...lines, ...formatCreatedAgents(profile.createdAgents)].join("\n");
1493
+ }
1494
+ function formatProfile(profile) {
1495
+ return profile.kind === "human" ? formatHumanProfile(profile) : formatAgentProfile(profile);
1496
+ }
1497
+
1498
+ // src/commands/profile/show.ts
1499
+ function normalizeTarget(target) {
1500
+ if (target === void 0) return null;
1501
+ const trimmed = target.trim();
1502
+ if (!trimmed) {
1503
+ fail("INVALID_ARG", "profile target must not be empty");
1504
+ }
1505
+ if (!trimmed.startsWith("@")) {
1506
+ fail("INVALID_ARG", "profile target must start with @");
1507
+ }
1508
+ return trimmed;
1509
+ }
1510
+ function registerProfileShowCommand(parent) {
1511
+ parent.command("show").description("Show a profile. Omit the target to show your own profile.").argument("[target]", "Handle like @alice; omit to show your own profile").option("--json", "Emit machine-readable JSON").action(async (target, opts) => {
1512
+ let ctx;
1513
+ try {
1514
+ ctx = loadAgentContext();
1515
+ } catch (err) {
1516
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
1517
+ throw err;
1518
+ }
1519
+ const normalizedTarget = normalizeTarget(target);
1520
+ const params = new URLSearchParams();
1521
+ if (normalizedTarget) params.set("target", normalizedTarget);
1522
+ const client = new ApiClient(ctx);
1523
+ const pathname = params.size > 0 ? `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile?${params.toString()}` : `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile`;
1524
+ const res = await client.request("GET", pathname);
1525
+ if (!res.ok || !res.data) {
1526
+ const code = res.status >= 500 ? "SERVER_5XX" : "PROFILE_SHOW_FAILED";
1527
+ fail(code, res.error ?? `HTTP ${res.status}`);
1528
+ }
1529
+ if (opts.json) {
1530
+ emit({ ok: true, data: res.data });
1531
+ return;
1532
+ }
1533
+ process.stdout.write(`${formatProfile(res.data)}
1534
+ `);
1535
+ });
1536
+ }
1537
+
1538
+ // src/commands/profile/update.ts
1539
+ import { basename as basename2 } from "path";
1540
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1541
+ var MAX_PROFILE_AVATAR_BYTES = 2 * 1024 * 1024;
1542
+ var PROFILE_AVATAR_MIME_TYPES = /* @__PURE__ */ new Set([
1543
+ "image/jpeg",
1544
+ "image/png",
1545
+ "image/gif",
1546
+ "image/webp"
1547
+ ]);
1548
+ var FILENAME_MIME_MAP2 = {
1549
+ ".jpg": "image/jpeg",
1550
+ ".jpeg": "image/jpeg",
1551
+ ".png": "image/png",
1552
+ ".gif": "image/gif",
1553
+ ".webp": "image/webp"
1554
+ };
1555
+ function inferImageMimeType(filename, buffer) {
1556
+ const lowerFilename = filename.toLowerCase();
1557
+ if (buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
1558
+ return "image/png";
1559
+ }
1560
+ if (buffer.length >= 3 && buffer.subarray(0, 3).equals(Buffer.from([255, 216, 255]))) {
1561
+ return "image/jpeg";
1562
+ }
1563
+ if (buffer.length >= 6) {
1564
+ const header = buffer.subarray(0, 6).toString("ascii");
1565
+ if (header === "GIF87a" || header === "GIF89a") return "image/gif";
1566
+ }
1567
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
1568
+ return "image/webp";
1569
+ }
1570
+ const dot = lowerFilename.lastIndexOf(".");
1571
+ return dot >= 0 ? FILENAME_MIME_MAP2[lowerFilename.slice(dot)] ?? null : null;
1572
+ }
1573
+ function readAvatarFile(avatarFile) {
1574
+ if (!existsSync2(avatarFile)) {
1575
+ fail("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
1576
+ }
1577
+ const stat = statSync2(avatarFile);
1578
+ if (!stat.isFile()) {
1579
+ fail("PROFILE_FILE_NOT_FOUND", `Avatar file is not a regular file: ${avatarFile}`);
1580
+ }
1581
+ if (stat.size > MAX_PROFILE_AVATAR_BYTES) {
1582
+ fail(
1583
+ "PROFILE_AVATAR_TOO_LARGE",
1584
+ `Avatar file is ${stat.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
1585
+ );
1586
+ }
1587
+ const buffer = readFileSync2(avatarFile);
1588
+ const filename = basename2(avatarFile);
1589
+ const mimeType = inferImageMimeType(filename, buffer);
1590
+ if (!mimeType || !PROFILE_AVATAR_MIME_TYPES.has(mimeType)) {
1591
+ fail(
1592
+ "PROFILE_AVATAR_BAD_FORMAT",
1593
+ "Avatar must be a JPEG, PNG, GIF, or WebP image"
1594
+ );
1595
+ }
1596
+ return { filename, buffer, mimeType };
1597
+ }
1598
+ function registerProfileUpdateCommand(parent) {
1599
+ parent.command("update").description("Update your own profile").requiredOption("--avatar-file <path>", "Path to a local image file to use as your avatar").option("--json", "Emit machine-readable JSON").action(async (opts) => {
1600
+ let ctx;
1601
+ try {
1602
+ ctx = loadAgentContext();
1603
+ } catch (err) {
1604
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
1605
+ throw err;
1606
+ }
1607
+ if (!opts.avatarFile) {
1608
+ fail("INVALID_ARG", "--avatar-file is required");
1609
+ }
1610
+ const avatar = readAvatarFile(opts.avatarFile);
1611
+ const form = new FormData();
1612
+ const avatarBytes = Uint8Array.from(avatar.buffer);
1613
+ form.append("avatar", new Blob([avatarBytes], { type: avatar.mimeType }), avatar.filename);
1614
+ const client = new ApiClient(ctx);
1615
+ const res = await client.requestMultipart(
1616
+ "POST",
1617
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/profile/avatar`,
1618
+ form
1619
+ );
1620
+ if (!res.ok || !res.data) {
1621
+ const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "PROFILE_UPDATE_FAILED");
1622
+ fail(code, res.error ?? `HTTP ${res.status}`);
1623
+ }
1624
+ if (opts.json) {
1625
+ emit({ ok: true, data: res.data });
1626
+ return;
1627
+ }
1628
+ process.stdout.write(`${formatProfile(res.data)}
1629
+ `);
1630
+ });
1631
+ }
1632
+
1148
1633
  // src/commands/reminder/_format.ts
1149
1634
  function toLocalTime2(iso) {
1150
1635
  const d = new Date(iso);
@@ -1369,6 +1854,9 @@ registerTaskCreateCommand(taskCmd);
1369
1854
  registerTaskClaimCommand(taskCmd);
1370
1855
  registerTaskUnclaimCommand(taskCmd);
1371
1856
  registerTaskUpdateCommand(taskCmd);
1857
+ var profileCmd = program.command("profile").description("Profile operations");
1858
+ registerProfileShowCommand(profileCmd);
1859
+ registerProfileUpdateCommand(profileCmd);
1372
1860
  var reminderCmd = program.command("reminder").description("Reminder operations");
1373
1861
  registerReminderScheduleCommand(reminderCmd);
1374
1862
  registerReminderListCommand(reminderCmd);
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-JAB3HALZ.js";
12
+ } from "./chunk-RNEIFBXW.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
15
  } from "./chunk-JG7ONJZ6.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-JAB3HALZ.js";
6
+ } from "./chunk-RNEIFBXW.js";
7
7
  import "./chunk-JG7ONJZ6.js";
8
8
 
9
9
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.41.1-alpha.0",
3
+ "version": "0.42.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"