@oriro/orirocli 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1133 -130
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -5130,7 +5130,7 @@ var init_assemble = __esm({
5130
5130
  // src/agents/worktree.ts
5131
5131
  import { execFile } from "child_process";
5132
5132
  import { promisify } from "util";
5133
- import { join as join27, basename as basename2 } from "path";
5133
+ import { join as join28, basename as basename2 } from "path";
5134
5134
  function parseAgentsSlash(line) {
5135
5135
  const m = /^\/agents(?:\s+(\S[\s\S]*))?$/i.exec(line.trim());
5136
5136
  if (!m) return void 0;
@@ -5152,7 +5152,7 @@ function fanBranch(stamp, i) {
5152
5152
  return `oriro/agents/${stamp}-a${i + 1}`;
5153
5153
  }
5154
5154
  function fanDir(repoRoot, stamp, i) {
5155
- return join27(oriroDir(), "worktrees", `${basename2(repoRoot)}-${stamp}-a${i + 1}`);
5155
+ return join28(oriroDir(), "worktrees", `${basename2(repoRoot)}-${stamp}-a${i + 1}`);
5156
5156
  }
5157
5157
  async function git(cwd, ...args) {
5158
5158
  try {
@@ -7030,13 +7030,119 @@ function parsePlanSlash(line) {
7030
7030
  return { cmd: "reject" };
7031
7031
  }
7032
7032
 
7033
+ // src/repl-ui/slash-imagine.ts
7034
+ import { existsSync as existsSync15, writeFileSync as writeFileSync18 } from "fs";
7035
+ import { join as join26 } from "path";
7036
+
7037
+ // src/repl-ui/artifacts.ts
7038
+ var LANG_EXT = {
7039
+ python: "py",
7040
+ py: "py",
7041
+ javascript: "js",
7042
+ js: "js",
7043
+ typescript: "ts",
7044
+ ts: "ts",
7045
+ tsx: "tsx",
7046
+ jsx: "jsx",
7047
+ html: "html",
7048
+ css: "css",
7049
+ json: "json",
7050
+ yaml: "yaml",
7051
+ yml: "yml",
7052
+ bash: "sh",
7053
+ sh: "sh",
7054
+ shell: "sh",
7055
+ sql: "sql",
7056
+ go: "go",
7057
+ rust: "rs",
7058
+ rs: "rs",
7059
+ java: "java",
7060
+ c: "c",
7061
+ cpp: "cpp",
7062
+ "c++": "cpp",
7063
+ ruby: "rb",
7064
+ rb: "rb",
7065
+ php: "php",
7066
+ markdown: "md",
7067
+ md: "md",
7068
+ svg: "svg"
7069
+ };
7070
+ function extFor(lang) {
7071
+ return LANG_EXT[lang.toLowerCase()] ?? "txt";
7072
+ }
7073
+ function extractArtifacts(text) {
7074
+ const out = [];
7075
+ if (!text) return out;
7076
+ const fence = /```([\w+#.-]*)\n([\s\S]*?)```/g;
7077
+ let m;
7078
+ while ((m = fence.exec(text)) !== null) {
7079
+ const lang = (m[1] ?? "").trim();
7080
+ const content = (m[2] ?? "").replace(/\n$/, "");
7081
+ if (!content.trim()) continue;
7082
+ const isSvg = lang.toLowerCase() === "svg" || /^\s*<svg[\s>]/.test(content);
7083
+ out.push({
7084
+ kind: isSvg ? "svg" : "code",
7085
+ lang: lang || (isSvg ? "svg" : ""),
7086
+ content,
7087
+ suggestedName: `artifact-${out.length + 1}.${isSvg ? "svg" : extFor(lang)}`
7088
+ });
7089
+ }
7090
+ const svg = /<svg[\s>][\s\S]*?<\/svg>/gi;
7091
+ while ((m = svg.exec(text)) !== null) {
7092
+ const content = m[0];
7093
+ if (out.some((a) => a.content.includes(content))) continue;
7094
+ out.push({ kind: "svg", lang: "svg", content, suggestedName: `artifact-${out.length + 1}.svg` });
7095
+ }
7096
+ return out;
7097
+ }
7098
+ var current3 = [];
7099
+ function setArtifacts(a) {
7100
+ current3 = a;
7101
+ }
7102
+ function getArtifacts() {
7103
+ return current3;
7104
+ }
7105
+
7106
+ // src/repl-ui/slash-imagine.ts
7107
+ init_theme();
7108
+ function isImagineSlash(cmd) {
7109
+ return /^\/imagine(\s|$)/i.test(cmd.trim());
7110
+ }
7111
+ function imagineTask(raw) {
7112
+ const rest = raw.trim().replace(/^\/imagine\s*/i, "").trim();
7113
+ return rest.length ? rest : void 0;
7114
+ }
7115
+ var IMAGINE_PRIMER = "IMAGE MODE: you are ORIRO's image engine. Create ONE complete, self-contained SVG artwork for the request below. Reply with ONLY a single fenced ```svg code block containing valid standalone SVG \u2014 root <svg> with xmlns and a viewBox, generous use of shapes/paths/gradients, NO external images, fonts, scripts or links. No prose before or after the block.";
7116
+ function imagineDest(now, cwd = process.cwd()) {
7117
+ const p = (n, w = 2) => String(n).padStart(w, "0");
7118
+ const base = `imagine-${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
7119
+ let dest = join26(cwd, `${base}.svg`);
7120
+ for (let i = 2; existsSync15(dest); i++) dest = join26(cwd, `${base}-${i}.svg`);
7121
+ return dest;
7122
+ }
7123
+ function imagineResultLines(finalText, now = /* @__PURE__ */ new Date(), cwd) {
7124
+ const svg = extractArtifacts(finalText).find((a) => a.kind === "svg");
7125
+ if (!svg) {
7126
+ return [dim(" \u2300 no SVG came back this turn \u2014 /imagine again (free), or rephrase the scene.")];
7127
+ }
7128
+ const dest = imagineDest(now, cwd);
7129
+ try {
7130
+ writeFileSync18(dest, svg.content, "utf8");
7131
+ } catch (e) {
7132
+ return [dim(` \u2717 could not save the image: ${e instanceof Error ? e.message : String(e)} \u2014 /save it via /review instead`)];
7133
+ }
7134
+ return [
7135
+ ` ${fgHex(PALETTE.success, "\u2713 imagined")} \u2192 ${accent(dest)} ${dim(`(${svg.content.length} bytes \u2014 open it in any browser)`)}`
7136
+ ];
7137
+ }
7138
+
7033
7139
  // src/repl-ui/tui-repl.ts
7034
7140
  init_posture_gate();
7035
7141
  init_scribe_pi();
7036
7142
  init_filter();
7037
7143
 
7038
7144
  // src/repl-ui/verify-actions.ts
7039
- import { existsSync as existsSync15 } from "fs";
7145
+ import { existsSync as existsSync16 } from "fs";
7040
7146
  import { isAbsolute, resolve } from "path";
7041
7147
  var CLAIM = /\b(?:have|has)\s+been\s+created\b|\b(?:created|wrote|written|saved|generated)\b(?![ \t]*(?:by you|it yourself))/i;
7042
7148
  var SUGGESTION = /\byou\s+(?:can|could|should|may)\s+(?:create|add|save|make|put)\b/i;
@@ -7049,7 +7155,7 @@ function phantomFileWarning(reply, cwd = process.cwd()) {
7049
7155
  if (!p) continue;
7050
7156
  if (/^https?:|node_modules|<[^>]+>|your-|example\./i.test(p)) continue;
7051
7157
  const abs = isAbsolute(p) ? p : resolve(cwd, p.replace(/^[.][\\/]/, ""));
7052
- if (!existsSync15(abs)) missing.add(p);
7158
+ if (!existsSync16(abs)) missing.add(p);
7053
7159
  }
7054
7160
  if (missing.size === 0) return "";
7055
7161
  if (SUGGESTION.test(reply) && !/\b(?:have|has)\s+been\s+created\b/i.test(reply)) return "";
@@ -7169,78 +7275,7 @@ function handleUsage() {
7169
7275
  }
7170
7276
 
7171
7277
  // src/repl-ui/slash-artifacts.ts
7172
- import { existsSync as existsSync16, writeFileSync as writeFileSync18 } from "fs";
7173
-
7174
- // src/repl-ui/artifacts.ts
7175
- var LANG_EXT = {
7176
- python: "py",
7177
- py: "py",
7178
- javascript: "js",
7179
- js: "js",
7180
- typescript: "ts",
7181
- ts: "ts",
7182
- tsx: "tsx",
7183
- jsx: "jsx",
7184
- html: "html",
7185
- css: "css",
7186
- json: "json",
7187
- yaml: "yaml",
7188
- yml: "yml",
7189
- bash: "sh",
7190
- sh: "sh",
7191
- shell: "sh",
7192
- sql: "sql",
7193
- go: "go",
7194
- rust: "rs",
7195
- rs: "rs",
7196
- java: "java",
7197
- c: "c",
7198
- cpp: "cpp",
7199
- "c++": "cpp",
7200
- ruby: "rb",
7201
- rb: "rb",
7202
- php: "php",
7203
- markdown: "md",
7204
- md: "md",
7205
- svg: "svg"
7206
- };
7207
- function extFor(lang) {
7208
- return LANG_EXT[lang.toLowerCase()] ?? "txt";
7209
- }
7210
- function extractArtifacts(text) {
7211
- const out = [];
7212
- if (!text) return out;
7213
- const fence = /```([\w+#.-]*)\n([\s\S]*?)```/g;
7214
- let m;
7215
- while ((m = fence.exec(text)) !== null) {
7216
- const lang = (m[1] ?? "").trim();
7217
- const content = (m[2] ?? "").replace(/\n$/, "");
7218
- if (!content.trim()) continue;
7219
- const isSvg = lang.toLowerCase() === "svg" || /^\s*<svg[\s>]/.test(content);
7220
- out.push({
7221
- kind: isSvg ? "svg" : "code",
7222
- lang: lang || (isSvg ? "svg" : ""),
7223
- content,
7224
- suggestedName: `artifact-${out.length + 1}.${isSvg ? "svg" : extFor(lang)}`
7225
- });
7226
- }
7227
- const svg = /<svg[\s>][\s\S]*?<\/svg>/gi;
7228
- while ((m = svg.exec(text)) !== null) {
7229
- const content = m[0];
7230
- if (out.some((a) => a.content.includes(content))) continue;
7231
- out.push({ kind: "svg", lang: "svg", content, suggestedName: `artifact-${out.length + 1}.svg` });
7232
- }
7233
- return out;
7234
- }
7235
- var current3 = [];
7236
- function setArtifacts(a) {
7237
- current3 = a;
7238
- }
7239
- function getArtifacts() {
7240
- return current3;
7241
- }
7242
-
7243
- // src/repl-ui/slash-artifacts.ts
7278
+ import { existsSync as existsSync17, writeFileSync as writeFileSync19 } from "fs";
7244
7279
  init_theme();
7245
7280
  function isArtifactSlash(cmd) {
7246
7281
  return /^\/(review|artifacts?|save)(\s|$)/i.test(cmd.trim());
@@ -7257,9 +7292,9 @@ function handleArtifactSlash(raw) {
7257
7292
  const art = arts[idx - 1];
7258
7293
  if (!art) return [dim(" no such artifact")];
7259
7294
  const dest = parts[2] || art.suggestedName;
7260
- if (existsSync16(dest)) return [dim(` \u2717 ${dest} already exists \u2014 give a different path: /save ${idx} <path>`)];
7295
+ if (existsSync17(dest)) return [dim(` \u2717 ${dest} already exists \u2014 give a different path: /save ${idx} <path>`)];
7261
7296
  try {
7262
- writeFileSync18(dest, art.content, "utf8");
7297
+ writeFileSync19(dest, art.content, "utf8");
7263
7298
  } catch (e) {
7264
7299
  return [dim(` \u2717 could not write ${dest}: ${e instanceof Error ? e.message : String(e)}`)];
7265
7300
  }
@@ -7317,8 +7352,8 @@ async function handleCompact(session, cmd) {
7317
7352
  }
7318
7353
 
7319
7354
  // src/context/init-agents.ts
7320
- import { existsSync as existsSync17, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync19 } from "fs";
7321
- import { join as join26, basename } from "path";
7355
+ import { existsSync as existsSync18, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync20 } from "fs";
7356
+ import { join as join27, basename } from "path";
7322
7357
  var CODE_EXT = {
7323
7358
  ts: "TypeScript",
7324
7359
  tsx: "TypeScript",
@@ -7351,8 +7386,8 @@ function readJson(p) {
7351
7386
  }
7352
7387
  function detectProject(cwd) {
7353
7388
  const facts = { name: basename(cwd) || "project", languages: [], commands: [], topDirs: [] };
7354
- const pkgPath = join26(cwd, "package.json");
7355
- if (existsSync17(pkgPath)) {
7389
+ const pkgPath = join27(cwd, "package.json");
7390
+ if (existsSync18(pkgPath)) {
7356
7391
  const pkg = readJson(pkgPath);
7357
7392
  if (typeof pkg.name === "string" && pkg.name) facts.name = pkg.name;
7358
7393
  if (typeof pkg.description === "string" && pkg.description) facts.description = pkg.description;
@@ -7360,11 +7395,11 @@ function detectProject(cwd) {
7360
7395
  for (const key of ["dev", "build", "test", "lint", "start"]) {
7361
7396
  if (scripts[key]) facts.commands.push({ label: key, cmd: `npm run ${key}` });
7362
7397
  }
7363
- } else if (existsSync17(join26(cwd, "pyproject.toml")) || existsSync17(join26(cwd, "requirements.txt"))) {
7398
+ } else if (existsSync18(join27(cwd, "pyproject.toml")) || existsSync18(join27(cwd, "requirements.txt"))) {
7364
7399
  if (!facts.description) facts.description = "Python project";
7365
- } else if (existsSync17(join26(cwd, "Cargo.toml"))) {
7400
+ } else if (existsSync18(join27(cwd, "Cargo.toml"))) {
7366
7401
  facts.commands.push({ label: "build", cmd: "cargo build" }, { label: "test", cmd: "cargo test" });
7367
- } else if (existsSync17(join26(cwd, "go.mod"))) {
7402
+ } else if (existsSync18(join27(cwd, "go.mod"))) {
7368
7403
  facts.commands.push({ label: "build", cmd: "go build ./..." }, { label: "test", cmd: "go test ./..." });
7369
7404
  }
7370
7405
  const langCount = /* @__PURE__ */ new Map();
@@ -7379,7 +7414,7 @@ function detectProject(cwd) {
7379
7414
  } catch {
7380
7415
  }
7381
7416
  for (const e of entries) {
7382
- const full = join26(cwd, e);
7417
+ const full = join27(cwd, e);
7383
7418
  let isDir = false;
7384
7419
  try {
7385
7420
  isDir = statSync3(full).isDirectory();
@@ -7392,7 +7427,7 @@ function detectProject(cwd) {
7392
7427
  try {
7393
7428
  for (const f of readdirSync3(full)) {
7394
7429
  try {
7395
- if (statSync3(join26(full, f)).isFile()) tallyExt(f);
7430
+ if (statSync3(join27(full, f)).isFile()) tallyExt(f);
7396
7431
  } catch {
7397
7432
  }
7398
7433
  }
@@ -7426,10 +7461,10 @@ function generateAgentsMd(cwd) {
7426
7461
  return lines.join("\n");
7427
7462
  }
7428
7463
  function writeAgentsMd(cwd = process.cwd(), force = false) {
7429
- const path = join26(cwd, "AGENTS.md");
7464
+ const path = join27(cwd, "AGENTS.md");
7430
7465
  const facts = detectProject(cwd);
7431
- if (existsSync17(path) && !force) return { path, created: false, facts };
7432
- writeFileSync19(path, generateAgentsMd(cwd), "utf8");
7466
+ if (existsSync18(path) && !force) return { path, created: false, facts };
7467
+ writeFileSync20(path, generateAgentsMd(cwd), "utf8");
7433
7468
  return { path, created: true, facts };
7434
7469
  }
7435
7470
 
@@ -7599,6 +7634,7 @@ async function runTuiRepl(session) {
7599
7634
  ` ${accent("/review")} artifacts ${accent("/save")} <n> [path] ${accent("/init")} AGENTS.md ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
7600
7635
  ` ${accent("/sessions")} list saved ${accent("/undo")} rewind a turn ${dim("resume:")} ${accent("oriro -c")} / ${accent("oriro --resume <id>")}`,
7601
7636
  ` ${accent("/plan")} <task> plan read-only ${accent("/approve")} execute it ${accent("/reject")} discard ${accent("/agents")} parallel worktree fan-out`,
7637
+ ` ${accent("/imagine")} <scene> draw an SVG artwork (keyless, auto-saved)`,
7602
7638
  ` ${dim("Shift+Tab")} posture ${dim("Alt+Shift+T")} thinking ${accent("/help")} ${accent("/exit")}`
7603
7639
  ].join("\n");
7604
7640
  chat.addChild(new Text(help, 0, 0));
@@ -7726,6 +7762,18 @@ async function runTuiRepl(session) {
7726
7762
  turnText = plan.task;
7727
7763
  }
7728
7764
  }
7765
+ let imagineTurn = false;
7766
+ if (isImagineSlash(slash)) {
7767
+ const task = imagineTask(text);
7768
+ if (!task) {
7769
+ chat.addChild(new Text(dim(" usage: /imagine <what to draw> \u2014 ORIRO draws a self-contained SVG and saves it here"), 0, 0));
7770
+ editor.setText("");
7771
+ tui.requestRender();
7772
+ return;
7773
+ }
7774
+ imagineTurn = true;
7775
+ turnText = task;
7776
+ }
7729
7777
  if (slash === "/voice") {
7730
7778
  editor.setText("");
7731
7779
  const status = new Text(dim(" \u{1F399} listening\u2026 (needs ffmpeg + the transformers voice peer)"), 0, 0);
@@ -7765,6 +7813,9 @@ async function runTuiRepl(session) {
7765
7813
  bumpTurns();
7766
7814
  void (async () => {
7767
7815
  let english = internalPrompt ?? await translateIncoming(turnText);
7816
+ if (imagineTurn) english = `${IMAGINE_PRIMER}
7817
+
7818
+ ${english}`;
7768
7819
  if (getMode() === "plan") english = `${PLAN_PRIMER}
7769
7820
 
7770
7821
  ${english}`;
@@ -7810,6 +7861,7 @@ ${english}`;
7810
7861
  if (getMode() === "plan" && notePlanOutput(finalText)) {
7811
7862
  chat.addChild(new Text(dim(" \u25A2 plan ready \u2014 ") + accent("/approve") + dim(" to execute \xB7 ") + accent("/reject") + dim(" to discard"), 0, 0));
7812
7863
  }
7864
+ if (imagineTurn) chat.addChild(new Text(imagineResultLines(finalText).join("\n"), 0, 0));
7813
7865
  tui.requestRender();
7814
7866
  busy = false;
7815
7867
  })();
@@ -7823,8 +7875,8 @@ ${english}`;
7823
7875
  // src/voice/mic.ts
7824
7876
  import { spawn as spawn3 } from "child_process";
7825
7877
  import { tmpdir as tmpdir3 } from "os";
7826
- import { join as join28 } from "path";
7827
- import { existsSync as existsSync18, statSync as statSync4 } from "fs";
7878
+ import { join as join29 } from "path";
7879
+ import { existsSync as existsSync19, statSync as statSync4 } from "fs";
7828
7880
  function recorders(outFile, seconds) {
7829
7881
  const dur = String(seconds);
7830
7882
  if (process.platform === "darwin") {
@@ -7845,12 +7897,12 @@ function recorders(outFile, seconds) {
7845
7897
  ];
7846
7898
  }
7847
7899
  async function recordMic(seconds = 6) {
7848
- const outFile = join28(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
7900
+ const outFile = join29(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
7849
7901
  for (const r of recorders(outFile, seconds)) {
7850
7902
  const okFile = await new Promise((resolve3) => {
7851
7903
  const child = spawn3(r.cmd, r.args, { stdio: "ignore" });
7852
7904
  child.on("error", () => resolve3(false));
7853
- child.on("close", (code) => resolve3(code === 0 && existsSync18(outFile) && statSync4(outFile).size > 44));
7905
+ child.on("close", (code) => resolve3(code === 0 && existsSync19(outFile) && statSync4(outFile).size > 44));
7854
7906
  });
7855
7907
  if (okFile) return outFile;
7856
7908
  }
@@ -7922,6 +7974,7 @@ function replHelp() {
7922
7974
  ${dim("Continuity")} ${accent("/sessions")} list saved sessions ${dim("resume:")} ${accent("oriro -c")} ${dim("or")} ${accent("oriro --resume <id>")}
7923
7975
  ${dim("Plan loop")} ${accent("/plan")} <task> read-only plan ${accent("/approve")} execute it ${accent("/reject")} discard
7924
7976
  ${dim("Fan-out")} ${accent("/agents")} <A> | <B> parallel sub-agents in isolated git worktrees
7977
+ ${dim("Images")} ${accent("/imagine")} <scene> draw an SVG artwork (keyless, auto-saved to cwd)
7925
7978
  ${dim("Artifacts")} ${accent("/review")} code/SVG from the last reply ${accent("/save")} <n> [path] write one
7926
7979
  ${dim("Project")} ${accent("/init")} write a starter AGENTS.md ORIRO reads each session
7927
7980
  ${dim("Capabilities")} ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")} speak a turn
@@ -8054,8 +8107,22 @@ async function runReadlineRepl(session) {
8054
8107
  turnText = plan.task;
8055
8108
  }
8056
8109
  }
8110
+ let imagineTurn = false;
8111
+ if (isImagineSlash(slash)) {
8112
+ const task = imagineTask(line);
8113
+ if (!task) {
8114
+ stdout7.write(` ${dim("usage: /imagine <what to draw> \u2014 ORIRO draws a self-contained SVG and saves it here")}
8115
+ `);
8116
+ continue;
8117
+ }
8118
+ imagineTurn = true;
8119
+ turnText = task;
8120
+ }
8057
8121
  bumpTurns();
8058
8122
  let english = internalPrompt ?? await translateIncoming(turnText);
8123
+ if (imagineTurn) english = `${IMAGINE_PRIMER}
8124
+
8125
+ ${english}`;
8059
8126
  if (getMode() === "plan") english = `${PLAN_PRIMER}
8060
8127
 
8061
8128
  ${english}`;
@@ -8086,6 +8153,7 @@ ${hint}
8086
8153
  stdout7.write(` ${dim("\u25A2 plan ready \u2014")} ${accent("/approve")} ${dim("to execute \xB7")} ${accent("/reject")} ${dim("to discard")}
8087
8154
  `);
8088
8155
  }
8156
+ if (imagineTurn) stdout7.write(imagineResultLines(shown).join("\n") + "\n");
8089
8157
  }
8090
8158
  } finally {
8091
8159
  process.removeListener("SIGINT", onSigint);
@@ -8194,8 +8262,8 @@ import jmespath from "jmespath";
8194
8262
 
8195
8263
  // src/config/store.ts
8196
8264
  init_paths();
8197
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
8198
- import { join as join29 } from "path";
8265
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync21, mkdirSync as mkdirSync16 } from "fs";
8266
+ import { join as join30 } from "path";
8199
8267
  var KEYS = {
8200
8268
  output: {
8201
8269
  desc: "default output format for list commands: text | json | csv",
@@ -8217,7 +8285,7 @@ function validateConfig(key, value) {
8217
8285
  return KEYS[key].validate?.(value) ?? null;
8218
8286
  }
8219
8287
  function file4() {
8220
- return join29(oriroDir(), "config.json");
8288
+ return join30(oriroDir(), "config.json");
8221
8289
  }
8222
8290
  var cache = null;
8223
8291
  function readAll() {
@@ -8239,7 +8307,7 @@ function configAll() {
8239
8307
  function configSet(key, value) {
8240
8308
  const all = { ...readAll(), [key]: value };
8241
8309
  mkdirSync16(oriroDir(), { recursive: true });
8242
- writeFileSync20(file4(), JSON.stringify(all, null, 2), "utf8");
8310
+ writeFileSync21(file4(), JSON.stringify(all, null, 2), "utf8");
8243
8311
  cache = all;
8244
8312
  }
8245
8313
  function configUnset(key) {
@@ -8247,7 +8315,7 @@ function configUnset(key) {
8247
8315
  if (!(key in all)) return false;
8248
8316
  const rest = { ...all };
8249
8317
  delete rest[key];
8250
- writeFileSync20(file4(), JSON.stringify(rest, null, 2), "utf8");
8318
+ writeFileSync21(file4(), JSON.stringify(rest, null, 2), "utf8");
8251
8319
  cache = rest;
8252
8320
  return true;
8253
8321
  }
@@ -8496,7 +8564,7 @@ init_wal();
8496
8564
  init_retrieval();
8497
8565
 
8498
8566
  // src/scribe/transcript.ts
8499
- import { existsSync as existsSync20, readFileSync as readFileSync23 } from "fs";
8567
+ import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
8500
8568
  function parseHookStdin(raw) {
8501
8569
  try {
8502
8570
  const j = JSON.parse(raw);
@@ -8529,7 +8597,7 @@ function isHumanUser(e) {
8529
8597
  }
8530
8598
  var FILE_KEYS = ["file_path", "path", "notebook_path", "filePath"];
8531
8599
  function lastTurnFromTranscript(path) {
8532
- if (!existsSync20(path)) return null;
8600
+ if (!existsSync21(path)) return null;
8533
8601
  const raw = readFileSync23(path, "utf8");
8534
8602
  const entries = [];
8535
8603
  for (const line of raw.split("\n")) {
@@ -8908,10 +8976,10 @@ function registerConnectorsCommand(program2) {
8908
8976
 
8909
8977
  // src/channels/config.ts
8910
8978
  init_paths();
8911
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
8912
- import { join as join30 } from "path";
8979
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync22 } from "fs";
8980
+ import { join as join31 } from "path";
8913
8981
  function file5() {
8914
- return join30(oriroDir(), "channels.json");
8982
+ return join31(oriroDir(), "channels.json");
8915
8983
  }
8916
8984
  function readChannels() {
8917
8985
  try {
@@ -8924,10 +8992,10 @@ function readChannels() {
8924
8992
  function saveChannel(cfg) {
8925
8993
  const all = readChannels().filter((c) => c.kind !== cfg.kind);
8926
8994
  all.push(cfg);
8927
- writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
8995
+ writeFileSync22(join31(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
8928
8996
  }
8929
8997
  function removeChannel(kind) {
8930
- writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
8998
+ writeFileSync22(join31(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
8931
8999
  }
8932
9000
 
8933
9001
  // src/channels/telegram.ts
@@ -9048,9 +9116,9 @@ async function startDiscord(token) {
9048
9116
 
9049
9117
  // src/channels/whatsapp.ts
9050
9118
  init_paths();
9051
- import { join as join31 } from "path";
9119
+ import { join as join32 } from "path";
9052
9120
  function whatsappAuthDir() {
9053
- return join31(oriroDir(), "whatsapp-auth");
9121
+ return join32(oriroDir(), "whatsapp-auth");
9054
9122
  }
9055
9123
  async function startWhatsApp() {
9056
9124
  let baileys;
@@ -9170,8 +9238,8 @@ function registerChannelsCommand(program2) {
9170
9238
 
9171
9239
  // src/commands/skills.ts
9172
9240
  init_loader();
9173
- import { existsSync as existsSync21, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
9174
- import { resolve as resolve2, join as join32, basename as basename3, dirname as dirname4 } from "path";
9241
+ import { existsSync as existsSync22, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
9242
+ import { resolve as resolve2, join as join33, basename as basename3, dirname as dirname4 } from "path";
9175
9243
  init_theme();
9176
9244
  function registerSkillsCommand(program2) {
9177
9245
  const skills = program2.command("skills").description("the ORIRO skill library \u2014 bundled + your own");
@@ -9204,28 +9272,28 @@ function registerSkillsCommand(program2) {
9204
9272
  });
9205
9273
  skills.command("add <path>").description("add your own skill \u2014 a folder containing SKILL.md, or a SKILL.md file").action((p) => {
9206
9274
  const src = resolve2(p);
9207
- if (!existsSync21(src)) die(`not found: ${src}`);
9275
+ if (!existsSync22(src)) die(`not found: ${src}`);
9208
9276
  const dest = userSkillsDir();
9209
9277
  mkdirSync17(dest, { recursive: true });
9210
9278
  const st = statSync5(src);
9211
9279
  if (st.isDirectory()) {
9212
- if (!existsSync21(join32(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
9280
+ if (!existsSync22(join33(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
9213
9281
  const name = basename3(src);
9214
- cpSync(src, join32(dest, name), { recursive: true });
9215
- ok(`added skill ${accent(name)} \u2192 ${join32(dest, name)}`);
9282
+ cpSync(src, join33(dest, name), { recursive: true });
9283
+ ok(`added skill ${accent(name)} \u2192 ${join33(dest, name)}`);
9216
9284
  } else if (basename3(src).toLowerCase() === "skill.md") {
9217
9285
  const name = basename3(dirname4(src)) || "custom-skill";
9218
- mkdirSync17(join32(dest, name), { recursive: true });
9219
- cpSync(src, join32(dest, name, "SKILL.md"));
9220
- ok(`added skill ${accent(name)} \u2192 ${join32(dest, name)}`);
9286
+ mkdirSync17(join33(dest, name), { recursive: true });
9287
+ cpSync(src, join33(dest, name, "SKILL.md"));
9288
+ ok(`added skill ${accent(name)} \u2192 ${join33(dest, name)}`);
9221
9289
  } else {
9222
9290
  die("expected a folder containing SKILL.md, or a SKILL.md file");
9223
9291
  }
9224
9292
  info("It loads on next launch \u2014 and is available in chat via /skill.");
9225
9293
  });
9226
9294
  skills.command("remove <name>").description("remove a skill you added").option("-f, --force", "skip the confirmation prompt").action(async (name, opts) => {
9227
- const target = join32(userSkillsDir(), name);
9228
- if (!existsSync21(target)) {
9295
+ const target = join33(userSkillsDir(), name);
9296
+ if (!existsSync22(target)) {
9229
9297
  info(`'${name}' is not a user-added skill \u2014 nothing to remove`);
9230
9298
  return;
9231
9299
  }
@@ -9829,7 +9897,7 @@ function registerConfigCommand(program2) {
9829
9897
 
9830
9898
  // src/commands/setup.ts
9831
9899
  import { rmSync as rmSync5 } from "fs";
9832
- import { join as join33 } from "path";
9900
+ import { join as join34 } from "path";
9833
9901
  import { stdin as stdin12, stdout as stdout11 } from "process";
9834
9902
  init_paths();
9835
9903
  init_theme();
@@ -9839,14 +9907,14 @@ var MARKERS = [
9839
9907
  "skills-onboarded.json",
9840
9908
  "connectors-onboarded.json",
9841
9909
  "models-onboarded.json",
9842
- join33("routers", "onboarded.json")
9910
+ join34("routers", "onboarded.json")
9843
9911
  ];
9844
9912
  function registerSetupCommand(program2) {
9845
9913
  program2.command("setup").description("run the guided setup wizard (language \xB7 routers \xB7 connectors \xB7 skills \xB7 avatar)").option("--reset", "clear your settled choices and re-ask every step").action(async (opts) => {
9846
9914
  if (opts.reset) {
9847
9915
  for (const m of MARKERS) {
9848
9916
  try {
9849
- rmSync5(join33(oriroDir(), m), { force: true });
9917
+ rmSync5(join34(oriroDir(), m), { force: true });
9850
9918
  } catch {
9851
9919
  }
9852
9920
  }
@@ -9862,9 +9930,942 @@ function registerSetupCommand(program2) {
9862
9930
  });
9863
9931
  }
9864
9932
 
9933
+ // src/config/session.ts
9934
+ init_paths();
9935
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync23, rmSync as rmSync6, chmodSync } from "fs";
9936
+ import { join as join35 } from "path";
9937
+ function sessionPath() {
9938
+ return join35(ensureOriroDir(), "session.json");
9939
+ }
9940
+ function saveSession(setupToken) {
9941
+ const p = sessionPath();
9942
+ writeFileSync23(p, JSON.stringify({ setup_token: setupToken, saved_at: Date.now() }), "utf8");
9943
+ try {
9944
+ chmodSync(p, 384);
9945
+ } catch {
9946
+ }
9947
+ }
9948
+ function readSetupToken() {
9949
+ if (process.env.ORIRO_SETUP_TOKEN) return process.env.ORIRO_SETUP_TOKEN;
9950
+ try {
9951
+ const s = JSON.parse(readFileSync27(sessionPath(), "utf8"));
9952
+ return s.setup_token || void 0;
9953
+ } catch {
9954
+ return void 0;
9955
+ }
9956
+ }
9957
+ function readLicense() {
9958
+ return process.env.ORIRO_LICENSE_KEY ?? "oriro-local-v1";
9959
+ }
9960
+ function isLoggedIn() {
9961
+ return !!readSetupToken();
9962
+ }
9963
+ function clearSession() {
9964
+ try {
9965
+ rmSync6(sessionPath(), { force: true });
9966
+ } catch {
9967
+ }
9968
+ }
9969
+
9970
+ // src/commands/login.ts
9971
+ init_theme();
9972
+ function registerLoginCommand(program2) {
9973
+ program2.command("login [code]").description("authorize model downloads on this machine (paste the code from oriro.app)").option("--status", "show whether this machine is authorized").action((code, opts) => {
9974
+ if (opts.status) {
9975
+ info(isLoggedIn() ? "this machine is authorized for downloads" : `not authorized \u2014 run ${accent("oriro login <code>")}`);
9976
+ return;
9977
+ }
9978
+ if (!code) {
9979
+ die(`paste your setup code: ${accent("oriro login <code>")} \u2014 get it on oriro.app \u2192 Download \u2192 \u201CConnect this computer\u201D.`);
9980
+ }
9981
+ if (!/^[a-f0-9]{32,64}$/i.test(code)) die("that doesn't look like a valid setup code (expected a 32\u201364 char hex code).");
9982
+ saveSession(code);
9983
+ heading("Authorized");
9984
+ ok(`this machine can now download models \u2014 run ${accent("oriro models pull")}`);
9985
+ info(dim("stored locally in ~/.oriro/session.json (0600); run `oriro logout` to remove."));
9986
+ });
9987
+ program2.command("logout").description("remove this machine's download authorization").action(() => {
9988
+ clearSession();
9989
+ ok("logged out \u2014 download authorization removed from this machine");
9990
+ });
9991
+ }
9992
+
9993
+ // src/commands/models.ts
9994
+ init_paths();
9995
+ import { existsSync as existsSync27, statSync as statSync8 } from "fs";
9996
+ import { join as join40 } from "path";
9997
+
9998
+ // src/weights/container-stream.ts
9999
+ import { createCipheriv, createDecipheriv, randomBytes, createHash } from "crypto";
10000
+ import { open, stat } from "fs/promises";
10001
+ var MAGIC = Buffer.from("ORX1", "ascii");
10002
+ var VERSION = 1;
10003
+ var IO_CHUNK = 4 * 1024 * 1024;
10004
+ function u32(n) {
10005
+ const b = Buffer.alloc(4);
10006
+ b.writeUInt32BE(n, 0);
10007
+ return b;
10008
+ }
10009
+ async function packOrxToFile(srcGgufPath, destOrxPath, opts) {
10010
+ if (opts.kek.length !== 32) throw new Error("KEK must be 32 bytes (AES-256)");
10011
+ const { size } = await stat(srcGgufPath);
10012
+ const hash = createHash("sha256");
10013
+ {
10014
+ const fh = await open(srcGgufPath, "r");
10015
+ try {
10016
+ const b = Buffer.alloc(IO_CHUNK);
10017
+ let pos = 0;
10018
+ for (; ; ) {
10019
+ const { bytesRead } = await fh.read(b, 0, IO_CHUNK, pos);
10020
+ if (!bytesRead) break;
10021
+ hash.update(b.subarray(0, bytesRead));
10022
+ pos += bytesRead;
10023
+ }
10024
+ } finally {
10025
+ await fh.close();
10026
+ }
10027
+ }
10028
+ const sha256 = hash.digest("hex");
10029
+ const cek = randomBytes(32);
10030
+ const payloadNonce = randomBytes(12);
10031
+ const aad = Buffer.from(
10032
+ JSON.stringify({ v: VERSION, modelId: opts.meta.modelId, version: opts.meta.version, watermark: opts.watermark, sha256 }),
10033
+ "utf8"
10034
+ );
10035
+ const wrapNonce = randomBytes(12);
10036
+ const wc = createCipheriv("aes-256-gcm", opts.kek, wrapNonce);
10037
+ wc.setAAD(aad);
10038
+ const wrappedCek = Buffer.concat([wc.update(cek), wc.final()]);
10039
+ const wrapTag = wc.getAuthTag();
10040
+ const header2 = {
10041
+ ...opts.meta,
10042
+ cipher: "AES-256-GCM",
10043
+ wrapNonce: wrapNonce.toString("base64"),
10044
+ wrappedCek: wrappedCek.toString("base64"),
10045
+ wrapTag: wrapTag.toString("base64"),
10046
+ payloadNonce: payloadNonce.toString("base64"),
10047
+ watermark: opts.watermark,
10048
+ sha256,
10049
+ payloadLen: size
10050
+ // GCM ciphertext length == plaintext length
10051
+ };
10052
+ const headerJson = Buffer.from(JSON.stringify(header2), "utf8");
10053
+ const out = await open(destOrxPath, "w");
10054
+ try {
10055
+ await out.write(Buffer.concat([MAGIC, Buffer.from([VERSION]), u32(headerJson.length), headerJson]));
10056
+ const pc = createCipheriv("aes-256-gcm", cek, payloadNonce);
10057
+ pc.setAAD(aad);
10058
+ const fh = await open(srcGgufPath, "r");
10059
+ try {
10060
+ const b = Buffer.alloc(IO_CHUNK);
10061
+ let pos = 0;
10062
+ for (; ; ) {
10063
+ const { bytesRead } = await fh.read(b, 0, IO_CHUNK, pos);
10064
+ if (!bytesRead) break;
10065
+ const enc = pc.update(b.subarray(0, bytesRead));
10066
+ if (enc.length) await out.write(enc);
10067
+ pos += bytesRead;
10068
+ }
10069
+ } finally {
10070
+ await fh.close();
10071
+ }
10072
+ const fin = pc.final();
10073
+ await out.write(Buffer.concat([fin, pc.getAuthTag()]));
10074
+ } finally {
10075
+ await out.close();
10076
+ }
10077
+ return header2;
10078
+ }
10079
+ async function unpackOrxToFile(srcOrxPath, destGgufPath, kek) {
10080
+ if (kek.length !== 32) throw new Error("KEK must be 32 bytes (AES-256)");
10081
+ const src = await open(srcOrxPath, "r");
10082
+ try {
10083
+ const pre = Buffer.alloc(9);
10084
+ const { bytesRead: pr } = await src.read(pre, 0, 9, 0);
10085
+ if (pr < 9 || !pre.subarray(0, 4).equals(MAGIC)) throw new Error("not an ORX container");
10086
+ if (pre[4] !== VERSION) throw new Error(`unsupported ORX version ${pre[4]}`);
10087
+ const headerLen = pre.readUInt32BE(5);
10088
+ const hbuf = Buffer.alloc(headerLen);
10089
+ await src.read(hbuf, 0, headerLen, 9);
10090
+ const header2 = JSON.parse(hbuf.toString("utf8"));
10091
+ const aad = Buffer.from(
10092
+ JSON.stringify({ v: VERSION, modelId: header2.modelId, version: header2.version, watermark: header2.watermark, sha256: header2.sha256 }),
10093
+ "utf8"
10094
+ );
10095
+ const wc = createDecipheriv("aes-256-gcm", kek, Buffer.from(header2.wrapNonce, "base64"));
10096
+ wc.setAAD(aad);
10097
+ wc.setAuthTag(Buffer.from(header2.wrapTag, "base64"));
10098
+ let cek;
10099
+ try {
10100
+ cek = Buffer.concat([wc.update(Buffer.from(header2.wrappedCek, "base64")), wc.final()]);
10101
+ } catch {
10102
+ throw new Error("ORX unlock failed \u2014 wrong device/license, or the container was tampered with");
10103
+ }
10104
+ const cipherStart = 9 + headerLen;
10105
+ const cipherLen = header2.payloadLen;
10106
+ const tag = Buffer.alloc(16);
10107
+ await src.read(tag, 0, 16, cipherStart + cipherLen);
10108
+ const pc = createDecipheriv("aes-256-gcm", cek, Buffer.from(header2.payloadNonce, "base64"));
10109
+ pc.setAAD(aad);
10110
+ pc.setAuthTag(tag);
10111
+ const hash = createHash("sha256");
10112
+ const out = await open(destGgufPath, "w");
10113
+ try {
10114
+ const b = Buffer.alloc(IO_CHUNK);
10115
+ let pos = cipherStart;
10116
+ let remaining = cipherLen;
10117
+ while (remaining > 0) {
10118
+ const { bytesRead } = await src.read(b, 0, Math.min(IO_CHUNK, remaining), pos);
10119
+ if (!bytesRead) break;
10120
+ const dec = pc.update(b.subarray(0, bytesRead));
10121
+ if (dec.length) {
10122
+ await out.write(dec);
10123
+ hash.update(dec);
10124
+ }
10125
+ pos += bytesRead;
10126
+ remaining -= bytesRead;
10127
+ }
10128
+ let fin;
10129
+ try {
10130
+ fin = pc.final();
10131
+ } catch {
10132
+ throw new Error("ORX payload decryption failed \u2014 container corrupt or tampered");
10133
+ }
10134
+ if (fin.length) {
10135
+ await out.write(fin);
10136
+ hash.update(fin);
10137
+ }
10138
+ } finally {
10139
+ await out.close();
10140
+ }
10141
+ if (hash.digest("hex") !== header2.sha256) {
10142
+ throw new Error("ORX integrity check failed \u2014 decrypted weights do not match the manifest");
10143
+ }
10144
+ return header2;
10145
+ } finally {
10146
+ await src.close();
10147
+ }
10148
+ }
10149
+
10150
+ // src/weights/binding.ts
10151
+ init_paths();
10152
+ import { randomBytes as randomBytes2, scryptSync, createHash as createHash2 } from "crypto";
10153
+ import { existsSync as existsSync23, mkdirSync as mkdirSync18, readFileSync as readFileSync28, writeFileSync as writeFileSync24, chmodSync as chmodSync2 } from "fs";
10154
+ import { hostname, userInfo, platform as platform2, arch } from "os";
10155
+ import { join as join36 } from "path";
10156
+ var SCRYPT_N = 1 << 15;
10157
+ var SCRYPT_R = 8;
10158
+ var SCRYPT_P = 1;
10159
+ function weightsDir() {
10160
+ const d = join36(oriroDir(), "weights");
10161
+ mkdirSync18(d, { recursive: true });
10162
+ return d;
10163
+ }
10164
+ function installSecret() {
10165
+ const p = join36(weightsDir(), ".install");
10166
+ if (existsSync23(p)) {
10167
+ const b = Buffer.from(readFileSync28(p, "utf8").trim(), "hex");
10168
+ if (b.length === 32) return b;
10169
+ }
10170
+ const secret = randomBytes2(32);
10171
+ writeFileSync24(p, secret.toString("hex"), "utf8");
10172
+ try {
10173
+ chmodSync2(p, 384);
10174
+ } catch {
10175
+ }
10176
+ return secret;
10177
+ }
10178
+ function deviceFingerprint() {
10179
+ const u = userInfo();
10180
+ return createHash2("sha256").update([hostname(), u.username, platform2(), arch(), String(u.uid)].join("|")).digest("hex");
10181
+ }
10182
+ function deriveKek(licenseKey) {
10183
+ const salt = createHash2("sha256").update("oriro-orx-v1|" + deviceFingerprint()).digest();
10184
+ const material = Buffer.concat([installSecret(), Buffer.from(licenseKey, "utf8")]);
10185
+ return scryptSync(material, salt, 32, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: 128 * 1024 * 1024 });
10186
+ }
10187
+
10188
+ // src/weights/pull.ts
10189
+ init_paths();
10190
+ import { createHash as createHash3 } from "crypto";
10191
+ import { open as open2, stat as stat2, mkdir, unlink } from "fs/promises";
10192
+ import { existsSync as existsSync24 } from "fs";
10193
+ import { join as join37 } from "path";
10194
+ function nextRange(offset, total, chunk) {
10195
+ if (offset >= total) return null;
10196
+ return { start: offset, end: Math.min(offset + chunk, total) - 1 };
10197
+ }
10198
+ async function probeSize(url, signal) {
10199
+ const r = await fetch(url, { headers: { Range: "bytes=0-0" }, signal });
10200
+ const cr = r.headers.get("content-range");
10201
+ await r.arrayBuffer().catch(() => void 0);
10202
+ if (!r.ok && r.status !== 206) throw new Error(`size probe HTTP ${r.status}`);
10203
+ if (!cr) throw new Error("server did not return a byte range \u2014 cannot size the model");
10204
+ const total = Number(cr.split("/")[1]);
10205
+ if (!Number.isFinite(total) || total <= 1) throw new Error("could not determine model size");
10206
+ return total;
10207
+ }
10208
+ async function sha256File(path) {
10209
+ const hash = createHash3("sha256");
10210
+ const fh = await open2(path, "r");
10211
+ try {
10212
+ const b = Buffer.alloc(4 * 1024 * 1024);
10213
+ let pos = 0;
10214
+ for (; ; ) {
10215
+ const { bytesRead } = await fh.read(b, 0, b.length, pos);
10216
+ if (!bytesRead) break;
10217
+ hash.update(b.subarray(0, bytesRead));
10218
+ pos += bytesRead;
10219
+ }
10220
+ } finally {
10221
+ await fh.close();
10222
+ }
10223
+ return hash.digest("hex");
10224
+ }
10225
+ async function fetchRange(urlRef, range, spec, retries) {
10226
+ let attempt = 0;
10227
+ let refreshes = 0;
10228
+ for (; ; ) {
10229
+ if (spec.signal?.aborted) throw new DOMException("Aborted", "AbortError");
10230
+ try {
10231
+ const r = await fetch(urlRef.url, { headers: { Range: `bytes=${range.start}-${range.end}` }, signal: spec.signal });
10232
+ if (r.status === 401 && spec.refresh && refreshes < 3) {
10233
+ refreshes++;
10234
+ urlRef.url = await spec.refresh();
10235
+ continue;
10236
+ }
10237
+ if (r.status === 200) throw new Error("server ignored Range (200) \u2014 refusing to write a full body at an offset");
10238
+ if (r.status !== 206) throw new Error(`chunk HTTP ${r.status}`);
10239
+ const ab = await r.arrayBuffer();
10240
+ const want = range.end - range.start + 1;
10241
+ if (ab.byteLength !== want) throw new Error(`short chunk (${ab.byteLength}/${want})`);
10242
+ return Buffer.from(ab);
10243
+ } catch (e) {
10244
+ attempt++;
10245
+ if (spec.signal?.aborted || attempt >= retries) throw e;
10246
+ await new Promise((res) => setTimeout(res, Math.min(500 * 2 ** (attempt - 1), 4e3)));
10247
+ }
10248
+ }
10249
+ }
10250
+ async function pullModelToOrx(spec) {
10251
+ const stageDir = join37(oriroDir(), "weights", "staging");
10252
+ await mkdir(stageDir, { recursive: true });
10253
+ const part = join37(stageDir, `${spec.modelId}.gguf.part`);
10254
+ const chunk = spec.chunkBytes ?? 16 * 1024 * 1024;
10255
+ const retries = spec.retries ?? 5;
10256
+ const urlRef = { url: spec.url };
10257
+ let offset = existsSync24(part) ? (await stat2(part)).size : 0;
10258
+ if (offset > spec.sizeBytes) {
10259
+ await unlink(part);
10260
+ offset = 0;
10261
+ }
10262
+ spec.onProgress?.(offset, spec.sizeBytes);
10263
+ const fh = await open2(part, offset > 0 ? "r+" : "w");
10264
+ try {
10265
+ let cursor = offset;
10266
+ for (let range = nextRange(cursor, spec.sizeBytes, chunk); range; range = nextRange(cursor, spec.sizeBytes, chunk)) {
10267
+ const buf = await fetchRange(urlRef, range, spec, retries);
10268
+ await fh.write(buf, 0, buf.length, cursor);
10269
+ cursor += buf.length;
10270
+ spec.onProgress?.(cursor, spec.sizeBytes);
10271
+ }
10272
+ } finally {
10273
+ await fh.close();
10274
+ }
10275
+ const actual = await sha256File(part);
10276
+ if (spec.sha256 && actual !== spec.sha256) {
10277
+ await unlink(part);
10278
+ throw new Error(`${spec.modelId}: download failed the integrity check (sha256 mismatch) \u2014 please retry`);
10279
+ }
10280
+ const orxPath2 = join37(oriroDir(), "weights", `${spec.modelId}.orx`);
10281
+ await packOrxToFile(part, orxPath2, {
10282
+ kek: deriveKek(spec.licenseKey),
10283
+ watermark: spec.watermark ?? `orx:${spec.modelId}-v${spec.version ?? "2.4"}:${actual.slice(0, 12)}`,
10284
+ meta: { modelId: spec.modelId, version: spec.version ?? "2.4", createdTs: spec.createdTs }
10285
+ });
10286
+ await unlink(part);
10287
+ return { orxPath: orxPath2, modelId: spec.modelId, bytes: spec.sizeBytes };
10288
+ }
10289
+
10290
+ // src/weights/serve.ts
10291
+ import { createServer } from "http";
10292
+ import { randomBytes as randomBytes4 } from "crypto";
10293
+ import { existsSync as existsSync26 } from "fs";
10294
+ import { join as join39 } from "path";
10295
+
10296
+ // src/weights/secure-load.ts
10297
+ init_paths();
10298
+ import {
10299
+ chmodSync as chmodSync3,
10300
+ mkdirSync as mkdirSync19,
10301
+ writeFileSync as writeFileSync25,
10302
+ existsSync as existsSync25,
10303
+ statSync as statSync6,
10304
+ openSync as openSync5,
10305
+ writeSync as writeSync5,
10306
+ closeSync as closeSync5,
10307
+ unlinkSync
10308
+ } from "fs";
10309
+ import { join as join38 } from "path";
10310
+ import { randomBytes as randomBytes3 } from "crypto";
10311
+ function secureDir() {
10312
+ const d = join38(ensureOriroDir(), "weights", "run");
10313
+ mkdirSync19(d, { recursive: true });
10314
+ try {
10315
+ chmodSync3(d, 448);
10316
+ } catch {
10317
+ }
10318
+ return d;
10319
+ }
10320
+ function shredAndUnlink(path) {
10321
+ try {
10322
+ if (!existsSync25(path)) return;
10323
+ const size = statSync6(path).size;
10324
+ const fd = openSync5(path, "r+");
10325
+ try {
10326
+ const chunk = randomBytes3(1024 * 1024);
10327
+ for (let off = 0; off < size; off += chunk.length) {
10328
+ writeSync5(fd, chunk, 0, Math.min(chunk.length, size - off), off);
10329
+ }
10330
+ } finally {
10331
+ closeSync5(fd);
10332
+ }
10333
+ unlinkSync(path);
10334
+ } catch {
10335
+ }
10336
+ }
10337
+ var live = /* @__PURE__ */ new Set();
10338
+ var exitHooked = false;
10339
+ function hookExit() {
10340
+ if (exitHooked) return;
10341
+ exitHooked = true;
10342
+ const cleanup = () => {
10343
+ for (const p of live) shredAndUnlink(p);
10344
+ live.clear();
10345
+ };
10346
+ process.once("exit", cleanup);
10347
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
10348
+ process.once(sig, () => {
10349
+ cleanup();
10350
+ process.exit(130);
10351
+ });
10352
+ }
10353
+ }
10354
+ async function decryptToSecureFile(orxPath2, licenseKey) {
10355
+ hookExit();
10356
+ const path = join38(secureDir(), `run-${randomBytes3(6).toString("hex")}.gguf`);
10357
+ live.add(path);
10358
+ let header2;
10359
+ try {
10360
+ header2 = await unpackOrxToFile(orxPath2, path, deriveKek(licenseKey));
10361
+ } catch (e) {
10362
+ live.delete(path);
10363
+ shredAndUnlink(path);
10364
+ throw e;
10365
+ }
10366
+ try {
10367
+ chmodSync3(path, 384);
10368
+ } catch {
10369
+ }
10370
+ let disposed = false;
10371
+ return {
10372
+ path,
10373
+ modelId: header2.modelId,
10374
+ dispose() {
10375
+ if (disposed) return;
10376
+ disposed = true;
10377
+ live.delete(path);
10378
+ shredAndUnlink(path);
10379
+ }
10380
+ };
10381
+ }
10382
+
10383
+ // src/weights/template.ts
10384
+ var IM_START = "<|im_start|>";
10385
+ var IM_END = "<|im_end|>";
10386
+ var STOP_SEQUENCES = [IM_END, "<|endoftext|>"];
10387
+ var SYSTEM = {
10388
+ gauss: "You are Gauss, ORIRO's builder intelligence \u2014 you help people build websites, apps, and APIs and ship software. Lead with the deliverable immediately, no preamble. You are part of ORIRO (oriro.ai), the free AI platform. You never mention any other AI model \u2014 you are Gauss, and that is all.",
10389
+ avila: "You are Avila, ORIRO's orchestration intelligence. You help users plan, coordinate ORIRO's features, and manage multi-step workflows. You are part of ORIRO (oriro.ai), the free AI platform. You never mention any other AI model \u2014 you are Avila, and that is all."
10390
+ };
10391
+ function systemFor(modelId) {
10392
+ const s = SYSTEM[modelId.toLowerCase()];
10393
+ if (!s) throw new Error(`no system prompt for model "${modelId}"`);
10394
+ return s;
10395
+ }
10396
+ function buildPrompt(modelId, messages, systemOverride) {
10397
+ const sys = systemOverride ?? systemFor(modelId);
10398
+ let prompt = `${IM_START}system
10399
+ ${sys}${IM_END}
10400
+ `;
10401
+ for (const m of messages) {
10402
+ if (m.role === "system") continue;
10403
+ prompt += `${IM_START}${m.role}
10404
+ ${m.content}${IM_END}
10405
+ `;
10406
+ }
10407
+ prompt += `${IM_START}assistant
10408
+ `;
10409
+ return { prompt, stops: STOP_SEQUENCES };
10410
+ }
10411
+
10412
+ // src/weights/think-strip.ts
10413
+ var OPEN = "<think>";
10414
+ var CLOSE = "</think>";
10415
+ function thinkFilter() {
10416
+ let state = "before";
10417
+ let buf = "";
10418
+ return {
10419
+ push(chunk) {
10420
+ if (state === "after") return chunk;
10421
+ buf += chunk;
10422
+ if (state === "before") {
10423
+ const trimmed = buf.replace(/^\s+/, "");
10424
+ if (trimmed === "") return "";
10425
+ if (OPEN.startsWith(trimmed)) return "";
10426
+ if (trimmed.startsWith(OPEN)) {
10427
+ state = "inside";
10428
+ buf = trimmed.slice(OPEN.length);
10429
+ } else {
10430
+ state = "after";
10431
+ const out = buf;
10432
+ buf = "";
10433
+ return out;
10434
+ }
10435
+ }
10436
+ const idx = buf.indexOf(CLOSE);
10437
+ if (idx === -1) {
10438
+ const keep = Math.min(buf.length, CLOSE.length - 1);
10439
+ buf = buf.slice(buf.length - keep);
10440
+ return "";
10441
+ }
10442
+ state = "after";
10443
+ const after = buf.slice(idx + CLOSE.length).replace(/^\s+/, "");
10444
+ buf = "";
10445
+ return after;
10446
+ },
10447
+ end() {
10448
+ if (state === "before") {
10449
+ const out = buf;
10450
+ buf = "";
10451
+ return out;
10452
+ }
10453
+ buf = "";
10454
+ return "";
10455
+ }
10456
+ };
10457
+ }
10458
+
10459
+ // src/weights/engine.ts
10460
+ async function importLlama() {
10461
+ try {
10462
+ return await import("node-llama-cpp");
10463
+ } catch {
10464
+ throw new Error(
10465
+ "The on-device engine isn't available. The ORIRO app ships it prebuilt; from source, install the optional dependency with: npm i node-llama-cpp"
10466
+ );
10467
+ }
10468
+ }
10469
+ async function loadEngine(modelPath, modelId, nCtx, mmprojPath) {
10470
+ const mod = await importLlama();
10471
+ const getLlama = mod.getLlama;
10472
+ const LlamaCompletion = mod.LlamaCompletion;
10473
+ const llama = await getLlama();
10474
+ const model = await llama.loadModel(mmprojPath ? { modelPath, mmproj: mmprojPath } : { modelPath });
10475
+ const context = await model.createContext({ contextSize: nCtx });
10476
+ return {
10477
+ async chat(messages, onToken, opts) {
10478
+ const { prompt, stops } = buildPrompt(modelId, messages, opts?.systemOverride);
10479
+ const completion = new LlamaCompletion({ contextSequence: context.getSequence() });
10480
+ const filter = thinkFilter();
10481
+ let visible = "";
10482
+ await completion.generateCompletion(prompt, {
10483
+ onTextChunk: (t) => {
10484
+ const v = filter.push(t);
10485
+ if (v) {
10486
+ visible += v;
10487
+ onToken(v);
10488
+ }
10489
+ },
10490
+ customStopTriggers: stops,
10491
+ maxTokens: opts?.maxTokens,
10492
+ signal: opts?.signal
10493
+ });
10494
+ const tail = filter.end();
10495
+ if (tail) {
10496
+ visible += tail;
10497
+ onToken(tail);
10498
+ }
10499
+ completion.dispose?.();
10500
+ return visible;
10501
+ },
10502
+ async dispose() {
10503
+ await context.dispose();
10504
+ await model.dispose();
10505
+ }
10506
+ };
10507
+ }
10508
+
10509
+ // src/weights/preflight.ts
10510
+ import { freemem } from "os";
10511
+ var MiB = 1024 * 1024;
10512
+ var GiB = 1024 * MiB;
10513
+ var DEFAULT_CTX = 8192;
10514
+ var CTX_FLOOR = 2048;
10515
+ var HEADROOM = 1.25;
10516
+ function kvBytesPerToken(paramsB) {
10517
+ return Math.max(0.05, paramsB * 0.014) * MiB;
10518
+ }
10519
+ function planContext(fp, requestedCtx, freeBytes = freemem()) {
10520
+ const weights = fp.fileBytes;
10521
+ const budget = freeBytes / HEADROOM - weights;
10522
+ if (budget <= 0) {
10523
+ return {
10524
+ decision: "refuse",
10525
+ nCtx: 0,
10526
+ reason: `Not enough free memory: this model needs about ${(weights / GiB).toFixed(1)} GB resident and only ${(freeBytes / GiB).toFixed(1)} GB is free. Close some apps and try again.`
10527
+ };
10528
+ }
10529
+ const maxCtxByRam = Math.floor(budget / kvBytesPerToken(fp.paramsB));
10530
+ const want = requestedCtx > 0 ? requestedCtx : DEFAULT_CTX;
10531
+ const nCtx = Math.max(0, Math.min(want, maxCtxByRam));
10532
+ if (nCtx < CTX_FLOOR) {
10533
+ return {
10534
+ decision: "refuse",
10535
+ nCtx: 0,
10536
+ reason: `Not enough free memory for a usable context window (only ${nCtx.toLocaleString()} tokens fit). Close some apps and try again.`
10537
+ };
10538
+ }
10539
+ if (nCtx < want) {
10540
+ return {
10541
+ decision: "reduced",
10542
+ nCtx,
10543
+ reason: `Context set to ${nCtx.toLocaleString()} tokens to fit your free memory (${(freeBytes / GiB).toFixed(1)} GB). The model still works \u2014 just a shorter memory per chat.`
10544
+ };
10545
+ }
10546
+ return { decision: "ok", nCtx, reason: `Ready \u2014 ${nCtx.toLocaleString()} tokens of context.` };
10547
+ }
10548
+
10549
+ // src/weights/local-runtime.ts
10550
+ import { statSync as statSync7 } from "fs";
10551
+ async function startLocalModel(orxPath2, licenseKey, paramsB, opts = {}) {
10552
+ const secure = await decryptToSecureFile(orxPath2, licenseKey);
10553
+ let mmproj = null;
10554
+ try {
10555
+ if (opts.mmprojOrxPath) mmproj = await decryptToSecureFile(opts.mmprojOrxPath, licenseKey);
10556
+ const plan = planContext({ paramsB, fileBytes: statSync7(secure.path).size }, opts.requestedCtx ?? 0);
10557
+ if (plan.decision === "refuse") throw new Error(plan.reason);
10558
+ const engine = await loadEngine(secure.path, secure.modelId, plan.nCtx, mmproj?.path);
10559
+ let disposed = false;
10560
+ return {
10561
+ modelId: secure.modelId,
10562
+ plan,
10563
+ async chat(messages, onToken, o) {
10564
+ const last = messages[messages.length - 1];
10565
+ const answer = await engine.chat(messages, onToken, { signal: o?.signal });
10566
+ if (opts.capture && last?.role === "user") {
10567
+ opts.capture.captureConsentedTurn(secure.modelId, last.content, answer);
10568
+ }
10569
+ return answer;
10570
+ },
10571
+ async dispose() {
10572
+ if (disposed) return;
10573
+ disposed = true;
10574
+ await engine.dispose();
10575
+ mmproj?.dispose();
10576
+ secure.dispose();
10577
+ }
10578
+ };
10579
+ } catch (e) {
10580
+ mmproj?.dispose();
10581
+ secure.dispose();
10582
+ throw e;
10583
+ }
10584
+ }
10585
+
10586
+ // src/weights/serve.ts
10587
+ init_paths();
10588
+ var KNOWN_MODELS = ["gauss", "avila"];
10589
+ var MODEL_PARAMS_B = { gauss: 9, avila: 9 };
10590
+ var DEFAULT_PORT = 11435;
10591
+ var ORIRO_ORIGIN = /^https:\/\/(www\.)?oriro\.(ai|app)$/;
10592
+ function orxPathFor(modelId) {
10593
+ return join39(oriroDir(), "weights", `${modelId}.orx`);
10594
+ }
10595
+ function normalizeModel(raw) {
10596
+ const s = String(raw ?? "").toLowerCase();
10597
+ for (const m of KNOWN_MODELS) if (s.includes(m)) return m;
10598
+ return "gauss";
10599
+ }
10600
+ function sseData(obj) {
10601
+ return `data: ${JSON.stringify(obj)}
10602
+
10603
+ `;
10604
+ }
10605
+ function deltaChunk(model, id, content) {
10606
+ return { id, object: "chat.completion.chunk", model, choices: [{ index: 0, delta: { content }, finish_reason: null }] };
10607
+ }
10608
+ function finalChunk(model, id) {
10609
+ return { id, object: "chat.completion.chunk", model, choices: [{ index: 0, delta: {}, finish_reason: "stop" }] };
10610
+ }
10611
+ async function readJson2(req) {
10612
+ const chunks = [];
10613
+ for await (const c of req) chunks.push(c);
10614
+ try {
10615
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
10616
+ } catch {
10617
+ return {};
10618
+ }
10619
+ }
10620
+ async function startLocalServer(opts) {
10621
+ const warm = /* @__PURE__ */ new Map();
10622
+ const getModel = (modelId) => {
10623
+ let p = warm.get(modelId);
10624
+ if (!p) {
10625
+ const mmprojPath = orxPathFor(`${modelId}-mmproj`);
10626
+ p = startLocalModel(orxPathFor(modelId), opts.licenseKey, MODEL_PARAMS_B[modelId] ?? 9, {
10627
+ mmprojOrxPath: existsSync26(mmprojPath) ? mmprojPath : void 0
10628
+ });
10629
+ warm.set(modelId, p);
10630
+ }
10631
+ return p;
10632
+ };
10633
+ const server = createServer((req, res) => {
10634
+ void handle(req, res, getModel).catch(() => {
10635
+ if (!res.headersSent) res.writeHead(500);
10636
+ res.end();
10637
+ });
10638
+ });
10639
+ const port = opts.port ?? DEFAULT_PORT;
10640
+ await new Promise((resolve3) => server.listen(port, opts.host ?? "127.0.0.1", resolve3));
10641
+ return {
10642
+ port,
10643
+ async close() {
10644
+ await new Promise((r) => server.close(() => r()));
10645
+ for (const p of warm.values()) {
10646
+ try {
10647
+ (await p).dispose();
10648
+ } catch {
10649
+ }
10650
+ }
10651
+ }
10652
+ };
10653
+ }
10654
+ async function handle(req, res, getModel) {
10655
+ const origin = req.headers.origin;
10656
+ if (origin && ORIRO_ORIGIN.test(origin)) {
10657
+ res.setHeader("access-control-allow-origin", origin);
10658
+ res.setHeader("vary", "origin");
10659
+ }
10660
+ if (req.method === "OPTIONS") {
10661
+ if (origin && ORIRO_ORIGIN.test(origin)) {
10662
+ res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
10663
+ res.setHeader("access-control-allow-headers", "content-type, authorization");
10664
+ res.setHeader("access-control-allow-private-network", "true");
10665
+ }
10666
+ res.writeHead(204).end();
10667
+ return;
10668
+ }
10669
+ const url = new URL(req.url ?? "/", "http://localhost");
10670
+ if (url.pathname === "/health") {
10671
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ status: "ok" }));
10672
+ return;
10673
+ }
10674
+ if (url.pathname === "/v1/models") {
10675
+ const data = KNOWN_MODELS.filter((m) => existsSync26(orxPathFor(m))).map((id) => ({ id, object: "model" }));
10676
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ object: "list", data }));
10677
+ return;
10678
+ }
10679
+ if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
10680
+ await chatCompletions(req, res, getModel);
10681
+ return;
10682
+ }
10683
+ res.writeHead(404).end();
10684
+ }
10685
+ async function chatCompletions(req, res, getModel) {
10686
+ const body = await readJson2(req);
10687
+ const modelId = normalizeModel(body.model);
10688
+ const messages = Array.isArray(body.messages) ? body.messages : [];
10689
+ const id = `chatcmpl-${randomBytes4(8).toString("hex")}`;
10690
+ let model;
10691
+ try {
10692
+ model = await getModel(modelId);
10693
+ } catch (e) {
10694
+ res.writeHead(503, { "content-type": "application/json" }).end(JSON.stringify({ error: { message: e.message } }));
10695
+ return;
10696
+ }
10697
+ res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
10698
+ try {
10699
+ await model.chat(messages, (t) => {
10700
+ res.write(sseData(deltaChunk(modelId, id, t)));
10701
+ });
10702
+ res.write(sseData(finalChunk(modelId, id)));
10703
+ res.write("data: [DONE]\n\n");
10704
+ } catch (e) {
10705
+ res.write(sseData({ error: { message: e.message } }));
10706
+ }
10707
+ res.end();
10708
+ }
10709
+
10710
+ // src/commands/models.ts
10711
+ init_theme();
10712
+ var MODELS = [
10713
+ { id: "gauss", label: "Gauss V2.4", paramsB: 9 },
10714
+ { id: "avila", label: "Avila V2.4", paramsB: 9 }
10715
+ ];
10716
+ function orxPath(id) {
10717
+ return join40(oriroDir(), "weights", `${id}.orx`);
10718
+ }
10719
+ function gb(n) {
10720
+ return (n / 1e9).toFixed(2) + " GB";
10721
+ }
10722
+ function modelIdFromFilename(p) {
10723
+ const base = (p.replace(/\\/g, "/").split("/").pop() ?? "").toLowerCase().replace(/\.gguf$/, "");
10724
+ const root = base.includes("gauss") ? "gauss" : base.includes("avila") ? "avila" : null;
10725
+ if (!root) return null;
10726
+ return base.includes("mmproj") ? `${root}-mmproj` : root;
10727
+ }
10728
+ async function fetchManifest(base, setupToken) {
10729
+ const r = await fetch(`${base}/api/weights/manifest`, {
10730
+ method: "POST",
10731
+ headers: { "content-type": "application/json" },
10732
+ body: JSON.stringify({ setup_token: setupToken })
10733
+ });
10734
+ if (r.status === 401 || r.status === 403) {
10735
+ throw new Error(`downloads refused (HTTP ${r.status}) \u2014 run \`oriro login\` to authorize this machine${r.status === 403 ? " (device attestation required)" : ""}.`);
10736
+ }
10737
+ if (!r.ok) throw new Error(`weights manifest HTTP ${r.status}`);
10738
+ const d = await r.json();
10739
+ return d.models ?? {};
10740
+ }
10741
+ async function resumeUrl(base, setupToken, modelId) {
10742
+ const r = await fetch(`${base}/api/weights/resume`, {
10743
+ method: "POST",
10744
+ headers: { "content-type": "application/json" },
10745
+ body: JSON.stringify({ key: `${modelId}.gguf`, setup_token: setupToken })
10746
+ });
10747
+ const d = await r.json().catch(() => ({}));
10748
+ if (!r.ok || !d.model?.url) throw new Error("link refresh failed");
10749
+ return d.model.url;
10750
+ }
10751
+ function registerModelsCommand(program2) {
10752
+ const models = program2.command("models").description("run Gauss & Avila on this machine \u2014 download, serve, status (no Ollama)");
10753
+ models.command("status").description("show which models are installed on this machine").action(() => {
10754
+ heading("ORIRO models \u2014 on this machine");
10755
+ for (const m of MODELS) {
10756
+ const p = orxPath(m.id);
10757
+ if (existsSync27(p)) ok(`${m.label} \u2014 installed (${gb(statSync8(p).size)}, device-locked)`);
10758
+ else info(`${m.label} \u2014 ${dim("not downloaded")}`);
10759
+ }
10760
+ info(dim(`location: ${join40(oriroDir(), "weights")}`));
10761
+ });
10762
+ models.command("pull").description("download Gauss then Avila to this machine (one at a time, resumable)").option("--only <id>", "download just one model (gauss|avila)").action(async (opts) => {
10763
+ const base = process.env.ORIRO_API_BASE ?? "https://oriro.ai";
10764
+ const license = readLicense();
10765
+ const setupToken = readSetupToken();
10766
+ if (!setupToken) die("not authorized for downloads \u2014 run `oriro login <code>` first (code from oriro.app).");
10767
+ const pick = opts.only ? MODELS.filter((m) => m.id === opts.only.toLowerCase()) : [...MODELS];
10768
+ if (!pick.length) die(`unknown model "${opts.only}" (use gauss|avila)`);
10769
+ let manifest;
10770
+ try {
10771
+ manifest = await fetchManifest(base, setupToken);
10772
+ } catch (e) {
10773
+ return die(e.message);
10774
+ }
10775
+ for (const m of pick) {
10776
+ const entry = manifest[m.id];
10777
+ if (!entry?.url) return die(`the weights manifest has no entry for ${m.id}`);
10778
+ heading(m.label);
10779
+ const size = await probeSize(entry.url).catch((e) => die(e.message));
10780
+ info(`downloading ${gb(size)} \u2014 resumable, streams straight to disk`);
10781
+ let lastPct = -1;
10782
+ await pullModelToOrx({
10783
+ modelId: m.id,
10784
+ url: entry.url,
10785
+ sizeBytes: size,
10786
+ sha256: entry.sha256 ?? "",
10787
+ licenseKey: license,
10788
+ createdTs: Date.now(),
10789
+ version: "2.4",
10790
+ refresh: () => resumeUrl(base, setupToken, m.id),
10791
+ onProgress: (done, total) => {
10792
+ const pct = Math.floor(done / total * 100);
10793
+ if (pct !== lastPct) {
10794
+ lastPct = pct;
10795
+ process.stdout.write(`\r ${accent(String(pct).padStart(3))}% ${gb(done)} / ${gb(total)} `);
10796
+ }
10797
+ }
10798
+ });
10799
+ process.stdout.write("\n");
10800
+ ok(`${m.label} ready \u2014 locked to this device`);
10801
+ const vision = manifest[`${m.id}-mmproj`];
10802
+ if (vision?.url) {
10803
+ info(`${m.label} vision projector \u2026`);
10804
+ const vsize = await probeSize(vision.url).catch(() => 0);
10805
+ if (vsize > 0) {
10806
+ await pullModelToOrx({
10807
+ modelId: `${m.id}-mmproj`,
10808
+ url: vision.url,
10809
+ sizeBytes: vsize,
10810
+ sha256: vision.sha256 ?? "",
10811
+ licenseKey: license,
10812
+ createdTs: Date.now(),
10813
+ version: "2.4",
10814
+ refresh: () => resumeUrl(base, setupToken, `${m.id}-mmproj`)
10815
+ });
10816
+ ok(`${m.label} vision ready`);
10817
+ }
10818
+ }
10819
+ }
10820
+ ok("all set \u2014 run `oriro models serve` to use them locally");
10821
+ });
10822
+ models.command("import <files...>").description("device-lock GGUFs you downloaded from oriro.app into runnable .orx (local, no login)").action(async (files) => {
10823
+ const license = readLicense();
10824
+ heading("Import models to this machine");
10825
+ let done = 0;
10826
+ for (const f of files) {
10827
+ if (!existsSync27(f)) {
10828
+ info(`skip ${f} \u2014 file not found`);
10829
+ continue;
10830
+ }
10831
+ const id = modelIdFromFilename(f);
10832
+ if (!id) {
10833
+ info(`skip ${f} \u2014 filename must contain 'gauss' or 'avila'`);
10834
+ continue;
10835
+ }
10836
+ const label = MODELS.find((m) => m.id === id)?.label ?? id;
10837
+ info(`device-locking ${label} \u2026`);
10838
+ await packOrxToFile(f, orxPath(id), {
10839
+ kek: deriveKek(license),
10840
+ watermark: `orx:${id}-import`,
10841
+ meta: { modelId: id, version: "2.4", createdTs: Date.now() }
10842
+ });
10843
+ ok(`${label} imported (${gb(statSync8(orxPath(id)).size)}, locked to this device)`);
10844
+ done++;
10845
+ }
10846
+ if (!done) die("nothing imported \u2014 pass the gauss.gguf / avila.gguf you downloaded from oriro.app");
10847
+ ok("run `oriro models serve` to use them locally (no Ollama)");
10848
+ });
10849
+ models.command("serve").description("run the models locally on an OpenAI-compatible endpoint (no Ollama)").option("-p, --port <n>", "port (default 11435)", (v) => parseInt(v, 10)).action(async (opts) => {
10850
+ const license = readLicense();
10851
+ const installed = MODELS.filter((m) => existsSync27(orxPath(m.id)));
10852
+ if (!installed.length) die("no models installed \u2014 run `oriro models pull` first.");
10853
+ const server = await startLocalServer({ licenseKey: license, port: opts.port });
10854
+ heading("ORIRO local endpoint");
10855
+ ok(`serving ${installed.map((m) => m.label).join(" + ")} on http://127.0.0.1:${server.port}`);
10856
+ info(dim("OpenAI-compatible: POST /v1/chat/completions \xB7 GET /v1/models \xB7 GET /health"));
10857
+ info(dim("oriro.ai / oriro.app reach it directly (CORS built in). Ctrl-C to stop."));
10858
+ await new Promise((resolve3) => {
10859
+ process.on("SIGINT", () => {
10860
+ void server.close().then(resolve3);
10861
+ });
10862
+ });
10863
+ });
10864
+ }
10865
+
9865
10866
  // src/commands/import.ts
9866
- import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync4, statSync as statSync6, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
9867
- import { join as join34, basename as basename4 } from "path";
10867
+ import { existsSync as existsSync28, readFileSync as readFileSync29, readdirSync as readdirSync4, statSync as statSync9, cpSync as cpSync2, mkdirSync as mkdirSync20 } from "fs";
10868
+ import { join as join41, basename as basename4 } from "path";
9868
10869
  init_mcp_client();
9869
10870
  init_custom();
9870
10871
  init_loader();
@@ -9872,10 +10873,10 @@ init_theme();
9872
10873
  function registerImportCommand(program2) {
9873
10874
  const imp = program2.command("import").description("migrate from another CLI (MCP servers, skills)");
9874
10875
  imp.command("mcp <file>").description("import MCP servers from a Claude-compatible mcp.json (Guardian-vetted)").action((file6) => {
9875
- if (!existsSync22(file6)) die(`no such file: ${file6}`);
10876
+ if (!existsSync28(file6)) die(`no such file: ${file6}`);
9876
10877
  let servers;
9877
10878
  try {
9878
- const j = JSON.parse(readFileSync27(file6, "utf8"));
10879
+ const j = JSON.parse(readFileSync29(file6, "utf8"));
9879
10880
  servers = j.mcpServers ?? j.servers ?? {};
9880
10881
  } catch (e) {
9881
10882
  die(`could not parse ${file6}: ${e instanceof Error ? e.message : String(e)}`);
@@ -9921,14 +10922,14 @@ function registerImportCommand(program2) {
9921
10922
  info(`${imported} imported \xB7 ${blocked2} blocked${imported ? ` \u2014 they connect in-session; see \`oriro connectors custom\`` : ""}`);
9922
10923
  });
9923
10924
  imp.command("skills <dir>").description("import SKILL.md skill folders from another CLI's skills directory").action((dir) => {
9924
- if (!existsSync22(dir) || !statSync6(dir).isDirectory()) die(`no such directory: ${dir}`);
10925
+ if (!existsSync28(dir) || !statSync9(dir).isDirectory()) die(`no such directory: ${dir}`);
9925
10926
  const dest = userSkillsDir();
9926
- mkdirSync18(dest, { recursive: true });
10927
+ mkdirSync20(dest, { recursive: true });
9927
10928
  heading("Import skills");
9928
- const sources = existsSync22(join34(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join34(dir, e)).filter((p) => statSync6(p).isDirectory() && existsSync22(join34(p, "SKILL.md")));
10929
+ const sources = existsSync28(join41(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join41(dir, e)).filter((p) => statSync9(p).isDirectory() && existsSync28(join41(p, "SKILL.md")));
9929
10930
  let n = 0;
9930
10931
  for (const src of sources) {
9931
- cpSync2(src, join34(dest, basename4(src)), { recursive: true });
10932
+ cpSync2(src, join41(dest, basename4(src)), { recursive: true });
9932
10933
  process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename4(src))}
9933
10934
  `);
9934
10935
  n++;
@@ -10039,6 +11040,8 @@ registerVoiceCommand(program);
10039
11040
  registerAgentsCommand(program);
10040
11041
  registerConfigCommand(program);
10041
11042
  registerSetupCommand(program);
11043
+ registerLoginCommand(program);
11044
+ registerModelsCommand(program);
10042
11045
  registerImportCommand(program);
10043
11046
  registerCompletionCommand(program);
10044
11047
  enableHelpOnError(program);