@dylanrussell/agent-router 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -15244,19 +15244,31 @@ var init_zod = __esm({
15244
15244
  });
15245
15245
 
15246
15246
  // src/core/schema.ts
15247
- var StateFileSchema, AgentEntrySchema, StackFileSchema, ConfigFileSchema, OpencodeJsonSchema;
15247
+ var FallbackSchema, RoutingEntrySchema, StateFileSchema, AgentEntrySchema, StackFileSchema, ConfigFileSchema, OpencodeJsonSchema;
15248
15248
  var init_schema = __esm({
15249
15249
  "src/core/schema.ts"() {
15250
15250
  "use strict";
15251
15251
  init_zod();
15252
+ FallbackSchema = external_exports.object({
15253
+ model: external_exports.string().regex(/^[^/\s]+\/\S+$/, "Expected provider/model"),
15254
+ variant: external_exports.string().min(1).optional()
15255
+ }).strict();
15256
+ RoutingEntrySchema = external_exports.object({
15257
+ model: external_exports.string().min(1),
15258
+ variant: external_exports.string().min(1).nullable().optional(),
15259
+ fallbacks: external_exports.array(FallbackSchema).max(8).optional()
15260
+ }).strict();
15252
15261
  StateFileSchema = external_exports.object({
15253
15262
  version: external_exports.literal(1),
15254
15263
  active: external_exports.string().min(1),
15255
15264
  previousActive: external_exports.string().min(1).nullable(),
15256
- lastSwitchedAt: external_exports.string().min(1)
15265
+ lastSwitchedAt: external_exports.string().min(1),
15266
+ fallbackAgents: external_exports.record(external_exports.string(), RoutingEntrySchema).optional()
15257
15267
  }).strict();
15258
15268
  AgentEntrySchema = external_exports.object({
15259
- model: external_exports.string().min(1)
15269
+ model: external_exports.string().min(1),
15270
+ variant: external_exports.string().min(1).nullable().optional(),
15271
+ fallbacks: external_exports.array(FallbackSchema).max(8).optional()
15260
15272
  }).passthrough();
15261
15273
  StackFileSchema = external_exports.object({
15262
15274
  agents: external_exports.record(external_exports.string(), AgentEntrySchema)
@@ -16086,8 +16098,300 @@ init_errors();
16086
16098
  import { existsSync as existsSync3 } from "fs";
16087
16099
  import { readFile as readFile2, readdir } from "fs/promises";
16088
16100
  import path4 from "path";
16101
+
16102
+ // src/core/yaml-lite.ts
16103
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
16104
+ "null",
16105
+ "true",
16106
+ "false",
16107
+ "yes",
16108
+ "no",
16109
+ "on",
16110
+ "off",
16111
+ "~",
16112
+ // YAML 1.1 nulls
16113
+ "Null",
16114
+ "NULL",
16115
+ "True",
16116
+ "False",
16117
+ "Yes",
16118
+ "No",
16119
+ "On",
16120
+ "Off"
16121
+ ]);
16122
+ var BARE_STRING_RE = /^[A-Za-z0-9._\-\/+@$%^&()~]+$/;
16123
+ function needsQuoting(s) {
16124
+ if (s === "") return true;
16125
+ if (RESERVED_WORDS.has(s)) return true;
16126
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(s)) return true;
16127
+ if (/^0x[0-9A-Fa-f]+$/.test(s)) return true;
16128
+ if (/^[+-]?\.(inf|Inf|INF|nan|NaN|NAN)$/.test(s)) return true;
16129
+ if (!BARE_STRING_RE.test(s)) return true;
16130
+ return false;
16131
+ }
16132
+ function serializeOptionValue(value) {
16133
+ if (value === null || value === void 0) return "null";
16134
+ if (typeof value === "boolean") return value ? "true" : "false";
16135
+ if (typeof value === "number") {
16136
+ if (!Number.isFinite(value)) return String(value);
16137
+ return JSON.stringify(value);
16138
+ }
16139
+ if (typeof value === "bigint") return String(value);
16140
+ if (typeof value === "string") return needsQuoting(value) ? JSON.stringify(value) : value;
16141
+ return JSON.stringify(value);
16142
+ }
16143
+ function stripComment(line) {
16144
+ let inSingle = false;
16145
+ let inDouble = false;
16146
+ for (let i = 0; i < line.length; i++) {
16147
+ const ch = line[i];
16148
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
16149
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
16150
+ else if (ch === "#" && !inSingle && !inDouble) {
16151
+ if (i === 0 || /\s/.test(line[i - 1] ?? "")) return line.slice(0, i);
16152
+ }
16153
+ }
16154
+ return line;
16155
+ }
16156
+ function tokenize(block) {
16157
+ const out = [];
16158
+ for (const raw of block.split(/\r?\n/)) {
16159
+ if (raw.trim() === "") continue;
16160
+ const indentMatch = /^( *)/.exec(raw);
16161
+ const indent = indentMatch?.[1]?.length ?? 0;
16162
+ const content = stripComment(raw.slice(indent));
16163
+ if (content.trim() === "") continue;
16164
+ out.push({ indent, text: content.trimEnd(), raw });
16165
+ }
16166
+ return out;
16167
+ }
16168
+ function coerceScalar(raw) {
16169
+ const v = raw.trim();
16170
+ if (v === "" || v === "null" || v === "~" || v === "Null" || v === "NULL") return null;
16171
+ if (v === "true" || v === "True" || v === "TRUE") return true;
16172
+ if (v === "false" || v === "False" || v === "FALSE") return false;
16173
+ if (v === "yes" || v === "Yes" || v === "YES") return true;
16174
+ if (v === "no" || v === "No" || v === "NO") return false;
16175
+ if (v === "on" || v === "On" || v === "ON") return true;
16176
+ if (v === "off" || v === "Off" || v === "OFF") return false;
16177
+ if (/^[+-]?\d+$/.test(v)) return Number.parseInt(v, 10);
16178
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(v)) return Number.parseFloat(v);
16179
+ if (/^0x[0-9A-Fa-f]+$/.test(v)) return Number.parseInt(v, 16);
16180
+ return v;
16181
+ }
16182
+ function parseQuoted(s) {
16183
+ const quote = s[0] ?? "";
16184
+ if (quote !== '"' && quote !== "'") return null;
16185
+ if (quote === '"') {
16186
+ let out2 = "";
16187
+ for (let i = 1; i < s.length; i++) {
16188
+ const ch = s[i] ?? "";
16189
+ if (ch === "\\" && i + 1 < s.length) {
16190
+ const next = s[i + 1] ?? "";
16191
+ const map2 = {
16192
+ n: "\n",
16193
+ t: " ",
16194
+ r: "\r",
16195
+ '"': '"',
16196
+ "\\": "\\",
16197
+ "/": "/",
16198
+ b: "\b",
16199
+ f: "\f"
16200
+ };
16201
+ out2 += map2[next] ?? next;
16202
+ i++;
16203
+ } else if (ch === '"') {
16204
+ return { value: out2, rest: s.slice(i + 1) };
16205
+ } else {
16206
+ out2 += ch;
16207
+ }
16208
+ }
16209
+ return null;
16210
+ }
16211
+ let out = "";
16212
+ for (let i = 1; i < s.length; i++) {
16213
+ const ch = s[i] ?? "";
16214
+ if (ch === "'") {
16215
+ if (s[i + 1] === "'") {
16216
+ out += "'";
16217
+ i++;
16218
+ } else {
16219
+ return { value: out, rest: s.slice(i + 1) };
16220
+ }
16221
+ } else {
16222
+ out += ch;
16223
+ }
16224
+ }
16225
+ return null;
16226
+ }
16227
+ function splitFlow(raw) {
16228
+ const parts = [];
16229
+ let depth = 0;
16230
+ let inSingle = false;
16231
+ let inDouble = false;
16232
+ let start = 0;
16233
+ for (let i = 0; i < raw.length; i++) {
16234
+ const ch = raw[i];
16235
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
16236
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
16237
+ else if (!inSingle && !inDouble) {
16238
+ if (ch === "{" || ch === "[") depth++;
16239
+ else if (ch === "}" || ch === "]") depth--;
16240
+ else if (ch === "," && depth === 0) {
16241
+ parts.push(raw.slice(start, i));
16242
+ start = i + 1;
16243
+ }
16244
+ }
16245
+ }
16246
+ parts.push(raw.slice(start));
16247
+ return parts;
16248
+ }
16249
+ function parseFlowValue(raw) {
16250
+ const s = raw.trim();
16251
+ if (s === "") return null;
16252
+ const first = s[0] ?? "";
16253
+ if (first === "{") {
16254
+ const inner = s.slice(1, s.length - 1);
16255
+ const obj = {};
16256
+ for (const part of splitFlow(inner)) {
16257
+ const t = part.trim();
16258
+ if (t === "") continue;
16259
+ const colon = findColon(t);
16260
+ if (colon < 0) {
16261
+ obj[t] = null;
16262
+ } else {
16263
+ const k = t.slice(0, colon).trim();
16264
+ const v = t.slice(colon + 1).trim();
16265
+ obj[stripKeyQuotes(k)] = parseFlowValue(v);
16266
+ }
16267
+ }
16268
+ return obj;
16269
+ }
16270
+ if (first === "[") {
16271
+ const inner = s.slice(1, s.length - 1);
16272
+ return splitFlow(inner).filter((p) => p.trim() !== "").map((p) => parseFlowValue(p));
16273
+ }
16274
+ if (first === '"' || first === "'") {
16275
+ const q = parseQuoted(s);
16276
+ return q ? q.value : s;
16277
+ }
16278
+ return coerceScalar(s);
16279
+ }
16280
+ function findColon(s) {
16281
+ let inSingle = false;
16282
+ let inDouble = false;
16283
+ let depth = 0;
16284
+ for (let i = 0; i < s.length; i++) {
16285
+ const ch = s[i];
16286
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
16287
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
16288
+ else if (!inSingle && !inDouble) {
16289
+ if (ch === "{" || ch === "[") depth++;
16290
+ else if (ch === "}" || ch === "]") depth--;
16291
+ else if (ch === ":" && depth === 0) return i;
16292
+ }
16293
+ }
16294
+ return -1;
16295
+ }
16296
+ function stripKeyQuotes(k) {
16297
+ if (k.length >= 2 && (k[0] === '"' && k.at(-1) === '"' || k[0] === "'" && k.at(-1) === "'")) {
16298
+ return k.slice(1, -1);
16299
+ }
16300
+ return k;
16301
+ }
16302
+ function parseMapping(lines, startIdx, indent) {
16303
+ const out = {};
16304
+ let i = startIdx;
16305
+ while (i < lines.length) {
16306
+ const line = lines[i];
16307
+ if (!line) break;
16308
+ if (line.indent < indent) break;
16309
+ if (line.indent > indent) {
16310
+ i++;
16311
+ continue;
16312
+ }
16313
+ const colon = findColon(line.text);
16314
+ if (colon < 0) {
16315
+ i++;
16316
+ continue;
16317
+ }
16318
+ const key = stripKeyQuotes(line.text.slice(0, colon).trim());
16319
+ if (key === "") {
16320
+ i++;
16321
+ continue;
16322
+ }
16323
+ const rest = line.text.slice(colon + 1).trim();
16324
+ if (rest !== "") {
16325
+ out[key] = parseFlowValue(rest);
16326
+ i++;
16327
+ } else {
16328
+ const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
16329
+ let j = i + 1;
16330
+ while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
16331
+ if (j === i + 1) {
16332
+ out[key] = null;
16333
+ i++;
16334
+ } else {
16335
+ const childBlock = lines.slice(i + 1, j);
16336
+ const firstChild = childBlock[0];
16337
+ if (firstChild?.text.trimStart().startsWith("- ")) {
16338
+ out[key] = parseSequence(childBlock, childIndent);
16339
+ } else {
16340
+ out[key] = parseMapping(childBlock, 0, childIndent).value;
16341
+ }
16342
+ i = j;
16343
+ }
16344
+ }
16345
+ }
16346
+ return { value: out, next: i };
16347
+ }
16348
+ function parseSequence(lines, indent) {
16349
+ const out = [];
16350
+ let i = 0;
16351
+ while (i < lines.length) {
16352
+ const line = lines[i];
16353
+ if (!line) break;
16354
+ if (line.indent < indent) break;
16355
+ const t = line.text.trimStart();
16356
+ if (!t.startsWith("- ")) {
16357
+ i++;
16358
+ continue;
16359
+ }
16360
+ const item = t.slice(2).trim();
16361
+ if (item === "") {
16362
+ const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
16363
+ let j = i + 1;
16364
+ while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
16365
+ out.push(parseMapping(lines.slice(i + 1, j), 0, childIndent).value);
16366
+ i = j;
16367
+ } else {
16368
+ out.push(parseFlowValue(item));
16369
+ i++;
16370
+ }
16371
+ }
16372
+ return out;
16373
+ }
16374
+ function parseFrontmatterBlock(block) {
16375
+ const lines = tokenize(block);
16376
+ if (lines.length === 0) return null;
16377
+ const { value } = parseMapping(lines, 0, 0);
16378
+ return value;
16379
+ }
16380
+
16381
+ // src/core/frontmatter.ts
16089
16382
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
16090
16383
  var MODEL_LINE_RE = /^model:[ \t]*(.*)$/m;
16384
+ var RESERVED_AGENT_KEYS = /* @__PURE__ */ new Set([
16385
+ "name",
16386
+ "mode",
16387
+ "description",
16388
+ "permission",
16389
+ "color",
16390
+ "tools",
16391
+ "prompt",
16392
+ "steps",
16393
+ "maxSteps"
16394
+ ]);
16091
16395
  function cleanModelValue(raw) {
16092
16396
  let v = raw;
16093
16397
  const hash2 = v.search(/[ \t]#/);
@@ -16116,6 +16420,55 @@ function setFrontmatterModel(content, model) {
16116
16420
  const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
16117
16421
  return nextFm + content.slice(fm[0].length);
16118
16422
  }
16423
+ function getFrontmatterOptions(content) {
16424
+ const fm = FRONTMATTER_RE.exec(content);
16425
+ if (!fm?.[1]) return {};
16426
+ const parsed = parseFrontmatterBlock(fm[1]);
16427
+ if (!parsed) return {};
16428
+ const out = {};
16429
+ for (const [k, v] of Object.entries(parsed)) {
16430
+ if (k === "model" || RESERVED_AGENT_KEYS.has(k)) continue;
16431
+ out[k] = v;
16432
+ }
16433
+ return out;
16434
+ }
16435
+ function setFrontmatterOptions(content, options) {
16436
+ const fm = FRONTMATTER_RE.exec(content);
16437
+ if (!fm?.[1]) throw new Error("no frontmatter block");
16438
+ const block = fm[1];
16439
+ const keys = Object.keys(options);
16440
+ if (keys.length === 0) return content;
16441
+ const eol = block.includes("\r\n") ? "\r\n" : "\n";
16442
+ const lines = block.split(/\r?\n/);
16443
+ const handled = /* @__PURE__ */ new Set();
16444
+ for (let i = 0; i < lines.length; i++) {
16445
+ const m = /^([A-Za-z0-9_-]+):[ \t]*(.*)$/.exec(lines[i] ?? "");
16446
+ if (!m) continue;
16447
+ const key = m[1] ?? "";
16448
+ if (!(key in options)) continue;
16449
+ handled.add(key);
16450
+ let end = i + 1;
16451
+ while (end < lines.length && /^[ \t]+/.test(lines[end] ?? "")) end++;
16452
+ const value = options[key];
16453
+ if (value === null || value === void 0) {
16454
+ lines.splice(i, end - i);
16455
+ i--;
16456
+ } else {
16457
+ lines.splice(i, end - i, `${key}: ${serializeOptionValue(value)}`);
16458
+ }
16459
+ }
16460
+ const appended = [];
16461
+ for (const [key, value] of Object.entries(options)) {
16462
+ if (handled.has(key)) continue;
16463
+ if (value === null || value === void 0) continue;
16464
+ appended.push(`${key}: ${serializeOptionValue(value)}`);
16465
+ }
16466
+ const nextBlock = appended.length > 0 ? lines.join(eol) + eol + appended.join(eol) : lines.join(eol);
16467
+ if (nextBlock === block) return content;
16468
+ const blockStart = fm[0].indexOf(block);
16469
+ const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
16470
+ return nextFm + content.slice(fm[0].length);
16471
+ }
16119
16472
  function agentFilePath(agentsDir, name) {
16120
16473
  return path4.join(agentsDir, `${name}.md`);
16121
16474
  }
@@ -16129,7 +16482,7 @@ async function listAgentFiles(agentsDir) {
16129
16482
  }
16130
16483
  return names.filter((n) => n.endsWith(".md")).map((n) => n.slice(0, -".md".length)).sort();
16131
16484
  }
16132
- async function readAgentModels(agentsDir) {
16485
+ async function readAgentEntries(agentsDir) {
16133
16486
  const out = {};
16134
16487
  for (const name of await listAgentFiles(agentsDir)) {
16135
16488
  const filePath = agentFilePath(agentsDir, name);
@@ -16140,10 +16493,17 @@ async function readAgentModels(agentsDir) {
16140
16493
  throw new IOError(`Failed to read ${filePath}: ${cause.message}`, cause);
16141
16494
  }
16142
16495
  const model = getFrontmatterModel(content);
16143
- if (model !== null) out[name] = model;
16496
+ if (model === null) continue;
16497
+ out[name] = { model, options: getFrontmatterOptions(content) };
16144
16498
  }
16145
16499
  return out;
16146
16500
  }
16501
+ async function readAgentModels(agentsDir) {
16502
+ const entries = await readAgentEntries(agentsDir);
16503
+ const out = {};
16504
+ for (const [name, entry] of Object.entries(entries)) out[name] = entry.model;
16505
+ return out;
16506
+ }
16147
16507
  async function readAgentFileStrict(agentsDir, name) {
16148
16508
  const filePath = agentFilePath(agentsDir, name);
16149
16509
  if (!existsSync3(filePath)) {
@@ -16159,7 +16519,7 @@ async function readAgentFileStrict(agentsDir, name) {
16159
16519
  if (model === null) {
16160
16520
  throw new AgentFileError(name, filePath, "no frontmatter `model:` line to rewrite");
16161
16521
  }
16162
- return { filePath, content, model };
16522
+ return { filePath, content, model, options: getFrontmatterOptions(content) };
16163
16523
  }
16164
16524
 
16165
16525
  // src/core/history.ts
@@ -16349,6 +16709,9 @@ function collectModelRefs(stack) {
16349
16709
  const refs = [];
16350
16710
  for (const [k, v] of Object.entries(stack.agents)) {
16351
16711
  refs.push({ path: `agents.${k}.model`, modelId: v.model });
16712
+ for (const [index, candidate] of (v.fallbacks ?? []).entries()) {
16713
+ refs.push({ path: `agents.${k}.fallbacks.${index}.model`, modelId: candidate.model });
16714
+ }
16352
16715
  }
16353
16716
  return refs;
16354
16717
  }
@@ -16428,15 +16791,14 @@ async function applyStack(paths, name, options = {}) {
16428
16791
  const pending = [];
16429
16792
  for (const [agent, entry] of Object.entries(target.agents)) {
16430
16793
  const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);
16431
- pending.push({
16432
- agent,
16433
- filePath,
16434
- next: model === entry.model ? null : setFrontmatterModel(content, entry.model)
16435
- });
16794
+ const wantOptions = entryOptions(entry);
16795
+ const afterOptions = setFrontmatterOptions(content, wantOptions);
16796
+ const nextContent = entry.model === model ? afterOptions : setFrontmatterModel(afterOptions, entry.model);
16797
+ pending.push({ agent, filePath, next: nextContent === content ? null : nextContent });
16436
16798
  }
16437
16799
  const prevState = await readState(paths.statePath);
16438
16800
  const prevActive = prevState?.active ?? null;
16439
- const displaced = { agents: modelsToStackAgents(await readAgentModels(paths.agentsDir)) };
16801
+ const displaced = { agents: await captureAgents(paths) };
16440
16802
  const historyId = await appendHistory(
16441
16803
  paths.historyDir,
16442
16804
  prevActive ?? "(none)",
@@ -16454,7 +16816,20 @@ async function applyStack(paths, name, options = {}) {
16454
16816
  version: 1,
16455
16817
  active: name,
16456
16818
  previousActive: prevActive,
16457
- lastSwitchedAt: (/* @__PURE__ */ new Date()).toISOString()
16819
+ lastSwitchedAt: (/* @__PURE__ */ new Date()).toISOString(),
16820
+ fallbackAgents: Object.fromEntries(
16821
+ Object.entries(target.agents).filter(([, entry]) => entry.fallbacks?.length).map(([agent, entry]) => {
16822
+ const variant = "variant" in entry ? entry.variant : displaced.agents[agent]?.variant;
16823
+ return [
16824
+ agent,
16825
+ {
16826
+ model: entry.model,
16827
+ variant: typeof variant === "string" ? variant : void 0,
16828
+ fallbacks: entry.fallbacks
16829
+ }
16830
+ ];
16831
+ })
16832
+ )
16458
16833
  });
16459
16834
  await trimHistory(paths.historyDir).catch(() => {
16460
16835
  });
@@ -16466,14 +16841,40 @@ async function applyStack(paths, name, options = {}) {
16466
16841
  restartRequired: true
16467
16842
  };
16468
16843
  }
16469
- function modelsToStackAgents(models) {
16844
+ function entryOptions(entry) {
16845
+ const rec = entry;
16846
+ const out = {};
16847
+ for (const [k, v] of Object.entries(rec)) {
16848
+ if (k === "model" || k === "fallbacks" || RESERVED_AGENT_KEYS.has(k)) continue;
16849
+ out[k] = v;
16850
+ }
16851
+ return out;
16852
+ }
16853
+ function entriesToStackAgents(entries) {
16470
16854
  const out = {};
16471
- for (const k of Object.keys(models).sort()) {
16472
- const model = models[k];
16473
- if (model !== void 0) out[k] = { model };
16855
+ for (const name of Object.keys(entries).sort()) {
16856
+ const entry = entries[name];
16857
+ if (!entry) continue;
16858
+ const { model, options } = entry;
16859
+ const stackEntry = { model };
16860
+ for (const [k, v] of Object.entries(options)) {
16861
+ if (k !== "fallbacks") stackEntry[k] = v;
16862
+ }
16863
+ out[name] = stackEntry;
16474
16864
  }
16475
16865
  return out;
16476
16866
  }
16867
+ async function captureAgents(paths) {
16868
+ const agents = entriesToStackAgents(await readAgentEntries(paths.agentsDir));
16869
+ const state = await readState(paths.statePath);
16870
+ for (const [name, entry] of Object.entries(agents)) {
16871
+ const routing = state?.fallbackAgents?.[name];
16872
+ if (routing && routing.model === entry.model && (routing.variant ?? void 0) === (entry.variant ?? void 0)) {
16873
+ entry.fallbacks = routing.fallbacks;
16874
+ }
16875
+ }
16876
+ return agents;
16877
+ }
16477
16878
  async function back(paths, n = 1, options = {}) {
16478
16879
  if (n < 1) throw new UserError("`back -n` must be at least 1.");
16479
16880
  const state = await readState(paths.statePath);
@@ -16507,8 +16908,7 @@ async function captureStack(paths, name, options = {}) {
16507
16908
  if (existsSync6(dest) && !options.force) {
16508
16909
  throw new UserError(`Stack "${name}" already exists. Use --force to overwrite.`);
16509
16910
  }
16510
- const models = await readAgentModels(paths.agentsDir);
16511
- const agents = modelsToStackAgents(models);
16911
+ const agents = await captureAgents(paths);
16512
16912
  if (Object.keys(agents).length === 0) {
16513
16913
  throw new UserError(
16514
16914
  `No agent .md files with a frontmatter \`model:\` line found in ${paths.agentsDir}.`
@@ -16551,7 +16951,7 @@ async function exportStack(paths, name, toFile) {
16551
16951
  }
16552
16952
 
16553
16953
  // src/version.ts
16554
- var VERSION = "1.0.6";
16954
+ var VERSION = "1.1.0";
16555
16955
 
16556
16956
  // src/cli.ts
16557
16957
  var log = (...args) => console.log(...args);
@@ -16706,11 +17106,7 @@ async function cmdValidate(paths, name, opts) {
16706
17106
  targets.push({
16707
17107
  name: "(current frontmatter)",
16708
17108
  load: async () => {
16709
- const models = await readAgentModels(paths.agentsDir);
16710
- const agents = Object.fromEntries(
16711
- Object.entries(models).map(([k, model]) => [k, { model }])
16712
- );
16713
- return { agents };
17109
+ return { agents: await captureAgents(paths) };
16714
17110
  }
16715
17111
  });
16716
17112
  } else if (opts.all) {