@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.
- package/README.md +4 -4
- package/dist/cli.js +122 -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
|
|
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
|
|
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
|
-
- **
|
|
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
|
-
- **
|
|
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.
|
|
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": {
|