@phnx-labs/agents-cli 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +143 -39
- package/README.md +6 -6
- package/dist/commands/alias.js +2 -2
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +793 -83
- package/dist/commands/cloud.js +8 -0
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/exec.js +70 -1
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +81 -39
- package/dist/commands/plugins.js +224 -17
- package/dist/commands/prune.js +29 -1
- package/dist/commands/pull.js +3 -3
- package/dist/commands/repo.js +1 -1
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.js +154 -20
- package/dist/commands/sessions.js +62 -19
- package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
- package/dist/commands/{init.js → setup.js} +22 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/view.js +78 -20
- package/dist/commands/workflows.d.ts +10 -0
- package/dist/commands/workflows.js +457 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +48 -36
- package/dist/lib/agents.js +2 -2
- package/dist/lib/auto-pull-worker.js +2 -3
- package/dist/lib/auto-pull.js +2 -2
- package/dist/lib/browser/cdp.d.ts +7 -1
- package/dist/lib/browser/cdp.js +32 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +41 -3
- package/dist/lib/browser/devices.d.ts +4 -0
- package/dist/lib/browser/devices.js +27 -0
- package/dist/lib/browser/drivers/local.js +22 -6
- package/dist/lib/browser/drivers/ssh.js +9 -2
- package/dist/lib/browser/input.d.ts +1 -0
- package/dist/lib/browser/input.js +3 -0
- package/dist/lib/browser/ipc.js +158 -23
- package/dist/lib/browser/profiles.d.ts +10 -2
- package/dist/lib/browser/profiles.js +122 -37
- package/dist/lib/browser/service.d.ts +91 -13
- package/dist/lib/browser/service.js +767 -132
- package/dist/lib/browser/types.d.ts +91 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.d.ts +28 -1
- package/dist/lib/cloud/rush.js +69 -14
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -15
- package/dist/lib/commands.js +11 -7
- package/dist/lib/daemon.js +2 -3
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.js +2 -2
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +138 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1237 -22
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +8 -66
- package/dist/lib/permissions.js +18 -18
- package/dist/lib/plugins.d.ts +94 -24
- package/dist/lib/plugins.js +702 -123
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resource-patterns.d.ts +41 -0
- package/dist/lib/resource-patterns.js +82 -0
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/resources/index.d.ts +17 -0
- package/dist/lib/resources/index.js +7 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources/workflows.d.ts +24 -0
- package/dist/lib/resources/workflows.js +110 -0
- package/dist/lib/resources.d.ts +6 -1
- package/dist/lib/resources.js +12 -2
- package/dist/lib/rotate.js +3 -4
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +92 -6
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +18 -0
- package/dist/lib/session/db.js +109 -5
- package/dist/lib/session/discover.d.ts +6 -0
- package/dist/lib/session/discover.js +55 -29
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +4 -52
- package/dist/lib/shims.js +23 -15
- package/dist/lib/skills.js +6 -2
- package/dist/lib/sqlite.js +10 -4
- package/dist/lib/state.d.ts +101 -16
- package/dist/lib/state.js +179 -31
- package/dist/lib/subagents.d.ts +28 -0
- package/dist/lib/subagents.js +98 -1
- package/dist/lib/sync-manifest.d.ts +1 -1
- package/dist/lib/sync-manifest.js +3 -3
- package/dist/lib/teams/persistence.js +15 -5
- package/dist/lib/teams/registry.js +2 -2
- package/dist/lib/types.d.ts +75 -17
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.js +2 -2
- package/dist/lib/versions.d.ts +3 -0
- package/dist/lib/versions.js +158 -47
- package/dist/lib/workflows.d.ts +79 -0
- package/dist/lib/workflows.js +233 -0
- package/package.json +1 -5
- package/scripts/postinstall.js +60 -59
- package/dist/commands/fork.d.ts +0 -10
- package/dist/commands/fork.js +0 -146
|
@@ -11,17 +11,26 @@ export interface BrowserProfile {
|
|
|
11
11
|
viewport?: {
|
|
12
12
|
width: number;
|
|
13
13
|
height: number;
|
|
14
|
+
x?: number;
|
|
15
|
+
y?: number;
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
export interface ChromeOptions {
|
|
17
19
|
headless?: boolean;
|
|
18
20
|
args?: string[];
|
|
21
|
+
viewport?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
x?: number;
|
|
25
|
+
y?: number;
|
|
26
|
+
};
|
|
19
27
|
}
|
|
20
28
|
export interface Task {
|
|
21
29
|
id: string;
|
|
30
|
+
name: string;
|
|
22
31
|
profile: string;
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
tabs: Record<string, string>;
|
|
33
|
+
currentTabId?: string;
|
|
25
34
|
createdAt: number;
|
|
26
35
|
pid: number;
|
|
27
36
|
}
|
|
@@ -36,17 +45,39 @@ export interface ProfileStatus {
|
|
|
36
45
|
running: boolean;
|
|
37
46
|
port?: number;
|
|
38
47
|
pid?: number;
|
|
48
|
+
/** The port declared in the profile's first endpoint, when it differs from the running port. */
|
|
49
|
+
configuredPort?: number;
|
|
39
50
|
tasks: TaskStatus[];
|
|
40
51
|
}
|
|
41
52
|
export interface TaskStatus {
|
|
42
53
|
id: string;
|
|
54
|
+
name: string;
|
|
43
55
|
tabCount: number;
|
|
56
|
+
currentTabId?: string;
|
|
44
57
|
createdAt: number;
|
|
58
|
+
endedAt?: number;
|
|
59
|
+
domains?: string[];
|
|
60
|
+
tabs?: Array<{
|
|
61
|
+
id: string;
|
|
62
|
+
url: string;
|
|
63
|
+
title?: string;
|
|
64
|
+
current?: boolean;
|
|
65
|
+
}>;
|
|
45
66
|
}
|
|
46
|
-
export
|
|
67
|
+
export interface HistoricalTask {
|
|
68
|
+
id: string;
|
|
69
|
+
name: string;
|
|
70
|
+
profile: string;
|
|
71
|
+
createdAt: number;
|
|
72
|
+
endedAt: number;
|
|
73
|
+
domains: string[];
|
|
74
|
+
tabCount: number;
|
|
75
|
+
}
|
|
76
|
+
export type IPCAction = 'start' | 'launch-profile' | 'done' | 'stop' | 'status' | 'history' | 'navigate' | 'tab-add' | 'tab-focus' | 'tab-close' | 'tab-list' | 'evaluate' | 'screenshot' | 'refs' | 'click' | 'type' | 'press' | 'hover' | 'scroll' | 'set-viewport' | 'set-device' | 'console' | 'errors' | 'requests' | 'response-body' | 'wait' | 'set-download-path' | 'wait-download';
|
|
47
77
|
export interface IPCRequest {
|
|
48
78
|
action: IPCAction;
|
|
49
79
|
task?: string;
|
|
80
|
+
taskName?: string;
|
|
50
81
|
profile?: string;
|
|
51
82
|
url?: string;
|
|
52
83
|
tabId?: string;
|
|
@@ -55,8 +86,26 @@ export interface IPCRequest {
|
|
|
55
86
|
ref?: number;
|
|
56
87
|
text?: string;
|
|
57
88
|
key?: string;
|
|
89
|
+
scrollX?: number;
|
|
90
|
+
scrollY?: number;
|
|
91
|
+
scrollAtX?: number;
|
|
92
|
+
scrollAtY?: number;
|
|
58
93
|
interactive?: boolean;
|
|
59
94
|
limit?: number;
|
|
95
|
+
width?: number;
|
|
96
|
+
height?: number;
|
|
97
|
+
deviceName?: string;
|
|
98
|
+
mobile?: boolean;
|
|
99
|
+
deviceScaleFactor?: number;
|
|
100
|
+
level?: 'log' | 'info' | 'warn' | 'error';
|
|
101
|
+
clear?: boolean;
|
|
102
|
+
filter?: string;
|
|
103
|
+
urlPattern?: string;
|
|
104
|
+
maxChars?: number;
|
|
105
|
+
waitType?: 'time' | 'selector' | 'url' | 'function' | 'load';
|
|
106
|
+
waitValue?: string | number;
|
|
107
|
+
timeout?: number;
|
|
108
|
+
downloadPath?: string;
|
|
60
109
|
}
|
|
61
110
|
export interface IPCResponse {
|
|
62
111
|
ok: boolean;
|
|
@@ -66,10 +115,49 @@ export interface IPCResponse {
|
|
|
66
115
|
windowTargetId?: string;
|
|
67
116
|
tabs?: TabInfo[];
|
|
68
117
|
profiles?: ProfileStatus[];
|
|
118
|
+
history?: HistoricalTask[];
|
|
69
119
|
result?: unknown;
|
|
70
120
|
path?: string;
|
|
71
121
|
refs?: string;
|
|
122
|
+
port?: number;
|
|
123
|
+
pid?: number;
|
|
124
|
+
logs?: ConsoleEntry[];
|
|
125
|
+
errors?: ErrorEntry[];
|
|
126
|
+
requests?: NetworkRequest[];
|
|
127
|
+
body?: string;
|
|
128
|
+
downloadPath?: string;
|
|
129
|
+
devices?: string[];
|
|
130
|
+
}
|
|
131
|
+
export interface ConsoleEntry {
|
|
132
|
+
level: 'log' | 'info' | 'warn' | 'error';
|
|
133
|
+
text: string;
|
|
134
|
+
timestamp: number;
|
|
135
|
+
url?: string;
|
|
136
|
+
line?: number;
|
|
137
|
+
}
|
|
138
|
+
export interface ErrorEntry {
|
|
139
|
+
message: string;
|
|
140
|
+
stack?: string;
|
|
141
|
+
timestamp: number;
|
|
142
|
+
url?: string;
|
|
143
|
+
line?: number;
|
|
144
|
+
}
|
|
145
|
+
export interface NetworkRequest {
|
|
146
|
+
id: string;
|
|
147
|
+
url: string;
|
|
148
|
+
method: string;
|
|
149
|
+
status?: number;
|
|
150
|
+
mimeType?: string;
|
|
151
|
+
timestamp: number;
|
|
152
|
+
}
|
|
153
|
+
export interface DeviceDescriptor {
|
|
154
|
+
width: number;
|
|
155
|
+
height: number;
|
|
156
|
+
deviceScaleFactor: number;
|
|
157
|
+
mobile: boolean;
|
|
72
158
|
}
|
|
73
159
|
export declare const TASK_ID_REGEX: RegExp;
|
|
74
160
|
export declare function isValidTaskId(id: string): boolean;
|
|
75
161
|
export declare function generateTaskId(): string;
|
|
162
|
+
export declare function generateShortId(): string;
|
|
163
|
+
export declare function generateFunName(): string;
|
|
@@ -5,3 +5,19 @@ export function isValidTaskId(id) {
|
|
|
5
5
|
export function generateTaskId() {
|
|
6
6
|
return crypto.randomUUID().slice(0, 8);
|
|
7
7
|
}
|
|
8
|
+
export function generateShortId() {
|
|
9
|
+
return crypto.randomUUID().split('-')[0]; // 8 chars
|
|
10
|
+
}
|
|
11
|
+
const ADJECTIVES = [
|
|
12
|
+
'swift', 'cosmic', 'jolly', 'quiet', 'bold', 'bright', 'calm', 'eager',
|
|
13
|
+
'golden', 'happy', 'keen', 'lucky', 'noble', 'proud', 'quick', 'royal',
|
|
14
|
+
];
|
|
15
|
+
const NOUNS = [
|
|
16
|
+
'falcon', 'comet', 'tiger', 'nebula', 'phoenix', 'river', 'summit', 'wave',
|
|
17
|
+
'aurora', 'breeze', 'crystal', 'dragon', 'ember', 'forest', 'glacier', 'harbor',
|
|
18
|
+
];
|
|
19
|
+
export function generateFunName() {
|
|
20
|
+
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
21
|
+
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
22
|
+
return `${adj}-${noun}`;
|
|
23
|
+
}
|
package/dist/lib/cloud/rush.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Requires the Rush GitHub App installed on the target repo.
|
|
6
6
|
*/
|
|
7
7
|
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
8
|
+
export declare const RUSH_CONSENT_PATH: string;
|
|
9
|
+
export declare function hasRushUploadConsent(opts?: DispatchOptions): boolean;
|
|
8
10
|
/** One version's entry in the account manifest sent on every dispatch. */
|
|
9
11
|
export interface AccountManifestEntry {
|
|
10
12
|
version: string;
|
|
@@ -35,7 +37,7 @@ export interface AccountTokenEntry {
|
|
|
35
37
|
* Returns null when no Claude versions are signed in (the dispatch falls back
|
|
36
38
|
* to the platform-wide key, current behavior).
|
|
37
39
|
*/
|
|
38
|
-
export declare function buildAccountManifest(): Promise<AccountManifest | null>;
|
|
40
|
+
export declare function buildAccountManifest(strategy?: string): Promise<AccountManifest | null>;
|
|
39
41
|
/**
|
|
40
42
|
* Re-load OAuth blobs for the given versions so they can be uploaded to the
|
|
41
43
|
* server on a retry. Only the versions named in the manifest are loaded — we
|
|
@@ -52,6 +54,7 @@ export declare function buildDispatchBody(input: {
|
|
|
52
54
|
agent?: string;
|
|
53
55
|
prompt: string;
|
|
54
56
|
mode?: string;
|
|
57
|
+
strategy?: string;
|
|
55
58
|
resolvedRepos: Array<{
|
|
56
59
|
installation_id: number;
|
|
57
60
|
repo_owner: string;
|
|
@@ -60,6 +63,30 @@ export declare function buildDispatchBody(input: {
|
|
|
60
63
|
accountManifest?: AccountManifest | null;
|
|
61
64
|
accountTokens?: AccountTokenEntry[] | null;
|
|
62
65
|
}): Record<string, unknown>;
|
|
66
|
+
/** A single account registered in Rush Cloud's multi-account rotation pool. */
|
|
67
|
+
export interface RemoteAccount {
|
|
68
|
+
id: string;
|
|
69
|
+
provider: string;
|
|
70
|
+
email: string | null;
|
|
71
|
+
subscription_type: string | null;
|
|
72
|
+
five_hour_pct: number | null;
|
|
73
|
+
seven_day_pct: number | null;
|
|
74
|
+
usage_fetched_at: string | null;
|
|
75
|
+
created_at: string;
|
|
76
|
+
}
|
|
77
|
+
/** Fetch all Claude accounts in this user's Rush Cloud rotation pool (no tokens). */
|
|
78
|
+
export declare function listRemoteAccounts(): Promise<RemoteAccount[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Register a CLAUDE_CODE_OAUTH_TOKEN with Rush Cloud's rotation pool.
|
|
81
|
+
* The server validates the token against the Anthropic usage API and stores it
|
|
82
|
+
* encrypted in Vault. Returns the account metadata (no token).
|
|
83
|
+
*/
|
|
84
|
+
export declare function addRemoteAccount(provider: string, pastedToken: string): Promise<RemoteAccount & {
|
|
85
|
+
five_hour_pct: number | null;
|
|
86
|
+
seven_day_pct: number | null;
|
|
87
|
+
}>;
|
|
88
|
+
/** Remove a Claude account from Rush Cloud's rotation pool by its ID. */
|
|
89
|
+
export declare function removeRemoteAccount(id: string): Promise<void>;
|
|
63
90
|
export declare class RushCloudProvider implements CloudProvider {
|
|
64
91
|
id: "rush";
|
|
65
92
|
name: string;
|
package/dist/lib/cloud/rush.js
CHANGED
|
@@ -9,20 +9,21 @@ import * as path from 'path';
|
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import * as yaml from 'yaml';
|
|
12
|
-
import {
|
|
12
|
+
import { getCloudDir } from '../state.js';
|
|
13
13
|
import { resolveDispatchRepos } from './types.js';
|
|
14
14
|
import { parseSSE } from './stream.js';
|
|
15
15
|
import { listInstalledVersions, getVersionHomePath } from '../versions.js';
|
|
16
16
|
import { getAccountInfo } from '../agents.js';
|
|
17
17
|
import { loadClaudeOauth } from '../usage.js';
|
|
18
|
+
import { selectBalancedVersion } from '../rotate.js';
|
|
18
19
|
const PROXY_BASE = 'https://api.prix.dev';
|
|
19
20
|
const USER_YAML = path.join(os.homedir(), '.rush', 'user.yaml');
|
|
20
21
|
// Persistent consent record for uploading Claude OAuth blobs to Rush Cloud.
|
|
21
22
|
// Created on first explicit consent (env var or flag); subsequent dispatches
|
|
22
23
|
// see it and proceed without re-prompting.
|
|
23
|
-
const RUSH_CONSENT_PATH = path.join(
|
|
24
|
+
export const RUSH_CONSENT_PATH = path.join(getCloudDir(), 'rush-consent.json');
|
|
24
25
|
const RUSH_CONSENT_ENV = 'AGENTS_RUSH_UPLOAD_TOKENS';
|
|
25
|
-
function hasRushUploadConsent(opts) {
|
|
26
|
+
export function hasRushUploadConsent(opts) {
|
|
26
27
|
if (process.env[RUSH_CONSENT_ENV] === '1')
|
|
27
28
|
return true;
|
|
28
29
|
const po = opts?.providerOptions;
|
|
@@ -184,20 +185,37 @@ async function readClaudeCredentialsBlob(home) {
|
|
|
184
185
|
* Returns null when no Claude versions are signed in (the dispatch falls back
|
|
185
186
|
* to the platform-wide key, current behavior).
|
|
186
187
|
*/
|
|
187
|
-
export async function buildAccountManifest() {
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
188
|
+
export async function buildAccountManifest(strategy) {
|
|
189
|
+
let candidateVersions;
|
|
190
|
+
if (strategy === 'balanced') {
|
|
191
|
+
// Use the same health-checked, deduped-by-email set that `agents run --balanced` uses.
|
|
192
|
+
// `result.healthy` contains one candidate per unique email, ordered by remaining capacity.
|
|
193
|
+
const result = await selectBalancedVersion('claude');
|
|
194
|
+
if (!result || result.healthy.length === 0)
|
|
195
|
+
return null;
|
|
196
|
+
candidateVersions = result.healthy
|
|
197
|
+
.filter((c) => !!c.email)
|
|
198
|
+
.map((c) => ({ version: c.version, email: c.email }));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Default: all installed versions that have a signed-in account.
|
|
202
|
+
const versions = listInstalledVersions('claude');
|
|
203
|
+
if (versions.length === 0)
|
|
204
|
+
return null;
|
|
205
|
+
const rows = await Promise.all(versions.map(async (version) => {
|
|
206
|
+
const home = getVersionHomePath('claude', version);
|
|
207
|
+
const info = await getAccountInfo('claude', home);
|
|
208
|
+
return info.email ? { version, email: info.email } : null;
|
|
209
|
+
}));
|
|
210
|
+
candidateVersions = rows.filter((r) => r !== null);
|
|
211
|
+
}
|
|
191
212
|
const entries = [];
|
|
192
|
-
for (const version of
|
|
213
|
+
for (const { version, email } of candidateVersions) {
|
|
193
214
|
const home = getVersionHomePath('claude', version);
|
|
194
|
-
const info = await getAccountInfo('claude', home);
|
|
195
|
-
if (!info.email)
|
|
196
|
-
continue;
|
|
197
215
|
const blob = await readClaudeCredentialsBlob(home);
|
|
198
216
|
if (!blob)
|
|
199
217
|
continue;
|
|
200
|
-
entries.push({ version, email
|
|
218
|
+
entries.push({ version, email, cred_fp: sha256(blob) });
|
|
201
219
|
}
|
|
202
220
|
if (entries.length === 0)
|
|
203
221
|
return null;
|
|
@@ -237,6 +255,7 @@ export function buildDispatchBody(input) {
|
|
|
237
255
|
prompt: input.prompt,
|
|
238
256
|
repos: input.resolvedRepos,
|
|
239
257
|
mode: input.mode,
|
|
258
|
+
...(input.strategy ? { strategy: input.strategy } : {}),
|
|
240
259
|
};
|
|
241
260
|
if (input.resolvedRepos.length === 1) {
|
|
242
261
|
body.installation_id = primary.installation_id;
|
|
@@ -251,6 +270,37 @@ export function buildDispatchBody(input) {
|
|
|
251
270
|
}
|
|
252
271
|
return body;
|
|
253
272
|
}
|
|
273
|
+
/** Fetch all Claude accounts in this user's Rush Cloud rotation pool (no tokens). */
|
|
274
|
+
export async function listRemoteAccounts() {
|
|
275
|
+
const token = readToken();
|
|
276
|
+
const res = await api('GET', '/api/v1/cloud-accounts', token);
|
|
277
|
+
if (!res.ok) {
|
|
278
|
+
throw new Error(`Failed to list accounts (${res.status}): ${sanitizeErrorBody(await res.text())}`);
|
|
279
|
+
}
|
|
280
|
+
const data = await res.json();
|
|
281
|
+
return data.accounts ?? [];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Register a CLAUDE_CODE_OAUTH_TOKEN with Rush Cloud's rotation pool.
|
|
285
|
+
* The server validates the token against the Anthropic usage API and stores it
|
|
286
|
+
* encrypted in Vault. Returns the account metadata (no token).
|
|
287
|
+
*/
|
|
288
|
+
export async function addRemoteAccount(provider, pastedToken) {
|
|
289
|
+
const token = readToken();
|
|
290
|
+
const res = await api('POST', '/api/v1/cloud-accounts', token, { provider, token: pastedToken });
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
throw new Error(`Failed to add account (${res.status}): ${sanitizeErrorBody(await res.text())}`);
|
|
293
|
+
}
|
|
294
|
+
return await res.json();
|
|
295
|
+
}
|
|
296
|
+
/** Remove a Claude account from Rush Cloud's rotation pool by its ID. */
|
|
297
|
+
export async function removeRemoteAccount(id) {
|
|
298
|
+
const token = readToken();
|
|
299
|
+
const res = await api('DELETE', `/api/v1/cloud-accounts/${encodeURIComponent(id)}`, token);
|
|
300
|
+
if (!res.ok) {
|
|
301
|
+
throw new Error(`Failed to remove account (${res.status}): ${sanitizeErrorBody(await res.text())}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
254
304
|
export class RushCloudProvider {
|
|
255
305
|
id = 'rush';
|
|
256
306
|
name = 'Rush Cloud';
|
|
@@ -289,13 +339,18 @@ export class RushCloudProvider {
|
|
|
289
339
|
repo_owner: r.owner,
|
|
290
340
|
repo_name: r.name,
|
|
291
341
|
})));
|
|
292
|
-
const
|
|
342
|
+
const strategy = options.providerOptions?.strategy;
|
|
343
|
+
// When balanced, the server owns the pool and rotates internally — no
|
|
344
|
+
// client-side manifest needed. We just forward the strategy so the server
|
|
345
|
+
// knows to load from Vault instead of waiting for a manifest.
|
|
346
|
+
const accountManifest = strategy === 'balanced' ? null : await buildAccountManifest();
|
|
293
347
|
const body = buildDispatchBody({
|
|
294
348
|
agent: options.agent,
|
|
295
349
|
prompt: options.prompt,
|
|
296
350
|
mode: options.providerOptions?.mode,
|
|
297
351
|
resolvedRepos,
|
|
298
352
|
accountManifest,
|
|
353
|
+
strategy,
|
|
299
354
|
});
|
|
300
355
|
let res = await api('POST', '/api/v1/cloud-runs', token, body);
|
|
301
356
|
// Server detects drift (new account or rotated token) by comparing the
|
|
@@ -317,7 +372,7 @@ export class RushCloudProvider {
|
|
|
317
372
|
``,
|
|
318
373
|
`To consent, re-run with one of:`,
|
|
319
374
|
` AGENTS_RUSH_UPLOAD_TOKENS=1 agents cloud run ...`,
|
|
320
|
-
` agents cloud run --upload-account-tokens
|
|
375
|
+
` agents cloud run --upload-account-tokens ...`,
|
|
321
376
|
``,
|
|
322
377
|
`Consent will be recorded at ${RUSH_CONSENT_PATH} so you won't be asked again.`,
|
|
323
378
|
`Remove that file to revoke.`,
|
package/dist/lib/cloud/store.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import Database from '../sqlite.js';
|
|
11
|
-
import {
|
|
12
|
-
const CLOUD_DIR =
|
|
11
|
+
import { getCloudDir } from '../state.js';
|
|
12
|
+
const CLOUD_DIR = getCloudDir();
|
|
13
13
|
const DB_PATH = path.join(CLOUD_DIR, 'tasks.db');
|
|
14
14
|
const SCHEMA = `
|
|
15
15
|
CREATE TABLE IF NOT EXISTS tasks (
|
package/dist/lib/commands.d.ts
CHANGED
|
@@ -35,10 +35,6 @@ export interface InstalledCommand {
|
|
|
35
35
|
path: string;
|
|
36
36
|
description?: string;
|
|
37
37
|
}
|
|
38
|
-
/** Parse command metadata (name, description) from YAML frontmatter or TOML headers. */
|
|
39
|
-
export declare function parseCommandMetadata(filePath: string): CommandMetadata | null;
|
|
40
|
-
/** Validate command metadata, returning errors and warnings. */
|
|
41
|
-
export declare function validateCommandMetadata(metadata: CommandMetadata | null, commandName: string): ValidationResult;
|
|
42
38
|
/** Discover all command markdown files in a repository's commands/ directory. */
|
|
43
39
|
export declare function discoverCommands(repoPath: string): DiscoveredCommand[];
|
|
44
40
|
/** Find the source path for a command in a repository. */
|
|
@@ -79,6 +75,7 @@ export declare function installCommandToVersion(agent: AgentId, version: string,
|
|
|
79
75
|
};
|
|
80
76
|
/**
|
|
81
77
|
* Remove a single command from a specific version home.
|
|
78
|
+
* Soft-deletes to ~/.agents/.trash/commands/.
|
|
82
79
|
*/
|
|
83
80
|
export declare function removeCommandFromVersion(agent: AgentId, version: string, commandName: string): {
|
|
84
81
|
success: boolean;
|
|
@@ -97,17 +94,6 @@ export declare function iterCommandsCapableVersions(filter?: {
|
|
|
97
94
|
}>;
|
|
98
95
|
/** Remove a command from an agent's config directory. */
|
|
99
96
|
export declare function uninstallCommand(agentId: AgentId, commandName: string): boolean;
|
|
100
|
-
/** List command names installed for an agent in the active version home. */
|
|
101
|
-
export declare function listInstalledCommands(agentId: AgentId): string[];
|
|
102
|
-
/**
|
|
103
|
-
* Check if a command exists for an agent.
|
|
104
|
-
*/
|
|
105
|
-
export declare function commandExists(agentId: AgentId, commandName: string): boolean;
|
|
106
|
-
/**
|
|
107
|
-
* Check if installed command content matches source content.
|
|
108
|
-
* Handles format conversion (markdown to TOML for Gemini).
|
|
109
|
-
*/
|
|
110
|
-
export declare function commandContentMatches(agentId: AgentId, commandName: string, sourcePath: string): boolean;
|
|
111
97
|
/**
|
|
112
98
|
* List installed commands with scope information.
|
|
113
99
|
* Pass options.home to read from a version-managed agent's home directory.
|
package/dist/lib/commands.js
CHANGED
|
@@ -11,11 +11,11 @@ import * as path from 'path';
|
|
|
11
11
|
import * as yaml from 'yaml';
|
|
12
12
|
import { AGENTS, COMMANDS_CAPABLE_AGENTS, ensureCommandsDir } from './agents.js';
|
|
13
13
|
import { markdownToToml } from './convert.js';
|
|
14
|
-
import { getCommandsDir, getUserCommandsDir, getEnabledExtraRepos, getProjectAgentsDir, getSkillsDir } from './state.js';
|
|
14
|
+
import { getCommandsDir, getUserCommandsDir, getEnabledExtraRepos, getProjectAgentsDir, getSkillsDir, getTrashCommandsDir } from './state.js';
|
|
15
15
|
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from './versions.js';
|
|
16
16
|
import { commandSkillMatches, installCommandSkillToVersion, listCommandSkillsInVersion, removeCommandSkillFromVersion, shouldInstallCommandAsSkill, } from './command-skills.js';
|
|
17
17
|
/** Parse command metadata (name, description) from YAML frontmatter or TOML headers. */
|
|
18
|
-
|
|
18
|
+
function parseCommandMetadata(filePath) {
|
|
19
19
|
if (!fs.existsSync(filePath)) {
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
@@ -51,7 +51,7 @@ export function parseCommandMetadata(filePath) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
/** Validate command metadata, returning errors and warnings. */
|
|
54
|
-
|
|
54
|
+
function validateCommandMetadata(metadata, commandName) {
|
|
55
55
|
const errors = [];
|
|
56
56
|
const warnings = [];
|
|
57
57
|
if (!metadata) {
|
|
@@ -279,6 +279,7 @@ export function installCommandToVersion(agent, version, commandName, method = 'c
|
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
281
281
|
* Remove a single command from a specific version home.
|
|
282
|
+
* Soft-deletes to ~/.agents/.trash/commands/.
|
|
282
283
|
*/
|
|
283
284
|
export function removeCommandFromVersion(agent, version, commandName) {
|
|
284
285
|
const versionHome = getVersionHomePath(agent, version);
|
|
@@ -292,7 +293,10 @@ export function removeCommandFromVersion(agent, version, commandName) {
|
|
|
292
293
|
return { success: true };
|
|
293
294
|
}
|
|
294
295
|
try {
|
|
295
|
-
|
|
296
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
297
|
+
const trashDir = path.join(getTrashCommandsDir(), agent, version, commandName);
|
|
298
|
+
fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
|
|
299
|
+
fs.renameSync(targetPath, path.join(trashDir, `${commandName}${ext}.${stamp}`));
|
|
296
300
|
}
|
|
297
301
|
catch (err) {
|
|
298
302
|
return { success: false, error: err.message };
|
|
@@ -332,7 +336,7 @@ export function uninstallCommand(agentId, commandName) {
|
|
|
332
336
|
return false;
|
|
333
337
|
}
|
|
334
338
|
/** List command names installed for an agent in the active version home. */
|
|
335
|
-
|
|
339
|
+
function listInstalledCommands(agentId) {
|
|
336
340
|
const agent = AGENTS[agentId];
|
|
337
341
|
const home = getEffectiveHome(agentId);
|
|
338
342
|
const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
|
|
@@ -348,7 +352,7 @@ export function listInstalledCommands(agentId) {
|
|
|
348
352
|
/**
|
|
349
353
|
* Check if a command exists for an agent.
|
|
350
354
|
*/
|
|
351
|
-
|
|
355
|
+
function commandExists(agentId, commandName) {
|
|
352
356
|
const agent = AGENTS[agentId];
|
|
353
357
|
const home = getEffectiveHome(agentId);
|
|
354
358
|
const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
|
|
@@ -366,7 +370,7 @@ function normalizeContent(content) {
|
|
|
366
370
|
* Check if installed command content matches source content.
|
|
367
371
|
* Handles format conversion (markdown to TOML for Gemini).
|
|
368
372
|
*/
|
|
369
|
-
|
|
373
|
+
function commandContentMatches(agentId, commandName, sourcePath) {
|
|
370
374
|
const agent = AGENTS[agentId];
|
|
371
375
|
const home = getEffectiveHome(agentId);
|
|
372
376
|
const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
|
package/dist/lib/daemon.js
CHANGED
|
@@ -10,13 +10,12 @@ import { spawn, execSync, execFileSync } from 'child_process';
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as os from 'os';
|
|
13
|
-
import {
|
|
13
|
+
import { getDaemonDir as getDaemonDirRoot } from './state.js';
|
|
14
14
|
import { listJobs as listAllJobs } from './routines.js';
|
|
15
15
|
import { JobScheduler } from './scheduler.js';
|
|
16
16
|
import { executeJobDetached, monitorRunningJobs } from './runner.js';
|
|
17
17
|
import { BrowserService } from './browser/service.js';
|
|
18
18
|
import { BrowserIPCServer } from './browser/ipc.js';
|
|
19
|
-
const DAEMON_DIR = 'helpers/daemon';
|
|
20
19
|
const PID_FILE = 'daemon.pid';
|
|
21
20
|
const LOCK_FILE = 'daemon.lock';
|
|
22
21
|
const LOG_FILE = 'logs.jsonl';
|
|
@@ -25,7 +24,7 @@ const LOG_ROTATE_COUNT = 3;
|
|
|
25
24
|
const PLIST_NAME = 'com.phnx-labs.agents-daemon';
|
|
26
25
|
const SYSTEMD_UNIT = 'agents-daemon.service';
|
|
27
26
|
function getDaemonDir() {
|
|
28
|
-
const dir =
|
|
27
|
+
const dir = getDaemonDirRoot();
|
|
29
28
|
fs.mkdirSync(dir, { recursive: true });
|
|
30
29
|
return dir;
|
|
31
30
|
}
|
package/dist/lib/doctor-diff.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
24
24
|
import { AGENTS } from './agents.js';
|
|
25
|
-
import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, getResolvedRulesDir, getUserRulesDir,
|
|
25
|
+
import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, getResolvedRulesDir, getUserRulesDir, getEffectivePromptcutsPath, } from './state.js';
|
|
26
26
|
import { getAvailableResources, getActuallySyncedResources, getVersionHomePath, } from './versions.js';
|
|
27
27
|
import { markdownToToml } from './convert.js';
|
|
28
28
|
import { resolveImports, supportsRulesImports } from './rules/compile.js';
|
|
@@ -426,10 +426,10 @@ function diffPresenceOnly(kind, available, synced) {
|
|
|
426
426
|
return rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
427
427
|
}
|
|
428
428
|
function diffPromptcuts() {
|
|
429
|
-
const
|
|
430
|
-
if (!
|
|
429
|
+
const sourcePath = getEffectivePromptcutsPath();
|
|
430
|
+
if (!fs.existsSync(sourcePath))
|
|
431
431
|
return [];
|
|
432
|
-
return [{ kind: 'promptcuts', name: 'promptcuts.yaml', status: 'ok', sourcePath
|
|
432
|
+
return [{ kind: 'promptcuts', name: 'promptcuts.yaml', status: 'ok', sourcePath }];
|
|
433
433
|
}
|
|
434
434
|
export function diffVersionResources(agent, version, options = {}) {
|
|
435
435
|
const cwd = options.cwd ?? process.cwd();
|
package/dist/lib/events.js
CHANGED
|
@@ -15,8 +15,8 @@ import * as fs from 'fs';
|
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import * as os from 'os';
|
|
17
17
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
const LOGS_DIR = path.join(
|
|
18
|
+
// Logs live under the cache bucket — they're regenerable telemetry.
|
|
19
|
+
const LOGS_DIR = path.join(os.homedir(), '.agents', '.cache', 'logs');
|
|
20
20
|
/** Default retention period in days. */
|
|
21
21
|
const DEFAULT_RETENTION_DAYS = 30;
|
|
22
22
|
/** Default max length for truncated strings. */
|
package/dist/lib/hooks.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare function installHookToVersion(agent: AgentId, version: string, ho
|
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
68
|
* Remove a single hook (script + data file) from a specific version home.
|
|
69
|
+
* Soft-deletes to ~/.agents/.trash/hooks/.
|
|
69
70
|
*/
|
|
70
71
|
export declare function removeHookFromVersion(agent: AgentId, version: string, hookName: string): {
|
|
71
72
|
success: boolean;
|
|
@@ -113,10 +114,11 @@ export declare function installHooksCentrally(source: string): Promise<{
|
|
|
113
114
|
*/
|
|
114
115
|
export declare function listCentralHooks(): HookEntry[];
|
|
115
116
|
/**
|
|
116
|
-
* Parse
|
|
117
|
-
* and user
|
|
118
|
-
*
|
|
119
|
-
*
|
|
117
|
+
* Parse hook manifests. Reads system hooks from ~/.agents-system/hooks.yaml
|
|
118
|
+
* (npm-shipped defaults) and user hooks from the `hooks:` section of
|
|
119
|
+
* ~/.agents/agents.yaml. Merges with user-wins-on-key-collision precedence.
|
|
120
|
+
* A user entry with `enabled: false` disables the system-shipped hook of
|
|
121
|
+
* the same name without forking the system file.
|
|
120
122
|
*
|
|
121
123
|
* Hooks marked `enabled: false` are dropped from the returned map.
|
|
122
124
|
*/
|
|
@@ -124,10 +126,12 @@ export declare function parseHookManifest(): Record<string, ManifestHook>;
|
|
|
124
126
|
/**
|
|
125
127
|
* Register hooks as lifecycle events in an agent's config.
|
|
126
128
|
* Reads hooks.yaml manifest, merges into the agent's config file(s).
|
|
127
|
-
* Only manages hooks whose command paths are under ~/.agents/hooks
|
|
128
|
-
* Does not remove user-added hooks.
|
|
129
|
+
* Only manages hooks whose command paths are under ~/.agents/hooks/ or
|
|
130
|
+
* ~/.agents-system/hooks/. Does not remove user-added hooks.
|
|
129
131
|
*
|
|
130
|
-
* @param agentsDirOverride -
|
|
132
|
+
* @param agentsDirOverride - When provided, treats this single dir as the
|
|
133
|
+
* only managed hook root. Used by tests to inject a temp path. In normal
|
|
134
|
+
* operation, both user and system roots are consulted with user precedence.
|
|
131
135
|
*/
|
|
132
136
|
export declare function registerHooksToSettings(agentId: AgentId, versionHome: string, hookManifest?: Record<string, ManifestHook>, agentsDirOverride?: string): {
|
|
133
137
|
registered: string[];
|