@commonlyai/cli 0.1.228 → 0.1.230

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.228",
3
+ "version": "0.1.230",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -560,7 +560,12 @@ const listAgents = async (opts, deps) => {
560
560
  return 0;
561
561
  }
562
562
 
563
- log(`${col('NAME', 16)} ${col('INSTANCE', 10)} ${col('RUNTIME', 14)} ${col('HOST', 6)} ${col('STATUS', 10)} LAST SEEN`);
563
+ // The pod roster also names the handle a person types to reach each agent
564
+ // (F44). It is printed whole: a cut handle reaches nobody.
565
+ const handleOf = (inst) => (inst.mention ? `@${inst.mention}` : '');
566
+ const handleWidth = opts.pod ? Math.max(6, ...rows.map((inst) => handleOf(inst).length)) : 0;
567
+ const handleCol = (s) => (opts.pod ? `${col(s, handleWidth)} ` : '');
568
+ log(`${col('NAME', 16)} ${col('INSTANCE', 10)} ${handleCol('HANDLE')}${col('RUNTIME', 14)} ${col('HOST', 6)} ${col('STATUS', 10)} LAST SEEN`);
564
569
  log('─'.repeat(80));
565
570
  rows.forEach((inst) => {
566
571
  // The pod roster names the agent `name`; the admin route uses `agentName`.
@@ -569,7 +574,7 @@ const listAgents = async (opts, deps) => {
569
574
  const host = inst.config?.runtime?.host || inst.runtime?.host || '';
570
575
  const seen = inst.lastActiveAt || inst.usage?.lastUsedAt || inst.connection?.lastSeenAt;
571
576
  const lastSeen = seen ? new Date(seen).toLocaleString() : 'never';
572
- log(`${col(displayName, 16)} ${col(inst.instanceId, 10)} ${col(runtimeType, 14)} ${col(host, 6)} ${col(inst.status, 10)} ${lastSeen}`);
577
+ log(`${col(displayName, 16)} ${col(inst.instanceId, 10)} ${handleCol(handleOf(inst))}${col(runtimeType, 14)} ${col(host, 6)} ${col(inst.status, 10)} ${lastSeen}`);
573
578
  });
574
579
  return 0;
575
580
  } catch (err) {
@@ -579,6 +584,64 @@ const listAgents = async (opts, deps) => {
579
584
  }
580
585
  };
581
586
 
587
+ /**
588
+ * The seat `agent logs <name> --pod` reads (F44). An agent has three names: its
589
+ * agentName, its instance, and the handle its @mentions route to. Any of them
590
+ * finds it in the pod roster, so a seat whose instance isn't "default" no longer
591
+ * reads as idle. An explicit --instance-id is used as given.
592
+ */
593
+ export const resolveLogsSeat = async (client, podId, typed, instanceId) => {
594
+ // Activity is stored under the bare agentName, never an @ spelling.
595
+ const bare = String(typed).trim().replace(/^@/, '');
596
+ const isHandle = String(typed).trim().startsWith('@');
597
+ // A plain name with an explicit instance is used as given. An @handle still
598
+ // needs the roster: activity is stored under the agentName, not the handle.
599
+ if (instanceId && !isHandle) return { agentName: bare, instanceId };
600
+ const want = bare.toLowerCase();
601
+ let agents;
602
+ try {
603
+ ({ agents = [] } = await client.get(`/api/registry/pods/${encodeURIComponent(podId)}/agents`));
604
+ } catch (err) {
605
+ // The default instance is still worth reading when the roster isn't.
606
+ return {
607
+ agentName: bare,
608
+ instanceId: instanceId || 'default',
609
+ note: `The pod roster couldn't be read (${err.message}), so instance ${instanceId || 'default'} was read for '${bare}'.`,
610
+ };
611
+ }
612
+ const seatOf = (inst) => ({ agentName: inst.name || inst.agentName, instanceId: inst.instanceId || 'default' });
613
+ // An @handle routes to one seat, so it wins over another seat's instance of the same name.
614
+ if (isHandle) {
615
+ const byHandle = agents.filter((inst) => typeof inst.mention === 'string' && inst.mention.toLowerCase() === want);
616
+ if (byHandle.length === 1) return instanceId ? { ...seatOf(byHandle[0]), instanceId } : seatOf(byHandle[0]);
617
+ if (instanceId) {
618
+ return {
619
+ agentName: bare,
620
+ instanceId,
621
+ note: `'${typed}' is not a handle in this pod's roster, so agent name '${bare}' was read at instance ${instanceId}.`,
622
+ };
623
+ }
624
+ }
625
+ const named = (inst) => [inst.mention, inst.name || inst.agentName, inst.instanceId !== 'default' && inst.instanceId]
626
+ .some((n) => typeof n === 'string' && n.toLowerCase() === want);
627
+ const hits = agents.filter(named);
628
+ if (hits.length > 1) {
629
+ const pairs = hits.map((h) => `${h.name || h.agentName}/${h.instanceId}`).join(', ');
630
+ throw new Error(`${hits.length} seats are named '${typed}' here: ${pairs}. `
631
+ + `Run: commonly agent logs <agentName> --pod ${podId} --instance-id <instance>`);
632
+ }
633
+ if (hits.length === 0) {
634
+ // A typo must not read like an idle seat, so say what was read instead.
635
+ const handles = agents.map((inst) => inst.mention).filter(Boolean).map((m) => `@${m}`).join(', ');
636
+ return {
637
+ agentName: bare,
638
+ instanceId: 'default',
639
+ note: `'${typed}' is not in this pod's roster, so instance default was read.${handles ? ` Seats here: ${handles}` : ''}`,
640
+ };
641
+ }
642
+ return seatOf(hits[0]);
643
+ };
644
+
582
645
  /**
583
646
  * Enumerate every agent attached on this laptop — i.e. every file in
584
647
  * ~/.commonly/tokens/*.json. Cross-references the session store for a
@@ -4466,7 +4529,7 @@ Use --local to find the name you'd pass to 'agent run' or 'agent detach'.
4466
4529
  .command('logs <name>')
4467
4530
  .description("Show a seat's recent log: local daemon log, or server activity with --pod")
4468
4531
  .option('--pod <podId>', 'Read server-side events and runs for a pod you belong to')
4469
- .option('--instance-id <id>', 'Seat instance ID', 'default')
4532
+ .option('--instance-id <id>', 'Seat instance ID (with --pod, found from the name when omitted)')
4470
4533
  .option('--follow', 'Continue printing new output', false)
4471
4534
  .option('--lines <n>', 'Number of trailing local lines to show', '50')
4472
4535
  .option('--instance <url>', 'Target Commonly instance (with --pod)')
@@ -4520,10 +4583,12 @@ Use --local to find the name you'd pass to 'agent run' or 'agent detach'.
4520
4583
  if (!token) { console.error('Not logged in. Run: commonly login'); process.exit(1); }
4521
4584
  const client = createClient({ instance: resolveInstanceUrl(opts.instance), token });
4522
4585
  const printActivity = async () => {
4586
+ const seat = await resolveLogsSeat(client, opts.pod, name, opts.instanceId);
4587
+ if (seat.note) console.error(seat.note);
4523
4588
  const { events = [], runs = [] } = await client.get('/api/agents/seat-activity', {
4524
4589
  podId: opts.pod,
4525
- agentName: name,
4526
- instanceId: opts.instanceId,
4590
+ agentName: seat.agentName,
4591
+ instanceId: seat.instanceId,
4527
4592
  });
4528
4593
  events.forEach((e) => {
4529
4594
  const ts = new Date(e.createdAt).toTimeString().slice(0, 8);
@@ -40,6 +40,8 @@ const templateLiteral = (value) => {
40
40
  };
41
41
  // Explicit API-only list: new MCP tools do not silently gain public authority.
42
42
  // attach_file reads this process's filesystem, including its credential file.
43
+ // catch_up is left out too: it moves the read cursor past history unread, so a
44
+ // public seat past 50 unseen falls back to continueAnyway, which it keeps.
43
45
  export const PUBLIC_GOOSE_TOOLS = Object.freeze([
44
46
  'commonly_get_started', 'commonly_post_message', 'commonly_claim_message',
45
47
  'commonly_release_claim', 'commonly_get_messages', 'commonly_get_context',
@@ -2,4 +2,6 @@
2
2
  // Claude workspace seats follow the same list. Each
3
3
  // adapter removes them in its own vocabulary (TASK-287; Goose's public list
4
4
  // already leaves them out).
5
- export const PUBLIC_WITHHELD_TOOLS = Object.freeze(['commonly_attach_file']);
5
+ // commonly_catch_up moves the read cursor past history unread; a wrapper seat
6
+ // already catches up before each turn, so no seat on this list needs the tool.
7
+ export const PUBLIC_WITHHELD_TOOLS = Object.freeze(['commonly_attach_file', 'commonly_catch_up']);