@phnx-labs/agents-cli 1.15.0 → 1.17.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +143 -39
  2. package/README.md +6 -6
  3. package/dist/commands/alias.js +2 -2
  4. package/dist/commands/browser-picker.d.ts +21 -0
  5. package/dist/commands/browser-picker.js +114 -0
  6. package/dist/commands/browser.js +793 -83
  7. package/dist/commands/cloud.js +8 -0
  8. package/dist/commands/commands.js +72 -22
  9. package/dist/commands/daemon.js +2 -2
  10. package/dist/commands/exec.js +70 -1
  11. package/dist/commands/hooks.js +71 -26
  12. package/dist/commands/mcp.js +81 -39
  13. package/dist/commands/plugins.js +224 -17
  14. package/dist/commands/prune.js +29 -1
  15. package/dist/commands/pull.js +3 -3
  16. package/dist/commands/repo.js +1 -1
  17. package/dist/commands/routines.js +2 -2
  18. package/dist/commands/secrets.js +154 -20
  19. package/dist/commands/sessions.js +62 -19
  20. package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
  21. package/dist/commands/{init.js → setup.js} +22 -21
  22. package/dist/commands/skills.js +60 -19
  23. package/dist/commands/subagents.js +41 -13
  24. package/dist/commands/utils.d.ts +16 -0
  25. package/dist/commands/utils.js +32 -0
  26. package/dist/commands/view.js +78 -20
  27. package/dist/commands/workflows.d.ts +10 -0
  28. package/dist/commands/workflows.js +457 -0
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +48 -36
  31. package/dist/lib/agents.js +2 -2
  32. package/dist/lib/auto-pull-worker.js +2 -3
  33. package/dist/lib/auto-pull.js +2 -2
  34. package/dist/lib/browser/cdp.d.ts +7 -1
  35. package/dist/lib/browser/cdp.js +32 -1
  36. package/dist/lib/browser/chrome.d.ts +10 -0
  37. package/dist/lib/browser/chrome.js +41 -3
  38. package/dist/lib/browser/devices.d.ts +4 -0
  39. package/dist/lib/browser/devices.js +27 -0
  40. package/dist/lib/browser/drivers/local.js +22 -6
  41. package/dist/lib/browser/drivers/ssh.js +9 -2
  42. package/dist/lib/browser/input.d.ts +1 -0
  43. package/dist/lib/browser/input.js +3 -0
  44. package/dist/lib/browser/ipc.js +158 -23
  45. package/dist/lib/browser/profiles.d.ts +10 -2
  46. package/dist/lib/browser/profiles.js +122 -37
  47. package/dist/lib/browser/service.d.ts +91 -13
  48. package/dist/lib/browser/service.js +767 -132
  49. package/dist/lib/browser/types.d.ts +91 -3
  50. package/dist/lib/browser/types.js +16 -0
  51. package/dist/lib/cloud/rush.d.ts +28 -1
  52. package/dist/lib/cloud/rush.js +69 -14
  53. package/dist/lib/cloud/store.js +2 -2
  54. package/dist/lib/commands.d.ts +1 -15
  55. package/dist/lib/commands.js +11 -7
  56. package/dist/lib/daemon.js +2 -3
  57. package/dist/lib/doctor-diff.js +4 -4
  58. package/dist/lib/events.js +2 -2
  59. package/dist/lib/hooks.d.ts +11 -7
  60. package/dist/lib/hooks.js +138 -49
  61. package/dist/lib/migrate.d.ts +1 -1
  62. package/dist/lib/migrate.js +1237 -22
  63. package/dist/lib/models.js +2 -2
  64. package/dist/lib/permissions.d.ts +8 -66
  65. package/dist/lib/permissions.js +18 -18
  66. package/dist/lib/plugins.d.ts +94 -24
  67. package/dist/lib/plugins.js +702 -123
  68. package/dist/lib/pty-server.js +9 -10
  69. package/dist/lib/resource-patterns.d.ts +41 -0
  70. package/dist/lib/resource-patterns.js +82 -0
  71. package/dist/lib/resources/hooks.d.ts +5 -1
  72. package/dist/lib/resources/hooks.js +21 -4
  73. package/dist/lib/resources/index.d.ts +17 -0
  74. package/dist/lib/resources/index.js +7 -0
  75. package/dist/lib/resources/types.d.ts +1 -1
  76. package/dist/lib/resources/workflows.d.ts +24 -0
  77. package/dist/lib/resources/workflows.js +110 -0
  78. package/dist/lib/resources.d.ts +6 -1
  79. package/dist/lib/resources.js +12 -2
  80. package/dist/lib/rotate.js +3 -4
  81. package/dist/lib/session/active.d.ts +3 -0
  82. package/dist/lib/session/active.js +92 -6
  83. package/dist/lib/session/cloud.js +2 -2
  84. package/dist/lib/session/db.d.ts +18 -0
  85. package/dist/lib/session/db.js +109 -5
  86. package/dist/lib/session/discover.d.ts +6 -0
  87. package/dist/lib/session/discover.js +55 -29
  88. package/dist/lib/session/team-filter.js +2 -2
  89. package/dist/lib/shims.d.ts +4 -52
  90. package/dist/lib/shims.js +23 -15
  91. package/dist/lib/skills.js +6 -2
  92. package/dist/lib/sqlite.js +10 -4
  93. package/dist/lib/state.d.ts +101 -16
  94. package/dist/lib/state.js +179 -31
  95. package/dist/lib/subagents.d.ts +28 -0
  96. package/dist/lib/subagents.js +98 -1
  97. package/dist/lib/sync-manifest.d.ts +1 -1
  98. package/dist/lib/sync-manifest.js +3 -3
  99. package/dist/lib/teams/persistence.js +15 -5
  100. package/dist/lib/teams/registry.js +2 -2
  101. package/dist/lib/types.d.ts +75 -17
  102. package/dist/lib/types.js +3 -3
  103. package/dist/lib/usage.js +2 -2
  104. package/dist/lib/versions.d.ts +3 -0
  105. package/dist/lib/versions.js +158 -47
  106. package/dist/lib/workflows.d.ts +79 -0
  107. package/dist/lib/workflows.js +233 -0
  108. package/package.json +1 -5
  109. package/scripts/postinstall.js +60 -59
  110. package/dist/commands/fork.d.ts +0 -10
  111. package/dist/commands/fork.js +0 -146
package/CHANGELOG.md CHANGED
@@ -1,62 +1,166 @@
1
1
  # Changelog
2
2
 
3
- ## 1.14.6
3
+ ## 1.17.0
4
4
 
5
- **Fix: OAuth token refresh now persists to Keychain**
5
+ **Workflows: a new first-class resource**
6
6
 
7
- - Fixed bug where refreshed Claude OAuth tokens were used but never saved back to macOS Keychain
8
- - Previously, agents-cli would refresh expired tokens on each run but discard them, eventually exhausting the refresh token
9
- - Now refreshed `accessToken`, `refreshToken`, and `expiresAt` are written back to Keychain after successful refresh
10
- - Accounts will stay healthy across runs without requiring re-login
7
+ - `agents workflows list / add / remove / view` WORKFLOW.md bundles (with optional `subagents/`, `skills/`, `plugins/`) install from GitHub or a local path and resolve through the same system → user → project layer model as every other resource.
8
+ - `agents run <name>` resolves a workflow or named subagent as an orchestrator: prepends WORKFLOW.md / AGENT.md body to the prompt, copies `subagents/*` into `~/.claude/agents/` for Agent-tool discovery, and syncs workflow-scoped `skills/` and `plugins/` at run time.
9
+ - `agents view` now has a workflows section.
11
10
 
12
- ## 1.14.5
11
+ **Browser**
13
12
 
14
- **Browser: custom binary and Electron app support**
13
+ - Port-per-profile with auto-allocation and viewport enforcement — concurrent browser profiles no longer collide on CDP ports.
14
+ - `agents browser scroll` plus new `profiles launch`, `profiles doctor`, `profiles prime`, viewport position, and port diagnostics commands.
15
+ - `agents browser profiles list` now shows a description column when any profile has one.
16
+ - `isProcessRunning` treats EPERM as process-alive (fixes false-negative on sandboxed processes).
15
17
 
16
- - Added `binary` field to browser profiles for specifying custom executable paths (e.g., Electron apps like Rush)
17
- - Added `electron` field to browser profiles — when true, uses existing windows instead of creating new ones (Electron doesn't support `Target.createTarget`)
18
- - New `custom` browser type that requires a binary path
19
- - Works with both local and SSH-based browser connections
20
- - Example profile for Rush: `agents browser profiles edit rush --browser custom --binary "/Applications/Rush.app/Contents/MacOS/Rush" --electron`
18
+ **Cloud dispatch**
19
+
20
+ - `--balanced` strategy and `--upload-account-tokens` flag on cloud dispatch.
21
+ - Remote account API client; `--balanced` skips the client manifest path.
22
+
23
+ **Plugin system extension**
24
+
25
+ - Plugins now ship with `commands/`, `agents/`, `bin/`, MCP configs, settings, and `install` / `update` hooks. Discovery and sync extended end-to-end.
26
+
27
+ **Secrets**
28
+
29
+ - `agents secrets import <bundle> --from-1password` / `export <bundle> --to-1password` with vault picker, skip-empty-fields on import, overwrite-only-with-`--force` on export. Wires the existing 1Password library into the CLI.
30
+
31
+ **Sandbox**
32
+
33
+ - `scripts/sandbox.sh --pr` — author real PRs from a Crabbox-isolated box via a bare-mirror clone off main.
34
+ - `sandbox.sh --linear` and `--post-file` post run output to Linear tickets.
35
+ - Dynamic GitHub App token, `gh` CLI installed, stale git credentials cleaned.
36
+
37
+ **Sessions / SQLite concurrency**
38
+
39
+ - Scan coordinator prevents concurrent session indexing.
40
+ - SQLite concurrency hardened with `BEGIN IMMEDIATE` and ledger recheck on contention.
41
+ - Session discovery uses `getHistoryDir` for version roots and backup paths.
42
+
43
+ **Run / shims / hooks**
44
+
45
+ - Versioned alias shims regenerate on startup if missing.
46
+ - Hooks prefer version-home scripts to prevent path breakage when the source dir moves.
47
+ - Linux: claude shim sources `CLAUDE_CODE_OAUTH_TOKEN` from the per-version `.oauth_token` file when unset.
48
+
49
+ **Resource UI**
50
+
51
+ - `agents view` replaces path columns with OSC 8 hyperlinks for commands, skills, and rules.
52
+ - Flat version resource lists replaced with source-pattern selection.
53
+
54
+ **CI / security**
55
+
56
+ - Gitleaks secret-scanning workflow on every push (switched to the free CLI, no org license needed).
57
+
58
+ **Postinstall**
59
+
60
+ - Correct shims dir, expanded aliases, prints changelog on install.
61
+
62
+ **Dev**
63
+
64
+ - Test isolation via vitest `pool: 'forks'`; mock state paths instead of hitting real `~/.agents/`.
65
+ - Concurrent-writes benchmark for the session indexer.
66
+ - Dead code + phantom deps removed: `src/commands/fork.ts`, `@aws-sdk/client-s3`, `@modelcontextprotocol/sdk`, `semver`.
67
+
68
+ ## 1.16.0
21
69
 
22
- ## Unreleased
70
+ **System-repo sweep: ~/.agents-system reduced to npm-shipped defaults only**
23
71
 
24
- **System repo moved to `~/.agents-system`; `~/.agents` is now free for user-owned repos**
72
+ - New migrators move every form of operational state out of ~/.agents-system into user-side buckets: sessions, teams (live + per-run), trash, repos (→ ~/.agents-<alias>/ peer dirs), legacy swarm/, cache/, cloud/.
73
+ - SQLite DBs merge row-level (INSERT OR IGNORE) into the user-side DB; filesystem dirs merge dir-by-dir with user-side winning on collision.
74
+ - Dead artifacts dropped automatically: bin/agents-keychain-*, empty shims/, .DS_Store-only versions/ skeletons.
75
+ - Unrecognized leftover dirs print a one-line stderr warning so future drift surfaces immediately.
76
+ - Migration diagnostics moved to stderr — `eval "$(agents secrets export …)"` stops being polluted by log lines.
77
+ - DB merge now skips FTS5 virtual + shadow tables (previously corrupted the session_text index). Indexer re-populates FTS on the next scan.
78
+ - Stale ~/.agents-system/agents.yaml is now dropped when a user copy exists.
25
79
 
26
- - The CLI-managed global root now lives under `~/.agents-system/` instead of `~/.agents/`. This includes `agents.yaml`, commands, skills, hooks, rules, versions, shims, routines, runs, plugins, secrets, and managed extra-repo clones.
27
- - `agents init` and `agents pull` now bootstrap and sync the fixed system repo in `~/.agents-system/`.
28
- - `agents repo init` now supports `--from <source>` and no longer pushes users toward generated names like `~/.agents-mine`; `~/.agents` is now the recommended clean path for a personal repo.
80
+ **~/.agents split into .history/ and .cache/ buckets**
29
81
 
30
- **Scheduler merged into `routines`; top-level help simplified**
82
+ - Durable runtime state (sessions, versions, runs, teams/agents, trash, backups) moves to ~/.agents/.history/.
83
+ - Regenerable runtime state (shims, packages, cloud, logs, swarmify, helpers, browser runtime, fetch cache, dot-files) moves to ~/.agents/.cache/.
84
+ - Single-line gitignore for backing up ~/.agents/ — no more per-subdir cherry-picking.
31
85
 
32
- - `agents routines add` now auto-starts the background scheduler when it is not already running. First-time users no longer need a separate `daemon start` step — the common path is just `routines add`.
33
- - New subcommands mirror the old daemon controls under `routines`: `agents routines start`, `stop`, `status`, `scheduler-logs`. The word "daemon" is no longer exposed in user-facing help.
34
- - `agents daemon <start|stop|status|logs>` is **deprecated**. It still works but prints a migration warning and is hidden from top-level help. Will be removed in v2.0.
35
- - Top-level help restructured: the old "Automation" grab-bag is gone. Commands are now grouped as **Run agents** (`run`, `teams`, `sessions`), **Schedule** (`routines`), and **Helpers** (`pty`).
86
+ **Browser: profiles fold into agents.yaml + many new automation commands**
36
87
 
37
- **Dev: sqlite auto-rebuild on `bun run test`**
88
+ - Profile YAMLs at ~/.agents/browser/profiles/*.yaml now live as a `browser:` section in agents.yaml. Single user-facing file, single sync.
89
+ - Single window per profile; `start` renamed to `open`; new tab subcommands; session history with profile picker; viewport piped through to the launched browser.
90
+ - New commands: `agents browser set viewport`, `set device`, `devices`, `console`, `errors`, `requests`, `responsebody`, `wait`, `download`, `waitdownload`.
38
91
 
39
- - Added `scripts/rebuild-sqlite.sh` and a `pretest` hook. The script probes `better-sqlite3` by opening an in-memory database and only rebuilds (plain `node-gyp rebuild --release`, no Electron flags) when the probe fails. Fixes the napi ABI mismatch that `bun install` sometimes leaves behind.
92
+ **Hooks: hooks.yaml folded into agents.yaml `hooks:` section**
40
93
 
41
- **Default upstream moved to `phnx-labs/.agents`**
94
+ - ~/.agents/hooks.yaml is migrated into agents.yaml on first run; the standalone file is removed.
95
+ - System repo ships the same shape — one config file, layered project > user > system.
42
96
 
43
- - `DEFAULT_SYSTEM_REPO` now resolves to `gh:phnx-labs/.agents` — a curated, org-owned upstream. `agents pull` (no args) and `agents fork` target the new repo on fresh machines.
44
- - Existing users whose upstream still points at the old default see a one-time nudge on `agents pull --upstream` with the command to switch. Nothing else breaks; legacy remotes continue to work.
97
+ **Sessions & secrets**
45
98
 
46
- **Consolidate sessions command**
99
+ - `agents secrets exec <bundle> -- <command>` injects a bundle's env vars into a one-shot subprocess (no shell-state leakage).
100
+ - `agents sessions` now groups active sessions by workspace and surfaces session topics in the picker.
101
+ - Session discovery scans both version repos; migrator merges overlapping versions instead of leaving duplicates.
47
102
 
48
- - Removed `agents sessions list` and `agents sessions view` subcommands; `agents sessions` is now a single smart command
49
- - Positional query resolves to a session ID (renders directly), a path (`.`, `../`, `/path`) to filter by project, or free text for search
50
- - Claude `/resume` history fallback (previously only in `view`) now fires from the top-level command too
51
- - Picker shows the selected-session preview by default; space hides it
103
+ **Renames**
52
104
 
53
- **Session view flags: filters + formats split cleanly**
105
+ - `agents init` `agents setup`.
106
+ - `permissions/sets/` → `permissions/presets/` (resource directory + on-disk migration to match rules/presets convention).
54
107
 
55
- - Formats: default is the activity summary; `--markdown` renders the full conversation (user + assistant + thinking + tool calls) as markdown; `--json` emits normalized events
56
- - Filters compose orthogonally: `--include <roles>` / `--exclude <roles>` (values: `user`, `assistant`, `thinking`, `tools`), plus `--first N` / `--last N` to slice by turn (a turn starts at each user message)
57
- - Any filter flag without `--markdown`/`--json` defaults to markdown output summary is an aggregate view so filters would be meaningless there
58
- - Mutual exclusion: `--include` vs `--exclude`, `--first` vs `--last`
59
- - Removed `--transcript`, `--trace`, `--timeline`, and `--role` (replaced by the above)
108
+ **Dev**
109
+
110
+ - Crabbox remote-test profile (~$0.14/hr) + `scripts/sandbox.sh` documented in README and CLAUDE.md. Tests run remotely to avoid freezing the local machine.
111
+
112
+ ## 1.15.0
113
+
114
+ **Secrets: Linux support via libsecret/GNOME Keyring**
115
+
116
+ - `agents secrets` now works on Linux backed by libsecret/GNOME Keyring with the same UX as macOS Keychain. Headless workarounds documented.
117
+ - New `agents password generate` subcommand.
118
+ - Lifecycle events emitted for secrets and other subsystems; richer metadata (timing helpers) on the events system.
119
+
120
+ **Browser**
121
+
122
+ - HTTP and WebSocket endpoint support for remote browsers.
123
+ - Concurrent Electron profile forks no longer step on each other; cleanup hardened.
124
+ - Remote browser restart works; SSH port handling improved; page target created when none exists for Electron apps.
125
+ - Events emitted for navigation and screenshots.
126
+
127
+ **First-run UX**
128
+
129
+ - Improved new-user experience: clearer CLI help, better defaults, audit-log opt-out, better run-timing display.
130
+
131
+ **Prune**
132
+
133
+ - `agents prune` learned `trash`, `sessions`, and `runs` cleanup targets.
134
+
135
+ **Fixes**
136
+
137
+ - Command-injection hole in daemon + secrets closed.
138
+ - Layered permission resolution corrected; daemon tests isolated from real user state.
139
+ - `.tmp-bun` gitignore pattern fixed.
140
+ - `codex` interactive mode no longer routes through `exec` subcommand.
141
+
142
+ **Docs**
143
+
144
+ - Security/privacy section in README, browser skill + automation guide, FAQ updated with audit-log transparency.
145
+
146
+ ## 1.14.6
147
+
148
+ **Fix: OAuth token refresh now persists to Keychain**
149
+
150
+ - Fixed bug where refreshed Claude OAuth tokens were used but never saved back to macOS Keychain
151
+ - Previously, agents-cli would refresh expired tokens on each run but discard them, eventually exhausting the refresh token
152
+ - Now refreshed `accessToken`, `refreshToken`, and `expiresAt` are written back to Keychain after successful refresh
153
+ - Accounts will stay healthy across runs without requiring re-login
154
+
155
+ ## 1.14.5
156
+
157
+ **Browser: custom binary and Electron app support**
158
+
159
+ - Added `binary` field to browser profiles for specifying custom executable paths (e.g., Electron apps like Rush)
160
+ - Added `electron` field to browser profiles — when true, uses existing windows instead of creating new ones (Electron doesn't support `Target.createTarget`)
161
+ - New `custom` browser type that requires a binary path
162
+ - Works with both local and SSH-based browser connections
163
+ - Example profile for Rush: `agents browser profiles edit rush --browser custom --binary "/Applications/Rush.app/Contents/MacOS/Rush" --electron`
60
164
 
61
165
  ## 1.12.0
62
166
 
package/README.md CHANGED
@@ -253,12 +253,12 @@ Give agents access to a real browser — no relay extension, no cloud service, n
253
253
  agents browser profiles create work --browser chrome
254
254
 
255
255
  # Start a task, navigate, interact
256
- agents browser start login-flow --profile work
257
- agents browser navigate login-flow https://app.example.com
256
+ agents browser start --profile work --task login-flow --url https://app.example.com
258
257
  agents browser refs login-flow # Get interactive element refs
259
- agents browser click login-flow <tab> 42 # Click element ref 42
260
- agents browser type login-flow <tab> 15 "hello"
258
+ agents browser click login-flow 42 # Click element ref 42
259
+ agents browser type login-flow 15 "hello" # Type into element ref 15
261
260
  agents browser screenshot login-flow # Smart resizing, token-efficient
261
+ agents browser done login-flow # Close task's tabs when finished
262
262
  ```
263
263
 
264
264
  ### Why this works where Playwright fails
@@ -294,9 +294,9 @@ agents browser profiles create bank --browser chrome --secrets bank-creds
294
294
  Control Electron apps (Slack, Discord, VS Code, your own app) with custom binaries:
295
295
 
296
296
  ```bash
297
- agents browser profiles create rush \
297
+ agents browser profiles create slack \
298
298
  --browser custom \
299
- --binary "/Applications/Rush.app/Contents/MacOS/Rush" \
299
+ --binary "/Applications/Slack.app/Contents/MacOS/Slack" \
300
300
  --electron
301
301
  ```
302
302
 
@@ -1,10 +1,10 @@
1
1
  import chalk from 'chalk';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import { getAgentsDir } from '../lib/state.js';
4
+ import { getUserAgentsDir } from '../lib/state.js';
5
5
  import { getShimsDir } from '../lib/shims.js';
6
6
  import { ALL_AGENT_IDS, AGENTS } from '../lib/agents.js';
7
- const ALIASES_FILE = path.join(getAgentsDir(), 'aliases.json');
7
+ const ALIASES_FILE = path.join(getUserAgentsDir(), 'aliases.json');
8
8
  /** Names that would clobber an agent CLI shim or the `agents` binary itself. */
9
9
  function reservedNames() {
10
10
  const reserved = new Set(['agents']);
@@ -0,0 +1,21 @@
1
+ import type { ProfileStatus, TaskStatus } from '../lib/browser/types.js';
2
+ export interface BrowserTask {
3
+ task: TaskStatus;
4
+ profile: ProfileStatus;
5
+ }
6
+ export interface PickedBrowserTask {
7
+ task: BrowserTask;
8
+ action: 'view' | 'stop';
9
+ }
10
+ /** Build the preview pane for a browser task. */
11
+ export declare function buildBrowserPreview(item: BrowserTask): string;
12
+ /** Build the list label for a browser task. */
13
+ export declare function buildBrowserLabel(item: BrowserTask, query: string): string;
14
+ export interface BrowserPickerConfig {
15
+ message: string;
16
+ tasks: BrowserTask[];
17
+ pageSize?: number;
18
+ initialSearch?: string;
19
+ }
20
+ /** Show an interactive browser task picker. */
21
+ export declare function browserTaskPicker(config: BrowserPickerConfig): Promise<PickedBrowserTask | null>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Interactive browser task picker with preview.
3
+ *
4
+ * Powers the fuzzy-searchable task list shown by `agents browser status` in a TTY.
5
+ * Shows task name, profile, domains, and tab count; preview pane shows full tab list.
6
+ */
7
+ import chalk from 'chalk';
8
+ import { itemPicker } from '../lib/picker.js';
9
+ const DOT = chalk.gray(' · ');
10
+ function humanDuration(ms) {
11
+ const s = Math.floor(ms / 1000);
12
+ if (s < 60)
13
+ return `${s}s`;
14
+ const m = Math.floor(s / 60);
15
+ if (m < 60)
16
+ return `${m}m`;
17
+ const h = Math.floor(m / 60);
18
+ const mm = m % 60;
19
+ if (h < 24)
20
+ return mm ? `${h}h ${mm}m` : `${h}h`;
21
+ const d = Math.floor(h / 24);
22
+ const hh = h % 24;
23
+ return hh ? `${d}d ${hh}h` : `${d}d`;
24
+ }
25
+ function formatAge(ms) {
26
+ return humanDuration(Date.now() - ms) + ' ago';
27
+ }
28
+ function truncate(s, max) {
29
+ return s.length > max ? s.slice(0, max - 1) + '…' : s;
30
+ }
31
+ /** Build the preview pane for a browser task. */
32
+ export function buildBrowserPreview(item) {
33
+ const { task, profile } = item;
34
+ const lines = [];
35
+ // Line 1: Task name (ID: xxx)
36
+ lines.push(chalk.bold.white(`Task: ${task.name}`) + chalk.gray(` (${task.id})`));
37
+ // Line 2: Profile info
38
+ const profileParts = [chalk.cyan(profile.name)];
39
+ if (profile.port)
40
+ profileParts.push(`port ${profile.port}`);
41
+ if (profile.pid)
42
+ profileParts.push(`pid ${profile.pid}`);
43
+ lines.push(chalk.gray('Profile: ') + profileParts.join(DOT));
44
+ // Line 3: Timing
45
+ const started = formatAge(task.createdAt);
46
+ const duration = humanDuration(Date.now() - task.createdAt);
47
+ lines.push(chalk.gray('Started: ') + chalk.white(started) + DOT + chalk.gray('Duration: ') + chalk.white(duration));
48
+ // Blank line
49
+ lines.push('');
50
+ // Tabs section
51
+ if (task.tabs && task.tabs.length > 0) {
52
+ lines.push(chalk.gray('Tabs:'));
53
+ const termWidth = process.stdout.columns || 80;
54
+ const urlMax = Math.max(30, termWidth - 16);
55
+ for (const tab of task.tabs) {
56
+ const marker = tab.current ? chalk.yellow('*') : ' ';
57
+ const id = chalk.gray(tab.id);
58
+ const url = truncate(tab.url, urlMax);
59
+ lines.push(` ${id} ${marker} ${chalk.white(url)}`);
60
+ }
61
+ }
62
+ else {
63
+ lines.push(chalk.gray('No tabs open'));
64
+ }
65
+ return lines.join('\n');
66
+ }
67
+ /** Build the list label for a browser task. */
68
+ export function buildBrowserLabel(item, query) {
69
+ const { task, profile } = item;
70
+ const termWidth = process.stdout.columns || 80;
71
+ // Format: name profile tabs domains age
72
+ const name = task.name.padEnd(20);
73
+ const profileName = profile.name.padEnd(12);
74
+ const tabs = `${task.tabCount} tab${task.tabCount === 1 ? '' : 's'}`.padEnd(8);
75
+ const age = formatAge(task.createdAt);
76
+ // Domains - fill remaining space
77
+ const fixedWidth = 20 + 12 + 8 + 10; // name + profile + tabs + age
78
+ const domainsWidth = Math.max(10, termWidth - fixedWidth - 4);
79
+ const domains = task.domains?.length
80
+ ? truncate(task.domains.join(', '), domainsWidth)
81
+ : chalk.gray('no sites');
82
+ // Highlight query matches
83
+ let label = `${name}${chalk.cyan(profileName)}${tabs}${domains.padEnd(domainsWidth)}${chalk.gray(age)}`;
84
+ if (query) {
85
+ const re = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
86
+ label = label.replace(re, chalk.yellow.bold('$1'));
87
+ }
88
+ return label;
89
+ }
90
+ /** Show an interactive browser task picker. */
91
+ export async function browserTaskPicker(config) {
92
+ const picked = await itemPicker({
93
+ message: config.message,
94
+ items: config.tasks,
95
+ filter: (query) => {
96
+ if (!query)
97
+ return config.tasks;
98
+ const q = query.toLowerCase();
99
+ return config.tasks.filter((t) => t.task.name.toLowerCase().includes(q) ||
100
+ t.profile.name.toLowerCase().includes(q) ||
101
+ t.task.domains?.some((d) => d.toLowerCase().includes(q)));
102
+ },
103
+ labelFor: buildBrowserLabel,
104
+ buildPreview: buildBrowserPreview,
105
+ shortIdFor: (t) => t.task.name,
106
+ pageSize: config.pageSize ?? 10,
107
+ initialSearch: config.initialSearch,
108
+ emptyMessage: 'No browser tasks running.',
109
+ enterHint: 'view tabs',
110
+ });
111
+ if (!picked)
112
+ return null;
113
+ return { task: picked.item, action: 'view' };
114
+ }