@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.
- package/dist/cli.js +258 -52
- 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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
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
|
|
7464
|
-
if (fmt === "json") return JSON.stringify(
|
|
7465
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8210
|
+
import { join as join30 } from "path";
|
|
8006
8211
|
function file5() {
|
|
8007
|
-
return
|
|
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(
|
|
8225
|
+
writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
|
|
8021
8226
|
}
|
|
8022
8227
|
function removeChannel(kind) {
|
|
8023
|
-
writeFileSync21(
|
|
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
|
|
8345
|
+
import { join as join31 } from "path";
|
|
8141
8346
|
function whatsappAuthDir() {
|
|
8142
|
-
return
|
|
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
|
|
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
|
|
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(
|
|
8299
|
-
const name =
|
|
8300
|
-
cpSync(src,
|
|
8301
|
-
ok(`added skill ${accent(name)} \u2192 ${
|
|
8302
|
-
} else if (
|
|
8303
|
-
const name =
|
|
8304
|
-
mkdirSync17(
|
|
8305
|
-
cpSync(src,
|
|
8306
|
-
ok(`added skill ${accent(name)} \u2192 ${
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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,
|
|
8996
|
-
process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(
|
|
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.
|
|
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",
|