spacecode 2.0.0 → 2.0.2
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 +184 -30
- package/dist/index.js.map +4 -4
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +2 -2
- package/dist/repl.mjs +93547 -90257
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -585,6 +585,7 @@ var config_exports = {};
|
|
|
585
585
|
__export(config_exports, {
|
|
586
586
|
addTask: () => addTask,
|
|
587
587
|
addTasks: () => addTasks,
|
|
588
|
+
applyApiKeyFlag: () => applyApiKeyFlag,
|
|
588
589
|
cancelTask: () => cancelTask,
|
|
589
590
|
clearAuth: () => clearAuth,
|
|
590
591
|
getConfigDir: () => getConfigDir,
|
|
@@ -837,6 +838,9 @@ async function clearAuth() {
|
|
|
837
838
|
}
|
|
838
839
|
}
|
|
839
840
|
}
|
|
841
|
+
function applyApiKeyFlag(key) {
|
|
842
|
+
if (key?.startsWith("sk_")) process.env.SPACECODE_TOKEN = key;
|
|
843
|
+
}
|
|
840
844
|
async function resolveAuthToken() {
|
|
841
845
|
const envToken = process.env.SPACECODE_TOKEN;
|
|
842
846
|
if (envToken) {
|
|
@@ -7421,17 +7425,6 @@ function renderEvent(event) {
|
|
|
7421
7425
|
case "text": {
|
|
7422
7426
|
break;
|
|
7423
7427
|
}
|
|
7424
|
-
case "text": {
|
|
7425
|
-
if (phase === "thinking") {
|
|
7426
|
-
stopThinkingTimer();
|
|
7427
|
-
clearLine();
|
|
7428
|
-
process.stderr.write("\n");
|
|
7429
|
-
}
|
|
7430
|
-
phase = "streaming";
|
|
7431
|
-
process.stdout.write(renderMarkdown(event.content));
|
|
7432
|
-
if (!event.content.endsWith("\n")) process.stdout.write("\n");
|
|
7433
|
-
break;
|
|
7434
|
-
}
|
|
7435
7428
|
case "text_delta": {
|
|
7436
7429
|
if (phase === "thinking") {
|
|
7437
7430
|
stopThinkingTimer();
|
|
@@ -7904,6 +7897,143 @@ var init_init = __esm({
|
|
|
7904
7897
|
}
|
|
7905
7898
|
});
|
|
7906
7899
|
|
|
7900
|
+
// src/auth/device-protocol.ts
|
|
7901
|
+
function parseDeviceCode(status2, body) {
|
|
7902
|
+
if (status2 === 429) {
|
|
7903
|
+
throw new Error("Too many login attempts; wait a few minutes and retry");
|
|
7904
|
+
}
|
|
7905
|
+
if (status2 === 404 || status2 === 401 || status2 === 503) {
|
|
7906
|
+
throw new Error(
|
|
7907
|
+
`Browser sign-in is not available on the server yet (status ${status2}); use \`spacecode login --api-key\` instead`
|
|
7908
|
+
);
|
|
7909
|
+
}
|
|
7910
|
+
if (status2 !== 200) {
|
|
7911
|
+
throw new Error(`Could not start login: status ${status2}`);
|
|
7912
|
+
}
|
|
7913
|
+
if (typeof body !== "object" || body === null) {
|
|
7914
|
+
throw new Error(`Could not start login: status ${status2}`);
|
|
7915
|
+
}
|
|
7916
|
+
const obj = body;
|
|
7917
|
+
const requiredStringFields = [
|
|
7918
|
+
"device_code",
|
|
7919
|
+
"user_code",
|
|
7920
|
+
"verification_uri"
|
|
7921
|
+
];
|
|
7922
|
+
for (const field of requiredStringFields) {
|
|
7923
|
+
const value = obj[field];
|
|
7924
|
+
if (typeof value !== "string") {
|
|
7925
|
+
throw new Error(`Could not start login: status ${status2}`);
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
const interval = obj["interval"];
|
|
7929
|
+
if (typeof interval !== "number" || interval <= 0) {
|
|
7930
|
+
throw new Error(`Could not start login: status ${status2}`);
|
|
7931
|
+
}
|
|
7932
|
+
const expiresIn = obj["expires_in"];
|
|
7933
|
+
if (typeof expiresIn !== "number" || expiresIn <= 0) {
|
|
7934
|
+
throw new Error(`Could not start login: status ${status2}`);
|
|
7935
|
+
}
|
|
7936
|
+
const verificationUriComplete = typeof obj["verification_uri_complete"] === "string" ? obj["verification_uri_complete"].trim() : "";
|
|
7937
|
+
const url = verificationUriComplete !== "" ? verificationUriComplete : obj["verification_uri"];
|
|
7938
|
+
return {
|
|
7939
|
+
deviceCode: obj["device_code"],
|
|
7940
|
+
userCode: obj["user_code"],
|
|
7941
|
+
url,
|
|
7942
|
+
intervalSec: interval,
|
|
7943
|
+
expiresInSec: expiresIn
|
|
7944
|
+
};
|
|
7945
|
+
}
|
|
7946
|
+
function classifyTokenResponse(status2, body, intervalSec) {
|
|
7947
|
+
if (status2 === 200) {
|
|
7948
|
+
if (typeof body === "object" && body !== null) {
|
|
7949
|
+
const b = body;
|
|
7950
|
+
if (typeof b.key === "string" && b.key.startsWith("sk_")) {
|
|
7951
|
+
const result = {
|
|
7952
|
+
kind: "done",
|
|
7953
|
+
key: b.key
|
|
7954
|
+
};
|
|
7955
|
+
if (typeof b.keyPrefix === "string") {
|
|
7956
|
+
result.keyPrefix = b.keyPrefix;
|
|
7957
|
+
}
|
|
7958
|
+
return result;
|
|
7959
|
+
}
|
|
7960
|
+
}
|
|
7961
|
+
throw new Error("Login returned an invalid key");
|
|
7962
|
+
}
|
|
7963
|
+
const isObjectBody = typeof body === "object" && body !== null;
|
|
7964
|
+
const error = isObjectBody ? body.error : void 0;
|
|
7965
|
+
if (error === "authorization_pending") {
|
|
7966
|
+
return { kind: "pending", intervalSec };
|
|
7967
|
+
}
|
|
7968
|
+
if (error === "slow_down") {
|
|
7969
|
+
return { kind: "pending", intervalSec: intervalSec + 5 };
|
|
7970
|
+
}
|
|
7971
|
+
if (error === "expired_token") {
|
|
7972
|
+
throw new Error("Login code expired");
|
|
7973
|
+
}
|
|
7974
|
+
if (error === "access_denied") {
|
|
7975
|
+
throw new Error("Login was denied");
|
|
7976
|
+
}
|
|
7977
|
+
if (error === "invalid_grant") {
|
|
7978
|
+
throw new Error("Login code is no longer valid; run `spacecode login` again");
|
|
7979
|
+
}
|
|
7980
|
+
if (error === "max_keys_reached") {
|
|
7981
|
+
throw new Error("Your organization has reached its API key limit; revoke an old key in the dashboard and retry");
|
|
7982
|
+
}
|
|
7983
|
+
const errMsg = typeof error === "string" ? `: ${error}` : "";
|
|
7984
|
+
throw new Error(`Login failed (${status2})${errMsg}`);
|
|
7985
|
+
}
|
|
7986
|
+
var init_device_protocol = __esm({
|
|
7987
|
+
"src/auth/device-protocol.ts"() {
|
|
7988
|
+
"use strict";
|
|
7989
|
+
}
|
|
7990
|
+
});
|
|
7991
|
+
|
|
7992
|
+
// src/auth/device-login.ts
|
|
7993
|
+
async function deviceLogin(baseUrl, deps) {
|
|
7994
|
+
const start = await deps.post(`${baseUrl}/api/device/code`, {});
|
|
7995
|
+
const info = parseDeviceCode(start.status, start.body);
|
|
7996
|
+
deps.log(`Your code: ${info.userCode}
|
|
7997
|
+
Approve sign-in at: ${info.url}`);
|
|
7998
|
+
try {
|
|
7999
|
+
await deps.openBrowser(info.url);
|
|
8000
|
+
} catch (err) {
|
|
8001
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
8002
|
+
deps.log(`Could not open a browser (${reason}); open ${info.url} manually`);
|
|
8003
|
+
}
|
|
8004
|
+
const deadline = deps.now() + info.expiresInSec * 1e3;
|
|
8005
|
+
let interval = info.intervalSec;
|
|
8006
|
+
let failures = 0;
|
|
8007
|
+
for (; ; ) {
|
|
8008
|
+
if (deps.now() >= deadline) {
|
|
8009
|
+
throw new Error("Login code expired");
|
|
8010
|
+
}
|
|
8011
|
+
await deps.sleep(interval * 1e3);
|
|
8012
|
+
let response;
|
|
8013
|
+
try {
|
|
8014
|
+
response = await deps.post(`${baseUrl}/api/device/token`, { device_code: info.deviceCode });
|
|
8015
|
+
} catch (err) {
|
|
8016
|
+
failures += 1;
|
|
8017
|
+
if (failures >= MAX_CONSECUTIVE_POST_FAILURES) throw err;
|
|
8018
|
+
continue;
|
|
8019
|
+
}
|
|
8020
|
+
failures = 0;
|
|
8021
|
+
const result = classifyTokenResponse(response.status, response.body, interval);
|
|
8022
|
+
if (result.kind === "done") {
|
|
8023
|
+
return result.keyPrefix === void 0 ? { key: result.key } : { key: result.key, keyPrefix: result.keyPrefix };
|
|
8024
|
+
}
|
|
8025
|
+
interval = result.intervalSec;
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
var MAX_CONSECUTIVE_POST_FAILURES;
|
|
8029
|
+
var init_device_login = __esm({
|
|
8030
|
+
"src/auth/device-login.ts"() {
|
|
8031
|
+
"use strict";
|
|
8032
|
+
init_device_protocol();
|
|
8033
|
+
MAX_CONSECUTIVE_POST_FAILURES = 3;
|
|
8034
|
+
}
|
|
8035
|
+
});
|
|
8036
|
+
|
|
7907
8037
|
// src/commands/auth.ts
|
|
7908
8038
|
async function login(options = {}) {
|
|
7909
8039
|
const apiKey2 = options.apiKey?.trim();
|
|
@@ -7911,22 +8041,42 @@ async function login(options = {}) {
|
|
|
7911
8041
|
if (!apiKey2.startsWith("sk_") || apiKey2.length < 12 || /\s/.test(apiKey2)) {
|
|
7912
8042
|
throw new Error("That does not look like a SpaceCode API key (expected sk_...).");
|
|
7913
8043
|
}
|
|
7914
|
-
await
|
|
7915
|
-
accessToken: apiKey2,
|
|
7916
|
-
user: { id: "api-key", githubId: "", githubLogin: `api-key ${apiKey2.slice(0, 7)}...` }
|
|
7917
|
-
});
|
|
8044
|
+
await saveApiKey(apiKey2);
|
|
7918
8045
|
console.log(import_chalk17.default.green("API key saved. You are signed in."));
|
|
7919
8046
|
return;
|
|
7920
8047
|
}
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
8048
|
+
const { key } = await deviceLogin(process.env.SPACECODE_AUTH_URL ?? DEFAULT_AUTH_URL, {
|
|
8049
|
+
post: postJson,
|
|
8050
|
+
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
|
|
8051
|
+
now: () => Date.now(),
|
|
8052
|
+
openBrowser: async (url) => {
|
|
8053
|
+
const { default: open } = await import("open");
|
|
8054
|
+
await open(url);
|
|
8055
|
+
},
|
|
8056
|
+
log: (msg) => console.log(msg)
|
|
8057
|
+
});
|
|
8058
|
+
await saveApiKey(key);
|
|
8059
|
+
console.log(import_chalk17.default.green("Signed in. API key saved to ~/.spacecode/auth.json."));
|
|
8060
|
+
}
|
|
8061
|
+
async function saveApiKey(apiKey2) {
|
|
8062
|
+
await saveAuth({
|
|
8063
|
+
accessToken: apiKey2,
|
|
8064
|
+
user: { id: "api-key", githubId: "", githubLogin: `api-key ${apiKey2.slice(0, 7)}...` }
|
|
8065
|
+
});
|
|
8066
|
+
}
|
|
8067
|
+
async function postJson(url, body) {
|
|
8068
|
+
const res = await fetch(url, {
|
|
8069
|
+
method: "POST",
|
|
8070
|
+
headers: { "content-type": "application/json" },
|
|
8071
|
+
body: JSON.stringify(body),
|
|
8072
|
+
signal: AbortSignal.timeout(15e3)
|
|
8073
|
+
});
|
|
8074
|
+
let parsed = null;
|
|
8075
|
+
try {
|
|
8076
|
+
parsed = await res.json();
|
|
8077
|
+
} catch {
|
|
8078
|
+
}
|
|
8079
|
+
return { status: res.status, body: parsed };
|
|
7930
8080
|
}
|
|
7931
8081
|
async function logout() {
|
|
7932
8082
|
const auth = await loadAuth();
|
|
@@ -7992,7 +8142,7 @@ async function requireAuth() {
|
|
|
7992
8142
|
}
|
|
7993
8143
|
return new BackendApiClient(apiUrl, freshAuth.accessToken);
|
|
7994
8144
|
}
|
|
7995
|
-
var import_chalk17;
|
|
8145
|
+
var import_chalk17, DEFAULT_AUTH_URL;
|
|
7996
8146
|
var init_auth2 = __esm({
|
|
7997
8147
|
"src/commands/auth.ts"() {
|
|
7998
8148
|
"use strict";
|
|
@@ -8000,6 +8150,8 @@ var init_auth2 = __esm({
|
|
|
8000
8150
|
init_remote();
|
|
8001
8151
|
init_client();
|
|
8002
8152
|
import_chalk17 = __toESM(require("chalk"));
|
|
8153
|
+
init_device_login();
|
|
8154
|
+
DEFAULT_AUTH_URL = "https://spacecode-app.g-a-l-a-c-t-i-c.com";
|
|
8003
8155
|
}
|
|
8004
8156
|
});
|
|
8005
8157
|
|
|
@@ -8600,7 +8752,7 @@ Agent error: ${message}
|
|
|
8600
8752
|
fileSync.start();
|
|
8601
8753
|
}
|
|
8602
8754
|
const ttyHost = new URL(wsUrl.replace("wss://", "https://").replace("ws://", "http://")).host;
|
|
8603
|
-
const version = "2.0.
|
|
8755
|
+
const version = "2.0.2";
|
|
8604
8756
|
if (isResume) {
|
|
8605
8757
|
let turnCount = 0;
|
|
8606
8758
|
try {
|
|
@@ -23761,7 +23913,7 @@ async function startLocalRepl(opts = {}) {
|
|
|
23761
23913
|
"Local REPL not built. Run `pnpm --filter @spacecode/agent-ui build` (or set SPACECODE_REPL_BIN), or use `spacecode --cloud` for a cloud session."
|
|
23762
23914
|
);
|
|
23763
23915
|
}
|
|
23764
|
-
const args = [bin, ...opts.yolo ? ["--yolo"] : []];
|
|
23916
|
+
const args = [bin, ...opts.yolo ? ["--yolo"] : [], ...opts.resume ? ["--continue"] : []];
|
|
23765
23917
|
return new Promise((resolve2, reject) => {
|
|
23766
23918
|
const child = (0, import_node_child_process3.spawn)(process.execPath, args, { stdio: "inherit" });
|
|
23767
23919
|
child.on("error", reject);
|
|
@@ -24042,11 +24194,12 @@ var init_index = __esm({
|
|
|
24042
24194
|
init_token();
|
|
24043
24195
|
init_agent2();
|
|
24044
24196
|
init_sentry();
|
|
24197
|
+
init_config();
|
|
24045
24198
|
init_config3();
|
|
24046
24199
|
init_analytics();
|
|
24047
24200
|
initPostHog();
|
|
24048
24201
|
program = new import_commander.Command();
|
|
24049
|
-
program.name("spacecode").description("SpaceCode - launch cloud dev environments with AI coding agents").version("2.0.
|
|
24202
|
+
program.name("spacecode").description("SpaceCode - launch cloud dev environments with AI coding agents").version("2.0.2").enablePositionalOptions();
|
|
24050
24203
|
program.command("launch").description("Launch a new cloud session").option("-R, --repo <repo>", "Clone a GitHub repository into workspace (URL or owner/repo)").option("-n, --new [name]", "Start a new project (optionally specify name, or will prompt)").option("--name <name>", "Name this session for easy reconnection").option("--destroy", "Destroy the session on exit (default: session stays alive)").option("-t, --idle-timeout <minutes>", "Auto-shutdown timeout in minutes (default: 15)", "15").option("--proxy-mode <mode>", 'API proxy mode: "legacy" (round-robin GLM) or "pipeline" (multi-model router)', "pipeline").option("--own-key", "Bring your own API key (no proxy)").option("--api-key <key>", "Use a specific API key (bypasses proxy)").action(async (options) => {
|
|
24051
24204
|
await launchWithOptions(options);
|
|
24052
24205
|
});
|
|
@@ -24285,11 +24438,12 @@ Batch ${result.groupId}: ${result.dispatched}/${result.total} dispatched`));
|
|
|
24285
24438
|
showConfigPaths();
|
|
24286
24439
|
}
|
|
24287
24440
|
});
|
|
24288
|
-
program.argument("[target]", "Repo (owner/repo) to launch, or session name/ID to attach").option("-p, --prompt [prompt]", "Run headless (non-interactive)").option("--output-format <format>", "Output format for -p: text (default) or json", "text").option("-v, --verbose", "Show detailed progress (phases, stderr)").option("-R, --repo <repo>", "Clone a GitHub repository into workspace").option("-n, --new [name]", "Start a new project").option("--name <name>", "Name this session for easy reconnection").option("--destroy", "Destroy the session on exit").option("-b, --branch <branch>", "Branch name for the task").option("--no-commit", "Skip auto-commit and PR creation").option("-t, --idle-timeout <minutes>", "Auto-shutdown timeout (default: 15)", "15").option("--proxy-mode <mode>", "API proxy mode", "pipeline").option("--own-key", "Bring your own API key").option("--api-key <key>", "Use a specific API key").option("--session <id>", "Resume an existing agent session").option("--cloud", "With no target: start a cloud agent session instead of the local REPL").option("--yolo", "Local REPL: skip confirmation prompts (policy denies still apply)").action(async (target, options) => {
|
|
24441
|
+
program.argument("[target]", "Repo (owner/repo) to launch, or session name/ID to attach").option("-p, --prompt [prompt]", "Run headless (non-interactive)").option("--output-format <format>", "Output format for -p: text (default) or json", "text").option("-v, --verbose", "Show detailed progress (phases, stderr)").option("-R, --repo <repo>", "Clone a GitHub repository into workspace").option("-n, --new [name]", "Start a new project").option("--name <name>", "Name this session for easy reconnection").option("--destroy", "Destroy the session on exit").option("-b, --branch <branch>", "Branch name for the task").option("--no-commit", "Skip auto-commit and PR creation").option("-t, --idle-timeout <minutes>", "Auto-shutdown timeout (default: 15)", "15").option("--proxy-mode <mode>", "API proxy mode", "pipeline").option("--own-key", "Bring your own API key").option("--api-key <key>", "Use a specific API key").option("--session <id>", "Resume an existing agent session").option("--cloud", "With no target: start a cloud agent session instead of the local REPL").option("--yolo", "Local REPL: skip confirmation prompts (policy denies still apply)").option("--continue", "Local REPL: resume the newest saved conversation for this project").action(async (target, options) => {
|
|
24289
24442
|
try {
|
|
24443
|
+
applyApiKeyFlag(options.apiKey);
|
|
24290
24444
|
if (!target && !options.session && options.prompt === void 0 && !options.cloud && !options.repo && !options.new) {
|
|
24291
24445
|
const { startLocalRepl: startLocalRepl2 } = await Promise.resolve().then(() => (init_repl(), repl_exports));
|
|
24292
|
-
process.exit(await startLocalRepl2({ yolo: options.yolo }));
|
|
24446
|
+
process.exit(await startLocalRepl2({ yolo: options.yolo, resume: options.continue }));
|
|
24293
24447
|
}
|
|
24294
24448
|
if (options.session) {
|
|
24295
24449
|
let sessionId = options.session;
|