@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
|
@@ -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
|
-
}
|
package/dist/lib/drive-sync.d.ts
DELETED
|
@@ -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;
|
package/dist/lib/drive-sync.js
DELETED
|
@@ -1,238 +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
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import * as os from 'os';
|
|
12
|
-
import { execFile } from 'child_process';
|
|
13
|
-
import { promisify } from 'util';
|
|
14
|
-
import { getDriveDir } from './state.js';
|
|
15
|
-
import { AGENTS } from './agents.js';
|
|
16
|
-
import { sshExec } from './ssh-exec.js';
|
|
17
|
-
const execFileAsync = promisify(execFile);
|
|
18
|
-
// `remote` flows from disk into rsync/ssh argv. Use a strict regex so a
|
|
19
|
-
// tampered ~/.agents/drive/config.json can't sneak shell metacharacters or
|
|
20
|
-
// argv flags (leading -) past the boundary.
|
|
21
|
-
const REMOTE_RE = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+$/;
|
|
22
|
-
function assertValidRemote(remote) {
|
|
23
|
-
if (!REMOTE_RE.test(remote)) {
|
|
24
|
-
throw new Error(`Invalid drive remote: ${JSON.stringify(remote)}. Expected: user@host`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const AGENT = 'claude';
|
|
28
|
-
function configPath() {
|
|
29
|
-
return path.join(getDriveDir(), 'config.json');
|
|
30
|
-
}
|
|
31
|
-
/** Read drive config from disk, returning defaults if missing. */
|
|
32
|
-
export function readDriveConfig() {
|
|
33
|
-
const p = configPath();
|
|
34
|
-
if (fs.existsSync(p)) {
|
|
35
|
-
try {
|
|
36
|
-
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
37
|
-
// Re-validate `remote` on every read. setRemote validates on write, but
|
|
38
|
-
// the file can be tampered with out-of-band (a malicious skill, a synced
|
|
39
|
-
// config from a hostile repo, etc.) and pull/push shell out with the value.
|
|
40
|
-
if (parsed.remote != null) {
|
|
41
|
-
assertValidRemote(parsed.remote);
|
|
42
|
-
}
|
|
43
|
-
return parsed;
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
// If the config is malformed or has a tainted remote, fail closed rather
|
|
47
|
-
// than silently fall back to defaults — a tainted remote that fell back
|
|
48
|
-
// to null would mask the attack.
|
|
49
|
-
if (err instanceof SyntaxError) {
|
|
50
|
-
// Bad JSON: fall through to defaults.
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
throw err;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return { remote: null, attached: false, previousTargets: null, lastPull: null, lastPush: null };
|
|
58
|
-
}
|
|
59
|
-
/** Write drive config to disk. */
|
|
60
|
-
export function writeDriveConfig(config) {
|
|
61
|
-
const dir = getDriveDir();
|
|
62
|
-
if (!fs.existsSync(dir)) {
|
|
63
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
-
}
|
|
65
|
-
fs.writeFileSync(configPath(), JSON.stringify(config, null, 2));
|
|
66
|
-
}
|
|
67
|
-
/** Set the remote target (user@host) for rsync operations. */
|
|
68
|
-
export function setRemote(target) {
|
|
69
|
-
assertValidRemote(target);
|
|
70
|
-
const config = readDriveConfig();
|
|
71
|
-
config.remote = target;
|
|
72
|
-
writeDriveConfig(config);
|
|
73
|
-
}
|
|
74
|
-
/** Pull drive data from the remote host via rsync. */
|
|
75
|
-
export async function pull() {
|
|
76
|
-
const config = readDriveConfig();
|
|
77
|
-
if (!config.remote)
|
|
78
|
-
throw new Error('No remote configured. Run: agents drive remote <user@host>');
|
|
79
|
-
assertValidRemote(config.remote);
|
|
80
|
-
const localDir = getDriveDir() + '/';
|
|
81
|
-
const remoteSpec = `${config.remote}:~/.agents/drive/`;
|
|
82
|
-
// Argv form: no shell, no interpolation — even a tainted `config.remote` that
|
|
83
|
-
// somehow got past the regex cannot escape into command syntax.
|
|
84
|
-
await execFileAsync('rsync', ['-az', '--exclude=config.json', remoteSpec, localDir]);
|
|
85
|
-
config.lastPull = new Date().toISOString();
|
|
86
|
-
writeDriveConfig(config);
|
|
87
|
-
}
|
|
88
|
-
/** Push local drive data to the remote host via rsync. */
|
|
89
|
-
export async function push() {
|
|
90
|
-
const config = readDriveConfig();
|
|
91
|
-
if (!config.remote)
|
|
92
|
-
throw new Error('No remote configured. Run: agents drive remote <user@host>');
|
|
93
|
-
assertValidRemote(config.remote);
|
|
94
|
-
const localDir = getDriveDir() + '/';
|
|
95
|
-
const remoteSpec = `${config.remote}:~/.agents/drive/`;
|
|
96
|
-
// Ensure remote directory exists via the shared hardened SSH primitive.
|
|
97
|
-
const mkdir = sshExec(config.remote, 'mkdir -p ~/.agents/drive', { timeoutMs: 30_000 });
|
|
98
|
-
if (mkdir.code !== 0) {
|
|
99
|
-
throw new Error(`Failed to prepare remote drive directory on ${config.remote}${mkdir.stderr.trim() ? `: ${mkdir.stderr.trim()}` : ''}`);
|
|
100
|
-
}
|
|
101
|
-
await execFileAsync('rsync', ['-az', '--exclude=config.json', localDir, remoteSpec]);
|
|
102
|
-
config.lastPush = new Date().toISOString();
|
|
103
|
-
writeDriveConfig(config);
|
|
104
|
-
}
|
|
105
|
-
/** Attach drive by swapping the agent's config directory symlink to the drive directory. */
|
|
106
|
-
export function attach() {
|
|
107
|
-
const config = readDriveConfig();
|
|
108
|
-
if (config.attached)
|
|
109
|
-
throw new Error('Drive is already attached');
|
|
110
|
-
const home = os.homedir();
|
|
111
|
-
const agentConfig = AGENTS[AGENT];
|
|
112
|
-
const driveDir = getDriveDir();
|
|
113
|
-
const configDirPath = agentConfig.configDir; // ~/.claude
|
|
114
|
-
const driveConfigDir = path.join(driveDir, `.${AGENT}`); // ~/.agents/drive/.claude
|
|
115
|
-
// Read current symlink target
|
|
116
|
-
let prevConfigDir = null;
|
|
117
|
-
try {
|
|
118
|
-
const stat = fs.lstatSync(configDirPath);
|
|
119
|
-
if (stat.isSymbolicLink()) {
|
|
120
|
-
prevConfigDir = fs.readlinkSync(configDirPath);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// Path doesn't exist
|
|
125
|
-
}
|
|
126
|
-
if (!prevConfigDir) {
|
|
127
|
-
throw new Error(`${configDirPath} is not a symlink. Run 'agents use claude' first.`);
|
|
128
|
-
}
|
|
129
|
-
// Save previous targets for home files
|
|
130
|
-
const prevHomeFiles = {};
|
|
131
|
-
for (const hf of agentConfig.homeFiles || []) {
|
|
132
|
-
const hfPath = path.join(home, hf);
|
|
133
|
-
try {
|
|
134
|
-
const stat = fs.lstatSync(hfPath);
|
|
135
|
-
if (stat.isSymbolicLink()) {
|
|
136
|
-
prevHomeFiles[hf] = fs.readlinkSync(hfPath);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// File doesn't exist
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
config.previousTargets = {
|
|
144
|
-
configDir: prevConfigDir,
|
|
145
|
-
homeFiles: prevHomeFiles,
|
|
146
|
-
};
|
|
147
|
-
// Ensure drive config dir exists
|
|
148
|
-
if (!fs.existsSync(driveConfigDir)) {
|
|
149
|
-
fs.mkdirSync(driveConfigDir, { recursive: true });
|
|
150
|
-
}
|
|
151
|
-
// Swap config dir symlink
|
|
152
|
-
fs.unlinkSync(configDirPath);
|
|
153
|
-
fs.symlinkSync(driveConfigDir, configDirPath);
|
|
154
|
-
// Swap home files
|
|
155
|
-
for (const hf of agentConfig.homeFiles || []) {
|
|
156
|
-
const hfPath = path.join(home, hf);
|
|
157
|
-
const driveHfPath = path.join(driveDir, hf);
|
|
158
|
-
if (!fs.existsSync(driveHfPath)) {
|
|
159
|
-
fs.writeFileSync(driveHfPath, '{}');
|
|
160
|
-
}
|
|
161
|
-
try {
|
|
162
|
-
fs.unlinkSync(hfPath);
|
|
163
|
-
}
|
|
164
|
-
catch { /* doesn't exist */ }
|
|
165
|
-
fs.symlinkSync(driveHfPath, hfPath);
|
|
166
|
-
}
|
|
167
|
-
config.attached = true;
|
|
168
|
-
writeDriveConfig(config);
|
|
169
|
-
}
|
|
170
|
-
/** Detach drive by restoring the agent's config directory to its previous symlink target. */
|
|
171
|
-
export function detach() {
|
|
172
|
-
const config = readDriveConfig();
|
|
173
|
-
if (!config.attached)
|
|
174
|
-
throw new Error('Drive is not attached');
|
|
175
|
-
if (!config.previousTargets)
|
|
176
|
-
throw new Error('No previous targets saved');
|
|
177
|
-
const home = os.homedir();
|
|
178
|
-
const agentConfig = AGENTS[AGENT];
|
|
179
|
-
// Restore config dir symlink
|
|
180
|
-
const configDirPath = agentConfig.configDir;
|
|
181
|
-
fs.unlinkSync(configDirPath);
|
|
182
|
-
fs.symlinkSync(config.previousTargets.configDir, configDirPath);
|
|
183
|
-
// Restore home files
|
|
184
|
-
for (const hf of agentConfig.homeFiles || []) {
|
|
185
|
-
const hfPath = path.join(home, hf);
|
|
186
|
-
const prevTarget = config.previousTargets.homeFiles[hf];
|
|
187
|
-
if (prevTarget) {
|
|
188
|
-
try {
|
|
189
|
-
fs.unlinkSync(hfPath);
|
|
190
|
-
}
|
|
191
|
-
catch { /* doesn't exist */ }
|
|
192
|
-
fs.symlinkSync(prevTarget, hfPath);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
config.attached = false;
|
|
196
|
-
config.previousTargets = null;
|
|
197
|
-
writeDriveConfig(config);
|
|
198
|
-
}
|
|
199
|
-
/** Gather current drive status by inspecting config, symlinks, and timestamps. */
|
|
200
|
-
export function getDriveStatus() {
|
|
201
|
-
const config = readDriveConfig();
|
|
202
|
-
const home = os.homedir();
|
|
203
|
-
const agentConfig = AGENTS[AGENT];
|
|
204
|
-
let configDirTarget = null;
|
|
205
|
-
try {
|
|
206
|
-
const stat = fs.lstatSync(agentConfig.configDir);
|
|
207
|
-
if (stat.isSymbolicLink()) {
|
|
208
|
-
configDirTarget = fs.readlinkSync(agentConfig.configDir);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
// Not a symlink or doesn't exist
|
|
213
|
-
}
|
|
214
|
-
const homeFileTargets = {};
|
|
215
|
-
for (const hf of agentConfig.homeFiles || []) {
|
|
216
|
-
try {
|
|
217
|
-
const stat = fs.lstatSync(path.join(home, hf));
|
|
218
|
-
if (stat.isSymbolicLink()) {
|
|
219
|
-
homeFileTargets[hf] = fs.readlinkSync(path.join(home, hf));
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
homeFileTargets[hf] = null;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
homeFileTargets[hf] = null;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
remote: config.remote,
|
|
231
|
-
attached: config.attached,
|
|
232
|
-
lastPull: config.lastPull,
|
|
233
|
-
lastPush: config.lastPush,
|
|
234
|
-
driveDir: getDriveDir(),
|
|
235
|
-
configDirTarget,
|
|
236
|
-
homeFileTargets,
|
|
237
|
-
};
|
|
238
|
-
}
|
package/dist/lib/hq/floor.d.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { ActiveSession } from '../session/active.js';
|
|
2
|
-
import type { OpenBlock } from '../feed.js';
|
|
3
|
-
import type { AgentStatusDetail, TaskInfo } from '../teams/api.js';
|
|
4
|
-
export type HqRoomKind = 'team' | 'machine' | 'lobby';
|
|
5
|
-
export type HqAgentMood = 'working' | 'waiting' | 'blocked' | 'celebrating' | 'done' | 'idle';
|
|
6
|
-
export type HqAmbientKind = 'needs_input' | 'error' | 'pr' | 'team_active' | 'idle_scene';
|
|
7
|
-
export interface HqAction {
|
|
8
|
-
id: string;
|
|
9
|
-
label: string;
|
|
10
|
-
command: string[];
|
|
11
|
-
target: {
|
|
12
|
-
kind: 'agent' | 'room' | 'floor';
|
|
13
|
-
id: string;
|
|
14
|
-
};
|
|
15
|
-
destructive?: boolean;
|
|
16
|
-
input?: Array<{
|
|
17
|
-
name: string;
|
|
18
|
-
label: string;
|
|
19
|
-
placeholder?: string;
|
|
20
|
-
required?: boolean;
|
|
21
|
-
}>;
|
|
22
|
-
}
|
|
23
|
-
export interface HqRoom {
|
|
24
|
-
id: string;
|
|
25
|
-
kind: HqRoomKind;
|
|
26
|
-
name: string;
|
|
27
|
-
machine?: string;
|
|
28
|
-
team?: string;
|
|
29
|
-
counts: {
|
|
30
|
-
agents: number;
|
|
31
|
-
needsInput: number;
|
|
32
|
-
running: number;
|
|
33
|
-
failed: number;
|
|
34
|
-
};
|
|
35
|
-
actions: HqAction[];
|
|
36
|
-
}
|
|
37
|
-
export interface HqAgent {
|
|
38
|
-
id: string;
|
|
39
|
-
label: string;
|
|
40
|
-
agent: string;
|
|
41
|
-
roomId: string;
|
|
42
|
-
mood: HqAgentMood;
|
|
43
|
-
status: string;
|
|
44
|
-
activity?: string;
|
|
45
|
-
preview?: string;
|
|
46
|
-
machine?: string;
|
|
47
|
-
team?: string;
|
|
48
|
-
teammate?: string;
|
|
49
|
-
sessionId?: string;
|
|
50
|
-
mailboxId?: string;
|
|
51
|
-
pid?: number;
|
|
52
|
-
prUrl?: string;
|
|
53
|
-
ticketId?: string;
|
|
54
|
-
actions: HqAction[];
|
|
55
|
-
}
|
|
56
|
-
export interface HqAmbientEvent {
|
|
57
|
-
id: string;
|
|
58
|
-
kind: HqAmbientKind;
|
|
59
|
-
roomId: string;
|
|
60
|
-
agentId?: string;
|
|
61
|
-
label: string;
|
|
62
|
-
intensity: 'low' | 'medium' | 'high';
|
|
63
|
-
}
|
|
64
|
-
export interface HqFloorSnapshot {
|
|
65
|
-
version: 1;
|
|
66
|
-
generatedAt: string;
|
|
67
|
-
counters: {
|
|
68
|
-
rooms: number;
|
|
69
|
-
agents: number;
|
|
70
|
-
teams: number;
|
|
71
|
-
needsInput: number;
|
|
72
|
-
failed: number;
|
|
73
|
-
prs: number;
|
|
74
|
-
};
|
|
75
|
-
rooms: HqRoom[];
|
|
76
|
-
agents: HqAgent[];
|
|
77
|
-
ambientEvents: HqAmbientEvent[];
|
|
78
|
-
actions: HqAction[];
|
|
79
|
-
}
|
|
80
|
-
export interface BuildHqFloorInput {
|
|
81
|
-
generatedAt?: Date;
|
|
82
|
-
sessions: ActiveSession[];
|
|
83
|
-
teams: TaskInfo[];
|
|
84
|
-
teammatesByTeam: Map<string, AgentStatusDetail[]>;
|
|
85
|
-
blocks: OpenBlock[];
|
|
86
|
-
}
|
|
87
|
-
export declare function buildHqFloor(input: BuildHqFloorInput): HqFloorSnapshot;
|