@oriro/orirocli 0.3.3 → 0.3.5
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 +418 -105
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -3382,19 +3382,77 @@ import {
|
|
|
3382
3382
|
createAgentSession as createAgentSession2,
|
|
3383
3383
|
AuthStorage as AuthStorage2,
|
|
3384
3384
|
ModelRegistry as ModelRegistry2,
|
|
3385
|
-
SessionManager as SessionManager2,
|
|
3386
3385
|
SettingsManager,
|
|
3387
3386
|
DefaultResourceLoader,
|
|
3388
3387
|
getAgentDir
|
|
3389
3388
|
} from "@earendil-works/pi-coding-agent";
|
|
3390
3389
|
|
|
3390
|
+
// src/sessions/store.ts
|
|
3391
|
+
import { join as join16 } from "path";
|
|
3392
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
3393
|
+
function sessionsDir() {
|
|
3394
|
+
return join16(oriroDir(), "sessions");
|
|
3395
|
+
}
|
|
3396
|
+
async function findByIdPrefix(cwd, idish, verb) {
|
|
3397
|
+
const infos = await SessionManager.list(cwd, sessionsDir());
|
|
3398
|
+
const exact = infos.find((s) => s.id === idish);
|
|
3399
|
+
if (exact) return exact;
|
|
3400
|
+
const pref = infos.filter((s) => s.id.startsWith(idish));
|
|
3401
|
+
if (pref.length === 1) return pref[0];
|
|
3402
|
+
if (pref.length > 1) throw new Error(`'${idish}' matches ${pref.length} sessions \u2014 use a longer id (oriro sessions)`);
|
|
3403
|
+
throw new Error(`no session '${idish}' to ${verb} here \u2014 see: oriro sessions`);
|
|
3404
|
+
}
|
|
3405
|
+
async function resolveSessionManager(cwd, opts = {}) {
|
|
3406
|
+
const dir = sessionsDir();
|
|
3407
|
+
if (opts.ephemeral) return { sm: SessionManager.inMemory(cwd), note: "ephemeral \u2014 this session is NOT saved" };
|
|
3408
|
+
if (opts.continue) return { sm: SessionManager.continueRecent(cwd, dir), note: "continuing your most recent session" };
|
|
3409
|
+
if (opts.resumeId) {
|
|
3410
|
+
const hit = await findByIdPrefix(cwd, opts.resumeId, "resume");
|
|
3411
|
+
return { sm: SessionManager.open(hit.path, dir), note: `resumed ${hit.id.slice(0, 8)} (${hit.messageCount} msgs)` };
|
|
3412
|
+
}
|
|
3413
|
+
if (opts.forkId) {
|
|
3414
|
+
const hit = await findByIdPrefix(cwd, opts.forkId, "fork");
|
|
3415
|
+
return { sm: SessionManager.forkFrom(hit.path, cwd, dir), note: `forked a new session from ${hit.id.slice(0, 8)}` };
|
|
3416
|
+
}
|
|
3417
|
+
return { sm: SessionManager.create(cwd, dir), note: "new session (saved locally \u2014 resume with `oriro -c`)" };
|
|
3418
|
+
}
|
|
3419
|
+
async function listSessions(cwd = process.cwd()) {
|
|
3420
|
+
const infos = await SessionManager.list(cwd, sessionsDir());
|
|
3421
|
+
return infos.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
3422
|
+
}
|
|
3423
|
+
function shortWhen(d) {
|
|
3424
|
+
const mon = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][d.getMonth()] ?? "";
|
|
3425
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
3426
|
+
return `${mon} ${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
3427
|
+
}
|
|
3428
|
+
function formatSessionList(infos) {
|
|
3429
|
+
if (!infos.length) return [dim(" no saved sessions yet \u2014 they're created as you chat. Resume the last with `oriro -c`.")];
|
|
3430
|
+
const lines = [];
|
|
3431
|
+
for (const s of infos) {
|
|
3432
|
+
const id = s.id.slice(0, 8);
|
|
3433
|
+
const first = (s.firstMessage ?? s.name ?? "(empty)").replace(/\s+/g, " ").trim().slice(0, 56);
|
|
3434
|
+
lines.push(` ${accent(id)} ${dim(shortWhen(s.modified).padEnd(12))} ${dim(`${String(s.messageCount).padStart(3)} msg`)} ${first}`);
|
|
3435
|
+
}
|
|
3436
|
+
lines.push(dim(` ${infos.length} session${infos.length === 1 ? "" : "s"} \xB7 resume: `) + accent("oriro --resume <id>") + dim(" \xB7 continue last: ") + accent("oriro -c"));
|
|
3437
|
+
return lines;
|
|
3438
|
+
}
|
|
3439
|
+
function sessionRows(infos) {
|
|
3440
|
+
return infos.map((s) => ({
|
|
3441
|
+
id: s.id,
|
|
3442
|
+
messages: s.messageCount,
|
|
3443
|
+
modified: s.modified.toISOString(),
|
|
3444
|
+
cwd: s.cwd,
|
|
3445
|
+
first: (s.firstMessage ?? "").replace(/\s+/g, " ").trim().slice(0, 80)
|
|
3446
|
+
}));
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3391
3449
|
// src/routers/mux-provider.ts
|
|
3392
3450
|
import { streamSimple as piStreamSimple2, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
3393
3451
|
import { register as registerOpenAICompletions } from "@earendil-works/pi-ai/openai-completions";
|
|
3394
3452
|
|
|
3395
3453
|
// src/routers/mux.ts
|
|
3396
3454
|
import { existsSync as existsSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
3397
|
-
import { join as
|
|
3455
|
+
import { join as join17 } from "path";
|
|
3398
3456
|
var COOLDOWN_DEFAULT_MS = 6e4;
|
|
3399
3457
|
var UNHEALTHY_AFTER = 3;
|
|
3400
3458
|
var RouterMux = class {
|
|
@@ -3464,11 +3522,11 @@ var RouterMux = class {
|
|
|
3464
3522
|
}
|
|
3465
3523
|
};
|
|
3466
3524
|
function healthStatePath(dir) {
|
|
3467
|
-
return
|
|
3525
|
+
return join17(dir, "routers", "health.json");
|
|
3468
3526
|
}
|
|
3469
3527
|
function saveMuxState(dir, stats) {
|
|
3470
3528
|
const p = healthStatePath(dir);
|
|
3471
|
-
mkdirSync9(
|
|
3529
|
+
mkdirSync9(join17(dir, "routers"), { recursive: true });
|
|
3472
3530
|
writeFileSync12(p, JSON.stringify(stats, null, 2), "utf8");
|
|
3473
3531
|
}
|
|
3474
3532
|
function loadMuxState(dir) {
|
|
@@ -3570,28 +3628,28 @@ import { Type } from "typebox";
|
|
|
3570
3628
|
|
|
3571
3629
|
// src/scribe/capture.ts
|
|
3572
3630
|
import { closeSync as closeSync2, fsyncSync as fsyncSync2, mkdirSync as mkdirSync12, openSync as openSync2, writeSync as writeSync2 } from "fs";
|
|
3573
|
-
import { join as
|
|
3631
|
+
import { join as join19 } from "path";
|
|
3574
3632
|
|
|
3575
3633
|
// src/scribe/digest.ts
|
|
3576
3634
|
import { existsSync as existsSync8, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
3577
3635
|
|
|
3578
3636
|
// src/scribe/paths.ts
|
|
3579
|
-
import { join as
|
|
3637
|
+
import { join as join18 } from "path";
|
|
3580
3638
|
function scribeDir() {
|
|
3581
3639
|
const override = process.env.ORIRO_SCRIBE_DIR?.trim();
|
|
3582
|
-
return override && override.length > 0 ? override :
|
|
3640
|
+
return override && override.length > 0 ? override : join18(CONFIG_DIR, "scribe");
|
|
3583
3641
|
}
|
|
3584
3642
|
function journalFile(date) {
|
|
3585
|
-
return
|
|
3643
|
+
return join18(scribeDir(), `${date}.md`);
|
|
3586
3644
|
}
|
|
3587
3645
|
function digestFile() {
|
|
3588
|
-
return
|
|
3646
|
+
return join18(scribeDir(), "_digest.md");
|
|
3589
3647
|
}
|
|
3590
3648
|
function timelineFile() {
|
|
3591
|
-
return
|
|
3649
|
+
return join18(scribeDir(), "_timeline.md");
|
|
3592
3650
|
}
|
|
3593
3651
|
function artifactsDir() {
|
|
3594
|
-
return
|
|
3652
|
+
return join18(scribeDir(), "artifacts");
|
|
3595
3653
|
}
|
|
3596
3654
|
|
|
3597
3655
|
// src/scribe/digest.ts
|
|
@@ -3774,7 +3832,7 @@ var INLINE_CAP = 4e3;
|
|
|
3774
3832
|
function sideFile(date, ts, kind, full) {
|
|
3775
3833
|
mkdirSync12(artifactsDir(), { recursive: true });
|
|
3776
3834
|
const name = `${date}_${ts.replace(/[:.]/g, "-")}_${kind}.md`;
|
|
3777
|
-
const p =
|
|
3835
|
+
const p = join19(artifactsDir(), name);
|
|
3778
3836
|
const fd = openSync2(p, "w");
|
|
3779
3837
|
try {
|
|
3780
3838
|
writeSync2(fd, full);
|
|
@@ -3866,12 +3924,12 @@ import {
|
|
|
3866
3924
|
writeFileSync as writeFileSync14,
|
|
3867
3925
|
writeSync as writeSync3
|
|
3868
3926
|
} from "fs";
|
|
3869
|
-
import { join as
|
|
3927
|
+
import { join as join20 } from "path";
|
|
3870
3928
|
function healthFile() {
|
|
3871
|
-
return
|
|
3929
|
+
return join20(scribeDir(), "_health.json");
|
|
3872
3930
|
}
|
|
3873
3931
|
function faultLogFile() {
|
|
3874
|
-
return
|
|
3932
|
+
return join20(scribeDir(), "_faults.log");
|
|
3875
3933
|
}
|
|
3876
3934
|
function read2() {
|
|
3877
3935
|
try {
|
|
@@ -3924,9 +3982,9 @@ import {
|
|
|
3924
3982
|
writeFileSync as writeFileSync15,
|
|
3925
3983
|
writeSync as writeSync4
|
|
3926
3984
|
} from "fs";
|
|
3927
|
-
import { join as
|
|
3985
|
+
import { join as join21 } from "path";
|
|
3928
3986
|
function walFile() {
|
|
3929
|
-
return
|
|
3987
|
+
return join21(scribeDir(), "_wal.jsonl");
|
|
3930
3988
|
}
|
|
3931
3989
|
function appendLine(obj) {
|
|
3932
3990
|
mkdirSync14(scribeDir(), { recursive: true });
|
|
@@ -4138,12 +4196,12 @@ function attachScribe(session) {
|
|
|
4138
4196
|
|
|
4139
4197
|
// src/context/project-md.ts
|
|
4140
4198
|
import { existsSync as existsSync13, readFileSync as readFileSync18, statSync as statSync2 } from "fs";
|
|
4141
|
-
import { join as
|
|
4199
|
+
import { join as join22, dirname as dirname3, parse } from "path";
|
|
4142
4200
|
var NAMES = ["AGENTS.md", "CLAUDE.md", ".oriro/ORIRO.md"];
|
|
4143
4201
|
var MAX_BYTES = 32 * 1024;
|
|
4144
4202
|
var MAX_LEVELS = 24;
|
|
4145
4203
|
function isRoot(dir) {
|
|
4146
|
-
return existsSync13(
|
|
4204
|
+
return existsSync13(join22(dir, ".git")) || existsSync13(join22(dir, ".oriro"));
|
|
4147
4205
|
}
|
|
4148
4206
|
function discoverProjectInstructions(cwd) {
|
|
4149
4207
|
const chain = [];
|
|
@@ -4151,7 +4209,7 @@ function discoverProjectInstructions(cwd) {
|
|
|
4151
4209
|
const rootOfDrive = parse(cwd).root;
|
|
4152
4210
|
for (let i = 0; i < MAX_LEVELS; i++) {
|
|
4153
4211
|
for (const name of NAMES) {
|
|
4154
|
-
const p =
|
|
4212
|
+
const p = join22(dir, name);
|
|
4155
4213
|
try {
|
|
4156
4214
|
if (existsSync13(p) && statSync2(p).isFile()) {
|
|
4157
4215
|
let text = readFileSync18(p, "utf8");
|
|
@@ -4454,6 +4512,90 @@ ${extra}` } : ctx;
|
|
|
4454
4512
|
return registry.find(MUX_PROVIDER, MUX_MODEL);
|
|
4455
4513
|
}
|
|
4456
4514
|
|
|
4515
|
+
// src/repl-ui/permission.ts
|
|
4516
|
+
var MODES = ["manual", "accept_edits", "auto", "plan"];
|
|
4517
|
+
var MODE_META = {
|
|
4518
|
+
manual: { label: "Manual", indicator: "\u25CF" },
|
|
4519
|
+
accept_edits: { label: "Accept Edits", indicator: "\u270E" },
|
|
4520
|
+
auto: { label: "Auto", indicator: "\u23F5\u23F5" },
|
|
4521
|
+
plan: { label: "Plan", indicator: "\u25A2" }
|
|
4522
|
+
};
|
|
4523
|
+
var current2 = "manual";
|
|
4524
|
+
function getMode() {
|
|
4525
|
+
return current2;
|
|
4526
|
+
}
|
|
4527
|
+
function setMode(m) {
|
|
4528
|
+
current2 = m;
|
|
4529
|
+
}
|
|
4530
|
+
function cycleMode() {
|
|
4531
|
+
const i = MODES.indexOf(current2);
|
|
4532
|
+
current2 = MODES[(i + 1) % MODES.length];
|
|
4533
|
+
return current2;
|
|
4534
|
+
}
|
|
4535
|
+
var thinking = false;
|
|
4536
|
+
function getThinking() {
|
|
4537
|
+
return thinking;
|
|
4538
|
+
}
|
|
4539
|
+
function toggleThinking() {
|
|
4540
|
+
thinking = !thinking;
|
|
4541
|
+
return thinking;
|
|
4542
|
+
}
|
|
4543
|
+
var THINKING_PRIMER = "Think step by step and plan your approach before acting. Reason carefully and check your work.";
|
|
4544
|
+
function classifyTool(toolName) {
|
|
4545
|
+
const n = toolName.toLowerCase();
|
|
4546
|
+
if (/(^|_)(read|ls|grep|find|glob|inspect|view|cat|list)/.test(n)) return "read";
|
|
4547
|
+
if (/(^|_)(edit|write|apply|patch|create|update|str_replace|multiedit)/.test(n)) return "edit";
|
|
4548
|
+
if (/(^|_)(bash|shell|exec|run|terminal|command|sh)/.test(n)) return "exec";
|
|
4549
|
+
return "other";
|
|
4550
|
+
}
|
|
4551
|
+
function decideTool(opts) {
|
|
4552
|
+
const mode = opts.mode ?? current2;
|
|
4553
|
+
if (opts.guardianBlocked) return { decision: "block", reason: "ORIRO Guardian" };
|
|
4554
|
+
const kind = classifyTool(opts.toolName);
|
|
4555
|
+
if (mode === "plan") {
|
|
4556
|
+
return kind === "read" ? { decision: "allow" } : { decision: "block", reason: "Plan mode is read-only" };
|
|
4557
|
+
}
|
|
4558
|
+
if (mode === "manual") {
|
|
4559
|
+
return kind === "read" ? { decision: "allow" } : { decision: "ask" };
|
|
4560
|
+
}
|
|
4561
|
+
if (mode === "accept_edits") {
|
|
4562
|
+
if (kind === "read" || kind === "edit") return { decision: "allow" };
|
|
4563
|
+
return { decision: "ask" };
|
|
4564
|
+
}
|
|
4565
|
+
return { decision: "allow" };
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
// src/repl-ui/posture-gate.ts
|
|
4569
|
+
var armed = false;
|
|
4570
|
+
function armPostureGate() {
|
|
4571
|
+
armed = true;
|
|
4572
|
+
}
|
|
4573
|
+
function registerPostureGate(pi) {
|
|
4574
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
4575
|
+
const d = decideTool({ toolName: event.toolName, guardianBlocked: false });
|
|
4576
|
+
if (d.decision === "block") {
|
|
4577
|
+
return {
|
|
4578
|
+
block: true,
|
|
4579
|
+
reason: `\u25A2 ${d.reason ?? "blocked by posture"} \u2014 present the plan as text; the user will /approve to execute`
|
|
4580
|
+
};
|
|
4581
|
+
}
|
|
4582
|
+
if (d.decision === "ask" && armed) {
|
|
4583
|
+
if (!ctx.hasUI) {
|
|
4584
|
+
return { block: true, reason: `posture '${getMode()}' requires approval and no UI is available` };
|
|
4585
|
+
}
|
|
4586
|
+
const choice = await ctx.ui.select(
|
|
4587
|
+
`\u25CF Posture '${getMode()}' \u2014 approve this action?
|
|
4588
|
+
Tool: ${event.toolName}
|
|
4589
|
+
|
|
4590
|
+
(Shift+Tab cycles postures; \u23F5\u23F5 Auto stops asking)`,
|
|
4591
|
+
["Allow once", "Deny"]
|
|
4592
|
+
);
|
|
4593
|
+
return choice === "Allow once" ? void 0 : { block: true, reason: "Denied by user (posture gate)" };
|
|
4594
|
+
}
|
|
4595
|
+
return void 0;
|
|
4596
|
+
});
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4457
4599
|
// src/head/pi-tool.ts
|
|
4458
4600
|
import { Type as Type2 } from "typebox";
|
|
4459
4601
|
|
|
@@ -4765,7 +4907,7 @@ async function comparePages(opts) {
|
|
|
4765
4907
|
|
|
4766
4908
|
// src/head/run.ts
|
|
4767
4909
|
import { writeFile } from "fs/promises";
|
|
4768
|
-
import { join as
|
|
4910
|
+
import { join as join23 } from "path";
|
|
4769
4911
|
|
|
4770
4912
|
// src/head/inspection-html.ts
|
|
4771
4913
|
var PRIORITY_COLOR = {
|
|
@@ -5267,7 +5409,7 @@ async function runInspect(target, competitors, opts = {}) {
|
|
|
5267
5409
|
const report = await comparePages({ targetUrl: target, competitorUrls: competitors.length ? competitors : [target] });
|
|
5268
5410
|
const files = [];
|
|
5269
5411
|
if (opts.html) {
|
|
5270
|
-
const path =
|
|
5412
|
+
const path = join23(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(target)}-inspect.html`);
|
|
5271
5413
|
await writeFile(path, buildInspectionHtml(report), "utf8");
|
|
5272
5414
|
files.push(path);
|
|
5273
5415
|
}
|
|
@@ -5283,7 +5425,7 @@ function parseHeadTargets(text, selfOrigin) {
|
|
|
5283
5425
|
async function runUrlToCode(url, opts = {}) {
|
|
5284
5426
|
try {
|
|
5285
5427
|
const res = await urlToCode(url, headModels(), { goal: opts.goal, stack: opts.stack });
|
|
5286
|
-
const codePath =
|
|
5428
|
+
const codePath = join23(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(url)}${extForStack(opts.stack)}`);
|
|
5287
5429
|
await writeFile(codePath, res.code, "utf8");
|
|
5288
5430
|
return { summary: `Reverse-engineered ${url} into clean code (${res.code.length} chars) \u2192 ${codePath}`, files: [codePath] };
|
|
5289
5431
|
} catch (e) {
|
|
@@ -5293,7 +5435,7 @@ async function runUrlToCode(url, opts = {}) {
|
|
|
5293
5435
|
async function runUrlToSpec(url, opts = {}) {
|
|
5294
5436
|
try {
|
|
5295
5437
|
const res = await urlToSpec(url, headModels(), { goal: opts.goal });
|
|
5296
|
-
const specPath =
|
|
5438
|
+
const specPath = join23(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(url)}.spec.yaml`);
|
|
5297
5439
|
await writeFile(specPath, res.spec, "utf8");
|
|
5298
5440
|
return { summary: `Reverse-engineered ${url} into a YAML build spec \u2192 ${specPath}`, files: [specPath] };
|
|
5299
5441
|
} catch (e) {
|
|
@@ -5305,7 +5447,7 @@ async function runCapture(urls, opts = {}) {
|
|
|
5305
5447
|
const { captureScreens: captureScreens2, buildScreenshotFlowHtml: buildScreenshotFlowHtml2 } = await Promise.resolve().then(() => (init_screenshot_flow(), screenshot_flow_exports));
|
|
5306
5448
|
const caps = await captureScreens2(urls, { video: opts.video });
|
|
5307
5449
|
const html = buildScreenshotFlowHtml2([{ name: "Captured screens", captures: caps }]);
|
|
5308
|
-
const flowPath =
|
|
5450
|
+
const flowPath = join23(opts.outDir ?? process.cwd(), "oriro-head-flow.html");
|
|
5309
5451
|
await writeFile(flowPath, html, "utf8");
|
|
5310
5452
|
const ok2 = caps.filter((c) => c.ok).length;
|
|
5311
5453
|
return { summary: `Captured ${ok2}/${caps.length} full-page screenshots \u2192 ${flowPath}`, files: [flowPath] };
|
|
@@ -5326,7 +5468,7 @@ async function runVideoToCode(videoPath, opts = {}) {
|
|
|
5326
5468
|
{ videoPath, frames, mimeType: mime, goal: opts.goal, stack: opts.stack },
|
|
5327
5469
|
headVideoModels()
|
|
5328
5470
|
);
|
|
5329
|
-
const codePath =
|
|
5471
|
+
const codePath = join23(opts.outDir ?? process.cwd(), `oriro-head-video${extForStack(opts.stack)}`);
|
|
5330
5472
|
await writeFile(codePath, res.code, "utf8");
|
|
5331
5473
|
return { summary: `Watched ${videoPath} \u2192 built code (${res.code.length} chars) \u2192 ${codePath}
|
|
5332
5474
|
(experimental on the free floor \u2014 add a vision-capable router for pixel-faithful results.)`, files: [codePath] };
|
|
@@ -5419,7 +5561,7 @@ function registerHead(pi) {
|
|
|
5419
5561
|
}
|
|
5420
5562
|
|
|
5421
5563
|
// src/orchestrate.ts
|
|
5422
|
-
import { createAgentSession, AuthStorage, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
5564
|
+
import { createAgentSession, AuthStorage, ModelRegistry, SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
5423
5565
|
import { Type as Type3 } from "typebox";
|
|
5424
5566
|
var MAX_AGENTS = 8;
|
|
5425
5567
|
var MAX_CONCURRENCY = 4;
|
|
@@ -5432,7 +5574,7 @@ async function runOnce(spec) {
|
|
|
5432
5574
|
model,
|
|
5433
5575
|
authStorage,
|
|
5434
5576
|
modelRegistry,
|
|
5435
|
-
sessionManager:
|
|
5577
|
+
sessionManager: SessionManager2.inMemory(),
|
|
5436
5578
|
noTools: "all"
|
|
5437
5579
|
});
|
|
5438
5580
|
let out = "";
|
|
@@ -5509,19 +5651,19 @@ import { Type as Type4 } from "typebox";
|
|
|
5509
5651
|
|
|
5510
5652
|
// src/agents/store.ts
|
|
5511
5653
|
import { mkdirSync as mkdirSync15, readFileSync as readFileSync19, writeFileSync as writeFileSync16, readdirSync as readdirSync2, rmSync as rmSync3, existsSync as existsSync14 } from "fs";
|
|
5512
|
-
import { join as
|
|
5654
|
+
import { join as join24 } from "path";
|
|
5513
5655
|
var SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
5514
5656
|
function isValidAgentName(name) {
|
|
5515
5657
|
return SLUG.test(name);
|
|
5516
5658
|
}
|
|
5517
5659
|
function agentsDir() {
|
|
5518
|
-
return
|
|
5660
|
+
return join24(oriroDir(), "agents");
|
|
5519
5661
|
}
|
|
5520
5662
|
function agentFile(name) {
|
|
5521
|
-
return
|
|
5663
|
+
return join24(agentsDir(), `${name}.json`);
|
|
5522
5664
|
}
|
|
5523
5665
|
function stateFile() {
|
|
5524
|
-
return
|
|
5666
|
+
return join24(agentsDir(), ".state.json");
|
|
5525
5667
|
}
|
|
5526
5668
|
function listAgents() {
|
|
5527
5669
|
const dir = agentsDir();
|
|
@@ -5530,7 +5672,7 @@ function listAgents() {
|
|
|
5530
5672
|
for (const f of readdirSync2(dir)) {
|
|
5531
5673
|
if (!f.endsWith(".json") || f.startsWith(".")) continue;
|
|
5532
5674
|
try {
|
|
5533
|
-
const def = JSON.parse(readFileSync19(
|
|
5675
|
+
const def = JSON.parse(readFileSync19(join24(dir, f), "utf8"));
|
|
5534
5676
|
if (def && typeof def.name === "string" && typeof def.task === "string") out.push(def);
|
|
5535
5677
|
} catch {
|
|
5536
5678
|
}
|
|
@@ -5843,9 +5985,9 @@ function registerToolList(pi, serverName, client, tools, seen = /* @__PURE__ */
|
|
|
5843
5985
|
|
|
5844
5986
|
// src/connectors/custom.ts
|
|
5845
5987
|
import { readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
|
|
5846
|
-
import { join as
|
|
5988
|
+
import { join as join25 } from "path";
|
|
5847
5989
|
function file3() {
|
|
5848
|
-
return
|
|
5990
|
+
return join25(oriroDir(), "mcp-custom.json");
|
|
5849
5991
|
}
|
|
5850
5992
|
function readCustomServers() {
|
|
5851
5993
|
try {
|
|
@@ -5857,13 +5999,13 @@ function readCustomServers() {
|
|
|
5857
5999
|
}
|
|
5858
6000
|
function saveCustomServer(server) {
|
|
5859
6001
|
const rest = readCustomServers().filter((s) => s.name.toLowerCase() !== server.name.toLowerCase());
|
|
5860
|
-
writeFileSync17(
|
|
6002
|
+
writeFileSync17(join25(ensureOriroDir(), "mcp-custom.json"), JSON.stringify([...rest, server], null, 2), "utf8");
|
|
5861
6003
|
}
|
|
5862
6004
|
function removeCustomServer(name) {
|
|
5863
6005
|
const before = readCustomServers();
|
|
5864
6006
|
const after = before.filter((s) => s.name.toLowerCase() !== name.toLowerCase());
|
|
5865
6007
|
if (after.length === before.length) return false;
|
|
5866
|
-
writeFileSync17(
|
|
6008
|
+
writeFileSync17(join25(ensureOriroDir(), "mcp-custom.json"), JSON.stringify(after, null, 2), "utf8");
|
|
5867
6009
|
return true;
|
|
5868
6010
|
}
|
|
5869
6011
|
function trustedServerNames() {
|
|
@@ -5919,6 +6061,7 @@ async function assembleOriroSession(opts = {}) {
|
|
|
5919
6061
|
// bundled library + the user's own ~/.oriro/skills
|
|
5920
6062
|
extensionFactories: [
|
|
5921
6063
|
registerGuardian,
|
|
6064
|
+
registerPostureGate,
|
|
5922
6065
|
registerHead,
|
|
5923
6066
|
registerScribe,
|
|
5924
6067
|
registerOrchestrator,
|
|
@@ -5928,16 +6071,17 @@ async function assembleOriroSession(opts = {}) {
|
|
|
5928
6071
|
]
|
|
5929
6072
|
});
|
|
5930
6073
|
await resourceLoader.reload();
|
|
6074
|
+
const { sm, note: sessionNote } = await resolveSessionManager(cwd, opts.resume);
|
|
5931
6075
|
const { session, extensionsResult } = await createAgentSession2({
|
|
5932
6076
|
model,
|
|
5933
6077
|
authStorage,
|
|
5934
6078
|
modelRegistry,
|
|
5935
6079
|
settingsManager,
|
|
5936
|
-
sessionManager:
|
|
6080
|
+
sessionManager: sm,
|
|
5937
6081
|
resourceLoader
|
|
5938
6082
|
});
|
|
5939
6083
|
attachScribe(session);
|
|
5940
|
-
return { session, extensionsResult };
|
|
6084
|
+
return { session, extensionsResult, sessionNote };
|
|
5941
6085
|
}
|
|
5942
6086
|
|
|
5943
6087
|
// src/language/nllb-translator.ts
|
|
@@ -6106,32 +6250,37 @@ async function translateOutgoing(text) {
|
|
|
6106
6250
|
// src/repl-ui/tui-repl.ts
|
|
6107
6251
|
import { ProcessTerminal, TUI, Editor, Text, Container } from "@earendil-works/pi-tui";
|
|
6108
6252
|
|
|
6109
|
-
// src/repl-ui/
|
|
6110
|
-
var
|
|
6111
|
-
var
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
function
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
}
|
|
6126
|
-
|
|
6127
|
-
function
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6253
|
+
// src/repl-ui/plan-mode.ts
|
|
6254
|
+
var PLAN_PRIMER = "PLAN MODE \u2014 read-only. Produce a concrete implementation plan for the request below: numbered steps, the exact files to change and how, the commands to run, and the risks. Do NOT make any changes \u2014 no edits, no writes, no commands (write/exec tools are blocked in this mode). Finish with a short 'Verify' list of what will prove the work is correct after execution.";
|
|
6255
|
+
var EXECUTE_PROMPT = "APPROVED: the plan you presented above has been approved by the user. Execute it now, step by step, exactly as written \u2014 implement, run, and verify each step. Do not re-plan and do not ask for approval again; Guardian still protects against dangerous actions.";
|
|
6256
|
+
var prevMode = "manual";
|
|
6257
|
+
var ready = false;
|
|
6258
|
+
function enterPlan(from) {
|
|
6259
|
+
if (from !== "plan") prevMode = from;
|
|
6260
|
+
ready = false;
|
|
6261
|
+
}
|
|
6262
|
+
function notePlanOutput(output) {
|
|
6263
|
+
ready = output.trim().length > 0;
|
|
6264
|
+
return ready;
|
|
6265
|
+
}
|
|
6266
|
+
function approvePlan() {
|
|
6267
|
+
if (!ready) return { ok: false, reason: "no plan is waiting for approval \u2014 /plan <task> first" };
|
|
6268
|
+
ready = false;
|
|
6269
|
+
return { ok: true, restoreMode: prevMode, prompt: EXECUTE_PROMPT };
|
|
6270
|
+
}
|
|
6271
|
+
function rejectPlan() {
|
|
6272
|
+
const had = ready;
|
|
6273
|
+
ready = false;
|
|
6274
|
+
return had;
|
|
6275
|
+
}
|
|
6276
|
+
function parsePlanSlash(line) {
|
|
6277
|
+
const m = /^\/(plan|approve|reject)(?:\s+(\S[\s\S]*))?$/i.exec(line.trim());
|
|
6278
|
+
if (!m) return void 0;
|
|
6279
|
+
const cmd = m[1].toLowerCase();
|
|
6280
|
+
if (cmd === "plan") return m[2] ? { cmd: "plan", task: m[2].trim() } : { cmd: "plan" };
|
|
6281
|
+
if (cmd === "approve") return { cmd: "approve" };
|
|
6282
|
+
return { cmd: "reject" };
|
|
6133
6283
|
}
|
|
6134
|
-
var THINKING_PRIMER = "Think step by step and plan your approach before acting. Reason carefully and check your work.";
|
|
6135
6284
|
|
|
6136
6285
|
// src/repl-ui/verify-actions.ts
|
|
6137
6286
|
import { existsSync as existsSync15 } from "fs";
|
|
@@ -6405,7 +6554,7 @@ async function handleCompact(session, cmd) {
|
|
|
6405
6554
|
|
|
6406
6555
|
// src/context/init-agents.ts
|
|
6407
6556
|
import { existsSync as existsSync17, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync19 } from "fs";
|
|
6408
|
-
import { join as
|
|
6557
|
+
import { join as join26, basename } from "path";
|
|
6409
6558
|
var CODE_EXT = {
|
|
6410
6559
|
ts: "TypeScript",
|
|
6411
6560
|
tsx: "TypeScript",
|
|
@@ -6438,7 +6587,7 @@ function readJson(p) {
|
|
|
6438
6587
|
}
|
|
6439
6588
|
function detectProject(cwd) {
|
|
6440
6589
|
const facts = { name: basename(cwd) || "project", languages: [], commands: [], topDirs: [] };
|
|
6441
|
-
const pkgPath =
|
|
6590
|
+
const pkgPath = join26(cwd, "package.json");
|
|
6442
6591
|
if (existsSync17(pkgPath)) {
|
|
6443
6592
|
const pkg = readJson(pkgPath);
|
|
6444
6593
|
if (typeof pkg.name === "string" && pkg.name) facts.name = pkg.name;
|
|
@@ -6447,11 +6596,11 @@ function detectProject(cwd) {
|
|
|
6447
6596
|
for (const key of ["dev", "build", "test", "lint", "start"]) {
|
|
6448
6597
|
if (scripts[key]) facts.commands.push({ label: key, cmd: `npm run ${key}` });
|
|
6449
6598
|
}
|
|
6450
|
-
} else if (existsSync17(
|
|
6599
|
+
} else if (existsSync17(join26(cwd, "pyproject.toml")) || existsSync17(join26(cwd, "requirements.txt"))) {
|
|
6451
6600
|
if (!facts.description) facts.description = "Python project";
|
|
6452
|
-
} else if (existsSync17(
|
|
6601
|
+
} else if (existsSync17(join26(cwd, "Cargo.toml"))) {
|
|
6453
6602
|
facts.commands.push({ label: "build", cmd: "cargo build" }, { label: "test", cmd: "cargo test" });
|
|
6454
|
-
} else if (existsSync17(
|
|
6603
|
+
} else if (existsSync17(join26(cwd, "go.mod"))) {
|
|
6455
6604
|
facts.commands.push({ label: "build", cmd: "go build ./..." }, { label: "test", cmd: "go test ./..." });
|
|
6456
6605
|
}
|
|
6457
6606
|
const langCount = /* @__PURE__ */ new Map();
|
|
@@ -6466,7 +6615,7 @@ function detectProject(cwd) {
|
|
|
6466
6615
|
} catch {
|
|
6467
6616
|
}
|
|
6468
6617
|
for (const e of entries) {
|
|
6469
|
-
const full =
|
|
6618
|
+
const full = join26(cwd, e);
|
|
6470
6619
|
let isDir = false;
|
|
6471
6620
|
try {
|
|
6472
6621
|
isDir = statSync3(full).isDirectory();
|
|
@@ -6479,7 +6628,7 @@ function detectProject(cwd) {
|
|
|
6479
6628
|
try {
|
|
6480
6629
|
for (const f of readdirSync3(full)) {
|
|
6481
6630
|
try {
|
|
6482
|
-
if (statSync3(
|
|
6631
|
+
if (statSync3(join26(full, f)).isFile()) tallyExt(f);
|
|
6483
6632
|
} catch {
|
|
6484
6633
|
}
|
|
6485
6634
|
}
|
|
@@ -6513,7 +6662,7 @@ function generateAgentsMd(cwd) {
|
|
|
6513
6662
|
return lines.join("\n");
|
|
6514
6663
|
}
|
|
6515
6664
|
function writeAgentsMd(cwd = process.cwd(), force = false) {
|
|
6516
|
-
const path =
|
|
6665
|
+
const path = join26(cwd, "AGENTS.md");
|
|
6517
6666
|
const facts = detectProject(cwd);
|
|
6518
6667
|
if (existsSync17(path) && !force) return { path, created: false, facts };
|
|
6519
6668
|
writeFileSync19(path, generateAgentsMd(cwd), "utf8");
|
|
@@ -6544,6 +6693,37 @@ function handleInit(cmd, cwd = process.cwd()) {
|
|
|
6544
6693
|
return lines;
|
|
6545
6694
|
}
|
|
6546
6695
|
|
|
6696
|
+
// src/repl-ui/slash-sessions.ts
|
|
6697
|
+
function isSessionsSlash(cmd) {
|
|
6698
|
+
return /^\/sessions?(\s|$)/i.test(cmd.trim());
|
|
6699
|
+
}
|
|
6700
|
+
function isUndoSlash(cmd) {
|
|
6701
|
+
return /^\/undo(\s|$)/i.test(cmd.trim());
|
|
6702
|
+
}
|
|
6703
|
+
async function handleSessions() {
|
|
6704
|
+
try {
|
|
6705
|
+
return formatSessionList(await listSessions());
|
|
6706
|
+
} catch (e) {
|
|
6707
|
+
return [` ${fgHex(PALETTE.error, "sessions failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
async function handleUndo(session) {
|
|
6711
|
+
try {
|
|
6712
|
+
const turns2 = session.getUserMessagesForForking();
|
|
6713
|
+
if (turns2.length < 2) {
|
|
6714
|
+
return [dim(" nothing to undo \u2014 this is the first turn of the session.")];
|
|
6715
|
+
}
|
|
6716
|
+
const target = turns2[turns2.length - 2];
|
|
6717
|
+
if (!target) return [dim(" nothing to undo.")];
|
|
6718
|
+
const res = await session.navigateTree(target.entryId, { label: "undo" });
|
|
6719
|
+
if (res.cancelled) return [dim(" undo cancelled.")];
|
|
6720
|
+
const preview = target.text.replace(/\s+/g, " ").trim().slice(0, 48);
|
|
6721
|
+
return [` ${fgHex(PALETTE.success, "\u21BA undone")} ${dim("\u2014 rewound to:")} ${accent(preview || "(prev turn)")}`];
|
|
6722
|
+
} catch (e) {
|
|
6723
|
+
return [` ${fgHex(PALETTE.error, "undo failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
|
|
6547
6727
|
// src/repl-ui/tui-repl.ts
|
|
6548
6728
|
var editorTheme = {
|
|
6549
6729
|
borderColor: (s) => dim(s),
|
|
@@ -6566,6 +6746,7 @@ function footerText() {
|
|
|
6566
6746
|
return `${bar} ${think} ${dim("Shift+Tab posture \xB7 Alt+Shift+T thinking \xB7 /exit")}`;
|
|
6567
6747
|
}
|
|
6568
6748
|
async function runTuiRepl(session) {
|
|
6749
|
+
armPostureGate();
|
|
6569
6750
|
const isEnglish3 = getTerminalLanguage().code.toLowerCase().startsWith("en");
|
|
6570
6751
|
const term = new ProcessTerminal();
|
|
6571
6752
|
const tui = new TUI(term, true);
|
|
@@ -6585,7 +6766,8 @@ async function runTuiRepl(session) {
|
|
|
6585
6766
|
};
|
|
6586
6767
|
const removeListener = tui.addInputListener((data) => {
|
|
6587
6768
|
if (data === "\x1B[Z") {
|
|
6588
|
-
|
|
6769
|
+
const before = getMode();
|
|
6770
|
+
if (cycleMode() === "plan") enterPlan(before);
|
|
6589
6771
|
refreshFooter();
|
|
6590
6772
|
return { consume: true };
|
|
6591
6773
|
}
|
|
@@ -6626,7 +6808,9 @@ async function runTuiRepl(session) {
|
|
|
6626
6808
|
const help = [
|
|
6627
6809
|
" Just type to chat \u2014 ORIRO writes and runs code for you (keyless, free).",
|
|
6628
6810
|
` ${accent("/routers")} pool add\xB7rotate ${accent("/model")} <id\u2026> switch ${accent("/usage")} health ${accent("/trace")} tool+router activity ${accent("/compact")} free context`,
|
|
6629
|
-
` ${accent("/review")} artifacts
|
|
6811
|
+
` ${accent("/review")} artifacts ${accent("/save")} <n> [path] ${accent("/init")} AGENTS.md ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
|
|
6812
|
+
` ${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`,
|
|
6630
6814
|
` ${dim("Shift+Tab")} posture ${dim("Alt+Shift+T")} thinking ${accent("/help")} ${accent("/exit")}`
|
|
6631
6815
|
].join("\n");
|
|
6632
6816
|
chat.addChild(new Text(help, 0, 0));
|
|
@@ -6695,6 +6879,53 @@ async function runTuiRepl(session) {
|
|
|
6695
6879
|
tui.requestRender();
|
|
6696
6880
|
return;
|
|
6697
6881
|
}
|
|
6882
|
+
if (isSessionsSlash(slash) || isUndoSlash(slash)) {
|
|
6883
|
+
editor.setText("");
|
|
6884
|
+
const pending = new Text(dim(" \u2026"), 0, 0);
|
|
6885
|
+
chat.addChild(pending);
|
|
6886
|
+
tui.requestRender();
|
|
6887
|
+
void (async () => {
|
|
6888
|
+
const lines = isUndoSlash(slash) ? await handleUndo(session) : await handleSessions();
|
|
6889
|
+
pending.setText(lines.join("\n"));
|
|
6890
|
+
tui.requestRender();
|
|
6891
|
+
})();
|
|
6892
|
+
return;
|
|
6893
|
+
}
|
|
6894
|
+
const plan = parsePlanSlash(text);
|
|
6895
|
+
let internalPrompt;
|
|
6896
|
+
let turnText = text;
|
|
6897
|
+
if (plan) {
|
|
6898
|
+
if (plan.cmd === "reject") {
|
|
6899
|
+
const had = rejectPlan();
|
|
6900
|
+
chat.addChild(new Text(dim(had ? " \u25A2 plan discarded \u2014 refine the request (still in Plan) or Shift+Tab out" : " \u25A2 nothing to reject \u2014 no plan is waiting"), 0, 0));
|
|
6901
|
+
editor.setText("");
|
|
6902
|
+
tui.requestRender();
|
|
6903
|
+
return;
|
|
6904
|
+
}
|
|
6905
|
+
if (plan.cmd === "approve") {
|
|
6906
|
+
const r = approvePlan();
|
|
6907
|
+
if (!r.ok) {
|
|
6908
|
+
chat.addChild(new Text(dim(` \u25A2 ${r.reason}`), 0, 0));
|
|
6909
|
+
editor.setText("");
|
|
6910
|
+
tui.requestRender();
|
|
6911
|
+
return;
|
|
6912
|
+
}
|
|
6913
|
+
setMode(r.restoreMode);
|
|
6914
|
+
refreshFooter();
|
|
6915
|
+
internalPrompt = r.prompt;
|
|
6916
|
+
} else {
|
|
6917
|
+
enterPlan(getMode());
|
|
6918
|
+
setMode("plan");
|
|
6919
|
+
refreshFooter();
|
|
6920
|
+
if (!plan.task) {
|
|
6921
|
+
chat.addChild(new Text(dim(" \u25A2 Plan mode \u2014 describe the task and I'll plan it (read-only). Then ") + accent("/approve") + dim(" to execute \xB7 ") + accent("/reject") + dim(" to discard."), 0, 0));
|
|
6922
|
+
editor.setText("");
|
|
6923
|
+
tui.requestRender();
|
|
6924
|
+
return;
|
|
6925
|
+
}
|
|
6926
|
+
turnText = plan.task;
|
|
6927
|
+
}
|
|
6928
|
+
}
|
|
6698
6929
|
if (slash === "/voice") {
|
|
6699
6930
|
editor.setText("");
|
|
6700
6931
|
const status = new Text(dim(" \u{1F399} listening\u2026 (needs ffmpeg + the transformers voice peer)"), 0, 0);
|
|
@@ -6733,7 +6964,10 @@ async function runTuiRepl(session) {
|
|
|
6733
6964
|
busy = true;
|
|
6734
6965
|
bumpTurns();
|
|
6735
6966
|
void (async () => {
|
|
6736
|
-
let english = await translateIncoming(
|
|
6967
|
+
let english = internalPrompt ?? await translateIncoming(turnText);
|
|
6968
|
+
if (getMode() === "plan") english = `${PLAN_PRIMER}
|
|
6969
|
+
|
|
6970
|
+
${english}`;
|
|
6737
6971
|
if (getThinking()) english = `${THINKING_PRIMER}
|
|
6738
6972
|
|
|
6739
6973
|
${english}`;
|
|
@@ -6773,6 +7007,9 @@ ${english}`;
|
|
|
6773
7007
|
const hint = arts.length ? dim(`
|
|
6774
7008
|
\u2398 ${arts.length} artifact${arts.length === 1 ? "" : "s"} \u2014 /review to save`) : "";
|
|
6775
7009
|
streaming.setText((finalText || dim("(no response)")) + (warn ? dim(warn) : "") + hint);
|
|
7010
|
+
if (getMode() === "plan" && notePlanOutput(finalText)) {
|
|
7011
|
+
chat.addChild(new Text(dim(" \u25A2 plan ready \u2014 ") + accent("/approve") + dim(" to execute \xB7 ") + accent("/reject") + dim(" to discard"), 0, 0));
|
|
7012
|
+
}
|
|
6776
7013
|
tui.requestRender();
|
|
6777
7014
|
busy = false;
|
|
6778
7015
|
})();
|
|
@@ -6786,7 +7023,7 @@ ${english}`;
|
|
|
6786
7023
|
// src/voice/mic.ts
|
|
6787
7024
|
import { spawn as spawn3 } from "child_process";
|
|
6788
7025
|
import { tmpdir as tmpdir3 } from "os";
|
|
6789
|
-
import { join as
|
|
7026
|
+
import { join as join27 } from "path";
|
|
6790
7027
|
import { existsSync as existsSync18, statSync as statSync4 } from "fs";
|
|
6791
7028
|
function recorders(outFile, seconds) {
|
|
6792
7029
|
const dur = String(seconds);
|
|
@@ -6808,7 +7045,7 @@ function recorders(outFile, seconds) {
|
|
|
6808
7045
|
];
|
|
6809
7046
|
}
|
|
6810
7047
|
async function recordMic(seconds = 6) {
|
|
6811
|
-
const outFile =
|
|
7048
|
+
const outFile = join27(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
|
|
6812
7049
|
for (const r of recorders(outFile, seconds)) {
|
|
6813
7050
|
const okFile = await new Promise((resolve3) => {
|
|
6814
7051
|
const child = spawn3(r.cmd, r.args, { stdio: "ignore" });
|
|
@@ -6878,7 +7115,9 @@ function replHelp() {
|
|
|
6878
7115
|
${dim("Just type to chat; ORIRO writes and runs code for you (keyless, free).")}
|
|
6879
7116
|
|
|
6880
7117
|
${dim("Models & routers")} ${accent("/routers")} list\xB7add\xB7rotate the racing pool ${accent("/model")} <id\u2026> switch
|
|
6881
|
-
${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")}
|
|
7118
|
+
${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} activity ${accent("/compact")} free context ${accent("/undo")} rewind a turn
|
|
7119
|
+
${dim("Continuity")} ${accent("/sessions")} list saved sessions ${dim("resume:")} ${accent("oriro -c")} ${dim("or")} ${accent("oriro --resume <id>")}
|
|
7120
|
+
${dim("Plan loop")} ${accent("/plan")} <task> read-only plan ${accent("/approve")} execute it ${accent("/reject")} discard
|
|
6882
7121
|
${dim("Artifacts")} ${accent("/review")} code/SVG from the last reply ${accent("/save")} <n> [path] write one
|
|
6883
7122
|
${dim("Project")} ${accent("/init")} write a starter AGENTS.md ORIRO reads each session
|
|
6884
7123
|
${dim("Capabilities")} ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")} speak a turn
|
|
@@ -6888,10 +7127,12 @@ function replHelp() {
|
|
|
6888
7127
|
|
|
6889
7128
|
`;
|
|
6890
7129
|
}
|
|
6891
|
-
async function runRepl() {
|
|
7130
|
+
async function runRepl(opts = {}) {
|
|
6892
7131
|
if (isFirstRun()) await runOnboarding();
|
|
6893
7132
|
else stdout7.write(banner());
|
|
6894
|
-
const { session } = await assembleOriroSession();
|
|
7133
|
+
const { session, sessionNote } = await assembleOriroSession({ resume: opts.resume });
|
|
7134
|
+
if (sessionNote) stdout7.write(` ${dim(sessionNote)}
|
|
7135
|
+
`);
|
|
6895
7136
|
setupVoiceInput();
|
|
6896
7137
|
if (stdin6.isTTY && stdout7.isTTY) {
|
|
6897
7138
|
await runTuiRepl(session);
|
|
@@ -6964,12 +7205,52 @@ async function runReadlineRepl(session) {
|
|
|
6964
7205
|
stdout7.write(handleInit(line).join("\n") + "\n");
|
|
6965
7206
|
continue;
|
|
6966
7207
|
}
|
|
7208
|
+
if (isSessionsSlash(slash)) {
|
|
7209
|
+
stdout7.write((await handleSessions()).join("\n") + "\n");
|
|
7210
|
+
continue;
|
|
7211
|
+
}
|
|
7212
|
+
if (isUndoSlash(slash)) {
|
|
7213
|
+
stdout7.write((await handleUndo(session)).join("\n") + "\n");
|
|
7214
|
+
continue;
|
|
7215
|
+
}
|
|
6967
7216
|
if (isArtifactSlash(slash)) {
|
|
6968
7217
|
stdout7.write(handleArtifactSlash(line).join("\n") + "\n");
|
|
6969
7218
|
continue;
|
|
6970
7219
|
}
|
|
7220
|
+
const plan = parsePlanSlash(line);
|
|
7221
|
+
let internalPrompt;
|
|
7222
|
+
let turnText = line;
|
|
7223
|
+
if (plan) {
|
|
7224
|
+
if (plan.cmd === "reject") {
|
|
7225
|
+
stdout7.write(` ${dim(rejectPlan() ? "\u25A2 plan discarded \u2014 refine the request (still in Plan) or /approve a new plan later" : "\u25A2 nothing to reject \u2014 no plan is waiting")}
|
|
7226
|
+
`);
|
|
7227
|
+
continue;
|
|
7228
|
+
}
|
|
7229
|
+
if (plan.cmd === "approve") {
|
|
7230
|
+
const r = approvePlan();
|
|
7231
|
+
if (!r.ok) {
|
|
7232
|
+
stdout7.write(` ${dim(`\u25A2 ${r.reason}`)}
|
|
7233
|
+
`);
|
|
7234
|
+
continue;
|
|
7235
|
+
}
|
|
7236
|
+
setMode(r.restoreMode);
|
|
7237
|
+
internalPrompt = r.prompt;
|
|
7238
|
+
} else {
|
|
7239
|
+
enterPlan(getMode());
|
|
7240
|
+
setMode("plan");
|
|
7241
|
+
if (!plan.task) {
|
|
7242
|
+
stdout7.write(` ${dim("\u25A2 Plan mode \u2014 describe the task and I'll plan it (read-only). Then")} ${accent("/approve")} ${dim("to execute \xB7")} ${accent("/reject")} ${dim("to discard.")}
|
|
7243
|
+
`);
|
|
7244
|
+
continue;
|
|
7245
|
+
}
|
|
7246
|
+
turnText = plan.task;
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
6971
7249
|
bumpTurns();
|
|
6972
|
-
|
|
7250
|
+
let english = internalPrompt ?? await translateIncoming(turnText);
|
|
7251
|
+
if (getMode() === "plan") english = `${PLAN_PRIMER}
|
|
7252
|
+
|
|
7253
|
+
${english}`;
|
|
6973
7254
|
noteUserInput(line);
|
|
6974
7255
|
let out = "";
|
|
6975
7256
|
const unsub = session.subscribe(
|
|
@@ -6993,6 +7274,10 @@ async function runReadlineRepl(session) {
|
|
|
6993
7274
|
stdout7.write(`${shown}${phantomFileWarning(shown)}
|
|
6994
7275
|
${hint}
|
|
6995
7276
|
`);
|
|
7277
|
+
if (getMode() === "plan" && notePlanOutput(shown)) {
|
|
7278
|
+
stdout7.write(` ${dim("\u25A2 plan ready \u2014")} ${accent("/approve")} ${dim("to execute \xB7")} ${accent("/reject")} ${dim("to discard")}
|
|
7279
|
+
`);
|
|
7280
|
+
}
|
|
6996
7281
|
}
|
|
6997
7282
|
} finally {
|
|
6998
7283
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -7092,7 +7377,7 @@ async function confirmDestructive(what, opts = {}) {
|
|
|
7092
7377
|
|
|
7093
7378
|
// src/config/store.ts
|
|
7094
7379
|
import { readFileSync as readFileSync22, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
|
|
7095
|
-
import { join as
|
|
7380
|
+
import { join as join28 } from "path";
|
|
7096
7381
|
var KEYS = {
|
|
7097
7382
|
output: {
|
|
7098
7383
|
desc: "default output format for list commands: text | json | csv",
|
|
@@ -7114,7 +7399,7 @@ function validateConfig(key, value) {
|
|
|
7114
7399
|
return KEYS[key].validate?.(value) ?? null;
|
|
7115
7400
|
}
|
|
7116
7401
|
function file4() {
|
|
7117
|
-
return
|
|
7402
|
+
return join28(oriroDir(), "config.json");
|
|
7118
7403
|
}
|
|
7119
7404
|
var cache = null;
|
|
7120
7405
|
function readAll() {
|
|
@@ -7198,6 +7483,27 @@ function outputError(opts) {
|
|
|
7198
7483
|
return f === "json" || f === "csv" || f === "text" ? null : `invalid --output '${opts.output}' \u2014 use text | json | csv`;
|
|
7199
7484
|
}
|
|
7200
7485
|
|
|
7486
|
+
// src/commands/sessions.ts
|
|
7487
|
+
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) => {
|
|
7489
|
+
const oerr = outputError(opts);
|
|
7490
|
+
if (oerr) die(oerr);
|
|
7491
|
+
const infos = await listSessions();
|
|
7492
|
+
if (isMachineOutput(opts) || opts.query) {
|
|
7493
|
+
process.stdout.write(
|
|
7494
|
+
renderList2(sessionRows(infos), {
|
|
7495
|
+
output: opts.output,
|
|
7496
|
+
query: opts.query,
|
|
7497
|
+
columns: ["id", "messages", "modified", "first", "cwd"]
|
|
7498
|
+
}) + "\n"
|
|
7499
|
+
);
|
|
7500
|
+
return;
|
|
7501
|
+
}
|
|
7502
|
+
heading("Sessions");
|
|
7503
|
+
process.stdout.write(formatSessionList(infos).join("\n") + "\n");
|
|
7504
|
+
});
|
|
7505
|
+
}
|
|
7506
|
+
|
|
7201
7507
|
// src/commands/routers.ts
|
|
7202
7508
|
function registerRoutersCommand(program2) {
|
|
7203
7509
|
const routers = program2.command("routers").description("manage the free-router pool the model runs on");
|
|
@@ -7696,9 +8002,9 @@ function registerConnectorsCommand(program2) {
|
|
|
7696
8002
|
|
|
7697
8003
|
// src/channels/config.ts
|
|
7698
8004
|
import { readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
|
|
7699
|
-
import { join as
|
|
8005
|
+
import { join as join29 } from "path";
|
|
7700
8006
|
function file5() {
|
|
7701
|
-
return
|
|
8007
|
+
return join29(oriroDir(), "channels.json");
|
|
7702
8008
|
}
|
|
7703
8009
|
function readChannels() {
|
|
7704
8010
|
try {
|
|
@@ -7711,10 +8017,10 @@ function readChannels() {
|
|
|
7711
8017
|
function saveChannel(cfg) {
|
|
7712
8018
|
const all = readChannels().filter((c) => c.kind !== cfg.kind);
|
|
7713
8019
|
all.push(cfg);
|
|
7714
|
-
writeFileSync21(
|
|
8020
|
+
writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
|
|
7715
8021
|
}
|
|
7716
8022
|
function removeChannel(kind) {
|
|
7717
|
-
writeFileSync21(
|
|
8023
|
+
writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
|
|
7718
8024
|
}
|
|
7719
8025
|
|
|
7720
8026
|
// src/channels/telegram.ts
|
|
@@ -7831,9 +8137,9 @@ async function startDiscord(token) {
|
|
|
7831
8137
|
}
|
|
7832
8138
|
|
|
7833
8139
|
// src/channels/whatsapp.ts
|
|
7834
|
-
import { join as
|
|
8140
|
+
import { join as join30 } from "path";
|
|
7835
8141
|
function whatsappAuthDir() {
|
|
7836
|
-
return
|
|
8142
|
+
return join30(oriroDir(), "whatsapp-auth");
|
|
7837
8143
|
}
|
|
7838
8144
|
async function startWhatsApp() {
|
|
7839
8145
|
let baileys;
|
|
@@ -7952,7 +8258,7 @@ function registerChannelsCommand(program2) {
|
|
|
7952
8258
|
|
|
7953
8259
|
// src/commands/skills.ts
|
|
7954
8260
|
import { existsSync as existsSync21, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
|
|
7955
|
-
import { resolve as resolve2, join as
|
|
8261
|
+
import { resolve as resolve2, join as join31, basename as basename2, dirname as dirname4 } from "path";
|
|
7956
8262
|
function registerSkillsCommand(program2) {
|
|
7957
8263
|
const skills = program2.command("skills").description("the ORIRO skill library \u2014 bundled + your own");
|
|
7958
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) => {
|
|
@@ -7989,22 +8295,22 @@ function registerSkillsCommand(program2) {
|
|
|
7989
8295
|
mkdirSync17(dest, { recursive: true });
|
|
7990
8296
|
const st = statSync5(src);
|
|
7991
8297
|
if (st.isDirectory()) {
|
|
7992
|
-
if (!existsSync21(
|
|
8298
|
+
if (!existsSync21(join31(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
|
|
7993
8299
|
const name = basename2(src);
|
|
7994
|
-
cpSync(src,
|
|
7995
|
-
ok(`added skill ${accent(name)} \u2192 ${
|
|
8300
|
+
cpSync(src, join31(dest, name), { recursive: true });
|
|
8301
|
+
ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
|
|
7996
8302
|
} else if (basename2(src).toLowerCase() === "skill.md") {
|
|
7997
8303
|
const name = basename2(dirname4(src)) || "custom-skill";
|
|
7998
|
-
mkdirSync17(
|
|
7999
|
-
cpSync(src,
|
|
8000
|
-
ok(`added skill ${accent(name)} \u2192 ${
|
|
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)}`);
|
|
8001
8307
|
} else {
|
|
8002
8308
|
die("expected a folder containing SKILL.md, or a SKILL.md file");
|
|
8003
8309
|
}
|
|
8004
8310
|
info("It loads on next launch \u2014 and is available in chat via /skill.");
|
|
8005
8311
|
});
|
|
8006
8312
|
skills.command("remove <name>").description("remove a skill you added").option("-f, --force", "skip the confirmation prompt").action(async (name, opts) => {
|
|
8007
|
-
const target =
|
|
8313
|
+
const target = join31(userSkillsDir(), name);
|
|
8008
8314
|
if (!existsSync21(target)) {
|
|
8009
8315
|
info(`'${name}' is not a user-added skill \u2014 nothing to remove`);
|
|
8010
8316
|
return;
|
|
@@ -8593,7 +8899,7 @@ function registerConfigCommand(program2) {
|
|
|
8593
8899
|
|
|
8594
8900
|
// src/commands/setup.ts
|
|
8595
8901
|
import { rmSync as rmSync5 } from "fs";
|
|
8596
|
-
import { join as
|
|
8902
|
+
import { join as join32 } from "path";
|
|
8597
8903
|
import { stdin as stdin12, stdout as stdout11 } from "process";
|
|
8598
8904
|
var MARKERS = [
|
|
8599
8905
|
"language.json",
|
|
@@ -8601,14 +8907,14 @@ var MARKERS = [
|
|
|
8601
8907
|
"skills-onboarded.json",
|
|
8602
8908
|
"connectors-onboarded.json",
|
|
8603
8909
|
"models-onboarded.json",
|
|
8604
|
-
|
|
8910
|
+
join32("routers", "onboarded.json")
|
|
8605
8911
|
];
|
|
8606
8912
|
function registerSetupCommand(program2) {
|
|
8607
8913
|
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) => {
|
|
8608
8914
|
if (opts.reset) {
|
|
8609
8915
|
for (const m of MARKERS) {
|
|
8610
8916
|
try {
|
|
8611
|
-
rmSync5(
|
|
8917
|
+
rmSync5(join32(oriroDir(), m), { force: true });
|
|
8612
8918
|
} catch {
|
|
8613
8919
|
}
|
|
8614
8920
|
}
|
|
@@ -8626,7 +8932,7 @@ function registerSetupCommand(program2) {
|
|
|
8626
8932
|
|
|
8627
8933
|
// src/commands/import.ts
|
|
8628
8934
|
import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync4, statSync as statSync6, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
|
|
8629
|
-
import { join as
|
|
8935
|
+
import { join as join33, basename as basename3 } from "path";
|
|
8630
8936
|
function registerImportCommand(program2) {
|
|
8631
8937
|
const imp = program2.command("import").description("migrate from another CLI (MCP servers, skills)");
|
|
8632
8938
|
imp.command("mcp <file>").description("import MCP servers from a Claude-compatible mcp.json (Guardian-vetted)").action((file6) => {
|
|
@@ -8683,10 +8989,10 @@ function registerImportCommand(program2) {
|
|
|
8683
8989
|
const dest = userSkillsDir();
|
|
8684
8990
|
mkdirSync18(dest, { recursive: true });
|
|
8685
8991
|
heading("Import skills");
|
|
8686
|
-
const sources = existsSync22(
|
|
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")));
|
|
8687
8993
|
let n = 0;
|
|
8688
8994
|
for (const src of sources) {
|
|
8689
|
-
cpSync2(src,
|
|
8995
|
+
cpSync2(src, join33(dest, basename3(src)), { recursive: true });
|
|
8690
8996
|
process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename3(src))}
|
|
8691
8997
|
`);
|
|
8692
8998
|
n++;
|
|
@@ -8747,7 +9053,7 @@ function enableHelpOnError(program2) {
|
|
|
8747
9053
|
// src/cli.ts
|
|
8748
9054
|
var version = createRequire(import.meta.url)("../package.json").version;
|
|
8749
9055
|
var program = new Command();
|
|
8750
|
-
program.name("oriro").description("ORIRO \u2014 a free, on-device-friendly terminal AI agent.").version(version, "-v, --version").option("-p, --print <prompt>", "headless one-shot: run a single prompt, print the answer, exit (CI-friendly)").option("--output-format <fmt>", "with --print: text | json | stream-json", "text").action(async (options, command) => {
|
|
9056
|
+
program.name("oriro").description("ORIRO \u2014 a free, on-device-friendly terminal AI agent.").version(version, "-v, --version").option("-p, --print <prompt>", "headless one-shot: run a single prompt, print the answer, exit (CI-friendly)").option("--output-format <fmt>", "with --print: text | json | stream-json", "text").option("-c, --continue", "resume your most recent session in this folder").option("--resume <id>", "resume a specific saved session (id or unique prefix \u2014 see: oriro sessions)").option("--fork <id>", "start a new session branched from an existing one").option("--no-session", "don't save this session to disk (ephemeral)").action(async (options, command) => {
|
|
8751
9057
|
if (options.print !== void 0) {
|
|
8752
9058
|
const fmt = options.outputFormat ?? "text";
|
|
8753
9059
|
if (!isOutputFormatMode(fmt)) {
|
|
@@ -8774,8 +9080,15 @@ program.name("oriro").description("ORIRO \u2014 a free, on-device-friendly termi
|
|
|
8774
9080
|
process.exitCode = 1;
|
|
8775
9081
|
return;
|
|
8776
9082
|
}
|
|
8777
|
-
|
|
9083
|
+
const resume = {
|
|
9084
|
+
continue: options.continue,
|
|
9085
|
+
resumeId: options.resume,
|
|
9086
|
+
forkId: options.fork,
|
|
9087
|
+
ephemeral: options.session === false
|
|
9088
|
+
};
|
|
9089
|
+
await runRepl({ resume });
|
|
8778
9090
|
});
|
|
9091
|
+
registerSessionsCommand(program);
|
|
8779
9092
|
registerRoutersCommand(program);
|
|
8780
9093
|
registerScribeCommand(program);
|
|
8781
9094
|
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.5",
|
|
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",
|
|
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",
|
|
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"
|