@oriro/orirocli 0.3.1 → 0.3.3

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 +262 -54
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -6363,6 +6363,187 @@ function handleArtifactSlash(raw) {
6363
6363
  return lines;
6364
6364
  }
6365
6365
 
6366
+ // src/repl-ui/slash-compact.ts
6367
+ function isCompactSlash(cmd) {
6368
+ return /^\/compact(\s|$)/i.test(cmd.trim());
6369
+ }
6370
+ function compactInstructions(cmd) {
6371
+ const rest = cmd.trim().replace(/^\/compact\s*/i, "").trim();
6372
+ return rest.length ? rest : void 0;
6373
+ }
6374
+ function formatCompactionResult(result) {
6375
+ const before = result.tokensBefore;
6376
+ const after = result.estimatedTokensAfter;
6377
+ const lines = [];
6378
+ if (typeof after === "number" && before > 0) {
6379
+ const freed = Math.max(0, before - after);
6380
+ const pct = Math.round(freed / before * 100);
6381
+ lines.push(
6382
+ ` ${fgHex(PALETTE.success, "\u2713 compacted")} ${dim(`${before.toLocaleString()} \u2192 ${after.toLocaleString()} tokens`)} ${accent(`(${pct}% freed)`)}`
6383
+ );
6384
+ } else {
6385
+ lines.push(` ${fgHex(PALETTE.success, "\u2713 compacted")} ${dim(`${before.toLocaleString()} tokens summarized`)}`);
6386
+ }
6387
+ lines.push(dim(" history summarized; the summary is kept, raw turns dropped. Keep going."));
6388
+ return lines;
6389
+ }
6390
+ async function handleCompact(session, cmd) {
6391
+ if (session.isCompacting) {
6392
+ return [dim(" compaction already in progress \u2014 hold on\u2026")];
6393
+ }
6394
+ if (session.messages.length < 4) {
6395
+ return [dim(" not much to compact yet \u2014 keep chatting, then /compact frees context.")];
6396
+ }
6397
+ try {
6398
+ const result = await session.compact(compactInstructions(cmd));
6399
+ if (!result) return [dim(" nothing to compact right now.")];
6400
+ return formatCompactionResult(result);
6401
+ } catch (e) {
6402
+ return [` ${fgHex(PALETTE.error, "compaction failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
6403
+ }
6404
+ }
6405
+
6406
+ // src/context/init-agents.ts
6407
+ import { existsSync as existsSync17, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync19 } from "fs";
6408
+ import { join as join25, basename } from "path";
6409
+ var CODE_EXT = {
6410
+ ts: "TypeScript",
6411
+ tsx: "TypeScript",
6412
+ js: "JavaScript",
6413
+ jsx: "JavaScript",
6414
+ mjs: "JavaScript",
6415
+ py: "Python",
6416
+ go: "Go",
6417
+ rs: "Rust",
6418
+ java: "Java",
6419
+ kt: "Kotlin",
6420
+ rb: "Ruby",
6421
+ php: "PHP",
6422
+ c: "C",
6423
+ h: "C",
6424
+ cpp: "C++",
6425
+ cc: "C++",
6426
+ cs: "C#",
6427
+ swift: "Swift",
6428
+ sh: "Shell",
6429
+ sql: "SQL"
6430
+ };
6431
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "target", "__pycache__", ".venv", "venv", ".oriro"]);
6432
+ function readJson(p) {
6433
+ try {
6434
+ return JSON.parse(readFileSync21(p, "utf8"));
6435
+ } catch {
6436
+ return {};
6437
+ }
6438
+ }
6439
+ function detectProject(cwd) {
6440
+ const facts = { name: basename(cwd) || "project", languages: [], commands: [], topDirs: [] };
6441
+ const pkgPath = join25(cwd, "package.json");
6442
+ if (existsSync17(pkgPath)) {
6443
+ const pkg = readJson(pkgPath);
6444
+ if (typeof pkg.name === "string" && pkg.name) facts.name = pkg.name;
6445
+ if (typeof pkg.description === "string" && pkg.description) facts.description = pkg.description;
6446
+ const scripts = pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
6447
+ for (const key of ["dev", "build", "test", "lint", "start"]) {
6448
+ if (scripts[key]) facts.commands.push({ label: key, cmd: `npm run ${key}` });
6449
+ }
6450
+ } else if (existsSync17(join25(cwd, "pyproject.toml")) || existsSync17(join25(cwd, "requirements.txt"))) {
6451
+ if (!facts.description) facts.description = "Python project";
6452
+ } else if (existsSync17(join25(cwd, "Cargo.toml"))) {
6453
+ facts.commands.push({ label: "build", cmd: "cargo build" }, { label: "test", cmd: "cargo test" });
6454
+ } else if (existsSync17(join25(cwd, "go.mod"))) {
6455
+ facts.commands.push({ label: "build", cmd: "go build ./..." }, { label: "test", cmd: "go test ./..." });
6456
+ }
6457
+ const langCount = /* @__PURE__ */ new Map();
6458
+ const tallyExt = (file6) => {
6459
+ const ext = file6.split(".").pop()?.toLowerCase();
6460
+ const lang = ext && CODE_EXT[ext];
6461
+ if (lang) langCount.set(lang, (langCount.get(lang) ?? 0) + 1);
6462
+ };
6463
+ let entries = [];
6464
+ try {
6465
+ entries = readdirSync3(cwd);
6466
+ } catch {
6467
+ }
6468
+ for (const e of entries) {
6469
+ const full = join25(cwd, e);
6470
+ let isDir = false;
6471
+ try {
6472
+ isDir = statSync3(full).isDirectory();
6473
+ } catch {
6474
+ continue;
6475
+ }
6476
+ if (isDir) {
6477
+ if (SKIP_DIRS.has(e) || e.startsWith(".")) continue;
6478
+ facts.topDirs.push(e);
6479
+ try {
6480
+ for (const f of readdirSync3(full)) {
6481
+ try {
6482
+ if (statSync3(join25(full, f)).isFile()) tallyExt(f);
6483
+ } catch {
6484
+ }
6485
+ }
6486
+ } catch {
6487
+ }
6488
+ } else {
6489
+ tallyExt(e);
6490
+ }
6491
+ }
6492
+ facts.languages = [...langCount.entries()].sort((a, b) => b[1] - a[1]).map(([l]) => l);
6493
+ facts.topDirs.sort();
6494
+ return facts;
6495
+ }
6496
+ function generateAgentsMd(cwd) {
6497
+ const f = detectProject(cwd);
6498
+ const lines = [];
6499
+ lines.push(`# ${f.name}`, "");
6500
+ lines.push(f.description ?? "_One-line description of what this project does._", "");
6501
+ lines.push("## Stack");
6502
+ lines.push(f.languages.length ? `- Languages: ${f.languages.join(", ")}` : "- Languages: _add the main languages_");
6503
+ if (f.topDirs.length) lines.push(`- Layout: ${f.topDirs.map((d) => `\`${d}/\``).join(", ")}`);
6504
+ lines.push("");
6505
+ lines.push("## Commands");
6506
+ if (f.commands.length) for (const c of f.commands) lines.push(`- ${c.label}: \`${c.cmd}\``);
6507
+ else lines.push("- _add build/test/run commands here_");
6508
+ lines.push("");
6509
+ lines.push("## Conventions");
6510
+ lines.push("- _House rules for this repo: style, patterns to follow, things never to touch._");
6511
+ lines.push("- _ORIRO reads this file automatically each session \u2014 keep it short and current._");
6512
+ lines.push("");
6513
+ return lines.join("\n");
6514
+ }
6515
+ function writeAgentsMd(cwd = process.cwd(), force = false) {
6516
+ const path = join25(cwd, "AGENTS.md");
6517
+ const facts = detectProject(cwd);
6518
+ if (existsSync17(path) && !force) return { path, created: false, facts };
6519
+ writeFileSync19(path, generateAgentsMd(cwd), "utf8");
6520
+ return { path, created: true, facts };
6521
+ }
6522
+
6523
+ // src/repl-ui/slash-init.ts
6524
+ function isInitSlash(cmd) {
6525
+ return /^\/init(\s|$)/i.test(cmd.trim());
6526
+ }
6527
+ function handleInit(cmd, cwd = process.cwd()) {
6528
+ const force = /(^|\s)--force(\s|$)/i.test(cmd);
6529
+ let res;
6530
+ try {
6531
+ res = writeAgentsMd(cwd, force);
6532
+ } catch (e) {
6533
+ return [` ${fgHex(PALETTE.error, "init failed")}: ${dim(e instanceof Error ? e.message : String(e))}`];
6534
+ }
6535
+ const lines = [];
6536
+ if (!res.created) {
6537
+ lines.push(` ${dim("AGENTS.md already exists")} ${accent(res.path)} ${dim("\u2014 use /init --force to overwrite.")}`);
6538
+ return lines;
6539
+ }
6540
+ const f = res.facts;
6541
+ lines.push(` ${fgHex(PALETTE.success, "\u2713 wrote")} ${accent(res.path)}`);
6542
+ 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"}` : ""}`));
6543
+ lines.push(dim(" edit it to add house rules \u2014 ORIRO reads it automatically each session."));
6544
+ return lines;
6545
+ }
6546
+
6366
6547
  // src/repl-ui/tui-repl.ts
6367
6548
  var editorTheme = {
6368
6549
  borderColor: (s) => dim(s),
@@ -6444,8 +6625,8 @@ async function runTuiRepl(session) {
6444
6625
  if (slash === "/help" || slash === "/?") {
6445
6626
  const help = [
6446
6627
  " Just type to chat \u2014 ORIRO writes and runs code for you (keyless, free).",
6447
- ` ${accent("/routers")} pool add\xB7rotate ${accent("/model")} <id\u2026> switch ${accent("/usage")} health ${accent("/trace")} tool+router activity`,
6448
- ` ${accent("/review")} artifacts from the last reply ${accent("/save")} <n> [path] ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
6628
+ ` ${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 from the last reply ${accent("/save")} <n> [path] ${accent("/init")} write AGENTS.md ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
6449
6630
  ` ${dim("Shift+Tab")} posture ${dim("Alt+Shift+T")} thinking ${accent("/help")} ${accent("/exit")}`
6450
6631
  ].join("\n");
6451
6632
  chat.addChild(new Text(help, 0, 0));
@@ -6496,6 +6677,24 @@ async function runTuiRepl(session) {
6496
6677
  tui.requestRender();
6497
6678
  return;
6498
6679
  }
6680
+ if (isCompactSlash(slash)) {
6681
+ editor.setText("");
6682
+ const pending = new Text(dim(" compacting\u2026"), 0, 0);
6683
+ chat.addChild(pending);
6684
+ tui.requestRender();
6685
+ void (async () => {
6686
+ const lines = await handleCompact(session, text);
6687
+ pending.setText(lines.join("\n"));
6688
+ tui.requestRender();
6689
+ })();
6690
+ return;
6691
+ }
6692
+ if (isInitSlash(slash)) {
6693
+ chat.addChild(new Text(handleInit(text).join("\n"), 0, 0));
6694
+ editor.setText("");
6695
+ tui.requestRender();
6696
+ return;
6697
+ }
6499
6698
  if (slash === "/voice") {
6500
6699
  editor.setText("");
6501
6700
  const status = new Text(dim(" \u{1F399} listening\u2026 (needs ffmpeg + the transformers voice peer)"), 0, 0);
@@ -6587,8 +6786,8 @@ ${english}`;
6587
6786
  // src/voice/mic.ts
6588
6787
  import { spawn as spawn3 } from "child_process";
6589
6788
  import { tmpdir as tmpdir3 } from "os";
6590
- import { join as join25 } from "path";
6591
- import { existsSync as existsSync17, statSync as statSync3 } from "fs";
6789
+ import { join as join26 } from "path";
6790
+ import { existsSync as existsSync18, statSync as statSync4 } from "fs";
6592
6791
  function recorders(outFile, seconds) {
6593
6792
  const dur = String(seconds);
6594
6793
  if (process.platform === "darwin") {
@@ -6609,12 +6808,12 @@ function recorders(outFile, seconds) {
6609
6808
  ];
6610
6809
  }
6611
6810
  async function recordMic(seconds = 6) {
6612
- const outFile = join25(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
6811
+ const outFile = join26(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
6613
6812
  for (const r of recorders(outFile, seconds)) {
6614
6813
  const okFile = await new Promise((resolve3) => {
6615
6814
  const child = spawn3(r.cmd, r.args, { stdio: "ignore" });
6616
6815
  child.on("error", () => resolve3(false));
6617
- child.on("close", (code) => resolve3(code === 0 && existsSync17(outFile) && statSync3(outFile).size > 44));
6816
+ child.on("close", (code) => resolve3(code === 0 && existsSync18(outFile) && statSync4(outFile).size > 44));
6618
6817
  });
6619
6818
  if (okFile) return outFile;
6620
6819
  }
@@ -6679,8 +6878,9 @@ function replHelp() {
6679
6878
  ${dim("Just type to chat; ORIRO writes and runs code for you (keyless, free).")}
6680
6879
 
6681
6880
  ${dim("Models & routers")} ${accent("/routers")} list\xB7add\xB7rotate the racing pool ${accent("/model")} <id\u2026> switch
6682
- ${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} show tool + router activity
6881
+ ${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} show tool + router activity ${accent("/compact")} free context
6683
6882
  ${dim("Artifacts")} ${accent("/review")} code/SVG from the last reply ${accent("/save")} <n> [path] write one
6883
+ ${dim("Project")} ${accent("/init")} write a starter AGENTS.md ORIRO reads each session
6684
6884
  ${dim("Capabilities")} ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")} speak a turn
6685
6885
  ${dim("General")} ${accent("/help")} this ${accent("/exit")} / ${accent("/quit")} leave ${dim("(Ctrl-D / Ctrl-C also exit)")}
6686
6886
 
@@ -6756,6 +6956,14 @@ async function runReadlineRepl(session) {
6756
6956
  `);
6757
6957
  continue;
6758
6958
  }
6959
+ if (isCompactSlash(slash)) {
6960
+ stdout7.write((await handleCompact(session, line)).join("\n") + "\n");
6961
+ continue;
6962
+ }
6963
+ if (isInitSlash(slash)) {
6964
+ stdout7.write(handleInit(line).join("\n") + "\n");
6965
+ continue;
6966
+ }
6759
6967
  if (isArtifactSlash(slash)) {
6760
6968
  stdout7.write(handleArtifactSlash(line).join("\n") + "\n");
6761
6969
  continue;
@@ -6883,8 +7091,8 @@ async function confirmDestructive(what, opts = {}) {
6883
7091
  }
6884
7092
 
6885
7093
  // src/config/store.ts
6886
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16 } from "fs";
6887
- import { join as join26 } from "path";
7094
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
7095
+ import { join as join27 } from "path";
6888
7096
  var KEYS = {
6889
7097
  output: {
6890
7098
  desc: "default output format for list commands: text | json | csv",
@@ -6906,13 +7114,13 @@ function validateConfig(key, value) {
6906
7114
  return KEYS[key].validate?.(value) ?? null;
6907
7115
  }
6908
7116
  function file4() {
6909
- return join26(oriroDir(), "config.json");
7117
+ return join27(oriroDir(), "config.json");
6910
7118
  }
6911
7119
  var cache = null;
6912
7120
  function readAll() {
6913
7121
  if (cache) return cache;
6914
7122
  try {
6915
- const v = JSON.parse(readFileSync21(file4(), "utf8"));
7123
+ const v = JSON.parse(readFileSync22(file4(), "utf8"));
6916
7124
  cache = v && typeof v === "object" ? v : {};
6917
7125
  } catch {
6918
7126
  cache = {};
@@ -6928,7 +7136,7 @@ function configAll() {
6928
7136
  function configSet(key, value) {
6929
7137
  const all = { ...readAll(), [key]: value };
6930
7138
  mkdirSync16(oriroDir(), { recursive: true });
6931
- writeFileSync19(file4(), JSON.stringify(all, null, 2), "utf8");
7139
+ writeFileSync20(file4(), JSON.stringify(all, null, 2), "utf8");
6932
7140
  cache = all;
6933
7141
  }
6934
7142
  function configUnset(key) {
@@ -6936,7 +7144,7 @@ function configUnset(key) {
6936
7144
  if (!(key in all)) return false;
6937
7145
  const rest = { ...all };
6938
7146
  delete rest[key];
6939
- writeFileSync19(file4(), JSON.stringify(rest, null, 2), "utf8");
7147
+ writeFileSync20(file4(), JSON.stringify(rest, null, 2), "utf8");
6940
7148
  cache = rest;
6941
7149
  return true;
6942
7150
  }
@@ -7079,10 +7287,10 @@ function registerRoutersCommand(program2) {
7079
7287
  }
7080
7288
 
7081
7289
  // src/commands/scribe.ts
7082
- import { readFileSync as readFileSync23 } from "fs";
7290
+ import { readFileSync as readFileSync24 } from "fs";
7083
7291
 
7084
7292
  // src/scribe/transcript.ts
7085
- import { existsSync as existsSync19, readFileSync as readFileSync22 } from "fs";
7293
+ import { existsSync as existsSync20, readFileSync as readFileSync23 } from "fs";
7086
7294
  function parseHookStdin(raw) {
7087
7295
  try {
7088
7296
  const j = JSON.parse(raw);
@@ -7115,8 +7323,8 @@ function isHumanUser(e) {
7115
7323
  }
7116
7324
  var FILE_KEYS = ["file_path", "path", "notebook_path", "filePath"];
7117
7325
  function lastTurnFromTranscript(path) {
7118
- if (!existsSync19(path)) return null;
7119
- const raw = readFileSync22(path, "utf8");
7326
+ if (!existsSync20(path)) return null;
7327
+ const raw = readFileSync23(path, "utf8");
7120
7328
  const entries = [];
7121
7329
  for (const line of raw.split("\n")) {
7122
7330
  if (!line.trim()) continue;
@@ -7177,7 +7385,7 @@ function lastTurnFromTranscript(path) {
7177
7385
  // src/commands/scribe.ts
7178
7386
  function readStdin() {
7179
7387
  try {
7180
- return readFileSync23(0, "utf8");
7388
+ return readFileSync24(0, "utf8");
7181
7389
  } catch {
7182
7390
  return "";
7183
7391
  }
@@ -7487,14 +7695,14 @@ function registerConnectorsCommand(program2) {
7487
7695
  }
7488
7696
 
7489
7697
  // src/channels/config.ts
7490
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync20 } from "fs";
7491
- import { join as join27 } from "path";
7698
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
7699
+ import { join as join28 } from "path";
7492
7700
  function file5() {
7493
- return join27(oriroDir(), "channels.json");
7701
+ return join28(oriroDir(), "channels.json");
7494
7702
  }
7495
7703
  function readChannels() {
7496
7704
  try {
7497
- const v = JSON.parse(readFileSync24(file5(), "utf8"));
7705
+ const v = JSON.parse(readFileSync25(file5(), "utf8"));
7498
7706
  return Array.isArray(v) ? v : [];
7499
7707
  } catch {
7500
7708
  return [];
@@ -7503,10 +7711,10 @@ function readChannels() {
7503
7711
  function saveChannel(cfg) {
7504
7712
  const all = readChannels().filter((c) => c.kind !== cfg.kind);
7505
7713
  all.push(cfg);
7506
- writeFileSync20(join27(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
7714
+ writeFileSync21(join28(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
7507
7715
  }
7508
7716
  function removeChannel(kind) {
7509
- writeFileSync20(join27(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
7717
+ writeFileSync21(join28(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
7510
7718
  }
7511
7719
 
7512
7720
  // src/channels/telegram.ts
@@ -7623,9 +7831,9 @@ async function startDiscord(token) {
7623
7831
  }
7624
7832
 
7625
7833
  // src/channels/whatsapp.ts
7626
- import { join as join28 } from "path";
7834
+ import { join as join29 } from "path";
7627
7835
  function whatsappAuthDir() {
7628
- return join28(oriroDir(), "whatsapp-auth");
7836
+ return join29(oriroDir(), "whatsapp-auth");
7629
7837
  }
7630
7838
  async function startWhatsApp() {
7631
7839
  let baileys;
@@ -7743,8 +7951,8 @@ function registerChannelsCommand(program2) {
7743
7951
  }
7744
7952
 
7745
7953
  // src/commands/skills.ts
7746
- import { existsSync as existsSync20, statSync as statSync4, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
7747
- import { resolve as resolve2, join as join29, basename, dirname as dirname4 } from "path";
7954
+ import { existsSync as existsSync21, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
7955
+ import { resolve as resolve2, join as join30, basename as basename2, dirname as dirname4 } from "path";
7748
7956
  function registerSkillsCommand(program2) {
7749
7957
  const skills = program2.command("skills").description("the ORIRO skill library \u2014 bundled + your own");
7750
7958
  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) => {
@@ -7776,28 +7984,28 @@ function registerSkillsCommand(program2) {
7776
7984
  });
7777
7985
  skills.command("add <path>").description("add your own skill \u2014 a folder containing SKILL.md, or a SKILL.md file").action((p) => {
7778
7986
  const src = resolve2(p);
7779
- if (!existsSync20(src)) die(`not found: ${src}`);
7987
+ if (!existsSync21(src)) die(`not found: ${src}`);
7780
7988
  const dest = userSkillsDir();
7781
7989
  mkdirSync17(dest, { recursive: true });
7782
- const st = statSync4(src);
7990
+ const st = statSync5(src);
7783
7991
  if (st.isDirectory()) {
7784
- if (!existsSync20(join29(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
7785
- const name = basename(src);
7786
- cpSync(src, join29(dest, name), { recursive: true });
7787
- ok(`added skill ${accent(name)} \u2192 ${join29(dest, name)}`);
7788
- } else if (basename(src).toLowerCase() === "skill.md") {
7789
- const name = basename(dirname4(src)) || "custom-skill";
7790
- mkdirSync17(join29(dest, name), { recursive: true });
7791
- cpSync(src, join29(dest, name, "SKILL.md"));
7792
- ok(`added skill ${accent(name)} \u2192 ${join29(dest, name)}`);
7992
+ if (!existsSync21(join30(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
7993
+ const name = basename2(src);
7994
+ cpSync(src, join30(dest, name), { recursive: true });
7995
+ ok(`added skill ${accent(name)} \u2192 ${join30(dest, name)}`);
7996
+ } else if (basename2(src).toLowerCase() === "skill.md") {
7997
+ const name = basename2(dirname4(src)) || "custom-skill";
7998
+ mkdirSync17(join30(dest, name), { recursive: true });
7999
+ cpSync(src, join30(dest, name, "SKILL.md"));
8000
+ ok(`added skill ${accent(name)} \u2192 ${join30(dest, name)}`);
7793
8001
  } else {
7794
8002
  die("expected a folder containing SKILL.md, or a SKILL.md file");
7795
8003
  }
7796
8004
  info("It loads on next launch \u2014 and is available in chat via /skill.");
7797
8005
  });
7798
8006
  skills.command("remove <name>").description("remove a skill you added").option("-f, --force", "skip the confirmation prompt").action(async (name, opts) => {
7799
- const target = join29(userSkillsDir(), name);
7800
- if (!existsSync20(target)) {
8007
+ const target = join30(userSkillsDir(), name);
8008
+ if (!existsSync21(target)) {
7801
8009
  info(`'${name}' is not a user-added skill \u2014 nothing to remove`);
7802
8010
  return;
7803
8011
  }
@@ -7980,7 +8188,7 @@ function registerVoiceCommand(program2) {
7980
8188
  }
7981
8189
 
7982
8190
  // src/agents/catalog.ts
7983
- import { readFileSync as readFileSync25 } from "fs";
8191
+ import { readFileSync as readFileSync26 } from "fs";
7984
8192
  function parseAgentDef(raw, now) {
7985
8193
  if (!raw || typeof raw !== "object") return { ok: false, error: "not a JSON object" };
7986
8194
  const o = raw;
@@ -8007,7 +8215,7 @@ async function fetchAgentSource(pathOrUrl) {
8007
8215
  if (!res.ok) throw new Error(`fetch failed: HTTP ${res.status}`);
8008
8216
  return await res.json();
8009
8217
  }
8010
- return JSON.parse(readFileSync25(pathOrUrl, "utf8"));
8218
+ return JSON.parse(readFileSync26(pathOrUrl, "utf8"));
8011
8219
  }
8012
8220
  async function addAgentFromSource(pathOrUrl, now) {
8013
8221
  let raw;
@@ -8385,7 +8593,7 @@ function registerConfigCommand(program2) {
8385
8593
 
8386
8594
  // src/commands/setup.ts
8387
8595
  import { rmSync as rmSync5 } from "fs";
8388
- import { join as join30 } from "path";
8596
+ import { join as join31 } from "path";
8389
8597
  import { stdin as stdin12, stdout as stdout11 } from "process";
8390
8598
  var MARKERS = [
8391
8599
  "language.json",
@@ -8393,14 +8601,14 @@ var MARKERS = [
8393
8601
  "skills-onboarded.json",
8394
8602
  "connectors-onboarded.json",
8395
8603
  "models-onboarded.json",
8396
- join30("routers", "onboarded.json")
8604
+ join31("routers", "onboarded.json")
8397
8605
  ];
8398
8606
  function registerSetupCommand(program2) {
8399
8607
  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) => {
8400
8608
  if (opts.reset) {
8401
8609
  for (const m of MARKERS) {
8402
8610
  try {
8403
- rmSync5(join30(oriroDir(), m), { force: true });
8611
+ rmSync5(join31(oriroDir(), m), { force: true });
8404
8612
  } catch {
8405
8613
  }
8406
8614
  }
@@ -8417,15 +8625,15 @@ function registerSetupCommand(program2) {
8417
8625
  }
8418
8626
 
8419
8627
  // src/commands/import.ts
8420
- import { existsSync as existsSync21, readFileSync as readFileSync26, readdirSync as readdirSync3, statSync as statSync5, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
8421
- import { join as join31, basename as basename2 } from "path";
8628
+ 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 join32, basename as basename3 } from "path";
8422
8630
  function registerImportCommand(program2) {
8423
8631
  const imp = program2.command("import").description("migrate from another CLI (MCP servers, skills)");
8424
8632
  imp.command("mcp <file>").description("import MCP servers from a Claude-compatible mcp.json (Guardian-vetted)").action((file6) => {
8425
- if (!existsSync21(file6)) die(`no such file: ${file6}`);
8633
+ if (!existsSync22(file6)) die(`no such file: ${file6}`);
8426
8634
  let servers;
8427
8635
  try {
8428
- const j = JSON.parse(readFileSync26(file6, "utf8"));
8636
+ const j = JSON.parse(readFileSync27(file6, "utf8"));
8429
8637
  servers = j.mcpServers ?? j.servers ?? {};
8430
8638
  } catch (e) {
8431
8639
  die(`could not parse ${file6}: ${e instanceof Error ? e.message : String(e)}`);
@@ -8471,15 +8679,15 @@ function registerImportCommand(program2) {
8471
8679
  info(`${imported} imported \xB7 ${blocked2} blocked${imported ? ` \u2014 they connect in-session; see \`oriro connectors custom\`` : ""}`);
8472
8680
  });
8473
8681
  imp.command("skills <dir>").description("import SKILL.md skill folders from another CLI's skills directory").action((dir) => {
8474
- if (!existsSync21(dir) || !statSync5(dir).isDirectory()) die(`no such directory: ${dir}`);
8682
+ if (!existsSync22(dir) || !statSync6(dir).isDirectory()) die(`no such directory: ${dir}`);
8475
8683
  const dest = userSkillsDir();
8476
8684
  mkdirSync18(dest, { recursive: true });
8477
8685
  heading("Import skills");
8478
- 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")));
8686
+ const sources = existsSync22(join32(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join32(dir, e)).filter((p) => statSync6(p).isDirectory() && existsSync22(join32(p, "SKILL.md")));
8479
8687
  let n = 0;
8480
8688
  for (const src of sources) {
8481
- cpSync2(src, join31(dest, basename2(src)), { recursive: true });
8482
- process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename2(src))}
8689
+ cpSync2(src, join32(dest, basename3(src)), { recursive: true });
8690
+ process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename3(src))}
8483
8691
  `);
8484
8692
  n++;
8485
8693
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oriro/orirocli",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
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",
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",
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"