@slock-ai/daemon 0.40.2 → 0.41.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
@@ -235,7 +235,7 @@ function formatServerInfo(data) {
235
235
  const agents = data.agents ?? [];
236
236
  const humans = data.humans ?? [];
237
237
  text += "### Channels\n";
238
- 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.\n';
238
+ 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
239
  if (channels.length > 0) {
240
240
  for (const t of channels) {
241
241
  const status = t.joined ? "joined" : "not joined";
@@ -271,6 +271,63 @@ function formatServerInfo(data) {
271
271
  }
272
272
  return text;
273
273
  }
274
+ function formatChannelMembers(data) {
275
+ let text = "## Channel Members\n\n";
276
+ const ref = data.channel?.ref ?? "(unknown)";
277
+ const type = data.channel?.type ? ` (${data.channel.type})` : "";
278
+ const agents = data.agents ?? [];
279
+ const humans = data.humans ?? [];
280
+ text += `Channel: ${ref}${type}
281
+ `;
282
+ text += "Members means join/post authority for this surface.\n\n";
283
+ text += "### Agents\n";
284
+ if (agents.length > 0) {
285
+ for (const a of agents) {
286
+ text += a.description ? ` - @${a.name} (${a.status}) \u2014 ${a.description}
287
+ ` : ` - @${a.name} (${a.status})
288
+ `;
289
+ }
290
+ } else {
291
+ text += " (none)\n";
292
+ }
293
+ text += "\n### Humans\n";
294
+ if (humans.length > 0) {
295
+ for (const u of humans) {
296
+ text += u.description ? ` - @${u.name} \u2014 ${u.description}
297
+ ` : ` - @${u.name}
298
+ `;
299
+ }
300
+ } else {
301
+ text += " (none)\n";
302
+ }
303
+ return text;
304
+ }
305
+
306
+ // src/commands/channel/members.ts
307
+ function registerChannelMembersCommand(parent) {
308
+ parent.command("members").description("List agents and humans who are members of a channel, DM, or thread").argument("<target>", "Channel / DM / thread target, e.g. #proj-runtime, dm:@alice, #proj-runtime:abcd1234").action(async (target) => {
309
+ let ctx;
310
+ try {
311
+ ctx = loadAgentContext();
312
+ } catch (err) {
313
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
314
+ throw err;
315
+ }
316
+ const client = new ApiClient(ctx);
317
+ const channel = String(target || "").trim();
318
+ if (!channel) fail("MEMBERS_FAILED", "target is required");
319
+ const encoded = encodeURIComponent(channel);
320
+ const res = await client.request(
321
+ "GET",
322
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/channel-members?channel=${encoded}`
323
+ );
324
+ if (!res.ok) {
325
+ const code = res.status >= 500 ? "SERVER_5XX" : "MEMBERS_FAILED";
326
+ fail(code, res.error ?? `HTTP ${res.status}`);
327
+ }
328
+ process.stdout.write(formatChannelMembers(res.data));
329
+ });
330
+ }
274
331
 
275
332
  // src/commands/server/info.ts
276
333
  function registerServerInfoCommand(parent) {
@@ -295,6 +352,113 @@ function registerServerInfoCommand(parent) {
295
352
  });
296
353
  }
297
354
 
355
+ // src/commands/channel/leave.ts
356
+ function parseRegularChannelTarget(target) {
357
+ if (!target.startsWith("#")) return null;
358
+ if (target.includes(":")) return null;
359
+ const name = target.slice(1).trim();
360
+ return name.length > 0 ? name : null;
361
+ }
362
+ function formatLeaveChannelResult(target) {
363
+ return `Left ${target}. You can still inspect visible public channel history there, but you can no longer send or receive ordinary channel delivery until a human adds you again.`;
364
+ }
365
+ function formatAlreadyNotJoined(target) {
366
+ return `Already not joined in ${target}.`;
367
+ }
368
+ function registerChannelLeaveCommand(parent) {
369
+ parent.command("leave").description("Leave a regular channel you have joined").requiredOption("--target <target>", "Regular channel to leave, e.g. '#engineering'").action(async (opts) => {
370
+ const channelName = parseRegularChannelTarget(opts.target);
371
+ if (!channelName) {
372
+ fail("INVALID_TARGET", "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.");
373
+ }
374
+ let ctx;
375
+ try {
376
+ ctx = loadAgentContext();
377
+ } catch (err) {
378
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
379
+ throw err;
380
+ }
381
+ const client = new ApiClient(ctx);
382
+ const infoRes = await client.request(
383
+ "GET",
384
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/server`
385
+ );
386
+ if (!infoRes.ok) {
387
+ const code = infoRes.status >= 500 ? "SERVER_5XX" : "INFO_FAILED";
388
+ fail(code, infoRes.error ?? `HTTP ${infoRes.status}`);
389
+ }
390
+ const channel = (infoRes.data?.channels ?? []).find((candidate) => candidate.name === channelName);
391
+ if (!channel) {
392
+ fail("NOT_FOUND", `Channel not found: ${opts.target}`);
393
+ }
394
+ if (!channel.joined) {
395
+ process.stdout.write(formatAlreadyNotJoined(opts.target) + "\n");
396
+ return;
397
+ }
398
+ const leaveRes = await client.request(
399
+ "POST",
400
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/channels/${encodeURIComponent(channel.id)}/leave`
401
+ );
402
+ if (!leaveRes.ok) {
403
+ const code = leaveRes.status >= 500 ? "SERVER_5XX" : "LEAVE_FAILED";
404
+ fail(code, leaveRes.error ?? `HTTP ${leaveRes.status}`);
405
+ }
406
+ process.stdout.write(formatLeaveChannelResult(opts.target) + "\n");
407
+ });
408
+ }
409
+
410
+ // src/commands/thread/unfollow.ts
411
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
412
+ var SHORT_ID_RE = /^[0-9a-f]{8}$/i;
413
+ function parseThreadTarget(target) {
414
+ const trimmed = target.trim();
415
+ if (UUID_RE.test(trimmed)) return trimmed;
416
+ if (trimmed.startsWith("#")) {
417
+ const rest = trimmed.slice(1);
418
+ const lastColon = rest.lastIndexOf(":");
419
+ if (lastColon > 0 && SHORT_ID_RE.test(rest.slice(lastColon + 1))) {
420
+ return trimmed;
421
+ }
422
+ }
423
+ if (trimmed.startsWith("dm:@") || trimmed.startsWith("DM:@")) {
424
+ const rest = trimmed.slice(4);
425
+ const lastColon = rest.lastIndexOf(":");
426
+ if (lastColon > 0 && SHORT_ID_RE.test(rest.slice(lastColon + 1))) {
427
+ return trimmed;
428
+ }
429
+ }
430
+ return null;
431
+ }
432
+ function formatUnfollowThreadResult(target) {
433
+ return `Unfollowed ${target}. You can still inspect the thread when its parent conversation is visible, but you will no longer receive ordinary thread delivery unless you follow it again or are mentioned.`;
434
+ }
435
+ function registerThreadUnfollowCommand(parent) {
436
+ parent.command("unfollow").description("Stop following a thread you no longer need ordinary delivery for").requiredOption("--target <target>", "Thread target, e.g. '#engineering:abcd1234' or 'dm:@alice:abcd1234'").action(async (opts) => {
437
+ const thread = parseThreadTarget(opts.target);
438
+ if (!thread) {
439
+ fail("INVALID_TARGET", "Thread must be a thread target like '#channel:abcd1234', 'dm:@peer:abcd1234', or a thread channel UUID.");
440
+ }
441
+ let ctx;
442
+ try {
443
+ ctx = loadAgentContext();
444
+ } catch (err) {
445
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
446
+ throw err;
447
+ }
448
+ const client = new ApiClient(ctx);
449
+ const res = await client.request(
450
+ "POST",
451
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/threads/unfollow`,
452
+ { thread }
453
+ );
454
+ if (!res.ok) {
455
+ const code = res.status >= 500 ? "SERVER_5XX" : "UNFOLLOW_FAILED";
456
+ fail(code, res.error ?? `HTTP ${res.status}`);
457
+ }
458
+ process.stdout.write(formatUnfollowThreadResult(thread) + "\n");
459
+ });
460
+ }
461
+
298
462
  // src/commands/message/_format.ts
299
463
  function toLocalTime(iso) {
300
464
  const d = new Date(iso);
@@ -1184,6 +1348,11 @@ program.name("slock").description(
1184
1348
  ).version("0.0.1");
1185
1349
  var authCmd = program.command("auth").description("Auth introspection");
1186
1350
  registerWhoamiCommand(authCmd);
1351
+ var channelCmd = program.command("channel").description("Channel membership operations");
1352
+ registerChannelMembersCommand(channelCmd);
1353
+ registerChannelLeaveCommand(channelCmd);
1354
+ var threadCmd = program.command("thread").description("Thread attention operations");
1355
+ registerThreadUnfollowCommand(threadCmd);
1187
1356
  var serverCmd = program.command("server").description("Server / workspace introspection");
1188
1357
  registerServerInfoCommand(serverCmd);
1189
1358
  var messageCmd = program.command("message").description("Message operations");
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-PB75DRIF.js";
12
+ } from "./chunk-KFVDXO5Y.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-PB75DRIF.js";
6
+ } from "./chunk-KFVDXO5Y.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.40.2",
3
+ "version": "0.41.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"