@phnx-labs/agents-cli 1.22.41 → 1.22.43
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 +86 -0
- package/README.md +10 -4
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +25 -7
- package/dist/cli/command-registry.d.ts +2 -3
- package/dist/cli/command-registry.js +4 -8
- package/dist/commands/accounts.d.ts +29 -0
- package/dist/commands/accounts.js +220 -29
- package/dist/commands/alias.d.ts +7 -4
- package/dist/commands/alias.js +34 -9
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +108 -0
- package/dist/commands/browser-sessions-picker.js +42 -62
- package/dist/commands/browser.js +30 -38
- package/dist/commands/computer-actions.d.ts +1 -1
- package/dist/commands/computer-actions.js +1 -1
- package/dist/commands/computer-sessions-picker.js +32 -55
- package/dist/commands/computer.d.ts +1 -1
- package/dist/commands/computer.js +22 -8
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +5 -3
- package/dist/commands/doctor.js +23 -8
- package/dist/commands/exec.js +3 -2
- package/dist/commands/feed.js +2 -31
- package/dist/commands/harness.js +3 -3
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/insights.js +163 -85
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/mine.js +1 -1
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/org.d.ts +6 -0
- package/dist/commands/org.js +175 -0
- package/dist/commands/packages.js +1 -1
- package/dist/commands/profiles.js +2 -2
- package/dist/commands/prune.js +1 -1
- package/dist/commands/routines.js +44 -26
- package/dist/commands/run-account-picker.d.ts +19 -0
- package/dist/commands/run-account-picker.js +81 -0
- package/dist/commands/sessions-picker-factory.d.ts +40 -0
- package/dist/commands/sessions-picker-factory.js +67 -0
- package/dist/commands/sessions-picker.js +124 -122
- package/dist/commands/setup-browser.js +4 -4
- package/dist/commands/setup-mine.js +1 -1
- package/dist/commands/setup-preferences.js +2 -2
- package/dist/commands/setup.js +10 -4
- package/dist/commands/snapshot.d.ts +1 -1
- package/dist/commands/snapshot.js +4 -4
- package/dist/commands/status.d.ts +4 -2
- package/dist/commands/status.js +10 -7
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +30 -3
- package/dist/commands/versions.js +1 -1
- package/dist/commands/view.js +7 -1
- package/dist/commands/webhook.js +1 -1
- package/dist/lib/account-capabilities.d.ts +10 -0
- package/dist/lib/account-capabilities.js +34 -0
- package/dist/lib/accounting/rotate.d.ts +5 -3
- package/dist/lib/accounting/rotate.js +36 -7
- package/dist/lib/accounting/usage.d.ts +18 -4
- package/dist/lib/accounting/usage.js +138 -27
- package/dist/lib/agent-spec/agents.d.ts +584 -0
- package/dist/lib/agent-spec/agents.js +3127 -0
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +2 -584
- package/dist/lib/agents.js +2 -3127
- package/dist/lib/browser/drivers/local.d.ts +8 -2
- package/dist/lib/browser/drivers/local.js +8 -2
- package/dist/lib/browser/drivers/ssh.d.ts +8 -2
- package/dist/lib/browser/drivers/ssh.js +10 -4
- package/dist/lib/browser/hygiene.d.ts +1 -1
- package/dist/lib/browser/hygiene.js +5 -1
- package/dist/lib/browser/profiles.d.ts +74 -1
- package/dist/lib/browser/profiles.js +114 -5
- package/dist/lib/browser/runtime-state.d.ts +11 -5
- package/dist/lib/browser/runtime-state.js +13 -9
- package/dist/lib/browser/service.d.ts +68 -30
- package/dist/lib/browser/service.js +305 -194
- package/dist/lib/browser/types.d.ts +95 -3
- package/dist/lib/browser/types.js +51 -0
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/cloud/rush.d.ts +0 -15
- package/dist/lib/cloud/rush.js +0 -35
- package/dist/lib/computer/actions.d.ts +1 -1
- package/dist/lib/computer/actions.js +1 -1
- package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
- package/dist/lib/computer/dispatch.d.ts +1 -1
- package/dist/lib/computer/download.d.ts +1 -1
- package/dist/lib/computer/download.js +3 -3
- package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
- package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
- package/dist/lib/crabbox/runtimes.d.ts +6 -2
- package/dist/lib/crabbox/runtimes.js +21 -5
- package/dist/lib/daemon/daemon.js +87 -59
- package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +28 -11
- package/dist/lib/{runner.js → daemon/runner.js} +143 -123
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +4 -4
- package/dist/lib/devices/fleet-inventory.js +1 -1
- package/dist/lib/devices/harness-inventory.js +1 -1
- package/dist/lib/devices/health-report.js +1 -1
- package/dist/lib/devices/registry.js +4 -22
- package/dist/lib/doctor-diff.d.ts +1 -1
- package/dist/lib/doctor-diff.js +1 -1
- package/dist/lib/drift-sync.d.ts +1 -1
- package/dist/lib/drift-sync.js +2 -2
- package/dist/lib/drift.d.ts +1 -1
- package/dist/lib/drift.js +1 -1
- package/dist/lib/entitlement.d.ts +31 -0
- package/dist/lib/entitlement.js +137 -0
- package/dist/lib/exec.js +52 -205
- package/dist/lib/feed/feed.js +2 -8
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/format.js +16 -0
- package/dist/lib/fs-atomic.d.ts +7 -0
- package/dist/lib/fs-atomic.js +9 -0
- package/dist/lib/github/pr-mergeable.d.ts +49 -0
- package/dist/lib/github/pr-mergeable.js +157 -0
- package/dist/lib/github/pr-verdict.d.ts +67 -0
- package/dist/lib/github/pr-verdict.js +72 -0
- package/dist/lib/harness/adapter.d.ts +161 -0
- package/dist/lib/harness/adapter.js +47 -0
- package/dist/lib/harness/adapters/claude.d.ts +2 -0
- package/dist/lib/harness/adapters/claude.js +110 -0
- package/dist/lib/harness/adapters/codex.d.ts +2 -0
- package/dist/lib/harness/adapters/codex.js +83 -0
- package/dist/lib/harness/adapters/copilot.d.ts +2 -0
- package/dist/lib/harness/adapters/copilot.js +23 -0
- package/dist/lib/harness/adapters/cursor.d.ts +2 -0
- package/dist/lib/harness/adapters/cursor.js +38 -0
- package/dist/lib/harness/adapters/droid.d.ts +2 -0
- package/dist/lib/harness/adapters/droid.js +17 -0
- package/dist/lib/harness/adapters/grok.d.ts +2 -0
- package/dist/lib/harness/adapters/grok.js +14 -0
- package/dist/lib/harness/adapters/kimi.d.ts +2 -0
- package/dist/lib/harness/adapters/kimi.js +49 -0
- package/dist/lib/harness/adapters/muse.d.ts +2 -0
- package/dist/lib/harness/adapters/muse.js +44 -0
- package/dist/lib/harness/adapters/opencode.d.ts +2 -0
- package/dist/lib/harness/adapters/opencode.js +14 -0
- package/dist/lib/harness/exec-config-version.d.ts +8 -0
- package/dist/lib/harness/exec-config-version.js +22 -0
- package/dist/lib/harness/index.d.ts +1 -0
- package/dist/lib/harness/index.js +34 -0
- package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
- package/dist/lib/{hooks.js → hooks/install.js} +11 -11
- package/dist/lib/hosts/passthrough.d.ts +10 -0
- package/dist/lib/hosts/passthrough.js +28 -6
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/import.js +1 -1
- package/dist/lib/installations/index.d.ts +4 -4
- package/dist/lib/installations/index.js +3 -4
- package/dist/lib/installations/migrate.js +6 -6
- package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
- package/dist/lib/{shims.js → installations/shims.js} +13 -115
- package/dist/lib/installations/store.d.ts +141 -3
- package/dist/lib/installations/store.js +475 -5
- package/dist/lib/installations/versions.d.ts +40 -125
- package/dist/lib/installations/versions.js +57 -462
- package/dist/lib/isolation-boundary-report.d.ts +1 -1
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/mailbox-gc.js +2 -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.js +19 -8
- package/dist/lib/monitors/config.d.ts +2 -2
- package/dist/lib/monitors/config.js +3 -2
- package/dist/lib/monitors/dispatch.d.ts +1 -1
- package/dist/lib/monitors/dispatch.js +2 -2
- package/dist/lib/monitors/state.d.ts +1 -1
- package/dist/lib/origin-machine.d.ts +18 -0
- package/dist/lib/origin-machine.js +34 -0
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/perf/spool.js +1 -1
- package/dist/lib/plugins/plugins.js +1 -1
- package/dist/lib/plugins/skills.js +1 -1
- package/dist/lib/prix-account.d.ts +158 -0
- package/dist/lib/prix-account.js +214 -0
- package/dist/lib/profiles.d.ts +7 -0
- package/dist/lib/profiles.js +26 -1
- package/dist/lib/project-key.js +2 -2
- package/dist/lib/project-launch.js +1 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/resource-inventory.d.ts +1 -1
- package/dist/lib/resource-inventory.js +1 -1
- package/dist/lib/rules/rules.js +1 -1
- package/dist/lib/sandbox.d.ts +24 -0
- package/dist/lib/sandbox.js +101 -0
- package/dist/lib/scheduling/routines.d.ts +18 -9
- package/dist/lib/scheduling/routines.js +17 -9
- 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/agent.js +9 -1
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.js +1 -1
- package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
- package/dist/lib/self-heal/checks/path.js +1 -1
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/service-manifest.d.ts +20 -0
- package/dist/lib/service-manifest.js +34 -0
- package/dist/lib/session/cloud.js +1 -1
- package/dist/lib/session/db.js +1 -1
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +3 -3
- package/dist/lib/session/fork.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -1
- package/dist/lib/session/origin-machine.d.ts +1 -18
- package/dist/lib/session/origin-machine.js +4 -34
- package/dist/lib/session/parse.js +24 -42
- package/dist/lib/session/recovery.js +1 -1
- package/dist/lib/session/relative-time.d.ts +1 -44
- package/dist/lib/session/relative-time.js +4 -120
- package/dist/lib/session/remote-list.js +1 -1
- package/dist/lib/session/remote.js +2 -2
- package/dist/lib/session/short-id.d.ts +1 -17
- package/dist/lib/session/short-id.js +4 -20
- package/dist/lib/session/width.d.ts +1 -29
- package/dist/lib/session/width.js +4 -101
- package/dist/lib/snapshot.d.ts +2 -2
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/staleness/prune.d.ts +1 -1
- package/dist/lib/staleness/prune.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +4 -1
- package/dist/lib/startup/command-registry.js +12 -5
- package/dist/lib/teams/agents.js +2 -19
- package/dist/lib/teams/parsers.js +20 -26
- package/dist/lib/teams/placement-probe.js +1 -1
- package/dist/lib/teams/registry.js +2 -21
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/text/relative-time.d.ts +44 -0
- package/dist/lib/text/relative-time.js +120 -0
- package/dist/lib/text/short-id.d.ts +17 -0
- package/dist/lib/text/short-id.js +20 -0
- package/dist/lib/text/width.d.ts +29 -0
- package/dist/lib/text/width.js +101 -0
- package/dist/lib/triggers/handlers.d.ts +7 -0
- package/dist/lib/triggers/handlers.js +9 -1
- package/dist/lib/triggers/webhook.d.ts +1 -0
- package/dist/lib/triggers/webhook.js +3 -2
- package/dist/lib/uninstall.js +1 -1
- package/dist/lib/version.d.ts +9 -2
- package/dist/lib/version.js +11 -9
- package/dist/lib/view-types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/commands/tickets.d.ts +0 -2
- package/dist/commands/tickets.js +0 -43
- package/dist/lib/observe-aliases.d.ts +0 -30
- package/dist/lib/observe-aliases.js +0 -38
- package/dist/lib/tickets/list.d.ts +0 -53
- package/dist/lib/tickets/list.js +0 -153
- /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
|
@@ -24,7 +24,7 @@ import { getRuntimeStateDir, getHelpersDir } from '../state.js';
|
|
|
24
24
|
import { getCliVersion, resolveAgentsBin, resolveInstalledLayout } from '../version.js';
|
|
25
25
|
import { copyAppBundle, withInstallLock } from '../app-bundle-install.js';
|
|
26
26
|
import { compareVersions } from '../agent-spec/primitives.js';
|
|
27
|
-
import { namespacedServiceLabel, serviceManifestHomeEnv } from '../service-manifest.js';
|
|
27
|
+
import { namespacedServiceLabel, serviceManifestHomeEnv, serviceManagerRegistrationAllowed } from '../service-manifest.js';
|
|
28
28
|
const APP_BUNDLE_NAME = 'MenubarHelper.app';
|
|
29
29
|
const INSTALL_DIR_NAME = 'agents-cli';
|
|
30
30
|
const SERVICE_LABEL_BASE = 'com.phnx-labs.agents-menubar';
|
|
@@ -333,6 +333,11 @@ ${envXml}
|
|
|
333
333
|
* is the only sequence that reliably re-attaches the status item.
|
|
334
334
|
*/
|
|
335
335
|
export function restartMenubarLaunchAgent(uid, plist, exec = execFileSync) {
|
|
336
|
+
const reg = serviceManagerRegistrationAllowed();
|
|
337
|
+
if (!reg.allowed) {
|
|
338
|
+
process.stderr.write(`[agents] ${reg.reason}\n`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
336
341
|
const serviceTarget = `gui/${uid}/${serviceLabel()}`;
|
|
337
342
|
const opts = { stdio: ['ignore', 'ignore', 'ignore'] };
|
|
338
343
|
try {
|
|
@@ -467,15 +472,21 @@ export function disableMenubarService() {
|
|
|
467
472
|
if (!onDarwin())
|
|
468
473
|
return;
|
|
469
474
|
const plist = servicePlistPath();
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
catch {
|
|
475
|
+
const reg = serviceManagerRegistrationAllowed();
|
|
476
|
+
if (reg.allowed) {
|
|
477
|
+
const uid = process.getuid?.() ?? 0;
|
|
475
478
|
try {
|
|
476
|
-
execFileSync('launchctl', ['
|
|
479
|
+
execFileSync('launchctl', ['bootout', `gui/${uid}/${serviceLabel()}`], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
try {
|
|
483
|
+
execFileSync('launchctl', ['unload', '-w', plist], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
484
|
+
}
|
|
485
|
+
catch { /* not loaded */ }
|
|
477
486
|
}
|
|
478
|
-
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
process.stderr.write(`[agents] ${reg.reason}\n`);
|
|
479
490
|
}
|
|
480
491
|
try {
|
|
481
492
|
fs.unlinkSync(plist);
|
|
@@ -68,8 +68,8 @@ export type MonitorActionType = 'run' | 'routine' | 'notify' | 'webhook-out';
|
|
|
68
68
|
*/
|
|
69
69
|
export interface ActionConfig {
|
|
70
70
|
type: MonitorActionType;
|
|
71
|
-
/** run: which agent to spawn. */
|
|
72
|
-
agent?: AgentId;
|
|
71
|
+
/** run: which agent to spawn — a native harness id or a custom harness name (agents harness list). */
|
|
72
|
+
agent?: AgentId | (string & {});
|
|
73
73
|
/** run: the prompt; `{event}` is replaced with the fired event summary. */
|
|
74
74
|
prompt?: string;
|
|
75
75
|
/** run: execution mode (shared with routines). */
|
|
@@ -17,6 +17,7 @@ import { safeJoin, isSafeSegmentName } from '../paths.js';
|
|
|
17
17
|
import { atomicWriteFileSync } from '../fs-atomic.js';
|
|
18
18
|
import { machineId, normalizeHost } from '../machine-id.js';
|
|
19
19
|
import { ALL_AGENT_IDS } from '../agents.js';
|
|
20
|
+
import { isCustomHarnessName } from '../profiles.js';
|
|
20
21
|
/** Default values applied to every monitor config when fields are omitted. */
|
|
21
22
|
const MONITOR_DEFAULTS = {
|
|
22
23
|
enabled: true,
|
|
@@ -215,8 +216,8 @@ export function validateMonitor(config) {
|
|
|
215
216
|
errors.push(`action has conflicting fields (${populatedAct.join(', ')}); specify exactly one action`);
|
|
216
217
|
}
|
|
217
218
|
if (action.type === 'run') {
|
|
218
|
-
if (action.agent && !ALL_AGENT_IDS.includes(action.agent)) {
|
|
219
|
-
errors.push(`action.agent must be one of: ${ALL_AGENT_IDS.join(', ')}`);
|
|
219
|
+
if (action.agent && !ALL_AGENT_IDS.includes(action.agent) && !isCustomHarnessName(action.agent)) {
|
|
220
|
+
errors.push(`action.agent must be one of: ${ALL_AGENT_IDS.join(', ')}, or a custom harness (agents harness list)`);
|
|
220
221
|
}
|
|
221
222
|
if (!action.prompt || typeof action.prompt !== 'string') {
|
|
222
223
|
errors.push("action.type 'run' requires action.prompt");
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* On a fire, the monitor feeds the event to an action. Every `run`/`routine`
|
|
5
5
|
* action goes through the *same* detached spawn cron and webhook fires use
|
|
6
|
-
* (executeJobDetached, lib/runner.ts) — a monitor never duplicates spawn logic,
|
|
6
|
+
* (executeJobDetached, lib/daemon/runner.ts) — a monitor never duplicates spawn logic,
|
|
7
7
|
* it synthesizes a JobConfig and hands it to the one dispatch seam. `notify`
|
|
8
8
|
* routes the owner through the one channel seam (sendToOwner → lookupTransport,
|
|
9
9
|
* lib/notify.ts) — recipient from notify.owner, no hardcoded chat id, and an
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* On a fire, the monitor feeds the event to an action. Every `run`/`routine`
|
|
5
5
|
* action goes through the *same* detached spawn cron and webhook fires use
|
|
6
|
-
* (executeJobDetached, lib/runner.ts) — a monitor never duplicates spawn logic,
|
|
6
|
+
* (executeJobDetached, lib/daemon/runner.ts) — a monitor never duplicates spawn logic,
|
|
7
7
|
* it synthesizes a JobConfig and hands it to the one dispatch seam. `notify`
|
|
8
8
|
* routes the owner through the one channel seam (sendToOwner → lookupTransport,
|
|
9
9
|
* lib/notify.ts) — recipient from notify.owner, no hardcoded chat id, and an
|
|
10
10
|
* unresolvable channel comes back as `ok: false` instead of exiting the daemon;
|
|
11
11
|
* `webhook-out` POSTs the event.
|
|
12
12
|
*/
|
|
13
|
-
import { executeJobDetached } from '../runner.js';
|
|
13
|
+
import { executeJobDetached } from '../daemon/runner.js';
|
|
14
14
|
import { readJob } from '../scheduling/routines.js';
|
|
15
15
|
import { sendToOwner } from '../notify.js';
|
|
16
16
|
/** Replace `{event}` in a prompt with the fired event summary. */
|
|
@@ -110,7 +110,7 @@ export interface FireRecord extends MonitorEvent {
|
|
|
110
110
|
* snapshot: `executeJobDetached` writes `status: 'running'` before spawning
|
|
111
111
|
* and returns immediately — the real outcome (`completed`/`failed`/`timeout`)
|
|
112
112
|
* lands later, asynchronously, in `executeJobDetachedClaimed`'s own
|
|
113
|
-
* `settle()` on child exit/error (lib/runner.ts). So a fire recorded
|
|
113
|
+
* `settle()` on child exit/error (lib/daemon/runner.ts). So a fire recorded
|
|
114
114
|
* `runStatusAtFire: 'running'` had its `ok` frozen before the run actually
|
|
115
115
|
* finished — that is the signal a future reconciliation pass (a daemon tick
|
|
116
116
|
* that revisits `running`-at-fire records and patches `ok` for real) would
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve which machine a session transcript originated on.
|
|
3
|
+
*
|
|
4
|
+
* Cross-machine sync mirrors a remote transcript to
|
|
5
|
+
* `backups/<agent>/<machine>/<subdir>/…`. Every other transcript is a live-home
|
|
6
|
+
* file on this box, so the origin is the local machine id.
|
|
7
|
+
*
|
|
8
|
+
* Kept in a leaf module (no session/db imports) so both discovery and the
|
|
9
|
+
* sessions DB upsert path can stamp `machine` without circular deps.
|
|
10
|
+
*/
|
|
11
|
+
/** Local machine id, cached for the process lifetime. */
|
|
12
|
+
export declare function localMachineId(): string;
|
|
13
|
+
/**
|
|
14
|
+
* The machine a discovered session originated on.
|
|
15
|
+
* When the path sits under the agent's backups root, the first segment below it
|
|
16
|
+
* is the origin machine id; otherwise it's the local machine.
|
|
17
|
+
*/
|
|
18
|
+
export declare function machineForSessionFile(filePath: string, agent: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve which machine a session transcript originated on.
|
|
3
|
+
*
|
|
4
|
+
* Cross-machine sync mirrors a remote transcript to
|
|
5
|
+
* `backups/<agent>/<machine>/<subdir>/…`. Every other transcript is a live-home
|
|
6
|
+
* file on this box, so the origin is the local machine id.
|
|
7
|
+
*
|
|
8
|
+
* Kept in a leaf module (no session/db imports) so both discovery and the
|
|
9
|
+
* sessions DB upsert path can stamp `machine` without circular deps.
|
|
10
|
+
*/
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { getHistoryDir } from './state.js';
|
|
13
|
+
import { machineId } from './machine-id.js';
|
|
14
|
+
let _localMachineId;
|
|
15
|
+
/** Local machine id, cached for the process lifetime. */
|
|
16
|
+
export function localMachineId() {
|
|
17
|
+
return (_localMachineId ??= machineId());
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The machine a discovered session originated on.
|
|
21
|
+
* When the path sits under the agent's backups root, the first segment below it
|
|
22
|
+
* is the origin machine id; otherwise it's the local machine.
|
|
23
|
+
*/
|
|
24
|
+
export function machineForSessionFile(filePath, agent) {
|
|
25
|
+
if (!filePath)
|
|
26
|
+
return localMachineId();
|
|
27
|
+
const base = path.join(getHistoryDir(), 'backups', agent) + path.sep;
|
|
28
|
+
if (filePath.startsWith(base)) {
|
|
29
|
+
const seg = filePath.slice(base.length).split(path.sep)[0];
|
|
30
|
+
if (seg)
|
|
31
|
+
return seg;
|
|
32
|
+
}
|
|
33
|
+
return localMachineId();
|
|
34
|
+
}
|
package/dist/lib/perf/db.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import Database from '../sqlite.js';
|
|
10
10
|
import { getPerfDbPath, getPerfDir } from '../state.js';
|
|
11
|
-
import { localMachineId } from '../
|
|
11
|
+
import { localMachineId } from '../origin-machine.js';
|
|
12
12
|
import { resolveProjectKey } from '../project-key.js';
|
|
13
13
|
import { percentile } from '../percentile.js';
|
|
14
14
|
import { resolveSpoolPath, shortSessionId, _resetPerfSpoolForTest } from './spool.js';
|
package/dist/lib/perf/spool.js
CHANGED
|
@@ -9,7 +9,7 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { getPerfSpoolPath } from '../state.js';
|
|
12
|
-
import { localMachineId } from '../
|
|
12
|
+
import { localMachineId } from '../origin-machine.js';
|
|
13
13
|
let _spoolOverride = null;
|
|
14
14
|
let _disabled = false;
|
|
15
15
|
/** Test seam — pair with db._resetPerfDbForTest. */
|
|
@@ -16,7 +16,7 @@ import { execFileSync } from 'child_process';
|
|
|
16
16
|
import { getPluginsDir, getTrashPluginsDir, getExtraPluginsDir, getProjectPluginsDir, getSystemPluginsDir } from '../state.js';
|
|
17
17
|
import { IS_WINDOWS, isWindowsAbsolutePath, homeDir } from '../platform/index.js';
|
|
18
18
|
import { assertSafeGitTransport, resolveSnapshotSha } from '../git.js';
|
|
19
|
-
import { listInstalledVersions, getVersionHomePath } from '../installations/
|
|
19
|
+
import { listInstalledVersions, getVersionHomePath } from '../installations/store.js';
|
|
20
20
|
import { AGENTS, agentConfigDirName } from '../agents.js';
|
|
21
21
|
import { capableAgents, isCapable } from '../capabilities.js';
|
|
22
22
|
import { shouldInstallCommandAsSkill, installCommandSkillToVersion } from '../command-skills.js';
|
|
@@ -14,7 +14,7 @@ import { normalizeAliases } from '../resource-aliases.js';
|
|
|
14
14
|
import { AGENTS, ensureSkillsDir, agentConfigDirName } from '../agents.js';
|
|
15
15
|
import { capableAgents, isCapable } from '../capabilities.js';
|
|
16
16
|
import { getUserSkillsDir, getSkillsDir as getSystemSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, getTrashSkillsDir } from '../state.js';
|
|
17
|
-
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from '../installations/
|
|
17
|
+
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
|
|
18
18
|
import { listCommandSkillsInVersion } from '../command-skills.js';
|
|
19
19
|
import { emit } from '../feed/events.js';
|
|
20
20
|
const HOME = os.homedir();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the Rush account layer (`api.prix.dev`) — backs `agents auth` and
|
|
3
|
+
* `agents org`. `agents org` maps to `/api/v1/spaces`, not `/api/v1/orgs`: spaces
|
|
4
|
+
* already carry the free-tier caps (1 owned space, 3 members) and can exist
|
|
5
|
+
* standalone with no parent organization, matching a "just want a team" CLI flow
|
|
6
|
+
* better than the heavier enterprise-tenancy `orgs` routes (domain, SSO). See
|
|
7
|
+
* `.agents/artifacts/2026-08-20/agi-cli-client-integration-spec.md` §4.2 in the
|
|
8
|
+
* agi-cli-web repo for the full reasoning and the live-confirmed route/shape audit.
|
|
9
|
+
*
|
|
10
|
+
* Token custody: `agents auth login` writes its own session file
|
|
11
|
+
* (`getPrixSessionFile()`), separate from `~/.rush/user.yaml`, so `agents auth
|
|
12
|
+
* logout` never signs the user out of `rush` (the two CLIs share a backend
|
|
13
|
+
* account, not a credential store). Reads fall back to `~/.rush/user.yaml`
|
|
14
|
+
* (the pattern `readRushToken` in `lib/secrets/drivers/rush.ts` already uses)
|
|
15
|
+
* so a user who is only signed in via `rush login` still gets a working
|
|
16
|
+
* `agents auth whoami` / `agents org` with zero extra login step.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PRIX_API_BASE = "https://api.prix.dev";
|
|
19
|
+
/** The session `agents auth login` owns. */
|
|
20
|
+
export interface PrixSession {
|
|
21
|
+
access_token: string;
|
|
22
|
+
refresh_token?: string;
|
|
23
|
+
/** Unix ms. */
|
|
24
|
+
expires_at?: number;
|
|
25
|
+
email?: string;
|
|
26
|
+
userId?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function getPrixSessionFile(): string;
|
|
29
|
+
export declare function readPrixSession(): PrixSession | null;
|
|
30
|
+
export declare function writePrixSession(session: PrixSession): void;
|
|
31
|
+
export declare function clearPrixSession(): boolean;
|
|
32
|
+
/** Where the caller's Bearer token came from — surfaced by `whoami` so the user knows which login is live. */
|
|
33
|
+
export type PrixTokenSource = 'agents' | 'rush';
|
|
34
|
+
export declare function resolvePrixToken(): {
|
|
35
|
+
token: string;
|
|
36
|
+
source: PrixTokenSource;
|
|
37
|
+
} | null;
|
|
38
|
+
export declare class PrixApiError extends Error {
|
|
39
|
+
status: number;
|
|
40
|
+
constructor(status: number, message: string);
|
|
41
|
+
}
|
|
42
|
+
export interface WhoAmI {
|
|
43
|
+
userId: string;
|
|
44
|
+
email: string;
|
|
45
|
+
valid: true;
|
|
46
|
+
}
|
|
47
|
+
/** `GET /api/v1/auth/me` — live-confirmed shape: `{email, userId, valid}`. */
|
|
48
|
+
export declare function fetchWhoAmI(token?: string): Promise<WhoAmI>;
|
|
49
|
+
export interface DeviceAuthorization {
|
|
50
|
+
device_code: string;
|
|
51
|
+
user_code: string;
|
|
52
|
+
verification_uri: string;
|
|
53
|
+
verification_uri_complete: string;
|
|
54
|
+
expires_in: number;
|
|
55
|
+
interval: number;
|
|
56
|
+
}
|
|
57
|
+
/** `POST /api/v1/auth/device/authorization` — public, no token. */
|
|
58
|
+
export declare function startDeviceAuthorization(): Promise<DeviceAuthorization>;
|
|
59
|
+
export type DeviceTokenPoll = {
|
|
60
|
+
status: 'authorized';
|
|
61
|
+
access_token: string;
|
|
62
|
+
refresh_token?: string;
|
|
63
|
+
expires_in?: number;
|
|
64
|
+
user: {
|
|
65
|
+
email: string;
|
|
66
|
+
id: string;
|
|
67
|
+
};
|
|
68
|
+
} | {
|
|
69
|
+
status: 'pending';
|
|
70
|
+
} | {
|
|
71
|
+
status: 'slow_down';
|
|
72
|
+
} | {
|
|
73
|
+
status: 'expired';
|
|
74
|
+
} | {
|
|
75
|
+
status: 'denied';
|
|
76
|
+
};
|
|
77
|
+
/** `POST /api/v1/auth/device/token` — one poll attempt. Callers own the interval loop. */
|
|
78
|
+
export declare function pollDeviceToken(deviceCode: string): Promise<DeviceTokenPoll>;
|
|
79
|
+
export interface SpaceSummary {
|
|
80
|
+
id: string;
|
|
81
|
+
slug: string;
|
|
82
|
+
name: string;
|
|
83
|
+
organization_id: string | null;
|
|
84
|
+
owner_user_id: string;
|
|
85
|
+
invite_code?: string;
|
|
86
|
+
user_role: 'owner' | 'admin' | 'member';
|
|
87
|
+
created_at: string;
|
|
88
|
+
}
|
|
89
|
+
export interface SpaceMember {
|
|
90
|
+
user_id: string;
|
|
91
|
+
email: string;
|
|
92
|
+
name?: string;
|
|
93
|
+
avatar_url?: string;
|
|
94
|
+
role: 'owner' | 'admin' | 'member';
|
|
95
|
+
joined_at: string;
|
|
96
|
+
}
|
|
97
|
+
/** `GET /api/v1/spaces` — live-confirmed: array of `SpaceSummary`. */
|
|
98
|
+
export declare function listSpaces(): Promise<SpaceSummary[]>;
|
|
99
|
+
/** `POST /api/v1/spaces` — 403 if the caller already owns a space (free tier: 1). */
|
|
100
|
+
export declare function createSpace(input: {
|
|
101
|
+
name: string;
|
|
102
|
+
slug: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
}): Promise<SpaceSummary>;
|
|
105
|
+
/** `GET /api/v1/spaces/:id` — requires membership. */
|
|
106
|
+
export declare function getSpace(spaceId: string): Promise<SpaceSummary>;
|
|
107
|
+
/** `GET /api/v1/spaces/:id/members`. */
|
|
108
|
+
export declare function listSpaceMembers(spaceId: string): Promise<SpaceMember[]>;
|
|
109
|
+
export type CreateSpaceInviteResult = {
|
|
110
|
+
invited: true;
|
|
111
|
+
email: string;
|
|
112
|
+
role: string;
|
|
113
|
+
member_added: true;
|
|
114
|
+
} | {
|
|
115
|
+
invited: true;
|
|
116
|
+
email: string;
|
|
117
|
+
role: string;
|
|
118
|
+
invite_code: string;
|
|
119
|
+
member_added?: false;
|
|
120
|
+
};
|
|
121
|
+
/** `POST /api/v1/spaces/:id/invites` — sends a real email for the pending-invite path. */
|
|
122
|
+
export declare function createSpaceInvite(spaceId: string, email: string, role?: 'admin' | 'member'): Promise<CreateSpaceInviteResult>;
|
|
123
|
+
export interface SpaceInvite {
|
|
124
|
+
id: string;
|
|
125
|
+
space_id: string;
|
|
126
|
+
email: string;
|
|
127
|
+
role: 'admin' | 'member';
|
|
128
|
+
invite_code: string;
|
|
129
|
+
created_at: string;
|
|
130
|
+
}
|
|
131
|
+
/** `GET /api/v1/spaces/:id/invites`. */
|
|
132
|
+
export declare function listSpaceInvites(spaceId: string): Promise<SpaceInvite[]>;
|
|
133
|
+
/** `DELETE /api/v1/spaces/:id/invites/:inviteId`. */
|
|
134
|
+
export declare function revokeSpaceInvite(spaceId: string, inviteId: string): Promise<{
|
|
135
|
+
revoked: true;
|
|
136
|
+
}>;
|
|
137
|
+
/** `PATCH /api/v1/spaces/:id/members/:userId` — owner-only for admin changes. Route takes userId, not email. */
|
|
138
|
+
export declare function updateSpaceMemberRole(spaceId: string, userId: string, role: 'admin' | 'member'): Promise<{
|
|
139
|
+
user_id: string;
|
|
140
|
+
role: string;
|
|
141
|
+
updated: true;
|
|
142
|
+
}>;
|
|
143
|
+
/** `DELETE /api/v1/spaces/:id/members/:userId` — owner, admin, or the member themself (leave). */
|
|
144
|
+
export declare function removeSpaceMember(spaceId: string, userId: string): Promise<void>;
|
|
145
|
+
/** `DELETE /api/v1/spaces/:id` — soft delete, 30-day restore window. */
|
|
146
|
+
export declare function deleteSpace(spaceId: string): Promise<void>;
|
|
147
|
+
/** `agents-cli-space-name` -> `agi-cli-space-name`; lowercase, hyphenated, matches the backend's `^[a-z0-9-]+$` slug rule. */
|
|
148
|
+
export declare function slugify(name: string): string;
|
|
149
|
+
/**
|
|
150
|
+
* Resolve the `--space` a command should act on: an explicit id/slug match
|
|
151
|
+
* against the caller's own space list, or — with nothing passed — the
|
|
152
|
+
* caller's sole space (free tier caps ownership at one, so this is almost
|
|
153
|
+
* always unambiguous). Pure over an already-fetched list so it's cheaply
|
|
154
|
+
* unit-testable with a fixture.
|
|
155
|
+
*/
|
|
156
|
+
export declare function resolveSpaceFromList(spaces: SpaceSummary[], explicit?: string): SpaceSummary;
|
|
157
|
+
/** Resolve a member's email to their `user_id` from an already-fetched member list. */
|
|
158
|
+
export declare function resolveMemberFromList(members: SpaceMember[], email: string): SpaceMember;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the Rush account layer (`api.prix.dev`) — backs `agents auth` and
|
|
3
|
+
* `agents org`. `agents org` maps to `/api/v1/spaces`, not `/api/v1/orgs`: spaces
|
|
4
|
+
* already carry the free-tier caps (1 owned space, 3 members) and can exist
|
|
5
|
+
* standalone with no parent organization, matching a "just want a team" CLI flow
|
|
6
|
+
* better than the heavier enterprise-tenancy `orgs` routes (domain, SSO). See
|
|
7
|
+
* `.agents/artifacts/2026-08-20/agi-cli-client-integration-spec.md` §4.2 in the
|
|
8
|
+
* agi-cli-web repo for the full reasoning and the live-confirmed route/shape audit.
|
|
9
|
+
*
|
|
10
|
+
* Token custody: `agents auth login` writes its own session file
|
|
11
|
+
* (`getPrixSessionFile()`), separate from `~/.rush/user.yaml`, so `agents auth
|
|
12
|
+
* logout` never signs the user out of `rush` (the two CLIs share a backend
|
|
13
|
+
* account, not a credential store). Reads fall back to `~/.rush/user.yaml`
|
|
14
|
+
* (the pattern `readRushToken` in `lib/secrets/drivers/rush.ts` already uses)
|
|
15
|
+
* so a user who is only signed in via `rush login` still gets a working
|
|
16
|
+
* `agents auth whoami` / `agents org` with zero extra login step.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import * as os from 'os';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import * as yaml from 'yaml';
|
|
22
|
+
import { getRuntimeStateDir } from './state.js';
|
|
23
|
+
export const PRIX_API_BASE = 'https://api.prix.dev';
|
|
24
|
+
const RUSH_USER_YAML = path.join(os.homedir(), '.rush', 'user.yaml');
|
|
25
|
+
/** Computed per-call (not cached at module load) so `AGENTS_STATE_DIR` overrides in tests take effect. */
|
|
26
|
+
function prixSessionFile() {
|
|
27
|
+
return path.join(getRuntimeStateDir(), 'prix-account.json');
|
|
28
|
+
}
|
|
29
|
+
/** Read the token `rush login` wrote, with no expiry check — the same shape `readRushToken` reads. */
|
|
30
|
+
function readRushSessionToken() {
|
|
31
|
+
if (!fs.existsSync(RUSH_USER_YAML))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const data = yaml.parse(fs.readFileSync(RUSH_USER_YAML, 'utf-8'));
|
|
35
|
+
return data?.session?.access_token ?? null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function getPrixSessionFile() {
|
|
42
|
+
return prixSessionFile();
|
|
43
|
+
}
|
|
44
|
+
export function readPrixSession() {
|
|
45
|
+
const file = prixSessionFile();
|
|
46
|
+
if (!fs.existsSync(file))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
50
|
+
if (!parsed || typeof parsed.access_token !== 'string')
|
|
51
|
+
return null;
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function writePrixSession(session) {
|
|
59
|
+
const file = prixSessionFile();
|
|
60
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
61
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
62
|
+
fs.writeFileSync(tmp, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
63
|
+
fs.renameSync(tmp, file);
|
|
64
|
+
}
|
|
65
|
+
export function clearPrixSession() {
|
|
66
|
+
const file = prixSessionFile();
|
|
67
|
+
if (!fs.existsSync(file))
|
|
68
|
+
return false;
|
|
69
|
+
fs.rmSync(file);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
export function resolvePrixToken() {
|
|
73
|
+
const own = readPrixSession();
|
|
74
|
+
if (own?.access_token)
|
|
75
|
+
return { token: own.access_token, source: 'agents' };
|
|
76
|
+
const rushToken = readRushSessionToken();
|
|
77
|
+
if (rushToken)
|
|
78
|
+
return { token: rushToken, source: 'rush' };
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
export class PrixApiError extends Error {
|
|
82
|
+
status;
|
|
83
|
+
constructor(status, message) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.status = status;
|
|
86
|
+
this.name = 'PrixApiError';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function prixRequest(method, endpoint, opts = {}) {
|
|
90
|
+
const auth = opts.auth !== false;
|
|
91
|
+
let token = opts.token;
|
|
92
|
+
if (auth && !token) {
|
|
93
|
+
const resolved = resolvePrixToken();
|
|
94
|
+
if (!resolved)
|
|
95
|
+
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
96
|
+
token = resolved.token;
|
|
97
|
+
}
|
|
98
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
99
|
+
if (token)
|
|
100
|
+
headers.Authorization = `Bearer ${token}`;
|
|
101
|
+
const res = await fetch(`${PRIX_API_BASE}${endpoint}`, {
|
|
102
|
+
method,
|
|
103
|
+
headers,
|
|
104
|
+
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
|
105
|
+
});
|
|
106
|
+
const text = await res.text();
|
|
107
|
+
const data = text ? JSON.parse(text) : undefined;
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
const message = (data && typeof data === 'object' && 'error' in data) ? String(data.error) : `${res.status} ${res.statusText}`;
|
|
110
|
+
throw new PrixApiError(res.status, message);
|
|
111
|
+
}
|
|
112
|
+
return data;
|
|
113
|
+
}
|
|
114
|
+
/** `GET /api/v1/auth/me` — live-confirmed shape: `{email, userId, valid}`. */
|
|
115
|
+
export async function fetchWhoAmI(token) {
|
|
116
|
+
return prixRequest('GET', '/api/v1/auth/me', { token });
|
|
117
|
+
}
|
|
118
|
+
/** `POST /api/v1/auth/device/authorization` — public, no token. */
|
|
119
|
+
export async function startDeviceAuthorization() {
|
|
120
|
+
return prixRequest('POST', '/api/v1/auth/device/authorization', { auth: false, body: {} });
|
|
121
|
+
}
|
|
122
|
+
/** `POST /api/v1/auth/device/token` — one poll attempt. Callers own the interval loop. */
|
|
123
|
+
export async function pollDeviceToken(deviceCode) {
|
|
124
|
+
try {
|
|
125
|
+
const data = await prixRequest('POST', '/api/v1/auth/device/token', { auth: false, body: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: deviceCode } });
|
|
126
|
+
return { status: 'authorized', ...data };
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err instanceof PrixApiError) {
|
|
130
|
+
if (err.message.includes('authorization_pending'))
|
|
131
|
+
return { status: 'pending' };
|
|
132
|
+
if (err.message.includes('slow_down'))
|
|
133
|
+
return { status: 'slow_down' };
|
|
134
|
+
if (err.message.includes('expired_token'))
|
|
135
|
+
return { status: 'expired' };
|
|
136
|
+
if (err.message.includes('access_denied'))
|
|
137
|
+
return { status: 'denied' };
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** `GET /api/v1/spaces` — live-confirmed: array of `SpaceSummary`. */
|
|
143
|
+
export async function listSpaces() {
|
|
144
|
+
return prixRequest('GET', '/api/v1/spaces');
|
|
145
|
+
}
|
|
146
|
+
/** `POST /api/v1/spaces` — 403 if the caller already owns a space (free tier: 1). */
|
|
147
|
+
export async function createSpace(input) {
|
|
148
|
+
return prixRequest('POST', '/api/v1/spaces', { body: input });
|
|
149
|
+
}
|
|
150
|
+
/** `GET /api/v1/spaces/:id` — requires membership. */
|
|
151
|
+
export async function getSpace(spaceId) {
|
|
152
|
+
return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}`);
|
|
153
|
+
}
|
|
154
|
+
/** `GET /api/v1/spaces/:id/members`. */
|
|
155
|
+
export async function listSpaceMembers(spaceId) {
|
|
156
|
+
return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members`);
|
|
157
|
+
}
|
|
158
|
+
/** `POST /api/v1/spaces/:id/invites` — sends a real email for the pending-invite path. */
|
|
159
|
+
export async function createSpaceInvite(spaceId, email, role = 'member') {
|
|
160
|
+
return prixRequest('POST', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites`, { body: { email, role } });
|
|
161
|
+
}
|
|
162
|
+
/** `GET /api/v1/spaces/:id/invites`. */
|
|
163
|
+
export async function listSpaceInvites(spaceId) {
|
|
164
|
+
return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites`);
|
|
165
|
+
}
|
|
166
|
+
/** `DELETE /api/v1/spaces/:id/invites/:inviteId`. */
|
|
167
|
+
export async function revokeSpaceInvite(spaceId, inviteId) {
|
|
168
|
+
return prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites/${encodeURIComponent(inviteId)}`);
|
|
169
|
+
}
|
|
170
|
+
/** `PATCH /api/v1/spaces/:id/members/:userId` — owner-only for admin changes. Route takes userId, not email. */
|
|
171
|
+
export async function updateSpaceMemberRole(spaceId, userId, role) {
|
|
172
|
+
return prixRequest('PATCH', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(userId)}`, { body: { role } });
|
|
173
|
+
}
|
|
174
|
+
/** `DELETE /api/v1/spaces/:id/members/:userId` — owner, admin, or the member themself (leave). */
|
|
175
|
+
export async function removeSpaceMember(spaceId, userId) {
|
|
176
|
+
await prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(userId)}`);
|
|
177
|
+
}
|
|
178
|
+
/** `DELETE /api/v1/spaces/:id` — soft delete, 30-day restore window. */
|
|
179
|
+
export async function deleteSpace(spaceId) {
|
|
180
|
+
await prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}`);
|
|
181
|
+
}
|
|
182
|
+
/** `agents-cli-space-name` -> `agi-cli-space-name`; lowercase, hyphenated, matches the backend's `^[a-z0-9-]+$` slug rule. */
|
|
183
|
+
export function slugify(name) {
|
|
184
|
+
const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
185
|
+
return slug || 'space';
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the `--space` a command should act on: an explicit id/slug match
|
|
189
|
+
* against the caller's own space list, or — with nothing passed — the
|
|
190
|
+
* caller's sole space (free tier caps ownership at one, so this is almost
|
|
191
|
+
* always unambiguous). Pure over an already-fetched list so it's cheaply
|
|
192
|
+
* unit-testable with a fixture.
|
|
193
|
+
*/
|
|
194
|
+
export function resolveSpaceFromList(spaces, explicit) {
|
|
195
|
+
if (explicit) {
|
|
196
|
+
const match = spaces.find(s => s.id === explicit || s.slug === explicit);
|
|
197
|
+
if (!match)
|
|
198
|
+
throw new Error(`No space matching '${explicit}'. Run 'agents org list' to see your spaces.`);
|
|
199
|
+
return match;
|
|
200
|
+
}
|
|
201
|
+
if (spaces.length === 0)
|
|
202
|
+
throw new Error("You have no spaces. Create one with 'agents org create <name>'.");
|
|
203
|
+
if (spaces.length > 1) {
|
|
204
|
+
throw new Error(`You belong to ${spaces.length} spaces — pass --space <id-or-slug>: ${spaces.map(s => s.slug).join(', ')}`);
|
|
205
|
+
}
|
|
206
|
+
return spaces[0];
|
|
207
|
+
}
|
|
208
|
+
/** Resolve a member's email to their `user_id` from an already-fetched member list. */
|
|
209
|
+
export function resolveMemberFromList(members, email) {
|
|
210
|
+
const match = members.find(m => m.email.toLowerCase() === email.toLowerCase());
|
|
211
|
+
if (!match)
|
|
212
|
+
throw new Error(`'${email}' is not a member of this space. Run 'agents org members' to see who is.`);
|
|
213
|
+
return match;
|
|
214
|
+
}
|
package/dist/lib/profiles.d.ts
CHANGED
|
@@ -96,6 +96,13 @@ export declare function getProfilePath(name: string): string;
|
|
|
96
96
|
export declare function validateProfileName(name: string): void;
|
|
97
97
|
/** Check whether a profile YAML file exists on disk. */
|
|
98
98
|
export declare function profileExists(name: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* True when `name` is a custom harness (a profile that is not shadowing a
|
|
101
|
+
* native agent id) — the predicate routines/monitors use to accept an
|
|
102
|
+
* `agent:` value that `agents run` will resolve as a profile. A native id
|
|
103
|
+
* always reads as native, matching exec's resolution order.
|
|
104
|
+
*/
|
|
105
|
+
export declare function isCustomHarnessName(name: string): boolean;
|
|
99
106
|
/** Read and parse a profile from disk. Throws if not found or malformed. */
|
|
100
107
|
export declare function readProfile(name: string): Profile;
|
|
101
108
|
/** Write a profile to disk atomically (write-to-tmp then rename). */
|
package/dist/lib/profiles.js
CHANGED
|
@@ -9,6 +9,7 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import * as crypto from 'node:crypto';
|
|
11
11
|
import * as yaml from 'yaml';
|
|
12
|
+
import { ALL_AGENT_IDS } from './agents.js';
|
|
12
13
|
import { getUserAgentsDir } from './state.js';
|
|
13
14
|
import { deleteKeychainToken, getKeychainToken, hasKeychainToken, keychainItemName } from './secrets/profiles.js';
|
|
14
15
|
import { getPreset } from './profiles-presets.js';
|
|
@@ -39,6 +40,17 @@ export function validateProfileName(name) {
|
|
|
39
40
|
export function profileExists(name) {
|
|
40
41
|
return fs.existsSync(profilePath(name));
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* True when `name` is a custom harness (a profile that is not shadowing a
|
|
45
|
+
* native agent id) — the predicate routines/monitors use to accept an
|
|
46
|
+
* `agent:` value that `agents run` will resolve as a profile. A native id
|
|
47
|
+
* always reads as native, matching exec's resolution order.
|
|
48
|
+
*/
|
|
49
|
+
export function isCustomHarnessName(name) {
|
|
50
|
+
if (!PROFILE_NAME_PATTERN.test(name))
|
|
51
|
+
return false;
|
|
52
|
+
return !ALL_AGENT_IDS.includes(name) && profileExists(name);
|
|
53
|
+
}
|
|
42
54
|
/** Read and parse a profile from disk. Throws if not found or malformed. */
|
|
43
55
|
export function readProfile(name) {
|
|
44
56
|
validateProfileName(name);
|
|
@@ -77,7 +89,11 @@ function migrateLegacyProfileAuth(profile, file) {
|
|
|
77
89
|
}
|
|
78
90
|
const migratedAccount = findAccount(accountName);
|
|
79
91
|
const oldItem = profile.auth.keychainItem;
|
|
80
|
-
|
|
92
|
+
// Reference by NAME, not id: profiles sync fleet-wide with `agents repo push`
|
|
93
|
+
// while account ids are minted per-device, so an id ref breaks on every other
|
|
94
|
+
// machine ("Unknown account '<uuid>'"). Names are the portable handle — the
|
|
95
|
+
// registry resolves both, and `accounts rename` already rewrites name refs.
|
|
96
|
+
profile.account = migratedAccount.name;
|
|
81
97
|
profile.provider = provider;
|
|
82
98
|
delete profile.auth;
|
|
83
99
|
delete profile.authOptional;
|
|
@@ -477,6 +493,15 @@ export function renameProfile(oldName, newName) {
|
|
|
477
493
|
export function resolveProfileEnv(profile) {
|
|
478
494
|
const env = { ...profile.env };
|
|
479
495
|
if (profile.account) {
|
|
496
|
+
if (!findAccount(profile.account)) {
|
|
497
|
+
// The commonest cause: the profile synced here via `agents repo push/pull`
|
|
498
|
+
// but its account ref was minted on another device (legacy id refs), so
|
|
499
|
+
// the bare "Unknown account" from the registry gives the user nothing to
|
|
500
|
+
// act on. Name the harness and the repair.
|
|
501
|
+
throw new Error(`Harness '${profile.name}' references account '${profile.account}', which does not exist on this device. ` +
|
|
502
|
+
`Accounts are per-machine; pick one from 'agents accounts list' and repoint with ` +
|
|
503
|
+
`'agents harness edit ${profile.name} --account <name>', or add it with 'agents accounts add'.`);
|
|
504
|
+
}
|
|
480
505
|
const account = resolveCredentialAccount(profile.account, profile.host.agent, profile.provider);
|
|
481
506
|
Object.assign(env, account.env);
|
|
482
507
|
}
|