@oriro/orirocli 0.3.2 → 0.3.4

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 +390 -96
  2. 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 join16 } from "path";
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 join16(dir, "routers", "health.json");
3525
+ return join17(dir, "routers", "health.json");
3468
3526
  }
3469
3527
  function saveMuxState(dir, stats) {
3470
3528
  const p = healthStatePath(dir);
3471
- mkdirSync9(join16(dir, "routers"), { recursive: true });
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 join18 } from "path";
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 join17 } from "path";
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 : join17(CONFIG_DIR, "scribe");
3640
+ return override && override.length > 0 ? override : join18(CONFIG_DIR, "scribe");
3583
3641
  }
3584
3642
  function journalFile(date) {
3585
- return join17(scribeDir(), `${date}.md`);
3643
+ return join18(scribeDir(), `${date}.md`);
3586
3644
  }
3587
3645
  function digestFile() {
3588
- return join17(scribeDir(), "_digest.md");
3646
+ return join18(scribeDir(), "_digest.md");
3589
3647
  }
3590
3648
  function timelineFile() {
3591
- return join17(scribeDir(), "_timeline.md");
3649
+ return join18(scribeDir(), "_timeline.md");
3592
3650
  }
3593
3651
  function artifactsDir() {
3594
- return join17(scribeDir(), "artifacts");
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 = join18(artifactsDir(), name);
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 join19 } from "path";
3927
+ import { join as join20 } from "path";
3870
3928
  function healthFile() {
3871
- return join19(scribeDir(), "_health.json");
3929
+ return join20(scribeDir(), "_health.json");
3872
3930
  }
3873
3931
  function faultLogFile() {
3874
- return join19(scribeDir(), "_faults.log");
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 join20 } from "path";
3985
+ import { join as join21 } from "path";
3928
3986
  function walFile() {
3929
- return join20(scribeDir(), "_wal.jsonl");
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 join21, dirname as dirname3, parse } from "path";
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(join21(dir, ".git")) || existsSync13(join21(dir, ".oriro"));
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 = join21(dir, name);
4212
+ const p = join22(dir, name);
4155
4213
  try {
4156
4214
  if (existsSync13(p) && statSync2(p).isFile()) {
4157
4215
  let text = readFileSync18(p, "utf8");
@@ -4765,7 +4823,7 @@ async function comparePages(opts) {
4765
4823
 
4766
4824
  // src/head/run.ts
4767
4825
  import { writeFile } from "fs/promises";
4768
- import { join as join22 } from "path";
4826
+ import { join as join23 } from "path";
4769
4827
 
4770
4828
  // src/head/inspection-html.ts
4771
4829
  var PRIORITY_COLOR = {
@@ -5267,7 +5325,7 @@ async function runInspect(target, competitors, opts = {}) {
5267
5325
  const report = await comparePages({ targetUrl: target, competitorUrls: competitors.length ? competitors : [target] });
5268
5326
  const files = [];
5269
5327
  if (opts.html) {
5270
- const path = join22(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(target)}-inspect.html`);
5328
+ const path = join23(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(target)}-inspect.html`);
5271
5329
  await writeFile(path, buildInspectionHtml(report), "utf8");
5272
5330
  files.push(path);
5273
5331
  }
@@ -5283,7 +5341,7 @@ function parseHeadTargets(text, selfOrigin) {
5283
5341
  async function runUrlToCode(url, opts = {}) {
5284
5342
  try {
5285
5343
  const res = await urlToCode(url, headModels(), { goal: opts.goal, stack: opts.stack });
5286
- const codePath = join22(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(url)}${extForStack(opts.stack)}`);
5344
+ const codePath = join23(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(url)}${extForStack(opts.stack)}`);
5287
5345
  await writeFile(codePath, res.code, "utf8");
5288
5346
  return { summary: `Reverse-engineered ${url} into clean code (${res.code.length} chars) \u2192 ${codePath}`, files: [codePath] };
5289
5347
  } catch (e) {
@@ -5293,7 +5351,7 @@ async function runUrlToCode(url, opts = {}) {
5293
5351
  async function runUrlToSpec(url, opts = {}) {
5294
5352
  try {
5295
5353
  const res = await urlToSpec(url, headModels(), { goal: opts.goal });
5296
- const specPath = join22(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(url)}.spec.yaml`);
5354
+ const specPath = join23(opts.outDir ?? process.cwd(), `oriro-head-${hostSlug(url)}.spec.yaml`);
5297
5355
  await writeFile(specPath, res.spec, "utf8");
5298
5356
  return { summary: `Reverse-engineered ${url} into a YAML build spec \u2192 ${specPath}`, files: [specPath] };
5299
5357
  } catch (e) {
@@ -5305,7 +5363,7 @@ async function runCapture(urls, opts = {}) {
5305
5363
  const { captureScreens: captureScreens2, buildScreenshotFlowHtml: buildScreenshotFlowHtml2 } = await Promise.resolve().then(() => (init_screenshot_flow(), screenshot_flow_exports));
5306
5364
  const caps = await captureScreens2(urls, { video: opts.video });
5307
5365
  const html = buildScreenshotFlowHtml2([{ name: "Captured screens", captures: caps }]);
5308
- const flowPath = join22(opts.outDir ?? process.cwd(), "oriro-head-flow.html");
5366
+ const flowPath = join23(opts.outDir ?? process.cwd(), "oriro-head-flow.html");
5309
5367
  await writeFile(flowPath, html, "utf8");
5310
5368
  const ok2 = caps.filter((c) => c.ok).length;
5311
5369
  return { summary: `Captured ${ok2}/${caps.length} full-page screenshots \u2192 ${flowPath}`, files: [flowPath] };
@@ -5326,7 +5384,7 @@ async function runVideoToCode(videoPath, opts = {}) {
5326
5384
  { videoPath, frames, mimeType: mime, goal: opts.goal, stack: opts.stack },
5327
5385
  headVideoModels()
5328
5386
  );
5329
- const codePath = join22(opts.outDir ?? process.cwd(), `oriro-head-video${extForStack(opts.stack)}`);
5387
+ const codePath = join23(opts.outDir ?? process.cwd(), `oriro-head-video${extForStack(opts.stack)}`);
5330
5388
  await writeFile(codePath, res.code, "utf8");
5331
5389
  return { summary: `Watched ${videoPath} \u2192 built code (${res.code.length} chars) \u2192 ${codePath}
5332
5390
  (experimental on the free floor \u2014 add a vision-capable router for pixel-faithful results.)`, files: [codePath] };
@@ -5419,7 +5477,7 @@ function registerHead(pi) {
5419
5477
  }
5420
5478
 
5421
5479
  // src/orchestrate.ts
5422
- import { createAgentSession, AuthStorage, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
5480
+ import { createAgentSession, AuthStorage, ModelRegistry, SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
5423
5481
  import { Type as Type3 } from "typebox";
5424
5482
  var MAX_AGENTS = 8;
5425
5483
  var MAX_CONCURRENCY = 4;
@@ -5432,7 +5490,7 @@ async function runOnce(spec) {
5432
5490
  model,
5433
5491
  authStorage,
5434
5492
  modelRegistry,
5435
- sessionManager: SessionManager.inMemory(),
5493
+ sessionManager: SessionManager2.inMemory(),
5436
5494
  noTools: "all"
5437
5495
  });
5438
5496
  let out = "";
@@ -5509,19 +5567,19 @@ import { Type as Type4 } from "typebox";
5509
5567
 
5510
5568
  // src/agents/store.ts
5511
5569
  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 join23 } from "path";
5570
+ import { join as join24 } from "path";
5513
5571
  var SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
5514
5572
  function isValidAgentName(name) {
5515
5573
  return SLUG.test(name);
5516
5574
  }
5517
5575
  function agentsDir() {
5518
- return join23(oriroDir(), "agents");
5576
+ return join24(oriroDir(), "agents");
5519
5577
  }
5520
5578
  function agentFile(name) {
5521
- return join23(agentsDir(), `${name}.json`);
5579
+ return join24(agentsDir(), `${name}.json`);
5522
5580
  }
5523
5581
  function stateFile() {
5524
- return join23(agentsDir(), ".state.json");
5582
+ return join24(agentsDir(), ".state.json");
5525
5583
  }
5526
5584
  function listAgents() {
5527
5585
  const dir = agentsDir();
@@ -5530,7 +5588,7 @@ function listAgents() {
5530
5588
  for (const f of readdirSync2(dir)) {
5531
5589
  if (!f.endsWith(".json") || f.startsWith(".")) continue;
5532
5590
  try {
5533
- const def = JSON.parse(readFileSync19(join23(dir, f), "utf8"));
5591
+ const def = JSON.parse(readFileSync19(join24(dir, f), "utf8"));
5534
5592
  if (def && typeof def.name === "string" && typeof def.task === "string") out.push(def);
5535
5593
  } catch {
5536
5594
  }
@@ -5843,9 +5901,9 @@ function registerToolList(pi, serverName, client, tools, seen = /* @__PURE__ */
5843
5901
 
5844
5902
  // src/connectors/custom.ts
5845
5903
  import { readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
5846
- import { join as join24 } from "path";
5904
+ import { join as join25 } from "path";
5847
5905
  function file3() {
5848
- return join24(oriroDir(), "mcp-custom.json");
5906
+ return join25(oriroDir(), "mcp-custom.json");
5849
5907
  }
5850
5908
  function readCustomServers() {
5851
5909
  try {
@@ -5857,13 +5915,13 @@ function readCustomServers() {
5857
5915
  }
5858
5916
  function saveCustomServer(server) {
5859
5917
  const rest = readCustomServers().filter((s) => s.name.toLowerCase() !== server.name.toLowerCase());
5860
- writeFileSync17(join24(ensureOriroDir(), "mcp-custom.json"), JSON.stringify([...rest, server], null, 2), "utf8");
5918
+ writeFileSync17(join25(ensureOriroDir(), "mcp-custom.json"), JSON.stringify([...rest, server], null, 2), "utf8");
5861
5919
  }
5862
5920
  function removeCustomServer(name) {
5863
5921
  const before = readCustomServers();
5864
5922
  const after = before.filter((s) => s.name.toLowerCase() !== name.toLowerCase());
5865
5923
  if (after.length === before.length) return false;
5866
- writeFileSync17(join24(ensureOriroDir(), "mcp-custom.json"), JSON.stringify(after, null, 2), "utf8");
5924
+ writeFileSync17(join25(ensureOriroDir(), "mcp-custom.json"), JSON.stringify(after, null, 2), "utf8");
5867
5925
  return true;
5868
5926
  }
5869
5927
  function trustedServerNames() {
@@ -5928,16 +5986,17 @@ async function assembleOriroSession(opts = {}) {
5928
5986
  ]
5929
5987
  });
5930
5988
  await resourceLoader.reload();
5989
+ const { sm, note: sessionNote } = await resolveSessionManager(cwd, opts.resume);
5931
5990
  const { session, extensionsResult } = await createAgentSession2({
5932
5991
  model,
5933
5992
  authStorage,
5934
5993
  modelRegistry,
5935
5994
  settingsManager,
5936
- sessionManager: SessionManager2.inMemory(),
5995
+ sessionManager: sm,
5937
5996
  resourceLoader
5938
5997
  });
5939
5998
  attachScribe(session);
5940
- return { session, extensionsResult };
5999
+ return { session, extensionsResult, sessionNote };
5941
6000
  }
5942
6001
 
5943
6002
  // src/language/nllb-translator.ts
@@ -6403,6 +6462,178 @@ async function handleCompact(session, cmd) {
6403
6462
  }
6404
6463
  }
6405
6464
 
6465
+ // src/context/init-agents.ts
6466
+ import { existsSync as existsSync17, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync19 } from "fs";
6467
+ import { join as join26, basename } from "path";
6468
+ var CODE_EXT = {
6469
+ ts: "TypeScript",
6470
+ tsx: "TypeScript",
6471
+ js: "JavaScript",
6472
+ jsx: "JavaScript",
6473
+ mjs: "JavaScript",
6474
+ py: "Python",
6475
+ go: "Go",
6476
+ rs: "Rust",
6477
+ java: "Java",
6478
+ kt: "Kotlin",
6479
+ rb: "Ruby",
6480
+ php: "PHP",
6481
+ c: "C",
6482
+ h: "C",
6483
+ cpp: "C++",
6484
+ cc: "C++",
6485
+ cs: "C#",
6486
+ swift: "Swift",
6487
+ sh: "Shell",
6488
+ sql: "SQL"
6489
+ };
6490
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "target", "__pycache__", ".venv", "venv", ".oriro"]);
6491
+ function readJson(p) {
6492
+ try {
6493
+ return JSON.parse(readFileSync21(p, "utf8"));
6494
+ } catch {
6495
+ return {};
6496
+ }
6497
+ }
6498
+ function detectProject(cwd) {
6499
+ const facts = { name: basename(cwd) || "project", languages: [], commands: [], topDirs: [] };
6500
+ const pkgPath = join26(cwd, "package.json");
6501
+ if (existsSync17(pkgPath)) {
6502
+ const pkg = readJson(pkgPath);
6503
+ if (typeof pkg.name === "string" && pkg.name) facts.name = pkg.name;
6504
+ if (typeof pkg.description === "string" && pkg.description) facts.description = pkg.description;
6505
+ const scripts = pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
6506
+ for (const key of ["dev", "build", "test", "lint", "start"]) {
6507
+ if (scripts[key]) facts.commands.push({ label: key, cmd: `npm run ${key}` });
6508
+ }
6509
+ } else if (existsSync17(join26(cwd, "pyproject.toml")) || existsSync17(join26(cwd, "requirements.txt"))) {
6510
+ if (!facts.description) facts.description = "Python project";
6511
+ } else if (existsSync17(join26(cwd, "Cargo.toml"))) {
6512
+ facts.commands.push({ label: "build", cmd: "cargo build" }, { label: "test", cmd: "cargo test" });
6513
+ } else if (existsSync17(join26(cwd, "go.mod"))) {
6514
+ facts.commands.push({ label: "build", cmd: "go build ./..." }, { label: "test", cmd: "go test ./..." });
6515
+ }
6516
+ const langCount = /* @__PURE__ */ new Map();
6517
+ const tallyExt = (file6) => {
6518
+ const ext = file6.split(".").pop()?.toLowerCase();
6519
+ const lang = ext && CODE_EXT[ext];
6520
+ if (lang) langCount.set(lang, (langCount.get(lang) ?? 0) + 1);
6521
+ };
6522
+ let entries = [];
6523
+ try {
6524
+ entries = readdirSync3(cwd);
6525
+ } catch {
6526
+ }
6527
+ for (const e of entries) {
6528
+ const full = join26(cwd, e);
6529
+ let isDir = false;
6530
+ try {
6531
+ isDir = statSync3(full).isDirectory();
6532
+ } catch {
6533
+ continue;
6534
+ }
6535
+ if (isDir) {
6536
+ if (SKIP_DIRS.has(e) || e.startsWith(".")) continue;
6537
+ facts.topDirs.push(e);
6538
+ try {
6539
+ for (const f of readdirSync3(full)) {
6540
+ try {
6541
+ if (statSync3(join26(full, f)).isFile()) tallyExt(f);
6542
+ } catch {
6543
+ }
6544
+ }
6545
+ } catch {
6546
+ }
6547
+ } else {
6548
+ tallyExt(e);
6549
+ }
6550
+ }
6551
+ facts.languages = [...langCount.entries()].sort((a, b) => b[1] - a[1]).map(([l]) => l);
6552
+ facts.topDirs.sort();
6553
+ return facts;
6554
+ }
6555
+ function generateAgentsMd(cwd) {
6556
+ const f = detectProject(cwd);
6557
+ const lines = [];
6558
+ lines.push(`# ${f.name}`, "");
6559
+ lines.push(f.description ?? "_One-line description of what this project does._", "");
6560
+ lines.push("## Stack");
6561
+ lines.push(f.languages.length ? `- Languages: ${f.languages.join(", ")}` : "- Languages: _add the main languages_");
6562
+ if (f.topDirs.length) lines.push(`- Layout: ${f.topDirs.map((d) => `\`${d}/\``).join(", ")}`);
6563
+ lines.push("");
6564
+ lines.push("## Commands");
6565
+ if (f.commands.length) for (const c of f.commands) lines.push(`- ${c.label}: \`${c.cmd}\``);
6566
+ else lines.push("- _add build/test/run commands here_");
6567
+ lines.push("");
6568
+ lines.push("## Conventions");
6569
+ lines.push("- _House rules for this repo: style, patterns to follow, things never to touch._");
6570
+ lines.push("- _ORIRO reads this file automatically each session \u2014 keep it short and current._");
6571
+ lines.push("");
6572
+ return lines.join("\n");
6573
+ }
6574
+ function writeAgentsMd(cwd = process.cwd(), force = false) {
6575
+ const path = join26(cwd, "AGENTS.md");
6576
+ const facts = detectProject(cwd);
6577
+ if (existsSync17(path) && !force) return { path, created: false, facts };
6578
+ writeFileSync19(path, generateAgentsMd(cwd), "utf8");
6579
+ return { path, created: true, facts };
6580
+ }
6581
+
6582
+ // src/repl-ui/slash-init.ts
6583
+ function isInitSlash(cmd) {
6584
+ return /^\/init(\s|$)/i.test(cmd.trim());
6585
+ }
6586
+ function handleInit(cmd, cwd = process.cwd()) {
6587
+ const force = /(^|\s)--force(\s|$)/i.test(cmd);
6588
+ let res;
6589
+ try {
6590
+ res = writeAgentsMd(cwd, force);
6591
+ } catch (e) {
6592
+ return [` ${fgHex(PALETTE.error, "init failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
6593
+ }
6594
+ const lines = [];
6595
+ if (!res.created) {
6596
+ lines.push(` ${dim("AGENTS.md already exists")} ${accent(res.path)} ${dim("\u2014 use /init --force to overwrite.")}`);
6597
+ return lines;
6598
+ }
6599
+ const f = res.facts;
6600
+ lines.push(` ${fgHex(PALETTE.success, "\u2713 wrote")} ${accent(res.path)}`);
6601
+ lines.push(dim(` detected: ${f.languages.length ? f.languages.join(", ") : "no languages"}${f.commands.length ? ` \xB7 ${f.commands.length} command${f.commands.length === 1 ? "" : "s"}` : ""}${f.topDirs.length ? ` \xB7 ${f.topDirs.length} dir${f.topDirs.length === 1 ? "" : "s"}` : ""}`));
6602
+ lines.push(dim(" edit it to add house rules \u2014 ORIRO reads it automatically each session."));
6603
+ return lines;
6604
+ }
6605
+
6606
+ // src/repl-ui/slash-sessions.ts
6607
+ function isSessionsSlash(cmd) {
6608
+ return /^\/sessions?(\s|$)/i.test(cmd.trim());
6609
+ }
6610
+ function isUndoSlash(cmd) {
6611
+ return /^\/undo(\s|$)/i.test(cmd.trim());
6612
+ }
6613
+ async function handleSessions() {
6614
+ try {
6615
+ return formatSessionList(await listSessions());
6616
+ } catch (e) {
6617
+ return [` ${fgHex(PALETTE.error, "sessions failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
6618
+ }
6619
+ }
6620
+ async function handleUndo(session) {
6621
+ try {
6622
+ const turns2 = session.getUserMessagesForForking();
6623
+ if (turns2.length < 2) {
6624
+ return [dim(" nothing to undo \u2014 this is the first turn of the session.")];
6625
+ }
6626
+ const target = turns2[turns2.length - 2];
6627
+ if (!target) return [dim(" nothing to undo.")];
6628
+ const res = await session.navigateTree(target.entryId, { label: "undo" });
6629
+ if (res.cancelled) return [dim(" undo cancelled.")];
6630
+ const preview = target.text.replace(/\s+/g, " ").trim().slice(0, 48);
6631
+ return [` ${fgHex(PALETTE.success, "\u21BA undone")} ${dim("\u2014 rewound to:")} ${accent(preview || "(prev turn)")}`];
6632
+ } catch (e) {
6633
+ return [` ${fgHex(PALETTE.error, "undo failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
6634
+ }
6635
+ }
6636
+
6406
6637
  // src/repl-ui/tui-repl.ts
6407
6638
  var editorTheme = {
6408
6639
  borderColor: (s) => dim(s),
@@ -6485,7 +6716,8 @@ async function runTuiRepl(session) {
6485
6716
  const help = [
6486
6717
  " Just type to chat \u2014 ORIRO writes and runs code for you (keyless, free).",
6487
6718
  ` ${accent("/routers")} pool add\xB7rotate ${accent("/model")} <id\u2026> switch ${accent("/usage")} health ${accent("/trace")} tool+router activity ${accent("/compact")} free context`,
6488
- ` ${accent("/review")} artifacts from the last reply ${accent("/save")} <n> [path] ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
6719
+ ` ${accent("/review")} artifacts ${accent("/save")} <n> [path] ${accent("/init")} AGENTS.md ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
6720
+ ` ${accent("/sessions")} list saved ${accent("/undo")} rewind a turn ${dim("resume:")} ${accent("oriro -c")} / ${accent("oriro --resume <id>")}`,
6489
6721
  ` ${dim("Shift+Tab")} posture ${dim("Alt+Shift+T")} thinking ${accent("/help")} ${accent("/exit")}`
6490
6722
  ].join("\n");
6491
6723
  chat.addChild(new Text(help, 0, 0));
@@ -6548,6 +6780,24 @@ async function runTuiRepl(session) {
6548
6780
  })();
6549
6781
  return;
6550
6782
  }
6783
+ if (isInitSlash(slash)) {
6784
+ chat.addChild(new Text(handleInit(text).join("\n"), 0, 0));
6785
+ editor.setText("");
6786
+ tui.requestRender();
6787
+ return;
6788
+ }
6789
+ if (isSessionsSlash(slash) || isUndoSlash(slash)) {
6790
+ editor.setText("");
6791
+ const pending = new Text(dim(" \u2026"), 0, 0);
6792
+ chat.addChild(pending);
6793
+ tui.requestRender();
6794
+ void (async () => {
6795
+ const lines = isUndoSlash(slash) ? await handleUndo(session) : await handleSessions();
6796
+ pending.setText(lines.join("\n"));
6797
+ tui.requestRender();
6798
+ })();
6799
+ return;
6800
+ }
6551
6801
  if (slash === "/voice") {
6552
6802
  editor.setText("");
6553
6803
  const status = new Text(dim(" \u{1F399} listening\u2026 (needs ffmpeg + the transformers voice peer)"), 0, 0);
@@ -6639,8 +6889,8 @@ ${english}`;
6639
6889
  // src/voice/mic.ts
6640
6890
  import { spawn as spawn3 } from "child_process";
6641
6891
  import { tmpdir as tmpdir3 } from "os";
6642
- import { join as join25 } from "path";
6643
- import { existsSync as existsSync17, statSync as statSync3 } from "fs";
6892
+ import { join as join27 } from "path";
6893
+ import { existsSync as existsSync18, statSync as statSync4 } from "fs";
6644
6894
  function recorders(outFile, seconds) {
6645
6895
  const dur = String(seconds);
6646
6896
  if (process.platform === "darwin") {
@@ -6661,12 +6911,12 @@ function recorders(outFile, seconds) {
6661
6911
  ];
6662
6912
  }
6663
6913
  async function recordMic(seconds = 6) {
6664
- const outFile = join25(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
6914
+ const outFile = join27(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
6665
6915
  for (const r of recorders(outFile, seconds)) {
6666
6916
  const okFile = await new Promise((resolve3) => {
6667
6917
  const child = spawn3(r.cmd, r.args, { stdio: "ignore" });
6668
6918
  child.on("error", () => resolve3(false));
6669
- child.on("close", (code) => resolve3(code === 0 && existsSync17(outFile) && statSync3(outFile).size > 44));
6919
+ child.on("close", (code) => resolve3(code === 0 && existsSync18(outFile) && statSync4(outFile).size > 44));
6670
6920
  });
6671
6921
  if (okFile) return outFile;
6672
6922
  }
@@ -6731,8 +6981,10 @@ function replHelp() {
6731
6981
  ${dim("Just type to chat; ORIRO writes and runs code for you (keyless, free).")}
6732
6982
 
6733
6983
  ${dim("Models & routers")} ${accent("/routers")} list\xB7add\xB7rotate the racing pool ${accent("/model")} <id\u2026> switch
6734
- ${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} show tool + router activity ${accent("/compact")} free context
6984
+ ${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} activity ${accent("/compact")} free context ${accent("/undo")} rewind a turn
6985
+ ${dim("Continuity")} ${accent("/sessions")} list saved sessions ${dim("resume:")} ${accent("oriro -c")} ${dim("or")} ${accent("oriro --resume <id>")}
6735
6986
  ${dim("Artifacts")} ${accent("/review")} code/SVG from the last reply ${accent("/save")} <n> [path] write one
6987
+ ${dim("Project")} ${accent("/init")} write a starter AGENTS.md ORIRO reads each session
6736
6988
  ${dim("Capabilities")} ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")} speak a turn
6737
6989
  ${dim("General")} ${accent("/help")} this ${accent("/exit")} / ${accent("/quit")} leave ${dim("(Ctrl-D / Ctrl-C also exit)")}
6738
6990
 
@@ -6740,10 +6992,12 @@ function replHelp() {
6740
6992
 
6741
6993
  `;
6742
6994
  }
6743
- async function runRepl() {
6995
+ async function runRepl(opts = {}) {
6744
6996
  if (isFirstRun()) await runOnboarding();
6745
6997
  else stdout7.write(banner());
6746
- const { session } = await assembleOriroSession();
6998
+ const { session, sessionNote } = await assembleOriroSession({ resume: opts.resume });
6999
+ if (sessionNote) stdout7.write(` ${dim(sessionNote)}
7000
+ `);
6747
7001
  setupVoiceInput();
6748
7002
  if (stdin6.isTTY && stdout7.isTTY) {
6749
7003
  await runTuiRepl(session);
@@ -6812,6 +7066,18 @@ async function runReadlineRepl(session) {
6812
7066
  stdout7.write((await handleCompact(session, line)).join("\n") + "\n");
6813
7067
  continue;
6814
7068
  }
7069
+ if (isInitSlash(slash)) {
7070
+ stdout7.write(handleInit(line).join("\n") + "\n");
7071
+ continue;
7072
+ }
7073
+ if (isSessionsSlash(slash)) {
7074
+ stdout7.write((await handleSessions()).join("\n") + "\n");
7075
+ continue;
7076
+ }
7077
+ if (isUndoSlash(slash)) {
7078
+ stdout7.write((await handleUndo(session)).join("\n") + "\n");
7079
+ continue;
7080
+ }
6815
7081
  if (isArtifactSlash(slash)) {
6816
7082
  stdout7.write(handleArtifactSlash(line).join("\n") + "\n");
6817
7083
  continue;
@@ -6939,8 +7205,8 @@ async function confirmDestructive(what, opts = {}) {
6939
7205
  }
6940
7206
 
6941
7207
  // src/config/store.ts
6942
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16 } from "fs";
6943
- import { join as join26 } from "path";
7208
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
7209
+ import { join as join28 } from "path";
6944
7210
  var KEYS = {
6945
7211
  output: {
6946
7212
  desc: "default output format for list commands: text | json | csv",
@@ -6962,13 +7228,13 @@ function validateConfig(key, value) {
6962
7228
  return KEYS[key].validate?.(value) ?? null;
6963
7229
  }
6964
7230
  function file4() {
6965
- return join26(oriroDir(), "config.json");
7231
+ return join28(oriroDir(), "config.json");
6966
7232
  }
6967
7233
  var cache = null;
6968
7234
  function readAll() {
6969
7235
  if (cache) return cache;
6970
7236
  try {
6971
- const v = JSON.parse(readFileSync21(file4(), "utf8"));
7237
+ const v = JSON.parse(readFileSync22(file4(), "utf8"));
6972
7238
  cache = v && typeof v === "object" ? v : {};
6973
7239
  } catch {
6974
7240
  cache = {};
@@ -6984,7 +7250,7 @@ function configAll() {
6984
7250
  function configSet(key, value) {
6985
7251
  const all = { ...readAll(), [key]: value };
6986
7252
  mkdirSync16(oriroDir(), { recursive: true });
6987
- writeFileSync19(file4(), JSON.stringify(all, null, 2), "utf8");
7253
+ writeFileSync20(file4(), JSON.stringify(all, null, 2), "utf8");
6988
7254
  cache = all;
6989
7255
  }
6990
7256
  function configUnset(key) {
@@ -6992,7 +7258,7 @@ function configUnset(key) {
6992
7258
  if (!(key in all)) return false;
6993
7259
  const rest = { ...all };
6994
7260
  delete rest[key];
6995
- writeFileSync19(file4(), JSON.stringify(rest, null, 2), "utf8");
7261
+ writeFileSync20(file4(), JSON.stringify(rest, null, 2), "utf8");
6996
7262
  cache = rest;
6997
7263
  return true;
6998
7264
  }
@@ -7046,6 +7312,27 @@ function outputError(opts) {
7046
7312
  return f === "json" || f === "csv" || f === "text" ? null : `invalid --output '${opts.output}' \u2014 use text | json | csv`;
7047
7313
  }
7048
7314
 
7315
+ // src/commands/sessions.ts
7316
+ function registerSessionsCommand(program2) {
7317
+ 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) => {
7318
+ const oerr = outputError(opts);
7319
+ if (oerr) die(oerr);
7320
+ const infos = await listSessions();
7321
+ if (isMachineOutput(opts) || opts.query) {
7322
+ process.stdout.write(
7323
+ renderList2(sessionRows(infos), {
7324
+ output: opts.output,
7325
+ query: opts.query,
7326
+ columns: ["id", "messages", "modified", "first", "cwd"]
7327
+ }) + "\n"
7328
+ );
7329
+ return;
7330
+ }
7331
+ heading("Sessions");
7332
+ process.stdout.write(formatSessionList(infos).join("\n") + "\n");
7333
+ });
7334
+ }
7335
+
7049
7336
  // src/commands/routers.ts
7050
7337
  function registerRoutersCommand(program2) {
7051
7338
  const routers = program2.command("routers").description("manage the free-router pool the model runs on");
@@ -7135,10 +7422,10 @@ function registerRoutersCommand(program2) {
7135
7422
  }
7136
7423
 
7137
7424
  // src/commands/scribe.ts
7138
- import { readFileSync as readFileSync23 } from "fs";
7425
+ import { readFileSync as readFileSync24 } from "fs";
7139
7426
 
7140
7427
  // src/scribe/transcript.ts
7141
- import { existsSync as existsSync19, readFileSync as readFileSync22 } from "fs";
7428
+ import { existsSync as existsSync20, readFileSync as readFileSync23 } from "fs";
7142
7429
  function parseHookStdin(raw) {
7143
7430
  try {
7144
7431
  const j = JSON.parse(raw);
@@ -7171,8 +7458,8 @@ function isHumanUser(e) {
7171
7458
  }
7172
7459
  var FILE_KEYS = ["file_path", "path", "notebook_path", "filePath"];
7173
7460
  function lastTurnFromTranscript(path) {
7174
- if (!existsSync19(path)) return null;
7175
- const raw = readFileSync22(path, "utf8");
7461
+ if (!existsSync20(path)) return null;
7462
+ const raw = readFileSync23(path, "utf8");
7176
7463
  const entries = [];
7177
7464
  for (const line of raw.split("\n")) {
7178
7465
  if (!line.trim()) continue;
@@ -7233,7 +7520,7 @@ function lastTurnFromTranscript(path) {
7233
7520
  // src/commands/scribe.ts
7234
7521
  function readStdin() {
7235
7522
  try {
7236
- return readFileSync23(0, "utf8");
7523
+ return readFileSync24(0, "utf8");
7237
7524
  } catch {
7238
7525
  return "";
7239
7526
  }
@@ -7543,14 +7830,14 @@ function registerConnectorsCommand(program2) {
7543
7830
  }
7544
7831
 
7545
7832
  // src/channels/config.ts
7546
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync20 } from "fs";
7547
- import { join as join27 } from "path";
7833
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
7834
+ import { join as join29 } from "path";
7548
7835
  function file5() {
7549
- return join27(oriroDir(), "channels.json");
7836
+ return join29(oriroDir(), "channels.json");
7550
7837
  }
7551
7838
  function readChannels() {
7552
7839
  try {
7553
- const v = JSON.parse(readFileSync24(file5(), "utf8"));
7840
+ const v = JSON.parse(readFileSync25(file5(), "utf8"));
7554
7841
  return Array.isArray(v) ? v : [];
7555
7842
  } catch {
7556
7843
  return [];
@@ -7559,10 +7846,10 @@ function readChannels() {
7559
7846
  function saveChannel(cfg) {
7560
7847
  const all = readChannels().filter((c) => c.kind !== cfg.kind);
7561
7848
  all.push(cfg);
7562
- writeFileSync20(join27(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
7849
+ writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
7563
7850
  }
7564
7851
  function removeChannel(kind) {
7565
- writeFileSync20(join27(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
7852
+ writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
7566
7853
  }
7567
7854
 
7568
7855
  // src/channels/telegram.ts
@@ -7679,9 +7966,9 @@ async function startDiscord(token) {
7679
7966
  }
7680
7967
 
7681
7968
  // src/channels/whatsapp.ts
7682
- import { join as join28 } from "path";
7969
+ import { join as join30 } from "path";
7683
7970
  function whatsappAuthDir() {
7684
- return join28(oriroDir(), "whatsapp-auth");
7971
+ return join30(oriroDir(), "whatsapp-auth");
7685
7972
  }
7686
7973
  async function startWhatsApp() {
7687
7974
  let baileys;
@@ -7799,8 +8086,8 @@ function registerChannelsCommand(program2) {
7799
8086
  }
7800
8087
 
7801
8088
  // src/commands/skills.ts
7802
- import { existsSync as existsSync20, statSync as statSync4, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
7803
- import { resolve as resolve2, join as join29, basename, dirname as dirname4 } from "path";
8089
+ import { existsSync as existsSync21, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
8090
+ import { resolve as resolve2, join as join31, basename as basename2, dirname as dirname4 } from "path";
7804
8091
  function registerSkillsCommand(program2) {
7805
8092
  const skills = program2.command("skills").description("the ORIRO skill library \u2014 bundled + your own");
7806
8093
  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) => {
@@ -7832,28 +8119,28 @@ function registerSkillsCommand(program2) {
7832
8119
  });
7833
8120
  skills.command("add <path>").description("add your own skill \u2014 a folder containing SKILL.md, or a SKILL.md file").action((p) => {
7834
8121
  const src = resolve2(p);
7835
- if (!existsSync20(src)) die(`not found: ${src}`);
8122
+ if (!existsSync21(src)) die(`not found: ${src}`);
7836
8123
  const dest = userSkillsDir();
7837
8124
  mkdirSync17(dest, { recursive: true });
7838
- const st = statSync4(src);
8125
+ const st = statSync5(src);
7839
8126
  if (st.isDirectory()) {
7840
- if (!existsSync20(join29(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
7841
- const name = basename(src);
7842
- cpSync(src, join29(dest, name), { recursive: true });
7843
- ok(`added skill ${accent(name)} \u2192 ${join29(dest, name)}`);
7844
- } else if (basename(src).toLowerCase() === "skill.md") {
7845
- const name = basename(dirname4(src)) || "custom-skill";
7846
- mkdirSync17(join29(dest, name), { recursive: true });
7847
- cpSync(src, join29(dest, name, "SKILL.md"));
7848
- ok(`added skill ${accent(name)} \u2192 ${join29(dest, name)}`);
8127
+ if (!existsSync21(join31(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
8128
+ const name = basename2(src);
8129
+ cpSync(src, join31(dest, name), { recursive: true });
8130
+ ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
8131
+ } else if (basename2(src).toLowerCase() === "skill.md") {
8132
+ const name = basename2(dirname4(src)) || "custom-skill";
8133
+ mkdirSync17(join31(dest, name), { recursive: true });
8134
+ cpSync(src, join31(dest, name, "SKILL.md"));
8135
+ ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
7849
8136
  } else {
7850
8137
  die("expected a folder containing SKILL.md, or a SKILL.md file");
7851
8138
  }
7852
8139
  info("It loads on next launch \u2014 and is available in chat via /skill.");
7853
8140
  });
7854
8141
  skills.command("remove <name>").description("remove a skill you added").option("-f, --force", "skip the confirmation prompt").action(async (name, opts) => {
7855
- const target = join29(userSkillsDir(), name);
7856
- if (!existsSync20(target)) {
8142
+ const target = join31(userSkillsDir(), name);
8143
+ if (!existsSync21(target)) {
7857
8144
  info(`'${name}' is not a user-added skill \u2014 nothing to remove`);
7858
8145
  return;
7859
8146
  }
@@ -8036,7 +8323,7 @@ function registerVoiceCommand(program2) {
8036
8323
  }
8037
8324
 
8038
8325
  // src/agents/catalog.ts
8039
- import { readFileSync as readFileSync25 } from "fs";
8326
+ import { readFileSync as readFileSync26 } from "fs";
8040
8327
  function parseAgentDef(raw, now) {
8041
8328
  if (!raw || typeof raw !== "object") return { ok: false, error: "not a JSON object" };
8042
8329
  const o = raw;
@@ -8063,7 +8350,7 @@ async function fetchAgentSource(pathOrUrl) {
8063
8350
  if (!res.ok) throw new Error(`fetch failed: HTTP ${res.status}`);
8064
8351
  return await res.json();
8065
8352
  }
8066
- return JSON.parse(readFileSync25(pathOrUrl, "utf8"));
8353
+ return JSON.parse(readFileSync26(pathOrUrl, "utf8"));
8067
8354
  }
8068
8355
  async function addAgentFromSource(pathOrUrl, now) {
8069
8356
  let raw;
@@ -8441,7 +8728,7 @@ function registerConfigCommand(program2) {
8441
8728
 
8442
8729
  // src/commands/setup.ts
8443
8730
  import { rmSync as rmSync5 } from "fs";
8444
- import { join as join30 } from "path";
8731
+ import { join as join32 } from "path";
8445
8732
  import { stdin as stdin12, stdout as stdout11 } from "process";
8446
8733
  var MARKERS = [
8447
8734
  "language.json",
@@ -8449,14 +8736,14 @@ var MARKERS = [
8449
8736
  "skills-onboarded.json",
8450
8737
  "connectors-onboarded.json",
8451
8738
  "models-onboarded.json",
8452
- join30("routers", "onboarded.json")
8739
+ join32("routers", "onboarded.json")
8453
8740
  ];
8454
8741
  function registerSetupCommand(program2) {
8455
8742
  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) => {
8456
8743
  if (opts.reset) {
8457
8744
  for (const m of MARKERS) {
8458
8745
  try {
8459
- rmSync5(join30(oriroDir(), m), { force: true });
8746
+ rmSync5(join32(oriroDir(), m), { force: true });
8460
8747
  } catch {
8461
8748
  }
8462
8749
  }
@@ -8473,15 +8760,15 @@ function registerSetupCommand(program2) {
8473
8760
  }
8474
8761
 
8475
8762
  // src/commands/import.ts
8476
- import { existsSync as existsSync21, readFileSync as readFileSync26, readdirSync as readdirSync3, statSync as statSync5, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
8477
- import { join as join31, basename as basename2 } from "path";
8763
+ import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync4, statSync as statSync6, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
8764
+ import { join as join33, basename as basename3 } from "path";
8478
8765
  function registerImportCommand(program2) {
8479
8766
  const imp = program2.command("import").description("migrate from another CLI (MCP servers, skills)");
8480
8767
  imp.command("mcp <file>").description("import MCP servers from a Claude-compatible mcp.json (Guardian-vetted)").action((file6) => {
8481
- if (!existsSync21(file6)) die(`no such file: ${file6}`);
8768
+ if (!existsSync22(file6)) die(`no such file: ${file6}`);
8482
8769
  let servers;
8483
8770
  try {
8484
- const j = JSON.parse(readFileSync26(file6, "utf8"));
8771
+ const j = JSON.parse(readFileSync27(file6, "utf8"));
8485
8772
  servers = j.mcpServers ?? j.servers ?? {};
8486
8773
  } catch (e) {
8487
8774
  die(`could not parse ${file6}: ${e instanceof Error ? e.message : String(e)}`);
@@ -8527,15 +8814,15 @@ function registerImportCommand(program2) {
8527
8814
  info(`${imported} imported \xB7 ${blocked2} blocked${imported ? ` \u2014 they connect in-session; see \`oriro connectors custom\`` : ""}`);
8528
8815
  });
8529
8816
  imp.command("skills <dir>").description("import SKILL.md skill folders from another CLI's skills directory").action((dir) => {
8530
- if (!existsSync21(dir) || !statSync5(dir).isDirectory()) die(`no such directory: ${dir}`);
8817
+ if (!existsSync22(dir) || !statSync6(dir).isDirectory()) die(`no such directory: ${dir}`);
8531
8818
  const dest = userSkillsDir();
8532
8819
  mkdirSync18(dest, { recursive: true });
8533
8820
  heading("Import skills");
8534
- const sources = existsSync21(join31(dir, "SKILL.md")) ? [dir] : readdirSync3(dir).map((e) => join31(dir, e)).filter((p) => statSync5(p).isDirectory() && existsSync21(join31(p, "SKILL.md")));
8821
+ 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")));
8535
8822
  let n = 0;
8536
8823
  for (const src of sources) {
8537
- cpSync2(src, join31(dest, basename2(src)), { recursive: true });
8538
- process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename2(src))}
8824
+ cpSync2(src, join33(dest, basename3(src)), { recursive: true });
8825
+ process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename3(src))}
8539
8826
  `);
8540
8827
  n++;
8541
8828
  }
@@ -8595,7 +8882,7 @@ function enableHelpOnError(program2) {
8595
8882
  // src/cli.ts
8596
8883
  var version = createRequire(import.meta.url)("../package.json").version;
8597
8884
  var program = new Command();
8598
- 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) => {
8885
+ 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) => {
8599
8886
  if (options.print !== void 0) {
8600
8887
  const fmt = options.outputFormat ?? "text";
8601
8888
  if (!isOutputFormatMode(fmt)) {
@@ -8622,8 +8909,15 @@ program.name("oriro").description("ORIRO \u2014 a free, on-device-friendly termi
8622
8909
  process.exitCode = 1;
8623
8910
  return;
8624
8911
  }
8625
- await runRepl();
8912
+ const resume = {
8913
+ continue: options.continue,
8914
+ resumeId: options.resume,
8915
+ forkId: options.fork,
8916
+ ephemeral: options.session === false
8917
+ };
8918
+ await runRepl({ resume });
8626
8919
  });
8920
+ registerSessionsCommand(program);
8627
8921
  registerRoutersCommand(program);
8628
8922
  registerScribeCommand(program);
8629
8923
  registerConnectorsCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oriro/orirocli",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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",
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",
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"