@phnx-labs/agents-cli 1.20.93 → 1.21.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 (112) hide show
  1. package/CHANGELOG.md +264 -0
  2. package/README.md +3 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/beta.js +3 -5
  5. package/dist/commands/doctor.js +23 -0
  6. package/dist/commands/exec.js +13 -0
  7. package/dist/commands/mailboxes.js +39 -1
  8. package/dist/commands/message.js +12 -1
  9. package/dist/commands/packages.js +14 -5
  10. package/dist/commands/projects.d.ts +28 -0
  11. package/dist/commands/projects.js +173 -47
  12. package/dist/commands/routines.js +27 -1
  13. package/dist/commands/rules.js +6 -0
  14. package/dist/commands/sessions.d.ts +17 -0
  15. package/dist/commands/sessions.js +16 -8
  16. package/dist/commands/setup-browser.js +5 -1
  17. package/dist/commands/setup-fleet.js +5 -0
  18. package/dist/commands/setup-preferences.d.ts +53 -0
  19. package/dist/commands/setup-preferences.js +142 -0
  20. package/dist/commands/setup.js +8 -9
  21. package/dist/commands/ssh.js +220 -10
  22. package/dist/commands/sync.d.ts +2 -2
  23. package/dist/commands/sync.js +4 -9
  24. package/dist/commands/watchdog.js +26 -3
  25. package/dist/index.js +22 -6
  26. package/dist/lib/activity.d.ts +7 -0
  27. package/dist/lib/activity.js +19 -2
  28. package/dist/lib/auto-dispatch.d.ts +6 -1
  29. package/dist/lib/auto-dispatch.js +7 -2
  30. package/dist/lib/beta.d.ts +1 -1
  31. package/dist/lib/beta.js +1 -1
  32. package/dist/lib/daemon.d.ts +13 -0
  33. package/dist/lib/daemon.js +80 -60
  34. package/dist/lib/device-config.d.ts +82 -0
  35. package/dist/lib/device-config.js +296 -0
  36. package/dist/lib/event-provenance.d.ts +19 -0
  37. package/dist/lib/event-provenance.js +48 -0
  38. package/dist/lib/events.d.ts +2 -2
  39. package/dist/lib/events.js +4 -53
  40. package/dist/lib/feed.d.ts +1 -1
  41. package/dist/lib/feed.js +2 -0
  42. package/dist/lib/git.d.ts +13 -2
  43. package/dist/lib/git.js +38 -6
  44. package/dist/lib/linear-project-counts.d.ts +72 -5
  45. package/dist/lib/linear-project-counts.js +90 -6
  46. package/dist/lib/linear-projects.d.ts +12 -0
  47. package/dist/lib/linear-projects.js +30 -0
  48. package/dist/lib/mailbox-gc.js +30 -7
  49. package/dist/lib/mailbox.d.ts +14 -1
  50. package/dist/lib/mailbox.js +35 -3
  51. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  52. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  53. package/dist/lib/menubar/install-menubar.d.ts +5 -21
  54. package/dist/lib/menubar/install-menubar.js +35 -44
  55. package/dist/lib/migrate.js +1 -1
  56. package/dist/lib/models.js +21 -11
  57. package/dist/lib/overdue.d.ts +1 -1
  58. package/dist/lib/overdue.js +54 -19
  59. package/dist/lib/project-import.d.ts +96 -0
  60. package/dist/lib/project-import.js +171 -0
  61. package/dist/lib/remote-agents-json.d.ts +14 -1
  62. package/dist/lib/remote-agents-json.js +21 -3
  63. package/dist/lib/routines.d.ts +53 -0
  64. package/dist/lib/routines.js +105 -4
  65. package/dist/lib/rules/run-sync.d.ts +18 -0
  66. package/dist/lib/rules/run-sync.js +92 -0
  67. package/dist/lib/scheduler.js +12 -2
  68. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  69. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  70. package/dist/lib/secrets/index.d.ts +4 -5
  71. package/dist/lib/secrets/index.js +4 -5
  72. package/dist/lib/session/active.d.ts +16 -2
  73. package/dist/lib/session/active.js +8 -4
  74. package/dist/lib/session/bundle.js +5 -1
  75. package/dist/lib/session/remote-list.d.ts +1 -15
  76. package/dist/lib/session/remote-list.js +22 -111
  77. package/dist/lib/session/sync/config.d.ts +13 -5
  78. package/dist/lib/session/sync/config.js +21 -13
  79. package/dist/lib/startup/command-registry.d.ts +0 -2
  80. package/dist/lib/startup/command-registry.js +1 -5
  81. package/dist/lib/state.d.ts +7 -2
  82. package/dist/lib/state.js +85 -10
  83. package/dist/lib/sync-umbrella.d.ts +11 -23
  84. package/dist/lib/sync-umbrella.js +14 -45
  85. package/dist/lib/teams/agents.d.ts +35 -3
  86. package/dist/lib/teams/agents.js +52 -8
  87. package/dist/lib/teams/scheduler.d.ts +29 -2
  88. package/dist/lib/teams/scheduler.js +60 -16
  89. package/dist/lib/types.d.ts +17 -1
  90. package/package.json +1 -2
  91. package/dist/commands/drive.d.ts +0 -10
  92. package/dist/commands/drive.js +0 -183
  93. package/dist/commands/hq.d.ts +0 -2
  94. package/dist/commands/hq.js +0 -58
  95. package/dist/commands/sessions-sync.d.ts +0 -17
  96. package/dist/commands/sessions-sync.js +0 -135
  97. package/dist/commands/sync-provision.d.ts +0 -23
  98. package/dist/commands/sync-provision.js +0 -107
  99. package/dist/lib/drive-sync.d.ts +0 -45
  100. package/dist/lib/drive-sync.js +0 -238
  101. package/dist/lib/hq/floor.d.ts +0 -87
  102. package/dist/lib/hq/floor.js +0 -243
  103. package/dist/lib/session/sync/crdt.d.ts +0 -44
  104. package/dist/lib/session/sync/crdt.js +0 -119
  105. package/dist/lib/session/sync/manifest.d.ts +0 -62
  106. package/dist/lib/session/sync/manifest.js +0 -100
  107. package/dist/lib/session/sync/provision.d.ts +0 -49
  108. package/dist/lib/session/sync/provision.js +0 -91
  109. package/dist/lib/session/sync/r2.d.ts +0 -32
  110. package/dist/lib/session/sync/r2.js +0 -121
  111. package/dist/lib/session/sync/sync.d.ts +0 -106
  112. package/dist/lib/session/sync/sync.js +0 -374
@@ -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
- }
@@ -1,2 +0,0 @@
1
- import type { Command } from 'commander';
2
- export declare function registerHqCommand(program: Command): void;
@@ -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>;
@@ -1,107 +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
- import chalk from 'chalk';
11
- import ora from 'ora';
12
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
13
- import { isSyncConfigured } from '../lib/session/sync/config.js';
14
- import { writeSyncBundle, probeR2Connectivity, readStoredEncKey } from '../lib/session/sync/provision.js';
15
- import { setBetaEnabled } from '../lib/beta.js';
16
- const SYNC_BETA = 'session-sync';
17
- const nonEmpty = (v) => (v.trim().length > 0 ? true : 'required');
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 async function promptAndProvisionSessionSync(opts = {}) {
24
- if (!isInteractiveTerminal()) {
25
- if (opts.explicit)
26
- console.error(chalk.red('Session-sync setup needs an interactive terminal.'));
27
- return;
28
- }
29
- const alreadyConfigured = isSyncConfigured();
30
- const { confirm, input, password } = await import('@inquirer/prompts');
31
- try {
32
- if (!opts.explicit) {
33
- if (alreadyConfigured)
34
- return; // first-run setup: nothing to do
35
- const want = await confirm({
36
- message: 'Set up cross-machine session sync (encrypted transcripts via Cloudflare R2)?',
37
- default: false,
38
- });
39
- if (!want)
40
- return;
41
- }
42
- else if (alreadyConfigured) {
43
- const reconfigure = await confirm({
44
- message: 'Session sync is already configured. Re-enter R2 credentials?',
45
- default: false,
46
- });
47
- if (!reconfigure) {
48
- const key = readStoredEncKey();
49
- if (key) {
50
- console.log(chalk.dim('\nShared encryption key (paste on every other machine you sync):'));
51
- console.log(' ' + chalk.cyan(key));
52
- }
53
- return;
54
- }
55
- }
56
- console.log(chalk.dim('\nR2 credentials — Cloudflare dashboard > R2 > Manage API Tokens (needs read+write):'));
57
- const accountId = (await input({ message: 'R2 account ID:', validate: nonEmpty })).trim();
58
- const bucketName = (await input({ message: 'R2 bucket name:', validate: nonEmpty })).trim();
59
- const accessKeyId = (await password({ message: 'R2 access key ID:', mask: true })).trim();
60
- const secretAccessKey = (await password({ message: 'R2 secret access key:', mask: true })).trim();
61
- const endpoint = (await input({
62
- message: 'S3 endpoint override (blank = Cloudflare R2):',
63
- default: '',
64
- })).trim();
65
- // Encryption key: the first machine mints one; every other machine pastes it
66
- // so the whole fabric shares a single key (and can decrypt each other).
67
- const firstMachine = await confirm({ message: 'Is this the FIRST machine in your sync fabric?', default: true });
68
- let encKey;
69
- if (!firstMachine) {
70
- encKey = (await password({
71
- message: 'Paste the shared R2_SYNC_ENC_KEY from your first machine:',
72
- mask: true,
73
- validate: nonEmpty,
74
- })).trim();
75
- }
76
- const { encKeyAction } = writeSyncBundle({
77
- accountId, bucketName, accessKeyId, secretAccessKey,
78
- endpoint: endpoint || undefined,
79
- encKey: encKey || undefined,
80
- });
81
- const spinner = ora('Validating R2 read+write...').start();
82
- const probe = await probeR2Connectivity();
83
- if (!probe.ok) {
84
- spinner.fail(`R2 probe failed: ${probe.error}`);
85
- console.log(chalk.yellow('Credentials were saved to the r2.backups bundle but not verified. Fix them and re-run ' +
86
- '`agents sessions sync --setup`, or rotate one key with `agents secrets rotate r2.backups <KEY>`.'));
87
- return; // do not auto-enable sync against credentials that don't work
88
- }
89
- spinner.succeed('R2 read+write verified.');
90
- setBetaEnabled([SYNC_BETA], true);
91
- console.log(chalk.green('\nSession sync configured and enabled') + chalk.dim(' (the daemon syncs every ~90s).'));
92
- if (encKeyAction === 'generated') {
93
- const key = readStoredEncKey();
94
- console.log(chalk.bold('\nShared encryption key — paste this on every OTHER machine you sync:'));
95
- console.log(' ' + chalk.cyan(key ?? '(unavailable)'));
96
- console.log(chalk.dim('Anyone with this key can decrypt your transcripts. Keep it secret.'));
97
- }
98
- console.log(chalk.dim('\nSync now with: agents sessions sync'));
99
- }
100
- catch (err) {
101
- if (isPromptCancelled(err)) {
102
- console.log(chalk.yellow('\nSession-sync setup cancelled.'));
103
- return;
104
- }
105
- throw err;
106
- }
107
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * Remote drive sync for agent sessions and config.
3
- *
4
- * Provides rsync-based push/pull between the local ~/.agents/drive/
5
- * directory and a remote host, plus attach/detach to swap the active
6
- * agent's config directory symlink to the drive directory (for working
7
- * with a remote machine's session data locally).
8
- */
9
- /** Persisted drive configuration stored at ~/.agents/drive/config.json. */
10
- export interface DriveConfig {
11
- remote: string | null;
12
- attached: boolean;
13
- previousTargets: {
14
- configDir: string;
15
- homeFiles: Record<string, string>;
16
- } | null;
17
- lastPull: string | null;
18
- lastPush: string | null;
19
- }
20
- /** Read drive config from disk, returning defaults if missing. */
21
- export declare function readDriveConfig(): DriveConfig;
22
- /** Write drive config to disk. */
23
- export declare function writeDriveConfig(config: DriveConfig): void;
24
- /** Set the remote target (user@host) for rsync operations. */
25
- export declare function setRemote(target: string): void;
26
- /** Pull drive data from the remote host via rsync. */
27
- export declare function pull(): Promise<void>;
28
- /** Push local drive data to the remote host via rsync. */
29
- export declare function push(): Promise<void>;
30
- /** Attach drive by swapping the agent's config directory symlink to the drive directory. */
31
- export declare function attach(): void;
32
- /** Detach drive by restoring the agent's config directory to its previous symlink target. */
33
- export declare function detach(): void;
34
- /** Current drive state for display purposes. */
35
- export interface DriveStatus {
36
- remote: string | null;
37
- attached: boolean;
38
- lastPull: string | null;
39
- lastPush: string | null;
40
- driveDir: string;
41
- configDirTarget: string | null;
42
- homeFileTargets: Record<string, string | null>;
43
- }
44
- /** Gather current drive status by inspecting config, symlinks, and timestamps. */
45
- export declare function getDriveStatus(): DriveStatus;