spacecode 2.12.1 → 2.12.3

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/repl.mjs CHANGED
@@ -6904,6 +6904,164 @@ var init_mcp_rpc_pending = __esm({
6904
6904
  }
6905
6905
  });
6906
6906
 
6907
+ // ../agent/src/import/mcp-sse-parser.ts
6908
+ function createSseParser(onData) {
6909
+ let buffer = "";
6910
+ let dataLines = [];
6911
+ let skipLf = false;
6912
+ function handleLine(line) {
6913
+ if (line === "") {
6914
+ if (dataLines.length === 0) return;
6915
+ const payload = dataLines.join("\n");
6916
+ dataLines = [];
6917
+ onData(payload);
6918
+ return;
6919
+ }
6920
+ if (line.startsWith(":")) return;
6921
+ const colon = line.indexOf(":");
6922
+ if (colon === -1) {
6923
+ if (line === "data") dataLines.push("");
6924
+ return;
6925
+ }
6926
+ if (line.slice(0, colon) !== "data") return;
6927
+ const value = line.slice(colon + 1);
6928
+ dataLines.push(value.startsWith(" ") ? value.slice(1) : value);
6929
+ }
6930
+ return {
6931
+ push(chunk) {
6932
+ buffer += chunk;
6933
+ let start = 0;
6934
+ for (let i = 0; i < buffer.length; i++) {
6935
+ const ch = buffer[i];
6936
+ if (skipLf && i === start && ch === "\n") {
6937
+ skipLf = false;
6938
+ start = i + 1;
6939
+ continue;
6940
+ }
6941
+ skipLf = false;
6942
+ if (ch === "\n") {
6943
+ handleLine(buffer.slice(start, i));
6944
+ start = i + 1;
6945
+ } else if (ch === "\r") {
6946
+ handleLine(buffer.slice(start, i));
6947
+ start = i + 1;
6948
+ if (buffer[i + 1] === "\n") i++, start++;
6949
+ else if (i + 1 === buffer.length) skipLf = true;
6950
+ }
6951
+ }
6952
+ buffer = buffer.slice(start);
6953
+ },
6954
+ end() {
6955
+ buffer = "";
6956
+ dataLines = [];
6957
+ skipLf = false;
6958
+ }
6959
+ };
6960
+ }
6961
+ var init_mcp_sse_parser = __esm({
6962
+ "../agent/src/import/mcp-sse-parser.ts"() {
6963
+ "use strict";
6964
+ }
6965
+ });
6966
+
6967
+ // ../agent/src/import/mcp-http-transport.ts
6968
+ function createHttpTransport(opts) {
6969
+ const aborter = new AbortController();
6970
+ let sessionId = null;
6971
+ let closed = false;
6972
+ let reported = false;
6973
+ const fail = (reason) => {
6974
+ if (closed || reported) return;
6975
+ reported = true;
6976
+ closed = true;
6977
+ aborter.abort();
6978
+ opts.onClose(reason);
6979
+ };
6980
+ const deliver = (parsed) => {
6981
+ for (const m2 of Array.isArray(parsed) ? parsed : [parsed]) opts.onMessage(m2);
6982
+ };
6983
+ const headers = () => ({
6984
+ ...opts.headers,
6985
+ "content-type": "application/json",
6986
+ accept: "application/json, text/event-stream",
6987
+ "mcp-protocol-version": opts.protocolVersion,
6988
+ ...sessionId !== null ? { "mcp-session-id": sessionId } : {}
6989
+ });
6990
+ async function readStream(res) {
6991
+ if (!res.body) return;
6992
+ const decoder = new TextDecoder();
6993
+ const parser = createSseParser((data) => {
6994
+ let parsed;
6995
+ try {
6996
+ parsed = JSON.parse(data);
6997
+ } catch (err) {
6998
+ if (!(err instanceof SyntaxError)) throw err;
6999
+ return;
7000
+ }
7001
+ deliver(parsed);
7002
+ });
7003
+ for await (const chunk of res.body) parser.push(decoder.decode(chunk, { stream: true }));
7004
+ parser.push(decoder.decode());
7005
+ parser.end();
7006
+ }
7007
+ async function post(message) {
7008
+ const hadSession = sessionId !== null;
7009
+ let res;
7010
+ try {
7011
+ res = await opts.fetch(opts.url, { method: "POST", headers: headers(), body: JSON.stringify(message), signal: aborter.signal });
7012
+ } catch (err) {
7013
+ return fail(`request failed: ${err instanceof Error ? err.name : "error"}`);
7014
+ }
7015
+ if (closed) return;
7016
+ if (res.status === 401 || res.status === 403) return fail("needs authorization (OAuth is not supported)");
7017
+ if (res.status === 404 && hadSession) return fail("session expired");
7018
+ if (res.status >= 400) return fail(`HTTP ${res.status}`);
7019
+ const sid = res.headers.get("mcp-session-id");
7020
+ if (sessionId === null && sid) sessionId = sid;
7021
+ if (res.status === 202) return;
7022
+ const type = (res.headers.get("content-type") ?? "").toLowerCase();
7023
+ try {
7024
+ if (type.startsWith("text/event-stream")) await readStream(res);
7025
+ else if (type.startsWith("application/json")) {
7026
+ let parsed;
7027
+ try {
7028
+ parsed = JSON.parse(await res.text());
7029
+ } catch (err) {
7030
+ if (!(err instanceof SyntaxError)) throw err;
7031
+ return fail("invalid JSON in response");
7032
+ }
7033
+ deliver(parsed);
7034
+ }
7035
+ } catch (err) {
7036
+ fail(`request failed: ${err instanceof Error ? err.name : "error"}`);
7037
+ }
7038
+ }
7039
+ return {
7040
+ send(message) {
7041
+ if (closed) throw new Error(CLOSED);
7042
+ void post(message);
7043
+ },
7044
+ async close() {
7045
+ if (closed) return;
7046
+ closed = true;
7047
+ aborter.abort();
7048
+ if (sessionId === null) return;
7049
+ try {
7050
+ await opts.fetch(opts.url, { method: "DELETE", headers: { ...opts.headers, "mcp-protocol-version": opts.protocolVersion, "mcp-session-id": sessionId } });
7051
+ } catch {
7052
+ }
7053
+ }
7054
+ };
7055
+ }
7056
+ var CLOSED;
7057
+ var init_mcp_http_transport = __esm({
7058
+ "../agent/src/import/mcp-http-transport.ts"() {
7059
+ "use strict";
7060
+ init_mcp_sse_parser();
7061
+ CLOSED = "MCP server connection is closed";
7062
+ }
7063
+ });
7064
+
6907
7065
  // ../agent/src/import/mcp-rpc.ts
6908
7066
  function createRpcSession(deps) {
6909
7067
  const pending = new PendingRequests();
@@ -7152,6 +7310,8 @@ var init_src = __esm({
7152
7310
  init_claude_json_mcp();
7153
7311
  init_mcp_rpc_message();
7154
7312
  init_mcp_rpc_pending();
7313
+ init_mcp_sse_parser();
7314
+ init_mcp_http_transport();
7155
7315
  init_mcp_rpc();
7156
7316
  init_mcp_tool_result();
7157
7317
  init_mcp_client();
@@ -7809,7 +7969,7 @@ var init_import_claude = __esm({
7809
7969
  init_api_key();
7810
7970
  init_profile_runtime();
7811
7971
  init_import_adapt();
7812
- USAGE = "Usage: spacecode import-claude [--dry-run] [--overwrite] [--no-adapt] [--yes] [--restore-original]";
7972
+ USAGE = "Usage: spacecode import claude [--dry-run] [--overwrite] [--no-adapt] [--yes] [--restore-original]";
7813
7973
  }
7814
7974
  });
7815
7975
 
@@ -7924,6 +8084,18 @@ var init_stdio_transport = __esm({
7924
8084
  function approvalEnv(env3) {
7925
8085
  return Object.entries(env3).map(([k2, v2]) => `${k2}=${v2}`).sort();
7926
8086
  }
8087
+ function approvalKey(def) {
8088
+ const envNames = approvalEnv(def.env);
8089
+ return def.transport === "http" ? { command: `mcp-http:${def.url}`, args: [], envNames } : { command: def.command, args: def.args, envNames };
8090
+ }
8091
+ function isSafeMcpUrl(url) {
8092
+ try {
8093
+ const u3 = new URL(url);
8094
+ return u3.protocol === "https:" || u3.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(u3.hostname);
8095
+ } catch {
8096
+ return false;
8097
+ }
8098
+ }
7927
8099
  function mcpToolName(server, tool) {
7928
8100
  return `mcp__${sanitize2(server)}__${sanitize2(tool)}`;
7929
8101
  }
@@ -7981,11 +8153,10 @@ var init_mcp_manager = __esm({
7981
8153
  }
7982
8154
  for (const def of this.deps.defs) {
7983
8155
  try {
7984
- if (def.transport !== "stdio") continue;
7985
- const envNames = approvalEnv(def.env);
7986
- if (!await this.deps.isStdioApproved({ command: def.command, args: def.args, envNames })) continue;
7987
- if (!expandServerEnv({ command: def.command, args: def.args, env: def.env }, this.deps.env).ok) continue;
7988
- const hit = getCachedTools(this.cache, serverCacheKey({ name: def.name, command: def.command, args: def.args, envNames }, tc.hash));
8156
+ const key = approvalKey(def);
8157
+ if (!await this.deps.isStdioApproved(key)) continue;
8158
+ if (!expandServerEnv({ command: def.transport === "http" ? def.url : def.command, args: def.args, env: def.env }, this.deps.env).ok) continue;
8159
+ const hit = getCachedTools(this.cache, serverCacheKey({ name: def.name, ...key }, tc.hash));
7989
8160
  if (hit) this.cachedTools.set(def.name, this.usable(def.name, hit).tools);
7990
8161
  } catch (err) {
7991
8162
  this.cacheNotes.push(`${def.name}: cached tools skipped (${err instanceof Error ? err.message : String(err)})`);
@@ -8038,7 +8209,7 @@ var init_mcp_manager = __esm({
8038
8209
  if (!tc) return Promise.resolve();
8039
8210
  this.cacheWrite = this.cacheWrite.then(async () => {
8040
8211
  try {
8041
- const key = serverCacheKey({ name: def.name, command: def.command, args: def.args, envNames: approvalEnv(def.env) }, tc.hash);
8212
+ const key = serverCacheKey({ name: def.name, ...approvalKey(def) }, tc.hash);
8042
8213
  const next = putCachedTools(this.cache, key, tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })));
8043
8214
  if (next === this.cache) return;
8044
8215
  await tc.write(serializeToolCache(next));
@@ -8053,38 +8224,56 @@ var init_mcp_manager = __esm({
8053
8224
  const name = def.name;
8054
8225
  const skip = (reason) => void this.servers.set(name, { name, def, status: "skipped", reason });
8055
8226
  const fail = (reason) => void this.servers.set(name, { name, def, status: "failed", reason });
8056
- if (def.transport !== "stdio") return skip("http servers are not supported yet");
8057
- const envNames = approvalEnv(def.env);
8227
+ const http = def.transport === "http";
8228
+ if (http && !isSafeMcpUrl(def.url)) return skip("its URL must be https (or localhost)");
8229
+ const key = approvalKey(def);
8058
8230
  let approved;
8059
8231
  try {
8060
- approved = await this.deps.isStdioApproved({ command: def.command, args: def.args, envNames });
8232
+ approved = await this.deps.isStdioApproved(key);
8061
8233
  } catch (err) {
8062
8234
  return fail(`approval check failed: ${err instanceof Error ? err.message : String(err)}`);
8063
8235
  }
8064
- if (!approved) return skip(`its command is not approved on this machine (${def.origin} definition)`);
8065
- const expanded = expandServerEnv({ command: def.command, args: def.args, env: def.env }, this.deps.env);
8236
+ if (!approved) return skip(`its ${http ? "URL and headers are" : "command is"} not approved on this machine (${def.origin} definition)`);
8237
+ const expanded = expandServerEnv({ command: http ? def.url : def.command, args: def.args, env: def.env }, this.deps.env);
8066
8238
  if (!expanded.ok) return skip(`set ${expanded.missing.join(", ")} in your environment`);
8239
+ if (http && !isSafeMcpUrl(expanded.command)) return skip("its URL must be https (or localhost)");
8067
8240
  this.servers.set(name, { name, def, status: "connecting" });
8068
8241
  let transport;
8069
8242
  try {
8070
8243
  let session2;
8071
8244
  let closeReason = null;
8072
- transport = (this.deps.start ?? startStdioTransport)({
8073
- command: expanded.command,
8074
- args: expanded.args,
8075
- env: expanded.env,
8076
- cwd: this.deps.cwd,
8077
- onMessage: (m2) => void session2.handleMessage(m2),
8078
- onClose: (reason) => {
8079
- closeReason = reason;
8080
- session2.close(reason);
8081
- const cur = this.servers.get(name);
8082
- if (cur?.status === "ready") this.servers.set(name, { name, def, status: "failed", reason: `server ${reason}` });
8083
- }
8084
- });
8085
- const t = transport;
8245
+ const onClose = (reason) => {
8246
+ closeReason = reason;
8247
+ session2.close(reason);
8248
+ const cur = this.servers.get(name);
8249
+ if (cur?.status === "ready") this.servers.set(name, { name, def, status: "failed", reason: `server ${reason}` });
8250
+ };
8251
+ let send;
8252
+ if (http) {
8253
+ const t = createHttpTransport({
8254
+ url: expanded.command,
8255
+ headers: expanded.env,
8256
+ fetch: this.deps.fetch ?? ((url, init) => fetch(url, { ...init, redirect: "error" })),
8257
+ protocolVersion: MCP_PROTOCOL_VERSION,
8258
+ onMessage: (m2) => void session2.handleMessage(m2),
8259
+ onClose
8260
+ });
8261
+ transport = t;
8262
+ send = (m2) => t.send(m2);
8263
+ } else {
8264
+ const t = (this.deps.start ?? startStdioTransport)({
8265
+ command: expanded.command,
8266
+ args: expanded.args,
8267
+ env: expanded.env,
8268
+ cwd: this.deps.cwd,
8269
+ onMessage: (m2) => void session2.handleMessage(m2),
8270
+ onClose
8271
+ });
8272
+ transport = t;
8273
+ send = (m2) => t.send(m2);
8274
+ }
8086
8275
  session2 = createRpcSession({
8087
- send: (m2) => t.send(m2),
8276
+ send,
8088
8277
  setTimer: (fn, ms) => setTimeout(fn, ms),
8089
8278
  clearTimer: (h) => clearTimeout(h)
8090
8279
  });
@@ -8208,10 +8397,10 @@ var init_mcp_manager = __esm({
8208
8397
  async function runApproveMcp(deps) {
8209
8398
  const { defs, statusLines } = await loadMcpDefs(deps);
8210
8399
  if (!deps.quiet) for (const l of statusLines) deps.log(l);
8211
- const stdio = defs.filter((d) => d.transport === "stdio");
8400
+ const stdio = defs;
8212
8401
  const todo = [];
8213
8402
  for (const d of stdio) {
8214
- const key = { command: d.command, args: d.args, envNames: approvalEnv(d.env) };
8403
+ const key = approvalKey(d);
8215
8404
  if (await deps.trust.stdioStatus(key) === "unapproved") todo.push(d);
8216
8405
  }
8217
8406
  if (todo.length === 0) {
@@ -8226,13 +8415,18 @@ async function runApproveMcp(deps) {
8226
8415
  ${todo.length} imported MCP server(s) want to run a command on this machine. Nothing spawns until you approve it (you can also run \`spacecode approve-mcp\` later).`);
8227
8416
  let approved = 0;
8228
8417
  for (const d of todo) {
8229
- deps.log(`
8418
+ deps.log(
8419
+ d.transport === "http" ? `
8420
+ MCP server ${show(d.name)} (${d.origin} definition) wants to connect from this machine to:
8421
+ ${show(d.url)}
8422
+ headers it will send (NAME=where the value comes from): ${approvalEnv(d.env).join(", ") || "(none)"}` : `
8230
8423
  MCP server ${show(d.name)} (${d.origin} definition) wants to run on this machine:
8231
8424
  ${show(d.command)} ${d.args.map(show).join(" ")}
8232
- environment it will receive (NAME=where the value comes from): ${approvalEnv(d.env).join(", ") || "(none)"}`);
8233
- const answer = (await deps.ask("Approve this command? [y/N] ") ?? "").trim().toLowerCase();
8425
+ environment it will receive (NAME=where the value comes from): ${approvalEnv(d.env).join(", ") || "(none)"}`
8426
+ );
8427
+ const answer = (await deps.ask("Approve? [y/N] ") ?? "").trim().toLowerCase();
8234
8428
  if (answer === "y" || answer === "yes") {
8235
- await deps.trust.approveStdio({ command: d.command, args: d.args, envNames: approvalEnv(d.env) });
8429
+ await deps.trust.approveStdio(approvalKey(d));
8236
8430
  deps.log(" approved");
8237
8431
  approved++;
8238
8432
  } else deps.log(" not approved");
@@ -35864,7 +36058,7 @@ var require_websocket_server = __commonJS({
35864
36058
  var keyRegex = /^[+/0-9A-Za-z]{22}==$/;
35865
36059
  var RUNNING = 0;
35866
36060
  var CLOSING = 1;
35867
- var CLOSED = 2;
36061
+ var CLOSED2 = 2;
35868
36062
  var WebSocketServer2 = class extends EventEmitter3 {
35869
36063
  /**
35870
36064
  * Create a `WebSocketServer` instance.
@@ -35993,7 +36187,7 @@ var require_websocket_server = __commonJS({
35993
36187
  * @public
35994
36188
  */
35995
36189
  close(cb) {
35996
- if (this._state === CLOSED) {
36190
+ if (this._state === CLOSED2) {
35997
36191
  if (cb) {
35998
36192
  this.once("close", () => {
35999
36193
  cb(new Error("The server is not running"));
@@ -36217,7 +36411,7 @@ var require_websocket_server = __commonJS({
36217
36411
  };
36218
36412
  }
36219
36413
  function emitClose(server) {
36220
- server._state = CLOSED;
36414
+ server._state = CLOSED2;
36221
36415
  server.emit("close");
36222
36416
  }
36223
36417
  function socketOnError() {
@@ -104173,7 +104367,7 @@ Skills are loaded on demand with the skill tool; bodies are not in the prompt. /
104173
104367
  (a) => a.kind === "skill" && (a.name === name || a.name.startsWith(`${name}.`) && a.description.startsWith(SIBLING_PREFIX))
104174
104368
  );
104175
104369
  for (const a of assets) await deps.client.delete("skill", a.name);
104176
- return `Deleted skill "${name}" and ${assets.length - 1} file(s) from the profile. Re-running import-claude would import it again.`;
104370
+ return `Deleted skill "${name}" and ${assets.length - 1} file(s) from the profile. Re-running "spacecode import claude" would import it again.`;
104177
104371
  }
104178
104372
  return "Usage: /skills | /skills show <name> [file] | /skills delete <name>";
104179
104373
  }
@@ -105893,7 +106087,13 @@ function clearOnResize(stdout) {
105893
106087
 
105894
106088
  // src/index.tsx
105895
106089
  var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
105896
- if (process.argv[2] === "import-claude") {
106090
+ if (process.argv[2] === "import" || process.argv[2] === "import-claude") {
106091
+ const importArgs = process.argv[2] === "import" ? process.argv.slice(4) : process.argv.slice(3);
106092
+ const importAgent = process.argv[2] === "import" ? process.argv[3] : "claude";
106093
+ if (importAgent !== "claude") {
106094
+ console.error(`Unknown agent "${importAgent ?? ""}". Supported: claude`);
106095
+ process.exit(2);
106096
+ }
105897
106097
  const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
105898
106098
  const { readFile: readFile12, readdir: readdir6 } = await import("node:fs/promises");
105899
106099
  const { execFile: execFile6 } = await import("node:child_process");
@@ -105901,7 +106101,7 @@ if (process.argv[2] === "import-claude") {
105901
106101
  execFile6("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
105902
106102
  });
105903
106103
  const code = await runImportClaude2({
105904
- argv: process.argv.slice(2),
106104
+ argv: ["import-claude", ...importArgs],
105905
106105
  env: process.env,
105906
106106
  fs: { readFile: (p) => readFile12(p, "utf-8"), readdir: (p) => readdir6(p) },
105907
106107
  readFile: (p) => readFile12(p, "utf-8"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacecode",
3
- "version": "2.12.1",
3
+ "version": "2.12.3",
4
4
  "description": "SpaceCode - launch cloud dev environments with AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",