@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.
- package/dist/index.js +36 -51
- 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
|
|
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
|
-
|
|
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
|
|
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 `
|
|
2919
|
-
\u2022
|
|
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
|
|
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
|
|
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(["
|
|
3222
|
-
var HOME_ONLY = /* @__PURE__ */ new Set(["FORGE_API_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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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}
|
|
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)
|
|
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.
|
|
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("--
|
|
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,
|
|
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 = "";
|