@oriro/orirocli 0.3.5 → 0.3.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.
Files changed (2) hide show
  1. package/dist/cli.js +258 -52
  2. package/package.json +4 -2
package/dist/cli.js CHANGED
@@ -4570,8 +4570,13 @@ var armed = false;
4570
4570
  function armPostureGate() {
4571
4571
  armed = true;
4572
4572
  }
4573
+ function bypassPosture(depthEnv) {
4574
+ const d = Number(depthEnv);
4575
+ return Number.isFinite(d) && d > 0;
4576
+ }
4573
4577
  function registerPostureGate(pi) {
4574
4578
  pi.on("tool_call", async (event, ctx) => {
4579
+ if (bypassPosture(process.env.ORIRO_AGENT_DEPTH)) return void 0;
4575
4580
  const d = decideTool({ toolName: event.toolName, guardianBlocked: false });
4576
4581
  if (d.decision === "block") {
4577
4582
  return {
@@ -6724,6 +6729,136 @@ async function handleUndo(session) {
6724
6729
  }
6725
6730
  }
6726
6731
 
6732
+ // src/agents/worktree.ts
6733
+ import { execFile } from "child_process";
6734
+ import { promisify } from "util";
6735
+ import { join as join27, basename as basename2 } from "path";
6736
+ var run = promisify(execFile);
6737
+ var MAX_FAN = 4;
6738
+ function parseAgentsSlash(line) {
6739
+ const m = /^\/agents(?:\s+(\S[\s\S]*))?$/i.exec(line.trim());
6740
+ if (!m) return void 0;
6741
+ const rest = m[1]?.trim();
6742
+ if (!rest) return { cmd: "help" };
6743
+ const nx = /^(\d+)x\s+(\S[\s\S]*)$/i.exec(rest);
6744
+ if (nx) {
6745
+ const n = Math.min(Math.max(Number(nx[1]), 1), MAX_FAN);
6746
+ return { cmd: "fan", tasks: Array.from({ length: n }, () => nx[2].trim()) };
6747
+ }
6748
+ const tasks = rest.split("|").map((s) => s.trim()).filter(Boolean).slice(0, MAX_FAN);
6749
+ return tasks.length ? { cmd: "fan", tasks } : { cmd: "help" };
6750
+ }
6751
+ function fanStamp(now) {
6752
+ const p = (n, w = 2) => String(n).padStart(w, "0");
6753
+ return `${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
6754
+ }
6755
+ function fanBranch(stamp, i) {
6756
+ return `oriro/agents/${stamp}-a${i + 1}`;
6757
+ }
6758
+ function fanDir(repoRoot, stamp, i) {
6759
+ return join27(oriroDir(), "worktrees", `${basename2(repoRoot)}-${stamp}-a${i + 1}`);
6760
+ }
6761
+ async function git(cwd, ...args) {
6762
+ try {
6763
+ const { stdout: stdout12 } = await run("git", ["-C", cwd, ...args], { windowsHide: true });
6764
+ return { ok: true, out: stdout12.trim() };
6765
+ } catch (e) {
6766
+ return { ok: false, out: e instanceof Error ? e.message : String(e) };
6767
+ }
6768
+ }
6769
+ async function gitRoot(cwd) {
6770
+ const r = await git(cwd, "rev-parse", "--show-toplevel");
6771
+ return r.ok && r.out ? r.out : void 0;
6772
+ }
6773
+ async function addWorktree(root, dir, branch) {
6774
+ const r = await git(root, "worktree", "add", "-b", branch, dir);
6775
+ return r.ok ? void 0 : r.out;
6776
+ }
6777
+ async function changedFiles(dir) {
6778
+ const r = await git(dir, "status", "--short");
6779
+ return r.ok && r.out ? r.out.split("\n").map((s) => s.trim()).filter(Boolean) : [];
6780
+ }
6781
+ async function removeWorktree(root, dir, branch, force = false) {
6782
+ await git(root, "worktree", "remove", ...force ? ["--force"] : [], dir);
6783
+ if (branch) await git(root, "branch", "-D", branch);
6784
+ }
6785
+ var SNIPPET = 400;
6786
+ function formatFanReport(reports) {
6787
+ const lines = [];
6788
+ for (const r of reports) {
6789
+ lines.push(` \u2692 ${r.role} ${r.ok ? "\u2713" : "\u2717"} \u2014 ${r.task.length > 70 ? `${r.task.slice(0, 70)}\u2026` : r.task}`);
6790
+ const snip = r.output.length > SNIPPET ? `${r.output.slice(0, SNIPPET)}\u2026` : r.output;
6791
+ if (snip) lines.push(...snip.split("\n").map((l) => ` ${l}`));
6792
+ if (r.dir && r.branch && r.changes?.length) {
6793
+ lines.push(` \u270E ${r.changes.length} file${r.changes.length === 1 ? "" : "s"} changed on ${r.branch}`);
6794
+ lines.push(` review: cd "${r.dir}" \xB7 keep: commit there, then \`git merge ${r.branch}\` here`);
6795
+ } else if (r.changes && r.changes.length === 0) {
6796
+ lines.push(" (no file changes \u2014 worktree cleaned up)");
6797
+ }
6798
+ }
6799
+ const kept = reports.filter((r) => r.dir).length;
6800
+ lines.push(` \u2692 fan-out done: ${reports.filter((r) => r.ok).length}/${reports.length} ok${kept ? ` \xB7 ${kept} worktree${kept === 1 ? "" : "s"} kept for review` : ""}`);
6801
+ return lines;
6802
+ }
6803
+
6804
+ // src/agents/fanout.ts
6805
+ var CONCURRENCY = 2;
6806
+ function defFor(role, task) {
6807
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6808
+ return { name: `fan-${role}`, task, createdAt: now, updatedAt: now };
6809
+ }
6810
+ async function runFanout(tasks, cwd) {
6811
+ const capped = tasks.slice(0, MAX_FAN);
6812
+ const root = await gitRoot(cwd);
6813
+ const stamp = fanStamp(/* @__PURE__ */ new Date());
6814
+ const prevDepth = process.env.ORIRO_AGENT_DEPTH;
6815
+ process.env.ORIRO_AGENT_DEPTH = String((Number(prevDepth) || 0) + 1);
6816
+ let reports;
6817
+ try {
6818
+ reports = await runPool(capped.map((task, i) => ({ task, i })), CONCURRENCY, async ({ task, i }) => {
6819
+ const role = `a${i + 1}`;
6820
+ if (!root) {
6821
+ const r2 = await runAgent2(defFor(role, task), { cwd });
6822
+ return { role, task, ok: r2.ok, output: r2.output };
6823
+ }
6824
+ const dir = fanDir(root, stamp, i);
6825
+ const branch = fanBranch(stamp, i);
6826
+ const err = await addWorktree(root, dir, branch);
6827
+ if (err) return { role, task, ok: false, output: `could not create worktree: ${err}` };
6828
+ const r = await runAgent2(defFor(role, task), { cwd: dir });
6829
+ const changes = await changedFiles(dir);
6830
+ if (changes.length === 0) {
6831
+ await removeWorktree(root, dir, branch);
6832
+ return { role, task, ok: r.ok, output: r.output, changes: [] };
6833
+ }
6834
+ return { role, task, ok: r.ok, output: r.output, dir, branch, changes };
6835
+ });
6836
+ } finally {
6837
+ if (prevDepth === void 0) delete process.env.ORIRO_AGENT_DEPTH;
6838
+ else process.env.ORIRO_AGENT_DEPTH = prevDepth;
6839
+ }
6840
+ const lines = formatFanReport(reports);
6841
+ if (!root) lines.unshift(" \u2692 not a git repo \u2014 agents ran in the SAME directory (no worktree isolation)");
6842
+ return lines;
6843
+ }
6844
+
6845
+ // src/repl-ui/slash-agents.ts
6846
+ function isAgentsSlash(slash) {
6847
+ return parseAgentsSlash(slash) !== void 0;
6848
+ }
6849
+ async function handleAgents(line) {
6850
+ const p = parseAgentsSlash(line);
6851
+ if (!p || p.cmd === "help") {
6852
+ return [
6853
+ ` ${accent("/agents")} ${dim("\u2014 parallel sub-agents in isolated git worktrees (results merged here)")}`,
6854
+ ` ${accent("/agents 3x <task>")} ${dim("three agents race the same task")}`,
6855
+ ` ${accent("/agents <task A> | <task B>")} ${dim(`different tasks in parallel (max ${MAX_FAN}; '|' separates tasks)`)}`,
6856
+ ` ${dim("each agent gets its own worktree + branch; clean ones are removed, changed ones kept for review")}`
6857
+ ];
6858
+ }
6859
+ return runFanout(p.tasks, process.cwd());
6860
+ }
6861
+
6727
6862
  // src/repl-ui/tui-repl.ts
6728
6863
  var editorTheme = {
6729
6864
  borderColor: (s) => dim(s),
@@ -6810,7 +6945,7 @@ async function runTuiRepl(session) {
6810
6945
  ` ${accent("/routers")} pool add\xB7rotate ${accent("/model")} <id\u2026> switch ${accent("/usage")} health ${accent("/trace")} tool+router activity ${accent("/compact")} free context`,
6811
6946
  ` ${accent("/review")} artifacts ${accent("/save")} <n> [path] ${accent("/init")} AGENTS.md ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
6812
6947
  ` ${accent("/sessions")} list saved ${accent("/undo")} rewind a turn ${dim("resume:")} ${accent("oriro -c")} / ${accent("oriro --resume <id>")}`,
6813
- ` ${accent("/plan")} <task> plan read-only ${accent("/approve")} execute it ${accent("/reject")} discard`,
6948
+ ` ${accent("/plan")} <task> plan read-only ${accent("/approve")} execute it ${accent("/reject")} discard ${accent("/agents")} parallel worktree fan-out`,
6814
6949
  ` ${dim("Shift+Tab")} posture ${dim("Alt+Shift+T")} thinking ${accent("/help")} ${accent("/exit")}`
6815
6950
  ].join("\n");
6816
6951
  chat.addChild(new Text(help, 0, 0));
@@ -6891,6 +7026,18 @@ async function runTuiRepl(session) {
6891
7026
  })();
6892
7027
  return;
6893
7028
  }
7029
+ if (isAgentsSlash(slash)) {
7030
+ editor.setText("");
7031
+ const pending = new Text(dim(" \u2692 deploying agents\u2026"), 0, 0);
7032
+ chat.addChild(pending);
7033
+ tui.requestRender();
7034
+ void (async () => {
7035
+ const lines = await handleAgents(text);
7036
+ pending.setText(lines.join("\n"));
7037
+ tui.requestRender();
7038
+ })();
7039
+ return;
7040
+ }
6894
7041
  const plan = parsePlanSlash(text);
6895
7042
  let internalPrompt;
6896
7043
  let turnText = text;
@@ -7023,7 +7170,7 @@ ${english}`;
7023
7170
  // src/voice/mic.ts
7024
7171
  import { spawn as spawn3 } from "child_process";
7025
7172
  import { tmpdir as tmpdir3 } from "os";
7026
- import { join as join27 } from "path";
7173
+ import { join as join28 } from "path";
7027
7174
  import { existsSync as existsSync18, statSync as statSync4 } from "fs";
7028
7175
  function recorders(outFile, seconds) {
7029
7176
  const dur = String(seconds);
@@ -7045,7 +7192,7 @@ function recorders(outFile, seconds) {
7045
7192
  ];
7046
7193
  }
7047
7194
  async function recordMic(seconds = 6) {
7048
- const outFile = join27(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
7195
+ const outFile = join28(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
7049
7196
  for (const r of recorders(outFile, seconds)) {
7050
7197
  const okFile = await new Promise((resolve3) => {
7051
7198
  const child = spawn3(r.cmd, r.args, { stdio: "ignore" });
@@ -7118,6 +7265,7 @@ function replHelp() {
7118
7265
  ${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} activity ${accent("/compact")} free context ${accent("/undo")} rewind a turn
7119
7266
  ${dim("Continuity")} ${accent("/sessions")} list saved sessions ${dim("resume:")} ${accent("oriro -c")} ${dim("or")} ${accent("oriro --resume <id>")}
7120
7267
  ${dim("Plan loop")} ${accent("/plan")} <task> read-only plan ${accent("/approve")} execute it ${accent("/reject")} discard
7268
+ ${dim("Fan-out")} ${accent("/agents")} <A> | <B> parallel sub-agents in isolated git worktrees
7121
7269
  ${dim("Artifacts")} ${accent("/review")} code/SVG from the last reply ${accent("/save")} <n> [path] write one
7122
7270
  ${dim("Project")} ${accent("/init")} write a starter AGENTS.md ORIRO reads each session
7123
7271
  ${dim("Capabilities")} ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")} speak a turn
@@ -7217,6 +7365,10 @@ async function runReadlineRepl(session) {
7217
7365
  stdout7.write(handleArtifactSlash(line).join("\n") + "\n");
7218
7366
  continue;
7219
7367
  }
7368
+ if (isAgentsSlash(slash)) {
7369
+ stdout7.write((await handleAgents(line)).join("\n") + "\n");
7370
+ continue;
7371
+ }
7220
7372
  const plan = parsePlanSlash(line);
7221
7373
  let internalPrompt;
7222
7374
  let turnText = line;
@@ -7375,9 +7527,12 @@ async function confirmDestructive(what, opts = {}) {
7375
7527
  }
7376
7528
  }
7377
7529
 
7530
+ // src/commands/output.ts
7531
+ import jmespath from "jmespath";
7532
+
7378
7533
  // src/config/store.ts
7379
7534
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
7380
- import { join as join28 } from "path";
7535
+ import { join as join29 } from "path";
7381
7536
  var KEYS = {
7382
7537
  output: {
7383
7538
  desc: "default output format for list commands: text | json | csv",
@@ -7399,7 +7554,7 @@ function validateConfig(key, value) {
7399
7554
  return KEYS[key].validate?.(value) ?? null;
7400
7555
  }
7401
7556
  function file4() {
7402
- return join28(oriroDir(), "config.json");
7557
+ return join29(oriroDir(), "config.json");
7403
7558
  }
7404
7559
  var cache = null;
7405
7560
  function readAll() {
@@ -7437,40 +7592,63 @@ function configUnset(key) {
7437
7592
  // src/commands/output.ts
7438
7593
  function parseFormat(o) {
7439
7594
  const f = (o ?? configGet("output") ?? "text").toLowerCase();
7440
- if (f === "json" || f === "csv" || f === "text") return f;
7441
- throw new Error(`invalid --output '${o}'. Use: text | json | csv`);
7595
+ if (f === "json" || f === "csv" || f === "text" || f === "md") return f;
7596
+ throw new Error(`invalid --output '${o}'. Use: text | json | csv | md`);
7442
7597
  }
7598
+ var LIGHTWEIGHT_QUERY = /^[\w.-]+(=[^:]*)?(:[\w.-]+)?$/;
7443
7599
  function applyQuery(rows, query) {
7444
7600
  if (!query) return rows;
7445
- const [filterPart, selectField] = query.split(":", 2);
7446
- let out = rows;
7447
- const fp = filterPart ?? "";
7448
- if (fp.includes("=")) {
7449
- const [field2, value] = fp.split("=", 2);
7450
- out = rows.filter((r) => String(r[field2] ?? "") === value);
7451
- } else if (fp && !selectField) {
7452
- return rows.map((r) => r[fp]);
7453
- }
7454
- if (selectField) return out.map((r) => r[selectField]);
7455
- return out;
7601
+ if (LIGHTWEIGHT_QUERY.test(query.trim())) {
7602
+ const [filterPart, selectField] = query.trim().split(":", 2);
7603
+ let out = rows;
7604
+ const fp = filterPart ?? "";
7605
+ if (fp.includes("=")) {
7606
+ const [field2, value] = fp.split("=", 2);
7607
+ out = rows.filter((r) => String(r[field2] ?? "") === value);
7608
+ } else if (fp && !selectField) {
7609
+ return rows.map((r) => r[fp]);
7610
+ }
7611
+ if (selectField) return out.map((r) => r[selectField]);
7612
+ return out;
7613
+ }
7614
+ try {
7615
+ return jmespath.search(rows, query);
7616
+ } catch (e) {
7617
+ throw new Error(`invalid --query '${query}' \u2014 not lightweight (field / field=value[:select]) nor valid JMESPath: ${e instanceof Error ? e.message : String(e)}`);
7618
+ }
7456
7619
  }
7457
7620
  function csvCell(v) {
7458
7621
  const s = v === null || v === void 0 ? "" : String(v);
7459
7622
  return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
7460
7623
  }
7624
+ function mdCell(v) {
7625
+ const s = v === null || v === void 0 ? "" : String(v);
7626
+ return s.replace(/\|/g, "\\|").replace(/\n/g, " ");
7627
+ }
7461
7628
  function renderList2(rows, opts = {}) {
7462
7629
  const fmt = parseFormat(opts.output);
7463
- const queried = applyQuery(rows, opts.query);
7464
- if (fmt === "json") return JSON.stringify(queried, null, 2);
7465
- if (!Array.isArray(queried) || queried.length === 0) return "";
7630
+ const raw = applyQuery(rows, opts.query);
7631
+ if (fmt === "json") return JSON.stringify(raw, null, 2);
7632
+ const queried = Array.isArray(raw) ? raw : raw === void 0 || raw === null ? [] : [raw];
7633
+ if (queried.length === 0) return "";
7466
7634
  const first = queried[0];
7467
7635
  const scalar = typeof first !== "object" || first === null;
7468
- if (scalar) return queried.map((v) => fmt === "csv" ? csvCell(v) : String(v)).join("\n");
7636
+ if (scalar) {
7637
+ if (fmt === "md") return queried.map((v) => `- ${mdCell(v)}`).join("\n");
7638
+ return queried.map((v) => fmt === "csv" ? csvCell(v) : String(v)).join("\n");
7639
+ }
7469
7640
  const objs = queried;
7470
7641
  const cols = opts.columns ?? [...new Set(objs.flatMap((r) => Object.keys(r)))];
7471
7642
  if (fmt === "csv") {
7472
7643
  return [cols.map(csvCell).join(","), ...objs.map((r) => cols.map((c) => csvCell(r[c])).join(","))].join("\n");
7473
7644
  }
7645
+ if (fmt === "md") {
7646
+ return [
7647
+ `| ${cols.map(mdCell).join(" | ")} |`,
7648
+ `| ${cols.map(() => "---").join(" | ")} |`,
7649
+ ...objs.map((r) => `| ${cols.map((c) => mdCell(r[c])).join(" | ")} |`)
7650
+ ].join("\n");
7651
+ }
7474
7652
  const widths = cols.map((c) => Math.max(c.length, ...objs.map((r) => String(r[c] ?? "").length)));
7475
7653
  const line = (cells) => cells.map((s, i) => s.padEnd(widths[i] ?? 0)).join(" ").trimEnd();
7476
7654
  return [line(cols), ...objs.map((r) => line(cols.map((c) => String(r[c] ?? ""))))].join("\n");
@@ -7480,12 +7658,12 @@ function isMachineOutput(opts) {
7480
7658
  }
7481
7659
  function outputError(opts) {
7482
7660
  const f = (opts.output ?? configGet("output") ?? "text").toLowerCase();
7483
- return f === "json" || f === "csv" || f === "text" ? null : `invalid --output '${opts.output}' \u2014 use text | json | csv`;
7661
+ return f === "json" || f === "csv" || f === "text" || f === "md" ? null : `invalid --output '${opts.output}' \u2014 use text | json | csv | md`;
7484
7662
  }
7485
7663
 
7486
7664
  // src/commands/sessions.ts
7487
7665
  function registerSessionsCommand(program2) {
7488
- program2.command("sessions").description("list your saved chat sessions (resume with `oriro -c` or `oriro --resume <id>`)").option("-o, --output <fmt>", "output format: text (default) | json | csv").option("-q, --query <expr>", "filter/select: 'field', 'field=value', or 'field=value:selectField'").action(async (opts) => {
7666
+ program2.command("sessions").description("list your saved chat sessions (resume with `oriro -c` or `oriro --resume <id>`)").option("-o, --output <fmt>", "output format: text (default) | json | csv | md").option("-q, --query <expr>", "filter/select: 'field', 'field=value[:selectField]', or any JMESPath").action(async (opts) => {
7489
7667
  const oerr = outputError(opts);
7490
7668
  if (oerr) die(oerr);
7491
7669
  const infos = await listSessions();
@@ -7504,10 +7682,37 @@ function registerSessionsCommand(program2) {
7504
7682
  });
7505
7683
  }
7506
7684
 
7685
+ // src/commands/project.ts
7686
+ function registerProjectCommands(program2) {
7687
+ program2.command("init").description("write a starter AGENTS.md for this project (same as the in-chat /init)").option("--force", "overwrite an existing AGENTS.md").action((opts) => {
7688
+ process.stdout.write(handleInit(`/init${opts.force ? " --force" : ""}`).join("\n") + "\n");
7689
+ });
7690
+ program2.command("compact [focus...]").description("summarize + free a saved session's history (default: most recent here; same as /compact)").option("--resume <id>", "compact a specific saved session (id or unique prefix)").action(async (focus, opts) => {
7691
+ let session;
7692
+ let sessionNote;
7693
+ try {
7694
+ ({ session, sessionNote } = await assembleOriroSession({
7695
+ resume: opts.resume ? { resumeId: opts.resume } : { continue: true }
7696
+ }));
7697
+ } catch (e) {
7698
+ die(e instanceof Error ? e.message : String(e));
7699
+ return;
7700
+ }
7701
+ if (sessionNote) process.stdout.write(` ${dim(sessionNote)}
7702
+ `);
7703
+ const lines = await handleCompact(session, `/compact${focus.length ? ` ${focus.join(" ")}` : ""}`);
7704
+ process.stdout.write(lines.join("\n") + "\n");
7705
+ try {
7706
+ session.dispose();
7707
+ } catch {
7708
+ }
7709
+ });
7710
+ }
7711
+
7507
7712
  // src/commands/routers.ts
7508
7713
  function registerRoutersCommand(program2) {
7509
7714
  const routers = program2.command("routers").description("manage the free-router pool the model runs on");
7510
- routers.command("list").description("list the router catalog and the active pool").option("-o, --output <fmt>", "output format: text (default) | json | csv").option("-q, --query <expr>", "filter/select: 'field', 'field=value', or 'field=value:selectField'").action((opts) => {
7715
+ routers.command("list").description("list the router catalog and the active pool").option("-o, --output <fmt>", "output format: text (default) | json | csv | md").option("-q, --query <expr>", "filter/select: 'field', 'field=value[:selectField]', or any JMESPath").action((opts) => {
7511
7716
  const oerr = outputError(opts);
7512
7717
  if (oerr) die(oerr);
7513
7718
  const pool = new Set(resolvePool().map((p) => p.id));
@@ -7839,7 +8044,7 @@ function parsePairs(s) {
7839
8044
  // src/commands/connectors.ts
7840
8045
  function registerConnectorsCommand(program2) {
7841
8046
  const connectors = program2.command("connectors").description("MCP connectors \u2014 add external tools/services (inert until used)");
7842
- connectors.command("list [category]").description("list the connector catalog (optionally filtered by category)").option("-o, --output <fmt>", "output format: text (default) | json | csv").option("-q, --query <expr>", "filter/select: 'field', 'field=value', or 'field=value:selectField'").action((category, opts) => {
8047
+ connectors.command("list [category]").description("list the connector catalog (optionally filtered by category)").option("-o, --output <fmt>", "output format: text (default) | json | csv | md").option("-q, --query <expr>", "filter/select: 'field', 'field=value[:selectField]', or any JMESPath").action((category, opts) => {
7843
8048
  const oerr = outputError(opts);
7844
8049
  if (oerr) die(oerr);
7845
8050
  if (category && !connectorCategories().includes(category)) {
@@ -8002,9 +8207,9 @@ function registerConnectorsCommand(program2) {
8002
8207
 
8003
8208
  // src/channels/config.ts
8004
8209
  import { readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
8005
- import { join as join29 } from "path";
8210
+ import { join as join30 } from "path";
8006
8211
  function file5() {
8007
- return join29(oriroDir(), "channels.json");
8212
+ return join30(oriroDir(), "channels.json");
8008
8213
  }
8009
8214
  function readChannels() {
8010
8215
  try {
@@ -8017,10 +8222,10 @@ function readChannels() {
8017
8222
  function saveChannel(cfg) {
8018
8223
  const all = readChannels().filter((c) => c.kind !== cfg.kind);
8019
8224
  all.push(cfg);
8020
- writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
8225
+ writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
8021
8226
  }
8022
8227
  function removeChannel(kind) {
8023
- writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
8228
+ writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
8024
8229
  }
8025
8230
 
8026
8231
  // src/channels/telegram.ts
@@ -8137,9 +8342,9 @@ async function startDiscord(token) {
8137
8342
  }
8138
8343
 
8139
8344
  // src/channels/whatsapp.ts
8140
- import { join as join30 } from "path";
8345
+ import { join as join31 } from "path";
8141
8346
  function whatsappAuthDir() {
8142
- return join30(oriroDir(), "whatsapp-auth");
8347
+ return join31(oriroDir(), "whatsapp-auth");
8143
8348
  }
8144
8349
  async function startWhatsApp() {
8145
8350
  let baileys;
@@ -8258,10 +8463,10 @@ function registerChannelsCommand(program2) {
8258
8463
 
8259
8464
  // src/commands/skills.ts
8260
8465
  import { existsSync as existsSync21, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
8261
- import { resolve as resolve2, join as join31, basename as basename2, dirname as dirname4 } from "path";
8466
+ import { resolve as resolve2, join as join32, basename as basename3, dirname as dirname4 } from "path";
8262
8467
  function registerSkillsCommand(program2) {
8263
8468
  const skills = program2.command("skills").description("the ORIRO skill library \u2014 bundled + your own");
8264
- skills.command("list").description("show CORE / TAIL skill counts (use --all to list names)").option("-a, --all", "list every skill name").option("-o, --output <fmt>", "output format: text (default) | json | csv").option("-q, --query <expr>", "filter/select: 'field', 'field=value', or 'field=value:selectField'").action(async (opts) => {
8469
+ skills.command("list").description("show CORE / TAIL skill counts (use --all to list names)").option("-a, --all", "list every skill name").option("-o, --output <fmt>", "output format: text (default) | json | csv | md").option("-q, --query <expr>", "filter/select: 'field', 'field=value[:selectField]', or any JMESPath").action(async (opts) => {
8265
8470
  const oerr = outputError(opts);
8266
8471
  if (oerr) die(oerr);
8267
8472
  const s = await loadOriroSkills();
@@ -8295,22 +8500,22 @@ function registerSkillsCommand(program2) {
8295
8500
  mkdirSync17(dest, { recursive: true });
8296
8501
  const st = statSync5(src);
8297
8502
  if (st.isDirectory()) {
8298
- if (!existsSync21(join31(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
8299
- const name = basename2(src);
8300
- cpSync(src, join31(dest, name), { recursive: true });
8301
- ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
8302
- } else if (basename2(src).toLowerCase() === "skill.md") {
8303
- const name = basename2(dirname4(src)) || "custom-skill";
8304
- mkdirSync17(join31(dest, name), { recursive: true });
8305
- cpSync(src, join31(dest, name, "SKILL.md"));
8306
- ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
8503
+ if (!existsSync21(join32(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
8504
+ const name = basename3(src);
8505
+ cpSync(src, join32(dest, name), { recursive: true });
8506
+ ok(`added skill ${accent(name)} \u2192 ${join32(dest, name)}`);
8507
+ } else if (basename3(src).toLowerCase() === "skill.md") {
8508
+ const name = basename3(dirname4(src)) || "custom-skill";
8509
+ mkdirSync17(join32(dest, name), { recursive: true });
8510
+ cpSync(src, join32(dest, name, "SKILL.md"));
8511
+ ok(`added skill ${accent(name)} \u2192 ${join32(dest, name)}`);
8307
8512
  } else {
8308
8513
  die("expected a folder containing SKILL.md, or a SKILL.md file");
8309
8514
  }
8310
8515
  info("It loads on next launch \u2014 and is available in chat via /skill.");
8311
8516
  });
8312
8517
  skills.command("remove <name>").description("remove a skill you added").option("-f, --force", "skip the confirmation prompt").action(async (name, opts) => {
8313
- const target = join31(userSkillsDir(), name);
8518
+ const target = join32(userSkillsDir(), name);
8314
8519
  if (!existsSync21(target)) {
8315
8520
  info(`'${name}' is not a user-added skill \u2014 nothing to remove`);
8316
8521
  return;
@@ -8631,7 +8836,7 @@ function registerAgentsCommand(program2) {
8631
8836
  info(`make one: ${accent('oriro agents make <name> --task "\u2026" [--router <id>] [--schedule 1h]')}`);
8632
8837
  info(`then: ${accent("oriro agents run <name>")} ${dim("\xB7 or")} ${accent("oriro agents tick")} ${dim("for scheduled ones")}`);
8633
8838
  });
8634
- agents.command("list").description("list your saved agents").option("-o, --output <fmt>", "output format: text (default) | json | csv").option("-q, --query <expr>", "filter/select: 'field', 'field=value', or 'field=value:selectField'").action((opts) => {
8839
+ agents.command("list").description("list your saved agents").option("-o, --output <fmt>", "output format: text (default) | json | csv | md").option("-q, --query <expr>", "filter/select: 'field', 'field=value[:selectField]', or any JMESPath").action((opts) => {
8635
8840
  const oerr = outputError(opts);
8636
8841
  if (oerr) die(oerr);
8637
8842
  const all = listAgents();
@@ -8899,7 +9104,7 @@ function registerConfigCommand(program2) {
8899
9104
 
8900
9105
  // src/commands/setup.ts
8901
9106
  import { rmSync as rmSync5 } from "fs";
8902
- import { join as join32 } from "path";
9107
+ import { join as join33 } from "path";
8903
9108
  import { stdin as stdin12, stdout as stdout11 } from "process";
8904
9109
  var MARKERS = [
8905
9110
  "language.json",
@@ -8907,14 +9112,14 @@ var MARKERS = [
8907
9112
  "skills-onboarded.json",
8908
9113
  "connectors-onboarded.json",
8909
9114
  "models-onboarded.json",
8910
- join32("routers", "onboarded.json")
9115
+ join33("routers", "onboarded.json")
8911
9116
  ];
8912
9117
  function registerSetupCommand(program2) {
8913
9118
  program2.command("setup").description("run the guided setup wizard (language \xB7 routers \xB7 connectors \xB7 skills \xB7 avatar)").option("--reset", "clear your settled choices and re-ask every step").action(async (opts) => {
8914
9119
  if (opts.reset) {
8915
9120
  for (const m of MARKERS) {
8916
9121
  try {
8917
- rmSync5(join32(oriroDir(), m), { force: true });
9122
+ rmSync5(join33(oriroDir(), m), { force: true });
8918
9123
  } catch {
8919
9124
  }
8920
9125
  }
@@ -8932,7 +9137,7 @@ function registerSetupCommand(program2) {
8932
9137
 
8933
9138
  // src/commands/import.ts
8934
9139
  import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync4, statSync as statSync6, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
8935
- import { join as join33, basename as basename3 } from "path";
9140
+ import { join as join34, basename as basename4 } from "path";
8936
9141
  function registerImportCommand(program2) {
8937
9142
  const imp = program2.command("import").description("migrate from another CLI (MCP servers, skills)");
8938
9143
  imp.command("mcp <file>").description("import MCP servers from a Claude-compatible mcp.json (Guardian-vetted)").action((file6) => {
@@ -8989,11 +9194,11 @@ function registerImportCommand(program2) {
8989
9194
  const dest = userSkillsDir();
8990
9195
  mkdirSync18(dest, { recursive: true });
8991
9196
  heading("Import skills");
8992
- const sources = existsSync22(join33(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join33(dir, e)).filter((p) => statSync6(p).isDirectory() && existsSync22(join33(p, "SKILL.md")));
9197
+ const sources = existsSync22(join34(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join34(dir, e)).filter((p) => statSync6(p).isDirectory() && existsSync22(join34(p, "SKILL.md")));
8993
9198
  let n = 0;
8994
9199
  for (const src of sources) {
8995
- cpSync2(src, join33(dest, basename3(src)), { recursive: true });
8996
- process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename3(src))}
9200
+ cpSync2(src, join34(dest, basename4(src)), { recursive: true });
9201
+ process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename4(src))}
8997
9202
  `);
8998
9203
  n++;
8999
9204
  }
@@ -9089,6 +9294,7 @@ program.name("oriro").description("ORIRO \u2014 a free, on-device-friendly termi
9089
9294
  await runRepl({ resume });
9090
9295
  });
9091
9296
  registerSessionsCommand(program);
9297
+ registerProjectCommands(program);
9092
9298
  registerRoutersCommand(program);
9093
9299
  registerScribeCommand(program);
9094
9300
  registerConnectorsCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oriro/orirocli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "ORIRO — a free, on-device-friendly terminal AI agent. Built on the Pi agent harness (used as a library).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "dev": "tsx src/cli.ts",
24
24
  "build": "tsup",
25
25
  "typecheck": "tsc --noEmit",
26
- "test:unit": "tsx scripts/test-tool-sanitize.ts && tsx scripts/test-guardian.ts && tsx scripts/test-scribe.ts && tsx scripts/test-race.ts && tsx scripts/test-weights.ts && tsx scripts/test-output.ts && tsx scripts/test-connectors.ts && tsx scripts/test-artifacts.ts && tsx scripts/test-project-md.ts && tsx scripts/test-compact.ts && tsx scripts/test-init.ts && tsx scripts/test-sessions.ts && tsx scripts/test-permission.ts && tsx scripts/test-plan-mode.ts",
26
+ "test:unit": "tsx scripts/test-tool-sanitize.ts && tsx scripts/test-guardian.ts && tsx scripts/test-scribe.ts && tsx scripts/test-race.ts && tsx scripts/test-weights.ts && tsx scripts/test-output.ts && tsx scripts/test-connectors.ts && tsx scripts/test-artifacts.ts && tsx scripts/test-project-md.ts && tsx scripts/test-compact.ts && tsx scripts/test-init.ts && tsx scripts/test-sessions.ts && tsx scripts/test-permission.ts && tsx scripts/test-plan-mode.ts && tsx scripts/test-agents-fanout.ts",
27
27
  "smoke": "npm run build && node scripts/smoke.mjs",
28
28
  "prepublishOnly": "npm run build && npm run test:unit && node scripts/smoke.mjs && node scripts/prepublish-check.mjs",
29
29
  "start": "node dist/cli.js"
@@ -37,6 +37,7 @@
37
37
  "commander": "^12.1.0",
38
38
  "discord.js": "^14.26.4",
39
39
  "grammy": "^1.44.0",
40
+ "jmespath": "^0.16.0",
40
41
  "typebox": "^1.1.38"
41
42
  },
42
43
  "peerDependencies": {
@@ -52,6 +53,7 @@
52
53
  }
53
54
  },
54
55
  "devDependencies": {
56
+ "@types/jmespath": "^0.15.2",
55
57
  "@types/node": "^22",
56
58
  "tsup": "^8",
57
59
  "tsx": "^4",