@phnx-labs/agents-cli 1.20.93 → 1.21.1
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 +290 -0
- package/README.md +4 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/beta.js +3 -5
- package/dist/commands/doctor.js +23 -0
- package/dist/commands/exec.js +13 -0
- package/dist/commands/feed.js +37 -23
- package/dist/commands/mailboxes.js +39 -1
- package/dist/commands/message.js +12 -1
- package/dist/commands/monitors.js +8 -6
- package/dist/commands/packages.js +14 -5
- package/dist/commands/projects.d.ts +28 -0
- package/dist/commands/projects.js +173 -47
- package/dist/commands/routines.js +27 -1
- package/dist/commands/rules.js +6 -0
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.js +16 -8
- package/dist/commands/setup-browser.js +5 -1
- package/dist/commands/setup-fleet.js +5 -0
- package/dist/commands/setup-preferences.d.ts +53 -0
- package/dist/commands/setup-preferences.js +142 -0
- package/dist/commands/setup.js +8 -9
- package/dist/commands/ssh.js +220 -10
- package/dist/commands/sync.d.ts +2 -2
- package/dist/commands/sync.js +4 -9
- package/dist/commands/watchdog.js +26 -3
- package/dist/index.js +22 -6
- package/dist/lib/activity.d.ts +13 -0
- package/dist/lib/activity.js +22 -2
- package/dist/lib/auto-dispatch.d.ts +6 -1
- package/dist/lib/auto-dispatch.js +7 -2
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/channels/providers/openclaw-telegram.js +1 -1
- package/dist/lib/channels/resolve.d.ts +18 -1
- package/dist/lib/channels/resolve.js +15 -5
- package/dist/lib/daemon.d.ts +13 -0
- package/dist/lib/daemon.js +80 -60
- package/dist/lib/device-config.d.ts +82 -0
- package/dist/lib/device-config.js +296 -0
- package/dist/lib/event-provenance.d.ts +19 -0
- package/dist/lib/event-provenance.js +48 -0
- package/dist/lib/events.d.ts +2 -2
- package/dist/lib/events.js +4 -53
- package/dist/lib/feed-broadcast.d.ts +41 -5
- package/dist/lib/feed-broadcast.js +110 -12
- package/dist/lib/feed-post.d.ts +16 -2
- package/dist/lib/feed-post.js +35 -7
- package/dist/lib/feed.d.ts +1 -1
- package/dist/lib/feed.js +3 -1
- package/dist/lib/git.d.ts +13 -2
- package/dist/lib/git.js +38 -6
- package/dist/lib/hooks.d.ts +16 -1
- package/dist/lib/hooks.js +61 -1
- package/dist/lib/linear-project-counts.d.ts +72 -5
- package/dist/lib/linear-project-counts.js +90 -6
- package/dist/lib/linear-projects.d.ts +12 -0
- package/dist/lib/linear-projects.js +30 -0
- package/dist/lib/mailbox-gc.js +30 -7
- package/dist/lib/mailbox.d.ts +14 -1
- package/dist/lib/mailbox.js +35 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +5 -21
- package/dist/lib/menubar/install-menubar.js +35 -44
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/models.js +21 -11
- package/dist/lib/monitors/config.d.ts +1 -1
- package/dist/lib/monitors/dispatch.d.ts +6 -2
- package/dist/lib/monitors/dispatch.js +10 -15
- package/dist/lib/notify.d.ts +42 -6
- package/dist/lib/notify.js +41 -32
- package/dist/lib/overdue.d.ts +1 -1
- package/dist/lib/overdue.js +54 -19
- package/dist/lib/project-import.d.ts +96 -0
- package/dist/lib/project-import.js +171 -0
- package/dist/lib/remote-agents-json.d.ts +14 -1
- package/dist/lib/remote-agents-json.js +21 -3
- package/dist/lib/routines.d.ts +53 -0
- package/dist/lib/routines.js +105 -4
- package/dist/lib/rules/run-sync.d.ts +18 -0
- package/dist/lib/rules/run-sync.js +92 -0
- package/dist/lib/scheduler.js +12 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/index.d.ts +4 -5
- package/dist/lib/secrets/index.js +4 -5
- package/dist/lib/session/active.d.ts +16 -2
- package/dist/lib/session/active.js +8 -4
- package/dist/lib/session/bundle.js +5 -1
- package/dist/lib/session/remote-list.d.ts +1 -15
- package/dist/lib/session/remote-list.js +22 -111
- package/dist/lib/session/sync/config.d.ts +13 -5
- package/dist/lib/session/sync/config.js +21 -13
- package/dist/lib/startup/command-registry.d.ts +0 -2
- package/dist/lib/startup/command-registry.js +1 -5
- package/dist/lib/state.d.ts +7 -2
- package/dist/lib/state.js +85 -10
- package/dist/lib/sync-umbrella.d.ts +11 -23
- package/dist/lib/sync-umbrella.js +14 -45
- package/dist/lib/teams/agents.d.ts +35 -3
- package/dist/lib/teams/agents.js +52 -8
- package/dist/lib/teams/scheduler.d.ts +29 -2
- package/dist/lib/teams/scheduler.js +60 -16
- package/dist/lib/types.d.ts +23 -1
- package/package.json +1 -2
- package/dist/commands/drive.d.ts +0 -10
- package/dist/commands/drive.js +0 -183
- package/dist/commands/hq.d.ts +0 -2
- package/dist/commands/hq.js +0 -58
- package/dist/commands/sessions-sync.d.ts +0 -17
- package/dist/commands/sessions-sync.js +0 -135
- package/dist/commands/sync-provision.d.ts +0 -23
- package/dist/commands/sync-provision.js +0 -107
- package/dist/lib/drive-sync.d.ts +0 -45
- package/dist/lib/drive-sync.js +0 -238
- package/dist/lib/hq/floor.d.ts +0 -87
- package/dist/lib/hq/floor.js +0 -243
- package/dist/lib/session/sync/crdt.d.ts +0 -44
- package/dist/lib/session/sync/crdt.js +0 -119
- package/dist/lib/session/sync/manifest.d.ts +0 -62
- package/dist/lib/session/sync/manifest.js +0 -100
- package/dist/lib/session/sync/provision.d.ts +0 -49
- package/dist/lib/session/sync/provision.js +0 -91
- package/dist/lib/session/sync/r2.d.ts +0 -32
- package/dist/lib/session/sync/r2.js +0 -121
- package/dist/lib/session/sync/sync.d.ts +0 -106
- package/dist/lib/session/sync/sync.js +0 -374
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* 3. else the team pool has many devices → least-loaded pick
|
|
12
12
|
* 4. else (no pin, no pool) → null == run local
|
|
13
13
|
*
|
|
14
|
+
* Step 3 is cap-aware: a device at its `agents.max-concurrent` cap (from the
|
|
15
|
+
* device doc, passed in via PlacementOptions) is excluded from the auto-pick,
|
|
16
|
+
* and an all-capped pool fails loud. Pins and pools of one are the user's own
|
|
17
|
+
* choice and are never second-guessed.
|
|
18
|
+
*
|
|
14
19
|
* A device whose name equals the local machine id is treated as "local" — it
|
|
15
20
|
* resolves to a null placement so the existing local spawn path runs unchanged,
|
|
16
21
|
* letting the local machine participate in a pool as just another member.
|
|
@@ -20,31 +25,70 @@ import { machineId } from '../session/sync/config.js';
|
|
|
20
25
|
function isLocalDevice(device) {
|
|
21
26
|
return device.toLowerCase() === machineId();
|
|
22
27
|
}
|
|
28
|
+
/** Count RUNNING teammates per pool device. Pure. A null/empty hostName is a
|
|
29
|
+
* LOCAL teammate — it counts against the pool member that is this machine,
|
|
30
|
+
* otherwise a cap on the local device could never engage. */
|
|
31
|
+
function loadByDevice(devices, roster) {
|
|
32
|
+
const load = new Map();
|
|
33
|
+
for (const d of devices)
|
|
34
|
+
load.set(d, 0);
|
|
35
|
+
for (const r of roster) {
|
|
36
|
+
if (r.status !== 'running')
|
|
37
|
+
continue;
|
|
38
|
+
const host = r.hostName ? r.hostName : devices.find((d) => isLocalDevice(d));
|
|
39
|
+
if (!host)
|
|
40
|
+
continue; // local teammate but this machine is not in the pool
|
|
41
|
+
if (load.has(host))
|
|
42
|
+
load.set(host, (load.get(host) ?? 0) + 1);
|
|
43
|
+
}
|
|
44
|
+
return load;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pool devices excluded from auto-pick because they are at (or over) their
|
|
48
|
+
* `agents.max-concurrent` cap. Returned with the live counts so the caller can
|
|
49
|
+
* state the reason to the user instead of the device silently never winning.
|
|
50
|
+
*/
|
|
51
|
+
export function cappedDevices(devices, roster, maxConcurrent) {
|
|
52
|
+
const load = loadByDevice(devices, roster);
|
|
53
|
+
const capped = [];
|
|
54
|
+
for (const d of devices) {
|
|
55
|
+
const cap = maxConcurrent[d];
|
|
56
|
+
if (cap === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
const running = load.get(d) ?? 0;
|
|
59
|
+
if (running >= cap)
|
|
60
|
+
capped.push({ device: d, running, cap });
|
|
61
|
+
}
|
|
62
|
+
return capped;
|
|
63
|
+
}
|
|
23
64
|
/**
|
|
24
65
|
* Pick the least-loaded device from the pool — the one with the fewest RUNNING
|
|
25
66
|
* teammates currently assigned to it. Ties break by pool order (first wins), so
|
|
26
67
|
* an empty pool fills round-robin-ish as teammates launch. Pure: counts the
|
|
27
68
|
* roster, no I/O.
|
|
69
|
+
*
|
|
70
|
+
* With `maxConcurrent`, devices at their cap are excluded; if EVERY device is
|
|
71
|
+
* capped this throws naming each cap and the fix — a loud failure beats a
|
|
72
|
+
* teammate silently landing on a machine its operator capped.
|
|
28
73
|
*/
|
|
29
|
-
export function pickLeastLoaded(devices, roster) {
|
|
74
|
+
export function pickLeastLoaded(devices, roster, maxConcurrent) {
|
|
30
75
|
if (devices.length === 0) {
|
|
31
76
|
throw new Error('pickLeastLoaded called with an empty device pool');
|
|
32
77
|
}
|
|
33
|
-
const load =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
load.set(r.hostName, (load.get(r.hostName) ?? 0) + 1);
|
|
78
|
+
const load = loadByDevice(devices, roster);
|
|
79
|
+
const capped = new Set(maxConcurrent ? cappedDevices(devices, roster, maxConcurrent).map((c) => c.device) : []);
|
|
80
|
+
const eligible = devices.filter((d) => !capped.has(d));
|
|
81
|
+
if (eligible.length === 0) {
|
|
82
|
+
const detail = devices
|
|
83
|
+
.map((d) => `${d} (${load.get(d) ?? 0}/${maxConcurrent[d]})`)
|
|
84
|
+
.join(', ');
|
|
85
|
+
throw new Error(`Every device in the pool is at its agents.max-concurrent cap: ${detail}. ` +
|
|
86
|
+
`Raise a cap with 'agents devices configure <name> --max-agents N' or add a device to the pool.`);
|
|
43
87
|
}
|
|
44
88
|
// Iterate the pool in declared order so the first device wins ties.
|
|
45
|
-
let best =
|
|
89
|
+
let best = eligible[0];
|
|
46
90
|
let bestLoad = load.get(best) ?? 0;
|
|
47
|
-
for (const d of
|
|
91
|
+
for (const d of eligible) {
|
|
48
92
|
const l = load.get(d) ?? 0;
|
|
49
93
|
if (l < bestLoad) {
|
|
50
94
|
best = d;
|
|
@@ -59,7 +103,7 @@ export function pickLeastLoaded(devices, roster) {
|
|
|
59
103
|
* `{ device: <name> }` for a remote placement. See the cascade in the module
|
|
60
104
|
* header.
|
|
61
105
|
*/
|
|
62
|
-
export function resolvePlacement(team, explicitDevice, roster) {
|
|
106
|
+
export function resolvePlacement(team, explicitDevice, roster, opts) {
|
|
63
107
|
// 1. Explicit pin wins — even without a pool.
|
|
64
108
|
if (explicitDevice) {
|
|
65
109
|
return { device: isLocalDevice(explicitDevice) ? null : explicitDevice };
|
|
@@ -72,7 +116,7 @@ export function resolvePlacement(team, explicitDevice, roster) {
|
|
|
72
116
|
if (pool.length === 1) {
|
|
73
117
|
return { device: isLocalDevice(pool[0]) ? null : pool[0] };
|
|
74
118
|
}
|
|
75
|
-
// 3. Many → least-loaded across the pool.
|
|
76
|
-
const picked = pickLeastLoaded(pool, roster);
|
|
119
|
+
// 3. Many → least-loaded across the pool (cap-aware when caps are provided).
|
|
120
|
+
const picked = pickLeastLoaded(pool, roster, opts?.maxConcurrent);
|
|
77
121
|
return { device: isLocalDevice(picked) ? null : picked };
|
|
78
122
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface BudgetConfig {
|
|
|
62
62
|
require_confirm_over?: number;
|
|
63
63
|
}
|
|
64
64
|
/** Preview features that users can opt into via `agents beta`. */
|
|
65
|
-
export type BetaFeatureName = '
|
|
65
|
+
export type BetaFeatureName = 'factory' | 'projects';
|
|
66
66
|
/** Subset of chalk color names used for agent-specific terminal output. */
|
|
67
67
|
export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
|
|
68
68
|
/** Static configuration for a single agent -- paths, capabilities, and format conventions. */
|
|
@@ -284,6 +284,12 @@ export type HookCache = string | HookCacheConfig;
|
|
|
284
284
|
export interface ManifestHook {
|
|
285
285
|
script: string;
|
|
286
286
|
events: string[];
|
|
287
|
+
/**
|
|
288
|
+
* Seconds before the hook is killed (default 600). In agents.yaml this may be
|
|
289
|
+
* written as a bare number (seconds) or a duration string (`5s`, `2m`,
|
|
290
|
+
* `1h30m`); `parseHookManifest` normalizes it to a seconds number here, so
|
|
291
|
+
* consumers always see a number.
|
|
292
|
+
*/
|
|
287
293
|
timeout?: number;
|
|
288
294
|
matcher?: string;
|
|
289
295
|
/** @deprecated Use the agent capability table; field is ignored. */
|
|
@@ -845,6 +851,22 @@ export interface Meta {
|
|
|
845
851
|
* `agents browser profiles set-default <name>`.
|
|
846
852
|
*/
|
|
847
853
|
defaultBrowserProfile?: string;
|
|
854
|
+
/**
|
|
855
|
+
* User-scope config block (`config:` in central agents.yaml). Holds the
|
|
856
|
+
* user-scope keys from the device-config registry (`lib/device-config.ts`) —
|
|
857
|
+
* today just `interactiveHost`. Syncs fleet-wide via `agents repo push/pull`.
|
|
858
|
+
* Device-scope keys live in {@link Meta.deviceConfig} instead.
|
|
859
|
+
*/
|
|
860
|
+
config?: Record<string, unknown>;
|
|
861
|
+
/**
|
|
862
|
+
* Device-scope config block, carried in memory under a distinct field so it
|
|
863
|
+
* can never leak into the central (synced) agents.yaml: `writeMetaUnlocked`
|
|
864
|
+
* routes it to `~/.agents/devices/<machine>/agents.yaml` under the `config:`
|
|
865
|
+
* key (mirroring how `defaultBrowserProfile` is routed), and
|
|
866
|
+
* `overlayMachineLocal` reads it back. Holds the device-scope keys from the
|
|
867
|
+
* device-config registry (`maxAgents`, `schedulerEnabled`, `notes`). Per-machine by design — unset = today's behavior.
|
|
868
|
+
*/
|
|
869
|
+
deviceConfig?: Record<string, unknown>;
|
|
848
870
|
/**
|
|
849
871
|
* Agent-host registry keyed by host name (`agents hosts`). Portable user
|
|
850
872
|
* config synced with `agents repo push/pull`. For `ssh-config` hosts this is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.1",
|
|
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",
|
|
@@ -90,7 +90,6 @@
|
|
|
90
90
|
"@xterm/headless": "6.0.0",
|
|
91
91
|
"@zed-industries/agent-client-protocol": "0.4.5",
|
|
92
92
|
"age-encryption": "0.3.0",
|
|
93
|
-
"aws4fetch": "1.0.20",
|
|
94
93
|
"chalk": "5.6.2",
|
|
95
94
|
"commander": "15.0.0",
|
|
96
95
|
"croner": "10.0.1",
|
package/dist/commands/drive.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drive sync commands.
|
|
3
|
-
*
|
|
4
|
-
* Registers the `agents drive` command tree for syncing agent session
|
|
5
|
-
* history across machines via rsync. Supports pull, push, attach
|
|
6
|
-
* (redirect agent homes to drive), and detach operations.
|
|
7
|
-
*/
|
|
8
|
-
import type { Command } from 'commander';
|
|
9
|
-
/** Register the `agents drive` command tree. */
|
|
10
|
-
export declare function registerDriveCommands(program: Command): void;
|
package/dist/commands/drive.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drive sync commands.
|
|
3
|
-
*
|
|
4
|
-
* Registers the `agents drive` command tree for syncing agent session
|
|
5
|
-
* history across machines via rsync. Supports pull, push, attach
|
|
6
|
-
* (redirect agent homes to drive), and detach operations.
|
|
7
|
-
*/
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import { betaEnableHint, isBetaEnabled } from '../lib/beta.js';
|
|
11
|
-
import { setRemote, pull, push, attach, detach, getDriveStatus, } from '../lib/drive-sync.js';
|
|
12
|
-
/** Register the `agents drive` command tree. */
|
|
13
|
-
export function registerDriveCommands(program) {
|
|
14
|
-
const enabled = isBetaEnabled('drive');
|
|
15
|
-
const driveCmd = program
|
|
16
|
-
.command('drive', { hidden: !enabled })
|
|
17
|
-
.description('Sync agent session history across machines via rsync. Set up once, then pull/push to keep sessions in sync.')
|
|
18
|
-
.addHelpText('after', `
|
|
19
|
-
Typical workflow:
|
|
20
|
-
# One-time setup: point to your remote machine
|
|
21
|
-
agents drive remote user@hostname
|
|
22
|
-
|
|
23
|
-
# Pull sessions from remote to local
|
|
24
|
-
agents drive pull
|
|
25
|
-
|
|
26
|
-
# Work locally with your agents...
|
|
27
|
-
|
|
28
|
-
# Push your new sessions back to remote
|
|
29
|
-
agents drive push
|
|
30
|
-
|
|
31
|
-
# Check sync state
|
|
32
|
-
agents drive status
|
|
33
|
-
|
|
34
|
-
Use case: Keep session history consistent across your laptop and desktop,
|
|
35
|
-
or back up sessions to a server you control.
|
|
36
|
-
`);
|
|
37
|
-
driveCmd.hook('preAction', () => {
|
|
38
|
-
if (enabled)
|
|
39
|
-
return;
|
|
40
|
-
console.error(chalk.red('agents drive is in beta.'));
|
|
41
|
-
console.error(chalk.gray(betaEnableHint('drive')));
|
|
42
|
-
process.exit(1);
|
|
43
|
-
});
|
|
44
|
-
driveCmd
|
|
45
|
-
.command('remote <target>')
|
|
46
|
-
.description('Set the rsync remote target (e.g., user@hostname). Sessions sync to <target>:~/.agents/drive/')
|
|
47
|
-
.addHelpText('after', `
|
|
48
|
-
Examples:
|
|
49
|
-
# Set remote to a server
|
|
50
|
-
agents drive remote user@server
|
|
51
|
-
|
|
52
|
-
# Set remote to a local machine on your network
|
|
53
|
-
agents drive remote macbook.local
|
|
54
|
-
`)
|
|
55
|
-
.action((target) => {
|
|
56
|
-
try {
|
|
57
|
-
setRemote(target);
|
|
58
|
-
console.log(chalk.green(`Remote set to ${target}`));
|
|
59
|
-
console.log(chalk.gray(`Syncs to ${target}:~/.agents/drive/`));
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
console.error(chalk.red(err.message));
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
driveCmd
|
|
67
|
-
.command('pull')
|
|
68
|
-
.description('Fetch sessions from the remote and merge them into your local ~/.agents/drive/.')
|
|
69
|
-
.addHelpText('after', `
|
|
70
|
-
Example:
|
|
71
|
-
agents drive pull
|
|
72
|
-
|
|
73
|
-
Run this when you switch machines to get the latest session history.
|
|
74
|
-
`)
|
|
75
|
-
.action(async () => {
|
|
76
|
-
const spinner = ora('Pulling from remote...').start();
|
|
77
|
-
try {
|
|
78
|
-
await pull();
|
|
79
|
-
spinner.succeed('Pull complete');
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
spinner.fail(err.message);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
driveCmd
|
|
87
|
-
.command('push')
|
|
88
|
-
.description('Upload your local sessions to the remote so other machines can pull them.')
|
|
89
|
-
.addHelpText('after', `
|
|
90
|
-
Example:
|
|
91
|
-
agents drive push
|
|
92
|
-
|
|
93
|
-
Run this after working locally to share your new sessions with other machines.
|
|
94
|
-
`)
|
|
95
|
-
.action(async () => {
|
|
96
|
-
const spinner = ora('Pushing to remote...').start();
|
|
97
|
-
try {
|
|
98
|
-
await push();
|
|
99
|
-
spinner.succeed('Push complete');
|
|
100
|
-
}
|
|
101
|
-
catch (err) {
|
|
102
|
-
spinner.fail(err.message);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
driveCmd
|
|
107
|
-
.command('attach')
|
|
108
|
-
.description('Point your agent homes (~/.claude, etc.) at drive so sessions write directly to the synced location.')
|
|
109
|
-
.addHelpText('after', `
|
|
110
|
-
Example:
|
|
111
|
-
agents drive attach
|
|
112
|
-
|
|
113
|
-
After attach, agent sessions are saved to ~/.agents/drive/ instead of version homes.
|
|
114
|
-
Use 'detach' to switch back to version-specific homes.
|
|
115
|
-
`)
|
|
116
|
-
.action(() => {
|
|
117
|
-
try {
|
|
118
|
-
attach();
|
|
119
|
-
console.log(chalk.green('Drive attached'));
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
console.error(chalk.red(err.message));
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
driveCmd
|
|
127
|
-
.command('detach')
|
|
128
|
-
.description('Restore agent homes back to their version-specific directories (undo attach).')
|
|
129
|
-
.addHelpText('after', `
|
|
130
|
-
Example:
|
|
131
|
-
agents drive detach
|
|
132
|
-
`)
|
|
133
|
-
.action(() => {
|
|
134
|
-
try {
|
|
135
|
-
detach();
|
|
136
|
-
console.log(chalk.green('Drive detached'));
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
console.error(chalk.red(err.message));
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
driveCmd
|
|
144
|
-
.command('status')
|
|
145
|
-
.description('Show current drive configuration, attach state, and last sync times.')
|
|
146
|
-
.addHelpText('after', `
|
|
147
|
-
Example:
|
|
148
|
-
agents drive status
|
|
149
|
-
`)
|
|
150
|
-
.action(() => {
|
|
151
|
-
const status = getDriveStatus();
|
|
152
|
-
console.log(chalk.bold('Drive'));
|
|
153
|
-
console.log(` Remote: ${status.remote || chalk.gray('not set')}`);
|
|
154
|
-
console.log(` Attached: ${status.attached ? chalk.green('yes') : chalk.gray('no')}`);
|
|
155
|
-
console.log(` Last pull: ${status.lastPull ? formatTime(status.lastPull) : chalk.gray('never')}`);
|
|
156
|
-
console.log(` Last push: ${status.lastPush ? formatTime(status.lastPush) : chalk.gray('never')}`);
|
|
157
|
-
console.log(` Path: ${chalk.gray(status.driveDir)}`);
|
|
158
|
-
console.log('');
|
|
159
|
-
console.log(chalk.bold('Symlinks'));
|
|
160
|
-
console.log(` ~/.claude -> ${status.configDirTarget || chalk.gray('not a symlink')}`);
|
|
161
|
-
for (const [hf, target] of Object.entries(status.homeFileTargets)) {
|
|
162
|
-
console.log(` ~/${hf} -> ${target || chalk.gray('not a symlink')}`);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
/** Format an ISO timestamp as a human-readable relative time (e.g. "5 min ago"). */
|
|
167
|
-
function formatTime(iso) {
|
|
168
|
-
const d = new Date(iso);
|
|
169
|
-
const now = Date.now();
|
|
170
|
-
const diffMin = Math.floor((now - d.getTime()) / 60_000);
|
|
171
|
-
if (diffMin < 1)
|
|
172
|
-
return 'just now';
|
|
173
|
-
if (diffMin < 60)
|
|
174
|
-
return `${diffMin} min ago`;
|
|
175
|
-
const diffHrs = Math.floor(diffMin / 60);
|
|
176
|
-
if (diffHrs < 24)
|
|
177
|
-
return `${diffHrs} hour${diffHrs === 1 ? '' : 's'} ago`;
|
|
178
|
-
const diffDays = Math.floor(diffHrs / 24);
|
|
179
|
-
if (diffDays < 7)
|
|
180
|
-
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
181
|
-
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
182
|
-
return `${months[d.getMonth()]} ${d.getDate()}`;
|
|
183
|
-
}
|
package/dist/commands/hq.d.ts
DELETED
package/dist/commands/hq.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { getActiveSessions } from '../lib/session/active.js';
|
|
3
|
-
import { AgentManager } from '../lib/teams/agents.js';
|
|
4
|
-
import { handleStatus, handleTasks } from '../lib/teams/api.js';
|
|
5
|
-
import { listAskStats, listBlocks } from '../lib/feed.js';
|
|
6
|
-
import { buildSessionSignals, synthesizeControlCards } from '../lib/feed-ranking.js';
|
|
7
|
-
import { discoverSessions } from '../lib/session/discover.js';
|
|
8
|
-
import { buildHqFloor } from '../lib/hq/floor.js';
|
|
9
|
-
import { die } from '../lib/format.js';
|
|
10
|
-
async function collectFloorSnapshot() {
|
|
11
|
-
const manager = new AgentManager();
|
|
12
|
-
const [sessions, tasksResult] = await Promise.all([
|
|
13
|
-
getActiveSessions(),
|
|
14
|
-
handleTasks(manager, 1000),
|
|
15
|
-
]);
|
|
16
|
-
const teammatesByTeam = new Map();
|
|
17
|
-
await Promise.all(tasksResult.tasks.map(async (team) => {
|
|
18
|
-
const status = await handleStatus(manager, team.task_name, 'all');
|
|
19
|
-
teammatesByTeam.set(team.task_name, status.agents);
|
|
20
|
-
}));
|
|
21
|
-
const sessionMetas = sessions.length > 0 ? await discoverSessions({ all: true, limit: 5000 }) : [];
|
|
22
|
-
const blocks = [
|
|
23
|
-
...listBlocks(),
|
|
24
|
-
...synthesizeControlCards(buildSessionSignals(sessions, sessionMetas), listAskStats()),
|
|
25
|
-
];
|
|
26
|
-
return buildHqFloor({
|
|
27
|
-
sessions,
|
|
28
|
-
teams: tasksResult.tasks,
|
|
29
|
-
teammatesByTeam,
|
|
30
|
-
blocks,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export function registerHqCommand(program) {
|
|
34
|
-
const hq = program
|
|
35
|
-
.command('hq')
|
|
36
|
-
.description('Machine-readable bridge for Agents HQ floor management.');
|
|
37
|
-
hq
|
|
38
|
-
.command('floor')
|
|
39
|
-
.description('Emit the live floor snapshot: rooms, agents, ambient events, and runnable actions.')
|
|
40
|
-
.option('--json', 'Output machine-readable JSON')
|
|
41
|
-
.action(async (opts) => {
|
|
42
|
-
try {
|
|
43
|
-
const snapshot = await collectFloorSnapshot();
|
|
44
|
-
if (opts.json || !process.stdout.isTTY) {
|
|
45
|
-
console.log(JSON.stringify(snapshot, null, 2));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
console.log(chalk.bold('Agents HQ floor'));
|
|
49
|
-
console.log(chalk.gray(`${snapshot.counters.agents} agents, ${snapshot.counters.rooms} rooms, ${snapshot.counters.needsInput} need input`));
|
|
50
|
-
for (const room of snapshot.rooms) {
|
|
51
|
-
console.log(`${chalk.cyan(room.name)} ${chalk.gray(`${room.counts.agents} agents, ${room.counts.running} running, ${room.counts.needsInput} need input`)}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
die(err.message);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `agents sessions sync` — push this machine's transcripts to R2 and pull every
|
|
3
|
-
* other machine's, merging copies of the same session via CRDT union. The local
|
|
4
|
-
* sessions index is rebuilt from the synced-in mirror by the normal scanner.
|
|
5
|
-
*/
|
|
6
|
-
import type { Command } from 'commander';
|
|
7
|
-
interface SyncCmdOptions {
|
|
8
|
-
verbose?: boolean;
|
|
9
|
-
json?: boolean;
|
|
10
|
-
enable?: boolean;
|
|
11
|
-
disable?: boolean;
|
|
12
|
-
status?: boolean;
|
|
13
|
-
setup?: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare function runSessionsSync(options: SyncCmdOptions): Promise<void>;
|
|
16
|
-
export declare function registerSessionsSyncCommand(sessionsCmd: Command): void;
|
|
17
|
-
export {};
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `agents sessions sync` — push this machine's transcripts to R2 and pull every
|
|
3
|
-
* other machine's, merging copies of the same session via CRDT union. The local
|
|
4
|
-
* sessions index is rebuilt from the synced-in mirror by the normal scanner.
|
|
5
|
-
*/
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import { setHelpSections } from '../lib/help.js';
|
|
8
|
-
import { isSyncConfigured, SYNC_BUNDLE } from '../lib/session/sync/config.js';
|
|
9
|
-
import { isBetaEnabled, setBetaEnabled, betaEnableHint } from '../lib/beta.js';
|
|
10
|
-
import { syncSessions } from '../lib/session/sync/sync.js';
|
|
11
|
-
/** The daemon's automatic session sync is gated by this beta feature. */
|
|
12
|
-
const SYNC_BETA = 'session-sync';
|
|
13
|
-
export async function runSessionsSync(options) {
|
|
14
|
-
// Toggle / status delegate to the `session-sync` beta feature — the single
|
|
15
|
-
// source of truth for whether the daemon auto-syncs (opt-in, off by default).
|
|
16
|
-
// These short-circuit before any network cycle.
|
|
17
|
-
if (options.disable) {
|
|
18
|
-
setBetaEnabled([SYNC_BETA], false);
|
|
19
|
-
console.log(chalk.yellow('Automatic session sync disabled') +
|
|
20
|
-
chalk.dim(' — the daemon stops pushing/pulling within ~90s. (Same as: agents beta disable session-sync)'));
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (options.enable) {
|
|
24
|
-
setBetaEnabled([SYNC_BETA], true);
|
|
25
|
-
console.log(chalk.green('Automatic session sync enabled') +
|
|
26
|
-
chalk.dim(' — the daemon resumes on its next cycle. (Same as: agents beta enable session-sync)'));
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (options.setup) {
|
|
30
|
-
const { promptAndProvisionSessionSync } = await import('./sync-provision.js');
|
|
31
|
-
await promptAndProvisionSessionSync({ explicit: true });
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (options.status) {
|
|
35
|
-
const enabled = isBetaEnabled(SYNC_BETA);
|
|
36
|
-
const configured = isSyncConfigured();
|
|
37
|
-
if (options.json) {
|
|
38
|
-
console.log(JSON.stringify({ enabled, configured }, null, 2));
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
console.log(`automatic sync: ${enabled ? chalk.green('enabled (beta)') : chalk.yellow('disabled')}` +
|
|
42
|
-
chalk.dim(' · ') +
|
|
43
|
-
`credentials: ${configured ? chalk.green('configured') : chalk.yellow(`missing (${SYNC_BUNDLE})`)}`);
|
|
44
|
-
if (!enabled)
|
|
45
|
-
console.log(chalk.dim(` ${betaEnableHint(SYNC_BETA)}`));
|
|
46
|
-
}
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (!isSyncConfigured()) {
|
|
50
|
-
console.error(chalk.red(`Sessions sync is not configured.`) +
|
|
51
|
-
`\nAdd R2 credentials to the '${SYNC_BUNDLE}' bundle:\n` +
|
|
52
|
-
` agents secrets add ${SYNC_BUNDLE} R2_ACCOUNT_ID\n` +
|
|
53
|
-
` agents secrets add ${SYNC_BUNDLE} R2_BUCKET_NAME\n` +
|
|
54
|
-
` agents secrets add ${SYNC_BUNDLE} R2_ACCESS_KEY_ID\n` +
|
|
55
|
-
` agents secrets add ${SYNC_BUNDLE} R2_SECRET_ACCESS_KEY\n` +
|
|
56
|
-
chalk.dim('\nOr let a guided prompt mint the bundle for you: ') + chalk.cyan('agents sessions sync --setup'));
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
const result = await syncSessions({
|
|
62
|
-
verbose: options.verbose,
|
|
63
|
-
log: msg => console.error(chalk.dim(msg)),
|
|
64
|
-
});
|
|
65
|
-
if (options.json) {
|
|
66
|
-
console.log(JSON.stringify(result, null, 2));
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
const parts = [
|
|
70
|
-
`pushed ${result.pushed}`,
|
|
71
|
-
`pulled ${result.pulled}`,
|
|
72
|
-
result.merged > 0 ? `merged ${result.merged}` : null,
|
|
73
|
-
].filter(Boolean);
|
|
74
|
-
console.log(chalk.green('synced') + ` ${result.machine}: ` + parts.join(', ') +
|
|
75
|
-
chalk.dim(` (${result.pushSkipped + result.pullSkipped} unchanged)`));
|
|
76
|
-
}
|
|
77
|
-
if (!options.json && result.warnings.length > 0) {
|
|
78
|
-
for (const w of result.warnings)
|
|
79
|
-
console.error(chalk.yellow(` warning: ${w}`));
|
|
80
|
-
}
|
|
81
|
-
if (result.errors.length > 0) {
|
|
82
|
-
for (const e of result.errors)
|
|
83
|
-
console.error(chalk.yellow(` ! ${e}`));
|
|
84
|
-
process.exitCode = 1;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
console.error(chalk.red(`sync failed: ${err.message}`));
|
|
89
|
-
process.exitCode = 1;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
export function registerSessionsSyncCommand(sessionsCmd) {
|
|
93
|
-
const syncCmd = sessionsCmd
|
|
94
|
-
.command('sync')
|
|
95
|
-
.description('Sync session transcripts across machines via R2 (CRDT merge). Claude, Codex, Droid, Grok, Kimi, and OpenCode.')
|
|
96
|
-
.option('-v, --verbose', 'Log each pushed and pulled session')
|
|
97
|
-
.option('--json', 'Output the sync result as JSON')
|
|
98
|
-
.option('--setup', 'Guided provisioning: mint the r2.backups bundle, verify connectivity, and enable sync')
|
|
99
|
-
.option('--enable', 'Opt in to automatic background sync (beta; alias for: agents beta enable session-sync)')
|
|
100
|
-
.option('--disable', 'Opt out of automatic background sync (alias for: agents beta disable session-sync)')
|
|
101
|
-
.option('--status', 'Show whether automatic sync is opted-in (beta) and configured');
|
|
102
|
-
setHelpSections(syncCmd, {
|
|
103
|
-
examples: `
|
|
104
|
-
# Guided first-time setup (mint R2 bundle, verify, enable)
|
|
105
|
-
agents sessions sync --setup
|
|
106
|
-
|
|
107
|
-
# One sync cycle (push local changes, pull + merge from other machines)
|
|
108
|
-
agents sessions sync
|
|
109
|
-
|
|
110
|
-
# See exactly what moved
|
|
111
|
-
agents sessions sync --verbose
|
|
112
|
-
|
|
113
|
-
# Stop this machine's daemon from auto-syncing (prefer on-demand --host reads)
|
|
114
|
-
agents sessions sync --disable
|
|
115
|
-
|
|
116
|
-
# Check the current switch + credential state
|
|
117
|
-
agents sessions sync --status
|
|
118
|
-
`,
|
|
119
|
-
notes: `
|
|
120
|
-
- Credentials come from the '${SYNC_BUNDLE}' secrets bundle (R2 S3 API, read+write).
|
|
121
|
-
- Each machine writes only its own prefix; conflicts are impossible by construction.
|
|
122
|
-
- Sessions present locally always win; synced-in copies fill in other machines' sessions.
|
|
123
|
-
- Automatic background sync is an opt-in BETA feature, OFF by default. The daemon only
|
|
124
|
-
syncs (~90s) once you opt in via 'agents beta enable session-sync' (or the --enable
|
|
125
|
-
alias here). A bare 'agents sessions sync' always forces a manual one-shot cycle
|
|
126
|
-
regardless of the beta opt-in.
|
|
127
|
-
`,
|
|
128
|
-
});
|
|
129
|
-
// `--json` is also declared on the parent `sessions` command, so a bare
|
|
130
|
-
// `options` arg would miss it (Commander binds the shared flag to the parent).
|
|
131
|
-
// optsWithGlobals() merges ancestor + local options so --json resolves here.
|
|
132
|
-
syncCmd.action(async (_options, cmd) => {
|
|
133
|
-
await runSessionsSync(cmd.optsWithGlobals());
|
|
134
|
-
});
|
|
135
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive provisioning flow for cross-machine session sync. Collects R2
|
|
3
|
-
* credentials, writes the `r2.backups` bundle (via the pure lib helper), probes
|
|
4
|
-
* connectivity, and opts the machine into the `session-sync` beta. Shared by
|
|
5
|
-
* `agents setup` (offered, opt-in) and `agents sessions sync --setup` (explicit).
|
|
6
|
-
*
|
|
7
|
-
* The prompt/UI lives here in the command layer; the credential-writing and
|
|
8
|
-
* connectivity logic is the pure, unit-tested `lib/session/sync/provision.ts`.
|
|
9
|
-
*/
|
|
10
|
-
export interface ProvisionFlowOptions {
|
|
11
|
-
/**
|
|
12
|
-
* True when invoked explicitly (`agents sessions sync --setup`): we go straight
|
|
13
|
-
* into provisioning. False when offered inside `agents setup`: we ask first,
|
|
14
|
-
* default to No, and silently skip when already configured.
|
|
15
|
-
*/
|
|
16
|
-
explicit?: boolean;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Run the guided session-sync setup. Never throws for expected outcomes
|
|
20
|
-
* (non-interactive shell, user declines, cancels, or a failed probe) — returns
|
|
21
|
-
* quietly so a first-run `agents setup` is never blocked by the optional step.
|
|
22
|
-
*/
|
|
23
|
-
export declare function promptAndProvisionSessionSync(opts?: ProvisionFlowOptions): Promise<void>;
|