@phnx-labs/agents-cli 1.22.6 → 1.22.8
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 +131 -1
- package/README.md +7 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +61 -0
- package/dist/commands/exec.js +83 -16
- package/dist/commands/feed.d.ts +2 -1
- package/dist/commands/feed.js +47 -20
- package/dist/commands/focus.js +22 -1
- package/dist/commands/harness.d.ts +0 -1
- package/dist/commands/harness.js +60 -4
- package/dist/commands/models.js +2 -2
- package/dist/commands/monitors.js +2 -2
- package/dist/commands/projects.js +122 -107
- package/dist/commands/routines.js +2 -2
- package/dist/commands/run-account-picker.js +2 -0
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/sessions-backfill.d.ts +33 -0
- package/dist/commands/sessions-backfill.js +83 -1
- package/dist/commands/sessions-stats.d.ts +36 -0
- package/dist/commands/sessions-stats.js +263 -0
- package/dist/commands/sessions.d.ts +1 -1
- package/dist/commands/sessions.js +21 -1
- package/dist/commands/snapshot.d.ts +11 -0
- package/dist/commands/snapshot.js +107 -0
- package/dist/commands/teams.js +2 -1
- package/dist/commands/view.d.ts +9 -1
- package/dist/commands/view.js +63 -12
- package/dist/index.js +4 -2
- package/dist/lib/activity.js +2 -2
- package/dist/lib/agents.js +68 -11
- package/dist/lib/analytics/recipes.js +11 -5
- package/dist/lib/browser/ipc.js +2 -0
- package/dist/lib/browser/profiles.d.ts +15 -7
- package/dist/lib/browser/profiles.js +53 -12
- package/dist/lib/browser/remote-control.d.ts +35 -0
- package/dist/lib/browser/remote-control.js +48 -0
- package/dist/lib/browser/service.d.ts +19 -0
- package/dist/lib/browser/service.js +19 -1
- package/dist/lib/browser/types.d.ts +14 -2
- package/dist/lib/byok-usage.d.ts +38 -0
- package/dist/lib/byok-usage.js +117 -0
- package/dist/lib/capabilities.js +1 -1
- package/dist/lib/device-config.js +8 -0
- package/dist/lib/exec.d.ts +20 -0
- package/dist/lib/exec.js +73 -8
- package/dist/lib/feed-outcome.d.ts +1 -0
- package/dist/lib/feed-outcome.js +2 -0
- package/dist/lib/feed-post.js +1 -1
- package/dist/lib/feed-ranking.d.ts +1 -0
- package/dist/lib/feed-ranking.js +4 -0
- package/dist/lib/feed.d.ts +4 -1
- package/dist/lib/feed.js +25 -0
- package/dist/lib/hosts/passthrough.d.ts +10 -1
- package/dist/lib/hosts/passthrough.js +23 -3
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/mcp.js +6 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/model-tiers.js +4 -1
- package/dist/lib/models.js +63 -0
- package/dist/lib/placement.d.ts +82 -0
- package/dist/lib/placement.js +188 -0
- package/dist/lib/profiles.d.ts +73 -10
- package/dist/lib/profiles.js +133 -15
- package/dist/lib/project-focus.d.ts +9 -0
- package/dist/lib/project-focus.js +23 -0
- package/dist/lib/project-key.d.ts +1 -1
- package/dist/lib/project-key.js +1 -1
- package/dist/lib/project-probe.d.ts +18 -0
- package/dist/lib/project-probe.js +46 -0
- package/dist/lib/project-status.d.ts +56 -0
- package/dist/lib/project-status.js +125 -18
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/rotate.d.ts +19 -4
- package/dist/lib/rotate.js +24 -1
- package/dist/lib/routines.d.ts +2 -0
- package/dist/lib/runner.d.ts +3 -0
- package/dist/lib/runner.js +90 -7
- 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/audit.js +1 -1
- package/dist/lib/secrets/index.d.ts +1 -0
- package/dist/lib/secrets/index.js +29 -15
- package/dist/lib/secrets/remote.d.ts +1 -1
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/session/active.d.ts +2 -0
- package/dist/lib/session/bash-command.d.ts +2 -3
- package/dist/lib/session/db.d.ts +92 -1
- package/dist/lib/session/db.js +230 -1
- package/dist/lib/session/digest.d.ts +1 -1
- package/dist/lib/session/digest.js +1 -1
- package/dist/lib/share/publish.js +24 -0
- package/dist/lib/snapshot.d.ts +103 -0
- package/dist/lib/snapshot.js +99 -0
- package/dist/lib/startup/command-registry.d.ts +1 -1
- package/dist/lib/startup/command-registry.js +2 -2
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/types.d.ts +11 -2
- package/dist/lib/usage.d.ts +5 -0
- package/dist/lib/usage.js +3 -3
- package/package.json +1 -1
- package/dist/commands/activity.d.ts +0 -87
- package/dist/commands/activity.js +0 -346
package/dist/lib/model-tiers.js
CHANGED
|
@@ -124,7 +124,10 @@ function newer(a, b) {
|
|
|
124
124
|
*/
|
|
125
125
|
function rankCatalog(agent, models) {
|
|
126
126
|
const usable = models.filter((m) => !PSEUDO.test(m.id));
|
|
127
|
-
|
|
127
|
+
// Cursor and Pi (Oh My Pi) are cross-provider aggregators: their ids are
|
|
128
|
+
// provider-qualified (`anthropic/…`, `openai/…`) and span vendors, so price of
|
|
129
|
+
// the normalized base id is the only unifying rank signal.
|
|
130
|
+
const aggregator = agent === 'cursor' || agent === 'pi';
|
|
128
131
|
const scored = usable.map((m) => {
|
|
129
132
|
const rawId = m.id;
|
|
130
133
|
const baseId = aggregator ? normalizeAggregatorId(rawId) : rawId;
|
package/dist/lib/models.js
CHANGED
|
@@ -234,6 +234,18 @@ export function locateModelSource(agent, version) {
|
|
|
234
234
|
return { path: pathBin, kind: 'cli' };
|
|
235
235
|
return null;
|
|
236
236
|
}
|
|
237
|
+
if (agent === 'pi') {
|
|
238
|
+
// omp (Oh My Pi) installs via `bun install -g`; a version-managed install
|
|
239
|
+
// exposes it under node_modules/.bin/omp, otherwise it lives on PATH. We let
|
|
240
|
+
// the CLI produce its own catalog via `omp models --json` (extractPiCatalog).
|
|
241
|
+
const cli = path.join(versionDir, 'node_modules', '.bin', 'omp');
|
|
242
|
+
if (fs.existsSync(cli))
|
|
243
|
+
return { path: cli, kind: 'cli' };
|
|
244
|
+
const pathBin = findOnPath('omp');
|
|
245
|
+
if (pathBin)
|
|
246
|
+
return { path: pathBin, kind: 'cli' };
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
237
249
|
return null;
|
|
238
250
|
}
|
|
239
251
|
/** Real Grok binaries are ~100MB+; failed-download stubs are tens of bytes. */
|
|
@@ -907,6 +919,55 @@ function extractKimiCatalog(binaryPath) {
|
|
|
907
919
|
}
|
|
908
920
|
return { models, aliases: {} };
|
|
909
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Extract Oh My Pi's catalog via `omp models --json`. omp is a cross-provider
|
|
924
|
+
* aggregator: its catalog is the union of every provider it has a key for, so
|
|
925
|
+
* ids are provider-qualified selectors (`anthropic/claude-opus-4-8`,
|
|
926
|
+
* `openai/gpt-5.2`, `xai/grok-4`, `deepseek/deepseek-chat`, …) — exactly the
|
|
927
|
+
* `provider/model` convention `ModelInfo.id` already uses. Output shape:
|
|
928
|
+
* {"models":[{"provider":"anthropic","id":"claude-opus-4-8",
|
|
929
|
+
* "selector":"anthropic/claude-opus-4-8","name":"Claude Opus 4.8",
|
|
930
|
+
* "cost":{...}}, ...]}
|
|
931
|
+
* The catalog is gated per-provider by key presence (no key -> that provider's
|
|
932
|
+
* models are absent, and an empty env yields `{"models":[]}`). That is truthful:
|
|
933
|
+
* the extractor surfaces exactly the providers the user has authenticated.
|
|
934
|
+
* Pricing is attached uniformly by getModelCatalog via getModelPricing(id),
|
|
935
|
+
* which strips the `provider/` prefix — so no per-catalog price shape here.
|
|
936
|
+
*/
|
|
937
|
+
function extractPiCatalog(binaryPath) {
|
|
938
|
+
let stdout;
|
|
939
|
+
try {
|
|
940
|
+
stdout = execFileSync(binaryPath, ['models', '--json'], {
|
|
941
|
+
encoding: 'utf-8',
|
|
942
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
943
|
+
timeout: 15_000,
|
|
944
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
catch {
|
|
948
|
+
return { models: [], aliases: {} };
|
|
949
|
+
}
|
|
950
|
+
let parsed;
|
|
951
|
+
try {
|
|
952
|
+
parsed = JSON.parse(stdout.replace(/\x1b\[[0-9;]*[A-Za-z]/g, ''));
|
|
953
|
+
}
|
|
954
|
+
catch {
|
|
955
|
+
return { models: [], aliases: {} };
|
|
956
|
+
}
|
|
957
|
+
if (!parsed || !Array.isArray(parsed.models))
|
|
958
|
+
return { models: [], aliases: {} };
|
|
959
|
+
const models = [];
|
|
960
|
+
const seen = new Set();
|
|
961
|
+
for (const m of parsed.models) {
|
|
962
|
+
// Prefer the provider-qualified selector; fall back to provider/id.
|
|
963
|
+
const id = m.selector || (m.provider && m.id ? `${m.provider}/${m.id}` : m.id);
|
|
964
|
+
if (!id || seen.has(id))
|
|
965
|
+
continue;
|
|
966
|
+
seen.add(id);
|
|
967
|
+
models.push({ id, displayName: typeof m.name === 'string' ? m.name : undefined });
|
|
968
|
+
}
|
|
969
|
+
return { models, aliases: {} };
|
|
970
|
+
}
|
|
910
971
|
/**
|
|
911
972
|
* Build (or load from cache) the model catalog for a specific (agent, version).
|
|
912
973
|
* Cache is keyed on source-file mtime (binary or js module), so re-extracts
|
|
@@ -964,6 +1025,8 @@ export function getModelCatalog(agent, version) {
|
|
|
964
1025
|
({ models, aliases } = extractKimiCatalog(src.path));
|
|
965
1026
|
else if (agent === 'grok')
|
|
966
1027
|
({ models, aliases } = extractGrokCatalog(src.path));
|
|
1028
|
+
else if (agent === 'pi')
|
|
1029
|
+
({ models, aliases } = extractPiCatalog(src.path));
|
|
967
1030
|
}
|
|
968
1031
|
// Attach per-token pricing where the offline table knows the model, so the
|
|
969
1032
|
// catalog carries $/token for the tier display and budgeting. Subscription /
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placement — one model for "where does the body run?"
|
|
3
|
+
*
|
|
4
|
+
* The CLI grew several doors that all mean execution target:
|
|
5
|
+
* run --host / --device / --lease / --box
|
|
6
|
+
* routines --placement / --run-on / hostStrategy
|
|
7
|
+
* monitors --run-on (body) vs --device (owner — NOT placement)
|
|
8
|
+
* teams --device (teammate pin)
|
|
9
|
+
* cloud run --provider host
|
|
10
|
+
*
|
|
11
|
+
* This module is the shared vocabulary. Old flags remain; --where is a
|
|
12
|
+
* thin alias on `agents run` that expands into them. Docs and help teach
|
|
13
|
+
* the matrix; stores stay separate (device registry, lease boxes, cloud).
|
|
14
|
+
*
|
|
15
|
+
* Owner (who may fire / evaluate) is NOT placement — see monitors.
|
|
16
|
+
*/
|
|
17
|
+
/** Where a job body executes. */
|
|
18
|
+
export type PlacementKind = 'local' | 'device' | 'fleet' | 'cloud' | 'lease';
|
|
19
|
+
/**
|
|
20
|
+
* Canonical placement object.
|
|
21
|
+
*
|
|
22
|
+
* kind: local — this machine
|
|
23
|
+
* kind: device — named box or affinity pick (target: name | "auto")
|
|
24
|
+
* kind: fleet — pick one online device at fire time (routines)
|
|
25
|
+
* kind: cloud — vendor cloud dispatch
|
|
26
|
+
* kind: lease — disposable crabbox (target: optional backend)
|
|
27
|
+
*/
|
|
28
|
+
export interface Placement {
|
|
29
|
+
kind: PlacementKind;
|
|
30
|
+
/** Device/host name, "auto", lease backend, or undefined. */
|
|
31
|
+
target?: string;
|
|
32
|
+
/** Flag or path that produced this (errors / diagnostics). */
|
|
33
|
+
source: string;
|
|
34
|
+
}
|
|
35
|
+
/** Run-flag bag the placement parser understands. */
|
|
36
|
+
export interface RunPlacementFlags {
|
|
37
|
+
where?: string;
|
|
38
|
+
host?: string;
|
|
39
|
+
device?: string;
|
|
40
|
+
on?: string;
|
|
41
|
+
computer?: string;
|
|
42
|
+
lease?: string | boolean;
|
|
43
|
+
box?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class PlacementError extends Error {
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse a `--where` / placement spec string.
|
|
50
|
+
*
|
|
51
|
+
* Accepted forms:
|
|
52
|
+
* local
|
|
53
|
+
* device[:name] | host[:name] (bare name → device:<name>)
|
|
54
|
+
* device:auto | auto | host:auto
|
|
55
|
+
* fleet
|
|
56
|
+
* cloud
|
|
57
|
+
* lease[:backend]
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseWhereSpec(raw: string, source?: string): Placement;
|
|
60
|
+
/** First non-empty host-family flag value (host / device / on / computer). */
|
|
61
|
+
export declare function hostFamilyTarget(flags: RunPlacementFlags): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve placement from run flags. `--where` wins only when no other
|
|
64
|
+
* placement flag is set; mixing is a PlacementError.
|
|
65
|
+
*/
|
|
66
|
+
export declare function placementFromRunFlags(flags: RunPlacementFlags): Placement;
|
|
67
|
+
/**
|
|
68
|
+
* Expand a resolved placement into the concrete run option fields the
|
|
69
|
+
* existing dispatch paths already understand. Pure — does not mutate input.
|
|
70
|
+
*
|
|
71
|
+
* `cloud` and `fleet` are not valid for a bare `agents run` (use `cloud run`
|
|
72
|
+
* / routines); they throw so callers fail loud.
|
|
73
|
+
*/
|
|
74
|
+
export declare function expandPlacementToRunFlags(placement: Placement): Pick<RunPlacementFlags, 'host' | 'device' | 'lease' | 'box'>;
|
|
75
|
+
/** Map routines hostStrategy (+ optional host) onto the shared Placement. */
|
|
76
|
+
export declare function placementFromHostStrategy(strategy: 'local' | 'host' | 'fleet' | 'cloud', host?: string): Placement;
|
|
77
|
+
/** One-line human form for logs / help. */
|
|
78
|
+
export declare function formatPlacement(p: Placement): string;
|
|
79
|
+
/**
|
|
80
|
+
* Short matrix for help footers and docs. Keep in sync with 00-concepts.md.
|
|
81
|
+
*/
|
|
82
|
+
export declare const PLACEMENT_MATRIX: string;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placement — one model for "where does the body run?"
|
|
3
|
+
*
|
|
4
|
+
* The CLI grew several doors that all mean execution target:
|
|
5
|
+
* run --host / --device / --lease / --box
|
|
6
|
+
* routines --placement / --run-on / hostStrategy
|
|
7
|
+
* monitors --run-on (body) vs --device (owner — NOT placement)
|
|
8
|
+
* teams --device (teammate pin)
|
|
9
|
+
* cloud run --provider host
|
|
10
|
+
*
|
|
11
|
+
* This module is the shared vocabulary. Old flags remain; --where is a
|
|
12
|
+
* thin alias on `agents run` that expands into them. Docs and help teach
|
|
13
|
+
* the matrix; stores stay separate (device registry, lease boxes, cloud).
|
|
14
|
+
*
|
|
15
|
+
* Owner (who may fire / evaluate) is NOT placement — see monitors.
|
|
16
|
+
*/
|
|
17
|
+
export class PlacementError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'PlacementError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const KINDS = new Set(['local', 'device', 'fleet', 'cloud', 'lease', 'host']);
|
|
24
|
+
/**
|
|
25
|
+
* Parse a `--where` / placement spec string.
|
|
26
|
+
*
|
|
27
|
+
* Accepted forms:
|
|
28
|
+
* local
|
|
29
|
+
* device[:name] | host[:name] (bare name → device:<name>)
|
|
30
|
+
* device:auto | auto | host:auto
|
|
31
|
+
* fleet
|
|
32
|
+
* cloud
|
|
33
|
+
* lease[:backend]
|
|
34
|
+
*/
|
|
35
|
+
export function parseWhereSpec(raw, source = '--where') {
|
|
36
|
+
const spec = raw.trim();
|
|
37
|
+
if (!spec) {
|
|
38
|
+
throw new PlacementError(`${source} requires a value (local | device:<name> | auto | lease | cloud | fleet)`);
|
|
39
|
+
}
|
|
40
|
+
const lower = spec.toLowerCase();
|
|
41
|
+
if (lower === 'local')
|
|
42
|
+
return { kind: 'local', source };
|
|
43
|
+
if (lower === 'auto')
|
|
44
|
+
return { kind: 'device', target: 'auto', source };
|
|
45
|
+
if (lower === 'fleet')
|
|
46
|
+
return { kind: 'fleet', source };
|
|
47
|
+
if (lower === 'cloud')
|
|
48
|
+
return { kind: 'cloud', source };
|
|
49
|
+
if (lower === 'lease')
|
|
50
|
+
return { kind: 'lease', source };
|
|
51
|
+
const colon = spec.indexOf(':');
|
|
52
|
+
if (colon === -1) {
|
|
53
|
+
// Bare token that is not a reserved kind → device target.
|
|
54
|
+
if (KINDS.has(lower)) {
|
|
55
|
+
// "device" / "host" alone means device with no pin (invalid for run).
|
|
56
|
+
throw new PlacementError(`${source} ${spec}: name a target (device:<name>, device:auto) or use local|lease|cloud|fleet`);
|
|
57
|
+
}
|
|
58
|
+
return { kind: 'device', target: spec, source };
|
|
59
|
+
}
|
|
60
|
+
const head = spec.slice(0, colon).toLowerCase();
|
|
61
|
+
const tail = spec.slice(colon + 1).trim();
|
|
62
|
+
if (!tail) {
|
|
63
|
+
throw new PlacementError(`${source} ${spec}: missing target after ':'`);
|
|
64
|
+
}
|
|
65
|
+
if (head === 'device' || head === 'host') {
|
|
66
|
+
return { kind: 'device', target: tail, source };
|
|
67
|
+
}
|
|
68
|
+
if (head === 'lease') {
|
|
69
|
+
return { kind: 'lease', target: tail, source };
|
|
70
|
+
}
|
|
71
|
+
if (head === 'cloud') {
|
|
72
|
+
return { kind: 'cloud', target: tail, source };
|
|
73
|
+
}
|
|
74
|
+
if (head === 'fleet') {
|
|
75
|
+
return { kind: 'fleet', target: tail, source };
|
|
76
|
+
}
|
|
77
|
+
throw new PlacementError(`${source} ${spec}: unknown kind '${head}' (use local | device:<name> | auto | lease[:backend] | cloud | fleet)`);
|
|
78
|
+
}
|
|
79
|
+
/** First non-empty host-family flag value (host / device / on / computer). */
|
|
80
|
+
export function hostFamilyTarget(flags) {
|
|
81
|
+
for (const v of [flags.host, flags.device, flags.on, flags.computer]) {
|
|
82
|
+
if (v)
|
|
83
|
+
return v;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve placement from run flags. `--where` wins only when no other
|
|
89
|
+
* placement flag is set; mixing is a PlacementError.
|
|
90
|
+
*/
|
|
91
|
+
export function placementFromRunFlags(flags) {
|
|
92
|
+
const where = flags.where?.trim();
|
|
93
|
+
const hostT = hostFamilyTarget(flags);
|
|
94
|
+
const hasLease = flags.lease !== undefined && flags.lease !== false;
|
|
95
|
+
const hasBox = !!flags.box;
|
|
96
|
+
const placementFlags = [];
|
|
97
|
+
if (where)
|
|
98
|
+
placementFlags.push('--where');
|
|
99
|
+
if (hostT)
|
|
100
|
+
placementFlags.push('--host/--device');
|
|
101
|
+
if (hasLease)
|
|
102
|
+
placementFlags.push('--lease');
|
|
103
|
+
if (hasBox)
|
|
104
|
+
placementFlags.push('--box');
|
|
105
|
+
if (placementFlags.length > 1) {
|
|
106
|
+
throw new PlacementError(`Conflicting placement flags: ${placementFlags.join(' + ')}. ` +
|
|
107
|
+
`Use one door — prefer --where (device:<name> | auto | lease | local).`);
|
|
108
|
+
}
|
|
109
|
+
if (where)
|
|
110
|
+
return parseWhereSpec(where, '--where');
|
|
111
|
+
if (hasBox)
|
|
112
|
+
return { kind: 'lease', target: flags.box, source: '--box' };
|
|
113
|
+
if (hasLease) {
|
|
114
|
+
const backend = typeof flags.lease === 'string' ? flags.lease : undefined;
|
|
115
|
+
return { kind: 'lease', target: backend, source: '--lease' };
|
|
116
|
+
}
|
|
117
|
+
if (hostT)
|
|
118
|
+
return { kind: 'device', target: hostT, source: '--host/--device' };
|
|
119
|
+
return { kind: 'local', source: 'default' };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Expand a resolved placement into the concrete run option fields the
|
|
123
|
+
* existing dispatch paths already understand. Pure — does not mutate input.
|
|
124
|
+
*
|
|
125
|
+
* `cloud` and `fleet` are not valid for a bare `agents run` (use `cloud run`
|
|
126
|
+
* / routines); they throw so callers fail loud.
|
|
127
|
+
*/
|
|
128
|
+
export function expandPlacementToRunFlags(placement) {
|
|
129
|
+
switch (placement.kind) {
|
|
130
|
+
case 'local':
|
|
131
|
+
return {};
|
|
132
|
+
case 'device':
|
|
133
|
+
if (!placement.target) {
|
|
134
|
+
throw new PlacementError(`${placement.source}: device placement needs a target (name or auto)`);
|
|
135
|
+
}
|
|
136
|
+
// Canonical host flag; --device is an alias of the same path.
|
|
137
|
+
return { host: placement.target };
|
|
138
|
+
case 'lease':
|
|
139
|
+
// --box reuses a warm slug; --where lease[:backend] / --lease provisions.
|
|
140
|
+
if (placement.source === '--box')
|
|
141
|
+
return { box: placement.target };
|
|
142
|
+
return placement.target ? { lease: placement.target } : { lease: true };
|
|
143
|
+
case 'fleet':
|
|
144
|
+
throw new PlacementError(`fleet placement is for routines (agents routines add … --placement fleet), not agents run. ` +
|
|
145
|
+
`Use --where device:auto for an affinity pick, or --where device:<name>.`);
|
|
146
|
+
case 'cloud':
|
|
147
|
+
throw new PlacementError(`cloud placement is agents cloud run (vendor cloud), not agents run. ` +
|
|
148
|
+
`For a disposable box use --where lease; for your fleet use --where device:<name>.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Map routines hostStrategy (+ optional host) onto the shared Placement. */
|
|
152
|
+
export function placementFromHostStrategy(strategy, host) {
|
|
153
|
+
switch (strategy) {
|
|
154
|
+
case 'local':
|
|
155
|
+
return { kind: 'local', source: 'hostStrategy:local' };
|
|
156
|
+
case 'host':
|
|
157
|
+
return { kind: 'device', target: host, source: 'hostStrategy:host' };
|
|
158
|
+
case 'fleet':
|
|
159
|
+
return { kind: 'fleet', source: 'hostStrategy:fleet' };
|
|
160
|
+
case 'cloud':
|
|
161
|
+
return { kind: 'cloud', source: 'hostStrategy:cloud' };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** One-line human form for logs / help. */
|
|
165
|
+
export function formatPlacement(p) {
|
|
166
|
+
if (p.kind === 'local')
|
|
167
|
+
return 'local';
|
|
168
|
+
if (p.target)
|
|
169
|
+
return `${p.kind}:${p.target}`;
|
|
170
|
+
return p.kind;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Short matrix for help footers and docs. Keep in sync with 00-concepts.md.
|
|
174
|
+
*/
|
|
175
|
+
export const PLACEMENT_MATRIX = `
|
|
176
|
+
Intent Flag / path
|
|
177
|
+
───────────────────────────── ──────────────────────────────────────────
|
|
178
|
+
This machine (default) or --where local
|
|
179
|
+
Named fleet box --where device:<name> (= --host / --device)
|
|
180
|
+
Affinity pick (14d usage) --where auto (= --device auto)
|
|
181
|
+
Disposable cloud box --where lease (= --lease)
|
|
182
|
+
Reuse warm crabbox --box <slug>
|
|
183
|
+
Routines: body on one box --run-on <name> / --placement host
|
|
184
|
+
Routines: pick any online --placement fleet
|
|
185
|
+
Vendor cloud task agents cloud run …
|
|
186
|
+
Monitors: who evaluates --device <owner> (NOT body placement)
|
|
187
|
+
Monitors: where action runs --run-on <host>
|
|
188
|
+
`.trim();
|
package/dist/lib/profiles.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { AgentId } from './types.js';
|
|
9
9
|
import { type Preset } from './profiles-presets.js';
|
|
10
|
+
import { type ModelTier } from './model-tiers.js';
|
|
10
11
|
/** A named profile binding an agent host, env vars, and optional keychain auth. */
|
|
11
12
|
export interface Profile {
|
|
12
13
|
name: string;
|
|
@@ -30,9 +31,10 @@ export interface Profile {
|
|
|
30
31
|
preset?: string;
|
|
31
32
|
provider?: string;
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* Stored for backward-compatible YAML parsing only — no longer read for
|
|
35
|
+
* display. `profileLabel()` always derives the display name from `name` via
|
|
36
|
+
* the vendor/brand table. Old YAML files that carry this key still parse
|
|
37
|
+
* correctly; it is simply ignored.
|
|
36
38
|
*/
|
|
37
39
|
label?: string;
|
|
38
40
|
/**
|
|
@@ -49,6 +51,19 @@ export interface Profile {
|
|
|
49
51
|
* changes; auth, base URL, and every other profile env value are preserved.
|
|
50
52
|
*/
|
|
51
53
|
fallback_model?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Per-tier model ids for this harness's OWN catalog, keyed by the same cost
|
|
56
|
+
* tiers `agents run --model cheap|default|best|ultra` uses for a native
|
|
57
|
+
* agent. Lets a custom harness (which runs through a host agent's binary,
|
|
58
|
+
* e.g. `deepseek-flash` hosted on `claude`) resolve a tier against its own
|
|
59
|
+
* models instead of colliding with the host agent's native catalog
|
|
60
|
+
* (`resolveTier` in model-tiers.ts, which only knows native agents).
|
|
61
|
+
* An unset tier clamps to the next CHEAPER tier that IS set (see
|
|
62
|
+
* `resolveProfileTierModel`). Omitted entirely -> tiers are not supported
|
|
63
|
+
* for this profile and a requested tier falls back to the harness's single
|
|
64
|
+
* pinned model, unchanged from before this field existed.
|
|
65
|
+
*/
|
|
66
|
+
models?: Partial<Record<ModelTier, string>>;
|
|
52
67
|
}
|
|
53
68
|
/**
|
|
54
69
|
* Stable, machine-readable summary used by `agents view` and `--json`.
|
|
@@ -57,7 +72,7 @@ export interface Profile {
|
|
|
57
72
|
*/
|
|
58
73
|
export interface ProfileSummary {
|
|
59
74
|
name: string;
|
|
60
|
-
/** Human-facing header label — `
|
|
75
|
+
/** Human-facing header label — always derived from `name` via the vendor/brand table. */
|
|
61
76
|
label: string;
|
|
62
77
|
agent: AgentId;
|
|
63
78
|
host: string;
|
|
@@ -111,8 +126,12 @@ export declare function profileModelEnvKey(profile: Profile): string | null;
|
|
|
111
126
|
*/
|
|
112
127
|
export declare function profileAuthLabel(profile: Profile): string;
|
|
113
128
|
/**
|
|
114
|
-
* Header label for the harness —
|
|
115
|
-
*
|
|
129
|
+
* Header label for the harness — derived from `profile.name` by splitting on
|
|
130
|
+
* `[-_]` and mapping each token through the vendor/brand table. Never reads
|
|
131
|
+
* the stored `label` field; old YAML files with a `label:` key are unaffected.
|
|
132
|
+
*
|
|
133
|
+
* Examples: `deepseek-flash` → `'DeepSeek Flash'`, `spark` → `'Spark'`,
|
|
134
|
+
* `deepseek_chat_v3` → `'DeepSeek Chat V3'`.
|
|
116
135
|
*/
|
|
117
136
|
export declare function profileLabel(profile: Profile): string;
|
|
118
137
|
/** Build a stable, machine-readable summary for list and view surfaces. */
|
|
@@ -139,8 +158,6 @@ export interface HostModelOptions {
|
|
|
139
158
|
/** Env var the host reads its auth token from; pair with `provider` to attach keychain auth. */
|
|
140
159
|
authEnvVar?: string;
|
|
141
160
|
description?: string;
|
|
142
|
-
/** Human-facing header label; defaults to the harness name. */
|
|
143
|
-
label?: string;
|
|
144
161
|
}
|
|
145
162
|
/**
|
|
146
163
|
* Build a custom-harness profile from a host CLI + model in one shot, without a
|
|
@@ -162,7 +179,6 @@ export interface ForkProfileOptions {
|
|
|
162
179
|
authEnvVar?: string;
|
|
163
180
|
/** Re-pin (or unpin, with an empty string) the host CLI version. */
|
|
164
181
|
version?: string;
|
|
165
|
-
label?: string;
|
|
166
182
|
description?: string;
|
|
167
183
|
}
|
|
168
184
|
/**
|
|
@@ -171,6 +187,26 @@ export interface ForkProfileOptions {
|
|
|
171
187
|
* diverge from here and deleting the source never affects the fork.
|
|
172
188
|
*/
|
|
173
189
|
export declare function forkProfile(source: Profile, name: string, opts?: ForkProfileOptions): Profile;
|
|
190
|
+
/**
|
|
191
|
+
* Edit an existing profile in-place, applying overrides without changing its
|
|
192
|
+
* name or lineage. Reuses {@link forkProfile}'s validation and override logic
|
|
193
|
+
* (model swap, base-URL validation, auth repoint), then restores the original
|
|
194
|
+
* `forkedFrom` so an edit never self-references the profile.
|
|
195
|
+
*
|
|
196
|
+
* Note: this returns the updated `Profile` object but does NOT write it to
|
|
197
|
+
* disk — callers should follow up with `writeProfile(result)` if persistence
|
|
198
|
+
* is needed.
|
|
199
|
+
*/
|
|
200
|
+
export declare function editProfile(source: Profile, opts?: ForkProfileOptions): Profile;
|
|
201
|
+
/**
|
|
202
|
+
* Rename a profile on disk, then rewrite `forkedFrom` in every other profile
|
|
203
|
+
* that pointed at the old name so lineage display never goes stale.
|
|
204
|
+
*
|
|
205
|
+
* Throws if `oldName` does not exist or `newName` already exists. There is no
|
|
206
|
+
* `--force` / overwrite path — a collision is a hard error directing the user
|
|
207
|
+
* to remove the target first.
|
|
208
|
+
*/
|
|
209
|
+
export declare function renameProfile(oldName: string, newName: string): void;
|
|
174
210
|
/**
|
|
175
211
|
* Resolve a profile into the env block that should be injected into the
|
|
176
212
|
* spawned agent process. Reads the token from keychain at exec time so the
|
|
@@ -193,13 +229,40 @@ export interface ResolvedProfileRun {
|
|
|
193
229
|
envKey: string;
|
|
194
230
|
model: string;
|
|
195
231
|
};
|
|
232
|
+
/**
|
|
233
|
+
* Set when the caller requested a cost tier (`--model cheap|default|...`)
|
|
234
|
+
* but this profile has no `models:` entry to resolve it against (not even a
|
|
235
|
+
* cheaper tier to clamp to). `env` is returned unmodified — the harness's
|
|
236
|
+
* single pinned model — and this note is informational only, matching the
|
|
237
|
+
* "using harness default" convention exec.ts's native tier block already
|
|
238
|
+
* uses; the caller prints it, it never throws.
|
|
239
|
+
*/
|
|
240
|
+
tierNote?: string;
|
|
241
|
+
/**
|
|
242
|
+
* Set when `requestedModel` was a tier token AND this profile resolved it
|
|
243
|
+
* against its own `models:` map. Callers that forward a `--model` value
|
|
244
|
+
* downstream (e.g. as `ExecOptions.model`) should substitute this in place
|
|
245
|
+
* of the original tier token — exec.ts's native tier block only knows how
|
|
246
|
+
* to resolve a tier against the HOST agent's own catalog, which is the
|
|
247
|
+
* wrong catalog for a profile's own harness identity. Undefined both when
|
|
248
|
+
* no tier was requested and when tier resolution degraded (see `tierNote`).
|
|
249
|
+
*/
|
|
250
|
+
resolvedModel?: string;
|
|
196
251
|
}
|
|
197
252
|
/**
|
|
198
253
|
* Resolve a name into (agent, version, env). Throws if the name is not a
|
|
199
254
|
* profile. Callers are expected to try agent-id resolution first and fall
|
|
200
255
|
* back to this when that fails, so we don't need a "isProfile" probe.
|
|
256
|
+
*
|
|
257
|
+
* `requestedModel` is the caller's raw `--model` value. When it is a cost-tier
|
|
258
|
+
* token (`cheap`/`default`/`best`/`ultra`), it is resolved against the
|
|
259
|
+
* profile's OWN `models:` map (see `resolveProfileTierModel`) and substituted
|
|
260
|
+
* into `env` as a concrete model id BEFORE returning — so exec.ts's native
|
|
261
|
+
* tier-resolution block (which indexes the HOST agent's catalog, e.g. Claude's
|
|
262
|
+
* own models) never sees a tier token for a profile-based run, and can't
|
|
263
|
+
* collide the profile's harness identity with its host's catalog.
|
|
201
264
|
*/
|
|
202
|
-
export declare function resolveProfileForRun(name: string): ResolvedProfileRun;
|
|
265
|
+
export declare function resolveProfileForRun(name: string, requestedModel?: string): ResolvedProfileRun;
|
|
203
266
|
/**
|
|
204
267
|
* Look up the preset a profile was created from, if any. Used by
|
|
205
268
|
* `profiles view` to show upstream metadata like signup URLs.
|