oroute-cli 0.3.0 → 0.3.1

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/oroute.cjs +184 -108
  2. package/package.json +1 -1
package/dist/oroute.cjs CHANGED
@@ -240950,6 +240950,33 @@ function getGitStatusSummary(cwd) {
240950
240950
  return void 0;
240951
240951
  }
240952
240952
  }
240953
+ function detectMonorepo(cwd) {
240954
+ const indicators = [
240955
+ { file: "pnpm-workspace.yaml", type: "pnpm workspace" },
240956
+ { file: "turbo.json", type: "turborepo" },
240957
+ { file: "nx.json", type: "nx" },
240958
+ { file: "lerna.json", type: "lerna" },
240959
+ { file: "rush.json", type: "rush" }
240960
+ ];
240961
+ for (const { file, type } of indicators) {
240962
+ if (fs10.existsSync(path9.join(cwd, file))) {
240963
+ const dirs = ["apps", "packages", "libs", "services"].filter(
240964
+ (d) => fs10.existsSync(path9.join(cwd, d)) && fs10.statSync(path9.join(cwd, d)).isDirectory()
240965
+ );
240966
+ const packages = dirs.flatMap((d) => {
240967
+ try {
240968
+ return fs10.readdirSync(path9.join(cwd, d)).filter(
240969
+ (f) => fs10.statSync(path9.join(cwd, d, f)).isDirectory()
240970
+ ).map((f) => `${d}/${f}`);
240971
+ } catch {
240972
+ return [];
240973
+ }
240974
+ });
240975
+ return `${type} (${packages.length} packages: ${packages.slice(0, 8).join(", ")}${packages.length > 8 ? "..." : ""})`;
240976
+ }
240977
+ }
240978
+ return null;
240979
+ }
240953
240980
  function loadProjectContext(cwd) {
240954
240981
  const contextFiles = [];
240955
240982
  const candidates = [
@@ -240970,6 +240997,10 @@ function loadProjectContext(cwd) {
240970
240997
  }
240971
240998
  const gitBranch = getGitBranch(cwd);
240972
240999
  const gitStatus = getGitStatusSummary(cwd);
241000
+ const monorepoType = detectMonorepo(cwd);
241001
+ if (monorepoType) {
241002
+ contextFiles.push({ name: "monorepo", content: monorepoType });
241003
+ }
240973
241004
  return { files: contextFiles, gitBranch, gitStatus };
240974
241005
  }
240975
241006
  function buildContextString(ctx) {
@@ -241695,6 +241726,7 @@ function getActiveTasks() {
241695
241726
  var import_meta = {};
241696
241727
  var debugMode = false;
241697
241728
  var dryRunMode = false;
241729
+ var skipConfirmations = false;
241698
241730
  var lastAiResponse = "";
241699
241731
  var undoStack = [];
241700
241732
  function isDryRun() {
@@ -241779,6 +241811,9 @@ var ALL_COMMANDS = [
241779
241811
  "/undo",
241780
241812
  "/next-task",
241781
241813
  "/dry-run",
241814
+ "/estimate-cost",
241815
+ "/skip-confirmation",
241816
+ "/yes",
241782
241817
  // Conversion / Info
241783
241818
  "/docx",
241784
241819
  "/xlsx",
@@ -242258,6 +242293,26 @@ function handleSlashCommand(input, ctx) {
242258
242293
  '"\uB2E4\uC74C \uC791\uC5C5" or "Pending Tasks\uC5D0\uC11C \uB2E4\uC74C \uAC70 \uC9C4\uD589\uD574\uC918"'
242259
242294
  );
242260
242295
  return "handled";
242296
+ case "/estimate-cost": {
242297
+ const tokenCount = ctx.messages.reduce((sum, m) => {
242298
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
242299
+ return sum + Math.ceil(text.length / 4);
242300
+ }, 0);
242301
+ const estInputCost = tokenCount / 1e6 * 3;
242302
+ const estOutputCost = 2e3 / 1e6 * 15;
242303
+ console.log(`${CYAN} Estimated cost for next request:${RESET}`);
242304
+ console.log(`${GRAY} Context: ~${tokenCount.toLocaleString()} tokens${RESET}`);
242305
+ console.log(`${GRAY} Input: ~$${estInputCost.toFixed(4)} (Sonnet rate)${RESET}`);
242306
+ console.log(`${GRAY} Output: ~$${estOutputCost.toFixed(4)} (est. 2K tokens)${RESET}`);
242307
+ console.log(`${GRAY} Total: ~$${(estInputCost + estOutputCost).toFixed(4)}${RESET}`);
242308
+ console.log(`${GRAY} (Auto-routing may select cheaper model)${RESET}`);
242309
+ return "handled";
242310
+ }
242311
+ case "/skip-confirmation":
242312
+ case "/yes":
242313
+ skipConfirmations = !skipConfirmations;
242314
+ console.log(`${skipConfirmations ? YELLOW : GREEN} Confirmations: ${skipConfirmations ? "SKIPPED (auto-yes)" : "ENABLED (will ask before writes)"}${RESET}`);
242315
+ return "handled";
242261
242316
  // -----------------------------------------------------------------------
242262
242317
  // Conversion / Info — natural language hints
242263
242318
  // -----------------------------------------------------------------------
@@ -243276,121 +243331,142 @@ function truncateResult(result) {
243276
243331
  async function executeTool(tool, cwd, config) {
243277
243332
  const confirmWrite = config.skipConfirmations ? false : config.confirmBeforeWrite !== false;
243278
243333
  const confirmExec = config.skipConfirmations ? false : config.confirmBeforeExecute !== false;
243334
+ const toolStart = performance.now();
243279
243335
  try {
243280
- switch (tool.name) {
243281
- case "read_file": {
243282
- const input = tool.input;
243283
- printToolUse("read_file", input.path);
243284
- const result = await readFileTool(input, cwd);
243285
- printToolResult(result.content);
243286
- return truncateResult(JSON.stringify({ content: result.content, lines: result.lines, size: result.size }));
243287
- }
243288
- case "write_file": {
243289
- const input = tool.input;
243290
- printToolUse("write_file", input.path);
243291
- const diff = generateDiff(input.path, input.content, cwd);
243292
- console.log(`${GRAY}${diff}${RESET}`);
243293
- if (isDryRun()) {
243294
- console.log(`${YELLOW} [DRY-RUN] Would write ${input.content.length} chars to ${input.path}${RESET}`);
243295
- return JSON.stringify({ success: true, note: "dry-run \u2014 not written" });
243296
- }
243297
- if (confirmWrite) {
243298
- const ok = await confirm(`Write to ${input.path}?`);
243299
- if (!ok) return JSON.stringify({ success: false, reason: "User declined" });
243300
- }
243301
- const result = writeFileTool(input, cwd);
243302
- printSuccess(`Written ${result.bytesWritten} bytes${result.isNew ? " (new file)" : ""}`);
243303
- return JSON.stringify(result);
243336
+ const result = await executeToolInner(tool, cwd, confirmWrite, confirmExec, config);
243337
+ const elapsed = performance.now() - toolStart;
243338
+ if (elapsed > 100) {
243339
+ console.log(`${DIM} (${tool.name} took ${elapsed.toFixed(0)}ms)${RESET}`);
243340
+ }
243341
+ return result;
243342
+ } catch (err) {
243343
+ const elapsed = performance.now() - toolStart;
243344
+ const msg = err instanceof Error ? err.message : String(err);
243345
+ printError(`${tool.name}: ${msg}`);
243346
+ if (elapsed > 100) {
243347
+ console.log(`${DIM} (failed after ${elapsed.toFixed(0)}ms)${RESET}`);
243348
+ }
243349
+ return JSON.stringify({ error: msg });
243350
+ }
243351
+ }
243352
+ async function executeToolInner(tool, cwd, confirmWrite, confirmExec, config) {
243353
+ switch (tool.name) {
243354
+ case "read_file": {
243355
+ const input = tool.input;
243356
+ printToolUse("read_file", input.path);
243357
+ const result = await readFileTool(input, cwd);
243358
+ printToolResult(result.content);
243359
+ return truncateResult(JSON.stringify({ content: result.content, lines: result.lines, size: result.size }));
243360
+ }
243361
+ case "write_file": {
243362
+ const input = tool.input;
243363
+ printToolUse("write_file", input.path);
243364
+ const resolved = path16.resolve(cwd, input.path);
243365
+ const dangerPaths = ["/etc", "/usr", "/bin", "/sbin", "/System", "/Windows", "node_modules"];
243366
+ if (dangerPaths.some((d) => resolved.startsWith(d) || resolved.includes("node_modules"))) {
243367
+ console.log(`${RED} \u26A0 WARNING: Writing to system/protected path: ${resolved}${RESET}`);
243304
243368
  }
243305
- case "list_directory": {
243306
- const input = tool.input;
243307
- printToolUse("list_directory", input.path);
243308
- const tree = listDirectoryTool(input, cwd);
243309
- printToolResult(tree);
243310
- return truncateResult(tree);
243369
+ if (resolved.includes(".env")) {
243370
+ console.log(`${YELLOW} \u26A0 Modifying .env file \u2014 secrets may be affected${RESET}`);
243311
243371
  }
243312
- case "search_files": {
243313
- const input = tool.input;
243314
- printToolUse("search_files", `"${input.pattern}" in ${input.path}`);
243315
- const output = searchFilesTool(input, cwd);
243316
- const formatted = output.results.map((r) => `${r.file}:${r.line}: ${r.content}`).join("\n");
243317
- const meta = output.hasMore ? `
243318
- (${output.totalFound}+ total matches, showing ${output.offset + 1}-${output.offset + output.results.length}. Use offset=${output.offset + output.limit} for more.)` : "";
243319
- printToolResult((formatted || "No matches found.") + meta);
243320
- return truncateResult(JSON.stringify(output));
243372
+ const diff = generateDiff(input.path, input.content, cwd);
243373
+ console.log(`${GRAY}${diff}${RESET}`);
243374
+ if (isDryRun()) {
243375
+ console.log(`${YELLOW} [DRY-RUN] Would write ${input.content.length} chars to ${input.path}${RESET}`);
243376
+ return JSON.stringify({ success: true, note: "dry-run \u2014 not written" });
243321
243377
  }
243322
- case "execute_command": {
243323
- const input = tool.input;
243324
- printToolUse("execute_command", input.command);
243325
- if (config.planMode || isDryRun()) {
243326
- const tag = config.planMode ? "PLAN MODE" : "DRY-RUN";
243327
- console.log(`${YELLOW} [${tag}] Would execute: ${input.command}${RESET}`);
243328
- return JSON.stringify({ stdout: `[${tag.toLowerCase()} \u2014 not executed]`, stderr: "", exitCode: 0 });
243329
- }
243330
- if (confirmExec) {
243331
- const ok = await confirm(`Execute: ${input.command}`);
243332
- if (!ok) return JSON.stringify({ stdout: "", stderr: "User declined", exitCode: -1 });
243333
- }
243334
- const result = executeCommandTool(input, cwd);
243335
- if (result.stdout) printToolResult(result.stdout);
243336
- if (result.stderr) console.log(`${RED}${result.stderr}${RESET}`);
243337
- if (result.exitCode === 0) printSuccess("Command completed");
243338
- else console.log(`${YELLOW} Exit code: ${result.exitCode}${RESET}`);
243339
- return truncateResult(JSON.stringify(result));
243378
+ if (confirmWrite) {
243379
+ const ok = await confirm(`Write to ${input.path}?`);
243380
+ if (!ok) return JSON.stringify({ success: false, reason: "User declined" });
243340
243381
  }
243341
- case "edit_file": {
243342
- const input = tool.input;
243343
- printToolUse("edit_file", input.path);
243344
- const diff = generateEditDiff(input);
243345
- console.log(`${GRAY}${diff}${RESET}`);
243346
- if (config.planMode || isDryRun()) {
243347
- const tag = config.planMode ? "PLAN MODE" : "DRY-RUN";
243348
- console.log(`${YELLOW} [${tag}] Would edit: ${input.path}${RESET}`);
243349
- return JSON.stringify({ success: true, note: `${tag.toLowerCase()} \u2014 not executed` });
243350
- }
243351
- if (confirmWrite) {
243352
- const ok = await confirm(`Edit ${input.path}?`);
243353
- if (!ok) return JSON.stringify({ success: false, reason: "User declined" });
243354
- }
243355
- const result = editFileTool(input, cwd);
243356
- printSuccess("Edit applied");
243357
- return JSON.stringify(result);
243382
+ const result = writeFileTool(input, cwd);
243383
+ printSuccess(`Written ${result.bytesWritten} bytes${result.isNew ? " (new file)" : ""}`);
243384
+ return JSON.stringify(result);
243385
+ }
243386
+ case "list_directory": {
243387
+ const input = tool.input;
243388
+ printToolUse("list_directory", input.path);
243389
+ const tree = listDirectoryTool(input, cwd);
243390
+ printToolResult(tree);
243391
+ return truncateResult(tree);
243392
+ }
243393
+ case "search_files": {
243394
+ const input = tool.input;
243395
+ printToolUse("search_files", `"${input.pattern}" in ${input.path}`);
243396
+ const output = searchFilesTool(input, cwd);
243397
+ const formatted = output.results.map((r) => `${r.file}:${r.line}: ${r.content}`).join("\n");
243398
+ const meta = output.hasMore ? `
243399
+ (${output.totalFound}+ total matches, showing ${output.offset + 1}-${output.offset + output.results.length}. Use offset=${output.offset + output.limit} for more.)` : "";
243400
+ printToolResult((formatted || "No matches found.") + meta);
243401
+ return truncateResult(JSON.stringify(output));
243402
+ }
243403
+ case "execute_command": {
243404
+ const input = tool.input;
243405
+ printToolUse("execute_command", input.command);
243406
+ if (config.planMode || isDryRun()) {
243407
+ const tag = config.planMode ? "PLAN MODE" : "DRY-RUN";
243408
+ console.log(`${YELLOW} [${tag}] Would execute: ${input.command}${RESET}`);
243409
+ return JSON.stringify({ stdout: `[${tag.toLowerCase()} \u2014 not executed]`, stderr: "", exitCode: 0 });
243358
243410
  }
243359
- case "glob": {
243360
- const input = tool.input;
243361
- printToolUse("glob", input.pattern);
243362
- const results = globTool(input, cwd);
243363
- const formatted = results.join("\n");
243364
- printToolResult(formatted || "No matches found.");
243365
- return truncateResult(JSON.stringify({ matches: results, count: results.length }));
243411
+ if (confirmExec) {
243412
+ const ok = await confirm(`Execute: ${input.command}`);
243413
+ if (!ok) return JSON.stringify({ stdout: "", stderr: "User declined", exitCode: -1 });
243366
243414
  }
243367
- case "read_image": {
243368
- const input = tool.input;
243369
- printToolUse("read_image", input.path);
243370
- const result = readImageTool(input, cwd);
243371
- printSuccess(`Read image: ${result.media_type}, ${(result.size / 1024).toFixed(1)}KB`);
243372
- return JSON.stringify(result);
243415
+ const result = executeCommandTool(input, cwd);
243416
+ if (result.stdout) printToolResult(result.stdout);
243417
+ if (result.stderr) console.log(`${RED}${result.stderr}${RESET}`);
243418
+ if (result.exitCode === 0) printSuccess("Command completed");
243419
+ else console.log(`${YELLOW} Exit code: ${result.exitCode}${RESET}`);
243420
+ return truncateResult(JSON.stringify(result));
243421
+ }
243422
+ case "edit_file": {
243423
+ const input = tool.input;
243424
+ printToolUse("edit_file", input.path);
243425
+ const diff = generateEditDiff(input);
243426
+ console.log(`${GRAY}${diff}${RESET}`);
243427
+ if (config.planMode || isDryRun()) {
243428
+ const tag = config.planMode ? "PLAN MODE" : "DRY-RUN";
243429
+ console.log(`${YELLOW} [${tag}] Would edit: ${input.path}${RESET}`);
243430
+ return JSON.stringify({ success: true, note: `${tag.toLowerCase()} \u2014 not executed` });
243373
243431
  }
243374
- case "diff_files": {
243375
- const input = tool.input;
243376
- printToolUse("diff_files", `${input.file_a} \u2194 ${input.file_b}`);
243377
- const result = diffFilesTool(input, cwd);
243378
- const coloredLines = result.split("\n").map((line) => {
243379
- if (line.startsWith("+")) return `${GREEN}${line}${RESET}`;
243380
- if (line.startsWith("-")) return `${RED}${line}${RESET}`;
243381
- if (line.startsWith("@")) return `${CYAN}${line}${RESET}`;
243382
- return line;
243383
- });
243384
- printToolResult(coloredLines.join("\n"));
243385
- return truncateResult(result);
243432
+ if (confirmWrite) {
243433
+ const ok = await confirm(`Edit ${input.path}?`);
243434
+ if (!ok) return JSON.stringify({ success: false, reason: "User declined" });
243386
243435
  }
243387
- default:
243388
- return JSON.stringify({ error: `Unknown tool: ${tool.name}` });
243436
+ const result = editFileTool(input, cwd);
243437
+ printSuccess("Edit applied");
243438
+ return JSON.stringify(result);
243389
243439
  }
243390
- } catch (err) {
243391
- const msg = err instanceof Error ? err.message : String(err);
243392
- printError(msg);
243393
- return JSON.stringify({ error: msg });
243440
+ case "glob": {
243441
+ const input = tool.input;
243442
+ printToolUse("glob", input.pattern);
243443
+ const results = globTool(input, cwd);
243444
+ const formatted = results.join("\n");
243445
+ printToolResult(formatted || "No matches found.");
243446
+ return truncateResult(JSON.stringify({ matches: results, count: results.length }));
243447
+ }
243448
+ case "read_image": {
243449
+ const input = tool.input;
243450
+ printToolUse("read_image", input.path);
243451
+ const result = readImageTool(input, cwd);
243452
+ printSuccess(`Read image: ${result.media_type}, ${(result.size / 1024).toFixed(1)}KB`);
243453
+ return JSON.stringify(result);
243454
+ }
243455
+ case "diff_files": {
243456
+ const input = tool.input;
243457
+ printToolUse("diff_files", `${input.file_a} \u2194 ${input.file_b}`);
243458
+ const result = diffFilesTool(input, cwd);
243459
+ const coloredLines = result.split("\n").map((line) => {
243460
+ if (line.startsWith("+")) return `${GREEN}${line}${RESET}`;
243461
+ if (line.startsWith("-")) return `${RED}${line}${RESET}`;
243462
+ if (line.startsWith("@")) return `${CYAN}${line}${RESET}`;
243463
+ return line;
243464
+ });
243465
+ printToolResult(coloredLines.join("\n"));
243466
+ return truncateResult(result);
243467
+ }
243468
+ default:
243469
+ return JSON.stringify({ error: `Unknown tool: ${tool.name}` });
243394
243470
  }
243395
243471
  }
243396
243472
  async function executeToolWithHooks(tool, cwd, config, hooks) {
@@ -244216,7 +244292,7 @@ function parseArgs(args) {
244216
244292
  let targetPath;
244217
244293
  let prompt;
244218
244294
  let debug = false;
244219
- let skipConfirmations = false;
244295
+ let skipConfirmations2 = false;
244220
244296
  let planMode = false;
244221
244297
  for (let i = 0; i < args.length; i++) {
244222
244298
  const arg = args[i];
@@ -244225,7 +244301,7 @@ function parseArgs(args) {
244225
244301
  continue;
244226
244302
  }
244227
244303
  if (arg === "--yes" || arg === "-y") {
244228
- skipConfirmations = true;
244304
+ skipConfirmations2 = true;
244229
244305
  continue;
244230
244306
  }
244231
244307
  if (arg === "--plan") {
@@ -244243,7 +244319,7 @@ function parseArgs(args) {
244243
244319
  if (arg.startsWith("-")) continue;
244244
244320
  targetPath = arg;
244245
244321
  }
244246
- return { command, path: targetPath, prompt, debug, skipConfirmations, planMode };
244322
+ return { command, path: targetPath, prompt, debug, skipConfirmations: skipConfirmations2, planMode };
244247
244323
  }
244248
244324
  async function promptInput(prompt) {
244249
244325
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oroute-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "O'Route CLI — AI API auto-routing for 13 models from your terminal",
5
5
  "type": "module",
6
6
  "bin": {