@dylanrussell/agent-router 1.0.6 → 1.0.7

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/tui.js CHANGED
@@ -14746,8 +14746,300 @@ import path7 from "path";
14746
14746
  import { existsSync as existsSync2 } from "fs";
14747
14747
  import { readFile as readFile2, readdir } from "fs/promises";
14748
14748
  import path4 from "path";
14749
+
14750
+ // src/core/yaml-lite.ts
14751
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
14752
+ "null",
14753
+ "true",
14754
+ "false",
14755
+ "yes",
14756
+ "no",
14757
+ "on",
14758
+ "off",
14759
+ "~",
14760
+ // YAML 1.1 nulls
14761
+ "Null",
14762
+ "NULL",
14763
+ "True",
14764
+ "False",
14765
+ "Yes",
14766
+ "No",
14767
+ "On",
14768
+ "Off"
14769
+ ]);
14770
+ var BARE_STRING_RE = /^[A-Za-z0-9._\-\/+@$%^&()~]+$/;
14771
+ function needsQuoting(s) {
14772
+ if (s === "") return true;
14773
+ if (RESERVED_WORDS.has(s)) return true;
14774
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(s)) return true;
14775
+ if (/^0x[0-9A-Fa-f]+$/.test(s)) return true;
14776
+ if (/^[+-]?\.(inf|Inf|INF|nan|NaN|NAN)$/.test(s)) return true;
14777
+ if (!BARE_STRING_RE.test(s)) return true;
14778
+ return false;
14779
+ }
14780
+ function serializeOptionValue(value) {
14781
+ if (value === null || value === void 0) return "null";
14782
+ if (typeof value === "boolean") return value ? "true" : "false";
14783
+ if (typeof value === "number") {
14784
+ if (!Number.isFinite(value)) return String(value);
14785
+ return JSON.stringify(value);
14786
+ }
14787
+ if (typeof value === "bigint") return String(value);
14788
+ if (typeof value === "string") return needsQuoting(value) ? JSON.stringify(value) : value;
14789
+ return JSON.stringify(value);
14790
+ }
14791
+ function stripComment(line) {
14792
+ let inSingle = false;
14793
+ let inDouble = false;
14794
+ for (let i = 0; i < line.length; i++) {
14795
+ const ch = line[i];
14796
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
14797
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
14798
+ else if (ch === "#" && !inSingle && !inDouble) {
14799
+ if (i === 0 || /\s/.test(line[i - 1] ?? "")) return line.slice(0, i);
14800
+ }
14801
+ }
14802
+ return line;
14803
+ }
14804
+ function tokenize(block) {
14805
+ const out = [];
14806
+ for (const raw of block.split(/\r?\n/)) {
14807
+ if (raw.trim() === "") continue;
14808
+ const indentMatch = /^( *)/.exec(raw);
14809
+ const indent = indentMatch?.[1]?.length ?? 0;
14810
+ const content = stripComment(raw.slice(indent));
14811
+ if (content.trim() === "") continue;
14812
+ out.push({ indent, text: content.trimEnd(), raw });
14813
+ }
14814
+ return out;
14815
+ }
14816
+ function coerceScalar(raw) {
14817
+ const v = raw.trim();
14818
+ if (v === "" || v === "null" || v === "~" || v === "Null" || v === "NULL") return null;
14819
+ if (v === "true" || v === "True" || v === "TRUE") return true;
14820
+ if (v === "false" || v === "False" || v === "FALSE") return false;
14821
+ if (v === "yes" || v === "Yes" || v === "YES") return true;
14822
+ if (v === "no" || v === "No" || v === "NO") return false;
14823
+ if (v === "on" || v === "On" || v === "ON") return true;
14824
+ if (v === "off" || v === "Off" || v === "OFF") return false;
14825
+ if (/^[+-]?\d+$/.test(v)) return Number.parseInt(v, 10);
14826
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(v)) return Number.parseFloat(v);
14827
+ if (/^0x[0-9A-Fa-f]+$/.test(v)) return Number.parseInt(v, 16);
14828
+ return v;
14829
+ }
14830
+ function parseQuoted(s) {
14831
+ const quote = s[0] ?? "";
14832
+ if (quote !== '"' && quote !== "'") return null;
14833
+ if (quote === '"') {
14834
+ let out2 = "";
14835
+ for (let i = 1; i < s.length; i++) {
14836
+ const ch = s[i] ?? "";
14837
+ if (ch === "\\" && i + 1 < s.length) {
14838
+ const next = s[i + 1] ?? "";
14839
+ const map2 = {
14840
+ n: "\n",
14841
+ t: " ",
14842
+ r: "\r",
14843
+ '"': '"',
14844
+ "\\": "\\",
14845
+ "/": "/",
14846
+ b: "\b",
14847
+ f: "\f"
14848
+ };
14849
+ out2 += map2[next] ?? next;
14850
+ i++;
14851
+ } else if (ch === '"') {
14852
+ return { value: out2, rest: s.slice(i + 1) };
14853
+ } else {
14854
+ out2 += ch;
14855
+ }
14856
+ }
14857
+ return null;
14858
+ }
14859
+ let out = "";
14860
+ for (let i = 1; i < s.length; i++) {
14861
+ const ch = s[i] ?? "";
14862
+ if (ch === "'") {
14863
+ if (s[i + 1] === "'") {
14864
+ out += "'";
14865
+ i++;
14866
+ } else {
14867
+ return { value: out, rest: s.slice(i + 1) };
14868
+ }
14869
+ } else {
14870
+ out += ch;
14871
+ }
14872
+ }
14873
+ return null;
14874
+ }
14875
+ function splitFlow(raw) {
14876
+ const parts = [];
14877
+ let depth = 0;
14878
+ let inSingle = false;
14879
+ let inDouble = false;
14880
+ let start = 0;
14881
+ for (let i = 0; i < raw.length; i++) {
14882
+ const ch = raw[i];
14883
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
14884
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
14885
+ else if (!inSingle && !inDouble) {
14886
+ if (ch === "{" || ch === "[") depth++;
14887
+ else if (ch === "}" || ch === "]") depth--;
14888
+ else if (ch === "," && depth === 0) {
14889
+ parts.push(raw.slice(start, i));
14890
+ start = i + 1;
14891
+ }
14892
+ }
14893
+ }
14894
+ parts.push(raw.slice(start));
14895
+ return parts;
14896
+ }
14897
+ function parseFlowValue(raw) {
14898
+ const s = raw.trim();
14899
+ if (s === "") return null;
14900
+ const first = s[0] ?? "";
14901
+ if (first === "{") {
14902
+ const inner = s.slice(1, s.length - 1);
14903
+ const obj = {};
14904
+ for (const part of splitFlow(inner)) {
14905
+ const t = part.trim();
14906
+ if (t === "") continue;
14907
+ const colon = findColon(t);
14908
+ if (colon < 0) {
14909
+ obj[t] = null;
14910
+ } else {
14911
+ const k = t.slice(0, colon).trim();
14912
+ const v = t.slice(colon + 1).trim();
14913
+ obj[stripKeyQuotes(k)] = parseFlowValue(v);
14914
+ }
14915
+ }
14916
+ return obj;
14917
+ }
14918
+ if (first === "[") {
14919
+ const inner = s.slice(1, s.length - 1);
14920
+ return splitFlow(inner).filter((p) => p.trim() !== "").map((p) => parseFlowValue(p));
14921
+ }
14922
+ if (first === '"' || first === "'") {
14923
+ const q = parseQuoted(s);
14924
+ return q ? q.value : s;
14925
+ }
14926
+ return coerceScalar(s);
14927
+ }
14928
+ function findColon(s) {
14929
+ let inSingle = false;
14930
+ let inDouble = false;
14931
+ let depth = 0;
14932
+ for (let i = 0; i < s.length; i++) {
14933
+ const ch = s[i];
14934
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
14935
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
14936
+ else if (!inSingle && !inDouble) {
14937
+ if (ch === "{" || ch === "[") depth++;
14938
+ else if (ch === "}" || ch === "]") depth--;
14939
+ else if (ch === ":" && depth === 0) return i;
14940
+ }
14941
+ }
14942
+ return -1;
14943
+ }
14944
+ function stripKeyQuotes(k) {
14945
+ if (k.length >= 2 && (k[0] === '"' && k.at(-1) === '"' || k[0] === "'" && k.at(-1) === "'")) {
14946
+ return k.slice(1, -1);
14947
+ }
14948
+ return k;
14949
+ }
14950
+ function parseMapping(lines, startIdx, indent) {
14951
+ const out = {};
14952
+ let i = startIdx;
14953
+ while (i < lines.length) {
14954
+ const line = lines[i];
14955
+ if (!line) break;
14956
+ if (line.indent < indent) break;
14957
+ if (line.indent > indent) {
14958
+ i++;
14959
+ continue;
14960
+ }
14961
+ const colon = findColon(line.text);
14962
+ if (colon < 0) {
14963
+ i++;
14964
+ continue;
14965
+ }
14966
+ const key = stripKeyQuotes(line.text.slice(0, colon).trim());
14967
+ if (key === "") {
14968
+ i++;
14969
+ continue;
14970
+ }
14971
+ const rest = line.text.slice(colon + 1).trim();
14972
+ if (rest !== "") {
14973
+ out[key] = parseFlowValue(rest);
14974
+ i++;
14975
+ } else {
14976
+ const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
14977
+ let j = i + 1;
14978
+ while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
14979
+ if (j === i + 1) {
14980
+ out[key] = null;
14981
+ i++;
14982
+ } else {
14983
+ const childBlock = lines.slice(i + 1, j);
14984
+ const firstChild = childBlock[0];
14985
+ if (firstChild?.text.trimStart().startsWith("- ")) {
14986
+ out[key] = parseSequence(childBlock, childIndent);
14987
+ } else {
14988
+ out[key] = parseMapping(childBlock, 0, childIndent).value;
14989
+ }
14990
+ i = j;
14991
+ }
14992
+ }
14993
+ }
14994
+ return { value: out, next: i };
14995
+ }
14996
+ function parseSequence(lines, indent) {
14997
+ const out = [];
14998
+ let i = 0;
14999
+ while (i < lines.length) {
15000
+ const line = lines[i];
15001
+ if (!line) break;
15002
+ if (line.indent < indent) break;
15003
+ const t = line.text.trimStart();
15004
+ if (!t.startsWith("- ")) {
15005
+ i++;
15006
+ continue;
15007
+ }
15008
+ const item = t.slice(2).trim();
15009
+ if (item === "") {
15010
+ const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
15011
+ let j = i + 1;
15012
+ while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
15013
+ out.push(parseMapping(lines.slice(i + 1, j), 0, childIndent).value);
15014
+ i = j;
15015
+ } else {
15016
+ out.push(parseFlowValue(item));
15017
+ i++;
15018
+ }
15019
+ }
15020
+ return out;
15021
+ }
15022
+ function parseFrontmatterBlock(block) {
15023
+ const lines = tokenize(block);
15024
+ if (lines.length === 0) return null;
15025
+ const { value } = parseMapping(lines, 0, 0);
15026
+ return value;
15027
+ }
15028
+
15029
+ // src/core/frontmatter.ts
14749
15030
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
14750
15031
  var MODEL_LINE_RE = /^model:[ \t]*(.*)$/m;
15032
+ var RESERVED_AGENT_KEYS = /* @__PURE__ */ new Set([
15033
+ "name",
15034
+ "mode",
15035
+ "description",
15036
+ "permission",
15037
+ "color",
15038
+ "tools",
15039
+ "prompt",
15040
+ "steps",
15041
+ "maxSteps"
15042
+ ]);
14751
15043
  function cleanModelValue(raw) {
14752
15044
  let v = raw;
14753
15045
  const hash2 = v.search(/[ \t]#/);
@@ -14776,6 +15068,55 @@ function setFrontmatterModel(content, model) {
14776
15068
  const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
14777
15069
  return nextFm + content.slice(fm[0].length);
14778
15070
  }
15071
+ function getFrontmatterOptions(content) {
15072
+ const fm = FRONTMATTER_RE.exec(content);
15073
+ if (!fm?.[1]) return {};
15074
+ const parsed = parseFrontmatterBlock(fm[1]);
15075
+ if (!parsed) return {};
15076
+ const out = {};
15077
+ for (const [k, v] of Object.entries(parsed)) {
15078
+ if (k === "model" || RESERVED_AGENT_KEYS.has(k)) continue;
15079
+ out[k] = v;
15080
+ }
15081
+ return out;
15082
+ }
15083
+ function setFrontmatterOptions(content, options) {
15084
+ const fm = FRONTMATTER_RE.exec(content);
15085
+ if (!fm?.[1]) throw new Error("no frontmatter block");
15086
+ const block = fm[1];
15087
+ const keys = Object.keys(options);
15088
+ if (keys.length === 0) return content;
15089
+ const eol = block.includes("\r\n") ? "\r\n" : "\n";
15090
+ const lines = block.split(/\r?\n/);
15091
+ const handled = /* @__PURE__ */ new Set();
15092
+ for (let i = 0; i < lines.length; i++) {
15093
+ const m = /^([A-Za-z0-9_-]+):[ \t]*(.*)$/.exec(lines[i] ?? "");
15094
+ if (!m) continue;
15095
+ const key = m[1] ?? "";
15096
+ if (!(key in options)) continue;
15097
+ handled.add(key);
15098
+ let end = i + 1;
15099
+ while (end < lines.length && /^[ \t]+/.test(lines[end] ?? "")) end++;
15100
+ const value = options[key];
15101
+ if (value === null || value === void 0) {
15102
+ lines.splice(i, end - i);
15103
+ i--;
15104
+ } else {
15105
+ lines.splice(i, end - i, `${key}: ${serializeOptionValue(value)}`);
15106
+ }
15107
+ }
15108
+ const appended = [];
15109
+ for (const [key, value] of Object.entries(options)) {
15110
+ if (handled.has(key)) continue;
15111
+ if (value === null || value === void 0) continue;
15112
+ appended.push(`${key}: ${serializeOptionValue(value)}`);
15113
+ }
15114
+ const nextBlock = appended.length > 0 ? lines.join(eol) + eol + appended.join(eol) : lines.join(eol);
15115
+ if (nextBlock === block) return content;
15116
+ const blockStart = fm[0].indexOf(block);
15117
+ const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
15118
+ return nextFm + content.slice(fm[0].length);
15119
+ }
14779
15120
  function agentFilePath(agentsDir, name) {
14780
15121
  return path4.join(agentsDir, `${name}.md`);
14781
15122
  }
@@ -14789,7 +15130,7 @@ async function listAgentFiles(agentsDir) {
14789
15130
  }
14790
15131
  return names.filter((n) => n.endsWith(".md")).map((n) => n.slice(0, -".md".length)).sort();
14791
15132
  }
14792
- async function readAgentModels(agentsDir) {
15133
+ async function readAgentEntries(agentsDir) {
14793
15134
  const out = {};
14794
15135
  for (const name of await listAgentFiles(agentsDir)) {
14795
15136
  const filePath = agentFilePath(agentsDir, name);
@@ -14800,7 +15141,8 @@ async function readAgentModels(agentsDir) {
14800
15141
  throw new IOError(`Failed to read ${filePath}: ${cause.message}`, cause);
14801
15142
  }
14802
15143
  const model = getFrontmatterModel(content);
14803
- if (model !== null) out[name] = model;
15144
+ if (model === null) continue;
15145
+ out[name] = { model, options: getFrontmatterOptions(content) };
14804
15146
  }
14805
15147
  return out;
14806
15148
  }
@@ -14819,7 +15161,7 @@ async function readAgentFileStrict(agentsDir, name) {
14819
15161
  if (model === null) {
14820
15162
  throw new AgentFileError(name, filePath, "no frontmatter `model:` line to rewrite");
14821
15163
  }
14822
- return { filePath, content, model };
15164
+ return { filePath, content, model, options: getFrontmatterOptions(content) };
14823
15165
  }
14824
15166
 
14825
15167
  // src/core/history.ts
@@ -15033,15 +15375,14 @@ async function applyStack(paths, name, options = {}) {
15033
15375
  const pending = [];
15034
15376
  for (const [agent, entry] of Object.entries(target.agents)) {
15035
15377
  const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);
15036
- pending.push({
15037
- agent,
15038
- filePath,
15039
- next: model === entry.model ? null : setFrontmatterModel(content, entry.model)
15040
- });
15378
+ const wantOptions = entryOptions(entry);
15379
+ const afterOptions = setFrontmatterOptions(content, wantOptions);
15380
+ const nextContent = entry.model === model ? afterOptions : setFrontmatterModel(afterOptions, entry.model);
15381
+ pending.push({ agent, filePath, next: nextContent === content ? null : nextContent });
15041
15382
  }
15042
15383
  const prevState = await readState(paths.statePath);
15043
15384
  const prevActive = prevState?.active ?? null;
15044
- const displaced = { agents: modelsToStackAgents(await readAgentModels(paths.agentsDir)) };
15385
+ const displaced = { agents: entriesToStackAgents(await readAgentEntries(paths.agentsDir)) };
15045
15386
  const historyId = await appendHistory(
15046
15387
  paths.historyDir,
15047
15388
  prevActive ?? "(none)",
@@ -15071,11 +15412,24 @@ async function applyStack(paths, name, options = {}) {
15071
15412
  restartRequired: true
15072
15413
  };
15073
15414
  }
15074
- function modelsToStackAgents(models) {
15415
+ function entryOptions(entry) {
15416
+ const rec = entry;
15417
+ const out = {};
15418
+ for (const [k, v] of Object.entries(rec)) {
15419
+ if (k === "model" || RESERVED_AGENT_KEYS.has(k)) continue;
15420
+ out[k] = v;
15421
+ }
15422
+ return out;
15423
+ }
15424
+ function entriesToStackAgents(entries) {
15075
15425
  const out = {};
15076
- for (const k of Object.keys(models).sort()) {
15077
- const model = models[k];
15078
- if (model !== void 0) out[k] = { model };
15426
+ for (const name of Object.keys(entries).sort()) {
15427
+ const entry = entries[name];
15428
+ if (!entry) continue;
15429
+ const { model, options } = entry;
15430
+ const stackEntry = { model };
15431
+ for (const [k, v] of Object.entries(options)) stackEntry[k] = v;
15432
+ out[name] = stackEntry;
15079
15433
  }
15080
15434
  return out;
15081
15435
  }