@scopeide/cli 0.1.0 → 0.1.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/index.js +36 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -699,7 +699,7 @@ var SITE_DEFAULTS = {
699
699
  ctaTitle: "Ready to code with {name} IDE?",
700
700
  ctaText: "Experience the AI-first code editor designed for maximum flow. Deep codebase indexing, instant tab completions, and autonomous multi-model agent workflows right on your desktop.",
701
701
  ctaButton: "Download {name} IDE",
702
- ctaNote: "Free tier included \xB7 Available for macOS, Linux & Windows \xB7 Open models or BYOK",
702
+ ctaNote: "Free tier included \xB7 Available for macOS, Linux & Windows \xB7 Open models",
703
703
  columns: [
704
704
  { title: "Product", links: [{ label: "IDE Features", href: "/features" }, { label: "Pricing", href: "/pricing" }, { label: "Documentation", href: "/docs" }, { label: "Changelog", href: "/changelog" }] },
705
705
  { title: "Resources", links: [{ label: "Desktop Downloads", href: "/docs#download" }, { label: "Quickstart Guide", href: "/docs#quickstart" }, { label: "Skills & Rules", href: "/docs#skills" }, { label: "Model Catalog", href: "/pricing#models" }] },
@@ -2106,6 +2106,12 @@ var GatewayError = class extends Error {
2106
2106
  return this.body?.error?.code;
2107
2107
  }
2108
2108
  };
2109
+ var UPSTREAM_HINT = /\b(deepseek|glm|zhipu|bigmodel|qwen|dashscope|kimi|moonshot|minimax|openai|anthropic|openrouter|a2agent|gemini|mistral|upstream)\b|does not exist or you do not have access|model provider/i;
2110
+ var MODEL_UNAVAILABLE = "The selected model is temporarily unavailable. Please try again shortly, or switch models with /model.";
2111
+ function publicErrorMessage(status, message) {
2112
+ if ([401, 402, 403, 429].includes(status)) return message;
2113
+ return status >= 500 || UPSTREAM_HINT.test(message) ? MODEL_UNAVAILABLE : message;
2114
+ }
2109
2115
  var NetworkError = class extends Error {
2110
2116
  constructor(url, reason) {
2111
2117
  super(`cannot reach server (${reason})`);
@@ -2115,6 +2121,8 @@ var NetworkError = class extends Error {
2115
2121
  url;
2116
2122
  reason;
2117
2123
  };
2124
+ var TRANSIENT_NET = /* @__PURE__ */ new Set(["UND_ERR_CONNECT_TIMEOUT", "UND_ERR_SOCKET", "ETIMEDOUT", "EAI_AGAIN", "ECONNRESET", "ENETUNREACH"]);
2125
+ var NET_RETRIES = 3;
2118
2126
  var TRANSIENT = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
2119
2127
  var MAX_RETRY_DELAY_MS = 1e4;
2120
2128
  function retryDelayMs(retryAfter, fallbackMs = 1e3) {
@@ -2146,7 +2154,14 @@ var OpenAICompatClient = class {
2146
2154
  signal: AbortSignal.timeout(this.cfg.timeoutMs ?? 18e4)
2147
2155
  });
2148
2156
  } catch (e) {
2149
- throw new NetworkError(url, e?.cause?.code ?? e?.name ?? e?.message ?? "network error");
2157
+ const code = e?.cause?.code;
2158
+ if (TRANSIENT_NET.has(code) && attempt <= NET_RETRIES) {
2159
+ const delayMs = 1e3 * 2 ** (attempt - 1);
2160
+ this.cfg.onRetry?.({ status: 0, attempt, delayMs });
2161
+ await sleep(delayMs);
2162
+ continue;
2163
+ }
2164
+ throw new NetworkError(url, code ?? e?.name ?? e?.message ?? "network error");
2150
2165
  }
2151
2166
  if (res.ok) return req.onToken && res.body ? await readStream(res.body, req.onToken, url) : parseResult(await res.json());
2152
2167
  const text2 = await res.text();
@@ -2163,7 +2178,7 @@ var OpenAICompatClient = class {
2163
2178
  message = parsed.error?.message ?? parsed.error ?? text2;
2164
2179
  } catch {
2165
2180
  }
2166
- throw new GatewayError(res.status, typeof message === "string" ? message : JSON.stringify(message), parsed);
2181
+ throw new GatewayError(res.status, publicErrorMessage(res.status, typeof message === "string" ? message : JSON.stringify(message)), parsed);
2167
2182
  }
2168
2183
  }
2169
2184
  };
@@ -2200,7 +2215,7 @@ async function readStream(body, onToken, url = "stream") {
2200
2215
  } catch {
2201
2216
  return;
2202
2217
  }
2203
- if (j.error) throw new GatewayError(502, j.error.message ?? "stream error", j);
2218
+ if (j.error) throw new GatewayError(502, publicErrorMessage(502, j.error.message ?? "stream error"), j);
2204
2219
  if (j.usage) usage2 = j.usage;
2205
2220
  const delta = j.choices?.[0]?.delta;
2206
2221
  if (!delta) return;
@@ -2907,7 +2922,7 @@ function explainError(e, _apiUrl) {
2907
2922
  case 502:
2908
2923
  case 503:
2909
2924
  case 504:
2910
- return `The server or model provider had a problem (${e.status}): ${safeMsg}
2925
+ return `${safeMsg}
2911
2926
  It was retried once. Try again shortly.`;
2912
2927
  default:
2913
2928
  return `${safeMsg} (HTTP ${e.status})`;
@@ -2915,14 +2930,11 @@ function explainError(e, _apiUrl) {
2915
2930
  }
2916
2931
  const msg = sanitize(e instanceof Error ? e.message : String(e));
2917
2932
  if (msg.includes("daily limit of 60 free-model requests") || msg.includes("free_daily_limit")) {
2918
- return `Provider rate limited: Daily limit of 60 free-model requests reached.
2919
- \u2022 Switch to mock mode: \`/model mock-model\`
2920
- \u2022 Or wait until 00:00 UTC for reset.`;
2933
+ return `Daily limit of 60 free-model requests reached.
2934
+ \u2022 Try again after 00:00 UTC.`;
2921
2935
  }
2922
2936
  if (msg.includes("Insufficient Balance")) {
2923
- return `Provider account has insufficient balance.
2924
- \u2022 Top up your balance, or
2925
- \u2022 Switch to mock mode: \`/model mock-model\``;
2937
+ return "The service is temporarily unavailable. Please try again shortly.";
2926
2938
  }
2927
2939
  return msg;
2928
2940
  }
@@ -2970,7 +2982,7 @@ async function models(modelToSet) {
2970
2982
  console.log(cs("cli.commands.models_on_your_0_plan", "Models on your {0} plan:", r.plan));
2971
2983
  for (const m of r.data) console.log(cs("cli.commands.0_1_2_x_3", "{0} {1} x{2} credits/token {3}", m.id === cfg.defaultModel ? "*" : " ", m.id.padEnd(28), m.multiplier, m.vision ? "[images]" : ""));
2972
2984
  if (r.data.some((m) => m.vision)) console.log(cs("cli.commands.images_can_read_images_attach", "\n[images] = can read images (attach with @file.png, drag a file in, or Ctrl+V)"));
2973
- if (!r.data.length) console.log(cs("cli.commands.no_models_available_the_server", "No models available \u2014 the server has no provider keys configured."));
2985
+ if (!r.data.length) console.log(cs("cli.commands.no_models_available_the_server", "No models are available for your account right now."));
2974
2986
  }
2975
2987
  async function usage() {
2976
2988
  const u = await api("/v1/usage");
@@ -3218,8 +3230,8 @@ function parseDotenv(text2) {
3218
3230
  }
3219
3231
  return out;
3220
3232
  }
3221
- var WANTED = /* @__PURE__ */ new Set(["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL", "FORGE_API_KEY", "FORGE_API_URL", "CUSTOM_BASE_URL", "CUSTOM_API_KEY", "CUSTOM_MODELS"]);
3222
- var HOME_ONLY = /* @__PURE__ */ new Set(["FORGE_API_URL", "DEEPSEEK_BASE_URL", "CUSTOM_BASE_URL"]);
3233
+ var WANTED = /* @__PURE__ */ new Set(["FORGE_API_KEY", "FORGE_API_URL"]);
3234
+ var HOME_ONLY = /* @__PURE__ */ new Set(["FORGE_API_URL"]);
3223
3235
  function loadDotenv(cwd = process.cwd()) {
3224
3236
  const home = path14.join(os6.homedir(), BRAND.dirName, ".env");
3225
3237
  for (const file of [path14.join(cwd, ".env"), home]) {
@@ -3698,10 +3710,10 @@ var BUILTIN_SLASH_MENU_ITEMS = [
3698
3710
  return cs("cli.ui.learn_taste_from_sessions_with", "Learn taste from sessions with other coding agents (Claude Code, Cursor, etc)");
3699
3711
  } },
3700
3712
  { command: "/login", get description() {
3701
- return cs("cli.ui.log_in_to_command_code", "Log in to " + BRAND.name + " or a provider");
3713
+ return cs("cli.ui.log_in_to_command_code", "Log in to " + BRAND.name);
3702
3714
  } },
3703
3715
  { command: "/logout", get description() {
3704
- return cs("cli.ui.log_out_of_command_code", "Log out of " + BRAND.name + " or a provider");
3716
+ return cs("cli.ui.log_out_of_command_code", "Log out of " + BRAND.name);
3705
3717
  } },
3706
3718
  { command: "/mcp", get description() {
3707
3719
  return cs("cli.ui.manage_mcp_server_connections", "Manage MCP server connections");
@@ -3754,9 +3766,6 @@ var BUILTIN_SLASH_MENU_ITEMS = [
3754
3766
  { command: "/pr-comments", get description() {
3755
3767
  return cs("cli.ui.fetch_all_pr_comments_for", "Fetch all PR comments for current branch");
3756
3768
  } },
3757
- { command: "/provider", get description() {
3758
- return `Connect to AI providers: ${BRAND.name}, your own provider keys`;
3759
- } },
3760
3769
  { command: "/reload", get description() {
3761
3770
  return cs("cli.ui.restart_command_code_and_resume", "Restart " + BRAND.name + " and resume this session");
3762
3771
  } },
@@ -4039,12 +4048,12 @@ ${THEME.assistantPrefix}\u25C6${THEME.reset} ${e.text}
4039
4048
  else if (e.type === "context_warning") console.error(cs("cli.run.large_context_0_tokens_per", "\x1B[33m\u26A0 large context: ~{0} tokens per request. Long contexts cost more; consider a fresh session or narrower @mentions.\x1B[0m", e.tokens.toLocaleString()));
4040
4049
  else if (e.type === "escalate") console.log(cs("cli.run.escalating_0_1_2", "\x1B[33m\u2191 escalating {0} \u2192 {1}: {2}\x1B[0m", e.from, e.to, e.reason));
4041
4050
  }
4042
- var onRetry = (i) => console.error(dim(` \u21BB server returned ${i.status}; retrying once in ${(i.delayMs / 1e3).toFixed(1)}s\u2026`));
4051
+ var onRetry = (i) => console.error(dim(` \u21BB ${i.status ? `server returned ${i.status}` : "connection problem"}; retrying in ${(i.delayMs / 1e3).toFixed(1)}s\u2026`));
4043
4052
  async function pickBackend(opts) {
4044
4053
  loadDotenv();
4045
4054
  const cfg = await loadConfig();
4046
4055
  const wantsMock = opts.model === "mock-model" || opts.model === "mock";
4047
- const useGateway = !!cfg.token && !opts.local;
4056
+ const useGateway = !!cfg.token;
4048
4057
  if (useGateway) {
4049
4058
  let chosenModel = opts.model ?? cfg.defaultModel ?? DEFAULT_MODEL;
4050
4059
  try {
@@ -4074,19 +4083,6 @@ async function pickBackend(opts) {
4074
4083
  };
4075
4084
  }
4076
4085
  if (wantsMock) return { client: new MockClient(), model: "mock-model", label: "local mock" };
4077
- if (opts.local) {
4078
- const customKey = process.env.CUSTOM_API_KEY;
4079
- if (customKey && process.env.CUSTOM_BASE_URL) {
4080
- const base = process.env.CUSTOM_BASE_URL.replace(/\/$/, "");
4081
- const customDefault = process.env.CUSTOM_MODELS?.split(",")[0]?.trim();
4082
- return { client: new OpenAICompatClient({ baseUrl: base, apiKey: customKey, onRetry }), model: opts.model ?? customDefault ?? DEFAULT_MODEL, label: "local custom" };
4083
- }
4084
- const key = process.env.DEEPSEEK_API_KEY;
4085
- if (key) {
4086
- const base = (process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com").replace(/\/$/, "");
4087
- return { client: new OpenAICompatClient({ baseUrl: `${base}/v1`, apiKey: key, onRetry }), model: opts.model ?? DEFAULT_MODEL, label: "local DeepSeek" };
4088
- }
4089
- }
4090
4086
  if (!cfg.token) {
4091
4087
  if (opts.prompt) {
4092
4088
  throw new GatewayError(401, "Not logged in \u2014 run `forge login` to authenticate.", {});
@@ -4102,7 +4098,7 @@ async function pickBackend(opts) {
4102
4098
  apiUrl: cfg.apiUrl
4103
4099
  };
4104
4100
  }
4105
- throw new Error(cs("cli.run.no_backend_available_either_0", 'no backend available. Either:\n \u2022 {0} login (use the ForgeCode API)\n \u2022 put DEEPSEEK_API_KEY=... in ./.env, then run with --local\n \u2022 try the offline mock: {1} --model mock-model -p "create hello.ts that logs hi"', BRAND.bin, BRAND.bin));
4101
+ throw new Error(cs("cli.run.no_backend_available", "Not logged in. Run `{0} login` to get started.", BRAND.bin));
4106
4102
  }
4107
4103
  async function resolveStrongModel(opts, backendLabel, cheap) {
4108
4104
  const explicit = opts.escalateTo ?? process.env.FORGE_ESCALATION_MODEL;
@@ -4287,15 +4283,7 @@ ${opts.appendSystemPrompt}` : "");
4287
4283
  } catch {
4288
4284
  }
4289
4285
  const fallbackList = [];
4290
- const customModels = process.env.CUSTOM_MODELS?.split(",").map((s) => s.trim()).filter(Boolean) ?? [];
4291
- for (const id of customModels) {
4292
- if (!fallbackList.some((m) => m.id === id)) {
4293
- fallbackList.push({ id, provider: "custom", description: "custom endpoint" });
4294
- }
4295
- }
4296
4286
  const standard = [
4297
- { id: "a2agent/deepseek-v4.1-flash-1017", provider: "a2agent", description: "fast flash model" },
4298
- { id: "deepseek-chat", provider: "deepseek", description: "deepseek-v3 / reasoning" },
4299
4287
  { id: "mock-model", provider: "mock", description: "offline test mock" }
4300
4288
  ];
4301
4289
  for (const m of standard) {
@@ -4386,10 +4374,9 @@ ${e.message}`);
4386
4374
  return okRun;
4387
4375
  }
4388
4376
  const files = r.changedFiles.length ? ` \xB7 changed: ${r.changedFiles.join(", ")}` : "";
4389
- const cache = r.usage.cacheReadTokens ? ` (${r.usage.cacheReadTokens} cached)` : "";
4390
4377
  const saved = r.cachedReads ? ` \xB7 ${r.cachedReads} repeat read(s) skipped` : "";
4391
4378
  const via = "finalModel" in r && r.escalated ? `${activeModel} \u2192 ${r.finalModel}` : activeModel;
4392
- console.log(cs("cli.run.0_worked_for_1_s", "\n{0}\u2713 Done in {1}s{2}\n{3}", THEME.workedPrefix, elapsedSec, THEME.reset, dim(`[${via} via ${label}] ${r.usage.promptTokens} in${cache} / ${r.usage.completionTokens} out${files}${saved}`)));
4379
+ console.log(cs("cli.run.0_worked_for_1_s", "\n{0}\u2713 Done in {1}s{2}\n{3}", THEME.workedPrefix, elapsedSec, THEME.reset, dim(`[${via}] ${r.usage.promptTokens} in / ${r.usage.completionTokens} out${files}${saved}`)));
4393
4380
  const stop = stopMessage(r, maxRunTokens);
4394
4381
  if (stop) console.log(`\x1B[33m${stop}\x1B[0m`);
4395
4382
  return r.stopReason === "done" && r.toolErrors === 0;
@@ -4476,14 +4463,12 @@ ${THEME.userMsgBg} ${THEME.userPointer}${THEME.bold}\u276F${THEME.reset}${THEME.
4476
4463
  };
4477
4464
  const resolveModelItems = (query) => {
4478
4465
  const models2 = availableModelsCache.length > 0 ? availableModelsCache : [
4479
- { id: "a2agent/deepseek-v4.1-flash-1017", provider: "a2agent", description: "fast flash model" },
4480
- { id: "deepseek-chat", provider: "deepseek", description: "deepseek-v3 / reasoning" },
4481
4466
  { id: "mock-model", provider: "mock", description: "offline test mock" }
4482
4467
  ];
4483
4468
  const q = query.trim().toLowerCase();
4484
4469
  const matches2 = models2.filter((m) => {
4485
4470
  if (!q) return true;
4486
- return m.id.toLowerCase().includes(q) || m.provider && m.provider.toLowerCase().includes(q);
4471
+ return m.id.toLowerCase().includes(q);
4487
4472
  });
4488
4473
  return matches2.map((m) => {
4489
4474
  const isCurrent = m.id === currentModel;
@@ -5497,9 +5482,9 @@ if (typeof dns.setDefaultResultOrder === "function") {
5497
5482
  if (typeof net.setDefaultAutoSelectFamily === "function") {
5498
5483
  net.setDefaultAutoSelectFamily(false);
5499
5484
  }
5500
- var program = new Command(BRAND.bin).description(`${BRAND.name} \u2014 CLI coding agent`).version("0.1.0");
5485
+ var program = new Command(BRAND.bin).description(`${BRAND.name} \u2014 CLI coding agent`).version("0.1.1");
5501
5486
  function runOptions(cmd) {
5502
- return cmd.option("-p, --prompt <text>", "run a single prompt headlessly").option("--model <id>", "model id (see `models`)").option("--local", "skip the gateway: use the offline mock or DEEPSEEK_API_KEY from .env").option("--max-steps <n>", "model calls per run (default 25, max 100)", (v) => Number.parseInt(v, 10)).option("--max-tokens <n>", "soft token budget for one run (default 300000)", (v) => Number.parseInt(v, 10)).option("--cascade", "start with the cheap default model; escalate to a stronger one only if the attempt fails").option("--effort <level>", "low (default) or high: high goes straight to the strongest model your plan allows").option("--escalate-to <id>", "model to escalate to (default: strongest on your plan, or SCOPE_ESCALATION_MODEL)").option("--verify <cmd>", "with --cascade: a shell command (e.g. 'pnpm test'); if it fails the strong model takes over").option("--plan", "plan mode: read-only exploration; prints a step-by-step plan instead of editing").option("-c, --continue", "continue the most recent session of this project").option("-r, --resume [id]", "resume a session by id (or pick one from a list)").option("--output-format <fmt>", "headless output: text (default), json (one result object) or stream-json (event lines)").option("--append-system-prompt <text>", "extra instructions added to the system prompt").option("--yolo", "auto-approve tool calls (dangerous shell is still blocked)").addOption(new Option("--allow-all", "dev only: disable project-root restrictions").hideHelp());
5487
+ return cmd.option("-p, --prompt <text>", "run a single prompt headlessly").option("--model <id>", "model id (see `models`)").option("--max-steps <n>", "model calls per run (default 25, max 100)", (v) => Number.parseInt(v, 10)).option("--max-tokens <n>", "soft token budget for one run (default 300000)", (v) => Number.parseInt(v, 10)).option("--cascade", "start with the cheap default model; escalate to a stronger one only if the attempt fails").option("--effort <level>", "low (default) or high: high goes straight to the strongest model your plan allows").option("--escalate-to <id>", "model to escalate to (default: strongest on your plan, or SCOPE_ESCALATION_MODEL)").option("--verify <cmd>", "with --cascade: a shell command (e.g. 'pnpm test'); if it fails the strong model takes over").option("--plan", "plan mode: read-only exploration; prints a step-by-step plan instead of editing").option("-c, --continue", "continue the most recent session of this project").option("-r, --resume [id]", "resume a session by id (or pick one from a list)").option("--output-format <fmt>", "headless output: text (default), json (one result object) or stream-json (event lines)").option("--append-system-prompt <text>", "extra instructions added to the system prompt").option("--yolo", "auto-approve tool calls (dangerous shell is still blocked)").addOption(new Option("--allow-all", "dev only: disable project-root restrictions").hideHelp());
5503
5488
  }
5504
5489
  var handle = (fn) => async (...a) => {
5505
5490
  try {
@@ -5509,7 +5494,7 @@ var handle = (fn) => async (...a) => {
5509
5494
  process.exit(1);
5510
5495
  }
5511
5496
  };
5512
- var toRun = (o) => ({ prompt: o.prompt, model: o.model, yolo: o.yolo, local: o.local, allowAll: o.allowAll, maxSteps: o.maxSteps, maxTokens: o.maxTokens, cascade: o.cascade, effort: o.effort, escalateTo: o.escalateTo, verify: o.verify, plan: o.plan, continue: o.continue, resume: o.resume, outputFormat: o.outputFormat, appendSystemPrompt: o.appendSystemPrompt });
5497
+ var toRun = (o) => ({ prompt: o.prompt, model: o.model, yolo: o.yolo, allowAll: o.allowAll, maxSteps: o.maxSteps, maxTokens: o.maxTokens, cascade: o.cascade, effort: o.effort, escalateTo: o.escalateTo, verify: o.verify, plan: o.plan, continue: o.continue, resume: o.resume, outputFormat: o.outputFormat, appendSystemPrompt: o.appendSystemPrompt });
5513
5498
  function readPiped(idleMs = 1500) {
5514
5499
  return new Promise((resolve) => {
5515
5500
  let data = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scopeide/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Scope — the AI coding agent for your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",