@oriro/orirocli 0.1.5 → 0.1.6

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 +47 -4
  2. package/dist/cli.js +2331 -1891
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,8 +5,8 @@ import { createRequire } from "module";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/repl.ts
8
- import { createInterface as createInterface4 } from "readline/promises";
9
- import { stdin as stdin4, stdout as stdout5 } from "process";
8
+ import { createInterface as createInterface5 } from "readline/promises";
9
+ import { stdin as stdin5, stdout as stdout6 } from "process";
10
10
 
11
11
  // src/ui/theme.ts
12
12
  var PALETTE = {
@@ -71,8 +71,8 @@ ${tagline}
71
71
  }
72
72
 
73
73
  // src/onboarding/wrapper.ts
74
- import { createInterface as createInterface3 } from "readline/promises";
75
- import { stdin as stdin3, stdout as stdout4 } from "process";
74
+ import { createInterface as createInterface4 } from "readline/promises";
75
+ import { stdin as stdin4, stdout as stdout5 } from "process";
76
76
 
77
77
  // src/language/languages.ts
78
78
  var LANGUAGES = [
@@ -1327,1819 +1327,25 @@ function hasScribeChoice() {
1327
1327
  }
1328
1328
  }
1329
1329
 
1330
- // src/onboarding/wrapper.ts
1331
- function isFirstRun() {
1332
- return !isLanguageConfigured() || !hasScribeChoice();
1333
- }
1334
- async function askYesNo(question) {
1335
- const rl = createInterface3({ input: stdin3, output: stdout4 });
1336
- try {
1337
- const a = (await ask(rl, `${question} ${dim("[Y/n]")} `)).trim().toLowerCase();
1338
- return a === "" || a === "y" || a === "yes";
1339
- } finally {
1340
- rl.close();
1341
- }
1342
- }
1343
- async function runOnboarding() {
1344
- stdout4.write(banner());
1345
- await runLanguageOnboarding();
1346
- await activateGuardian();
1347
- stdout4.write(` ${accent("\u{1F6E1} Guardian V3")} is on by default. ${accent("\u{1F9ED} Head")} is ready.
1348
-
1349
- `);
1350
- if (!isAvatarConfigured()) await runAvatarOnboarding();
1351
- if (!hasScribeChoice()) {
1352
- const yes = await askYesNo(
1353
- "Remember with me? The Scriber keeps your work in context on THIS machine only \u2014 it never leaves it."
1354
- );
1355
- setScribeConsent(yes);
1356
- stdout4.write(yes ? ` ${accent("\u{1F4D3} Scriber")} on.
1357
- ` : ` ${dim("Scriber off \u2014 `oriro scribe on` anytime.")}
1358
- `);
1359
- }
1360
- stdout4.write(`
1361
- ${accent("ORIRO is ready.")} ${dim("Type to chat \xB7 /exit to leave")}
1362
-
1363
- `);
1364
- }
1365
-
1366
- // src/onboarding/assemble.ts
1367
- import {
1368
- createAgentSession as createAgentSession2,
1369
- AuthStorage as AuthStorage2,
1370
- ModelRegistry as ModelRegistry2,
1371
- SessionManager as SessionManager2,
1372
- SettingsManager,
1373
- DefaultResourceLoader,
1374
- getAgentDir
1375
- } from "@earendil-works/pi-coding-agent";
1376
-
1377
- // src/routers/mux-provider.ts
1378
- import { streamSimple as piStreamSimple, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
1379
- import { register as registerOpenAICompletions } from "@earendil-works/pi-ai/openai-completions";
1380
-
1381
- // src/routers/mux.ts
1382
- import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
1383
- import { join as join10 } from "path";
1384
- var COOLDOWN_DEFAULT_MS = 6e4;
1385
- var UNHEALTHY_AFTER = 3;
1386
- var RouterMux = class {
1387
- stats = /* @__PURE__ */ new Map();
1388
- now;
1389
- constructor(routerIds, now = () => Date.now()) {
1390
- this.now = now;
1391
- for (const id of routerIds) {
1392
- this.stats.set(id, {
1393
- id,
1394
- latencyMs: Number.POSITIVE_INFINITY,
1395
- healthy: true,
1396
- cooldownUntil: 0,
1397
- consecutiveErrors: 0
1398
- });
1399
- }
1400
- }
1401
- /** Available routers, best-first (healthy, not cooling down, lowest latency). */
1402
- ranked() {
1403
- const t = this.now();
1404
- return [...this.stats.values()].filter((s) => s.healthy && s.cooldownUntil <= t).sort((a, b) => a.latencyMs - b.latencyMs).map((s) => s.id);
1405
- }
1406
- recordSuccess(id, latencyMs) {
1407
- const s = this.stats.get(id);
1408
- if (!s) return;
1409
- s.latencyMs = s.latencyMs === Number.POSITIVE_INFINITY ? latencyMs : 0.7 * s.latencyMs + 0.3 * latencyMs;
1410
- s.consecutiveErrors = 0;
1411
- s.healthy = true;
1412
- }
1413
- recordFailure(id, err) {
1414
- const s = this.stats.get(id);
1415
- if (!s) return;
1416
- s.consecutiveErrors += 1;
1417
- if (err?.status === 429) {
1418
- s.cooldownUntil = this.now() + (err.retryAfterMs ?? COOLDOWN_DEFAULT_MS);
1419
- }
1420
- if (s.consecutiveErrors >= UNHEALTHY_AFTER) s.healthy = false;
1421
- }
1422
- /** Run a call through the best router, failing over on error. Throws only if all exhausted. */
1423
- async run(call) {
1424
- const order = this.ranked();
1425
- if (order.length === 0) {
1426
- throw new Error(
1427
- "All selected routers are rate-limited or unavailable. Add a BYOK key, select more free routers, or retry shortly."
1428
- );
1429
- }
1430
- let lastErr;
1431
- for (const id of order) {
1432
- const t0 = this.now();
1433
- try {
1434
- const result = await call(id);
1435
- this.recordSuccess(id, this.now() - t0);
1436
- return { result, routerId: id };
1437
- } catch (e) {
1438
- const err = e;
1439
- this.recordFailure(id, { status: err?.status, retryAfterMs: err?.retryAfterMs });
1440
- lastErr = e;
1441
- }
1442
- }
1443
- throw lastErr instanceof Error ? lastErr : new Error("All selected routers failed this request.");
1444
- }
1445
- snapshot() {
1446
- return [...this.stats.values()].map((s) => ({ ...s }));
1447
- }
1448
- load(stats) {
1449
- for (const s of stats) if (this.stats.has(s.id)) this.stats.set(s.id, { ...s });
1450
- }
1451
- };
1452
- function healthStatePath(dir) {
1453
- return join10(dir, "routers", "health.json");
1454
- }
1455
- function saveMuxState(dir, stats) {
1456
- const p = healthStatePath(dir);
1457
- mkdirSync5(join10(dir, "routers"), { recursive: true });
1458
- writeFileSync7(p, JSON.stringify(stats, null, 2), "utf8");
1459
- }
1460
- function loadMuxState(dir) {
1461
- const p = healthStatePath(dir);
1462
- if (!existsSync3(p)) return [];
1463
- try {
1464
- const stats = JSON.parse(readFileSync8(p, "utf8"));
1465
- return stats.map((s) => ({ ...s, latencyMs: Number.isFinite(s.latencyMs) ? s.latencyMs : Number.POSITIVE_INFINITY }));
1466
- } catch {
1467
- return [];
1468
- }
1469
- }
1330
+ // src/routers/onboarding.ts
1331
+ import { createInterface as createInterface3 } from "readline/promises";
1332
+ import { stdin as stdin3, stdout as stdout4 } from "process";
1333
+ import { existsSync as existsSync4, mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "fs";
1334
+ import { join as join12 } from "path";
1470
1335
 
1471
- // src/routers/floor.ts
1472
- var KEYLESS_FLOOR = [
1473
- {
1336
+ // src/routers/catalog.ts
1337
+ var C4 = (e) => ({
1338
+ api: "openai-completions",
1339
+ freeModels: [],
1340
+ tier: "free",
1341
+ kind: "chat",
1342
+ ...e
1343
+ });
1344
+ var ROUTER_CATALOG = [
1345
+ // ── Keyless & live-verified (works now, zero keys, through the agent) ──
1346
+ C4({
1474
1347
  id: "pollinations",
1475
- name: "Pollinations (free)",
1476
- baseUrl: "https://text.pollinations.ai/openai",
1477
- model: "openai",
1478
- apiKey: "oriro-keyless"
1479
- },
1480
- {
1481
- id: "ollama-local",
1482
- name: "Ollama (on-device)",
1483
- baseUrl: "http://localhost:11434/v1",
1484
- model: "llama3.2",
1485
- apiKey: "ollama"
1486
- }
1487
- ];
1488
- function routerModel(r) {
1489
- return {
1490
- id: r.model,
1491
- name: r.name,
1492
- api: "openai-completions",
1493
- provider: r.id,
1494
- baseUrl: r.baseUrl,
1495
- reasoning: false,
1496
- input: ["text"],
1497
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1498
- contextWindow: 128e3,
1499
- maxTokens: 4096
1500
- };
1501
- }
1502
-
1503
- // src/routers/router-pool.ts
1504
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
1505
- import { join as join12 } from "path";
1506
-
1507
- // src/routers/pool.ts
1508
- import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
1509
- import { join as join11 } from "path";
1510
- function poolFile(dir) {
1511
- return join11(dir, "routers", "selected.json");
1512
- }
1513
- function loadPool(dir) {
1514
- const p = poolFile(dir);
1515
- if (!existsSync4(p)) return [];
1516
- try {
1517
- const v = JSON.parse(readFileSync9(p, "utf8"));
1518
- return Array.isArray(v) ? v : [];
1519
- } catch {
1520
- return [];
1521
- }
1522
- }
1523
- function savePool(dir, ids) {
1524
- mkdirSync6(join11(dir, "routers"), { recursive: true });
1525
- writeFileSync8(poolFile(dir), JSON.stringify([...new Set(ids)], null, 2), "utf8");
1526
- }
1527
-
1528
- // src/routers/validate.ts
1529
- var PROBE_TIMEOUT_MS = 12e3;
1530
- async function validateRouter(entry, key, modelId) {
1531
- const model = modelId ?? entry.freeModels[0] ?? "";
1532
- const t0 = Date.now();
1533
- const controller = new AbortController();
1534
- const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
1535
- try {
1536
- let res;
1537
- if (entry.api === "google-generative-ai") {
1538
- const url = `${entry.baseUrl.replace(/\/$/, "")}/models/${model}:generateContent${key ? `?key=${encodeURIComponent(key)}` : ""}`;
1539
- res = await fetch(url, {
1540
- method: "POST",
1541
- headers: { "content-type": "application/json" },
1542
- body: JSON.stringify({ contents: [{ parts: [{ text: "ping" }] }] }),
1543
- signal: controller.signal
1544
- });
1545
- } else {
1546
- const headers = { "content-type": "application/json" };
1547
- if (key) headers.authorization = `Bearer ${key}`;
1548
- res = await fetch(`${entry.baseUrl.replace(/\/$/, "")}/chat/completions`, {
1549
- method: "POST",
1550
- headers,
1551
- body: JSON.stringify({
1552
- model,
1553
- messages: [{ role: "user", content: "ping" }],
1554
- max_tokens: 1
1555
- }),
1556
- signal: controller.signal
1557
- });
1558
- }
1559
- return {
1560
- ok: res.ok,
1561
- latencyMs: Date.now() - t0,
1562
- model,
1563
- error: res.ok ? void 0 : `HTTP ${res.status}`
1564
- };
1565
- } catch (e) {
1566
- return {
1567
- ok: false,
1568
- latencyMs: Date.now() - t0,
1569
- model,
1570
- error: e instanceof Error ? e.message : String(e)
1571
- };
1572
- } finally {
1573
- clearTimeout(timer);
1574
- }
1575
- }
1576
-
1577
- // src/routers/router-pool.ts
1578
- var KEYLESS_SENTINEL = "oriro-keyless-no-key-required";
1579
- function regFile() {
1580
- return join12(oriroDir(), "routers", "registered.json");
1581
- }
1582
- function readReg() {
1583
- try {
1584
- return JSON.parse(readFileSync10(regFile(), "utf8"));
1585
- } catch {
1586
- return {};
1587
- }
1588
- }
1589
- function writeReg(m) {
1590
- mkdirSync7(join12(oriroDir(), "routers"), { recursive: true });
1591
- writeFileSync9(regFile(), JSON.stringify(m, null, 2), "utf8");
1592
- }
1593
- async function addRouter(entry, opts) {
1594
- if (entry.comingSoon) {
1595
- return { ok: false, validation: { ok: false, latencyMs: 0, model: "", error: "coming soon" } };
1596
- }
1597
- if (entry.kind && entry.kind !== "chat") {
1598
- return { ok: false, validation: { ok: false, latencyMs: 0, model: "", error: `'${entry.id}' is a ${entry.kind} router, not a chat router` } };
1599
- }
1600
- const key = opts?.key ?? (entry.keyless ? KEYLESS_SENTINEL : void 0);
1601
- const v = await validateRouter(entry, key, opts?.modelId);
1602
- if (!v.ok) return { ok: false, validation: v };
1603
- const router = {
1604
- id: entry.id,
1605
- name: entry.displayName,
1606
- baseUrl: entry.baseUrl,
1607
- model: opts?.modelId ?? v.model ?? entry.freeModels[0] ?? "",
1608
- apiKey: key ?? KEYLESS_SENTINEL
1609
- };
1610
- const reg = readReg();
1611
- reg[entry.id] = router;
1612
- writeReg(reg);
1613
- savePool(oriroDir(), [...loadPool(oriroDir()), entry.id]);
1614
- return { ok: true, validation: v };
1615
- }
1616
- function useRouters(ids) {
1617
- const reg = readReg();
1618
- const applied = ids.filter((id) => reg[id]);
1619
- const unknown = ids.filter((id) => !reg[id]);
1620
- if (applied.length > 0) savePool(oriroDir(), applied);
1621
- return { applied, unknown };
1622
- }
1623
- function resolvePool() {
1624
- const reg = readReg();
1625
- return loadPool(oriroDir()).map((id) => reg[id]).filter((r) => Boolean(r));
1626
- }
1627
-
1628
- // src/identity/filter.ts
1629
- var ORIRO_IDENTITY = "You are ORIRO, a free on-device AI assistant in the user's terminal. You are ORIRO and only ORIRO. Never state, imply, or reveal that you are, or are built on, any other model or company (such as GPT, Claude, Gemini, Qwen, Llama, Mistral, DeepSeek, OpenAI, Anthropic, Google, or Meta). If asked what you are, you are ORIRO.";
1630
- var BANNED_TEST = /\b(qwen|llama|mistral|mixtral|deepseek|gpt(?:-?\d(?:\.\d)?)?|claude|gemini|openai|anthropic|google|meta\s?ai|alibaba)\b/i;
1631
- var BANNED_REPLACE = new RegExp(BANNED_TEST.source, "gi");
1632
- var SELF_REF = /\b(i am|i'm|i was|based on|powered by|my name|my model|my architecture|trained|created by|made by|built (?:on|by)|developed by)\b/i;
1633
- var SELF_INTRO = /\b(i am|i'm)\s+(a|an)\b/i;
1634
- var AI_NOUN = /\b(assistant|ai|model|language model|bot|agent|chatbot)\b/i;
1635
- function applyIdentity(context) {
1636
- const sys = context.systemPrompt ? `${ORIRO_IDENTITY}
1637
-
1638
- ${context.systemPrompt}` : ORIRO_IDENTITY;
1639
- return { ...context, systemPrompt: sys };
1640
- }
1641
- function scrubIdentity(text) {
1642
- return text.replace(/[^.?!\n]+[.?!]?/g, (sentence) => {
1643
- let s = SELF_REF.test(sentence) && BANNED_TEST.test(sentence) ? sentence.replace(BANNED_REPLACE, "ORIRO") : sentence;
1644
- if (!/\boriro\b/i.test(s) && SELF_INTRO.test(s) && AI_NOUN.test(s)) {
1645
- s = s.replace(SELF_INTRO, "I am ORIRO, $2");
1646
- }
1647
- return s;
1648
- });
1649
- }
1650
- function scrubMessageIdentity(msg) {
1651
- return {
1652
- ...msg,
1653
- content: msg.content.map(
1654
- (c) => c.type === "text" ? { ...c, text: scrubIdentity(c.text) } : c
1655
- )
1656
- };
1657
- }
1658
-
1659
- // src/routers/tool-sanitize.ts
1660
- var CONTROL_TOKEN = /<\|[^|]*\|>/g;
1661
- var RECIPIENT_PREFIX = /^(?:to=)?(?:functions?|tools?|recipient)[.=]/i;
1662
- var RECIPIENT = /(?:to=)?(?:functions?|tools?|recipient)[.=]([A-Za-z0-9_.:-]+)/i;
1663
- var CLEAN_NAME = /^[A-Za-z0-9_.:-]+$/;
1664
- function sanitizeToolName(raw) {
1665
- if (!raw) return raw;
1666
- if (!raw.includes("<|") && !RECIPIENT_PREFIX.test(raw)) return raw;
1667
- const base = (raw.split("<|")[0] ?? "").replace(RECIPIENT_PREFIX, "").trim();
1668
- if (base && CLEAN_NAME.test(base)) return base;
1669
- const recip = raw.match(RECIPIENT);
1670
- if (recip?.[1]) return recip[1];
1671
- const m = raw.replace(CONTROL_TOKEN, " ").match(/[A-Za-z_][A-Za-z0-9_.:-]*/);
1672
- return m ? m[0] : raw;
1673
- }
1674
- function sanitizeMessageToolCalls(msg) {
1675
- let changed = false;
1676
- const content = msg.content.map((c) => {
1677
- if (c.type === "toolCall") {
1678
- const name = sanitizeToolName(c.name);
1679
- if (name !== c.name) {
1680
- changed = true;
1681
- return { ...c, name };
1682
- }
1683
- }
1684
- return c;
1685
- });
1686
- return changed ? { ...msg, content } : msg;
1687
- }
1688
- function sanitizeEventToolCalls(ev) {
1689
- let next = ev;
1690
- if ("partial" in next && next.partial) {
1691
- const partial = sanitizeMessageToolCalls(next.partial);
1692
- if (partial !== next.partial) next = { ...next, partial };
1693
- }
1694
- if (next.type === "toolcall_end" && next.toolCall) {
1695
- const name = sanitizeToolName(next.toolCall.name);
1696
- if (name !== next.toolCall.name) next = { ...next, toolCall: { ...next.toolCall, name } };
1697
- }
1698
- return next;
1699
- }
1700
-
1701
- // src/scribe/scribe-pi.ts
1702
- import { existsSync as existsSync9, readFileSync as readFileSync16 } from "fs";
1703
- import { Type } from "typebox";
1704
-
1705
- // src/scribe/capture.ts
1706
- import { closeSync as closeSync2, fsyncSync as fsyncSync2, mkdirSync as mkdirSync10, openSync as openSync2, writeSync as writeSync2 } from "fs";
1707
- import { join as join14 } from "path";
1708
-
1709
- // src/scribe/digest.ts
1710
- import { existsSync as existsSync5, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
1711
-
1712
- // src/scribe/paths.ts
1713
- import { join as join13 } from "path";
1714
- function scribeDir() {
1715
- const override = process.env.ORIRO_SCRIBE_DIR?.trim();
1716
- return override && override.length > 0 ? override : join13(CONFIG_DIR, "scribe");
1717
- }
1718
- function journalFile(date) {
1719
- return join13(scribeDir(), `${date}.md`);
1720
- }
1721
- function digestFile() {
1722
- return join13(scribeDir(), "_digest.md");
1723
- }
1724
- function timelineFile() {
1725
- return join13(scribeDir(), "_timeline.md");
1726
- }
1727
- function artifactsDir() {
1728
- return join13(scribeDir(), "artifacts");
1729
- }
1730
-
1731
- // src/scribe/digest.ts
1732
- var DIGEST_CAP = 8192;
1733
- var TIMELINE_DAY_CAP = 400;
1734
- function read(file4) {
1735
- return existsSync5(file4) ? readFileSync11(file4, "utf8") : "";
1736
- }
1737
- function updateDigest(summary, context) {
1738
- mkdirSync8(scribeDir(), { recursive: true });
1739
- const existing = read(digestFile());
1740
- let contextBlock = context?.trim();
1741
- if (!contextBlock) {
1742
- const m = existing.match(/## Context\n([\s\S]*?)\n## /);
1743
- contextBlock = m?.[1]?.trim() ?? "_(not set yet)_";
1744
- }
1745
- const recentMatch = existing.match(/## Recent activity[^\n]*\n([\s\S]*)$/);
1746
- const priorRecent = recentMatch?.[1]?.trim() ?? "";
1747
- let recent = summary.trim() ? `- ${summary.trim()}
1748
- ${priorRecent}` : priorRecent;
1749
- const header2 = `# ORIRO Scribe \u2014 Digest
1750
-
1751
- ## Context
1752
- ${contextBlock}
1753
-
1754
- ## Recent activity (newest first)
1755
- `;
1756
- let out = header2 + recent;
1757
- while (Buffer.byteLength(out, "utf8") > DIGEST_CAP && recent.includes("\n")) {
1758
- recent = recent.slice(0, recent.lastIndexOf("\n")).trimEnd();
1759
- out = header2 + recent;
1760
- }
1761
- writeFileSync10(digestFile(), out, "utf8");
1762
- }
1763
- function updateTimeline(date, topic) {
1764
- mkdirSync8(scribeDir(), { recursive: true });
1765
- const clean = topic.replace(/\s+/g, " ").trim();
1766
- if (!clean) return;
1767
- const lines = read(timelineFile()).split("\n").filter(Boolean);
1768
- const header2 = "# ORIRO Scribe \u2014 Timeline";
1769
- const body = lines.filter((l) => l !== header2);
1770
- const idx = body.findIndex((l) => l.startsWith(`- ${date} \xB7`));
1771
- if (idx === -1) {
1772
- body.push(`- ${date} \xB7 ${clean}`.slice(0, TIMELINE_DAY_CAP + date.length + 6));
1773
- } else {
1774
- let merged = `${body[idx]}; ${clean}`;
1775
- if (merged.length > TIMELINE_DAY_CAP) merged = `${merged.slice(0, TIMELINE_DAY_CAP)}\u2026`;
1776
- body[idx] = merged;
1777
- }
1778
- body.sort();
1779
- writeFileSync10(timelineFile(), `${header2}
1780
- ${body.join("\n")}
1781
- `, "utf8");
1782
- }
1783
- function readDigest() {
1784
- return read(digestFile());
1785
- }
1786
-
1787
- // src/scribe/journal.ts
1788
- import {
1789
- closeSync,
1790
- existsSync as existsSync6,
1791
- fsyncSync,
1792
- mkdirSync as mkdirSync9,
1793
- openSync,
1794
- readFileSync as readFileSync12,
1795
- writeSync
1796
- } from "fs";
1797
- function appendJournal(date, content) {
1798
- mkdirSync9(scribeDir(), { recursive: true });
1799
- const fd = openSync(journalFile(date), "a");
1800
- try {
1801
- writeSync(fd, content.endsWith("\n") ? content : `${content}
1802
- `);
1803
- fsyncSync(fd);
1804
- } finally {
1805
- closeSync(fd);
1806
- }
1807
- }
1808
- function readJournal(date) {
1809
- const f = journalFile(date);
1810
- return existsSync6(f) ? readFileSync12(f, "utf8") : "";
1811
- }
1812
-
1813
- // src/scribe/redact.ts
1814
- var RULES = [
1815
- {
1816
- label: "private-key",
1817
- re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
1818
- },
1819
- // Lone PEM markers — a key SPLIT across fields/turns leaves only a BEGIN-head or an END-tail in
1820
- // one field. A field carrying either marker is key material: redact the marker + its adjacent body
1821
- // (forward from BEGIN, backward to END) so no sub-threshold fragment can ever sit on disk.
1822
- { label: "private-key", re: /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*/g },
1823
- { label: "private-key", re: /[\s\S]*-----END[A-Z ]*PRIVATE KEY-----/g },
1824
- { label: "anthropic-key", re: /sk-ant-[A-Za-z0-9_-]{20,}/g },
1825
- { label: "openrouter-key", re: /sk-or-v1-[A-Za-z0-9]{20,}/g },
1826
- // Stripe-style keys (sk_live_/pk_live_/rk_test_/…), underscore segments.
1827
- { label: "stripe-key", re: /\b[srp]k_(?:live|test)_[A-Za-z0-9]{16,}/g },
1828
- // Generic sk- secret keys — allow hyphenated segments (sk-live-…, sk-proj-…) so a second
1829
- // hyphen no longer breaks the match (the gap the Scriber spike caught).
1830
- { label: "secret-key-sk", re: /sk[-_][A-Za-z0-9][A-Za-z0-9-]{14,}/g },
1831
- { label: "google-key", re: /AIza[0-9A-Za-z_-]{30,}/g },
1832
- { label: "groq-key", re: /gsk_[A-Za-z0-9]{20,}/g },
1833
- { label: "github-pat", re: /github_pat_[A-Za-z0-9_]{20,}/g },
1834
- { label: "github-token", re: /gh[posr]_[A-Za-z0-9]{30,}/g },
1835
- { label: "xai-key", re: /xai-[A-Za-z0-9]{20,}/g },
1836
- { label: "aws-key", re: /AKIA[0-9A-Z]{16}/g },
1837
- { label: "jwt", re: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}/g },
1838
- { label: "telegram-token", re: /\b\d{8,10}:[A-Za-z0-9_-]{30,}\b/g },
1839
- // Auth headers / inline credentials (any provider) — the audit found these leaked.
1840
- { label: "bearer-token", re: /\bbearer\s+[A-Za-z0-9._~+/=-]{12,}/gi },
1841
- { label: "basic-auth", re: /\bbasic\s+[A-Za-z0-9+/=]{12,}/gi },
1842
- // key: value / key=value secrets (password, token, secret, api_key, access_key, …).
1843
- { label: "secret-kv", re: /\b(?:pass(?:word|wd)?|pwd|secret|token|api[_-]?key|access[_-]?key|auth)\s*[:=]\s*\S{3,}/gi },
1844
- // Credentials embedded in a URL: scheme://user:PASSWORD@host → redact the password.
1845
- { label: "url-credential", re: /\b([a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:)[^/\s@]+(@)/gi },
1846
- { label: "email", re: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
1847
- { label: "phone", re: /(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}/g }
1848
- ];
1849
- function marker(label) {
1850
- return `\u27E8REDACTED:${label}\u27E9`;
1851
- }
1852
- function entropy(s) {
1853
- const freq = /* @__PURE__ */ new Map();
1854
- for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
1855
- let h = 0;
1856
- for (const n of freq.values()) {
1857
- const p = n / s.length;
1858
- h -= p * Math.log2(p);
1859
- }
1860
- return h;
1861
- }
1862
- function looksLikeUnknownSecret(token) {
1863
- if (token.length < 32) return false;
1864
- if (token.includes("\u27E8REDACTED:")) return false;
1865
- if (/^[0-9a-f]+$/i.test(token)) return false;
1866
- const classes = (/[a-z]/.test(token) ? 1 : 0) + (/[A-Z]/.test(token) ? 1 : 0) + (/[0-9]/.test(token) ? 1 : 0);
1867
- if (classes < 2) return false;
1868
- return entropy(token) >= 4.2;
1869
- }
1870
- function redact(input) {
1871
- const counts = /* @__PURE__ */ new Map();
1872
- let text = input;
1873
- for (const rule of RULES) {
1874
- text = text.replace(rule.re, () => {
1875
- counts.set(rule.label, (counts.get(rule.label) ?? 0) + 1);
1876
- return marker(rule.label);
1877
- });
1878
- }
1879
- text = text.split(/(\s+)/).map((tok) => {
1880
- if (looksLikeUnknownSecret(tok)) {
1881
- counts.set("high-entropy", (counts.get("high-entropy") ?? 0) + 1);
1882
- return marker("high-entropy");
1883
- }
1884
- return tok;
1885
- }).join("");
1886
- const redactions = [...counts.entries()].map(([label, count]) => ({
1887
- label,
1888
- count
1889
- }));
1890
- return { text, redactions };
1891
- }
1892
- function containsSecret(text) {
1893
- for (const rule of RULES) {
1894
- rule.re.lastIndex = 0;
1895
- if (rule.re.test(text)) return true;
1896
- }
1897
- for (const tok of text.split(/\s+/)) {
1898
- if (looksLikeUnknownSecret(tok)) return true;
1899
- }
1900
- return false;
1901
- }
1902
-
1903
- // src/scribe/capture.ts
1904
- var INLINE_CAP = 4e3;
1905
- function sideFile(date, ts, kind, full) {
1906
- mkdirSync10(artifactsDir(), { recursive: true });
1907
- const name = `${date}_${ts.replace(/[:.]/g, "-")}_${kind}.md`;
1908
- const p = join14(artifactsDir(), name);
1909
- const fd = openSync2(p, "w");
1910
- try {
1911
- writeSync2(fd, full);
1912
- fsyncSync2(fd);
1913
- } finally {
1914
- closeSync2(fd);
1915
- }
1916
- return p;
1917
- }
1918
- function field(date, ts, label, value) {
1919
- if (!value || !value.trim()) return "";
1920
- if (value.length > INLINE_CAP) {
1921
- const ref = sideFile(date, ts, label.toLowerCase().replace(/\s+/g, "-"), value);
1922
- return `**${label}** (full \u2192 ${ref}):
1923
- ${value.slice(0, INLINE_CAP)}
1924
- \u2026(truncated; full content in artifact)
1925
-
1926
- `;
1927
- }
1928
- return `**${label}:**
1929
- ${value}
1930
-
1931
- `;
1932
- }
1933
- function renderTurn(rec) {
1934
- let md = `## ${rec.ts}
1935
-
1936
- `;
1937
- md += field(rec.date, rec.ts, "User", rec.user);
1938
- md += field(rec.date, rec.ts, "Router", rec.router);
1939
- if (rec.tools?.length) md += `**Tools:** ${rec.tools.join(", ")}
1940
-
1941
- `;
1942
- if (rec.files?.length) md += `**Files:** ${rec.files.join(", ")}
1943
-
1944
- `;
1945
- md += field(rec.date, rec.ts, "Note", rec.note);
1946
- return `${md}---
1947
- `;
1948
- }
1949
- function oneLineSummary(rec) {
1950
- const bits = [];
1951
- if (rec.user) bits.push(rec.user.replace(/\s+/g, " ").slice(0, 80));
1952
- if (rec.files?.length) bits.push(`files: ${rec.files.slice(0, 3).join(", ")}`);
1953
- if (rec.note) bits.push(rec.note.replace(/\s+/g, " ").slice(0, 60));
1954
- return bits.join(" \xB7 ") || "(activity)";
1955
- }
1956
- function redactRecord(rec) {
1957
- const tally = /* @__PURE__ */ new Map();
1958
- const rd = (s) => {
1959
- if (!s) return s;
1960
- const r = redact(s);
1961
- for (const x of r.redactions) tally.set(x.label, (tally.get(x.label) ?? 0) + x.count);
1962
- return r.text;
1963
- };
1964
- const safeRec = {
1965
- ...rec,
1966
- user: rd(rec.user),
1967
- note: rd(rec.note),
1968
- router: rd(rec.router),
1969
- context: rd(rec.context),
1970
- files: rec.files?.map((f) => rd(f) ?? f)
1971
- };
1972
- return { rec: safeRec, redactions: [...tally.entries()].map(([label, count]) => ({ label, count })) };
1973
- }
1974
- function captureTurn(rec) {
1975
- const { rec: safeRec, redactions } = redactRecord(rec);
1976
- const journal = renderTurn(safeRec);
1977
- appendJournal(rec.date, `${journal}
1978
- `);
1979
- updateDigest(`${safeRec.ts} \xB7 ${oneLineSummary(safeRec)}`, safeRec.context);
1980
- updateTimeline(safeRec.date, oneLineSummary(safeRec));
1981
- const auditClean = !containsSecret(readJournal(rec.date)) && !containsSecret(readDigest() ?? "");
1982
- return {
1983
- journalDate: rec.date,
1984
- redactions,
1985
- bytes: Buffer.byteLength(journal, "utf8"),
1986
- auditClean
1987
- };
1988
- }
1989
-
1990
- // src/scribe/health.ts
1991
- import {
1992
- closeSync as closeSync3,
1993
- fsyncSync as fsyncSync3,
1994
- mkdirSync as mkdirSync11,
1995
- openSync as openSync3,
1996
- readFileSync as readFileSync13,
1997
- writeFileSync as writeFileSync11,
1998
- writeSync as writeSync3
1999
- } from "fs";
2000
- import { join as join15 } from "path";
2001
- function healthFile() {
2002
- return join15(scribeDir(), "_health.json");
2003
- }
2004
- function faultLogFile() {
2005
- return join15(scribeDir(), "_faults.log");
2006
- }
2007
- function read2() {
2008
- try {
2009
- return JSON.parse(readFileSync13(healthFile(), "utf8"));
2010
- } catch {
2011
- return { faultCount: 0 };
2012
- }
2013
- }
2014
- function write(h) {
2015
- mkdirSync11(scribeDir(), { recursive: true });
2016
- writeFileSync11(healthFile(), `${JSON.stringify(h, null, 2)}
2017
- `, "utf8");
2018
- }
2019
- function recordHealth() {
2020
- const h = read2();
2021
- h.lastWriteAt = (/* @__PURE__ */ new Date()).toISOString();
2022
- write(h);
2023
- }
2024
- function recordFault(role, err) {
2025
- try {
2026
- mkdirSync11(scribeDir(), { recursive: true });
2027
- const msg = `${(/* @__PURE__ */ new Date()).toISOString()} [${role}] ${err instanceof Error ? err.message : String(err)}`;
2028
- const fd = openSync3(faultLogFile(), "a");
2029
- try {
2030
- writeSync3(fd, `${msg}
2031
- `);
2032
- fsyncSync3(fd);
2033
- } finally {
2034
- closeSync3(fd);
2035
- }
2036
- const h = read2();
2037
- h.faultCount = (h.faultCount ?? 0) + 1;
2038
- h.lastFault = msg;
2039
- write(h);
2040
- } catch {
2041
- }
2042
- }
2043
-
2044
- // src/scribe/wal.ts
2045
- import {
2046
- closeSync as closeSync4,
2047
- existsSync as existsSync7,
2048
- fsyncSync as fsyncSync4,
2049
- mkdirSync as mkdirSync12,
2050
- openSync as openSync4,
2051
- readFileSync as readFileSync14,
2052
- writeFileSync as writeFileSync12,
2053
- writeSync as writeSync4
2054
- } from "fs";
2055
- import { join as join16 } from "path";
2056
- function walFile() {
2057
- return join16(scribeDir(), "_wal.jsonl");
2058
- }
2059
- function appendLine(obj) {
2060
- mkdirSync12(scribeDir(), { recursive: true });
2061
- const fd = openSync4(walFile(), "a");
2062
- try {
2063
- writeSync4(fd, `${JSON.stringify(obj)}
2064
- `);
2065
- fsyncSync4(fd);
2066
- } finally {
2067
- closeSync4(fd);
2068
- }
2069
- }
2070
- function walAppend(id, rec) {
2071
- appendLine({ t: "add", id, rec });
2072
- }
2073
- function walCommit(id) {
2074
- appendLine({ t: "commit", id });
2075
- }
2076
- function walPending() {
2077
- if (!existsSync7(walFile())) return [];
2078
- const committed = /* @__PURE__ */ new Set();
2079
- const adds = /* @__PURE__ */ new Map();
2080
- for (const line of readFileSync14(walFile(), "utf8").split("\n")) {
2081
- if (!line.trim()) continue;
2082
- try {
2083
- const e = JSON.parse(line);
2084
- if (e.t === "commit") committed.add(e.id);
2085
- else if (e.t === "add" && e.rec) adds.set(e.id, e.rec);
2086
- } catch {
2087
- }
2088
- }
2089
- const out = [];
2090
- for (const [id, rec] of adds) {
2091
- if (!committed.has(id)) out.push({ id, rec });
2092
- }
2093
- return out;
2094
- }
2095
- function walCompact() {
2096
- if (!existsSync7(walFile())) return;
2097
- const pending = walPending();
2098
- const body = pending.map((p) => JSON.stringify({ t: "add", id: p.id, rec: p.rec })).join("\n");
2099
- writeFileSync12(walFile(), body ? `${body}
2100
- ` : "", "utf8");
2101
- }
2102
-
2103
- // src/scribe/supervisor.ts
2104
- var draining = false;
2105
- function uid(ts) {
2106
- return `${ts}-${Math.random().toString(36).slice(2, 9)}`;
2107
- }
2108
- function drainBacklog() {
2109
- if (draining) return;
2110
- draining = true;
2111
- try {
2112
- let drained = 0;
2113
- for (const e of walPending()) {
2114
- try {
2115
- captureTurn(e.rec);
2116
- walCommit(e.id);
2117
- drained++;
2118
- } catch (err) {
2119
- recordFault("standby-replay", err);
2120
- break;
2121
- }
2122
- }
2123
- if (drained > 0) walCompact();
2124
- } finally {
2125
- draining = false;
2126
- }
2127
- }
2128
- function supervisedCapture(rec) {
2129
- try {
2130
- drainBacklog();
2131
- const id = uid(rec.ts);
2132
- const safe = redactRecord(rec).rec;
2133
- walAppend(id, safe);
2134
- try {
2135
- const res = captureTurn(safe);
2136
- walCommit(id);
2137
- walCompact();
2138
- recordHealth();
2139
- return res;
2140
- } catch (primaryErr) {
2141
- recordFault("primary", primaryErr);
2142
- try {
2143
- const res = captureTurn(safe);
2144
- walCommit(id);
2145
- walCompact();
2146
- recordHealth();
2147
- return res;
2148
- } catch (standbyErr) {
2149
- recordFault("standby", standbyErr);
2150
- return null;
2151
- }
2152
- }
2153
- } catch (fatal) {
2154
- recordFault("supervisor", fatal);
2155
- return null;
2156
- }
2157
- }
2158
-
2159
- // src/scribe/retrieval.ts
2160
- import { existsSync as existsSync8, readFileSync as readFileSync15, readdirSync } from "fs";
2161
- function listDays() {
2162
- const dir = scribeDir();
2163
- if (!existsSync8(dir)) return [];
2164
- return readdirSync(dir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).sort();
2165
- }
2166
- function readDay(date) {
2167
- const f = journalFile(date);
2168
- return existsSync8(f) ? readFileSync15(f, "utf8") : "";
2169
- }
2170
- function searchScribe(query, limit = 100) {
2171
- const q = query.toLowerCase().trim();
2172
- if (!q) return [];
2173
- const hits = [];
2174
- for (const date of listDays().reverse()) {
2175
- const lines = readDay(date).split("\n");
2176
- for (let i = 0; i < lines.length; i++) {
2177
- const ln = lines[i];
2178
- if (ln && ln.toLowerCase().includes(q)) {
2179
- hits.push({ date, line: i + 1, text: ln.trim().slice(0, 200) });
2180
- if (hits.length >= limit) return hits;
2181
- }
2182
- }
2183
- }
2184
- return hits;
2185
- }
2186
-
2187
- // src/scribe/scribe-pi.ts
2188
- function scribeTurn(input) {
2189
- if (!isScribeEnabled()) return;
2190
- const ts = (/* @__PURE__ */ new Date()).toISOString();
2191
- supervisedCapture({ ts, date: ts.slice(0, 10), ...input });
2192
- }
2193
- var pendingUserInput = "";
2194
- function noteUserInput(text) {
2195
- pendingUserInput = text;
2196
- }
2197
- function takePendingUserInput() {
2198
- const u = pendingUserInput;
2199
- pendingUserInput = "";
2200
- return u;
2201
- }
2202
- function buildScribeContext() {
2203
- if (!isScribeEnabled()) return "";
2204
- const parts = [];
2205
- try {
2206
- const t = timelineFile();
2207
- if (existsSync9(t)) parts.push(`# Work history \u2014 every day so far
2208
- ${readFileSync16(t, "utf8").trim()}`);
2209
- } catch {
2210
- }
2211
- try {
2212
- const d = readDigest();
2213
- if (d?.trim()) parts.push(`# Current context (recent)
2214
- ${d.trim()}`);
2215
- } catch {
2216
- }
2217
- if (!parts.length) return "";
2218
- return `${parts.join("\n\n")}
2219
-
2220
- (Call scribe_recall to fetch the full text of any past day or topic.)`;
2221
- }
2222
- function registerScribe(pi) {
2223
- pi.registerTool({
2224
- name: "scribe_recall",
2225
- label: "ORIRO Scribe",
2226
- description: "Recall the user's past work from the on-device journal: search by keyword, or read a specific day (YYYY-MM-DD). Use to recover decisions, code, files, and context from earlier sessions.",
2227
- parameters: Type.Object({
2228
- query: Type.Optional(Type.String({ description: "Keyword/topic to search across all journals." })),
2229
- day: Type.Optional(Type.String({ description: "A specific day YYYY-MM-DD to read in full." }))
2230
- }),
2231
- async execute(_id, params) {
2232
- let text;
2233
- const details = {};
2234
- if (!isScribeEnabled()) {
2235
- text = "Scribe is off (the user has not enabled it).";
2236
- } else if (params.day) {
2237
- text = readDay(params.day) || `No journal for ${params.day}. Days: ${listDays().join(", ") || "none"}`;
2238
- details.day = params.day;
2239
- } else {
2240
- const hits = params.query ? searchScribe(params.query) : [];
2241
- details.hits = hits;
2242
- text = hits.length ? hits.map((h) => `${h.date}:${h.line} ${h.text}`).join("\n") : `No matches${params.query ? ` for "${params.query}"` : ""}. Days recorded: ${listDays().join(", ") || "none"}`;
2243
- }
2244
- return { content: [{ type: "text", text }], details };
2245
- }
2246
- });
2247
- }
2248
- function attachScribe(session) {
2249
- let user = "";
2250
- let assistant = "";
2251
- const tools = /* @__PURE__ */ new Set();
2252
- session.subscribe((e) => {
2253
- if (!isScribeEnabled()) return;
2254
- if (e?.type === "user_message" || e?.type === "session_user_message") user = String(e.text ?? e.message ?? user);
2255
- if (e?.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") assistant += e.assistantMessageEvent.delta ?? "";
2256
- if ((e?.type === "tool_call" || e?.type === "tool_execution_start") && e.toolName) tools.add(String(e.toolName));
2257
- if (e?.type === "agent_end") {
2258
- const userText = takePendingUserInput() || user;
2259
- scribeTurn({ user: userText || void 0, router: "oriro-free", tools: [...tools], note: assistant.slice(0, 4e3) || void 0 });
2260
- user = "";
2261
- assistant = "";
2262
- tools.clear();
2263
- }
2264
- });
2265
- }
2266
-
2267
- // src/routers/mux-provider.ts
2268
- var MUX_PROVIDER = "oriro-mux";
2269
- var MUX_MODEL = "oriro-free";
2270
- function errToCallError(msg) {
2271
- const text = msg.errorMessage ?? "";
2272
- return /\b429\b|rate.?limit|too many requests/i.test(text) ? { status: 429 } : {};
2273
- }
2274
- function buildErrorMessage(message) {
2275
- return {
2276
- role: "assistant",
2277
- content: [],
2278
- api: "openai-completions",
2279
- provider: MUX_PROVIDER,
2280
- model: MUX_MODEL,
2281
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
2282
- stopReason: "error",
2283
- timestamp: Date.now(),
2284
- errorMessage: message
2285
- };
2286
- }
2287
- async function driveMux(out, mux, byId, context, options) {
2288
- let lastError;
2289
- for (const id of mux.ranked()) {
2290
- const router = byId.get(id);
2291
- if (!router) continue;
2292
- const t0 = Date.now();
2293
- let committed = false;
2294
- let lastPartial;
2295
- try {
2296
- const inner = piStreamSimple(routerModel(router), context, {
2297
- ...options ?? {},
2298
- apiKey: router.apiKey
2299
- });
2300
- let failedBeforeContent = false;
2301
- for await (const ev of inner) {
2302
- if (ev.type === "error") {
2303
- mux.recordFailure(id, errToCallError(ev.error));
2304
- if (!committed) {
2305
- lastError = ev.error;
2306
- failedBeforeContent = true;
2307
- break;
2308
- }
2309
- out.push(ev);
2310
- out.end(ev.error);
2311
- return;
2312
- }
2313
- committed = true;
2314
- if (ev.type === "done") {
2315
- mux.recordSuccess(id, Date.now() - t0);
2316
- const clean = sanitizeMessageToolCalls(scrubMessageIdentity(ev.message));
2317
- out.push({ type: "done", reason: ev.reason, message: clean });
2318
- out.end(clean);
2319
- return;
2320
- }
2321
- lastPartial = ev.partial;
2322
- out.push(sanitizeEventToolCalls(ev));
2323
- }
2324
- if (failedBeforeContent) continue;
2325
- if (!committed) {
2326
- mux.recordFailure(id, {});
2327
- lastError ??= buildErrorMessage("Router returned no output.");
2328
- continue;
2329
- }
2330
- mux.recordSuccess(id, Date.now() - t0);
2331
- out.end(lastPartial ? sanitizeMessageToolCalls(scrubMessageIdentity(lastPartial)) : void 0);
2332
- return;
2333
- } catch (e) {
2334
- mux.recordFailure(id, e);
2335
- }
2336
- }
2337
- const msg = lastError ?? buildErrorMessage(
2338
- "All keyless routers are unavailable. Add a BYOK key, select more free routers, or retry shortly."
2339
- );
2340
- out.push({ type: "error", reason: "error", error: msg });
2341
- out.end(msg);
2342
- }
2343
- function registerOriroMux(registry, opts = {}) {
2344
- registerOpenAICompletions();
2345
- const pooled = resolvePool();
2346
- const routers = opts.routers ?? (pooled.length > 0 ? pooled : KEYLESS_FLOOR);
2347
- const byId = new Map(routers.map((r) => [r.id, r]));
2348
- const mux = new RouterMux(routers.map((r) => r.id));
2349
- try {
2350
- mux.load(loadMuxState(oriroDir()));
2351
- } catch {
2352
- }
2353
- registry.registerProvider(MUX_PROVIDER, {
2354
- name: "ORIRO Free (keyless Mux)",
2355
- api: "openai-completions",
2356
- apiKey: "oriro-keyless",
2357
- // Placeholder — required by registry validation but never used: our custom streamSimple
2358
- // routes to the real keyless floor endpoints itself (see driveMux).
2359
- baseUrl: "http://oriro-mux.local",
2360
- models: [
2361
- {
2362
- id: MUX_MODEL,
2363
- name: "ORIRO Free (best-router)",
2364
- api: "openai-completions",
2365
- baseUrl: "http://oriro-mux.local",
2366
- reasoning: false,
2367
- input: ["text"],
2368
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2369
- contextWindow: 128e3,
2370
- maxTokens: 4096
2371
- }
2372
- ],
2373
- streamSimple: (_model, context, options) => {
2374
- const out = createAssistantMessageEventStream();
2375
- const ctx = applyIdentity(context);
2376
- const memory = buildScribeContext();
2377
- const withMemory = memory ? { ...ctx, systemPrompt: `${ctx.systemPrompt}
2378
-
2379
- ${memory}` } : ctx;
2380
- void driveMux(out, mux, byId, withMemory, options).finally(() => {
2381
- try {
2382
- saveMuxState(oriroDir(), mux.snapshot());
2383
- } catch {
2384
- }
2385
- });
2386
- return out;
2387
- }
2388
- });
2389
- return registry.find(MUX_PROVIDER, MUX_MODEL);
2390
- }
2391
-
2392
- // src/head/pi-tool.ts
2393
- import { Type as Type2 } from "typebox";
2394
-
2395
- // src/head/comparison-engine.ts
2396
- var SECTION_RULES = [
2397
- {
2398
- type: "hero",
2399
- label: "Hero",
2400
- priority: "CRITICAL",
2401
- markup: [/<h1[\s>]/],
2402
- recommend: "Add a clear above-the-fold hero \u2014 one headline that states the value + one primary CTA."
2403
- },
2404
- {
2405
- type: "navigation",
2406
- label: "Navigation",
2407
- priority: "CRITICAL",
2408
- markup: [/<nav[\s>]/, /role=["']navigation["']/],
2409
- recommend: "Add a top navigation so visitors can reach key sections."
2410
- },
2411
- {
2412
- type: "features",
2413
- label: "Features",
2414
- priority: "CRITICAL",
2415
- text: [/\bfeatures?\b/, /\bwhat you (?:can|get)\b/, /\bcapabilit/],
2416
- recommend: "Add a features section that spells out concrete capabilities, not adjectives."
2417
- },
2418
- {
2419
- type: "pricing",
2420
- label: "Pricing",
2421
- priority: "CRITICAL",
2422
- text: [/\bpricing\b/, /\bper month\b/, /\b\/mo\b/, /\bfree plan\b/, /\$\d/, /₹\d/, /€\d/],
2423
- recommend: 'Add transparent pricing \u2014 a critical conversion element; even a single "Free" tier helps.'
2424
- },
2425
- {
2426
- type: "cta",
2427
- label: "Call-to-Action",
2428
- priority: "CRITICAL",
2429
- text: [/\bget started\b/, /\bsign up\b/, /\bstart (?:free|now|building)\b/, /\btry (?:it|now|free)\b/, /\bbook a demo\b/, /\bget a demo\b/],
2430
- recommend: 'Add a strong, repeated primary CTA ("Get started") so the next step is obvious.'
2431
- },
2432
- {
2433
- type: "testimonials",
2434
- label: "Testimonials",
2435
- priority: "HIGH",
2436
- text: [/\btestimonial/, /\bwhat (?:our )?(?:customers|users) say\b/, /\bloved by\b/, /\breview(?:s|ed)\b/],
2437
- recommend: "Add 2\u20133 customer testimonials with names/photos to build trust."
2438
- },
2439
- {
2440
- type: "stats",
2441
- label: "Stats / Metrics",
2442
- priority: "HIGH",
2443
- text: [/\b\d[\d,.]*\s*[kkmm]\+?\s*(?:users|customers|developers|downloads|teams)\b/, /\b9\d(?:\.\d+)?%\b/, /\buptime\b/],
2444
- recommend: 'Add impressive metrics ("10K+ users", "99.9% uptime") as social proof.'
2445
- },
2446
- {
2447
- type: "video",
2448
- label: "Video",
2449
- priority: "HIGH",
2450
- markup: [/<video[\s>]/, /youtube\.com\/embed/, /player\.vimeo\.com/, /<iframe[^>]+(?:youtube|vimeo)/],
2451
- text: [/\bwatch the (?:video|demo)\b/],
2452
- recommend: "Add a short explainer/demo video \u2014 it lifts conversion on landing pages."
2453
- },
2454
- {
2455
- type: "demo",
2456
- label: "Live Demo",
2457
- priority: "HIGH",
2458
- text: [/\btry it (?:now|live|free)\b/, /\bplayground\b/, /\binteractive demo\b/, /\blive demo\b/],
2459
- recommend: 'Add a "try it" live demo or playground so visitors experience the product immediately.'
2460
- },
2461
- {
2462
- type: "socialProof",
2463
- label: "Social Proof",
2464
- priority: "HIGH",
2465
- text: [/\btrusted by\b/, /\bbacked by\b/, /\bused by\b/, /\bas seen (?:in|on)\b/, /\bcustomers include\b/],
2466
- recommend: 'Add social proof (customer/investor logos, "trusted by \u2026") near the hero.'
2467
- },
2468
- {
2469
- type: "faq",
2470
- label: "FAQ",
2471
- priority: "MEDIUM",
2472
- text: [/\bfaq\b/, /\bfrequently asked\b/],
2473
- markup: [/<details[\s>]/],
2474
- recommend: "Add an FAQ that answers the top objections before they become exits."
2475
- },
2476
- {
2477
- type: "integrations",
2478
- label: "Integrations",
2479
- priority: "MEDIUM",
2480
- text: [/\bintegrations?\b/, /\bworks with\b/, /\bconnect your\b/],
2481
- recommend: "Add an integrations section showing what the product connects to."
2482
- },
2483
- {
2484
- type: "newsletter",
2485
- label: "Newsletter / Capture",
2486
- priority: "MEDIUM",
2487
- text: [/\bsubscribe\b/, /\bnewsletter\b/, /\bjoin (?:the )?waitlist\b/],
2488
- markup: [/type=["']email["']/],
2489
- recommend: "Add an email capture (newsletter/waitlist) so non-converting visitors are not lost."
2490
- },
2491
- {
2492
- type: "comparison",
2493
- label: "Comparison",
2494
- priority: "MEDIUM",
2495
- text: [/\bcompare\b/, /\bcomparison\b/, /\b vs\.? \b/, /\bwhy choose\b/],
2496
- recommend: 'Add a comparison ("us vs alternatives") to win evaluators who are shopping around.'
2497
- },
2498
- {
2499
- type: "team",
2500
- label: "Team / About",
2501
- priority: "LOW",
2502
- text: [/\bour team\b/, /\bmeet the team\b/, /\bfounders?\b/, /\babout us\b/],
2503
- recommend: "Add a brief team/about section to humanize the brand."
2504
- }
2505
- ];
2506
- var PRIORITY_RANK = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
2507
- var PRIORITY_EFFORT = { CRITICAL: "L", HIGH: "M", MEDIUM: "M", LOW: "S" };
2508
- var FETCH_TIMEOUT_MS = 12e3;
2509
- var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36 ORIRO-Inspector";
2510
- async function fetchPage(url) {
2511
- const controller = new AbortController();
2512
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2513
- const start = Date.now();
2514
- try {
2515
- const res = await fetch(url, {
2516
- signal: controller.signal,
2517
- redirect: "follow",
2518
- headers: { "user-agent": UA, accept: "text/html,application/xhtml+xml" }
2519
- });
2520
- const html = await res.text();
2521
- return { html, ms: Date.now() - start, status: res.status, ok: res.ok, error: "" };
2522
- } catch (err) {
2523
- return { html: "", ms: Date.now() - start, status: 0, ok: false, error: err instanceof Error ? err.message : "fetch failed" };
2524
- } finally {
2525
- clearTimeout(timer);
2526
- }
2527
- }
2528
- function toText(html) {
2529
- return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/\s+/g, " ").toLowerCase().trim();
2530
- }
2531
- function firstMatch(re, hay) {
2532
- const m = re.exec(hay);
2533
- if (!m) return "";
2534
- const slice = (m[0] ?? "").trim();
2535
- return slice.length > 80 ? `${slice.slice(0, 77)}\u2026` : slice;
2536
- }
2537
- function detectSections(rawHtmlLower, text) {
2538
- const found = [];
2539
- for (const rule of SECTION_RULES) {
2540
- let evidence = "";
2541
- for (const re of rule.markup ?? []) {
2542
- const hit = firstMatch(re, rawHtmlLower);
2543
- if (hit) {
2544
- evidence = hit;
2545
- break;
2546
- }
2547
- }
2548
- if (!evidence) {
2549
- for (const re of rule.text ?? []) {
2550
- const hit = firstMatch(re, text);
2551
- if (hit) {
2552
- evidence = hit;
2553
- break;
2554
- }
2555
- }
2556
- }
2557
- if (evidence) found.push({ type: rule.type, label: rule.label, priority: rule.priority, evidence });
2558
- }
2559
- return found;
2560
- }
2561
- function extractMatches(re, html, max) {
2562
- const out = [];
2563
- for (const m of html.matchAll(re)) {
2564
- const inner = (m[1] ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
2565
- if (inner && !out.includes(inner)) out.push(inner);
2566
- if (out.length >= max) break;
2567
- }
2568
- return out;
2569
- }
2570
- var CTA_WORDS = /\b(get started|sign up|start free|start now|start building|try (?:it|now|free)|book a demo|get a demo|request access|join (?:the )?waitlist|download)\b/i;
2571
- function extractStructure(url, fr) {
2572
- const html = fr.html;
2573
- const lowerHtml = html.toLowerCase();
2574
- const text = toText(html);
2575
- const titleM = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
2576
- const title = (titleM?.[1] ?? "").replace(/\s+/g, " ").trim();
2577
- const descM = /<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i.exec(html) ?? /<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i.exec(html);
2578
- const description = (descM?.[1] ?? "").replace(/\s+/g, " ").trim();
2579
- const headings = extractMatches(/<h[1-3][^>]*>([\s\S]*?)<\/h[1-3]>/gi, html, 12);
2580
- const ctaAll = extractMatches(/<(?:a|button)[^>]*>([\s\S]*?)<\/(?:a|button)>/gi, html, 80);
2581
- const ctas = [];
2582
- for (const c of ctaAll) {
2583
- if (CTA_WORDS.test(c) && !ctas.includes(c)) ctas.push(c);
2584
- if (ctas.length >= 10) break;
2585
- }
2586
- const forms = (lowerHtml.match(/<form[\s>]/g) ?? []).length;
2587
- const links = (lowerHtml.match(/<a[\s>]/g) ?? []).length;
2588
- const images = (lowerHtml.match(/<img[\s>]/g) ?? []).length;
2589
- const hasVideo = /<video[\s>]/.test(lowerHtml) || /(?:youtube\.com\/embed|player\.vimeo\.com)/.test(lowerHtml);
2590
- const domNodes = (html.match(/<[a-z!\/]/gi) ?? []).length;
2591
- let note = "";
2592
- if (fr.ok && text.length < 400 && domNodes < 60) {
2593
- note = "Sparse HTML \u2014 likely a client-rendered (SPA) page; structure may be under-detected without a JS render.";
2594
- }
2595
- return {
2596
- url,
2597
- title,
2598
- description,
2599
- sections: detectSections(lowerHtml, text),
2600
- headings,
2601
- ctas,
2602
- forms,
2603
- links,
2604
- images,
2605
- hasVideo,
2606
- metrics: { htmlBytes: html.length, domNodes, fetchMs: fr.ms, status: fr.status },
2607
- ok: fr.ok && html.length > 0,
2608
- note: fr.ok ? note : `Could not load: ${fr.error || `HTTP ${fr.status}`}`
2609
- };
2610
- }
2611
- function ruleFor(type) {
2612
- return SECTION_RULES.find((r) => r.type === type) ?? SECTION_RULES[0];
2613
- }
2614
- function analyzeGaps(target, competitors) {
2615
- const targetTypes = new Set(target.sections.map((s) => s.type));
2616
- const compPresence = /* @__PURE__ */ new Map();
2617
- for (const comp of competitors) {
2618
- if (!comp.ok) continue;
2619
- for (const s of comp.sections) {
2620
- const list = compPresence.get(s.type) ?? [];
2621
- if (!list.includes(comp.url)) list.push(comp.url);
2622
- compPresence.set(s.type, list);
2623
- }
2624
- }
2625
- const missing = [];
2626
- const parity = [];
2627
- for (const [type, presentOn] of compPresence) {
2628
- if (targetTypes.has(type)) {
2629
- parity.push(type);
2630
- } else {
2631
- const rule = ruleFor(type);
2632
- missing.push({ section: type, label: rule.label, priority: rule.priority, presentOn, recommendation: rule.recommend });
2633
- }
2634
- }
2635
- missing.sort((a, b) => PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority] || b.presentOn.length - a.presentOn.length);
2636
- const advantages = target.sections.filter((s) => !compPresence.has(s.type));
2637
- return { missing, advantages, parity };
2638
- }
2639
- function generateActionItems(missing) {
2640
- return missing.map((g) => ({
2641
- title: `Add a ${g.label} section`,
2642
- priority: g.priority,
2643
- effort: PRIORITY_EFFORT[g.priority],
2644
- rationale: `${g.presentOn.length} of the compared page(s) have it; you don't. ${g.recommendation}`
2645
- }));
2646
- }
2647
- function hostOf(url) {
2648
- try {
2649
- return new URL(url).host.replace(/^www\./, "");
2650
- } catch {
2651
- return url;
2652
- }
2653
- }
2654
- function generateSummary(target, competitors, gaps) {
2655
- const okComps = competitors.filter((c) => c.ok);
2656
- const tName = hostOf(target.url);
2657
- if (!target.ok) return `Could not load ${tName} (${target.note}). Nothing to compare against yet.`;
2658
- if (okComps.length === 0) return `Loaded ${tName} (${target.sections.length} sections) but none of the comparison URLs could be loaded.`;
2659
- const crit = gaps.missing.filter((m) => m.priority === "CRITICAL").map((m) => m.label);
2660
- const high = gaps.missing.filter((m) => m.priority === "HIGH").map((m) => m.label);
2661
- const parts = [];
2662
- parts.push(`${tName} has ${target.sections.length} detectable sections; compared against ${okComps.length} page(s).`);
2663
- if (gaps.missing.length === 0) {
2664
- parts.push("No structural gaps found \u2014 you cover everything they do.");
2665
- } else {
2666
- parts.push(`${gaps.missing.length} gap(s) found.`);
2667
- if (crit.length) parts.push(`Critical: ${crit.join(", ")}.`);
2668
- if (high.length) parts.push(`High: ${high.join(", ")}.`);
2669
- }
2670
- if (gaps.advantages.length) parts.push(`Your edge: ${gaps.advantages.map((a) => a.label).join(", ")}.`);
2671
- return parts.join(" ");
2672
- }
2673
- function normalizeUrl(u) {
2674
- const t = (u || "").trim();
2675
- if (!t) return t;
2676
- return /^https?:\/\//i.test(t) ? t : `https://${t}`;
2677
- }
2678
- async function comparePages(opts) {
2679
- const targetUrl = normalizeUrl(opts.targetUrl);
2680
- const competitorUrls = (opts.competitorUrls ?? []).map(normalizeUrl).filter((u) => u.length > 0).slice(0, 30);
2681
- const [targetFetch, ...compFetches] = await Promise.all([
2682
- fetchPage(targetUrl),
2683
- ...competitorUrls.map((u) => fetchPage(u))
2684
- ]);
2685
- const target = extractStructure(targetUrl, targetFetch ?? { html: "", ms: 0, status: 0, ok: false, error: "no fetch" });
2686
- const competitors = competitorUrls.map(
2687
- (u, i) => extractStructure(u, compFetches[i] ?? { html: "", ms: 0, status: 0, ok: false, error: "no fetch" })
2688
- );
2689
- const gaps = analyzeGaps(target, competitors);
2690
- return {
2691
- target,
2692
- competitors,
2693
- missing: gaps.missing,
2694
- advantages: gaps.advantages,
2695
- parity: gaps.parity,
2696
- actionItems: generateActionItems(gaps.missing),
2697
- summary: generateSummary(target, competitors, gaps)
2698
- };
2699
- }
2700
-
2701
- // src/head/pi-tool.ts
2702
- function summarizeForCoder(report) {
2703
- const lines = [report.summary];
2704
- const page = (p) => ` \u2022 ${p.url} \u2014 ${p.ok ? `${p.sections.length} sections: ${p.sections.map((s) => s.type).join(", ")}` : `not readable (${p.note})`}`;
2705
- lines.push("Pages seen:");
2706
- lines.push(page(report.target));
2707
- for (const c of report.competitors) if (c.url !== report.target.url) lines.push(page(c));
2708
- if (report.missing.length) {
2709
- lines.push("Missing on the target (gaps to build):");
2710
- for (const g of report.missing.slice(0, 12)) lines.push(` \u2022 ${g.label} (${g.priority}) \u2014 ${g.recommendation}`);
2711
- }
2712
- if (report.actionItems.length) {
2713
- lines.push("Suggested action items:");
2714
- for (const a of report.actionItems.slice(0, 12)) lines.push(` \u2192 ${a.title} [${a.priority}/${a.effort}] \u2014 ${a.rationale}`);
2715
- }
2716
- return lines.join("\n");
2717
- }
2718
- var InspectSiteParams = Type2.Object({
2719
- url: Type2.String({ description: "The target website URL to inspect or rebuild from." }),
2720
- competitors: Type2.Optional(
2721
- Type2.Array(Type2.String(), { description: "Optional competitor/reference URLs to compare the target against." })
2722
- )
2723
- });
2724
- function registerHead(pi) {
2725
- pi.registerTool({
2726
- name: "inspect_site",
2727
- label: "ORIRO Head",
2728
- description: "Go out to a live website and SEE it: its sections, CTAs, structure, and any gaps versus competitor URLs. Returns a structured report to build from. Call this whenever the user wants to look at, compare against, or rebuild a website/page.",
2729
- parameters: InspectSiteParams,
2730
- async execute(_toolCallId, params) {
2731
- const target = params.url;
2732
- const competitors = params.competitors?.length ? params.competitors : [target];
2733
- const report = await comparePages({ targetUrl: target, competitorUrls: competitors });
2734
- return { content: [{ type: "text", text: summarizeForCoder(report) }], details: report };
2735
- }
2736
- });
2737
- }
2738
-
2739
- // src/orchestrate.ts
2740
- import { createAgentSession, AuthStorage, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
2741
- import { Type as Type3 } from "typebox";
2742
- var MAX_AGENTS = 8;
2743
- var MAX_CONCURRENCY = 4;
2744
- async function runOnce(spec) {
2745
- const authStorage = AuthStorage.inMemory();
2746
- const modelRegistry = ModelRegistry.inMemory(authStorage);
2747
- const model = registerOriroMux(modelRegistry);
2748
- if (!model) return { ...spec, ok: false, output: "no free model available" };
2749
- const { session } = await createAgentSession({
2750
- model,
2751
- authStorage,
2752
- modelRegistry,
2753
- sessionManager: SessionManager.inMemory(),
2754
- noTools: "all"
2755
- });
2756
- let out = "";
2757
- const unsub = session.subscribe((e) => {
2758
- if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") out += e.assistantMessageEvent.delta ?? "";
2759
- });
2760
- try {
2761
- await session.prompt(`You are the ${spec.role} sub-agent. ${spec.task}`);
2762
- } catch (e) {
2763
- return { ...spec, ok: false, output: e instanceof Error ? e.message : String(e) };
2764
- } finally {
2765
- unsub();
2766
- session.dispose();
2767
- }
2768
- return { ...spec, ok: out.trim().length > 0, output: out.trim() };
2769
- }
2770
- async function runAgent(spec) {
2771
- let last = await runOnce(spec);
2772
- if (!last.ok) last = await runOnce(spec);
2773
- return last;
2774
- }
2775
- async function runPool(items, n, fn) {
2776
- const results = new Array(items.length);
2777
- let i = 0;
2778
- async function worker() {
2779
- while (i < items.length) {
2780
- const idx = i++;
2781
- const item = items[idx];
2782
- if (item === void 0) continue;
2783
- results[idx] = await fn(item);
2784
- }
2785
- }
2786
- await Promise.all(Array.from({ length: Math.min(n, items.length) }, () => worker()));
2787
- return results;
2788
- }
2789
- async function orchestrate(opts) {
2790
- const agents = opts.agents.slice(0, MAX_AGENTS);
2791
- if ((opts.mode ?? "parallel") === "chain") {
2792
- const results = [];
2793
- let prev = "";
2794
- for (const a of agents) {
2795
- const r = await runAgent({ role: a.role, task: prev ? `${a.task}
2796
-
2797
- Previous result:
2798
- ${prev}` : a.task });
2799
- results.push(r);
2800
- prev = r.output;
2801
- }
2802
- return results;
2803
- }
2804
- return runPool(agents, MAX_CONCURRENCY, runAgent);
2805
- }
2806
- function registerOrchestrator(pi) {
2807
- pi.registerTool({
2808
- name: "deploy_agents",
2809
- label: "ORIRO Orchestrator",
2810
- description: "Deploy multiple sub-agents in parallel (or chained) to do work \u2014 e.g. 'spawn 4 QA + 2 coders, run the tests'. Each sub-agent runs FREE on the router pool. Give each agent a role and a task.",
2811
- parameters: Type3.Object({
2812
- agents: Type3.Array(Type3.Object({ role: Type3.String(), task: Type3.String() }), {
2813
- description: "The sub-agents to deploy (max 8)."
2814
- }),
2815
- mode: Type3.Optional(Type3.Union([Type3.Literal("parallel"), Type3.Literal("chain")]))
2816
- }),
2817
- async execute(_id, params) {
2818
- const results = await orchestrate({ agents: params.agents, mode: params.mode });
2819
- const text = results.map((r) => `[${r.role}] ${r.ok ? "\u2713" : "\u2717"} ${r.output.slice(0, 300)}`).join("\n");
2820
- return { content: [{ type: "text", text }], details: { results } };
2821
- }
2822
- });
2823
- }
2824
-
2825
- // src/skills/loader.ts
2826
- import { loadSkills, formatSkillsForPrompt } from "@earendil-works/pi-coding-agent";
2827
- import { fileURLToPath } from "url";
2828
- import { existsSync as existsSync10 } from "fs";
2829
- import { dirname as dirname2, join as join17 } from "path";
2830
- function packageRoot(start) {
2831
- let dir = start;
2832
- for (let i = 0; i < 10; i++) {
2833
- if (existsSync10(join17(dir, "package.json"))) return dir;
2834
- const parent = dirname2(dir);
2835
- if (parent === dir) break;
2836
- dir = parent;
2837
- }
2838
- return start;
2839
- }
2840
- function skillsDir() {
2841
- if (process.env.ORIRO_SKILLS_DIR) return process.env.ORIRO_SKILLS_DIR;
2842
- return join17(packageRoot(dirname2(fileURLToPath(import.meta.url))), "skills");
2843
- }
2844
- async function loadOriroSkills(dir = skillsDir()) {
2845
- const result = await loadSkills({
2846
- cwd: dir,
2847
- agentDir: dir,
2848
- skillPaths: [dir],
2849
- includeDefaults: false
2850
- });
2851
- const all = Array.isArray(result) ? result : result.skills ?? [];
2852
- return {
2853
- all,
2854
- core: all.filter((s) => !s.disableModelInvocation),
2855
- tail: all.filter((s) => s.disableModelInvocation),
2856
- prompt: formatSkillsForPrompt(all)
2857
- };
2858
- }
2859
-
2860
- // src/onboarding/assemble.ts
2861
- async function assembleOriroSession(opts = {}) {
2862
- const cwd = opts.cwd ?? process.cwd();
2863
- const authStorage = AuthStorage2.inMemory();
2864
- const modelRegistry = ModelRegistry2.inMemory(authStorage);
2865
- const settingsManager = SettingsManager.create(cwd);
2866
- const model = registerOriroMux(modelRegistry);
2867
- if (!model) throw new Error("ORIRO keyless model unavailable");
2868
- const resourceLoader = new DefaultResourceLoader({
2869
- cwd,
2870
- agentDir: getAgentDir(),
2871
- settingsManager,
2872
- additionalSkillPaths: [skillsDir()],
2873
- extensionFactories: [registerGuardian, registerHead, registerScribe, registerOrchestrator]
2874
- });
2875
- await resourceLoader.reload();
2876
- const { session, extensionsResult } = await createAgentSession2({
2877
- model,
2878
- authStorage,
2879
- modelRegistry,
2880
- settingsManager,
2881
- sessionManager: SessionManager2.inMemory(),
2882
- resourceLoader
2883
- });
2884
- attachScribe(session);
2885
- return { session, extensionsResult };
2886
- }
2887
-
2888
- // src/language/nllb-translator.ts
2889
- var NLLB_CODE = {
2890
- en: "eng_Latn",
2891
- zh: "zho_Hans",
2892
- de: "deu_Latn",
2893
- es: "spa_Latn",
2894
- ru: "rus_Cyrl",
2895
- ko: "kor_Hang",
2896
- fr: "fra_Latn",
2897
- ja: "jpn_Jpan",
2898
- pt: "por_Latn",
2899
- tr: "tur_Latn",
2900
- pl: "pol_Latn",
2901
- ca: "cat_Latn",
2902
- nl: "nld_Latn",
2903
- ar: "arb_Arab",
2904
- sv: "swe_Latn",
2905
- it: "ita_Latn",
2906
- id: "ind_Latn",
2907
- hi: "hin_Deva",
2908
- fi: "fin_Latn",
2909
- vi: "vie_Latn",
2910
- he: "heb_Hebr",
2911
- uk: "ukr_Cyrl",
2912
- el: "ell_Grek",
2913
- ms: "zsm_Latn",
2914
- cs: "ces_Latn",
2915
- ro: "ron_Latn",
2916
- da: "dan_Latn",
2917
- hu: "hun_Latn",
2918
- ta: "tam_Taml",
2919
- no: "nob_Latn",
2920
- th: "tha_Thai",
2921
- ur: "urd_Arab",
2922
- hr: "hrv_Latn",
2923
- bg: "bul_Cyrl",
2924
- lt: "lit_Latn",
2925
- mi: "mri_Latn",
2926
- ml: "mal_Mlym",
2927
- cy: "cym_Latn",
2928
- sk: "slk_Latn",
2929
- te: "tel_Telu",
2930
- fa: "pes_Arab",
2931
- lv: "lvs_Latn",
2932
- bn: "ben_Beng",
2933
- sr: "srp_Cyrl",
2934
- az: "azj_Latn",
2935
- sl: "slv_Latn",
2936
- kn: "kan_Knda",
2937
- et: "est_Latn",
2938
- mk: "mkd_Cyrl",
2939
- eu: "eus_Latn",
2940
- is: "isl_Latn",
2941
- hy: "hye_Armn",
2942
- ne: "npi_Deva",
2943
- mn: "khk_Cyrl",
2944
- bs: "bos_Latn",
2945
- kk: "kaz_Cyrl",
2946
- sq: "als_Latn",
2947
- sw: "swh_Latn",
2948
- gl: "glg_Latn",
2949
- mr: "mar_Deva",
2950
- pa: "pan_Guru",
2951
- si: "sin_Sinh",
2952
- km: "khm_Khmr",
2953
- sn: "sna_Latn",
2954
- yo: "yor_Latn",
2955
- so: "som_Latn",
2956
- af: "afr_Latn",
2957
- oc: "oci_Latn",
2958
- ka: "kat_Geor",
2959
- be: "bel_Cyrl",
2960
- tg: "tgk_Cyrl",
2961
- sd: "snd_Arab",
2962
- gu: "guj_Gujr",
2963
- am: "amh_Ethi",
2964
- yi: "ydd_Hebr",
2965
- lo: "lao_Laoo",
2966
- uz: "uzn_Latn",
2967
- fo: "fao_Latn",
2968
- ht: "hat_Latn",
2969
- ps: "pbt_Arab",
2970
- tk: "tuk_Latn",
2971
- nn: "nno_Latn",
2972
- mt: "mlt_Latn",
2973
- sa: "san_Deva",
2974
- lb: "ltz_Latn",
2975
- my: "mya_Mymr",
2976
- bo: "bod_Tibt",
2977
- tl: "tgl_Latn",
2978
- mg: "plt_Latn",
2979
- as: "asm_Beng",
2980
- tt: "tat_Cyrl",
2981
- ln: "lin_Latn",
2982
- ha: "hau_Latn",
2983
- ba: "bak_Cyrl",
2984
- jw: "jav_Latn",
2985
- su: "sun_Latn",
2986
- yue: "yue_Hant"
2987
- };
2988
- var ENG = "eng_Latn";
2989
- var toNllb = (iso) => NLLB_CODE[(iso || "").toLowerCase()] ?? ENG;
2990
- var NllbTranslator = class {
2991
- pipe = null;
2992
- loading = null;
2993
- ready() {
2994
- return this.pipe !== null;
2995
- }
2996
- /** Lazy-load NLLB-200 once (first-use download + cache). Idempotent. */
2997
- async load(modelId = "Xenova/nllb-200-distilled-600M") {
2998
- if (this.pipe) return;
2999
- if (this.loading) return this.loading;
3000
- this.loading = (async () => {
3001
- const { pipeline } = await import("@huggingface/transformers");
3002
- this.pipe = await pipeline("translation", modelId);
3003
- })();
3004
- return this.loading;
3005
- }
3006
- async run(text, src, tgt) {
3007
- if (!this.pipe) await this.load();
3008
- if (!this.pipe) return text;
3009
- const out = await this.pipe(text, { src_lang: src, tgt_lang: tgt });
3010
- return out?.[0]?.translation_text?.trim() || text;
3011
- }
3012
- toEnglish(text, fromLang) {
3013
- return this.run(text, toNllb(fromLang), ENG);
3014
- }
3015
- fromEnglish(english, toLang) {
3016
- return this.run(english, ENG, toNllb(toLang));
3017
- }
3018
- };
3019
- var instance = null;
3020
- function setupNllbTranslator(opts) {
3021
- if (!instance) {
3022
- instance = new NllbTranslator();
3023
- registerTranslator(instance);
3024
- }
3025
- if (opts?.preload) void instance.load();
3026
- return instance;
3027
- }
3028
-
3029
- // src/language/gateway.ts
3030
- var isEnglish2 = (code) => !code || code.toLowerCase().startsWith("en");
3031
- var isCommand = (text) => text.trimStart().startsWith("/");
3032
- async function ensureReady() {
3033
- try {
3034
- await setupNllbTranslator().load();
3035
- } catch {
3036
- }
3037
- }
3038
- async function translateIncoming(message) {
3039
- const lang = getTerminalLanguage().code;
3040
- if (isEnglish2(lang) || !message.trim() || isCommand(message)) return message;
3041
- await ensureReady();
3042
- return translateForCoder(message, lang);
3043
- }
3044
- async function translateOutgoing(text) {
3045
- const lang = getTerminalLanguage().code;
3046
- if (isEnglish2(lang) || !text.trim()) return text;
3047
- await ensureReady();
3048
- return translateForUser(text, lang);
3049
- }
3050
-
3051
- // src/repl.ts
3052
- function replHelp() {
3053
- return `
3054
- ${accent("ORIRO terminal \u2014 help")}
3055
- ${dim("Just type to chat; ORIRO writes and runs code for you (keyless, free).")}
3056
-
3057
- ${accent("/help")} this help ${accent("/exit")} or ${accent("/quit")} leave ${dim("Ctrl-D / Ctrl-C also exit")}
3058
- ${dim("Run these OUTSIDE the chat (in your shell):")}
3059
- ${dim("oriro skills \xB7 routers \xB7 connectors \xB7 channels \xB7 scribe \xB7 language \xB7 avatar")}
3060
-
3061
- `;
3062
- }
3063
- async function runRepl() {
3064
- if (isFirstRun()) await runOnboarding();
3065
- else stdout5.write(banner());
3066
- const isEnglish3 = getTerminalLanguage().code.toLowerCase().startsWith("en");
3067
- const { session } = await assembleOriroSession();
3068
- const rl = createInterface4({ input: stdin4, output: stdout5 });
3069
- let closing = false;
3070
- const onSigint = () => {
3071
- if (closing) return;
3072
- closing = true;
3073
- stdout5.write(dim("\nBye.\n"));
3074
- try {
3075
- rl.close();
3076
- } catch {
3077
- }
3078
- try {
3079
- session.dispose();
3080
- } catch {
3081
- }
3082
- process.exit(0);
3083
- };
3084
- process.on("SIGINT", onSigint);
3085
- try {
3086
- for (; ; ) {
3087
- let line;
3088
- try {
3089
- line = (await rl.question("\u203A ")).trim();
3090
- } catch {
3091
- break;
3092
- }
3093
- if (!line) continue;
3094
- const slash = line.toLowerCase();
3095
- if (slash === "/exit" || slash === "/quit") break;
3096
- if (slash === "/help" || slash === "/?") {
3097
- stdout5.write(replHelp());
3098
- continue;
3099
- }
3100
- const english = await translateIncoming(line);
3101
- noteUserInput(line);
3102
- let out = "";
3103
- const unsub = session.subscribe((e) => {
3104
- if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") {
3105
- const d = e.assistantMessageEvent.delta ?? "";
3106
- out += d;
3107
- if (isEnglish3) stdout5.write(d);
3108
- }
3109
- });
3110
- try {
3111
- await session.prompt(english);
3112
- } finally {
3113
- unsub();
3114
- }
3115
- if (isEnglish3) stdout5.write("\n\n");
3116
- else stdout5.write(`${await translateOutgoing(out.trim())}
3117
-
3118
- `);
3119
- }
3120
- } finally {
3121
- process.removeListener("SIGINT", onSigint);
3122
- if (!closing) {
3123
- rl.close();
3124
- session.dispose();
3125
- stdout5.write(dim("\nBye.\n"));
3126
- }
3127
- }
3128
- }
3129
-
3130
- // src/routers/catalog.ts
3131
- var C4 = (e) => ({
3132
- api: "openai-completions",
3133
- freeModels: [],
3134
- tier: "free",
3135
- kind: "chat",
3136
- ...e
3137
- });
3138
- var ROUTER_CATALOG = [
3139
- // ── Keyless & live-verified (works now, zero keys, through the agent) ──
3140
- C4({
3141
- id: "pollinations",
3142
- displayName: "Pollinations",
1348
+ displayName: "Pollinations",
3143
1349
  baseUrl: "https://text.pollinations.ai/openai",
3144
1350
  freeModels: ["openai", "mistral"],
3145
1351
  obtainUrl: "https://pollinations.ai",
@@ -3410,66 +1616,2106 @@ var ROUTER_CATALOG = [
3410
1616
  displayName: "Ollama (local)",
3411
1617
  api: "ollama",
3412
1618
  baseUrl: "http://localhost:11434/v1",
3413
- freeModels: ["llama3.2"],
3414
- keyless: true
3415
- }),
3416
- // ── Image / speech services (catalog completeness; not chat-routable by the Mux) ──
3417
- C4({
3418
- id: "stability",
3419
- displayName: "Stability AI",
3420
- baseUrl: "https://api.stability.ai/v2beta",
3421
- freeModels: ["stable-image-core"],
3422
- obtainUrl: "https://platform.stability.ai",
3423
- kind: "image"
3424
- }),
3425
- C4({
3426
- id: "fal",
3427
- displayName: "fal.ai",
3428
- baseUrl: "https://fal.run",
3429
- freeModels: ["fal-ai/flux/schnell"],
3430
- obtainUrl: "https://fal.ai",
3431
- kind: "image"
3432
- }),
3433
- C4({
3434
- id: "wavespeed",
3435
- displayName: "WaveSpeedAI",
3436
- baseUrl: "https://api.wavespeed.ai",
3437
- freeModels: [],
3438
- obtainUrl: "https://wavespeed.ai",
3439
- kind: "image"
3440
- }),
3441
- C4({
3442
- id: "ai-horde",
3443
- displayName: "AI Horde",
3444
- baseUrl: "https://aihorde.net/api/v2",
3445
- freeModels: [],
3446
- obtainUrl: "https://aihorde.net",
3447
- keyless: true,
3448
- kind: "image"
3449
- }),
3450
- C4({
3451
- id: "assemblyai",
3452
- displayName: "AssemblyAI",
3453
- baseUrl: "https://api.assemblyai.com/v2",
3454
- freeModels: [],
3455
- obtainUrl: "https://assemblyai.com",
3456
- kind: "speech"
3457
- }),
3458
- // ── Paid (requires payment/recharge — moved out of free per the CC rule) ──
3459
- C4({
3460
- id: "moonshot",
3461
- displayName: "Moonshot (Direct)",
3462
- baseUrl: "https://api.moonshot.ai/v1",
3463
- freeModels: ["kimi-k2.6"],
3464
- obtainUrl: "https://platform.moonshot.ai",
3465
- tier: "paid"
3466
- }),
3467
- // ── ORIRO models — coming soon, greyed/"(free)", not selectable yet ──
3468
- C4({ id: "oriro-gauss", displayName: "ORIRO-Gauss", baseUrl: "", comingSoon: true }),
3469
- C4({ id: "oriro-avila", displayName: "ORIRO-Avila", baseUrl: "", comingSoon: true })
1619
+ freeModels: ["llama3.2"],
1620
+ keyless: true
1621
+ }),
1622
+ // ── Image / speech services (catalog completeness; not chat-routable by the Mux) ──
1623
+ C4({
1624
+ id: "stability",
1625
+ displayName: "Stability AI",
1626
+ baseUrl: "https://api.stability.ai/v2beta",
1627
+ freeModels: ["stable-image-core"],
1628
+ obtainUrl: "https://platform.stability.ai",
1629
+ kind: "image"
1630
+ }),
1631
+ C4({
1632
+ id: "fal",
1633
+ displayName: "fal.ai",
1634
+ baseUrl: "https://fal.run",
1635
+ freeModels: ["fal-ai/flux/schnell"],
1636
+ obtainUrl: "https://fal.ai",
1637
+ kind: "image"
1638
+ }),
1639
+ C4({
1640
+ id: "wavespeed",
1641
+ displayName: "WaveSpeedAI",
1642
+ baseUrl: "https://api.wavespeed.ai",
1643
+ freeModels: [],
1644
+ obtainUrl: "https://wavespeed.ai",
1645
+ kind: "image"
1646
+ }),
1647
+ C4({
1648
+ id: "ai-horde",
1649
+ displayName: "AI Horde",
1650
+ baseUrl: "https://aihorde.net/api/v2",
1651
+ freeModels: [],
1652
+ obtainUrl: "https://aihorde.net",
1653
+ keyless: true,
1654
+ kind: "image"
1655
+ }),
1656
+ C4({
1657
+ id: "assemblyai",
1658
+ displayName: "AssemblyAI",
1659
+ baseUrl: "https://api.assemblyai.com/v2",
1660
+ freeModels: [],
1661
+ obtainUrl: "https://assemblyai.com",
1662
+ kind: "speech"
1663
+ }),
1664
+ // ── Paid (requires payment/recharge — moved out of free per the CC rule) ──
1665
+ C4({
1666
+ id: "moonshot",
1667
+ displayName: "Moonshot (Direct)",
1668
+ baseUrl: "https://api.moonshot.ai/v1",
1669
+ freeModels: ["kimi-k2.6"],
1670
+ obtainUrl: "https://platform.moonshot.ai",
1671
+ tier: "paid"
1672
+ }),
1673
+ // ── ORIRO models — coming soon, greyed/"(free)", not selectable yet ──
1674
+ C4({ id: "oriro-gauss", displayName: "ORIRO-Gauss", baseUrl: "", comingSoon: true }),
1675
+ C4({ id: "oriro-avila", displayName: "ORIRO-Avila", baseUrl: "", comingSoon: true })
1676
+ ];
1677
+ function routerById(id) {
1678
+ return ROUTER_CATALOG.find((r) => r.id === id);
1679
+ }
1680
+
1681
+ // src/routers/router-pool.ts
1682
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
1683
+ import { join as join11 } from "path";
1684
+
1685
+ // src/routers/pool.ts
1686
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
1687
+ import { join as join10 } from "path";
1688
+ function poolFile(dir) {
1689
+ return join10(dir, "routers", "selected.json");
1690
+ }
1691
+ function loadPool(dir) {
1692
+ const p = poolFile(dir);
1693
+ if (!existsSync3(p)) return [];
1694
+ try {
1695
+ const v = JSON.parse(readFileSync8(p, "utf8"));
1696
+ return Array.isArray(v) ? v : [];
1697
+ } catch {
1698
+ return [];
1699
+ }
1700
+ }
1701
+ function savePool(dir, ids) {
1702
+ mkdirSync5(join10(dir, "routers"), { recursive: true });
1703
+ writeFileSync7(poolFile(dir), JSON.stringify([...new Set(ids)], null, 2), "utf8");
1704
+ }
1705
+
1706
+ // src/routers/validate.ts
1707
+ var PROBE_TIMEOUT_MS = 12e3;
1708
+ async function validateRouter(entry, key, modelId) {
1709
+ const model = modelId ?? entry.freeModels[0] ?? "";
1710
+ const t0 = Date.now();
1711
+ const controller = new AbortController();
1712
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
1713
+ try {
1714
+ let res;
1715
+ if (entry.api === "google-generative-ai") {
1716
+ const url = `${entry.baseUrl.replace(/\/$/, "")}/models/${model}:generateContent${key ? `?key=${encodeURIComponent(key)}` : ""}`;
1717
+ res = await fetch(url, {
1718
+ method: "POST",
1719
+ headers: { "content-type": "application/json" },
1720
+ body: JSON.stringify({ contents: [{ parts: [{ text: "ping" }] }] }),
1721
+ signal: controller.signal
1722
+ });
1723
+ } else {
1724
+ const headers = { "content-type": "application/json" };
1725
+ if (key) headers.authorization = `Bearer ${key}`;
1726
+ res = await fetch(`${entry.baseUrl.replace(/\/$/, "")}/chat/completions`, {
1727
+ method: "POST",
1728
+ headers,
1729
+ body: JSON.stringify({
1730
+ model,
1731
+ messages: [{ role: "user", content: "ping" }],
1732
+ max_tokens: 1
1733
+ }),
1734
+ signal: controller.signal
1735
+ });
1736
+ }
1737
+ return {
1738
+ ok: res.ok,
1739
+ latencyMs: Date.now() - t0,
1740
+ model,
1741
+ error: res.ok ? void 0 : `HTTP ${res.status}`
1742
+ };
1743
+ } catch (e) {
1744
+ return {
1745
+ ok: false,
1746
+ latencyMs: Date.now() - t0,
1747
+ model,
1748
+ error: e instanceof Error ? e.message : String(e)
1749
+ };
1750
+ } finally {
1751
+ clearTimeout(timer);
1752
+ }
1753
+ }
1754
+
1755
+ // src/routers/router-pool.ts
1756
+ var KEYLESS_SENTINEL = "oriro-keyless-no-key-required";
1757
+ function regFile() {
1758
+ return join11(oriroDir(), "routers", "registered.json");
1759
+ }
1760
+ function readReg() {
1761
+ try {
1762
+ return JSON.parse(readFileSync9(regFile(), "utf8"));
1763
+ } catch {
1764
+ return {};
1765
+ }
1766
+ }
1767
+ function writeReg(m) {
1768
+ mkdirSync6(join11(oriroDir(), "routers"), { recursive: true });
1769
+ writeFileSync8(regFile(), JSON.stringify(m, null, 2), "utf8");
1770
+ }
1771
+ async function addRouter(entry, opts) {
1772
+ if (entry.comingSoon) {
1773
+ return { ok: false, validation: { ok: false, latencyMs: 0, model: "", error: "coming soon" } };
1774
+ }
1775
+ if (entry.kind && entry.kind !== "chat") {
1776
+ return { ok: false, validation: { ok: false, latencyMs: 0, model: "", error: `'${entry.id}' is a ${entry.kind} router, not a chat router` } };
1777
+ }
1778
+ const key = opts?.key ?? (entry.keyless ? KEYLESS_SENTINEL : void 0);
1779
+ const v = await validateRouter(entry, key, opts?.modelId);
1780
+ if (!v.ok) return { ok: false, validation: v };
1781
+ const router = {
1782
+ id: entry.id,
1783
+ name: entry.displayName,
1784
+ baseUrl: entry.baseUrl,
1785
+ model: opts?.modelId ?? v.model ?? entry.freeModels[0] ?? "",
1786
+ apiKey: key ?? KEYLESS_SENTINEL
1787
+ };
1788
+ const reg = readReg();
1789
+ reg[entry.id] = router;
1790
+ writeReg(reg);
1791
+ savePool(oriroDir(), [...loadPool(oriroDir()), entry.id]);
1792
+ return { ok: true, validation: v };
1793
+ }
1794
+ function useRouters(ids) {
1795
+ const reg = readReg();
1796
+ const applied = ids.filter((id) => reg[id]);
1797
+ const unknown = ids.filter((id) => !reg[id]);
1798
+ if (applied.length > 0) savePool(oriroDir(), applied);
1799
+ return { applied, unknown };
1800
+ }
1801
+ function resolvePool() {
1802
+ const reg = readReg();
1803
+ return loadPool(oriroDir()).map((id) => reg[id]).filter((r) => Boolean(r));
1804
+ }
1805
+
1806
+ // src/routers/onboarding.ts
1807
+ function markerFile() {
1808
+ return join12(oriroDir(), "routers", "onboarded.json");
1809
+ }
1810
+ function hasRouterChoice() {
1811
+ try {
1812
+ return existsSync4(markerFile());
1813
+ } catch {
1814
+ return false;
1815
+ }
1816
+ }
1817
+ function markRouterOnboarded() {
1818
+ try {
1819
+ mkdirSync7(join12(oriroDir(), "routers"), { recursive: true });
1820
+ writeFileSync9(markerFile(), `${JSON.stringify({ onboardedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
1821
+ `, "utf8");
1822
+ } catch {
1823
+ }
1824
+ }
1825
+ async function runRouterOnboarding() {
1826
+ stdout4.write(
1827
+ `
1828
+ ${accent("Routers")} \u2014 ORIRO runs on a ${accent("free keyless router")} by default. No key, $0, works right now.
1829
+ ${dim("Add your own key (any free provider) for a faster, private lane \u2014 or skip and stay keyless.")}
1830
+ `
1831
+ );
1832
+ const rl = createInterface3({ input: stdin3, output: stdout4 });
1833
+ try {
1834
+ const add = (await ask(rl, ` Add your own key now? ${dim("[y/N]")} `)).trim().toLowerCase();
1835
+ if (add === "y" || add === "yes") {
1836
+ const picks = ROUTER_CATALOG.filter(
1837
+ (r) => !r.comingSoon && !r.keyless && (!r.kind || r.kind === "chat")
1838
+ ).slice(0, 8);
1839
+ stdout4.write(`
1840
+ ${dim("Free providers (grab a free key from each provider's site):")}
1841
+ `);
1842
+ for (const r of picks) {
1843
+ stdout4.write(` ${accent(r.id.padEnd(14))} ${dim(r.displayName)}
1844
+ `);
1845
+ }
1846
+ stdout4.write(` ${dim("\u2026or any id from `oriro routers list`")}
1847
+
1848
+ `);
1849
+ const slug = (await ask(rl, ` Which provider? ${dim("(id, or blank to skip)")} `)).trim();
1850
+ if (slug) {
1851
+ const entry = routerById(slug);
1852
+ if (!entry) {
1853
+ stdout4.write(` ${dim(`Unknown '${slug}' \u2014 skipped. You can add it later: oriro routers add ${slug}`)}
1854
+ `);
1855
+ } else {
1856
+ const key = (await ask(rl, ` Paste your ${accent(entry.displayName)} API key: `)).trim();
1857
+ if (key) {
1858
+ stdout4.write(` ${dim("Validating\u2026")}
1859
+ `);
1860
+ const res = await addRouter(entry, { key });
1861
+ if (res.ok) {
1862
+ stdout4.write(
1863
+ ` ${accent("\u2713")} added ${accent(slug)} (${res.validation.latencyMs}ms) \u2014 it now races in your pool.
1864
+ `
1865
+ );
1866
+ } else {
1867
+ stdout4.write(
1868
+ ` ${dim(`Couldn't add ${slug}: ${res.validation.error ?? "validation failed"}. Staying keyless \u2014 retry: oriro routers add ${slug} --key <key>`)}
1869
+ `
1870
+ );
1871
+ }
1872
+ } else {
1873
+ stdout4.write(` ${dim("No key entered \u2014 staying keyless.")}
1874
+ `);
1875
+ }
1876
+ }
1877
+ }
1878
+ }
1879
+ } finally {
1880
+ rl.close();
1881
+ }
1882
+ markRouterOnboarded();
1883
+ stdout4.write(` ${dim("Manage routers anytime: ")}${accent("oriro routers list \xB7 add \xB7 use")}
1884
+ `);
1885
+ }
1886
+
1887
+ // src/onboarding/wrapper.ts
1888
+ function isFirstRun() {
1889
+ return !isLanguageConfigured() || !hasScribeChoice();
1890
+ }
1891
+ async function askYesNo(question) {
1892
+ const rl = createInterface4({ input: stdin4, output: stdout5 });
1893
+ try {
1894
+ const a = (await ask(rl, `${question} ${dim("[Y/n]")} `)).trim().toLowerCase();
1895
+ return a === "" || a === "y" || a === "yes";
1896
+ } finally {
1897
+ rl.close();
1898
+ }
1899
+ }
1900
+ async function runOnboarding() {
1901
+ stdout5.write(banner());
1902
+ await runLanguageOnboarding();
1903
+ await activateGuardian();
1904
+ stdout5.write(` ${accent("\u{1F6E1} Guardian V3")} is on by default. ${accent("\u{1F9ED} Head")} is ready.
1905
+
1906
+ `);
1907
+ if (!isAvatarConfigured()) await runAvatarOnboarding();
1908
+ if (!hasScribeChoice()) {
1909
+ const yes = await askYesNo(
1910
+ "Remember with me? The Scriber keeps your work in context on THIS machine only \u2014 it never leaves it."
1911
+ );
1912
+ setScribeConsent(yes);
1913
+ stdout5.write(yes ? ` ${accent("\u{1F4D3} Scriber")} on.
1914
+ ` : ` ${dim("Scriber off \u2014 `oriro scribe on` anytime.")}
1915
+ `);
1916
+ }
1917
+ if (!hasRouterChoice()) await runRouterOnboarding();
1918
+ stdout5.write(`
1919
+ ${accent("ORIRO is ready.")} ${dim("Type to chat \xB7 /exit to leave")}
1920
+
1921
+ `);
1922
+ }
1923
+
1924
+ // src/onboarding/assemble.ts
1925
+ import {
1926
+ createAgentSession as createAgentSession2,
1927
+ AuthStorage as AuthStorage2,
1928
+ ModelRegistry as ModelRegistry2,
1929
+ SessionManager as SessionManager2,
1930
+ SettingsManager,
1931
+ DefaultResourceLoader,
1932
+ getAgentDir
1933
+ } from "@earendil-works/pi-coding-agent";
1934
+
1935
+ // src/routers/mux-provider.ts
1936
+ import { streamSimple as piStreamSimple, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
1937
+ import { register as registerOpenAICompletions } from "@earendil-works/pi-ai/openai-completions";
1938
+
1939
+ // src/routers/mux.ts
1940
+ import { existsSync as existsSync5, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
1941
+ import { join as join13 } from "path";
1942
+ var COOLDOWN_DEFAULT_MS = 6e4;
1943
+ var UNHEALTHY_AFTER = 3;
1944
+ var RouterMux = class {
1945
+ stats = /* @__PURE__ */ new Map();
1946
+ now;
1947
+ constructor(routerIds, now = () => Date.now()) {
1948
+ this.now = now;
1949
+ for (const id of routerIds) {
1950
+ this.stats.set(id, {
1951
+ id,
1952
+ latencyMs: Number.POSITIVE_INFINITY,
1953
+ healthy: true,
1954
+ cooldownUntil: 0,
1955
+ consecutiveErrors: 0
1956
+ });
1957
+ }
1958
+ }
1959
+ /** Available routers, best-first (healthy, not cooling down, lowest latency). */
1960
+ ranked() {
1961
+ const t = this.now();
1962
+ return [...this.stats.values()].filter((s) => s.healthy && s.cooldownUntil <= t).sort((a, b) => a.latencyMs - b.latencyMs).map((s) => s.id);
1963
+ }
1964
+ recordSuccess(id, latencyMs) {
1965
+ const s = this.stats.get(id);
1966
+ if (!s) return;
1967
+ s.latencyMs = s.latencyMs === Number.POSITIVE_INFINITY ? latencyMs : 0.7 * s.latencyMs + 0.3 * latencyMs;
1968
+ s.consecutiveErrors = 0;
1969
+ s.healthy = true;
1970
+ }
1971
+ recordFailure(id, err) {
1972
+ const s = this.stats.get(id);
1973
+ if (!s) return;
1974
+ s.consecutiveErrors += 1;
1975
+ if (err?.status === 429) {
1976
+ s.cooldownUntil = this.now() + (err.retryAfterMs ?? COOLDOWN_DEFAULT_MS);
1977
+ }
1978
+ if (s.consecutiveErrors >= UNHEALTHY_AFTER) s.healthy = false;
1979
+ }
1980
+ /** Run a call through the best router, failing over on error. Throws only if all exhausted. */
1981
+ async run(call) {
1982
+ const order = this.ranked();
1983
+ if (order.length === 0) {
1984
+ throw new Error(
1985
+ "All selected routers are rate-limited or unavailable. Add a BYOK key, select more free routers, or retry shortly."
1986
+ );
1987
+ }
1988
+ let lastErr;
1989
+ for (const id of order) {
1990
+ const t0 = this.now();
1991
+ try {
1992
+ const result = await call(id);
1993
+ this.recordSuccess(id, this.now() - t0);
1994
+ return { result, routerId: id };
1995
+ } catch (e) {
1996
+ const err = e;
1997
+ this.recordFailure(id, { status: err?.status, retryAfterMs: err?.retryAfterMs });
1998
+ lastErr = e;
1999
+ }
2000
+ }
2001
+ throw lastErr instanceof Error ? lastErr : new Error("All selected routers failed this request.");
2002
+ }
2003
+ snapshot() {
2004
+ return [...this.stats.values()].map((s) => ({ ...s }));
2005
+ }
2006
+ load(stats) {
2007
+ for (const s of stats) if (this.stats.has(s.id)) this.stats.set(s.id, { ...s });
2008
+ }
2009
+ };
2010
+ function healthStatePath(dir) {
2011
+ return join13(dir, "routers", "health.json");
2012
+ }
2013
+ function saveMuxState(dir, stats) {
2014
+ const p = healthStatePath(dir);
2015
+ mkdirSync8(join13(dir, "routers"), { recursive: true });
2016
+ writeFileSync10(p, JSON.stringify(stats, null, 2), "utf8");
2017
+ }
2018
+ function loadMuxState(dir) {
2019
+ const p = healthStatePath(dir);
2020
+ if (!existsSync5(p)) return [];
2021
+ try {
2022
+ const stats = JSON.parse(readFileSync10(p, "utf8"));
2023
+ return stats.map((s) => ({ ...s, latencyMs: Number.isFinite(s.latencyMs) ? s.latencyMs : Number.POSITIVE_INFINITY }));
2024
+ } catch {
2025
+ return [];
2026
+ }
2027
+ }
2028
+
2029
+ // src/routers/floor.ts
2030
+ var KEYLESS_FLOOR = [
2031
+ {
2032
+ id: "pollinations",
2033
+ name: "Pollinations (free)",
2034
+ baseUrl: "https://text.pollinations.ai/openai",
2035
+ model: "openai",
2036
+ apiKey: "oriro-keyless"
2037
+ },
2038
+ {
2039
+ id: "ollama-local",
2040
+ name: "Ollama (on-device)",
2041
+ baseUrl: "http://localhost:11434/v1",
2042
+ model: "llama3.2",
2043
+ apiKey: "ollama"
2044
+ }
2045
+ ];
2046
+ function routerModel(r) {
2047
+ return {
2048
+ id: r.model,
2049
+ name: r.name,
2050
+ api: "openai-completions",
2051
+ provider: r.id,
2052
+ baseUrl: r.baseUrl,
2053
+ reasoning: false,
2054
+ input: ["text"],
2055
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2056
+ contextWindow: 128e3,
2057
+ maxTokens: 4096
2058
+ };
2059
+ }
2060
+
2061
+ // src/identity/filter.ts
2062
+ var ORIRO_IDENTITY = "You are ORIRO, a free on-device AI assistant in the user's terminal. You are ORIRO and only ORIRO. Never state, imply, or reveal that you are, or are built on, any other model or company (such as GPT, Claude, Gemini, Qwen, Llama, Mistral, DeepSeek, OpenAI, Anthropic, Google, or Meta). If asked what you are, you are ORIRO.";
2063
+ var BANNED_TEST = /\b(qwen|llama|mistral|mixtral|deepseek|gpt(?:-?\d(?:\.\d)?)?|claude|gemini|openai|anthropic|google|meta\s?ai|alibaba)\b/i;
2064
+ var BANNED_REPLACE = new RegExp(BANNED_TEST.source, "gi");
2065
+ var SELF_REF = /\b(i am|i'm|i was|based on|powered by|my name|my model|my architecture|trained|created by|made by|built (?:on|by)|developed by)\b/i;
2066
+ var SELF_INTRO = /\b(i am|i'm)\s+(a|an)\b/i;
2067
+ var AI_NOUN = /\b(assistant|ai|model|language model|bot|agent|chatbot)\b/i;
2068
+ function applyIdentity(context) {
2069
+ const sys = context.systemPrompt ? `${ORIRO_IDENTITY}
2070
+
2071
+ ${context.systemPrompt}` : ORIRO_IDENTITY;
2072
+ return { ...context, systemPrompt: sys };
2073
+ }
2074
+ function scrubIdentity(text) {
2075
+ return text.replace(/[^.?!\n]+[.?!]?/g, (sentence) => {
2076
+ let s = SELF_REF.test(sentence) && BANNED_TEST.test(sentence) ? sentence.replace(BANNED_REPLACE, "ORIRO") : sentence;
2077
+ if (!/\boriro\b/i.test(s) && SELF_INTRO.test(s) && AI_NOUN.test(s)) {
2078
+ s = s.replace(SELF_INTRO, "I am ORIRO, $2");
2079
+ }
2080
+ return s;
2081
+ });
2082
+ }
2083
+ function scrubMessageIdentity(msg) {
2084
+ return {
2085
+ ...msg,
2086
+ content: msg.content.map(
2087
+ (c) => c.type === "text" ? { ...c, text: scrubIdentity(c.text) } : c
2088
+ )
2089
+ };
2090
+ }
2091
+
2092
+ // src/routers/tool-sanitize.ts
2093
+ var CONTROL_TOKEN = /<\|[^|]*\|>/g;
2094
+ var RECIPIENT_PREFIX = /^(?:to=)?(?:functions?|tools?|recipient)[.=]/i;
2095
+ var RECIPIENT = /(?:to=)?(?:functions?|tools?|recipient)[.=]([A-Za-z0-9_.:-]+)/i;
2096
+ var CLEAN_NAME = /^[A-Za-z0-9_.:-]+$/;
2097
+ function sanitizeToolName(raw) {
2098
+ if (!raw) return raw;
2099
+ if (!raw.includes("<|") && !RECIPIENT_PREFIX.test(raw)) return raw;
2100
+ const base = (raw.split("<|")[0] ?? "").replace(RECIPIENT_PREFIX, "").trim();
2101
+ if (base && CLEAN_NAME.test(base)) return base;
2102
+ const recip = raw.match(RECIPIENT);
2103
+ if (recip?.[1]) return recip[1];
2104
+ const m = raw.replace(CONTROL_TOKEN, " ").match(/[A-Za-z_][A-Za-z0-9_.:-]*/);
2105
+ return m ? m[0] : raw;
2106
+ }
2107
+ function sanitizeMessageToolCalls(msg) {
2108
+ let changed = false;
2109
+ const content = msg.content.map((c) => {
2110
+ if (c.type === "toolCall") {
2111
+ const name = sanitizeToolName(c.name);
2112
+ if (name !== c.name) {
2113
+ changed = true;
2114
+ return { ...c, name };
2115
+ }
2116
+ }
2117
+ return c;
2118
+ });
2119
+ return changed ? { ...msg, content } : msg;
2120
+ }
2121
+ function sanitizeEventToolCalls(ev) {
2122
+ let next = ev;
2123
+ if ("partial" in next && next.partial) {
2124
+ const partial = sanitizeMessageToolCalls(next.partial);
2125
+ if (partial !== next.partial) next = { ...next, partial };
2126
+ }
2127
+ if (next.type === "toolcall_end" && next.toolCall) {
2128
+ const name = sanitizeToolName(next.toolCall.name);
2129
+ if (name !== next.toolCall.name) next = { ...next, toolCall: { ...next.toolCall, name } };
2130
+ }
2131
+ return next;
2132
+ }
2133
+
2134
+ // src/scribe/scribe-pi.ts
2135
+ import { existsSync as existsSync10, readFileSync as readFileSync16 } from "fs";
2136
+ import { Type } from "typebox";
2137
+
2138
+ // src/scribe/capture.ts
2139
+ import { closeSync as closeSync2, fsyncSync as fsyncSync2, mkdirSync as mkdirSync11, openSync as openSync2, writeSync as writeSync2 } from "fs";
2140
+ import { join as join15 } from "path";
2141
+
2142
+ // src/scribe/digest.ts
2143
+ import { existsSync as existsSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
2144
+
2145
+ // src/scribe/paths.ts
2146
+ import { join as join14 } from "path";
2147
+ function scribeDir() {
2148
+ const override = process.env.ORIRO_SCRIBE_DIR?.trim();
2149
+ return override && override.length > 0 ? override : join14(CONFIG_DIR, "scribe");
2150
+ }
2151
+ function journalFile(date) {
2152
+ return join14(scribeDir(), `${date}.md`);
2153
+ }
2154
+ function digestFile() {
2155
+ return join14(scribeDir(), "_digest.md");
2156
+ }
2157
+ function timelineFile() {
2158
+ return join14(scribeDir(), "_timeline.md");
2159
+ }
2160
+ function artifactsDir() {
2161
+ return join14(scribeDir(), "artifacts");
2162
+ }
2163
+
2164
+ // src/scribe/digest.ts
2165
+ var DIGEST_CAP = 8192;
2166
+ var TIMELINE_DAY_CAP = 400;
2167
+ function read(file4) {
2168
+ return existsSync6(file4) ? readFileSync11(file4, "utf8") : "";
2169
+ }
2170
+ function updateDigest(summary, context) {
2171
+ mkdirSync9(scribeDir(), { recursive: true });
2172
+ const existing = read(digestFile());
2173
+ let contextBlock = context?.trim();
2174
+ if (!contextBlock) {
2175
+ const m = existing.match(/## Context\n([\s\S]*?)\n## /);
2176
+ contextBlock = m?.[1]?.trim() ?? "_(not set yet)_";
2177
+ }
2178
+ const recentMatch = existing.match(/## Recent activity[^\n]*\n([\s\S]*)$/);
2179
+ const priorRecent = recentMatch?.[1]?.trim() ?? "";
2180
+ let recent = summary.trim() ? `- ${summary.trim()}
2181
+ ${priorRecent}` : priorRecent;
2182
+ const header2 = `# ORIRO Scribe \u2014 Digest
2183
+
2184
+ ## Context
2185
+ ${contextBlock}
2186
+
2187
+ ## Recent activity (newest first)
2188
+ `;
2189
+ let out = header2 + recent;
2190
+ while (Buffer.byteLength(out, "utf8") > DIGEST_CAP && recent.includes("\n")) {
2191
+ recent = recent.slice(0, recent.lastIndexOf("\n")).trimEnd();
2192
+ out = header2 + recent;
2193
+ }
2194
+ writeFileSync11(digestFile(), out, "utf8");
2195
+ }
2196
+ function updateTimeline(date, topic) {
2197
+ mkdirSync9(scribeDir(), { recursive: true });
2198
+ const clean = topic.replace(/\s+/g, " ").trim();
2199
+ if (!clean) return;
2200
+ const lines = read(timelineFile()).split("\n").filter(Boolean);
2201
+ const header2 = "# ORIRO Scribe \u2014 Timeline";
2202
+ const body = lines.filter((l) => l !== header2);
2203
+ const idx = body.findIndex((l) => l.startsWith(`- ${date} \xB7`));
2204
+ if (idx === -1) {
2205
+ body.push(`- ${date} \xB7 ${clean}`.slice(0, TIMELINE_DAY_CAP + date.length + 6));
2206
+ } else {
2207
+ let merged = `${body[idx]}; ${clean}`;
2208
+ if (merged.length > TIMELINE_DAY_CAP) merged = `${merged.slice(0, TIMELINE_DAY_CAP)}\u2026`;
2209
+ body[idx] = merged;
2210
+ }
2211
+ body.sort();
2212
+ writeFileSync11(timelineFile(), `${header2}
2213
+ ${body.join("\n")}
2214
+ `, "utf8");
2215
+ }
2216
+ function readDigest() {
2217
+ return read(digestFile());
2218
+ }
2219
+ function readTimeline() {
2220
+ return read(timelineFile());
2221
+ }
2222
+
2223
+ // src/scribe/journal.ts
2224
+ import {
2225
+ closeSync,
2226
+ existsSync as existsSync7,
2227
+ fsyncSync,
2228
+ mkdirSync as mkdirSync10,
2229
+ openSync,
2230
+ readFileSync as readFileSync12,
2231
+ writeSync
2232
+ } from "fs";
2233
+ function appendJournal(date, content) {
2234
+ mkdirSync10(scribeDir(), { recursive: true });
2235
+ const fd = openSync(journalFile(date), "a");
2236
+ try {
2237
+ writeSync(fd, content.endsWith("\n") ? content : `${content}
2238
+ `);
2239
+ fsyncSync(fd);
2240
+ } finally {
2241
+ closeSync(fd);
2242
+ }
2243
+ }
2244
+ function readJournal(date) {
2245
+ const f = journalFile(date);
2246
+ return existsSync7(f) ? readFileSync12(f, "utf8") : "";
2247
+ }
2248
+
2249
+ // src/scribe/redact.ts
2250
+ var RULES = [
2251
+ {
2252
+ label: "private-key",
2253
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
2254
+ },
2255
+ // Lone PEM markers — a key SPLIT across fields/turns leaves only a BEGIN-head or an END-tail in
2256
+ // one field. A field carrying either marker is key material: redact the marker + its adjacent body
2257
+ // (forward from BEGIN, backward to END) so no sub-threshold fragment can ever sit on disk.
2258
+ { label: "private-key", re: /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*/g },
2259
+ { label: "private-key", re: /[\s\S]*-----END[A-Z ]*PRIVATE KEY-----/g },
2260
+ { label: "anthropic-key", re: /sk-ant-[A-Za-z0-9_-]{20,}/g },
2261
+ { label: "openrouter-key", re: /sk-or-v1-[A-Za-z0-9]{20,}/g },
2262
+ // Stripe-style keys (sk_live_/pk_live_/rk_test_/…), underscore segments.
2263
+ { label: "stripe-key", re: /\b[srp]k_(?:live|test)_[A-Za-z0-9]{16,}/g },
2264
+ // Generic sk- secret keys — allow hyphenated segments (sk-live-…, sk-proj-…) so a second
2265
+ // hyphen no longer breaks the match (the gap the Scriber spike caught).
2266
+ { label: "secret-key-sk", re: /sk[-_][A-Za-z0-9][A-Za-z0-9-]{14,}/g },
2267
+ { label: "google-key", re: /AIza[0-9A-Za-z_-]{30,}/g },
2268
+ { label: "groq-key", re: /gsk_[A-Za-z0-9]{20,}/g },
2269
+ { label: "github-pat", re: /github_pat_[A-Za-z0-9_]{20,}/g },
2270
+ { label: "github-token", re: /gh[posr]_[A-Za-z0-9]{30,}/g },
2271
+ { label: "xai-key", re: /xai-[A-Za-z0-9]{20,}/g },
2272
+ { label: "aws-key", re: /AKIA[0-9A-Z]{16}/g },
2273
+ { label: "jwt", re: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}/g },
2274
+ { label: "telegram-token", re: /\b\d{8,10}:[A-Za-z0-9_-]{30,}\b/g },
2275
+ // Auth headers / inline credentials (any provider) — the audit found these leaked.
2276
+ { label: "bearer-token", re: /\bbearer\s+[A-Za-z0-9._~+/=-]{12,}/gi },
2277
+ { label: "basic-auth", re: /\bbasic\s+[A-Za-z0-9+/=]{12,}/gi },
2278
+ // key: value / key=value secrets (password, token, secret, api_key, access_key, …).
2279
+ { label: "secret-kv", re: /\b(?:pass(?:word|wd)?|pwd|secret|token|api[_-]?key|access[_-]?key|auth)\s*[:=]\s*\S{3,}/gi },
2280
+ // Credentials embedded in a URL: scheme://user:PASSWORD@host → redact the password.
2281
+ { label: "url-credential", re: /\b([a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:)[^/\s@]+(@)/gi },
2282
+ { label: "email", re: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
2283
+ { label: "phone", re: /(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}/g }
2284
+ ];
2285
+ function marker(label) {
2286
+ return `\u27E8REDACTED:${label}\u27E9`;
2287
+ }
2288
+ function entropy(s) {
2289
+ const freq = /* @__PURE__ */ new Map();
2290
+ for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
2291
+ let h = 0;
2292
+ for (const n of freq.values()) {
2293
+ const p = n / s.length;
2294
+ h -= p * Math.log2(p);
2295
+ }
2296
+ return h;
2297
+ }
2298
+ function looksLikeUnknownSecret(token) {
2299
+ if (token.length < 32) return false;
2300
+ if (token.includes("\u27E8REDACTED:")) return false;
2301
+ if (/^[0-9a-f]+$/i.test(token)) return false;
2302
+ const classes = (/[a-z]/.test(token) ? 1 : 0) + (/[A-Z]/.test(token) ? 1 : 0) + (/[0-9]/.test(token) ? 1 : 0);
2303
+ if (classes < 2) return false;
2304
+ return entropy(token) >= 4.2;
2305
+ }
2306
+ function redact(input) {
2307
+ const counts = /* @__PURE__ */ new Map();
2308
+ let text = input;
2309
+ for (const rule of RULES) {
2310
+ text = text.replace(rule.re, () => {
2311
+ counts.set(rule.label, (counts.get(rule.label) ?? 0) + 1);
2312
+ return marker(rule.label);
2313
+ });
2314
+ }
2315
+ text = text.split(/(\s+)/).map((tok) => {
2316
+ if (looksLikeUnknownSecret(tok)) {
2317
+ counts.set("high-entropy", (counts.get("high-entropy") ?? 0) + 1);
2318
+ return marker("high-entropy");
2319
+ }
2320
+ return tok;
2321
+ }).join("");
2322
+ const redactions = [...counts.entries()].map(([label, count]) => ({
2323
+ label,
2324
+ count
2325
+ }));
2326
+ return { text, redactions };
2327
+ }
2328
+ function containsSecret(text) {
2329
+ for (const rule of RULES) {
2330
+ rule.re.lastIndex = 0;
2331
+ if (rule.re.test(text)) return true;
2332
+ }
2333
+ for (const tok of text.split(/\s+/)) {
2334
+ if (looksLikeUnknownSecret(tok)) return true;
2335
+ }
2336
+ return false;
2337
+ }
2338
+
2339
+ // src/scribe/capture.ts
2340
+ var INLINE_CAP = 4e3;
2341
+ function sideFile(date, ts, kind, full) {
2342
+ mkdirSync11(artifactsDir(), { recursive: true });
2343
+ const name = `${date}_${ts.replace(/[:.]/g, "-")}_${kind}.md`;
2344
+ const p = join15(artifactsDir(), name);
2345
+ const fd = openSync2(p, "w");
2346
+ try {
2347
+ writeSync2(fd, full);
2348
+ fsyncSync2(fd);
2349
+ } finally {
2350
+ closeSync2(fd);
2351
+ }
2352
+ return p;
2353
+ }
2354
+ function field(date, ts, label, value) {
2355
+ if (!value || !value.trim()) return "";
2356
+ if (value.length > INLINE_CAP) {
2357
+ const ref = sideFile(date, ts, label.toLowerCase().replace(/\s+/g, "-"), value);
2358
+ return `**${label}** (full \u2192 ${ref}):
2359
+ ${value.slice(0, INLINE_CAP)}
2360
+ \u2026(truncated; full content in artifact)
2361
+
2362
+ `;
2363
+ }
2364
+ return `**${label}:**
2365
+ ${value}
2366
+
2367
+ `;
2368
+ }
2369
+ function renderTurn(rec) {
2370
+ let md = `## ${rec.ts}
2371
+
2372
+ `;
2373
+ md += field(rec.date, rec.ts, "User", rec.user);
2374
+ md += field(rec.date, rec.ts, "Router", rec.router);
2375
+ if (rec.tools?.length) md += `**Tools:** ${rec.tools.join(", ")}
2376
+
2377
+ `;
2378
+ if (rec.files?.length) md += `**Files:** ${rec.files.join(", ")}
2379
+
2380
+ `;
2381
+ md += field(rec.date, rec.ts, "Note", rec.note);
2382
+ return `${md}---
2383
+ `;
2384
+ }
2385
+ function oneLineSummary(rec) {
2386
+ const bits = [];
2387
+ if (rec.user) bits.push(rec.user.replace(/\s+/g, " ").slice(0, 80));
2388
+ if (rec.files?.length) bits.push(`files: ${rec.files.slice(0, 3).join(", ")}`);
2389
+ if (rec.note) bits.push(rec.note.replace(/\s+/g, " ").slice(0, 60));
2390
+ return bits.join(" \xB7 ") || "(activity)";
2391
+ }
2392
+ function redactRecord(rec) {
2393
+ const tally = /* @__PURE__ */ new Map();
2394
+ const rd = (s) => {
2395
+ if (!s) return s;
2396
+ const r = redact(s);
2397
+ for (const x of r.redactions) tally.set(x.label, (tally.get(x.label) ?? 0) + x.count);
2398
+ return r.text;
2399
+ };
2400
+ const safeRec = {
2401
+ ...rec,
2402
+ user: rd(rec.user),
2403
+ note: rd(rec.note),
2404
+ router: rd(rec.router),
2405
+ context: rd(rec.context),
2406
+ files: rec.files?.map((f) => rd(f) ?? f)
2407
+ };
2408
+ return { rec: safeRec, redactions: [...tally.entries()].map(([label, count]) => ({ label, count })) };
2409
+ }
2410
+ function captureTurn(rec) {
2411
+ const { rec: safeRec, redactions } = redactRecord(rec);
2412
+ const journal = renderTurn(safeRec);
2413
+ appendJournal(rec.date, `${journal}
2414
+ `);
2415
+ updateDigest(`${safeRec.ts} \xB7 ${oneLineSummary(safeRec)}`, safeRec.context);
2416
+ updateTimeline(safeRec.date, oneLineSummary(safeRec));
2417
+ const auditClean = !containsSecret(readJournal(rec.date)) && !containsSecret(readDigest() ?? "");
2418
+ return {
2419
+ journalDate: rec.date,
2420
+ redactions,
2421
+ bytes: Buffer.byteLength(journal, "utf8"),
2422
+ auditClean
2423
+ };
2424
+ }
2425
+
2426
+ // src/scribe/health.ts
2427
+ import {
2428
+ closeSync as closeSync3,
2429
+ fsyncSync as fsyncSync3,
2430
+ mkdirSync as mkdirSync12,
2431
+ openSync as openSync3,
2432
+ readFileSync as readFileSync13,
2433
+ writeFileSync as writeFileSync12,
2434
+ writeSync as writeSync3
2435
+ } from "fs";
2436
+ import { join as join16 } from "path";
2437
+ function healthFile() {
2438
+ return join16(scribeDir(), "_health.json");
2439
+ }
2440
+ function faultLogFile() {
2441
+ return join16(scribeDir(), "_faults.log");
2442
+ }
2443
+ function read2() {
2444
+ try {
2445
+ return JSON.parse(readFileSync13(healthFile(), "utf8"));
2446
+ } catch {
2447
+ return { faultCount: 0 };
2448
+ }
2449
+ }
2450
+ function write(h) {
2451
+ mkdirSync12(scribeDir(), { recursive: true });
2452
+ writeFileSync12(healthFile(), `${JSON.stringify(h, null, 2)}
2453
+ `, "utf8");
2454
+ }
2455
+ function recordHealth() {
2456
+ const h = read2();
2457
+ h.lastWriteAt = (/* @__PURE__ */ new Date()).toISOString();
2458
+ write(h);
2459
+ }
2460
+ function recordFault(role, err) {
2461
+ try {
2462
+ mkdirSync12(scribeDir(), { recursive: true });
2463
+ const msg = `${(/* @__PURE__ */ new Date()).toISOString()} [${role}] ${err instanceof Error ? err.message : String(err)}`;
2464
+ const fd = openSync3(faultLogFile(), "a");
2465
+ try {
2466
+ writeSync3(fd, `${msg}
2467
+ `);
2468
+ fsyncSync3(fd);
2469
+ } finally {
2470
+ closeSync3(fd);
2471
+ }
2472
+ const h = read2();
2473
+ h.faultCount = (h.faultCount ?? 0) + 1;
2474
+ h.lastFault = msg;
2475
+ write(h);
2476
+ } catch {
2477
+ }
2478
+ }
2479
+ function readHealth() {
2480
+ return read2();
2481
+ }
2482
+
2483
+ // src/scribe/wal.ts
2484
+ import {
2485
+ closeSync as closeSync4,
2486
+ existsSync as existsSync8,
2487
+ fsyncSync as fsyncSync4,
2488
+ mkdirSync as mkdirSync13,
2489
+ openSync as openSync4,
2490
+ readFileSync as readFileSync14,
2491
+ writeFileSync as writeFileSync13,
2492
+ writeSync as writeSync4
2493
+ } from "fs";
2494
+ import { join as join17 } from "path";
2495
+ function walFile() {
2496
+ return join17(scribeDir(), "_wal.jsonl");
2497
+ }
2498
+ function appendLine(obj) {
2499
+ mkdirSync13(scribeDir(), { recursive: true });
2500
+ const fd = openSync4(walFile(), "a");
2501
+ try {
2502
+ writeSync4(fd, `${JSON.stringify(obj)}
2503
+ `);
2504
+ fsyncSync4(fd);
2505
+ } finally {
2506
+ closeSync4(fd);
2507
+ }
2508
+ }
2509
+ function walAppend(id, rec) {
2510
+ appendLine({ t: "add", id, rec });
2511
+ }
2512
+ function walCommit(id) {
2513
+ appendLine({ t: "commit", id });
2514
+ }
2515
+ function walPending() {
2516
+ if (!existsSync8(walFile())) return [];
2517
+ const committed = /* @__PURE__ */ new Set();
2518
+ const adds = /* @__PURE__ */ new Map();
2519
+ for (const line of readFileSync14(walFile(), "utf8").split("\n")) {
2520
+ if (!line.trim()) continue;
2521
+ try {
2522
+ const e = JSON.parse(line);
2523
+ if (e.t === "commit") committed.add(e.id);
2524
+ else if (e.t === "add" && e.rec) adds.set(e.id, e.rec);
2525
+ } catch {
2526
+ }
2527
+ }
2528
+ const out = [];
2529
+ for (const [id, rec] of adds) {
2530
+ if (!committed.has(id)) out.push({ id, rec });
2531
+ }
2532
+ return out;
2533
+ }
2534
+ function walCompact() {
2535
+ if (!existsSync8(walFile())) return;
2536
+ const pending = walPending();
2537
+ const body = pending.map((p) => JSON.stringify({ t: "add", id: p.id, rec: p.rec })).join("\n");
2538
+ writeFileSync13(walFile(), body ? `${body}
2539
+ ` : "", "utf8");
2540
+ }
2541
+
2542
+ // src/scribe/supervisor.ts
2543
+ var draining = false;
2544
+ function uid(ts) {
2545
+ return `${ts}-${Math.random().toString(36).slice(2, 9)}`;
2546
+ }
2547
+ function drainBacklog() {
2548
+ if (draining) return;
2549
+ draining = true;
2550
+ try {
2551
+ let drained = 0;
2552
+ for (const e of walPending()) {
2553
+ try {
2554
+ captureTurn(e.rec);
2555
+ walCommit(e.id);
2556
+ drained++;
2557
+ } catch (err) {
2558
+ recordFault("standby-replay", err);
2559
+ break;
2560
+ }
2561
+ }
2562
+ if (drained > 0) walCompact();
2563
+ } finally {
2564
+ draining = false;
2565
+ }
2566
+ }
2567
+ function supervisedCapture(rec) {
2568
+ try {
2569
+ drainBacklog();
2570
+ const id = uid(rec.ts);
2571
+ const safe = redactRecord(rec).rec;
2572
+ walAppend(id, safe);
2573
+ try {
2574
+ const res = captureTurn(safe);
2575
+ walCommit(id);
2576
+ walCompact();
2577
+ recordHealth();
2578
+ return res;
2579
+ } catch (primaryErr) {
2580
+ recordFault("primary", primaryErr);
2581
+ try {
2582
+ const res = captureTurn(safe);
2583
+ walCommit(id);
2584
+ walCompact();
2585
+ recordHealth();
2586
+ return res;
2587
+ } catch (standbyErr) {
2588
+ recordFault("standby", standbyErr);
2589
+ return null;
2590
+ }
2591
+ }
2592
+ } catch (fatal) {
2593
+ recordFault("supervisor", fatal);
2594
+ return null;
2595
+ }
2596
+ }
2597
+
2598
+ // src/scribe/retrieval.ts
2599
+ import { existsSync as existsSync9, readFileSync as readFileSync15, readdirSync } from "fs";
2600
+ function listDays() {
2601
+ const dir = scribeDir();
2602
+ if (!existsSync9(dir)) return [];
2603
+ return readdirSync(dir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).sort();
2604
+ }
2605
+ function readDay(date) {
2606
+ const f = journalFile(date);
2607
+ return existsSync9(f) ? readFileSync15(f, "utf8") : "";
2608
+ }
2609
+ function searchScribe(query, limit = 100) {
2610
+ const q = query.toLowerCase().trim();
2611
+ if (!q) return [];
2612
+ const hits = [];
2613
+ for (const date of listDays().reverse()) {
2614
+ const lines = readDay(date).split("\n");
2615
+ for (let i = 0; i < lines.length; i++) {
2616
+ const ln = lines[i];
2617
+ if (ln && ln.toLowerCase().includes(q)) {
2618
+ hits.push({ date, line: i + 1, text: ln.trim().slice(0, 200) });
2619
+ if (hits.length >= limit) return hits;
2620
+ }
2621
+ }
2622
+ }
2623
+ return hits;
2624
+ }
2625
+
2626
+ // src/scribe/scribe-pi.ts
2627
+ function scribeTurn(input) {
2628
+ if (!isScribeEnabled()) return;
2629
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
2630
+ supervisedCapture({ ts, date: ts.slice(0, 10), ...input });
2631
+ }
2632
+ var pendingUserInput = "";
2633
+ function noteUserInput(text) {
2634
+ pendingUserInput = text;
2635
+ }
2636
+ function takePendingUserInput() {
2637
+ const u = pendingUserInput;
2638
+ pendingUserInput = "";
2639
+ return u;
2640
+ }
2641
+ function buildScribeContext() {
2642
+ if (!isScribeEnabled()) return "";
2643
+ const parts = [];
2644
+ try {
2645
+ const t = timelineFile();
2646
+ if (existsSync10(t)) parts.push(`# Work history \u2014 every day so far
2647
+ ${readFileSync16(t, "utf8").trim()}`);
2648
+ } catch {
2649
+ }
2650
+ try {
2651
+ const d = readDigest();
2652
+ if (d?.trim()) parts.push(`# Current context (recent)
2653
+ ${d.trim()}`);
2654
+ } catch {
2655
+ }
2656
+ if (!parts.length) return "";
2657
+ return `${parts.join("\n\n")}
2658
+
2659
+ (Call scribe_recall to fetch the full text of any past day or topic.)`;
2660
+ }
2661
+ function registerScribe(pi) {
2662
+ pi.registerTool({
2663
+ name: "scribe_recall",
2664
+ label: "ORIRO Scribe",
2665
+ description: "Recall the user's past work from the on-device journal: search by keyword, or read a specific day (YYYY-MM-DD). Use to recover decisions, code, files, and context from earlier sessions.",
2666
+ parameters: Type.Object({
2667
+ query: Type.Optional(Type.String({ description: "Keyword/topic to search across all journals." })),
2668
+ day: Type.Optional(Type.String({ description: "A specific day YYYY-MM-DD to read in full." }))
2669
+ }),
2670
+ async execute(_id, params) {
2671
+ let text;
2672
+ const details = {};
2673
+ if (!isScribeEnabled()) {
2674
+ text = "Scribe is off (the user has not enabled it).";
2675
+ } else if (params.day) {
2676
+ text = readDay(params.day) || `No journal for ${params.day}. Days: ${listDays().join(", ") || "none"}`;
2677
+ details.day = params.day;
2678
+ } else {
2679
+ const hits = params.query ? searchScribe(params.query) : [];
2680
+ details.hits = hits;
2681
+ text = hits.length ? hits.map((h) => `${h.date}:${h.line} ${h.text}`).join("\n") : `No matches${params.query ? ` for "${params.query}"` : ""}. Days recorded: ${listDays().join(", ") || "none"}`;
2682
+ }
2683
+ return { content: [{ type: "text", text }], details };
2684
+ }
2685
+ });
2686
+ }
2687
+ function attachScribe(session) {
2688
+ let user = "";
2689
+ let assistant = "";
2690
+ const tools = /* @__PURE__ */ new Set();
2691
+ session.subscribe((e) => {
2692
+ if (!isScribeEnabled()) return;
2693
+ if (e?.type === "user_message" || e?.type === "session_user_message") user = String(e.text ?? e.message ?? user);
2694
+ if (e?.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") assistant += e.assistantMessageEvent.delta ?? "";
2695
+ if ((e?.type === "tool_call" || e?.type === "tool_execution_start") && e.toolName) tools.add(String(e.toolName));
2696
+ if (e?.type === "agent_end") {
2697
+ const userText = takePendingUserInput() || user;
2698
+ scribeTurn({ user: userText || void 0, router: "oriro-free", tools: [...tools], note: assistant.slice(0, 4e3) || void 0 });
2699
+ user = "";
2700
+ assistant = "";
2701
+ tools.clear();
2702
+ }
2703
+ });
2704
+ }
2705
+
2706
+ // src/routers/mux-provider.ts
2707
+ var MUX_PROVIDER = "oriro-mux";
2708
+ var MUX_MODEL = "oriro-free";
2709
+ function errToCallError(msg) {
2710
+ const text = msg.errorMessage ?? "";
2711
+ return /\b429\b|rate.?limit|too many requests/i.test(text) ? { status: 429 } : {};
2712
+ }
2713
+ function buildErrorMessage(message) {
2714
+ return {
2715
+ role: "assistant",
2716
+ content: [],
2717
+ api: "openai-completions",
2718
+ provider: MUX_PROVIDER,
2719
+ model: MUX_MODEL,
2720
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
2721
+ stopReason: "error",
2722
+ timestamp: Date.now(),
2723
+ errorMessage: message
2724
+ };
2725
+ }
2726
+ async function driveMux(out, mux, byId, context, options) {
2727
+ let lastError;
2728
+ for (const id of mux.ranked()) {
2729
+ const router = byId.get(id);
2730
+ if (!router) continue;
2731
+ const t0 = Date.now();
2732
+ let committed = false;
2733
+ let lastPartial;
2734
+ try {
2735
+ const inner = piStreamSimple(routerModel(router), context, {
2736
+ ...options ?? {},
2737
+ apiKey: router.apiKey
2738
+ });
2739
+ let failedBeforeContent = false;
2740
+ for await (const ev of inner) {
2741
+ if (ev.type === "error") {
2742
+ mux.recordFailure(id, errToCallError(ev.error));
2743
+ if (!committed) {
2744
+ lastError = ev.error;
2745
+ failedBeforeContent = true;
2746
+ break;
2747
+ }
2748
+ out.push(ev);
2749
+ out.end(ev.error);
2750
+ return;
2751
+ }
2752
+ committed = true;
2753
+ if (ev.type === "done") {
2754
+ mux.recordSuccess(id, Date.now() - t0);
2755
+ const clean = sanitizeMessageToolCalls(scrubMessageIdentity(ev.message));
2756
+ out.push({ type: "done", reason: ev.reason, message: clean });
2757
+ out.end(clean);
2758
+ return;
2759
+ }
2760
+ lastPartial = ev.partial;
2761
+ out.push(sanitizeEventToolCalls(ev));
2762
+ }
2763
+ if (failedBeforeContent) continue;
2764
+ if (!committed) {
2765
+ mux.recordFailure(id, {});
2766
+ lastError ??= buildErrorMessage("Router returned no output.");
2767
+ continue;
2768
+ }
2769
+ mux.recordSuccess(id, Date.now() - t0);
2770
+ out.end(lastPartial ? sanitizeMessageToolCalls(scrubMessageIdentity(lastPartial)) : void 0);
2771
+ return;
2772
+ } catch (e) {
2773
+ mux.recordFailure(id, e);
2774
+ }
2775
+ }
2776
+ const msg = lastError ?? buildErrorMessage(
2777
+ "All keyless routers are unavailable. Add a BYOK key, select more free routers, or retry shortly."
2778
+ );
2779
+ out.push({ type: "error", reason: "error", error: msg });
2780
+ out.end(msg);
2781
+ }
2782
+ function registerOriroMux(registry, opts = {}) {
2783
+ registerOpenAICompletions();
2784
+ const pooled = resolvePool();
2785
+ const routers = opts.routers ?? (pooled.length > 0 ? pooled : KEYLESS_FLOOR);
2786
+ const byId = new Map(routers.map((r) => [r.id, r]));
2787
+ const mux = new RouterMux(routers.map((r) => r.id));
2788
+ try {
2789
+ mux.load(loadMuxState(oriroDir()));
2790
+ } catch {
2791
+ }
2792
+ registry.registerProvider(MUX_PROVIDER, {
2793
+ name: "ORIRO Free (keyless Mux)",
2794
+ api: "openai-completions",
2795
+ apiKey: "oriro-keyless",
2796
+ // Placeholder — required by registry validation but never used: our custom streamSimple
2797
+ // routes to the real keyless floor endpoints itself (see driveMux).
2798
+ baseUrl: "http://oriro-mux.local",
2799
+ models: [
2800
+ {
2801
+ id: MUX_MODEL,
2802
+ name: "ORIRO Free (best-router)",
2803
+ api: "openai-completions",
2804
+ baseUrl: "http://oriro-mux.local",
2805
+ reasoning: false,
2806
+ input: ["text"],
2807
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2808
+ contextWindow: 128e3,
2809
+ maxTokens: 4096
2810
+ }
2811
+ ],
2812
+ streamSimple: (_model, context, options) => {
2813
+ const out = createAssistantMessageEventStream();
2814
+ const ctx = applyIdentity(context);
2815
+ const memory = buildScribeContext();
2816
+ const withMemory = memory ? { ...ctx, systemPrompt: `${ctx.systemPrompt}
2817
+
2818
+ ${memory}` } : ctx;
2819
+ void driveMux(out, mux, byId, withMemory, options).finally(() => {
2820
+ try {
2821
+ saveMuxState(oriroDir(), mux.snapshot());
2822
+ } catch {
2823
+ }
2824
+ });
2825
+ return out;
2826
+ }
2827
+ });
2828
+ return registry.find(MUX_PROVIDER, MUX_MODEL);
2829
+ }
2830
+
2831
+ // src/head/pi-tool.ts
2832
+ import { Type as Type2 } from "typebox";
2833
+
2834
+ // src/head/comparison-engine.ts
2835
+ var SECTION_RULES = [
2836
+ {
2837
+ type: "hero",
2838
+ label: "Hero",
2839
+ priority: "CRITICAL",
2840
+ markup: [/<h1[\s>]/],
2841
+ recommend: "Add a clear above-the-fold hero \u2014 one headline that states the value + one primary CTA."
2842
+ },
2843
+ {
2844
+ type: "navigation",
2845
+ label: "Navigation",
2846
+ priority: "CRITICAL",
2847
+ markup: [/<nav[\s>]/, /role=["']navigation["']/],
2848
+ recommend: "Add a top navigation so visitors can reach key sections."
2849
+ },
2850
+ {
2851
+ type: "features",
2852
+ label: "Features",
2853
+ priority: "CRITICAL",
2854
+ text: [/\bfeatures?\b/, /\bwhat you (?:can|get)\b/, /\bcapabilit/],
2855
+ recommend: "Add a features section that spells out concrete capabilities, not adjectives."
2856
+ },
2857
+ {
2858
+ type: "pricing",
2859
+ label: "Pricing",
2860
+ priority: "CRITICAL",
2861
+ text: [/\bpricing\b/, /\bper month\b/, /\b\/mo\b/, /\bfree plan\b/, /\$\d/, /₹\d/, /€\d/],
2862
+ recommend: 'Add transparent pricing \u2014 a critical conversion element; even a single "Free" tier helps.'
2863
+ },
2864
+ {
2865
+ type: "cta",
2866
+ label: "Call-to-Action",
2867
+ priority: "CRITICAL",
2868
+ text: [/\bget started\b/, /\bsign up\b/, /\bstart (?:free|now|building)\b/, /\btry (?:it|now|free)\b/, /\bbook a demo\b/, /\bget a demo\b/],
2869
+ recommend: 'Add a strong, repeated primary CTA ("Get started") so the next step is obvious.'
2870
+ },
2871
+ {
2872
+ type: "testimonials",
2873
+ label: "Testimonials",
2874
+ priority: "HIGH",
2875
+ text: [/\btestimonial/, /\bwhat (?:our )?(?:customers|users) say\b/, /\bloved by\b/, /\breview(?:s|ed)\b/],
2876
+ recommend: "Add 2\u20133 customer testimonials with names/photos to build trust."
2877
+ },
2878
+ {
2879
+ type: "stats",
2880
+ label: "Stats / Metrics",
2881
+ priority: "HIGH",
2882
+ text: [/\b\d[\d,.]*\s*[kkmm]\+?\s*(?:users|customers|developers|downloads|teams)\b/, /\b9\d(?:\.\d+)?%\b/, /\buptime\b/],
2883
+ recommend: 'Add impressive metrics ("10K+ users", "99.9% uptime") as social proof.'
2884
+ },
2885
+ {
2886
+ type: "video",
2887
+ label: "Video",
2888
+ priority: "HIGH",
2889
+ markup: [/<video[\s>]/, /youtube\.com\/embed/, /player\.vimeo\.com/, /<iframe[^>]+(?:youtube|vimeo)/],
2890
+ text: [/\bwatch the (?:video|demo)\b/],
2891
+ recommend: "Add a short explainer/demo video \u2014 it lifts conversion on landing pages."
2892
+ },
2893
+ {
2894
+ type: "demo",
2895
+ label: "Live Demo",
2896
+ priority: "HIGH",
2897
+ text: [/\btry it (?:now|live|free)\b/, /\bplayground\b/, /\binteractive demo\b/, /\blive demo\b/],
2898
+ recommend: 'Add a "try it" live demo or playground so visitors experience the product immediately.'
2899
+ },
2900
+ {
2901
+ type: "socialProof",
2902
+ label: "Social Proof",
2903
+ priority: "HIGH",
2904
+ text: [/\btrusted by\b/, /\bbacked by\b/, /\bused by\b/, /\bas seen (?:in|on)\b/, /\bcustomers include\b/],
2905
+ recommend: 'Add social proof (customer/investor logos, "trusted by \u2026") near the hero.'
2906
+ },
2907
+ {
2908
+ type: "faq",
2909
+ label: "FAQ",
2910
+ priority: "MEDIUM",
2911
+ text: [/\bfaq\b/, /\bfrequently asked\b/],
2912
+ markup: [/<details[\s>]/],
2913
+ recommend: "Add an FAQ that answers the top objections before they become exits."
2914
+ },
2915
+ {
2916
+ type: "integrations",
2917
+ label: "Integrations",
2918
+ priority: "MEDIUM",
2919
+ text: [/\bintegrations?\b/, /\bworks with\b/, /\bconnect your\b/],
2920
+ recommend: "Add an integrations section showing what the product connects to."
2921
+ },
2922
+ {
2923
+ type: "newsletter",
2924
+ label: "Newsletter / Capture",
2925
+ priority: "MEDIUM",
2926
+ text: [/\bsubscribe\b/, /\bnewsletter\b/, /\bjoin (?:the )?waitlist\b/],
2927
+ markup: [/type=["']email["']/],
2928
+ recommend: "Add an email capture (newsletter/waitlist) so non-converting visitors are not lost."
2929
+ },
2930
+ {
2931
+ type: "comparison",
2932
+ label: "Comparison",
2933
+ priority: "MEDIUM",
2934
+ text: [/\bcompare\b/, /\bcomparison\b/, /\b vs\.? \b/, /\bwhy choose\b/],
2935
+ recommend: 'Add a comparison ("us vs alternatives") to win evaluators who are shopping around.'
2936
+ },
2937
+ {
2938
+ type: "team",
2939
+ label: "Team / About",
2940
+ priority: "LOW",
2941
+ text: [/\bour team\b/, /\bmeet the team\b/, /\bfounders?\b/, /\babout us\b/],
2942
+ recommend: "Add a brief team/about section to humanize the brand."
2943
+ }
3470
2944
  ];
3471
- function routerById(id) {
3472
- return ROUTER_CATALOG.find((r) => r.id === id);
2945
+ var PRIORITY_RANK = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
2946
+ var PRIORITY_EFFORT = { CRITICAL: "L", HIGH: "M", MEDIUM: "M", LOW: "S" };
2947
+ var FETCH_TIMEOUT_MS = 12e3;
2948
+ var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36 ORIRO-Inspector";
2949
+ async function fetchPage(url) {
2950
+ const controller = new AbortController();
2951
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2952
+ const start = Date.now();
2953
+ try {
2954
+ const res = await fetch(url, {
2955
+ signal: controller.signal,
2956
+ redirect: "follow",
2957
+ headers: { "user-agent": UA, accept: "text/html,application/xhtml+xml" }
2958
+ });
2959
+ const html = await res.text();
2960
+ return { html, ms: Date.now() - start, status: res.status, ok: res.ok, error: "" };
2961
+ } catch (err) {
2962
+ return { html: "", ms: Date.now() - start, status: 0, ok: false, error: err instanceof Error ? err.message : "fetch failed" };
2963
+ } finally {
2964
+ clearTimeout(timer);
2965
+ }
2966
+ }
2967
+ function toText(html) {
2968
+ return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/\s+/g, " ").toLowerCase().trim();
2969
+ }
2970
+ function firstMatch(re, hay) {
2971
+ const m = re.exec(hay);
2972
+ if (!m) return "";
2973
+ const slice = (m[0] ?? "").trim();
2974
+ return slice.length > 80 ? `${slice.slice(0, 77)}\u2026` : slice;
2975
+ }
2976
+ function detectSections(rawHtmlLower, text) {
2977
+ const found = [];
2978
+ for (const rule of SECTION_RULES) {
2979
+ let evidence = "";
2980
+ for (const re of rule.markup ?? []) {
2981
+ const hit = firstMatch(re, rawHtmlLower);
2982
+ if (hit) {
2983
+ evidence = hit;
2984
+ break;
2985
+ }
2986
+ }
2987
+ if (!evidence) {
2988
+ for (const re of rule.text ?? []) {
2989
+ const hit = firstMatch(re, text);
2990
+ if (hit) {
2991
+ evidence = hit;
2992
+ break;
2993
+ }
2994
+ }
2995
+ }
2996
+ if (evidence) found.push({ type: rule.type, label: rule.label, priority: rule.priority, evidence });
2997
+ }
2998
+ return found;
2999
+ }
3000
+ function extractMatches(re, html, max) {
3001
+ const out = [];
3002
+ for (const m of html.matchAll(re)) {
3003
+ const inner = (m[1] ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
3004
+ if (inner && !out.includes(inner)) out.push(inner);
3005
+ if (out.length >= max) break;
3006
+ }
3007
+ return out;
3008
+ }
3009
+ var CTA_WORDS = /\b(get started|sign up|start free|start now|start building|try (?:it|now|free)|book a demo|get a demo|request access|join (?:the )?waitlist|download)\b/i;
3010
+ function extractStructure(url, fr) {
3011
+ const html = fr.html;
3012
+ const lowerHtml = html.toLowerCase();
3013
+ const text = toText(html);
3014
+ const titleM = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
3015
+ const title = (titleM?.[1] ?? "").replace(/\s+/g, " ").trim();
3016
+ const descM = /<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i.exec(html) ?? /<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i.exec(html);
3017
+ const description = (descM?.[1] ?? "").replace(/\s+/g, " ").trim();
3018
+ const headings = extractMatches(/<h[1-3][^>]*>([\s\S]*?)<\/h[1-3]>/gi, html, 12);
3019
+ const ctaAll = extractMatches(/<(?:a|button)[^>]*>([\s\S]*?)<\/(?:a|button)>/gi, html, 80);
3020
+ const ctas = [];
3021
+ for (const c of ctaAll) {
3022
+ if (CTA_WORDS.test(c) && !ctas.includes(c)) ctas.push(c);
3023
+ if (ctas.length >= 10) break;
3024
+ }
3025
+ const forms = (lowerHtml.match(/<form[\s>]/g) ?? []).length;
3026
+ const links = (lowerHtml.match(/<a[\s>]/g) ?? []).length;
3027
+ const images = (lowerHtml.match(/<img[\s>]/g) ?? []).length;
3028
+ const hasVideo = /<video[\s>]/.test(lowerHtml) || /(?:youtube\.com\/embed|player\.vimeo\.com)/.test(lowerHtml);
3029
+ const domNodes = (html.match(/<[a-z!\/]/gi) ?? []).length;
3030
+ let note = "";
3031
+ if (fr.ok && text.length < 400 && domNodes < 60) {
3032
+ note = "Sparse HTML \u2014 likely a client-rendered (SPA) page; structure may be under-detected without a JS render.";
3033
+ }
3034
+ return {
3035
+ url,
3036
+ title,
3037
+ description,
3038
+ sections: detectSections(lowerHtml, text),
3039
+ headings,
3040
+ ctas,
3041
+ forms,
3042
+ links,
3043
+ images,
3044
+ hasVideo,
3045
+ metrics: { htmlBytes: html.length, domNodes, fetchMs: fr.ms, status: fr.status },
3046
+ ok: fr.ok && html.length > 0,
3047
+ note: fr.ok ? note : `Could not load: ${fr.error || `HTTP ${fr.status}`}`
3048
+ };
3049
+ }
3050
+ function ruleFor(type) {
3051
+ return SECTION_RULES.find((r) => r.type === type) ?? SECTION_RULES[0];
3052
+ }
3053
+ function analyzeGaps(target, competitors) {
3054
+ const targetTypes = new Set(target.sections.map((s) => s.type));
3055
+ const compPresence = /* @__PURE__ */ new Map();
3056
+ for (const comp of competitors) {
3057
+ if (!comp.ok) continue;
3058
+ for (const s of comp.sections) {
3059
+ const list = compPresence.get(s.type) ?? [];
3060
+ if (!list.includes(comp.url)) list.push(comp.url);
3061
+ compPresence.set(s.type, list);
3062
+ }
3063
+ }
3064
+ const missing = [];
3065
+ const parity = [];
3066
+ for (const [type, presentOn] of compPresence) {
3067
+ if (targetTypes.has(type)) {
3068
+ parity.push(type);
3069
+ } else {
3070
+ const rule = ruleFor(type);
3071
+ missing.push({ section: type, label: rule.label, priority: rule.priority, presentOn, recommendation: rule.recommend });
3072
+ }
3073
+ }
3074
+ missing.sort((a, b) => PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority] || b.presentOn.length - a.presentOn.length);
3075
+ const advantages = target.sections.filter((s) => !compPresence.has(s.type));
3076
+ return { missing, advantages, parity };
3077
+ }
3078
+ function generateActionItems(missing) {
3079
+ return missing.map((g) => ({
3080
+ title: `Add a ${g.label} section`,
3081
+ priority: g.priority,
3082
+ effort: PRIORITY_EFFORT[g.priority],
3083
+ rationale: `${g.presentOn.length} of the compared page(s) have it; you don't. ${g.recommendation}`
3084
+ }));
3085
+ }
3086
+ function hostOf(url) {
3087
+ try {
3088
+ return new URL(url).host.replace(/^www\./, "");
3089
+ } catch {
3090
+ return url;
3091
+ }
3092
+ }
3093
+ function generateSummary(target, competitors, gaps) {
3094
+ const okComps = competitors.filter((c) => c.ok);
3095
+ const tName = hostOf(target.url);
3096
+ if (!target.ok) return `Could not load ${tName} (${target.note}). Nothing to compare against yet.`;
3097
+ if (okComps.length === 0) return `Loaded ${tName} (${target.sections.length} sections) but none of the comparison URLs could be loaded.`;
3098
+ const crit = gaps.missing.filter((m) => m.priority === "CRITICAL").map((m) => m.label);
3099
+ const high = gaps.missing.filter((m) => m.priority === "HIGH").map((m) => m.label);
3100
+ const parts = [];
3101
+ parts.push(`${tName} has ${target.sections.length} detectable sections; compared against ${okComps.length} page(s).`);
3102
+ if (gaps.missing.length === 0) {
3103
+ parts.push("No structural gaps found \u2014 you cover everything they do.");
3104
+ } else {
3105
+ parts.push(`${gaps.missing.length} gap(s) found.`);
3106
+ if (crit.length) parts.push(`Critical: ${crit.join(", ")}.`);
3107
+ if (high.length) parts.push(`High: ${high.join(", ")}.`);
3108
+ }
3109
+ if (gaps.advantages.length) parts.push(`Your edge: ${gaps.advantages.map((a) => a.label).join(", ")}.`);
3110
+ return parts.join(" ");
3111
+ }
3112
+ function normalizeUrl(u) {
3113
+ const t = (u || "").trim();
3114
+ if (!t) return t;
3115
+ return /^https?:\/\//i.test(t) ? t : `https://${t}`;
3116
+ }
3117
+ async function comparePages(opts) {
3118
+ const targetUrl = normalizeUrl(opts.targetUrl);
3119
+ const competitorUrls = (opts.competitorUrls ?? []).map(normalizeUrl).filter((u) => u.length > 0).slice(0, 30);
3120
+ const [targetFetch, ...compFetches] = await Promise.all([
3121
+ fetchPage(targetUrl),
3122
+ ...competitorUrls.map((u) => fetchPage(u))
3123
+ ]);
3124
+ const target = extractStructure(targetUrl, targetFetch ?? { html: "", ms: 0, status: 0, ok: false, error: "no fetch" });
3125
+ const competitors = competitorUrls.map(
3126
+ (u, i) => extractStructure(u, compFetches[i] ?? { html: "", ms: 0, status: 0, ok: false, error: "no fetch" })
3127
+ );
3128
+ const gaps = analyzeGaps(target, competitors);
3129
+ return {
3130
+ target,
3131
+ competitors,
3132
+ missing: gaps.missing,
3133
+ advantages: gaps.advantages,
3134
+ parity: gaps.parity,
3135
+ actionItems: generateActionItems(gaps.missing),
3136
+ summary: generateSummary(target, competitors, gaps)
3137
+ };
3138
+ }
3139
+
3140
+ // src/head/pi-tool.ts
3141
+ function summarizeForCoder(report) {
3142
+ const lines = [report.summary];
3143
+ const page = (p) => ` \u2022 ${p.url} \u2014 ${p.ok ? `${p.sections.length} sections: ${p.sections.map((s) => s.type).join(", ")}` : `not readable (${p.note})`}`;
3144
+ lines.push("Pages seen:");
3145
+ lines.push(page(report.target));
3146
+ for (const c of report.competitors) if (c.url !== report.target.url) lines.push(page(c));
3147
+ if (report.missing.length) {
3148
+ lines.push("Missing on the target (gaps to build):");
3149
+ for (const g of report.missing.slice(0, 12)) lines.push(` \u2022 ${g.label} (${g.priority}) \u2014 ${g.recommendation}`);
3150
+ }
3151
+ if (report.actionItems.length) {
3152
+ lines.push("Suggested action items:");
3153
+ for (const a of report.actionItems.slice(0, 12)) lines.push(` \u2192 ${a.title} [${a.priority}/${a.effort}] \u2014 ${a.rationale}`);
3154
+ }
3155
+ return lines.join("\n");
3156
+ }
3157
+ var InspectSiteParams = Type2.Object({
3158
+ url: Type2.String({ description: "The target website URL to inspect or rebuild from." }),
3159
+ competitors: Type2.Optional(
3160
+ Type2.Array(Type2.String(), { description: "Optional competitor/reference URLs to compare the target against." })
3161
+ )
3162
+ });
3163
+ function registerHead(pi) {
3164
+ pi.registerTool({
3165
+ name: "inspect_site",
3166
+ label: "ORIRO Head",
3167
+ description: "Go out to a live website and SEE it: its sections, CTAs, structure, and any gaps versus competitor URLs. Returns a structured report to build from. Call this whenever the user wants to look at, compare against, or rebuild a website/page.",
3168
+ parameters: InspectSiteParams,
3169
+ async execute(_toolCallId, params) {
3170
+ const target = params.url;
3171
+ const competitors = params.competitors?.length ? params.competitors : [target];
3172
+ const report = await comparePages({ targetUrl: target, competitorUrls: competitors });
3173
+ return { content: [{ type: "text", text: summarizeForCoder(report) }], details: report };
3174
+ }
3175
+ });
3176
+ }
3177
+
3178
+ // src/orchestrate.ts
3179
+ import { createAgentSession, AuthStorage, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
3180
+ import { Type as Type3 } from "typebox";
3181
+ var MAX_AGENTS = 8;
3182
+ var MAX_CONCURRENCY = 4;
3183
+ async function runOnce(spec) {
3184
+ const authStorage = AuthStorage.inMemory();
3185
+ const modelRegistry = ModelRegistry.inMemory(authStorage);
3186
+ const model = registerOriroMux(modelRegistry);
3187
+ if (!model) return { ...spec, ok: false, output: "no free model available" };
3188
+ const { session } = await createAgentSession({
3189
+ model,
3190
+ authStorage,
3191
+ modelRegistry,
3192
+ sessionManager: SessionManager.inMemory(),
3193
+ noTools: "all"
3194
+ });
3195
+ let out = "";
3196
+ const unsub = session.subscribe((e) => {
3197
+ if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") out += e.assistantMessageEvent.delta ?? "";
3198
+ });
3199
+ try {
3200
+ await session.prompt(`You are the ${spec.role} sub-agent. ${spec.task}`);
3201
+ } catch (e) {
3202
+ return { ...spec, ok: false, output: e instanceof Error ? e.message : String(e) };
3203
+ } finally {
3204
+ unsub();
3205
+ session.dispose();
3206
+ }
3207
+ return { ...spec, ok: out.trim().length > 0, output: out.trim() };
3208
+ }
3209
+ async function runAgent(spec) {
3210
+ let last = await runOnce(spec);
3211
+ if (!last.ok) last = await runOnce(spec);
3212
+ return last;
3213
+ }
3214
+ async function runPool(items, n, fn) {
3215
+ const results = new Array(items.length);
3216
+ let i = 0;
3217
+ async function worker() {
3218
+ while (i < items.length) {
3219
+ const idx = i++;
3220
+ const item = items[idx];
3221
+ if (item === void 0) continue;
3222
+ results[idx] = await fn(item);
3223
+ }
3224
+ }
3225
+ await Promise.all(Array.from({ length: Math.min(n, items.length) }, () => worker()));
3226
+ return results;
3227
+ }
3228
+ async function orchestrate(opts) {
3229
+ const agents = opts.agents.slice(0, MAX_AGENTS);
3230
+ if ((opts.mode ?? "parallel") === "chain") {
3231
+ const results = [];
3232
+ let prev = "";
3233
+ for (const a of agents) {
3234
+ const r = await runAgent({ role: a.role, task: prev ? `${a.task}
3235
+
3236
+ Previous result:
3237
+ ${prev}` : a.task });
3238
+ results.push(r);
3239
+ prev = r.output;
3240
+ }
3241
+ return results;
3242
+ }
3243
+ return runPool(agents, MAX_CONCURRENCY, runAgent);
3244
+ }
3245
+ function registerOrchestrator(pi) {
3246
+ pi.registerTool({
3247
+ name: "deploy_agents",
3248
+ label: "ORIRO Orchestrator",
3249
+ description: "Deploy multiple sub-agents in parallel (or chained) to do work \u2014 e.g. 'spawn 4 QA + 2 coders, run the tests'. Each sub-agent runs FREE on the router pool. Give each agent a role and a task.",
3250
+ parameters: Type3.Object({
3251
+ agents: Type3.Array(Type3.Object({ role: Type3.String(), task: Type3.String() }), {
3252
+ description: "The sub-agents to deploy (max 8)."
3253
+ }),
3254
+ mode: Type3.Optional(Type3.Union([Type3.Literal("parallel"), Type3.Literal("chain")]))
3255
+ }),
3256
+ async execute(_id, params) {
3257
+ const results = await orchestrate({ agents: params.agents, mode: params.mode });
3258
+ const text = results.map((r) => `[${r.role}] ${r.ok ? "\u2713" : "\u2717"} ${r.output.slice(0, 300)}`).join("\n");
3259
+ return { content: [{ type: "text", text }], details: { results } };
3260
+ }
3261
+ });
3262
+ }
3263
+
3264
+ // src/skills/loader.ts
3265
+ import { loadSkills, formatSkillsForPrompt } from "@earendil-works/pi-coding-agent";
3266
+ import { fileURLToPath } from "url";
3267
+ import { existsSync as existsSync11 } from "fs";
3268
+ import { dirname as dirname2, join as join18 } from "path";
3269
+ function packageRoot(start) {
3270
+ let dir = start;
3271
+ for (let i = 0; i < 10; i++) {
3272
+ if (existsSync11(join18(dir, "package.json"))) return dir;
3273
+ const parent = dirname2(dir);
3274
+ if (parent === dir) break;
3275
+ dir = parent;
3276
+ }
3277
+ return start;
3278
+ }
3279
+ function skillsDir() {
3280
+ if (process.env.ORIRO_SKILLS_DIR) return process.env.ORIRO_SKILLS_DIR;
3281
+ return join18(packageRoot(dirname2(fileURLToPath(import.meta.url))), "skills");
3282
+ }
3283
+ async function loadOriroSkills(dir = skillsDir()) {
3284
+ const result = await loadSkills({
3285
+ cwd: dir,
3286
+ agentDir: dir,
3287
+ skillPaths: [dir],
3288
+ includeDefaults: false
3289
+ });
3290
+ const all = Array.isArray(result) ? result : result.skills ?? [];
3291
+ return {
3292
+ all,
3293
+ core: all.filter((s) => !s.disableModelInvocation),
3294
+ tail: all.filter((s) => s.disableModelInvocation),
3295
+ prompt: formatSkillsForPrompt(all)
3296
+ };
3297
+ }
3298
+
3299
+ // src/onboarding/assemble.ts
3300
+ async function assembleOriroSession(opts = {}) {
3301
+ const cwd = opts.cwd ?? process.cwd();
3302
+ const authStorage = AuthStorage2.inMemory();
3303
+ const modelRegistry = ModelRegistry2.inMemory(authStorage);
3304
+ const settingsManager = SettingsManager.create(cwd);
3305
+ const model = registerOriroMux(modelRegistry);
3306
+ if (!model) throw new Error("ORIRO keyless model unavailable");
3307
+ const resourceLoader = new DefaultResourceLoader({
3308
+ cwd,
3309
+ agentDir: getAgentDir(),
3310
+ settingsManager,
3311
+ additionalSkillPaths: [skillsDir()],
3312
+ extensionFactories: [registerGuardian, registerHead, registerScribe, registerOrchestrator]
3313
+ });
3314
+ await resourceLoader.reload();
3315
+ const { session, extensionsResult } = await createAgentSession2({
3316
+ model,
3317
+ authStorage,
3318
+ modelRegistry,
3319
+ settingsManager,
3320
+ sessionManager: SessionManager2.inMemory(),
3321
+ resourceLoader
3322
+ });
3323
+ attachScribe(session);
3324
+ return { session, extensionsResult };
3325
+ }
3326
+
3327
+ // src/language/nllb-translator.ts
3328
+ var NLLB_CODE = {
3329
+ en: "eng_Latn",
3330
+ zh: "zho_Hans",
3331
+ de: "deu_Latn",
3332
+ es: "spa_Latn",
3333
+ ru: "rus_Cyrl",
3334
+ ko: "kor_Hang",
3335
+ fr: "fra_Latn",
3336
+ ja: "jpn_Jpan",
3337
+ pt: "por_Latn",
3338
+ tr: "tur_Latn",
3339
+ pl: "pol_Latn",
3340
+ ca: "cat_Latn",
3341
+ nl: "nld_Latn",
3342
+ ar: "arb_Arab",
3343
+ sv: "swe_Latn",
3344
+ it: "ita_Latn",
3345
+ id: "ind_Latn",
3346
+ hi: "hin_Deva",
3347
+ fi: "fin_Latn",
3348
+ vi: "vie_Latn",
3349
+ he: "heb_Hebr",
3350
+ uk: "ukr_Cyrl",
3351
+ el: "ell_Grek",
3352
+ ms: "zsm_Latn",
3353
+ cs: "ces_Latn",
3354
+ ro: "ron_Latn",
3355
+ da: "dan_Latn",
3356
+ hu: "hun_Latn",
3357
+ ta: "tam_Taml",
3358
+ no: "nob_Latn",
3359
+ th: "tha_Thai",
3360
+ ur: "urd_Arab",
3361
+ hr: "hrv_Latn",
3362
+ bg: "bul_Cyrl",
3363
+ lt: "lit_Latn",
3364
+ mi: "mri_Latn",
3365
+ ml: "mal_Mlym",
3366
+ cy: "cym_Latn",
3367
+ sk: "slk_Latn",
3368
+ te: "tel_Telu",
3369
+ fa: "pes_Arab",
3370
+ lv: "lvs_Latn",
3371
+ bn: "ben_Beng",
3372
+ sr: "srp_Cyrl",
3373
+ az: "azj_Latn",
3374
+ sl: "slv_Latn",
3375
+ kn: "kan_Knda",
3376
+ et: "est_Latn",
3377
+ mk: "mkd_Cyrl",
3378
+ eu: "eus_Latn",
3379
+ is: "isl_Latn",
3380
+ hy: "hye_Armn",
3381
+ ne: "npi_Deva",
3382
+ mn: "khk_Cyrl",
3383
+ bs: "bos_Latn",
3384
+ kk: "kaz_Cyrl",
3385
+ sq: "als_Latn",
3386
+ sw: "swh_Latn",
3387
+ gl: "glg_Latn",
3388
+ mr: "mar_Deva",
3389
+ pa: "pan_Guru",
3390
+ si: "sin_Sinh",
3391
+ km: "khm_Khmr",
3392
+ sn: "sna_Latn",
3393
+ yo: "yor_Latn",
3394
+ so: "som_Latn",
3395
+ af: "afr_Latn",
3396
+ oc: "oci_Latn",
3397
+ ka: "kat_Geor",
3398
+ be: "bel_Cyrl",
3399
+ tg: "tgk_Cyrl",
3400
+ sd: "snd_Arab",
3401
+ gu: "guj_Gujr",
3402
+ am: "amh_Ethi",
3403
+ yi: "ydd_Hebr",
3404
+ lo: "lao_Laoo",
3405
+ uz: "uzn_Latn",
3406
+ fo: "fao_Latn",
3407
+ ht: "hat_Latn",
3408
+ ps: "pbt_Arab",
3409
+ tk: "tuk_Latn",
3410
+ nn: "nno_Latn",
3411
+ mt: "mlt_Latn",
3412
+ sa: "san_Deva",
3413
+ lb: "ltz_Latn",
3414
+ my: "mya_Mymr",
3415
+ bo: "bod_Tibt",
3416
+ tl: "tgl_Latn",
3417
+ mg: "plt_Latn",
3418
+ as: "asm_Beng",
3419
+ tt: "tat_Cyrl",
3420
+ ln: "lin_Latn",
3421
+ ha: "hau_Latn",
3422
+ ba: "bak_Cyrl",
3423
+ jw: "jav_Latn",
3424
+ su: "sun_Latn",
3425
+ yue: "yue_Hant"
3426
+ };
3427
+ var ENG = "eng_Latn";
3428
+ var toNllb = (iso) => NLLB_CODE[(iso || "").toLowerCase()] ?? ENG;
3429
+ var NllbTranslator = class {
3430
+ pipe = null;
3431
+ loading = null;
3432
+ ready() {
3433
+ return this.pipe !== null;
3434
+ }
3435
+ /** Lazy-load NLLB-200 once (first-use download + cache). Idempotent. */
3436
+ async load(modelId = "Xenova/nllb-200-distilled-600M") {
3437
+ if (this.pipe) return;
3438
+ if (this.loading) return this.loading;
3439
+ this.loading = (async () => {
3440
+ const { pipeline } = await import("@huggingface/transformers");
3441
+ this.pipe = await pipeline("translation", modelId);
3442
+ })();
3443
+ return this.loading;
3444
+ }
3445
+ async run(text, src, tgt) {
3446
+ if (!this.pipe) await this.load();
3447
+ if (!this.pipe) return text;
3448
+ const out = await this.pipe(text, { src_lang: src, tgt_lang: tgt });
3449
+ return out?.[0]?.translation_text?.trim() || text;
3450
+ }
3451
+ toEnglish(text, fromLang) {
3452
+ return this.run(text, toNllb(fromLang), ENG);
3453
+ }
3454
+ fromEnglish(english, toLang) {
3455
+ return this.run(english, ENG, toNllb(toLang));
3456
+ }
3457
+ };
3458
+ var instance = null;
3459
+ function setupNllbTranslator(opts) {
3460
+ if (!instance) {
3461
+ instance = new NllbTranslator();
3462
+ registerTranslator(instance);
3463
+ }
3464
+ if (opts?.preload) void instance.load();
3465
+ return instance;
3466
+ }
3467
+
3468
+ // src/language/gateway.ts
3469
+ var isEnglish2 = (code) => !code || code.toLowerCase().startsWith("en");
3470
+ var isCommand = (text) => text.trimStart().startsWith("/");
3471
+ async function ensureReady() {
3472
+ try {
3473
+ await setupNllbTranslator().load();
3474
+ } catch {
3475
+ }
3476
+ }
3477
+ async function translateIncoming(message) {
3478
+ const lang = getTerminalLanguage().code;
3479
+ if (isEnglish2(lang) || !message.trim() || isCommand(message)) return message;
3480
+ await ensureReady();
3481
+ return translateForCoder(message, lang);
3482
+ }
3483
+ async function translateOutgoing(text) {
3484
+ const lang = getTerminalLanguage().code;
3485
+ if (isEnglish2(lang) || !text.trim()) return text;
3486
+ await ensureReady();
3487
+ return translateForUser(text, lang);
3488
+ }
3489
+
3490
+ // src/repl-ui/tui-repl.ts
3491
+ import { ProcessTerminal, TUI, Editor, Text, Container } from "@earendil-works/pi-tui";
3492
+
3493
+ // src/repl-ui/permission.ts
3494
+ var MODES = ["manual", "accept_edits", "auto", "plan"];
3495
+ var MODE_META = {
3496
+ manual: { label: "Manual", indicator: "\u25CF" },
3497
+ accept_edits: { label: "Accept Edits", indicator: "\u270E" },
3498
+ auto: { label: "Auto", indicator: "\u23F5\u23F5" },
3499
+ plan: { label: "Plan", indicator: "\u25A2" }
3500
+ };
3501
+ var current = "manual";
3502
+ function getMode() {
3503
+ return current;
3504
+ }
3505
+ function cycleMode() {
3506
+ const i = MODES.indexOf(current);
3507
+ current = MODES[(i + 1) % MODES.length];
3508
+ return current;
3509
+ }
3510
+
3511
+ // src/repl-ui/tui-repl.ts
3512
+ var editorTheme = {
3513
+ borderColor: (s) => dim(s),
3514
+ selectList: {
3515
+ selectedPrefix: (s) => accent(s),
3516
+ selectedText: (s) => accent(s),
3517
+ description: (s) => dim(s),
3518
+ scrollInfo: (s) => dim(s),
3519
+ noMatch: (s) => dim(s)
3520
+ }
3521
+ };
3522
+ function footerText() {
3523
+ const cur = getMode();
3524
+ const bar = MODES.map((m) => {
3525
+ const meta = MODE_META[m];
3526
+ const s = `${meta.indicator} ${meta.label}`;
3527
+ return m === cur ? accent(s) : dim(s);
3528
+ }).join(dim(" \xB7 "));
3529
+ return `${bar} ${dim("Shift+Tab to switch \xB7 /exit")}`;
3530
+ }
3531
+ async function runTuiRepl(session) {
3532
+ const isEnglish3 = getTerminalLanguage().code.toLowerCase().startsWith("en");
3533
+ const term = new ProcessTerminal();
3534
+ const tui = new TUI(term, true);
3535
+ const chat = new Container();
3536
+ const editor = new Editor(tui, editorTheme, { paddingX: 1 });
3537
+ const sep = new Text(dim("\u2500".repeat(Math.max(8, term.columns))), 0, 0);
3538
+ const footer = new Text(footerText(), 0, 0);
3539
+ tui.addChild(chat);
3540
+ tui.addChild(editor);
3541
+ tui.addChild(sep);
3542
+ tui.addChild(footer);
3543
+ tui.setFocus(editor);
3544
+ const refreshFooter = () => {
3545
+ sep.setText(dim("\u2500".repeat(Math.max(8, term.columns))));
3546
+ footer.setText(footerText());
3547
+ tui.requestRender();
3548
+ };
3549
+ const removeListener = tui.addInputListener((data) => {
3550
+ if (data === "\x1B[Z") {
3551
+ cycleMode();
3552
+ refreshFooter();
3553
+ return { consume: true };
3554
+ }
3555
+ return void 0;
3556
+ });
3557
+ let stopped = false;
3558
+ const cleanup = () => {
3559
+ if (stopped) return;
3560
+ stopped = true;
3561
+ try {
3562
+ removeListener();
3563
+ } catch {
3564
+ }
3565
+ try {
3566
+ session.dispose();
3567
+ } catch {
3568
+ }
3569
+ try {
3570
+ tui.stop();
3571
+ } catch {
3572
+ }
3573
+ process.stdout.write(dim("\nBye.\n"));
3574
+ process.exit(0);
3575
+ };
3576
+ process.on("SIGINT", cleanup);
3577
+ let busy = false;
3578
+ editor.onSubmit = (raw) => {
3579
+ const text = raw.trim();
3580
+ if (!text || busy) return;
3581
+ const slash = text.toLowerCase();
3582
+ if (slash === "/exit" || slash === "/quit") return cleanup();
3583
+ if (slash === "/help" || slash === "/?") {
3584
+ chat.addChild(new Text(dim(" Just type to chat. Shift+Tab cycles posture. /exit to leave."), 0, 0));
3585
+ editor.setText("");
3586
+ tui.requestRender();
3587
+ return;
3588
+ }
3589
+ editor.addToHistory(text);
3590
+ editor.setText("");
3591
+ chat.addChild(new Text(`${accent("\u203A")} ${text}`, 0, 1));
3592
+ const streaming = new Text(dim("\u2026"), 0, 0);
3593
+ chat.addChild(streaming);
3594
+ tui.requestRender();
3595
+ busy = true;
3596
+ void (async () => {
3597
+ const english = await translateIncoming(text);
3598
+ noteUserInput(text);
3599
+ let out = "";
3600
+ const unsub = session.subscribe(
3601
+ (e) => {
3602
+ if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") {
3603
+ out += e.assistantMessageEvent.delta ?? "";
3604
+ if (isEnglish3) {
3605
+ streaming.setText(out);
3606
+ tui.requestRender();
3607
+ }
3608
+ }
3609
+ }
3610
+ );
3611
+ try {
3612
+ await session.prompt(english);
3613
+ } catch {
3614
+ streaming.setText(dim("(every free router is busy right now \u2014 give it a moment and try again)"));
3615
+ tui.requestRender();
3616
+ busy = false;
3617
+ unsub();
3618
+ return;
3619
+ }
3620
+ unsub();
3621
+ const finalText = isEnglish3 ? out.trim() : await translateOutgoing(out.trim());
3622
+ streaming.setText(finalText || dim("(no response)"));
3623
+ tui.requestRender();
3624
+ busy = false;
3625
+ })();
3626
+ };
3627
+ tui.start();
3628
+ refreshFooter();
3629
+ await new Promise(() => {
3630
+ });
3631
+ }
3632
+
3633
+ // src/repl.ts
3634
+ function replHelp() {
3635
+ return `
3636
+ ${accent("ORIRO terminal \u2014 help")}
3637
+ ${dim("Just type to chat; ORIRO writes and runs code for you (keyless, free).")}
3638
+
3639
+ ${accent("/help")} this help ${accent("/exit")} or ${accent("/quit")} leave ${dim("Ctrl-D / Ctrl-C also exit")}
3640
+ ${dim("Run these OUTSIDE the chat (in your shell):")}
3641
+ ${dim("oriro skills \xB7 routers \xB7 connectors \xB7 channels \xB7 scribe \xB7 language \xB7 avatar")}
3642
+
3643
+ `;
3644
+ }
3645
+ async function runRepl() {
3646
+ if (isFirstRun()) await runOnboarding();
3647
+ else stdout6.write(banner());
3648
+ const { session } = await assembleOriroSession();
3649
+ if (stdin5.isTTY && stdout6.isTTY) {
3650
+ await runTuiRepl(session);
3651
+ return;
3652
+ }
3653
+ await runReadlineRepl(session);
3654
+ }
3655
+ async function runReadlineRepl(session) {
3656
+ const isEnglish3 = getTerminalLanguage().code.toLowerCase().startsWith("en");
3657
+ const rl = createInterface5({ input: stdin5, output: stdout6 });
3658
+ let closing = false;
3659
+ const onSigint = () => {
3660
+ if (closing) return;
3661
+ closing = true;
3662
+ stdout6.write(dim("\nBye.\n"));
3663
+ try {
3664
+ rl.close();
3665
+ } catch {
3666
+ }
3667
+ try {
3668
+ session.dispose();
3669
+ } catch {
3670
+ }
3671
+ process.exit(0);
3672
+ };
3673
+ process.on("SIGINT", onSigint);
3674
+ try {
3675
+ for (; ; ) {
3676
+ let line;
3677
+ try {
3678
+ line = (await rl.question("\u203A ")).trim();
3679
+ } catch {
3680
+ break;
3681
+ }
3682
+ if (!line) continue;
3683
+ const slash = line.toLowerCase();
3684
+ if (slash === "/exit" || slash === "/quit") break;
3685
+ if (slash === "/help" || slash === "/?") {
3686
+ stdout6.write(replHelp());
3687
+ continue;
3688
+ }
3689
+ const english = await translateIncoming(line);
3690
+ noteUserInput(line);
3691
+ let out = "";
3692
+ const unsub = session.subscribe(
3693
+ (e) => {
3694
+ if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta") {
3695
+ const d = e.assistantMessageEvent.delta ?? "";
3696
+ out += d;
3697
+ if (isEnglish3) stdout6.write(d);
3698
+ }
3699
+ }
3700
+ );
3701
+ try {
3702
+ await session.prompt(english);
3703
+ } finally {
3704
+ unsub();
3705
+ }
3706
+ if (isEnglish3) stdout6.write("\n\n");
3707
+ else stdout6.write(`${await translateOutgoing(out.trim())}
3708
+
3709
+ `);
3710
+ }
3711
+ } finally {
3712
+ process.removeListener("SIGINT", onSigint);
3713
+ if (!closing) {
3714
+ rl.close();
3715
+ session.dispose();
3716
+ stdout6.write(dim("\nBye.\n"));
3717
+ }
3718
+ }
3473
3719
  }
3474
3720
 
3475
3721
  // src/commands/ui.ts
@@ -3534,6 +3780,117 @@ function registerRoutersCommand(program2) {
3534
3780
  }
3535
3781
 
3536
3782
  // src/commands/scribe.ts
3783
+ import { readFileSync as readFileSync18 } from "fs";
3784
+
3785
+ // src/scribe/transcript.ts
3786
+ import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
3787
+ function parseHookStdin(raw) {
3788
+ try {
3789
+ const j = JSON.parse(raw);
3790
+ return {
3791
+ transcriptPath: typeof j.transcript_path === "string" ? j.transcript_path : void 0,
3792
+ cwd: typeof j.cwd === "string" ? j.cwd : void 0,
3793
+ sessionId: typeof j.session_id === "string" ? j.session_id : void 0,
3794
+ stopHookActive: j.stop_hook_active === true
3795
+ };
3796
+ } catch {
3797
+ return { stopHookActive: false };
3798
+ }
3799
+ }
3800
+ function shouldCapture(cwd) {
3801
+ if (process.env.ORIRO_SCRIBE_ONLY !== "1") return true;
3802
+ if (!cwd) return false;
3803
+ return /oriro/i.test(cwd.replace(/\\/g, "/"));
3804
+ }
3805
+ function textOf(content) {
3806
+ if (!content) return "";
3807
+ if (typeof content === "string") return content;
3808
+ return content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
3809
+ }
3810
+ function isHumanUser(e) {
3811
+ if (e.type !== "user" && e.message?.role !== "user") return false;
3812
+ const c = e.message?.content;
3813
+ if (typeof c === "string") return c.trim().length > 0;
3814
+ if (Array.isArray(c)) return c.some((b) => b.type === "text" && (b.text ?? "").trim().length > 0);
3815
+ return false;
3816
+ }
3817
+ var FILE_KEYS = ["file_path", "path", "notebook_path", "filePath"];
3818
+ function lastTurnFromTranscript(path) {
3819
+ if (!existsSync12(path)) return null;
3820
+ const raw = readFileSync17(path, "utf8");
3821
+ const entries = [];
3822
+ for (const line of raw.split("\n")) {
3823
+ if (!line.trim()) continue;
3824
+ try {
3825
+ entries.push(JSON.parse(line));
3826
+ } catch {
3827
+ }
3828
+ }
3829
+ if (entries.length === 0) return null;
3830
+ let anchor;
3831
+ let start = -1;
3832
+ for (let i = entries.length - 1; i >= 0; i--) {
3833
+ const e = entries[i];
3834
+ if (e && isHumanUser(e)) {
3835
+ start = i;
3836
+ anchor = e;
3837
+ break;
3838
+ }
3839
+ }
3840
+ const slice = start === -1 ? entries : entries.slice(start);
3841
+ const user = anchor ? textOf(anchor.message?.content) : "";
3842
+ const noteParts = [];
3843
+ const tools = /* @__PURE__ */ new Set();
3844
+ const files = /* @__PURE__ */ new Set();
3845
+ let ts;
3846
+ for (const e of slice) {
3847
+ if (e.timestamp) ts = e.timestamp;
3848
+ const role = e.type ?? e.message?.role;
3849
+ const content = e.message?.content;
3850
+ if (role === "assistant") {
3851
+ const t = textOf(content);
3852
+ if (t) noteParts.push(t);
3853
+ }
3854
+ if (Array.isArray(content)) {
3855
+ for (const b of content) {
3856
+ if (b.type === "tool_use" && b.name) {
3857
+ tools.add(b.name);
3858
+ const input = b.input ?? {};
3859
+ for (const k of FILE_KEYS) {
3860
+ const v = input[k];
3861
+ if (typeof v === "string" && v.trim()) files.add(v.trim());
3862
+ }
3863
+ }
3864
+ }
3865
+ }
3866
+ }
3867
+ const note = noteParts.join("\n\n").trim();
3868
+ if (!user && !note && tools.size === 0) return null;
3869
+ return {
3870
+ user: user || void 0,
3871
+ note: note || void 0,
3872
+ tools: tools.size ? [...tools] : void 0,
3873
+ files: files.size ? [...files] : void 0,
3874
+ ts
3875
+ };
3876
+ }
3877
+
3878
+ // src/commands/scribe.ts
3879
+ function readStdin() {
3880
+ try {
3881
+ return readFileSync18(0, "utf8");
3882
+ } catch {
3883
+ return "";
3884
+ }
3885
+ }
3886
+ function csv(v) {
3887
+ if (typeof v !== "string") return void 0;
3888
+ const arr = v.split(",").map((s) => s.trim()).filter(Boolean);
3889
+ return arr.length ? arr : void 0;
3890
+ }
3891
+ function hasContent(rec) {
3892
+ return Boolean(rec.user?.trim() || rec.note?.trim() || rec.tools?.length || rec.files?.length);
3893
+ }
3537
3894
  function registerScribeCommand(program2) {
3538
3895
  const scribe = program2.command("scribe").description("the consent-gated local work journal (off by default)");
3539
3896
  scribe.command("on").description("enable the journal (recorded locally at ~/.oriro/scribe, never leaves your machine)").action(() => {
@@ -3548,11 +3905,94 @@ function registerScribeCommand(program2) {
3548
3905
  scribe.command("status").description("show whether the journal is on or off").action(() => {
3549
3906
  info(isScribeEnabled() ? "Scriber: ON" : "Scriber: OFF (default)");
3550
3907
  });
3908
+ scribe.command("capture").description("capture one turn into the journal (used by the Claude Code Stop hook + /scribe skill)").option("--hook", "read the Claude Code Stop-hook JSON from stdin and capture the latest turn").option("--json <record>", "capture an explicit TurnRecord (JSON)").option("--user <text>", "the user/request text for this turn").option("--note <text>", "a note / assistant summary for this turn").option("--router <name>", "which router/model produced the turn").option("--files <list>", "comma-separated file paths touched").option("--tools <list>", "comma-separated tool names used").action((opts) => {
3909
+ try {
3910
+ if (!isScribeEnabled()) {
3911
+ if (!opts.hook) info("Scriber is OFF \u2014 run `oriro scribe on` first.");
3912
+ return;
3913
+ }
3914
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3915
+ let rec = null;
3916
+ if (opts.hook) {
3917
+ const hook = parseHookStdin(readStdin());
3918
+ if (hook.stopHookActive) return;
3919
+ if (!shouldCapture(hook.cwd)) return;
3920
+ if (!hook.transcriptPath) return;
3921
+ const turn = lastTurnFromTranscript(hook.transcriptPath);
3922
+ if (!turn) return;
3923
+ const ts = turn.ts ?? now;
3924
+ rec = {
3925
+ ts,
3926
+ date: ts.slice(0, 10),
3927
+ user: turn.user,
3928
+ note: turn.note,
3929
+ tools: turn.tools,
3930
+ files: turn.files,
3931
+ router: opts.router ?? "claude-code",
3932
+ context: hook.cwd ? `cwd: ${hook.cwd}` : void 0
3933
+ };
3934
+ } else if (opts.json) {
3935
+ const parsed = JSON.parse(opts.json);
3936
+ const ts = parsed.ts ?? now;
3937
+ rec = { ...parsed, ts, date: parsed.date ?? ts.slice(0, 10) };
3938
+ } else {
3939
+ rec = {
3940
+ ts: now,
3941
+ date: now.slice(0, 10),
3942
+ user: opts.user,
3943
+ note: opts.note,
3944
+ router: opts.router,
3945
+ files: csv(opts.files),
3946
+ tools: csv(opts.tools)
3947
+ };
3948
+ }
3949
+ if (!rec || !hasContent(rec)) {
3950
+ if (!opts.hook) info("nothing to capture.");
3951
+ return;
3952
+ }
3953
+ const res = supervisedCapture(rec);
3954
+ if (!opts.hook) {
3955
+ if (res) {
3956
+ const red = res.redactions.length ? ` (redacted: ${res.redactions.map((r) => `${r.label}\xD7${r.count}`).join(", ")})` : "";
3957
+ ok(`captured \u2192 ${res.journalDate}.md${red}`);
3958
+ } else {
3959
+ info("capture deferred (logged); will retry next turn.");
3960
+ }
3961
+ }
3962
+ } catch (err) {
3963
+ if (!opts.hook) fail(`scribe capture: ${err instanceof Error ? err.message : String(err)}`);
3964
+ }
3965
+ });
3966
+ scribe.command("recall <query>").description("full-text search across every day's journal").option("-n, --limit <n>", "max matches", "50").action((query, opts) => {
3967
+ const limit = Math.max(1, Number(opts.limit) || 50);
3968
+ const hits = searchScribe(query, limit);
3969
+ if (!hits.length) {
3970
+ info(`no matches for "${query}".`);
3971
+ return;
3972
+ }
3973
+ heading(`Scribe \u2014 ${hits.length} match(es) for "${query}"`);
3974
+ for (const h of hits) info(`${h.date}:${h.line} \xB7 ${h.text}`);
3975
+ });
3976
+ scribe.command("digest").description("print the rolling digest (recent context, injectable in a flash)").action(() => {
3977
+ const d = readDigest();
3978
+ process.stdout.write(d?.trim() ? `${d.trim()}
3979
+ ` : "\xB7 digest empty (nothing captured yet).\n");
3980
+ });
3981
+ scribe.command("timeline").description("print the full-history timeline (one line per day)").action(() => {
3982
+ const t = readTimeline();
3983
+ process.stdout.write(t?.trim() ? `${t.trim()}
3984
+ ` : "\xB7 timeline empty (nothing captured yet).\n");
3985
+ });
3986
+ scribe.command("health").description("show the scribe writer's health (last write, fault count)").action(() => {
3987
+ const h = readHealth();
3988
+ info(`last write: ${h.lastWriteAt ?? "never"}`);
3989
+ info(`faults: ${h.faultCount}${h.lastFault ? ` (last: ${h.lastFault})` : ""}`);
3990
+ });
3551
3991
  }
3552
3992
 
3553
3993
  // src/connectors/connectors.ts
3554
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "fs";
3555
- import { join as join18 } from "path";
3994
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "fs";
3995
+ import { join as join19 } from "path";
3556
3996
 
3557
3997
  // src/connectors/catalog.ts
3558
3998
  var CONNECTOR_CATALOG = [
@@ -4542,18 +4982,18 @@ function connectorBySlug(slug) {
4542
4982
 
4543
4983
  // src/connectors/connectors.ts
4544
4984
  function file2() {
4545
- return join18(oriroDir(), "connectors.json");
4985
+ return join19(oriroDir(), "connectors.json");
4546
4986
  }
4547
4987
  function readAdded() {
4548
4988
  try {
4549
- const v = JSON.parse(readFileSync17(file2(), "utf8"));
4989
+ const v = JSON.parse(readFileSync19(file2(), "utf8"));
4550
4990
  return Array.isArray(v) ? v : [];
4551
4991
  } catch {
4552
4992
  return [];
4553
4993
  }
4554
4994
  }
4555
4995
  function writeAdded(slugs) {
4556
- writeFileSync13(join18(ensureOriroDir(), "connectors.json"), JSON.stringify([...new Set(slugs)], null, 2), "utf8");
4996
+ writeFileSync14(join19(ensureOriroDir(), "connectors.json"), JSON.stringify([...new Set(slugs)], null, 2), "utf8");
4557
4997
  }
4558
4998
  function listConnectors(category) {
4559
4999
  return category ? CONNECTOR_CATALOG.filter((c) => c.category === category) : CONNECTOR_CATALOG;
@@ -4620,14 +5060,14 @@ function registerConnectorsCommand(program2) {
4620
5060
  }
4621
5061
 
4622
5062
  // src/channels/config.ts
4623
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "fs";
4624
- import { join as join19 } from "path";
5063
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync15 } from "fs";
5064
+ import { join as join20 } from "path";
4625
5065
  function file3() {
4626
- return join19(oriroDir(), "channels.json");
5066
+ return join20(oriroDir(), "channels.json");
4627
5067
  }
4628
5068
  function readChannels() {
4629
5069
  try {
4630
- const v = JSON.parse(readFileSync18(file3(), "utf8"));
5070
+ const v = JSON.parse(readFileSync20(file3(), "utf8"));
4631
5071
  return Array.isArray(v) ? v : [];
4632
5072
  } catch {
4633
5073
  return [];
@@ -4636,10 +5076,10 @@ function readChannels() {
4636
5076
  function saveChannel(cfg) {
4637
5077
  const all = readChannels().filter((c) => c.kind !== cfg.kind);
4638
5078
  all.push(cfg);
4639
- writeFileSync14(join19(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
5079
+ writeFileSync15(join20(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
4640
5080
  }
4641
5081
  function removeChannel(kind) {
4642
- writeFileSync14(join19(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
5082
+ writeFileSync15(join20(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
4643
5083
  }
4644
5084
 
4645
5085
  // src/channels/telegram.ts
@@ -4756,9 +5196,9 @@ async function startDiscord(token) {
4756
5196
  }
4757
5197
 
4758
5198
  // src/channels/whatsapp.ts
4759
- import { join as join20 } from "path";
5199
+ import { join as join21 } from "path";
4760
5200
  function whatsappAuthDir() {
4761
- return join20(oriroDir(), "whatsapp-auth");
5201
+ return join21(oriroDir(), "whatsapp-auth");
4762
5202
  }
4763
5203
  async function startWhatsApp() {
4764
5204
  let baileys;
@@ -4893,7 +5333,7 @@ function registerSkillsCommand(program2) {
4893
5333
  }
4894
5334
 
4895
5335
  // src/commands/language.ts
4896
- import { stdin as stdin5 } from "process";
5336
+ import { stdin as stdin6 } from "process";
4897
5337
  function resolveLanguage(input) {
4898
5338
  return languageByCode(input) ?? LANGUAGES.find((l) => l.name.toLowerCase() === input.trim().toLowerCase());
4899
5339
  }
@@ -4915,7 +5355,7 @@ function registerLanguageCommand(program2) {
4915
5355
  ok(`${accent(lang.name)} is now your terminal language.`);
4916
5356
  return;
4917
5357
  }
4918
- if (stdin5.isTTY) {
5358
+ if (stdin6.isTTY) {
4919
5359
  const lang = await selectLanguageInteractive();
4920
5360
  setTerminalLanguage(lang);
4921
5361
  ok(`${accent(lang.name)} is now your terminal language.`);
@@ -4928,7 +5368,7 @@ function registerLanguageCommand(program2) {
4928
5368
  }
4929
5369
 
4930
5370
  // src/commands/avatar.ts
4931
- import { stdin as stdin6 } from "process";
5371
+ import { stdin as stdin7 } from "process";
4932
5372
  function registerAvatarCommand(program2) {
4933
5373
  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) => {
4934
5374
  if (opts.list) {
@@ -4946,7 +5386,7 @@ function registerAvatarCommand(program2) {
4946
5386
  ok(`${accent(avatar.slug)} is now your terminal face.`);
4947
5387
  return;
4948
5388
  }
4949
- if (stdin6.isTTY) {
5389
+ if (stdin7.isTTY) {
4950
5390
  const chosen = await selectAvatarInteractive();
4951
5391
  if (!chosen) {
4952
5392
  info("no change.");