@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +131 -1
  2. package/README.md +7 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/browser.js +61 -0
  5. package/dist/commands/exec.js +83 -16
  6. package/dist/commands/feed.d.ts +2 -1
  7. package/dist/commands/feed.js +47 -20
  8. package/dist/commands/focus.js +22 -1
  9. package/dist/commands/harness.d.ts +0 -1
  10. package/dist/commands/harness.js +60 -4
  11. package/dist/commands/models.js +2 -2
  12. package/dist/commands/monitors.js +2 -2
  13. package/dist/commands/projects.js +122 -107
  14. package/dist/commands/routines.js +2 -2
  15. package/dist/commands/run-account-picker.js +2 -0
  16. package/dist/commands/secrets.js +1 -1
  17. package/dist/commands/sessions-backfill.d.ts +33 -0
  18. package/dist/commands/sessions-backfill.js +83 -1
  19. package/dist/commands/sessions-stats.d.ts +36 -0
  20. package/dist/commands/sessions-stats.js +263 -0
  21. package/dist/commands/sessions.d.ts +1 -1
  22. package/dist/commands/sessions.js +21 -1
  23. package/dist/commands/snapshot.d.ts +11 -0
  24. package/dist/commands/snapshot.js +107 -0
  25. package/dist/commands/teams.js +2 -1
  26. package/dist/commands/view.d.ts +9 -1
  27. package/dist/commands/view.js +63 -12
  28. package/dist/index.js +4 -2
  29. package/dist/lib/activity.js +2 -2
  30. package/dist/lib/agents.js +68 -11
  31. package/dist/lib/analytics/recipes.js +11 -5
  32. package/dist/lib/browser/ipc.js +2 -0
  33. package/dist/lib/browser/profiles.d.ts +15 -7
  34. package/dist/lib/browser/profiles.js +53 -12
  35. package/dist/lib/browser/remote-control.d.ts +35 -0
  36. package/dist/lib/browser/remote-control.js +48 -0
  37. package/dist/lib/browser/service.d.ts +19 -0
  38. package/dist/lib/browser/service.js +19 -1
  39. package/dist/lib/browser/types.d.ts +14 -2
  40. package/dist/lib/byok-usage.d.ts +38 -0
  41. package/dist/lib/byok-usage.js +117 -0
  42. package/dist/lib/capabilities.js +1 -1
  43. package/dist/lib/device-config.js +8 -0
  44. package/dist/lib/exec.d.ts +20 -0
  45. package/dist/lib/exec.js +73 -8
  46. package/dist/lib/feed-outcome.d.ts +1 -0
  47. package/dist/lib/feed-outcome.js +2 -0
  48. package/dist/lib/feed-post.js +1 -1
  49. package/dist/lib/feed-ranking.d.ts +1 -0
  50. package/dist/lib/feed-ranking.js +4 -0
  51. package/dist/lib/feed.d.ts +4 -1
  52. package/dist/lib/feed.js +25 -0
  53. package/dist/lib/hosts/passthrough.d.ts +10 -1
  54. package/dist/lib/hosts/passthrough.js +23 -3
  55. package/dist/lib/hosts/remote-cmd.js +1 -0
  56. package/dist/lib/mcp.js +6 -1
  57. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  58. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  59. package/dist/lib/model-tiers.js +4 -1
  60. package/dist/lib/models.js +63 -0
  61. package/dist/lib/placement.d.ts +82 -0
  62. package/dist/lib/placement.js +188 -0
  63. package/dist/lib/profiles.d.ts +73 -10
  64. package/dist/lib/profiles.js +133 -15
  65. package/dist/lib/project-focus.d.ts +9 -0
  66. package/dist/lib/project-focus.js +23 -0
  67. package/dist/lib/project-key.d.ts +1 -1
  68. package/dist/lib/project-key.js +1 -1
  69. package/dist/lib/project-probe.d.ts +18 -0
  70. package/dist/lib/project-probe.js +46 -0
  71. package/dist/lib/project-status.d.ts +56 -0
  72. package/dist/lib/project-status.js +125 -18
  73. package/dist/lib/resources/mcp.js +3 -0
  74. package/dist/lib/resources/types.d.ts +1 -1
  75. package/dist/lib/rotate.d.ts +19 -4
  76. package/dist/lib/rotate.js +24 -1
  77. package/dist/lib/routines.d.ts +2 -0
  78. package/dist/lib/runner.d.ts +3 -0
  79. package/dist/lib/runner.js +90 -7
  80. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  81. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  82. package/dist/lib/secrets/audit.js +1 -1
  83. package/dist/lib/secrets/index.d.ts +1 -0
  84. package/dist/lib/secrets/index.js +29 -15
  85. package/dist/lib/secrets/remote.d.ts +1 -1
  86. package/dist/lib/secrets/remote.js +1 -1
  87. package/dist/lib/session/active.d.ts +2 -0
  88. package/dist/lib/session/bash-command.d.ts +2 -3
  89. package/dist/lib/session/db.d.ts +92 -1
  90. package/dist/lib/session/db.js +230 -1
  91. package/dist/lib/session/digest.d.ts +1 -1
  92. package/dist/lib/session/digest.js +1 -1
  93. package/dist/lib/share/publish.js +24 -0
  94. package/dist/lib/snapshot.d.ts +103 -0
  95. package/dist/lib/snapshot.js +99 -0
  96. package/dist/lib/startup/command-registry.d.ts +1 -1
  97. package/dist/lib/startup/command-registry.js +2 -2
  98. package/dist/lib/subagents-registry.js +3 -0
  99. package/dist/lib/types.d.ts +11 -2
  100. package/dist/lib/usage.d.ts +5 -0
  101. package/dist/lib/usage.js +3 -3
  102. package/package.json +1 -1
  103. package/dist/commands/activity.d.ts +0 -87
  104. package/dist/commands/activity.js +0 -346
@@ -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
- const aggregator = agent === 'cursor';
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;
@@ -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();
@@ -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
- * Human-facing label for the harness — what `agents view` prints as the
34
- * agent-type header, the same slot `AGENTS[id].name` fills for a native
35
- * harness. Defaults to the profile name when unset.
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 — `label` when set, else the profile name. */
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 — the slot `AGENTS[id].name` fills for a native
115
- * harness, so `agents view` can print custom and native harnesses the same way.
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.