@phnx-labs/agents-cli 1.22.42 → 1.22.44

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.
Files changed (256) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +15 -13
  3. package/dist/bootstrap.js +33 -13
  4. package/dist/cli/command-registry.d.ts +0 -8
  5. package/dist/cli/command-registry.js +1 -25
  6. package/dist/commands/alias.d.ts +7 -4
  7. package/dist/commands/alias.js +33 -8
  8. package/dist/commands/apply.d.ts +7 -10
  9. package/dist/commands/apply.js +15 -35
  10. package/dist/commands/artifacts.js +4 -5
  11. package/dist/commands/audit.d.ts +6 -6
  12. package/dist/commands/audit.js +12 -12
  13. package/dist/commands/auth.js +14 -6
  14. package/dist/commands/beta.d.ts +7 -1
  15. package/dist/commands/beta.js +16 -9
  16. package/dist/commands/commands.js +1 -1
  17. package/dist/commands/computer.js +20 -6
  18. package/dist/commands/daemon.js +1 -1
  19. package/dist/commands/doctor.d.ts +4 -2
  20. package/dist/commands/doctor.js +21 -9
  21. package/dist/commands/events.d.ts +2 -2
  22. package/dist/commands/events.js +6 -5
  23. package/dist/commands/exec.d.ts +1 -1
  24. package/dist/commands/exec.js +11 -6
  25. package/dist/commands/factory.d.ts +1 -1
  26. package/dist/commands/feed.js +1 -30
  27. package/dist/commands/fleet-capture.d.ts +1 -1
  28. package/dist/commands/fleet-capture.js +4 -5
  29. package/dist/commands/harness.js +4 -52
  30. package/dist/commands/hooks.js +1 -1
  31. package/dist/commands/insights.d.ts +1 -1
  32. package/dist/commands/insights.js +3 -2
  33. package/dist/commands/menubar.js +58 -1
  34. package/dist/commands/monitors.js +1 -1
  35. package/dist/commands/org.d.ts +5 -0
  36. package/dist/commands/org.js +83 -29
  37. package/dist/commands/output.js +2 -2
  38. package/dist/commands/profiles.js +2 -2
  39. package/dist/commands/prune.js +5 -4
  40. package/dist/commands/routines.js +43 -25
  41. package/dist/commands/routines.test-fixture.d.ts +86 -0
  42. package/dist/commands/routines.test-fixture.js +345 -0
  43. package/dist/commands/sessions-picker.js +124 -122
  44. package/dist/commands/sessions.test-fixture.d.ts +24 -0
  45. package/dist/commands/sessions.test-fixture.js +224 -0
  46. package/dist/commands/setup.js +9 -1
  47. package/dist/commands/share.d.ts +9 -10
  48. package/dist/commands/share.js +68 -50
  49. package/dist/commands/skills.js +1 -1
  50. package/dist/commands/snapshot.d.ts +1 -1
  51. package/dist/commands/snapshot.js +4 -4
  52. package/dist/commands/ssh.js +15 -62
  53. package/dist/commands/status.d.ts +4 -2
  54. package/dist/commands/status.js +10 -7
  55. package/dist/commands/subagents.js +1 -1
  56. package/dist/commands/sync.d.ts +1 -0
  57. package/dist/commands/sync.js +29 -2
  58. package/dist/commands/view.js +11 -2
  59. package/dist/commands/webhook.js +1 -1
  60. package/dist/lib/accounting/rotate.d.ts +5 -3
  61. package/dist/lib/accounting/rotate.js +36 -7
  62. package/dist/lib/accounting/usage.d.ts +18 -4
  63. package/dist/lib/accounting/usage.js +138 -27
  64. package/dist/lib/agent-spec/agents.d.ts +7 -7
  65. package/dist/lib/agent-spec/agents.js +15 -15
  66. package/dist/lib/analytics/mix-commands.d.ts +6 -28
  67. package/dist/lib/analytics/mix-commands.js +14 -40
  68. package/dist/lib/audit/log.d.ts +2 -2
  69. package/dist/lib/audit/log.js +2 -2
  70. package/dist/lib/beta.js +1 -1
  71. package/dist/lib/browser/domain-skills.d.ts +26 -6
  72. package/dist/lib/browser/domain-skills.js +81 -43
  73. package/dist/lib/cloud/rush.d.ts +0 -15
  74. package/dist/lib/cloud/rush.js +0 -35
  75. package/dist/lib/crabbox/runtimes.d.ts +6 -2
  76. package/dist/lib/crabbox/runtimes.js +21 -5
  77. package/dist/lib/daemon/daemon.js +97 -58
  78. package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
  79. package/dist/lib/daemon/daemon.test-fixture.js +71 -0
  80. package/dist/lib/daemon/runner.d.ts +18 -1
  81. package/dist/lib/daemon/runner.js +105 -11
  82. package/dist/lib/daemon-ticks.d.ts +38 -1
  83. package/dist/lib/daemon-ticks.js +52 -5
  84. package/dist/lib/device-config.js +1 -2
  85. package/dist/lib/devices/discovery-policy.d.ts +3 -3
  86. package/dist/lib/devices/discovery-policy.js +3 -3
  87. package/dist/lib/devices/doctor-findings.js +1 -1
  88. package/dist/lib/devices/fleet.d.ts +4 -8
  89. package/dist/lib/devices/fleet.js +3 -15
  90. package/dist/lib/devices/health-report.js +4 -4
  91. package/dist/lib/devices/pool.d.ts +0 -6
  92. package/dist/lib/devices/pool.js +0 -6
  93. package/dist/lib/devices/registry.d.ts +0 -15
  94. package/dist/lib/devices/registry.js +0 -9
  95. package/dist/lib/devices/stats-cache.d.ts +12 -0
  96. package/dist/lib/devices/stats-cache.js +24 -5
  97. package/dist/lib/drift-sync.d.ts +1 -1
  98. package/dist/lib/drift-sync.js +2 -2
  99. package/dist/lib/drift.d.ts +1 -1
  100. package/dist/lib/exec.js +13 -3
  101. package/dist/lib/fleet/capture.d.ts +1 -1
  102. package/dist/lib/fleet/manifest.js +1 -1
  103. package/dist/lib/fleet/remote-login.d.ts +2 -2
  104. package/dist/lib/fleet/remote-login.js +3 -4
  105. package/dist/lib/format.d.ts +9 -0
  106. package/dist/lib/format.js +16 -0
  107. package/dist/lib/github/pr-mergeable.d.ts +49 -0
  108. package/dist/lib/github/pr-mergeable.js +157 -0
  109. package/dist/lib/github/pr-verdict.d.ts +67 -0
  110. package/dist/lib/github/pr-verdict.js +72 -0
  111. package/dist/lib/harness/adapter.d.ts +6 -5
  112. package/dist/lib/hosts/dispatch.d.ts +2 -0
  113. package/dist/lib/hosts/dispatch.js +8 -1
  114. package/dist/lib/hosts/passthrough.d.ts +10 -0
  115. package/dist/lib/hosts/passthrough.js +28 -7
  116. package/dist/lib/hosts/providers/devices.js +1 -13
  117. package/dist/lib/hosts/ready.d.ts +12 -3
  118. package/dist/lib/hosts/ready.js +27 -12
  119. package/dist/lib/hosts/registry.d.ts +4 -5
  120. package/dist/lib/hosts/registry.js +3 -7
  121. package/dist/lib/hosts/session-index.js +1 -1
  122. package/dist/lib/installations/shims.js +1 -1
  123. package/dist/lib/installations/versions.d.ts +40 -0
  124. package/dist/lib/installations/versions.js +78 -4
  125. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
  126. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  127. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
  128. package/dist/lib/menubar/install-menubar.d.ts +109 -0
  129. package/dist/lib/menubar/install-menubar.js +218 -12
  130. package/dist/lib/monitors/config.d.ts +2 -2
  131. package/dist/lib/monitors/config.js +3 -2
  132. package/dist/lib/origin-machine.d.ts +18 -0
  133. package/dist/lib/origin-machine.js +34 -0
  134. package/dist/lib/perf/db.js +1 -1
  135. package/dist/lib/perf/spool.js +1 -1
  136. package/dist/lib/prix-account.d.ts +2 -1
  137. package/dist/lib/prix-account.js +2 -1
  138. package/dist/lib/profiles.d.ts +7 -0
  139. package/dist/lib/profiles.js +26 -1
  140. package/dist/lib/remote-agents-json.js +1 -7
  141. package/dist/lib/routines-placement.d.ts +1 -1
  142. package/dist/lib/routines-placement.js +1 -1
  143. package/dist/lib/sandbox.d.ts +24 -0
  144. package/dist/lib/sandbox.js +101 -0
  145. package/dist/lib/scheduling/routines.d.ts +18 -9
  146. package/dist/lib/scheduling/routines.js +16 -8
  147. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  148. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  149. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  150. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  151. package/dist/lib/secrets/agent.js +9 -1
  152. package/dist/lib/secrets/index.js +1 -1
  153. package/dist/lib/secrets/list-filter.js +1 -1
  154. package/dist/lib/service-manifest.d.ts +20 -0
  155. package/dist/lib/service-manifest.js +34 -0
  156. package/dist/lib/session/active.d.ts +4 -16
  157. package/dist/lib/session/active.js +2 -6
  158. package/dist/lib/session/cloud.js +1 -1
  159. package/dist/lib/session/db.d.ts +13 -98
  160. package/dist/lib/session/db.js +5 -12
  161. package/dist/lib/session/discover.d.ts +5 -81
  162. package/dist/lib/session/discover.js +5 -5
  163. package/dist/lib/session/fork.js +1 -1
  164. package/dist/lib/session/live-metadata.js +1 -1
  165. package/dist/lib/session/origin-machine.d.ts +1 -18
  166. package/dist/lib/session/origin-machine.js +4 -34
  167. package/dist/lib/session/relative-time.d.ts +1 -44
  168. package/dist/lib/session/relative-time.js +4 -120
  169. package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
  170. package/dist/lib/session/remote/remote-bundle.js +61 -0
  171. package/dist/lib/session/remote/remote-list.d.ts +148 -0
  172. package/dist/lib/session/remote/remote-list.js +607 -0
  173. package/dist/lib/session/remote/remote.d.ts +132 -0
  174. package/dist/lib/session/remote/remote.js +314 -0
  175. package/dist/lib/session/remote/watch.d.ts +101 -0
  176. package/dist/lib/session/remote/watch.js +241 -0
  177. package/dist/lib/session/remote-bundle.d.ts +1 -12
  178. package/dist/lib/session/remote-bundle.js +3 -61
  179. package/dist/lib/session/remote-list.d.ts +1 -148
  180. package/dist/lib/session/remote-list.js +3 -607
  181. package/dist/lib/session/remote.d.ts +1 -132
  182. package/dist/lib/session/remote.js +3 -314
  183. package/dist/lib/session/short-id.d.ts +1 -17
  184. package/dist/lib/session/short-id.js +4 -20
  185. package/dist/lib/session/watch.d.ts +1 -101
  186. package/dist/lib/session/watch.js +3 -242
  187. package/dist/lib/session/width.d.ts +1 -29
  188. package/dist/lib/session/width.js +4 -101
  189. package/dist/lib/share/config.d.ts +12 -2
  190. package/dist/lib/share/config.js +47 -11
  191. package/dist/lib/share/delete.js +1 -1
  192. package/dist/lib/share/publish.js +1 -1
  193. package/dist/lib/share/worker-template.js +2 -2
  194. package/dist/lib/smart-launch.d.ts +4 -4
  195. package/dist/lib/smart-launch.js +8 -18
  196. package/dist/lib/snapshot.d.ts +2 -2
  197. package/dist/lib/snapshot.js +1 -1
  198. package/dist/lib/staleness/index.d.ts +3 -1
  199. package/dist/lib/staleness/index.js +21 -1
  200. package/dist/lib/staleness/types.d.ts +11 -0
  201. package/dist/lib/staleness/writers/commands.js +13 -4
  202. package/dist/lib/staleness/writers/hooks.js +3 -1
  203. package/dist/lib/staleness/writers/mcp.d.ts +0 -7
  204. package/dist/lib/staleness/writers/mcp.js +16 -1
  205. package/dist/lib/staleness/writers/rules.js +1 -1
  206. package/dist/lib/staleness/writers/skills.js +3 -1
  207. package/dist/lib/staleness/writers/subagents.d.ts +0 -9
  208. package/dist/lib/staleness/writers/subagents.js +18 -1
  209. package/dist/lib/staleness/writers/types.d.ts +10 -0
  210. package/dist/lib/startup/command-registry.d.ts +10 -1
  211. package/dist/lib/startup/command-registry.js +26 -7
  212. package/dist/lib/startup/root-command.d.ts +18 -1
  213. package/dist/lib/startup/root-command.js +18 -1
  214. package/dist/lib/state.js +7 -0
  215. package/dist/lib/teams/placement-probe.js +28 -13
  216. package/dist/lib/teams/scheduler.d.ts +3 -1
  217. package/dist/lib/teams/scheduler.js +1 -1
  218. package/dist/lib/text/relative-time.d.ts +44 -0
  219. package/dist/lib/text/relative-time.js +120 -0
  220. package/dist/lib/text/short-id.d.ts +17 -0
  221. package/dist/lib/text/short-id.js +20 -0
  222. package/dist/lib/text/width.d.ts +29 -0
  223. package/dist/lib/text/width.js +101 -0
  224. package/dist/lib/triggers/handlers.d.ts +7 -0
  225. package/dist/lib/triggers/handlers.js +8 -0
  226. package/dist/lib/triggers/webhook.d.ts +1 -0
  227. package/dist/lib/triggers/webhook.js +2 -1
  228. package/dist/lib/types.d.ts +1 -1
  229. package/dist/lib/view-types.d.ts +7 -0
  230. package/package.json +2 -3
  231. package/dist/bin/agents +0 -0
  232. package/dist/commands/serve.d.ts +0 -10
  233. package/dist/commands/serve.js +0 -68
  234. package/dist/commands/tickets.d.ts +0 -2
  235. package/dist/commands/tickets.js +0 -43
  236. package/dist/commands/trends.d.ts +0 -10
  237. package/dist/commands/trends.js +0 -12
  238. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  239. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  240. package/dist/lib/observe-aliases.d.ts +0 -30
  241. package/dist/lib/observe-aliases.js +0 -38
  242. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  243. package/dist/lib/serve/control.d.ts +0 -95
  244. package/dist/lib/serve/control.js +0 -260
  245. package/dist/lib/serve/data.d.ts +0 -81
  246. package/dist/lib/serve/data.js +0 -91
  247. package/dist/lib/serve/page.d.ts +0 -7
  248. package/dist/lib/serve/page.js +0 -140
  249. package/dist/lib/serve/server.d.ts +0 -80
  250. package/dist/lib/serve/server.js +0 -145
  251. package/dist/lib/serve/stream.d.ts +0 -43
  252. package/dist/lib/serve/stream.js +0 -116
  253. package/dist/lib/serve/token.d.ts +0 -35
  254. package/dist/lib/serve/token.js +0 -85
  255. package/dist/lib/tickets/list.d.ts +0 -53
  256. package/dist/lib/tickets/list.js +0 -153
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Human-readable "time since" formatting for session timestamps. Lives here (not
3
+ * inline in `sessions.ts`) so both the session list renderer and the remote
4
+ * offline-cache banner (`remote.ts`) share one formatter — `sessions.ts` imports
5
+ * `remote.ts`, so a back-import would cycle.
6
+ */
7
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
8
+ /**
9
+ * Calendar label for a timestamp older than the relative window. A 2-digit year
10
+ * is appended outside the current year so "Jun 28" is never ambiguous across
11
+ * years (e.g. "Jun 28 '25").
12
+ */
13
+ function calendarLabel(thenMs, nowMs) {
14
+ const d = new Date(thenMs);
15
+ const label = `${MONTHS[d.getMonth()]} ${d.getDate()}`;
16
+ return d.getFullYear() === new Date(nowMs).getFullYear()
17
+ ? label
18
+ : `${label} '${String(d.getFullYear()).slice(-2)}`;
19
+ }
20
+ export function formatRelativeTime(isoTimestamp) {
21
+ const now = Date.now();
22
+ const then = new Date(isoTimestamp).getTime();
23
+ if (isNaN(then))
24
+ return isoTimestamp;
25
+ const diffMs = now - then;
26
+ const diffMin = Math.floor(diffMs / 60_000);
27
+ const diffHrs = Math.floor(diffMs / 3_600_000);
28
+ const diffDays = Math.floor(diffMs / 86_400_000);
29
+ if (diffMin < 1)
30
+ return 'just now';
31
+ if (diffMin < 60)
32
+ return `${diffMin} min ago`;
33
+ if (diffHrs < 24)
34
+ return `${diffHrs} hour${diffHrs === 1 ? '' : 's'} ago`;
35
+ if (diffDays < 7)
36
+ return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
37
+ // Older: show the date.
38
+ return calendarLabel(then, now);
39
+ }
40
+ /**
41
+ * Compact age — `now`, `45m`, `3h`, `2d`, then the calendar date. This is the
42
+ * *creation* half of a session's time cell: it renders right next to the long
43
+ * last-activity label, and one row can afford one long form, not two.
44
+ */
45
+ export function formatCompactAge(isoTimestamp) {
46
+ const now = Date.now();
47
+ const then = new Date(isoTimestamp).getTime();
48
+ if (isNaN(then))
49
+ return isoTimestamp;
50
+ const diffMs = now - then;
51
+ const diffMin = Math.floor(diffMs / 60_000);
52
+ const diffHrs = Math.floor(diffMs / 3_600_000);
53
+ const diffDays = Math.floor(diffMs / 86_400_000);
54
+ if (diffMin < 1)
55
+ return 'now';
56
+ if (diffMin < 60)
57
+ return `${diffMin}m`;
58
+ if (diffHrs < 24)
59
+ return `${diffHrs}h`;
60
+ if (diffDays < 7)
61
+ return `${diffDays}d`;
62
+ return calendarLabel(then, now);
63
+ }
64
+ /**
65
+ * Shortest creation→last-activity gap worth rendering as two fields. Below it
66
+ * both halves name the same minute, so the row would carry a duplicate instead
67
+ * of information.
68
+ */
69
+ const SPAN_MIN_MS = 60_000;
70
+ /**
71
+ * Split a session's timestamps into the two fields a listing renders: when it
72
+ * was created and when it was last active. One last-activity label answers
73
+ * neither "is this the old session I'm looking for" nor "how long did it run" —
74
+ * a session touched an hour ago may have started last week.
75
+ */
76
+ export function sessionAgeParts(createdIso, lastActivityIso) {
77
+ const lastIso = lastActivityIso ?? createdIso;
78
+ const last = formatRelativeTime(lastIso);
79
+ const createdMs = new Date(createdIso).getTime();
80
+ const lastMs = new Date(lastIso).getTime();
81
+ if (isNaN(createdMs) || isNaN(lastMs) || lastMs - createdMs < SPAN_MIN_MS)
82
+ return { last };
83
+ return { created: formatCompactAge(createdIso), last };
84
+ }
85
+ /**
86
+ * Parse a time filter string (relative like '7d' or an ISO timestamp) into epoch
87
+ * milliseconds. Backs `--since` on `sessions`, `cost`, `teams`, and `output`, and
88
+ * `--unused` on `secrets list`.
89
+ *
90
+ * It lives here, in a module with no imports, rather than in `discover.ts` where
91
+ * it started: `discover.ts` loads `../sqlite.js`, so importing this one function
92
+ * from there pulls `node:sqlite` into the caller's module graph and Node prints
93
+ * `ExperimentalWarning: SQLite …` on stderr. That is invisible in a command that
94
+ * already touches the session DB, and a broken contract in one that doesn't —
95
+ * `monitors --json` asserts a clean stderr. `discover.ts` re-exports it, so
96
+ * existing importers are unaffected.
97
+ */
98
+ export function parseTimeFilter(input) {
99
+ // Units: m=minute, h=hour, d=day, w=week, mo=month(30d), y=year(365d). `mo`
100
+ // must precede the single-letter alternatives so "1mo" isn't read as "1m"+"o".
101
+ const relativeMatch = input.match(/^(\d+)(mo|[mhdwy])$/i);
102
+ if (relativeMatch) {
103
+ const value = parseInt(relativeMatch[1], 10);
104
+ const unit = relativeMatch[2].toLowerCase();
105
+ if (unit === 'm')
106
+ return Date.now() - value * 60_000;
107
+ if (unit === 'h')
108
+ return Date.now() - value * 3_600_000;
109
+ if (unit === 'd')
110
+ return Date.now() - value * 86_400_000;
111
+ if (unit === 'w')
112
+ return Date.now() - value * 7 * 86_400_000;
113
+ if (unit === 'mo')
114
+ return Date.now() - value * 30 * 86_400_000;
115
+ if (unit === 'y')
116
+ return Date.now() - value * 365 * 86_400_000;
117
+ }
118
+ const ts = new Date(input).getTime();
119
+ return Number.isNaN(ts) ? 0 : ts;
120
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Canonical derivation of a session's 8-char `shortId`.
3
+ *
4
+ * Every session parser used to inline `id.slice(0, 8)` — some with a
5
+ * `.replace(prefix, '')` strip first (`session_`, `api-`, `ses_`). That strip is
6
+ * the bug: an id that is *only* its prefix (a bare `session_` directory, an id of
7
+ * exactly `api-`) strips to `''`, and `''.slice(0, 8)` is still `''`. An empty
8
+ * shortId then passes the `short_id TEXT NOT NULL` constraint (empty string is not
9
+ * NULL) yet matches nothing in the `short_id LIKE ?` picker lookups — a silently
10
+ * corrupt index row.
11
+ *
12
+ * This helper is the single source of truth: it strips the optional prefix, takes
13
+ * the first 8 chars, and — the whole point — guarantees a NON-EMPTY result by
14
+ * falling back to the unstripped id when the strip empties it, so a row always
15
+ * stays addressable by its shortId.
16
+ */
17
+ export declare function deriveShortId(id: string, stripPrefix?: RegExp): string;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Canonical derivation of a session's 8-char `shortId`.
3
+ *
4
+ * Every session parser used to inline `id.slice(0, 8)` — some with a
5
+ * `.replace(prefix, '')` strip first (`session_`, `api-`, `ses_`). That strip is
6
+ * the bug: an id that is *only* its prefix (a bare `session_` directory, an id of
7
+ * exactly `api-`) strips to `''`, and `''.slice(0, 8)` is still `''`. An empty
8
+ * shortId then passes the `short_id TEXT NOT NULL` constraint (empty string is not
9
+ * NULL) yet matches nothing in the `short_id LIKE ?` picker lookups — a silently
10
+ * corrupt index row.
11
+ *
12
+ * This helper is the single source of truth: it strips the optional prefix, takes
13
+ * the first 8 chars, and — the whole point — guarantees a NON-EMPTY result by
14
+ * falling back to the unstripped id when the strip empties it, so a row always
15
+ * stays addressable by its shortId.
16
+ */
17
+ export function deriveShortId(id, stripPrefix) {
18
+ const stripped = stripPrefix ? id.replace(stripPrefix, '') : id;
19
+ return stripped.slice(0, 8) || id.slice(0, 8) || id;
20
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Terminal display-width helpers.
3
+ *
4
+ * `String.length` is the wrong ruler for a terminal: it over-counts ANSI colour
5
+ * escapes (chalk output) and under-counts wide glyphs (CJK, emoji) which occupy
6
+ * two cells. The result is the drifting, wrapping session-table line users see
7
+ * under tmux and over `--device` SSH. Every renderer that sizes a session-table
8
+ * cell measures and truncates through this module so alignment is computed once,
9
+ * correctly, from the same source of truth.
10
+ */
11
+ /** Strip SGR colour + OSC-8 hyperlink escapes so width is measured on visible characters only. */
12
+ export declare function stripAnsi(s: string): string;
13
+ /** Visible display width of a string, ANSI-aware and wide-char-aware. */
14
+ export declare function stringWidth(s: string): number;
15
+ /**
16
+ * Truncate to a target display width, appending '…' when shortened. Operates on
17
+ * the visible (ANSI-stripped) string; callers colour the result afterwards so
18
+ * the ellipsis is never inserted mid-escape.
19
+ */
20
+ export declare function truncateToWidth(s: string, max: number): string;
21
+ /** Right-pad with spaces to a target display width. Never truncates. */
22
+ export declare function padToWidth(s: string, width: number): string;
23
+ /**
24
+ * Effective terminal width. Reads `$COLUMNS` first so it survives tmux and
25
+ * `--device` SSH (where `process.stdout.columns` is unset or wrong), falls back to
26
+ * the TTY's reported width, then to `fallback`. Clamped to a sane band so a
27
+ * bogus value can't produce a 0-wide or absurdly long table.
28
+ */
29
+ export declare function terminalWidth(fallback?: number): number;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Terminal display-width helpers.
3
+ *
4
+ * `String.length` is the wrong ruler for a terminal: it over-counts ANSI colour
5
+ * escapes (chalk output) and under-counts wide glyphs (CJK, emoji) which occupy
6
+ * two cells. The result is the drifting, wrapping session-table line users see
7
+ * under tmux and over `--device` SSH. Every renderer that sizes a session-table
8
+ * cell measures and truncates through this module so alignment is computed once,
9
+ * correctly, from the same source of truth.
10
+ */
11
+ /** SGR colour sequences emitted by chalk (e.g. `\x1b[32m`). */
12
+ const SGR_REGEX = /\x1b\[[0-9;]*m/g;
13
+ /**
14
+ * OSC-8 hyperlink sequences (`\x1b]8;;<url>\x1b\\` … `\x1b]8;;\x1b\\`, or with a
15
+ * BEL `\x07` terminator) emitted by `osc8()` in render.ts for clickable
16
+ * ticket/PR/path badges. These carry the URL, which is NOT visible — leaving it
17
+ * in over-counts width and, worse, lets a truncation land *inside* the escape and
18
+ * corrupt it. Stripping both the opener and closer leaves only the visible label
19
+ * between them, so width is measured on what the user sees and `truncateToWidth`
20
+ * can never cut a hyperlink in half.
21
+ */
22
+ const OSC8_REGEX = /\x1b\]8;[^;]*;.*?(?:\x1b\\|\x07)/g;
23
+ /** Strip SGR colour + OSC-8 hyperlink escapes so width is measured on visible characters only. */
24
+ export function stripAnsi(s) {
25
+ return s.replace(OSC8_REGEX, '').replace(SGR_REGEX, '');
26
+ }
27
+ /**
28
+ * Display cells for one code point: 0 for zero-width combining/ZWJ/variation
29
+ * selectors, 2 for East-Asian-wide and emoji ranges, 1 otherwise. Compact and
30
+ * dependency-free — covers the glyphs that actually show up in prompts/titles.
31
+ */
32
+ function charWidth(cp) {
33
+ if (cp === 0)
34
+ return 0;
35
+ // Zero-width: combining marks, zero-width joiner, variation selectors.
36
+ if ((cp >= 0x0300 && cp <= 0x036f) ||
37
+ cp === 0x200b || cp === 0x200d ||
38
+ (cp >= 0xfe00 && cp <= 0xfe0f))
39
+ return 0;
40
+ // Wide (2 cells): CJK, Hangul, fullwidth forms, emoji & pictographs.
41
+ if ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
42
+ (cp >= 0x2e80 && cp <= 0xa4cf) || // CJK radicals … Yi
43
+ (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables
44
+ (cp >= 0xf900 && cp <= 0xfaff) || // CJK compatibility ideographs
45
+ (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK compatibility forms
46
+ (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth forms
47
+ (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth signs
48
+ (cp >= 0x1f300 && cp <= 0x1faff) || // emoji & symbols
49
+ (cp >= 0x20000 && cp <= 0x3fffd) // CJK Ext-B and beyond
50
+ )
51
+ return 2;
52
+ return 1;
53
+ }
54
+ /** Visible display width of a string, ANSI-aware and wide-char-aware. */
55
+ export function stringWidth(s) {
56
+ const plain = stripAnsi(s);
57
+ let w = 0;
58
+ for (const ch of plain)
59
+ w += charWidth(ch.codePointAt(0));
60
+ return w;
61
+ }
62
+ /**
63
+ * Truncate to a target display width, appending '…' when shortened. Operates on
64
+ * the visible (ANSI-stripped) string; callers colour the result afterwards so
65
+ * the ellipsis is never inserted mid-escape.
66
+ */
67
+ export function truncateToWidth(s, max) {
68
+ if (max <= 0)
69
+ return '';
70
+ const plain = stripAnsi(s);
71
+ if (stringWidth(plain) <= max)
72
+ return plain;
73
+ let w = 0;
74
+ let out = '';
75
+ for (const ch of plain) {
76
+ const cw = charWidth(ch.codePointAt(0));
77
+ if (w + cw > max - 1)
78
+ break; // reserve one cell for the ellipsis
79
+ out += ch;
80
+ w += cw;
81
+ }
82
+ return out + '…';
83
+ }
84
+ /** Right-pad with spaces to a target display width. Never truncates. */
85
+ export function padToWidth(s, width) {
86
+ const pad = width - stringWidth(s);
87
+ return pad > 0 ? s + ' '.repeat(pad) : s;
88
+ }
89
+ /**
90
+ * Effective terminal width. Reads `$COLUMNS` first so it survives tmux and
91
+ * `--device` SSH (where `process.stdout.columns` is unset or wrong), falls back to
92
+ * the TTY's reported width, then to `fallback`. Clamped to a sane band so a
93
+ * bogus value can't produce a 0-wide or absurdly long table.
94
+ */
95
+ export function terminalWidth(fallback = 100) {
96
+ const env = Number.parseInt(process.env.COLUMNS ?? '', 10);
97
+ const raw = Number.isFinite(env) && env > 0
98
+ ? env
99
+ : (process.stdout.columns || fallback);
100
+ return Math.max(60, Math.min(200, raw));
101
+ }
@@ -116,6 +116,13 @@ export interface SlackMessageContext {
116
116
  user: string;
117
117
  /** Slash command name (`/agents`), or '' for an event delivery. */
118
118
  command: string;
119
+ /**
120
+ * Slash-command reply URL (`https://hooks.slack.com/commands/…`), or '' for an
121
+ * event delivery. Slack accepts a POST here for 30 minutes with no token and no
122
+ * channel membership, so it is the only reply path that works before the app has
123
+ * been invited to a channel.
124
+ */
125
+ response_url: string;
119
126
  }
120
127
  /**
121
128
  * Split a Slack message into a `{{slack.*}}` context. Strips a leading bot
@@ -294,6 +294,7 @@ export function parseSlackMessage(payload) {
294
294
  thread_ts: asString(payload.thread_ts),
295
295
  user: asString(payload.user),
296
296
  command: asString(payload.command),
297
+ response_url: asString(payload.response_url),
297
298
  };
298
299
  }
299
300
  /**
@@ -414,6 +415,13 @@ async function executeHandlerAction(handler, webhook, context, opts) {
414
415
  ...(substitutedCwd ? { cwd: substitutedCwd } : {}),
415
416
  ...(hostFields.host ? { host: hostFields.host } : {}),
416
417
  ...(hostFields.hostStrategy ? { hostStrategy: hostFields.hostStrategy } : {}),
418
+ // A handler is not a routine, so its name can never appear in this device's
419
+ // routine activation manifest — without this marker the gate answered "not
420
+ // activated here" and every delivery was skipped while the receiver logged
421
+ // `fired` (the same shape as RUSH-2681's monitor bug). The `routine:`
422
+ // delegate below deliberately omits it: that fires a REAL routine, which
423
+ // keeps its own activation gate.
424
+ dispatchedBy: 'webhook',
417
425
  };
418
426
  const dispatch = handler.run.agent
419
427
  ? (opts.dispatchAgent ?? dispatchDefault)
@@ -237,6 +237,7 @@ export interface WebhookServerOptions {
237
237
  * Start a localhost-bound receiver. It accepts only:
238
238
  * POST /hooks/github with X-Hub-Signature-256
239
239
  * POST /hooks/linear with Linear-Signature + fresh webhookTimestamp
240
+ * POST /hooks/slack with X-Slack-Signature + fresh X-Slack-Request-Timestamp
240
241
  *
241
242
  * **The ack is asynchronous (RUSH-2548).** Once a delivery has passed signature
242
243
  * verification, freshness, dedup, and rate limiting, the receiver writes
@@ -516,6 +516,7 @@ export function waitForListening(server) {
516
516
  * Start a localhost-bound receiver. It accepts only:
517
517
  * POST /hooks/github with X-Hub-Signature-256
518
518
  * POST /hooks/linear with Linear-Signature + fresh webhookTimestamp
519
+ * POST /hooks/slack with X-Slack-Signature + fresh X-Slack-Request-Timestamp
519
520
  *
520
521
  * **The ack is asynchronous (RUSH-2548).** Once a delivery has passed signature
521
522
  * verification, freshness, dedup, and rate limiting, the receiver writes
@@ -689,7 +690,7 @@ export function startWebhookServer(options) {
689
690
  inFlight.add(slackId);
690
691
  res.writeHead(200, { 'content-type': 'application/json' });
691
692
  res.end(slack.type === 'slash_command'
692
- ? JSON.stringify({ response_type: 'ephemeral', text: 'On it — replying in this thread.' })
693
+ ? JSON.stringify({ response_type: 'ephemeral', text: 'On it — replying in this channel.' })
693
694
  : '');
694
695
  void settleDelivery(slackWebhook, slackId).finally(() => inFlight.delete(slackId));
695
696
  return;
@@ -80,7 +80,7 @@ export interface BudgetConfig {
80
80
  */
81
81
  require_confirm_over?: number;
82
82
  }
83
- /** Preview features that users can opt into via `agents beta`. */
83
+ /** Preview features that users can opt into via `agents setup beta`. */
84
84
  export type BetaFeatureName = 'factory';
85
85
  /** Subset of chalk color names used for agent-specific terminal output. */
86
86
  export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
@@ -1,6 +1,7 @@
1
1
  import type { ConfiguredModelSource } from './models.js';
2
2
  import type { ProfileSummary } from './profiles.js';
3
3
  import type { AgentId } from './types.js';
4
+ import type { AuthVerdict } from './auth-health.js';
4
5
  export type SyncState = 'synced' | 'new' | 'modified' | 'deleted';
5
6
  export interface ViewJsonVersion {
6
7
  version: string;
@@ -8,6 +9,8 @@ export interface ViewJsonVersion {
8
9
  isolated: boolean;
9
10
  isIsolatedDefault: boolean;
10
11
  signedIn: boolean;
12
+ /** Live cached authentication verdict for this installed version. */
13
+ authVerdict: AuthVerdict | null;
11
14
  email: string | null;
12
15
  accountId?: string | null;
13
16
  organizationType?: string | null;
@@ -24,6 +27,10 @@ export interface ViewJsonVersion {
24
27
  usedPercent: number;
25
28
  resetsAt: string | null;
26
29
  }>;
30
+ unavailable?: {
31
+ reason: 'session_limit';
32
+ resetsAt: string;
33
+ };
27
34
  lastActive: string | null;
28
35
  path: string;
29
36
  configuredModel?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.42",
3
+ "version": "1.22.44",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,6 @@
25
25
  "dist/**/*.d.ts",
26
26
  "dist/**/*.json",
27
27
  "dist/**/*.sh",
28
- "dist/bin/**",
29
28
  "dist/lib/secrets/Agents CLI.app/**",
30
29
  "dist/lib/menubar/MenubarHelper.app/**",
31
30
  "scripts/postinstall.js",
@@ -49,7 +48,7 @@
49
48
  "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -d 'bin/Agents CLI.app' ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true) && ([ -f 'bin/agents-macos' ] && mkdir -p dist/bin && cp -f 'bin/agents-macos' 'dist/bin/agents' && chmod 755 'dist/bin/agents' || true)",
50
49
  "build:bin": "scripts/build-bin.sh",
51
50
  "prepare": "npm run build",
52
- "prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh && scripts/verify-cli-binary.sh",
51
+ "prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
53
52
  "postinstall": "node scripts/postinstall.js",
54
53
  "dev": "tsx src/index.ts",
55
54
  "changelog": "bun scripts/gen-changelog.ts",
package/dist/bin/agents DELETED
Binary file
@@ -1,10 +0,0 @@
1
- /**
2
- * `agents serve` — read-only local web companion.
3
- *
4
- * Boots a localhost-only (127.0.0.1) HTTP + SSE server that renders a live
5
- * dashboard of team status + per-worktree diffs, scheduled routines, and cloud
6
- * tasks. Everything is a viewer over data other commands already own — there
7
- * are no mutation endpoints. See src/lib/serve/.
8
- */
9
- import type { Command } from 'commander';
10
- export declare function registerServeCommand(program: Command): void;
@@ -1,68 +0,0 @@
1
- import chalk from 'chalk';
2
- import { startServeServer, DEFAULT_SERVE_PORT, SERVE_HOST } from '../lib/serve/server.js';
3
- import { startControlServer } from '../lib/serve/control.js';
4
- import { ensureControlToken } from '../lib/serve/token.js';
5
- export function registerServeCommand(program) {
6
- program
7
- .command('serve')
8
- .description('Read-only local web companion: team diffs, routines, and cloud status (binds 127.0.0.1 only).')
9
- .option('-p, --port <n>', `Port to bind on ${SERVE_HOST}`, String(DEFAULT_SERVE_PORT))
10
- .option('--interval <ms>', 'SSE refresh cadence in milliseconds', '3000')
11
- .option('--control', 'Authenticated control mode (RUSH-1731): adds bearer-gated POST /api/run and /api/session/:id/message so the iOS cockpit can dispatch + steer. Enables binding beyond loopback — pair a device with `agents devices pair-ios`.', false)
12
- .option('--bind <addr>', 'Address to bind in --control mode (e.g. your tailnet IP, or 0.0.0.0). Ignored without --control; read-only serve is always loopback.', SERVE_HOST)
13
- .action(async (opts) => {
14
- const port = parseInt(opts.port ?? '', 10) || DEFAULT_SERVE_PORT;
15
- const intervalMs = parseInt(opts.interval ?? '', 10) || 3000;
16
- try {
17
- if (opts.control) {
18
- const bind = opts.bind || SERVE_HOST;
19
- const minted = ensureControlToken('default');
20
- const { server, port: bound } = await startControlServer(port, bind, {
21
- cwd: process.cwd(),
22
- intervalMs,
23
- });
24
- const url = `http://${bind}:${bound}`;
25
- console.log(`${chalk.green('agents serve --control')} ${chalk.dim('→')} ${chalk.cyan(url)}`);
26
- if (minted.created) {
27
- console.log(chalk.yellow('New control token (shown once — store it in the cockpit now):'));
28
- console.log(` ${chalk.bold(minted.token)} ${chalk.dim(`(id ${minted.id})`)}`);
29
- }
30
- else {
31
- console.log(chalk.dim('Using existing control token(s). Issue another with `agents devices pair-ios`.'));
32
- }
33
- if (bind !== SERVE_HOST) {
34
- console.log(chalk.dim('bearer-gated · bind beyond loopback — keep this on the tailnet, never public Funnel · Ctrl-C to stop'));
35
- }
36
- else {
37
- console.log(chalk.dim('bearer-gated · loopback (pass --bind <tailnet-ip> to reach it from the phone) · Ctrl-C to stop'));
38
- }
39
- const shutdown = () => server.close(() => process.exit(0));
40
- process.on('SIGINT', shutdown);
41
- process.on('SIGTERM', shutdown);
42
- return;
43
- }
44
- const { server, port: bound } = await startServeServer(port, {
45
- cwd: process.cwd(),
46
- intervalMs,
47
- });
48
- const url = `http://${SERVE_HOST}:${bound}`;
49
- console.log(`${chalk.green('agents serve')} ${chalk.dim('→')} ${chalk.cyan(url)}`);
50
- console.log(chalk.dim('read-only · localhost only · Ctrl-C to stop'));
51
- const shutdown = () => {
52
- server.close(() => process.exit(0));
53
- };
54
- process.on('SIGINT', shutdown);
55
- process.on('SIGTERM', shutdown);
56
- }
57
- catch (err) {
58
- const msg = String(err?.message ?? err);
59
- if (msg.includes('EADDRINUSE')) {
60
- console.error(chalk.red(`Port ${port} is already in use. Pass --port <n> to pick another.`));
61
- }
62
- else {
63
- console.error(chalk.red(`Could not start serve: ${msg}`));
64
- }
65
- process.exit(1);
66
- }
67
- });
68
- }
@@ -1,2 +0,0 @@
1
- import type { Command } from 'commander';
2
- export declare function registerTicketsCommand(program: Command): void;
@@ -1,43 +0,0 @@
1
- import { setHelpSections } from '../lib/help.js';
2
- import { listTickets } from '../lib/tickets/list.js';
3
- export function registerTicketsCommand(program) {
4
- const tickets = program
5
- .command('tickets')
6
- .description('Read work items from the trackers linked to this workspace.');
7
- setHelpSections(tickets, {
8
- examples: `
9
- agents tickets list --json
10
- agents tickets list --github-assigned-only --json
11
- `,
12
- notes: `
13
- The response reports each source's availability explicitly. A failed tracker
14
- never makes another tracker's rows disappear.
15
- `,
16
- });
17
- tickets
18
- .command('list')
19
- .description('List workspace tickets from Linear and GitHub.')
20
- .option('--cwd <path>', 'Workspace used to resolve tracker context')
21
- .option('--no-linear', 'Exclude Linear tickets')
22
- .option('--no-github', 'Exclude GitHub issues')
23
- .option('--github-assigned-only', 'Only include GitHub issues assigned to the current user')
24
- .option('--json', 'Output machine-readable tickets, cycle metadata, and source availability')
25
- .action(async (options) => {
26
- const result = await listTickets({
27
- cwd: options.cwd ?? process.cwd(),
28
- linear: options.linear,
29
- github: options.github,
30
- githubAssignedOnly: options.githubAssignedOnly === true,
31
- });
32
- if (options.json) {
33
- console.log(JSON.stringify(result, null, 2));
34
- return;
35
- }
36
- console.log(`${result.tickets.length} ticket${result.tickets.length === 1 ? '' : 's'}`);
37
- for (const source of ['linear', 'github']) {
38
- const status = result.sources[source];
39
- if (!status.available && status.error)
40
- console.error(`${source}: ${status.error}`);
41
- }
42
- });
43
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Deprecated top-level spelling of the counter-mix tree.
3
- *
4
- * Implementation lives under `agents insights mix` (see
5
- * `lib/analytics/mix-commands.ts`). This file only registers the thin alias so
6
- * old scripts and agent briefs keep working while printing one deprecation line
7
- * — no second recipe implementation.
8
- */
9
- import type { Command } from 'commander';
10
- export declare function registerTrendsCommand(program: Command): void;
@@ -1,12 +0,0 @@
1
- /**
2
- * Deprecated top-level spelling of the counter-mix tree.
3
- *
4
- * Implementation lives under `agents insights mix` (see
5
- * `lib/analytics/mix-commands.ts`). This file only registers the thin alias so
6
- * old scripts and agent briefs keep working while printing one deprecation line
7
- * — no second recipe implementation.
8
- */
9
- import { registerDeprecatedTrendsAlias } from '../lib/analytics/mix-commands.js';
10
- export function registerTrendsCommand(program) {
11
- registerDeprecatedTrendsAlias(program);
12
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Observe-umbrella aliases (Phase 3 surface consolidation).
3
- *
4
- * Thin name → real command expansion. No store merge — feed / sessions / events
5
- * remain the stores; these are doors that point at the right reader.
6
- *
7
- * inbox → feed (needs-you default)
8
- *
9
- * `roster` was removed — use `agents sessions --active`.
10
- * `timeline` was removed — use `agents feed --filter updates` (RUSH-2692).
11
- * `audit` is NOT an alias here — `agents audit` is already the tamper-evident
12
- * run-dispatch log. Ops trail = `agents events` (optionally `--audit`).
13
- */
14
- export type ObserveAlias = 'inbox';
15
- export declare const OBSERVE_ALIASES: readonly ObserveAlias[];
16
- export interface ObserveExpandResult {
17
- /** argv for the real command (no program name): e.g. ['feed', '--filter', 'updates'] */
18
- argv: string[];
19
- /** One-line note for stderr (optional); empty when silent. */
20
- note: string;
21
- }
22
- /** True when `rest` already carries a `--filter` / `--filter=…` flag. */
23
- export declare function hasFilterFlag(rest: readonly string[]): boolean;
24
- /** True when `rest` already carries `--active`. */
25
- export declare function hasActiveFlag(rest: readonly string[]): boolean;
26
- /**
27
- * Expand an observe alias + remaining user args into the real command argv.
28
- * Returns null when `alias` is not an observe alias.
29
- */
30
- export declare function expandObserveAlias(alias: string, rest?: readonly string[]): ObserveExpandResult | null;
@@ -1,38 +0,0 @@
1
- /**
2
- * Observe-umbrella aliases (Phase 3 surface consolidation).
3
- *
4
- * Thin name → real command expansion. No store merge — feed / sessions / events
5
- * remain the stores; these are doors that point at the right reader.
6
- *
7
- * inbox → feed (needs-you default)
8
- *
9
- * `roster` was removed — use `agents sessions --active`.
10
- * `timeline` was removed — use `agents feed --filter updates` (RUSH-2692).
11
- * `audit` is NOT an alias here — `agents audit` is already the tamper-evident
12
- * run-dispatch log. Ops trail = `agents events` (optionally `--audit`).
13
- */
14
- export const OBSERVE_ALIASES = ['inbox'];
15
- /** True when `rest` already carries a `--filter` / `--filter=…` flag. */
16
- export function hasFilterFlag(rest) {
17
- return rest.some((a) => a === '--filter' || a.startsWith('--filter='));
18
- }
19
- /** True when `rest` already carries `--active`. */
20
- export function hasActiveFlag(rest) {
21
- return rest.some((a) => a === '--active');
22
- }
23
- /**
24
- * Expand an observe alias + remaining user args into the real command argv.
25
- * Returns null when `alias` is not an observe alias.
26
- */
27
- export function expandObserveAlias(alias, rest = []) {
28
- const tail = [...rest];
29
- switch (alias) {
30
- case 'inbox':
31
- return {
32
- argv: ['feed', ...tail],
33
- note: 'agents inbox → agents feed (needs-you inbox)',
34
- };
35
- default:
36
- return null;
37
- }
38
- }