@oriro/orirocli 0.1.1 → 0.1.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 (3) hide show
  1. package/README.md +4 -4
  2. package/dist/cli.js +122 -3
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -2,20 +2,20 @@
2
2
 
3
3
  # ORIRO‑Terminal - **“Head | Memory | Eyeball for AI”**
4
4
 
5
- # **FREE TIER | BYOK** | **Works in 99 Languages | Live Security Gaurdian-V3 (MCP Watch)**
5
+ # **FREE TIER | BYOK** | **Works in 100 Languages | Live Security Gaurdian-V3 (MCP Watch)**
6
6
 
7
- A terminal coder that **sees the web**, **speaks and listens in 99 languages**, **guards itself**, and can wear a **floating avatar that talks back in its own voice** — all on‑device.
7
+ A terminal coder that **sees the web**, **speaks and listens in 100 languages**, **guards itself**, and can wear a **floating avatar that talks back in its own voice** — all on‑device.
8
8
 
9
9
  A free, on-device-friendly terminal AI agent — built on the Pi agent harness (used as a library).
10
10
  Your language, your machine, no paid keys required.
11
11
 
12
12
  ## What's inside
13
13
  - **Keyless free-router Mux** — best-router selection + invisible failover across free providers, with an on-device floor. Never a paid key.
14
- - **99 languages** — type in your language; the model reasons in English; replies come back translated (on-device NLLB).
14
+ - **100 languages** — type in your language; the model reasons in English; replies come back translated (on-device NLLB).
15
15
  - **Guardian V3** — a security gate on every tool call, default-on, fail-closed.
16
16
  - **Head** — goes out, inspects a live site, and reverse-engineers it to code.
17
17
  - **Scriber** — a consent-gated, self-healing local work journal (never leaves your machine).
18
- - **322 skills**, **multi-agent orchestration** on the free pool, and **MCP connectors**.
18
+ - **323 skills**, **multi-agent orchestration** on the free pool, and **MCP connectors**.
19
19
  - **Channels** — drive ORIRO from Telegram/Discord/WhatsApp with your own bot.
20
20
 
21
21
  ## Install
package/dist/cli.js CHANGED
@@ -854,6 +854,10 @@ function avatarsInCategory(category) {
854
854
  const c = category.toLowerCase();
855
855
  return AVATARS.filter((a) => a.category.toLowerCase() === c);
856
856
  }
857
+ function avatarBySlug(slug) {
858
+ const s = (slug || "").toLowerCase();
859
+ return AVATARS.find((a) => a.slug.toLowerCase() === s);
860
+ }
857
861
  function avatarImageUrl(a) {
858
862
  return a.image_url.startsWith("http") ? a.image_url : `${AVATAR_ORIGIN}${a.image_url}`;
859
863
  }
@@ -877,6 +881,10 @@ function writeAvatarConfig(cfg) {
877
881
  function isAvatarConfigured() {
878
882
  return readAvatarConfig() !== null;
879
883
  }
884
+ function getSelectedAvatar() {
885
+ const cfg = readAvatarConfig();
886
+ return cfg && avatarBySlug(cfg.slug) || null;
887
+ }
880
888
  function setSelectedAvatar(avatar, opts) {
881
889
  const cfg = {
882
890
  slug: avatar.slug,
@@ -1519,6 +1527,48 @@ function scrubMessageIdentity(msg) {
1519
1527
  };
1520
1528
  }
1521
1529
 
1530
+ // src/routers/tool-sanitize.ts
1531
+ var CONTROL_TOKEN = /<\|[^|]*\|>/g;
1532
+ var RECIPIENT_PREFIX = /^(?:to=)?(?:functions?|tools?|recipient)[.=]/i;
1533
+ var RECIPIENT = /(?:to=)?(?:functions?|tools?|recipient)[.=]([A-Za-z0-9_.:-]+)/i;
1534
+ var CLEAN_NAME = /^[A-Za-z0-9_.:-]+$/;
1535
+ function sanitizeToolName(raw) {
1536
+ if (!raw) return raw;
1537
+ if (!raw.includes("<|") && !RECIPIENT_PREFIX.test(raw)) return raw;
1538
+ const base = (raw.split("<|")[0] ?? "").replace(RECIPIENT_PREFIX, "").trim();
1539
+ if (base && CLEAN_NAME.test(base)) return base;
1540
+ const recip = raw.match(RECIPIENT);
1541
+ if (recip?.[1]) return recip[1];
1542
+ const m = raw.replace(CONTROL_TOKEN, " ").match(/[A-Za-z_][A-Za-z0-9_.:-]*/);
1543
+ return m ? m[0] : raw;
1544
+ }
1545
+ function sanitizeMessageToolCalls(msg) {
1546
+ let changed = false;
1547
+ const content = msg.content.map((c) => {
1548
+ if (c.type === "toolCall") {
1549
+ const name = sanitizeToolName(c.name);
1550
+ if (name !== c.name) {
1551
+ changed = true;
1552
+ return { ...c, name };
1553
+ }
1554
+ }
1555
+ return c;
1556
+ });
1557
+ return changed ? { ...msg, content } : msg;
1558
+ }
1559
+ function sanitizeEventToolCalls(ev) {
1560
+ let next = ev;
1561
+ if ("partial" in next && next.partial) {
1562
+ const partial = sanitizeMessageToolCalls(next.partial);
1563
+ if (partial !== next.partial) next = { ...next, partial };
1564
+ }
1565
+ if (next.type === "toolcall_end" && next.toolCall) {
1566
+ const name = sanitizeToolName(next.toolCall.name);
1567
+ if (name !== next.toolCall.name) next = { ...next, toolCall: { ...next.toolCall, name } };
1568
+ }
1569
+ return next;
1570
+ }
1571
+
1522
1572
  // src/routers/mux-provider.ts
1523
1573
  var MUX_PROVIDER = "oriro-mux";
1524
1574
  var MUX_MODEL = "oriro-free";
@@ -1568,17 +1618,17 @@ async function driveMux(out, mux, byId, context, options) {
1568
1618
  committed = true;
1569
1619
  if (ev.type === "done") {
1570
1620
  mux.recordSuccess(id, Date.now() - t0);
1571
- const clean = scrubMessageIdentity(ev.message);
1621
+ const clean = sanitizeMessageToolCalls(scrubMessageIdentity(ev.message));
1572
1622
  out.push({ type: "done", reason: ev.reason, message: clean });
1573
1623
  out.end(clean);
1574
1624
  return;
1575
1625
  }
1576
1626
  lastPartial = ev.partial;
1577
- out.push(ev);
1627
+ out.push(sanitizeEventToolCalls(ev));
1578
1628
  }
1579
1629
  if (failedBeforeContent) continue;
1580
1630
  mux.recordSuccess(id, Date.now() - t0);
1581
- out.end(lastPartial ? scrubMessageIdentity(lastPartial) : void 0);
1631
+ out.end(lastPartial ? sanitizeMessageToolCalls(scrubMessageIdentity(lastPartial)) : void 0);
1582
1632
  return;
1583
1633
  } catch (e) {
1584
1634
  mux.recordFailure(id, e);
@@ -4543,6 +4593,73 @@ function registerSkillsCommand(program2) {
4543
4593
  });
4544
4594
  }
4545
4595
 
4596
+ // src/commands/language.ts
4597
+ import { stdin as stdin5 } from "process";
4598
+ function registerLanguageCommand(program2) {
4599
+ program2.command("language").description("show or change your terminal language").argument("[code]", "switch directly to this language (ISO code or name, e.g. es)").option("-a, --all", "list every available language").action(async (code, opts) => {
4600
+ if (opts.all) {
4601
+ heading(`Languages (${LANGUAGES.length})`);
4602
+ for (const l of LANGUAGES) {
4603
+ const star = l.neuralVoice ? accent("\u2605") : " ";
4604
+ process.stdout.write(` ${star} ${l.name} ${dim(`(${l.code})`)}
4605
+ `);
4606
+ }
4607
+ return;
4608
+ }
4609
+ if (code) {
4610
+ const lang = languageByCode(code);
4611
+ if (!lang) die(`unknown language '${code}' \u2014 run \`oriro language --all\` to see the list`);
4612
+ setTerminalLanguage(lang);
4613
+ ok(`${accent(lang.name)} is now your terminal language.`);
4614
+ return;
4615
+ }
4616
+ if (stdin5.isTTY) {
4617
+ const lang = await selectLanguageInteractive();
4618
+ setTerminalLanguage(lang);
4619
+ ok(`${accent(lang.name)} is now your terminal language.`);
4620
+ } else {
4621
+ const cur = getTerminalLanguage();
4622
+ info(`terminal language: ${accent(cur.name)} ${dim(`(${cur.code})`)}`);
4623
+ info(dim("change it with `oriro language <code>` (e.g. `oriro language es`) or `oriro language --all`"));
4624
+ }
4625
+ });
4626
+ }
4627
+
4628
+ // src/commands/avatar.ts
4629
+ import { stdin as stdin6 } from "process";
4630
+ function registerAvatarCommand(program2) {
4631
+ program2.command("avatar").description("show or change your terminal avatar").argument("[slug]", "set directly to this avatar slug").option("-l, --list", "list every avatar by category").action(async (slug, opts) => {
4632
+ if (opts.list) {
4633
+ for (const cat of avatarCategories()) {
4634
+ heading(cat);
4635
+ for (const a of avatarsInCategory(cat)) process.stdout.write(` ${accent(a.slug)}
4636
+ `);
4637
+ }
4638
+ return;
4639
+ }
4640
+ if (slug) {
4641
+ const avatar = avatarBySlug(slug);
4642
+ if (!avatar) die(`unknown avatar '${slug}' \u2014 run \`oriro avatar --list\` to see the faces`);
4643
+ setSelectedAvatar(avatar, { speak: true });
4644
+ ok(`${accent(avatar.slug)} is now your terminal face.`);
4645
+ return;
4646
+ }
4647
+ if (stdin6.isTTY) {
4648
+ const chosen = await selectAvatarInteractive();
4649
+ if (!chosen) {
4650
+ info("no change.");
4651
+ return;
4652
+ }
4653
+ setSelectedAvatar(chosen, { speak: true });
4654
+ await previewAvatar(chosen);
4655
+ } else {
4656
+ const cur = getSelectedAvatar();
4657
+ info(cur ? `terminal face: ${accent(cur.slug)}` : "no avatar set yet");
4658
+ info(dim("change it with `oriro avatar <slug>` or `oriro avatar --list`"));
4659
+ }
4660
+ });
4661
+ }
4662
+
4546
4663
  // src/cli.ts
4547
4664
  var version = createRequire(import.meta.url)("../package.json").version;
4548
4665
  var program = new Command();
@@ -4561,6 +4678,8 @@ registerScribeCommand(program);
4561
4678
  registerConnectorsCommand(program);
4562
4679
  registerChannelsCommand(program);
4563
4680
  registerSkillsCommand(program);
4681
+ registerLanguageCommand(program);
4682
+ registerAvatarCommand(program);
4564
4683
  program.parseAsync().catch((e) => {
4565
4684
  process.stderr.write(`
4566
4685
  ORIRO error: ${e instanceof Error ? e.stack ?? e.message : String(e)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oriro/orirocli",
3
- "version": "0.1.1",
3
+ "version": "0.1.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": {
@@ -22,8 +22,9 @@
22
22
  "dev": "tsx src/cli.ts",
23
23
  "build": "tsup",
24
24
  "typecheck": "tsc --noEmit",
25
+ "test:unit": "tsx scripts/test-tool-sanitize.ts",
25
26
  "smoke": "npm run build && node scripts/smoke.mjs",
26
- "prepublishOnly": "npm run build && node scripts/smoke.mjs && node scripts/prepublish-check.mjs",
27
+ "prepublishOnly": "npm run build && npm run test:unit && node scripts/smoke.mjs && node scripts/prepublish-check.mjs",
27
28
  "start": "node dist/cli.js"
28
29
  },
29
30
  "dependencies": {