@phnx-labs/agents-cli 1.16.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.
- package/CHANGELOG.md +65 -0
- package/dist/commands/browser.js +248 -9
- package/dist/commands/cloud.js +8 -0
- package/dist/commands/exec.js +70 -1
- package/dist/commands/plugins.js +179 -5
- package/dist/commands/prune.js +6 -0
- package/dist/commands/secrets.js +117 -19
- package/dist/commands/view.js +21 -8
- package/dist/commands/workflows.d.ts +10 -0
- package/dist/commands/workflows.js +457 -0
- package/dist/index.js +31 -16
- package/dist/lib/browser/cdp.js +7 -4
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +37 -2
- package/dist/lib/browser/drivers/local.js +13 -2
- package/dist/lib/browser/input.d.ts +1 -0
- package/dist/lib/browser/input.js +3 -0
- package/dist/lib/browser/ipc.js +14 -0
- package/dist/lib/browser/profiles.d.ts +5 -0
- package/dist/lib/browser/profiles.js +45 -0
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +29 -1
- package/dist/lib/browser/types.d.ts +11 -1
- package/dist/lib/cloud/rush.d.ts +28 -1
- package/dist/lib/cloud/rush.js +68 -13
- package/dist/lib/commands.d.ts +0 -15
- package/dist/lib/commands.js +5 -5
- package/dist/lib/hooks.js +24 -11
- package/dist/lib/migrate.js +59 -1
- package/dist/lib/permissions.d.ts +0 -58
- package/dist/lib/permissions.js +10 -10
- package/dist/lib/plugins.d.ts +75 -34
- package/dist/lib/plugins.js +640 -133
- package/dist/lib/resource-patterns.d.ts +41 -0
- package/dist/lib/resource-patterns.js +82 -0
- package/dist/lib/resources/index.d.ts +17 -0
- package/dist/lib/resources/index.js +7 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources/workflows.d.ts +24 -0
- package/dist/lib/resources/workflows.js +110 -0
- package/dist/lib/resources.d.ts +6 -1
- package/dist/lib/resources.js +12 -2
- package/dist/lib/session/db.d.ts +18 -0
- package/dist/lib/session/db.js +106 -7
- package/dist/lib/session/discover.d.ts +6 -0
- package/dist/lib/session/discover.js +28 -17
- package/dist/lib/shims.d.ts +3 -51
- package/dist/lib/shims.js +18 -10
- package/dist/lib/sqlite.js +10 -4
- package/dist/lib/state.d.ts +15 -2
- package/dist/lib/state.js +29 -8
- package/dist/lib/types.d.ts +43 -14
- package/dist/lib/versions.d.ts +3 -0
- package/dist/lib/versions.js +139 -27
- package/dist/lib/workflows.d.ts +79 -0
- package/dist/lib/workflows.js +233 -0
- package/package.json +1 -5
- package/scripts/postinstall.js +59 -58
- package/dist/commands/fork.d.ts +0 -10
- package/dist/commands/fork.js +0 -146
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.17.0
|
|
4
|
+
|
|
5
|
+
**Workflows: a new first-class resource**
|
|
6
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
**Browser**
|
|
12
|
+
|
|
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).
|
|
17
|
+
|
|
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
|
+
|
|
3
68
|
## 1.16.0
|
|
4
69
|
|
|
5
70
|
**System-repo sweep: ~/.agents-system reduced to npm-shipped defaults only**
|
package/dist/commands/browser.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { listProfiles, getProfile, createProfile, deleteProfile, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, } from '../lib/browser/profiles.js';
|
|
4
|
+
import { findBrowserPath, getPortOccupant } from '../lib/browser/chrome.js';
|
|
5
|
+
import { discoverBrowserWsUrl, verifyBrowserIdentity } from '../lib/browser/cdp.js';
|
|
2
6
|
import { sendIPCRequest } from '../lib/browser/ipc.js';
|
|
3
7
|
import { browserTaskPicker } from './browser-picker.js';
|
|
4
8
|
import { isInteractiveTerminal } from './utils.js';
|
|
@@ -28,11 +32,23 @@ function registerProfilesCommands(browser) {
|
|
|
28
32
|
console.log('Create one with: agents browser profiles create <name> --endpoint <url>');
|
|
29
33
|
return;
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const hasDescriptions = allProfiles.some(p => p.description);
|
|
36
|
+
if (hasDescriptions) {
|
|
37
|
+
console.log('NAME'.padEnd(20) + 'BROWSER'.padEnd(12) + 'DESCRIPTION'.padEnd(38) + 'ENDPOINTS');
|
|
38
|
+
console.log('-'.repeat(92));
|
|
39
|
+
for (const p of allProfiles) {
|
|
40
|
+
const endpoints = p.endpoints.join(', ');
|
|
41
|
+
const desc = (p.description ?? '').slice(0, 36).padEnd(38);
|
|
42
|
+
console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + desc + endpoints);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log('NAME'.padEnd(20) + 'BROWSER'.padEnd(12) + 'ENDPOINTS');
|
|
47
|
+
console.log('-'.repeat(72));
|
|
48
|
+
for (const p of allProfiles) {
|
|
49
|
+
const endpoints = p.endpoints.join(', ');
|
|
50
|
+
console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + endpoints);
|
|
51
|
+
}
|
|
36
52
|
}
|
|
37
53
|
});
|
|
38
54
|
const VALID_BROWSERS = ['chrome', 'comet', 'chromium', 'brave', 'edge'];
|
|
@@ -40,10 +56,12 @@ function registerProfilesCommands(browser) {
|
|
|
40
56
|
.command('create <name>')
|
|
41
57
|
.description('Create a new browser profile')
|
|
42
58
|
.requiredOption('-b, --browser <type>', `Browser type: ${VALID_BROWSERS.join(', ')}`)
|
|
43
|
-
.
|
|
59
|
+
.option('-e, --endpoint <url>', 'CDP endpoint URL (repeatable; auto-assigned if omitted)', collect, [])
|
|
44
60
|
.option('-s, --secrets <bundle>', 'Secrets bundle to inject')
|
|
45
61
|
.option('-d, --description <text>', 'Profile description')
|
|
46
62
|
.option('--headless', 'Run in headless mode')
|
|
63
|
+
.option('--window <WxH>', 'Window size, e.g. 1512x982')
|
|
64
|
+
.option('--position <X,Y>', 'Window position on screen, e.g. 80,80')
|
|
47
65
|
.action(async (name, opts) => {
|
|
48
66
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
49
67
|
console.error('Profile name must be lowercase alphanumeric with hyphens');
|
|
@@ -53,13 +71,43 @@ function registerProfilesCommands(browser) {
|
|
|
53
71
|
console.error(`Invalid browser type. Must be one of: ${VALID_BROWSERS.join(', ')}`);
|
|
54
72
|
process.exit(1);
|
|
55
73
|
}
|
|
74
|
+
// Auto-assign a free port if no endpoint was provided
|
|
75
|
+
let endpoints = opts.endpoint;
|
|
76
|
+
if (endpoints.length === 0) {
|
|
77
|
+
const freePort = await findFreeProfilePort();
|
|
78
|
+
endpoints = [`cdp://127.0.0.1:${freePort}`];
|
|
79
|
+
}
|
|
80
|
+
// Viewport is mandatory — default to 1512x982 if --window is not provided
|
|
81
|
+
let viewport = {
|
|
82
|
+
width: 1512,
|
|
83
|
+
height: 982,
|
|
84
|
+
};
|
|
85
|
+
if (opts.window) {
|
|
86
|
+
const m = String(opts.window).match(/^(\d+)x(\d+)$/);
|
|
87
|
+
if (!m) {
|
|
88
|
+
console.error('--window must be WxH, e.g. 1512x982');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
viewport.width = parseInt(m[1], 10);
|
|
92
|
+
viewport.height = parseInt(m[2], 10);
|
|
93
|
+
}
|
|
94
|
+
if (opts.position) {
|
|
95
|
+
const m = String(opts.position).match(/^(-?\d+),(-?\d+)$/);
|
|
96
|
+
if (!m) {
|
|
97
|
+
console.error('--position must be X,Y, e.g. 80,80');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
viewport.x = parseInt(m[1], 10);
|
|
101
|
+
viewport.y = parseInt(m[2], 10);
|
|
102
|
+
}
|
|
56
103
|
const profile = {
|
|
57
104
|
name,
|
|
58
105
|
description: opts.description,
|
|
59
106
|
browser: opts.browser,
|
|
60
|
-
endpoints
|
|
107
|
+
endpoints,
|
|
61
108
|
secrets: opts.secrets,
|
|
62
109
|
chrome: opts.headless ? { headless: true } : undefined,
|
|
110
|
+
viewport,
|
|
63
111
|
};
|
|
64
112
|
await createProfile(profile);
|
|
65
113
|
console.log(`Created profile: ${name}`);
|
|
@@ -103,6 +151,172 @@ function registerProfilesCommands(browser) {
|
|
|
103
151
|
await deleteProfile(name);
|
|
104
152
|
console.log(`Deleted profile: ${name}`);
|
|
105
153
|
});
|
|
154
|
+
profiles
|
|
155
|
+
.command('launch <name>')
|
|
156
|
+
.description('Start (or attach to) the profile\'s browser without creating a task')
|
|
157
|
+
.action(async (name) => {
|
|
158
|
+
const profile = await getProfile(name);
|
|
159
|
+
if (!profile) {
|
|
160
|
+
console.error(`Profile "${name}" not found`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const response = await sendIPCRequest({
|
|
164
|
+
action: 'launch-profile',
|
|
165
|
+
profile: name,
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
console.error(response.error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const pidLabel = response.pid ? `pid ${response.pid}` : 'attached';
|
|
172
|
+
console.log(`Launched "${name}" on port ${response.port} (${pidLabel})`);
|
|
173
|
+
console.log(`Next: agents browser start --profile ${name} --url <url>`);
|
|
174
|
+
});
|
|
175
|
+
profiles
|
|
176
|
+
.command('doctor <name>')
|
|
177
|
+
.description('Diagnose a browser profile: binary, port, user-data-dir, onboarding state')
|
|
178
|
+
.action(async (name) => {
|
|
179
|
+
const profile = await getProfile(name);
|
|
180
|
+
if (!profile) {
|
|
181
|
+
console.error(`Profile "${name}" not found`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const checks = [];
|
|
185
|
+
// 1. Binary exists for declared browser type
|
|
186
|
+
try {
|
|
187
|
+
const binPath = findBrowserPath(profile.browser, profile.binary);
|
|
188
|
+
checks.push({ label: 'binary', ok: true, detail: binPath });
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
checks.push({
|
|
192
|
+
label: 'binary',
|
|
193
|
+
ok: false,
|
|
194
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// 2. Configured port: free, or already serving the expected browser?
|
|
198
|
+
const port = extractConfiguredPort(profile);
|
|
199
|
+
let attachingToExistingBrowser = false;
|
|
200
|
+
if (port === undefined) {
|
|
201
|
+
checks.push({ label: 'port', ok: true, detail: 'no port in endpoint' });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const occupant = getPortOccupant(port);
|
|
205
|
+
if (!occupant) {
|
|
206
|
+
checks.push({ label: 'port', ok: true, detail: `${port} is free` });
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
try {
|
|
210
|
+
const { browser } = await discoverBrowserWsUrl(port);
|
|
211
|
+
verifyBrowserIdentity(browser, profile.browser, port);
|
|
212
|
+
checks.push({
|
|
213
|
+
label: 'port',
|
|
214
|
+
ok: true,
|
|
215
|
+
detail: `${port} serving ${browser} (pid ${occupant.pid})`,
|
|
216
|
+
});
|
|
217
|
+
attachingToExistingBrowser = true;
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
221
|
+
checks.push({
|
|
222
|
+
label: 'port',
|
|
223
|
+
ok: false,
|
|
224
|
+
detail: `${port} taken by ${occupant.command} (pid ${occupant.pid}) — ${msg}`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// 3. User-data-dir exists and is writable
|
|
230
|
+
const userDataDir = path.join(getProfileRuntimeDir(name), 'chrome-data');
|
|
231
|
+
try {
|
|
232
|
+
if (!fs.existsSync(userDataDir)) {
|
|
233
|
+
checks.push({
|
|
234
|
+
label: 'user-data-dir',
|
|
235
|
+
ok: true,
|
|
236
|
+
detail: `will be created at ${userDataDir}`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
fs.accessSync(userDataDir, fs.constants.W_OK);
|
|
241
|
+
checks.push({ label: 'user-data-dir', ok: true, detail: userDataDir });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
checks.push({
|
|
246
|
+
label: 'user-data-dir',
|
|
247
|
+
ok: false,
|
|
248
|
+
detail: `${userDataDir} not writable: ${err instanceof Error ? err.message : err}`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// 4. Onboarding heuristic — only meaningful when WE will launch the
|
|
252
|
+
// browser. When the configured port is already serving a debuggable
|
|
253
|
+
// browser, that browser owns its own user-data-dir and the priming
|
|
254
|
+
// status of our managed dir is irrelevant.
|
|
255
|
+
if (attachingToExistingBrowser) {
|
|
256
|
+
checks.push({
|
|
257
|
+
label: 'onboarding',
|
|
258
|
+
ok: true,
|
|
259
|
+
detail: 'n/a (attaching to existing browser)',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const localStatePath = path.join(userDataDir, 'Local State');
|
|
264
|
+
if (fs.existsSync(localStatePath)) {
|
|
265
|
+
const size = fs.statSync(localStatePath).size;
|
|
266
|
+
if (size > 0) {
|
|
267
|
+
checks.push({ label: 'onboarding', ok: true, detail: 'Local State present' });
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
checks.push({
|
|
271
|
+
label: 'onboarding',
|
|
272
|
+
ok: false,
|
|
273
|
+
detail: 'Local State is empty — run `agents browser profiles prime ' + name + '`',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
checks.push({
|
|
279
|
+
label: 'onboarding',
|
|
280
|
+
ok: false,
|
|
281
|
+
detail: 'Not primed yet — run `agents browser profiles prime ' + name + '`',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const allOk = checks.every((c) => c.ok);
|
|
286
|
+
for (const c of checks) {
|
|
287
|
+
const marker = c.ok ? 'OK ' : 'FAIL';
|
|
288
|
+
console.log(`${marker} ${c.label.padEnd(15)} ${c.detail}`);
|
|
289
|
+
}
|
|
290
|
+
if (!allOk)
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|
|
293
|
+
profiles
|
|
294
|
+
.command('prime <name>')
|
|
295
|
+
.description('Launch the profile so you can complete first-run onboarding interactively')
|
|
296
|
+
.action(async (name) => {
|
|
297
|
+
const profile = await getProfile(name);
|
|
298
|
+
if (!profile) {
|
|
299
|
+
console.error(`Profile "${name}" not found`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
const response = await sendIPCRequest({
|
|
303
|
+
action: 'launch-profile',
|
|
304
|
+
profile: name,
|
|
305
|
+
});
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
console.error(response.error);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
const pidLabel = response.pid ? `pid ${response.pid}` : 'attached';
|
|
311
|
+
console.log(`Launched "${name}" on port ${response.port} (${pidLabel}).`);
|
|
312
|
+
console.log('');
|
|
313
|
+
console.log('Finish any first-run / onboarding screens in the browser window');
|
|
314
|
+
console.log('(welcome, profile setup, default-browser prompt, sign-in, etc.).');
|
|
315
|
+
console.log('Once you reach a normal browsing surface, this profile is primed');
|
|
316
|
+
console.log('— its user-data-dir persists across runs, so you only do this once.');
|
|
317
|
+
console.log('');
|
|
318
|
+
console.log(`Next: agents browser start --profile ${name} --url <url>`);
|
|
319
|
+
});
|
|
106
320
|
}
|
|
107
321
|
function registerTaskCommands(browser) {
|
|
108
322
|
browser
|
|
@@ -370,7 +584,10 @@ function registerTaskCommands(browser) {
|
|
|
370
584
|
const portLabel = profile.configuredPort && profile.configuredPort !== profile.port
|
|
371
585
|
? `port ${profile.port} (configured ${profile.configuredPort})`
|
|
372
586
|
: `port ${profile.port}`;
|
|
373
|
-
|
|
587
|
+
// pid 0 means the daemon attached to a browser we didn't launch — no
|
|
588
|
+
// tracked pid. Render it as "attached" rather than the literal 0.
|
|
589
|
+
const pidLabel = profile.pid ? `pid ${profile.pid}` : 'attached';
|
|
590
|
+
console.log(`\n${profile.name} (${portLabel}, ${pidLabel})`);
|
|
374
591
|
if (profile.tasks.length === 0) {
|
|
375
592
|
console.log(' No active tasks');
|
|
376
593
|
}
|
|
@@ -588,6 +805,28 @@ function registerTaskCommands(browser) {
|
|
|
588
805
|
}
|
|
589
806
|
console.log('Hovered');
|
|
590
807
|
});
|
|
808
|
+
browser
|
|
809
|
+
.command('scroll <task> <deltaX> <deltaY>')
|
|
810
|
+
.description('Scroll the page by pixel amount')
|
|
811
|
+
.option('-t, --tab <tabId>', 'Tab ID (defaults to current)')
|
|
812
|
+
.option('-x, --at-x <x>', 'X coordinate to dispatch scroll from (default 0)', parseInt)
|
|
813
|
+
.option('-y, --at-y <y>', 'Y coordinate to dispatch scroll from (default 0)', parseInt)
|
|
814
|
+
.action(async (task, deltaX, deltaY, opts) => {
|
|
815
|
+
const response = await sendIPCRequest({
|
|
816
|
+
action: 'scroll',
|
|
817
|
+
task,
|
|
818
|
+
tabId: opts.tab,
|
|
819
|
+
scrollX: parseInt(deltaX, 10),
|
|
820
|
+
scrollY: parseInt(deltaY, 10),
|
|
821
|
+
scrollAtX: opts.atX,
|
|
822
|
+
scrollAtY: opts.atY,
|
|
823
|
+
});
|
|
824
|
+
if (!response.ok) {
|
|
825
|
+
console.error(response.error);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
console.log('Scrolled');
|
|
829
|
+
});
|
|
591
830
|
// ─── Viewport & Device ───────────────────────────────────────────────────────
|
|
592
831
|
const setCmd = browser.command('set').description('Set browser emulation options');
|
|
593
832
|
setCmd
|
package/dist/commands/cloud.js
CHANGED
|
@@ -106,6 +106,9 @@ Examples:
|
|
|
106
106
|
.option('--env <id>', 'Codex Cloud environment ID')
|
|
107
107
|
.option('--computer <name>', 'Factory/Droid computer target')
|
|
108
108
|
.option('--mode <mode>', 'Execution mode (e.g., plan, edit, full)')
|
|
109
|
+
.option('-b, --balanced', 'Shortcut for --strategy balanced. Route the factory run across all healthy accounts.')
|
|
110
|
+
.option('--strategy <strategy>', 'Account selection strategy for the factory: balanced. Sends all healthy accounts so the factory pod rotates between them on rate-limit.')
|
|
111
|
+
.option('--upload-account-tokens', 'Upload Claude OAuth credentials to Rush Cloud on first dispatch (consent recorded for future runs).')
|
|
109
112
|
.option('--json', 'Structured JSON output')
|
|
110
113
|
.option('--no-follow', 'Dispatch and exit without streaming output')
|
|
111
114
|
.addHelpText('after', `
|
|
@@ -165,6 +168,11 @@ Examples:
|
|
|
165
168
|
dispatchOptions.providerOptions.computer = options.computer;
|
|
166
169
|
if (options.mode)
|
|
167
170
|
dispatchOptions.providerOptions.mode = options.mode;
|
|
171
|
+
if (options.balanced || options.strategy === 'balanced') {
|
|
172
|
+
dispatchOptions.providerOptions.strategy = 'balanced';
|
|
173
|
+
}
|
|
174
|
+
if (options.uploadAccountTokens)
|
|
175
|
+
dispatchOptions.providerOptions.uploadAccountTokens = true;
|
|
168
176
|
// Dispatch
|
|
169
177
|
const spinner = ora({ text: `Dispatching to ${provider.name}...`, stream: process.stderr }).start();
|
|
170
178
|
let task;
|
package/dist/commands/exec.js
CHANGED
|
@@ -8,9 +8,22 @@
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { buildExecCommand, parseExecEnv, execAgent, runWithFallback, AGENT_COMMANDS, } from '../lib/exec.js';
|
|
10
10
|
import { profileExists, resolveProfileForRun } from '../lib/profiles.js';
|
|
11
|
+
import { getSystemAgentsDir, getUserAgentsDir } from '../lib/state.js';
|
|
12
|
+
/** Resolve a workflow by name. User repo wins over system repo. Returns the workflow dir or null. */
|
|
13
|
+
function resolveWorkflow(name) {
|
|
14
|
+
for (const base of [getUserAgentsDir(), getSystemAgentsDir()]) {
|
|
15
|
+
const dir = path.join(base, 'workflows', name);
|
|
16
|
+
if (fs.existsSync(path.join(dir, 'WORKFLOW.md')))
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
11
21
|
import { readBundle, resolveBundleEnv, describeBundle } from '../lib/secrets/bundles.js';
|
|
12
22
|
import { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, RUN_STRATEGIES, } from '../lib/rotate.js';
|
|
13
|
-
import { resolveVersionAlias } from '../lib/versions.js';
|
|
23
|
+
import { getGlobalDefault, getVersionHomePath, resolveVersionAlias } from '../lib/versions.js';
|
|
24
|
+
import { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion } from '../lib/plugins.js';
|
|
25
|
+
import * as fs from 'fs';
|
|
26
|
+
import * as path from 'path';
|
|
14
27
|
const VALID_AGENTS = Object.keys(AGENT_COMMANDS);
|
|
15
28
|
/** Type guard that narrows a string to a known AgentId. */
|
|
16
29
|
function isValidAgent(agent) {
|
|
@@ -122,6 +135,62 @@ Examples:
|
|
|
122
135
|
process.exit(1);
|
|
123
136
|
}
|
|
124
137
|
}
|
|
138
|
+
else if (resolveWorkflow(rawAgent)) {
|
|
139
|
+
// Workflow: ~/.agents-system/workflows/<name>/ or ~/.agents/workflows/<name>/
|
|
140
|
+
// Resolution: user repo wins over system repo (same precedence as all resources).
|
|
141
|
+
// Structure:
|
|
142
|
+
// WORKFLOW.md ← orchestrator instructions fed to claude as system prompt
|
|
143
|
+
// subagents/*.md ← flat .md files copied to ~/.claude/agents/ for Agent tool discovery
|
|
144
|
+
const workflowDir = resolveWorkflow(rawAgent);
|
|
145
|
+
agent = 'claude';
|
|
146
|
+
const resolvedVersion = resolveVersionAlias('claude', version);
|
|
147
|
+
const versionHome = getVersionHomePath('claude', resolvedVersion ?? getGlobalDefault('claude') ?? '');
|
|
148
|
+
const claudeAgentsDir = path.join(versionHome, '.claude', 'agents');
|
|
149
|
+
// Copy subagents/*.md into ~/.claude/agents/ so Claude's Agent tool finds them.
|
|
150
|
+
const subagentsDir = path.join(workflowDir, 'subagents');
|
|
151
|
+
if (fs.existsSync(subagentsDir)) {
|
|
152
|
+
fs.mkdirSync(claudeAgentsDir, { recursive: true });
|
|
153
|
+
for (const file of fs.readdirSync(subagentsDir).filter(f => f.endsWith('.md'))) {
|
|
154
|
+
fs.copyFileSync(path.join(subagentsDir, file), path.join(claudeAgentsDir, file));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Feed WORKFLOW.md body (strip frontmatter) as orchestrator system context.
|
|
158
|
+
const workflowMd = path.join(workflowDir, 'WORKFLOW.md');
|
|
159
|
+
const orchestratorBody = fs.existsSync(workflowMd)
|
|
160
|
+
? fs.readFileSync(workflowMd, 'utf-8').replace(/^---[\s\S]*?---\n/, '').trim()
|
|
161
|
+
: '';
|
|
162
|
+
if (orchestratorBody && prompt !== undefined) {
|
|
163
|
+
prompt = `${orchestratorBody}\n\n---\n\n${prompt}`;
|
|
164
|
+
}
|
|
165
|
+
// Sync workflow-scoped skills into the version home's skills dir.
|
|
166
|
+
const workflowSkillsDir = path.join(workflowDir, 'skills');
|
|
167
|
+
if (fs.existsSync(workflowSkillsDir)) {
|
|
168
|
+
const skillsTarget = path.join(claudeAgentsDir, '..', 'skills');
|
|
169
|
+
fs.mkdirSync(skillsTarget, { recursive: true });
|
|
170
|
+
for (const entry of fs.readdirSync(workflowSkillsDir, { withFileTypes: true })) {
|
|
171
|
+
if (!entry.isDirectory())
|
|
172
|
+
continue;
|
|
173
|
+
fs.cpSync(path.join(workflowSkillsDir, entry.name), path.join(skillsTarget, entry.name), { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Sync workflow-scoped plugins into the version home.
|
|
177
|
+
const workflowPluginsDir = path.join(workflowDir, 'plugins');
|
|
178
|
+
if (fs.existsSync(workflowPluginsDir)) {
|
|
179
|
+
for (const entry of fs.readdirSync(workflowPluginsDir, { withFileTypes: true })) {
|
|
180
|
+
if (!entry.isDirectory())
|
|
181
|
+
continue;
|
|
182
|
+
const pluginRoot = path.join(workflowPluginsDir, entry.name);
|
|
183
|
+
const manifest = loadPluginManifest(pluginRoot);
|
|
184
|
+
if (!manifest)
|
|
185
|
+
continue;
|
|
186
|
+
syncPluginToVersion(buildDiscoveredPlugin(pluginRoot, manifest), 'claude', versionHome);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const subagentCount = fs.existsSync(subagentsDir)
|
|
190
|
+
? fs.readdirSync(subagentsDir).filter(f => f.endsWith('.md')).length
|
|
191
|
+
: 0;
|
|
192
|
+
process.stderr.write(chalk.gray(`Workflow '${rawAgent}' → claude (${subagentCount} subagents)\n`));
|
|
193
|
+
}
|
|
125
194
|
else {
|
|
126
195
|
console.error(chalk.red(`Unknown agent: ${rawAgent}`));
|
|
127
196
|
console.error(chalk.gray(`Available agents: ${VALID_AGENTS.join(', ')}`));
|