spacecode 2.5.1 → 2.5.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/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/repl.mjs +496 -27
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -4580,9 +4580,22 @@ function guardHost(inner, opts) {
|
|
|
4580
4580
|
await log(kind, subject, d, false);
|
|
4581
4581
|
throw new PolicyViolation(d.rule, `Blocked by policy (${d.rule}): ${d.reason}`);
|
|
4582
4582
|
}
|
|
4583
|
+
const verdict = opts.rules?.evaluate(kind === "exec" ? "bash" : kind, subject);
|
|
4584
|
+
const ruleTag = verdict?.rule ? `${verdict.rule} [${verdict.scope}]` : "";
|
|
4585
|
+
if (verdict?.effect === "deny") {
|
|
4586
|
+
d = { action: "deny", rule: "rule-deny", reason: `denied by imported rule ${ruleTag}` };
|
|
4587
|
+
await log(kind, subject, d, false);
|
|
4588
|
+
throw new PolicyViolation(d.rule, `Blocked by policy (${d.rule}): ${d.reason}`);
|
|
4589
|
+
}
|
|
4583
4590
|
if (kind !== "exec" && opts.permissions?.mode === "default") {
|
|
4584
4591
|
d = { action: "confirm", rule: "confirm-edit", reason: "modifies a file" };
|
|
4585
4592
|
}
|
|
4593
|
+
if (verdict?.effect === "ask") {
|
|
4594
|
+
d = { action: "confirm", rule: "rule-ask", reason: `imported rule ${ruleTag} asks first${opts.confirm ? "" : "; no way to ask (headless), so refused"}` };
|
|
4595
|
+
} else if (verdict?.effect === "allow" && d.action === "confirm") {
|
|
4596
|
+
await log(kind, subject, { action: "allow", rule: "rule-allow", reason: `allowed by imported rule ${ruleTag} (would be ${d.rule})` }, true);
|
|
4597
|
+
return;
|
|
4598
|
+
}
|
|
4586
4599
|
if (d.action === "confirm" && kind === "exec" && opts.permissions?.allowsCommand(subject)) {
|
|
4587
4600
|
await log(kind, subject, { action: "allow", rule: "allow-session", reason: "approved earlier this session" }, true);
|
|
4588
4601
|
return;
|
|
@@ -4606,7 +4619,15 @@ function guardHost(inner, opts) {
|
|
|
4606
4619
|
return {
|
|
4607
4620
|
kind: inner.kind,
|
|
4608
4621
|
files: {
|
|
4609
|
-
readFile: (p) =>
|
|
4622
|
+
readFile: async (p) => {
|
|
4623
|
+
const v2 = opts.rules?.evaluate("read_file", p.replace(/^\.\//, ""));
|
|
4624
|
+
if (v2?.effect === "deny") {
|
|
4625
|
+
const d = { action: "deny", rule: "rule-deny", reason: `denied by imported rule ${v2.rule} [${v2.scope}]` };
|
|
4626
|
+
await log("read_file", p, d, false);
|
|
4627
|
+
throw new PolicyViolation(d.rule, `Blocked by policy (${d.rule}): ${d.reason}`);
|
|
4628
|
+
}
|
|
4629
|
+
return inner.files.readFile(p);
|
|
4630
|
+
},
|
|
4610
4631
|
listDirectory: (p) => inner.files.listDirectory(p),
|
|
4611
4632
|
writeFile: async (p, c) => {
|
|
4612
4633
|
await gate("write_file", p, allow, { oldText: "", newText: c });
|
|
@@ -5413,6 +5434,291 @@ var init_instructions_tool = __esm({
|
|
|
5413
5434
|
}
|
|
5414
5435
|
});
|
|
5415
5436
|
|
|
5437
|
+
// ../agent/src/import/trust.ts
|
|
5438
|
+
var TrustError, TrustStore;
|
|
5439
|
+
var init_trust = __esm({
|
|
5440
|
+
"../agent/src/import/trust.ts"() {
|
|
5441
|
+
"use strict";
|
|
5442
|
+
TrustError = class extends Error {
|
|
5443
|
+
};
|
|
5444
|
+
TrustStore = class {
|
|
5445
|
+
fs;
|
|
5446
|
+
storePath;
|
|
5447
|
+
hashFn;
|
|
5448
|
+
// session decisions are kept in memory only: key = `${realPath}|${contentHash}`
|
|
5449
|
+
sessionMap = /* @__PURE__ */ new Set();
|
|
5450
|
+
constructor(deps) {
|
|
5451
|
+
this.fs = deps.fs;
|
|
5452
|
+
this.storePath = deps.path;
|
|
5453
|
+
this.hashFn = deps.hash;
|
|
5454
|
+
}
|
|
5455
|
+
async repoStatus(repoPath, files) {
|
|
5456
|
+
const realPath = await this.fs.realPath(repoPath);
|
|
5457
|
+
const contentHash = this.computeContentHash(files);
|
|
5458
|
+
const store = await this.readStore();
|
|
5459
|
+
const entry = store.repos[realPath];
|
|
5460
|
+
if (entry?.decision === "never") return "never";
|
|
5461
|
+
if (this.sessionMap.has(`${realPath}|${contentHash}`)) return "session";
|
|
5462
|
+
if (entry?.decision === "trusted" && entry.contentHash === contentHash) return "trusted";
|
|
5463
|
+
return "untrusted";
|
|
5464
|
+
}
|
|
5465
|
+
async setRepo(repoPath, files, decision) {
|
|
5466
|
+
const realPath = await this.fs.realPath(repoPath);
|
|
5467
|
+
const contentHash = this.computeContentHash(files);
|
|
5468
|
+
if (decision === "session") {
|
|
5469
|
+
this.sessionMap.add(`${realPath}|${contentHash}`);
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
5472
|
+
await this.writeStore({ repos: { [realPath]: { decision, contentHash } }, stdio: {} });
|
|
5473
|
+
}
|
|
5474
|
+
async stdioStatus(def) {
|
|
5475
|
+
const store = await this.readStore();
|
|
5476
|
+
return store.stdio[this.computeStdioHash(def)] ? "approved" : "unapproved";
|
|
5477
|
+
}
|
|
5478
|
+
async approveStdio(def) {
|
|
5479
|
+
await this.writeStore({ repos: {}, stdio: { [this.computeStdioHash(def)]: true } });
|
|
5480
|
+
}
|
|
5481
|
+
computeContentHash(files) {
|
|
5482
|
+
const sorted = {};
|
|
5483
|
+
for (const key of Object.keys(files).sort()) sorted[key] = files[key];
|
|
5484
|
+
return this.hashFn(JSON.stringify(sorted));
|
|
5485
|
+
}
|
|
5486
|
+
computeStdioHash(def) {
|
|
5487
|
+
return this.hashFn(JSON.stringify([def.command, def.args, [...def.envNames].sort()]));
|
|
5488
|
+
}
|
|
5489
|
+
async readStore() {
|
|
5490
|
+
const text = await this.fs.readText(this.storePath);
|
|
5491
|
+
if (text === null) return { version: 1, repos: {}, stdio: {} };
|
|
5492
|
+
let parsed;
|
|
5493
|
+
try {
|
|
5494
|
+
parsed = JSON.parse(text);
|
|
5495
|
+
} catch (e) {
|
|
5496
|
+
throw new TrustError(`Failed to parse trust store at ${this.storePath}: ${String(e)}`);
|
|
5497
|
+
}
|
|
5498
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
5499
|
+
throw new TrustError(`Malformed trust store at ${this.storePath}`);
|
|
5500
|
+
}
|
|
5501
|
+
const obj = parsed;
|
|
5502
|
+
if (obj.version !== 1) {
|
|
5503
|
+
throw new TrustError(`Unsupported trust store version at ${this.storePath}`);
|
|
5504
|
+
}
|
|
5505
|
+
const repos = obj.repos ?? {};
|
|
5506
|
+
const stdio = obj.stdio ?? {};
|
|
5507
|
+
if (typeof repos !== "object" || repos === null) {
|
|
5508
|
+
throw new TrustError(`Malformed repos section in trust store at ${this.storePath}`);
|
|
5509
|
+
}
|
|
5510
|
+
if (typeof stdio !== "object" || stdio === null) {
|
|
5511
|
+
throw new TrustError(`Malformed stdio section in trust store at ${this.storePath}`);
|
|
5512
|
+
}
|
|
5513
|
+
return { version: 1, repos, stdio };
|
|
5514
|
+
}
|
|
5515
|
+
/** Re-reads immediately before the write and merges, so concurrent writers keep each other's entries. */
|
|
5516
|
+
async writeStore(delta) {
|
|
5517
|
+
const current = await this.readStore();
|
|
5518
|
+
const repos = { ...current.repos };
|
|
5519
|
+
for (const [key, next] of Object.entries(delta.repos)) {
|
|
5520
|
+
if (current.repos[key]?.decision === "never" && next.decision === "trusted") continue;
|
|
5521
|
+
repos[key] = next;
|
|
5522
|
+
}
|
|
5523
|
+
const store = { version: 1, repos, stdio: { ...current.stdio, ...delta.stdio } };
|
|
5524
|
+
await this.fs.writeTextAtomic(this.storePath, JSON.stringify(store, null, 2));
|
|
5525
|
+
}
|
|
5526
|
+
};
|
|
5527
|
+
}
|
|
5528
|
+
});
|
|
5529
|
+
|
|
5530
|
+
// ../agent/src/import/imports-config.ts
|
|
5531
|
+
function allOffConfig() {
|
|
5532
|
+
return {
|
|
5533
|
+
categories: {
|
|
5534
|
+
instructions: false,
|
|
5535
|
+
memory: false,
|
|
5536
|
+
permissions: false,
|
|
5537
|
+
commands: false,
|
|
5538
|
+
mcp: false,
|
|
5539
|
+
skills: false,
|
|
5540
|
+
agents: false
|
|
5541
|
+
},
|
|
5542
|
+
models: {}
|
|
5543
|
+
};
|
|
5544
|
+
}
|
|
5545
|
+
function isPlainObject(value) {
|
|
5546
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5547
|
+
}
|
|
5548
|
+
function parseImportsConfig(text, path12) {
|
|
5549
|
+
if (text === null) return { config: allOffConfig() };
|
|
5550
|
+
const fail = (reason) => ({ config: allOffConfig(), error: `${path12}: ${reason}` });
|
|
5551
|
+
let parsed;
|
|
5552
|
+
try {
|
|
5553
|
+
parsed = JSON.parse(text);
|
|
5554
|
+
} catch {
|
|
5555
|
+
return fail("invalid JSON");
|
|
5556
|
+
}
|
|
5557
|
+
if (!isPlainObject(parsed)) return fail("top-level JSON must be an object");
|
|
5558
|
+
if (parsed.version !== 1) return fail(`unsupported version ${String(parsed.version)}`);
|
|
5559
|
+
const config = allOffConfig();
|
|
5560
|
+
if (parsed.categories !== void 0) {
|
|
5561
|
+
if (!isPlainObject(parsed.categories)) return fail("categories must be an object");
|
|
5562
|
+
for (const [key, value] of Object.entries(parsed.categories)) {
|
|
5563
|
+
if (!IMPORT_CATEGORIES.includes(key)) return fail(`unknown category key "${key}"`);
|
|
5564
|
+
if (typeof value !== "boolean") return fail(`category "${key}" must be a boolean`);
|
|
5565
|
+
config.categories[key] = value;
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
if (parsed.models !== void 0) {
|
|
5569
|
+
if (!isPlainObject(parsed.models)) return fail("models must be an object");
|
|
5570
|
+
for (const key of MODEL_KEYS) {
|
|
5571
|
+
const value = parsed.models[key];
|
|
5572
|
+
if (value === void 0) continue;
|
|
5573
|
+
if (typeof value !== "string" || value.trim() === "") return fail(`model "${key}" must be a non-empty string`);
|
|
5574
|
+
config.models[key] = value;
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
return { config };
|
|
5578
|
+
}
|
|
5579
|
+
var IMPORT_CATEGORIES, MODEL_KEYS;
|
|
5580
|
+
var init_imports_config = __esm({
|
|
5581
|
+
"../agent/src/import/imports-config.ts"() {
|
|
5582
|
+
"use strict";
|
|
5583
|
+
IMPORT_CATEGORIES = [
|
|
5584
|
+
"instructions",
|
|
5585
|
+
"memory",
|
|
5586
|
+
"permissions",
|
|
5587
|
+
"commands",
|
|
5588
|
+
"mcp",
|
|
5589
|
+
"skills",
|
|
5590
|
+
"agents"
|
|
5591
|
+
];
|
|
5592
|
+
MODEL_KEYS = ["main", "agent", "skill", "router"];
|
|
5593
|
+
}
|
|
5594
|
+
});
|
|
5595
|
+
|
|
5596
|
+
// ../agent/src/import/permission-rules.ts
|
|
5597
|
+
function splitCommand(cmd) {
|
|
5598
|
+
const segments2 = [];
|
|
5599
|
+
let cur = "";
|
|
5600
|
+
let quote = null;
|
|
5601
|
+
let compound = false;
|
|
5602
|
+
const flush = () => {
|
|
5603
|
+
const s = cur.trim();
|
|
5604
|
+
if (s) segments2.push(s);
|
|
5605
|
+
cur = "";
|
|
5606
|
+
};
|
|
5607
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
5608
|
+
const ch = cmd[i];
|
|
5609
|
+
if (quote) {
|
|
5610
|
+
cur += ch;
|
|
5611
|
+
if (ch === quote) quote = null;
|
|
5612
|
+
} else if (ch === '"' || ch === "'") {
|
|
5613
|
+
quote = ch;
|
|
5614
|
+
cur += ch;
|
|
5615
|
+
} else if (";&|\n`()<>".includes(ch) || ch === "$" && cmd[i + 1] === "(") {
|
|
5616
|
+
compound = true;
|
|
5617
|
+
flush();
|
|
5618
|
+
} else {
|
|
5619
|
+
cur += ch;
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
if (quote) return { compound: true, segments: [cmd.trim()] };
|
|
5623
|
+
flush();
|
|
5624
|
+
return compound ? { compound, segments: segments2 } : { compound, segments: [cmd.trim()] };
|
|
5625
|
+
}
|
|
5626
|
+
function matchesBash(pattern, cmd, effect) {
|
|
5627
|
+
const { compound, segments: segments2 } = splitCommand(cmd);
|
|
5628
|
+
if (compound && effect === "allow") return false;
|
|
5629
|
+
const prefix = pattern.endsWith(":*") ? pattern.slice(0, -2).trim() : null;
|
|
5630
|
+
const hit = (s) => prefix === null ? s === pattern.trim() : s === prefix || s.startsWith(prefix) && /\s/.test(s[prefix.length] ?? "");
|
|
5631
|
+
return segments2.some(hit);
|
|
5632
|
+
}
|
|
5633
|
+
function globToRegExp(glob) {
|
|
5634
|
+
let re3 = "";
|
|
5635
|
+
for (let i = 0; i < glob.length; i++) {
|
|
5636
|
+
const ch = glob[i];
|
|
5637
|
+
if (ch === "*") {
|
|
5638
|
+
if (glob[i + 1] === "*") {
|
|
5639
|
+
re3 += ".*";
|
|
5640
|
+
i++;
|
|
5641
|
+
if (glob[i + 1] === "/") i++;
|
|
5642
|
+
} else re3 += "[^/]*";
|
|
5643
|
+
} else if (ch === "?") re3 += "[^/]";
|
|
5644
|
+
else re3 += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
5645
|
+
}
|
|
5646
|
+
return new RegExp("^" + re3 + "$");
|
|
5647
|
+
}
|
|
5648
|
+
function compile(text, effect, scope) {
|
|
5649
|
+
const m2 = /^([A-Za-z0-9_-]+)(?:\((.*)\))?$/s.exec(text.trim());
|
|
5650
|
+
if (!m2) return "malformed rule";
|
|
5651
|
+
const [, name, rawArg] = m2;
|
|
5652
|
+
const arg = rawArg === void 0 || rawArg.trim() === "" ? null : rawArg.trim();
|
|
5653
|
+
const tools = name.startsWith("mcp__") ? [name] : TOOL_MAP[name];
|
|
5654
|
+
if (!tools) return `unsupported tool ${name}`;
|
|
5655
|
+
if (arg === null) return { effect, scope, text, tools, arg, test: () => true };
|
|
5656
|
+
if (name === "Bash") return { effect, scope, text, tools, arg, test: (s, e) => matchesBash(arg, s, e) };
|
|
5657
|
+
if (name.startsWith("mcp__")) return "argument patterns are not supported for MCP tools";
|
|
5658
|
+
const re3 = globToRegExp(arg);
|
|
5659
|
+
return { effect, scope, text, tools, arg, test: (s) => re3.test(s) };
|
|
5660
|
+
}
|
|
5661
|
+
function buildRuleSet(sources) {
|
|
5662
|
+
const rules = [];
|
|
5663
|
+
const skipped = [];
|
|
5664
|
+
const counts = {};
|
|
5665
|
+
for (const s of ["user", "repo", "local"]) counts[s] = { allow: 0, deny: 0, ask: 0 };
|
|
5666
|
+
for (const src of sources) {
|
|
5667
|
+
for (const effect of ["deny", "ask", "allow"]) {
|
|
5668
|
+
for (const text of src[effect] ?? []) {
|
|
5669
|
+
const c = typeof text === "string" ? compile(text, effect, src.scope) : "malformed rule";
|
|
5670
|
+
if (typeof c === "string") skipped.push({ text: String(text), scope: src.scope, reason: c });
|
|
5671
|
+
else {
|
|
5672
|
+
rules.push(c);
|
|
5673
|
+
counts[src.scope][effect]++;
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
const frozen = Object.freeze(rules);
|
|
5679
|
+
return {
|
|
5680
|
+
skipped,
|
|
5681
|
+
counts,
|
|
5682
|
+
evaluate(tool, subject) {
|
|
5683
|
+
for (const effect of ["deny", "ask", "allow"]) {
|
|
5684
|
+
const r = frozen.find((c) => c.effect === effect && c.tools.includes(tool) && c.test(subject, effect));
|
|
5685
|
+
if (r) return { effect, rule: r.text, scope: r.scope };
|
|
5686
|
+
}
|
|
5687
|
+
return { effect: "none" };
|
|
5688
|
+
}
|
|
5689
|
+
};
|
|
5690
|
+
}
|
|
5691
|
+
function defaultModeFor(mode) {
|
|
5692
|
+
switch (mode) {
|
|
5693
|
+
case void 0:
|
|
5694
|
+
case "default":
|
|
5695
|
+
return { mode: "default" };
|
|
5696
|
+
case "acceptEdits":
|
|
5697
|
+
return { mode: "accept-edits" };
|
|
5698
|
+
case "plan":
|
|
5699
|
+
return { mode: "plan" };
|
|
5700
|
+
case "bypassPermissions":
|
|
5701
|
+
return { mode: "yolo" };
|
|
5702
|
+
default:
|
|
5703
|
+
return { mode: null, reason: `unmappable defaultMode "${mode}"` };
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
var TOOL_MAP;
|
|
5707
|
+
var init_permission_rules = __esm({
|
|
5708
|
+
"../agent/src/import/permission-rules.ts"() {
|
|
5709
|
+
"use strict";
|
|
5710
|
+
TOOL_MAP = {
|
|
5711
|
+
Bash: ["bash"],
|
|
5712
|
+
Read: ["read_file"],
|
|
5713
|
+
Edit: ["edit_file", "write_file"],
|
|
5714
|
+
Write: ["edit_file", "write_file"],
|
|
5715
|
+
MultiEdit: ["edit_file", "write_file"],
|
|
5716
|
+
Glob: ["list_directory"],
|
|
5717
|
+
Grep: ["search"]
|
|
5718
|
+
};
|
|
5719
|
+
}
|
|
5720
|
+
});
|
|
5721
|
+
|
|
5416
5722
|
// ../agent/src/index.ts
|
|
5417
5723
|
var init_src = __esm({
|
|
5418
5724
|
"../agent/src/index.ts"() {
|
|
@@ -5450,13 +5756,16 @@ var init_src = __esm({
|
|
|
5450
5756
|
init_coverage();
|
|
5451
5757
|
init_adapt_plan();
|
|
5452
5758
|
init_instructions_tool();
|
|
5759
|
+
init_trust();
|
|
5760
|
+
init_imports_config();
|
|
5761
|
+
init_permission_rules();
|
|
5453
5762
|
}
|
|
5454
5763
|
});
|
|
5455
5764
|
|
|
5456
5765
|
// src/api-key.ts
|
|
5457
5766
|
import { join } from "node:path";
|
|
5458
5767
|
async function resolveApiKey(deps) {
|
|
5459
|
-
const { env: env3, readFile:
|
|
5768
|
+
const { env: env3, readFile: readFile7, homedir: homedir3 } = deps;
|
|
5460
5769
|
for (const name of ["SPACECODE_API_KEY", "ANTHROPIC_API_KEY"]) {
|
|
5461
5770
|
const value = env3[name]?.trim();
|
|
5462
5771
|
if (value) return value;
|
|
@@ -5465,7 +5774,7 @@ async function resolveApiKey(deps) {
|
|
|
5465
5774
|
const file = join(dir, "auth.json");
|
|
5466
5775
|
let raw;
|
|
5467
5776
|
try {
|
|
5468
|
-
raw = await
|
|
5777
|
+
raw = await readFile7(file);
|
|
5469
5778
|
} catch (err) {
|
|
5470
5779
|
if (err.code === "ENOENT") return null;
|
|
5471
5780
|
throw new Error(`Cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -5518,10 +5827,17 @@ function fileCache(deps) {
|
|
|
5518
5827
|
};
|
|
5519
5828
|
}
|
|
5520
5829
|
async function prepareProfile(deps) {
|
|
5830
|
+
let state;
|
|
5521
5831
|
try {
|
|
5522
|
-
|
|
5832
|
+
state = await prepareProfileUnsafe(deps);
|
|
5523
5833
|
} catch (err) {
|
|
5524
|
-
|
|
5834
|
+
state = EMPTY([`profile: not loaded (unexpected error: ${err instanceof Error ? err.message : String(err)})`]);
|
|
5835
|
+
}
|
|
5836
|
+
if (!deps.importGate) return state;
|
|
5837
|
+
try {
|
|
5838
|
+
return { ...state, statusLines: [...state.statusLines, ...await deps.importGate()] };
|
|
5839
|
+
} catch (err) {
|
|
5840
|
+
return { ...state, statusLines: [...state.statusLines, `imports: gate failed (${err instanceof Error ? err.message : String(err)}); nothing from repos loads`] };
|
|
5525
5841
|
}
|
|
5526
5842
|
}
|
|
5527
5843
|
async function prepareProfileUnsafe(deps) {
|
|
@@ -32203,7 +32519,7 @@ var require_websocket = __commonJS({
|
|
|
32203
32519
|
var http = __require("http");
|
|
32204
32520
|
var net = __require("net");
|
|
32205
32521
|
var tls = __require("tls");
|
|
32206
|
-
var { randomBytes: randomBytes2, createHash } = __require("crypto");
|
|
32522
|
+
var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
|
|
32207
32523
|
var { Duplex, Readable } = __require("stream");
|
|
32208
32524
|
var { URL: URL2 } = __require("url");
|
|
32209
32525
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -32871,7 +33187,7 @@ var require_websocket = __commonJS({
|
|
|
32871
33187
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
32872
33188
|
return;
|
|
32873
33189
|
}
|
|
32874
|
-
const digest =
|
|
33190
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
32875
33191
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
32876
33192
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
32877
33193
|
return;
|
|
@@ -33240,7 +33556,7 @@ var require_websocket_server = __commonJS({
|
|
|
33240
33556
|
var EventEmitter3 = __require("events");
|
|
33241
33557
|
var http = __require("http");
|
|
33242
33558
|
var { Duplex } = __require("stream");
|
|
33243
|
-
var { createHash } = __require("crypto");
|
|
33559
|
+
var { createHash: createHash2 } = __require("crypto");
|
|
33244
33560
|
var extension2 = require_extension();
|
|
33245
33561
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
33246
33562
|
var subprotocol2 = require_subprotocol();
|
|
@@ -33547,7 +33863,7 @@ var require_websocket_server = __commonJS({
|
|
|
33547
33863
|
);
|
|
33548
33864
|
}
|
|
33549
33865
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
33550
|
-
const digest =
|
|
33866
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
33551
33867
|
const headers = [
|
|
33552
33868
|
"HTTP/1.1 101 Switching Protocols",
|
|
33553
33869
|
"Upgrade: websocket",
|
|
@@ -34487,7 +34803,7 @@ var init_source = __esm({
|
|
|
34487
34803
|
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
34488
34804
|
};
|
|
34489
34805
|
chalkFactory = (options) => {
|
|
34490
|
-
const chalk2 = (...
|
|
34806
|
+
const chalk2 = (...strings2) => strings2.join(" ");
|
|
34491
34807
|
applyOptions(chalk2, options);
|
|
34492
34808
|
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
34493
34809
|
return chalk2;
|
|
@@ -40921,7 +41237,7 @@ var require_core = __commonJS({
|
|
|
40921
41237
|
return match && match.index === 0;
|
|
40922
41238
|
}
|
|
40923
41239
|
var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
|
|
40924
|
-
function
|
|
41240
|
+
function join7(regexps, separator = "|") {
|
|
40925
41241
|
let numCaptures = 0;
|
|
40926
41242
|
return regexps.map((regex2) => {
|
|
40927
41243
|
numCaptures += 1;
|
|
@@ -41225,7 +41541,7 @@ var require_core = __commonJS({
|
|
|
41225
41541
|
this.exec = () => null;
|
|
41226
41542
|
}
|
|
41227
41543
|
const terminators = this.regexes.map((el) => el[1]);
|
|
41228
|
-
this.matcherRe = langRe(
|
|
41544
|
+
this.matcherRe = langRe(join7(terminators), true);
|
|
41229
41545
|
this.lastIndex = 0;
|
|
41230
41546
|
}
|
|
41231
41547
|
/** @param {string} s */
|
|
@@ -84152,12 +84468,12 @@ var require_source = __commonJS({
|
|
|
84152
84468
|
return openAll + string + closeAll;
|
|
84153
84469
|
};
|
|
84154
84470
|
var template;
|
|
84155
|
-
var chalkTag = (chalk3, ...
|
|
84156
|
-
const [firstString] =
|
|
84471
|
+
var chalkTag = (chalk3, ...strings2) => {
|
|
84472
|
+
const [firstString] = strings2;
|
|
84157
84473
|
if (!isArray(firstString) || !isArray(firstString.raw)) {
|
|
84158
|
-
return
|
|
84474
|
+
return strings2.join(" ");
|
|
84159
84475
|
}
|
|
84160
|
-
const arguments_ =
|
|
84476
|
+
const arguments_ = strings2.slice(1);
|
|
84161
84477
|
const parts = [firstString.raw[0]];
|
|
84162
84478
|
for (let i = 1; i < firstString.length; i++) {
|
|
84163
84479
|
parts.push(
|
|
@@ -100505,6 +100821,7 @@ function createLocalToolset(opts) {
|
|
|
100505
100821
|
audit: new FileAuditLog(auditFile),
|
|
100506
100822
|
mode: opts.yolo ? "yolo" : "enforce",
|
|
100507
100823
|
permissions: opts.permissions,
|
|
100824
|
+
...opts.rules ? { rules: opts.rules } : {},
|
|
100508
100825
|
confirm: opts.confirm
|
|
100509
100826
|
});
|
|
100510
100827
|
const { handlers: githubHandlers } = createGitHubTools((command) => host.exec(command));
|
|
@@ -100870,9 +101187,149 @@ var init_node_mentions = __esm({
|
|
|
100870
101187
|
}
|
|
100871
101188
|
});
|
|
100872
101189
|
|
|
101190
|
+
// src/import-gate.ts
|
|
101191
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
101192
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, realpath as realpath2, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
101193
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
101194
|
+
async function readOrNull(path12) {
|
|
101195
|
+
try {
|
|
101196
|
+
return await readFile4(path12, "utf8");
|
|
101197
|
+
} catch (err) {
|
|
101198
|
+
if (err.code === "ENOENT") return null;
|
|
101199
|
+
throw err;
|
|
101200
|
+
}
|
|
101201
|
+
}
|
|
101202
|
+
async function writeTextAtomic(path12, text) {
|
|
101203
|
+
await mkdir3(dirname3(path12), { recursive: true });
|
|
101204
|
+
const tmp = `${path12}.${process.pid}.${randomUUID()}.tmp`;
|
|
101205
|
+
await writeFile3(tmp, text, { mode: 384 });
|
|
101206
|
+
await chmod3(tmp, 384);
|
|
101207
|
+
await rename3(tmp, path12);
|
|
101208
|
+
}
|
|
101209
|
+
function nodeTrustFs() {
|
|
101210
|
+
return { readText: readOrNull, writeTextAtomic, realPath: (p) => realpath2(p) };
|
|
101211
|
+
}
|
|
101212
|
+
function createNodeTrustStore(configDir) {
|
|
101213
|
+
return new TrustStore({ fs: nodeTrustFs(), path: join4(configDir, "trust.json"), hash: sha256 });
|
|
101214
|
+
}
|
|
101215
|
+
async function importGateStatus(deps) {
|
|
101216
|
+
const lines2 = [];
|
|
101217
|
+
const configPath = join4(deps.configDir, "imports.json");
|
|
101218
|
+
try {
|
|
101219
|
+
const loaded = parseImportsConfig(await readOrNull(configPath), configPath);
|
|
101220
|
+
if (loaded.error) lines2.push(`imports: ${loaded.error} (every category off)`);
|
|
101221
|
+
else {
|
|
101222
|
+
const on = IMPORT_CATEGORIES.filter((c) => loaded.config.categories[c]);
|
|
101223
|
+
lines2.push(on.length ? `imports: on for ${on.join(", ")}` : "imports: every category off (default)");
|
|
101224
|
+
}
|
|
101225
|
+
} catch (err) {
|
|
101226
|
+
lines2.push(`imports: not read (${err instanceof Error ? err.message : String(err)}); every category off`);
|
|
101227
|
+
}
|
|
101228
|
+
try {
|
|
101229
|
+
const files = {};
|
|
101230
|
+
for (const name of REPO_SOURCE_FILES) {
|
|
101231
|
+
const text = await readOrNull(join4(deps.cwd, name));
|
|
101232
|
+
if (text !== null) files[name] = text;
|
|
101233
|
+
}
|
|
101234
|
+
const found = Object.keys(files);
|
|
101235
|
+
if (found.length === 0) return lines2;
|
|
101236
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
|
|
101237
|
+
const names = found.join(", ");
|
|
101238
|
+
if (status === "trusted" || status === "session") lines2.push(`repo sources: ${names} (trusted${status === "session" ? " for this session" : ""})`);
|
|
101239
|
+
else if (status === "never") lines2.push(`repo sources: ${names} NOT loaded (marked never trust)`);
|
|
101240
|
+
else lines2.push(`repo sources: ${names} NOT loaded (untrusted${deps.interactive ? "" : ", non-interactive run"})`);
|
|
101241
|
+
} catch (err) {
|
|
101242
|
+
lines2.push(`repo sources: NOT loaded (trust check failed: ${err instanceof Error ? err.message : String(err)})`);
|
|
101243
|
+
}
|
|
101244
|
+
return lines2;
|
|
101245
|
+
}
|
|
101246
|
+
var REPO_SOURCE_FILES, sha256;
|
|
101247
|
+
var init_import_gate = __esm({
|
|
101248
|
+
"src/import-gate.ts"() {
|
|
101249
|
+
"use strict";
|
|
101250
|
+
init_src();
|
|
101251
|
+
REPO_SOURCE_FILES = [".mcp.json", ".claude/settings.json", ".claude/settings.local.json", "CLAUDE.local.md"];
|
|
101252
|
+
sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
101253
|
+
}
|
|
101254
|
+
});
|
|
101255
|
+
|
|
101256
|
+
// src/permission-rules-loader.ts
|
|
101257
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
101258
|
+
import { join as join5 } from "node:path";
|
|
101259
|
+
async function readOrNull2(path12) {
|
|
101260
|
+
try {
|
|
101261
|
+
return await readFile5(path12, "utf8");
|
|
101262
|
+
} catch (err) {
|
|
101263
|
+
if (err.code === "ENOENT") return null;
|
|
101264
|
+
throw err;
|
|
101265
|
+
}
|
|
101266
|
+
}
|
|
101267
|
+
async function loadPermissionRules(deps) {
|
|
101268
|
+
const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
|
|
101269
|
+
const configPath = join5(deps.configDir, "imports.json");
|
|
101270
|
+
try {
|
|
101271
|
+
const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
|
|
101272
|
+
if (loaded.error) return none("permission rules: off (imports.json unreadable)");
|
|
101273
|
+
if (!loaded.config.categories.permissions) return none("permission rules: off (imports.json has permissions off)");
|
|
101274
|
+
} catch (err) {
|
|
101275
|
+
return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
101276
|
+
}
|
|
101277
|
+
const lines2 = [];
|
|
101278
|
+
const files = [{ scope: "user", path: join5(deps.homeDir, ".claude", "settings.json") }];
|
|
101279
|
+
try {
|
|
101280
|
+
const sources2 = {};
|
|
101281
|
+
for (const name of REPO_SOURCE_FILES) {
|
|
101282
|
+
const text = await readOrNull2(join5(deps.cwd, name));
|
|
101283
|
+
if (text !== null) sources2[name] = text;
|
|
101284
|
+
}
|
|
101285
|
+
if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
|
|
101286
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
|
|
101287
|
+
if (status === "trusted" || status === "session") {
|
|
101288
|
+
files.push({ scope: "repo", path: join5(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join5(deps.cwd, ".claude", "settings.local.json") });
|
|
101289
|
+
} else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
101290
|
+
}
|
|
101291
|
+
} catch (err) {
|
|
101292
|
+
lines2.push(`permission rules: repo settings NOT loaded (trust check failed: ${err instanceof Error ? err.message : String(err)})`);
|
|
101293
|
+
}
|
|
101294
|
+
const sources = [];
|
|
101295
|
+
let defaultMode;
|
|
101296
|
+
for (const f of files) {
|
|
101297
|
+
try {
|
|
101298
|
+
const text = await readOrNull2(f.path);
|
|
101299
|
+
if (text === null) continue;
|
|
101300
|
+
const parsed = JSON.parse(text);
|
|
101301
|
+
const perms = parsed?.permissions;
|
|
101302
|
+
if (!perms || typeof perms !== "object") continue;
|
|
101303
|
+
sources.push({ scope: f.scope, allow: strings(perms.allow), deny: strings(perms.deny), ask: strings(perms.ask) });
|
|
101304
|
+
if (typeof perms.defaultMode === "string") {
|
|
101305
|
+
const m2 = defaultModeFor(perms.defaultMode);
|
|
101306
|
+
if (m2.mode) defaultMode = m2.mode;
|
|
101307
|
+
else lines2.push(`permission rules: ${f.path}: ${m2.reason}`);
|
|
101308
|
+
}
|
|
101309
|
+
} catch (err) {
|
|
101310
|
+
lines2.push(`permission rules: ${f.path} NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101311
|
+
}
|
|
101312
|
+
}
|
|
101313
|
+
const rules = buildRuleSet(sources);
|
|
101314
|
+
const parts = ["user", "repo", "local"].filter((s) => sources.some((x2) => x2.scope === s)).map((s) => `${s} ${rules.counts[s].allow} allow/${rules.counts[s].deny} deny/${rules.counts[s].ask} ask`);
|
|
101315
|
+
lines2.unshift(`permission rules: ${parts.length ? parts.join(", ") : "none found"}${defaultMode ? `; default mode ${defaultMode}` : ""}`);
|
|
101316
|
+
for (const s of rules.skipped) lines2.push(`permission rules: skipped [${s.scope}] "${s.text}" (${s.reason})`);
|
|
101317
|
+
return { rules, defaultMode, statusLines: lines2 };
|
|
101318
|
+
}
|
|
101319
|
+
var strings;
|
|
101320
|
+
var init_permission_rules_loader = __esm({
|
|
101321
|
+
"src/permission-rules-loader.ts"() {
|
|
101322
|
+
"use strict";
|
|
101323
|
+
init_src();
|
|
101324
|
+
init_import_gate();
|
|
101325
|
+
strings = (v2) => Array.isArray(v2) ? v2.filter((x2) => typeof x2 === "string") : void 0;
|
|
101326
|
+
}
|
|
101327
|
+
});
|
|
101328
|
+
|
|
100873
101329
|
// src/useReplController.ts
|
|
100874
|
-
import { readFile as
|
|
101330
|
+
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
|
|
100875
101331
|
import { homedir as homedir2 } from "node:os";
|
|
101332
|
+
import { join as join6 } from "node:path";
|
|
100876
101333
|
import { execFile as execFile3 } from "node:child_process";
|
|
100877
101334
|
function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
|
|
100878
101335
|
const { exit } = use_app_default();
|
|
@@ -100914,6 +101371,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
100914
101371
|
const abortRef = (0, import_react30.useRef)(null);
|
|
100915
101372
|
const pendingRef = (0, import_react30.useRef)(null);
|
|
100916
101373
|
const toolsetRef = (0, import_react30.useRef)(null);
|
|
101374
|
+
const rulesRef = (0, import_react30.useRef)(void 0);
|
|
100917
101375
|
(0, import_react30.useEffect)(() => {
|
|
100918
101376
|
sessionTokensRef.current = sessionTokens;
|
|
100919
101377
|
}, [sessionTokens]);
|
|
@@ -100966,6 +101424,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
100966
101424
|
cwd: process.cwd(),
|
|
100967
101425
|
permissions,
|
|
100968
101426
|
onTodos: setTodos,
|
|
101427
|
+
...rulesRef.current ? { rules: rulesRef.current } : {},
|
|
100969
101428
|
...profileRef.current && profileRef.current.tools.length > 0 ? { extra: { tools: profileRef.current.tools, handlers: profileRef.current.handlers } } : {},
|
|
100970
101429
|
confirm: (req) => new Promise((resolve2) => {
|
|
100971
101430
|
const item = {
|
|
@@ -101165,7 +101624,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101165
101624
|
}
|
|
101166
101625
|
setIsRunning(true);
|
|
101167
101626
|
try {
|
|
101168
|
-
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) =>
|
|
101627
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile6(p, "utf-8"), homedir: homedir2 });
|
|
101169
101628
|
if (!apiKey2) {
|
|
101170
101629
|
notice(MISSING_KEY_MESSAGE);
|
|
101171
101630
|
return;
|
|
@@ -101187,7 +101646,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101187
101646
|
}
|
|
101188
101647
|
case "model": {
|
|
101189
101648
|
try {
|
|
101190
|
-
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) =>
|
|
101649
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile6(p, "utf-8"), homedir: homedir2 });
|
|
101191
101650
|
if (!apiKey2) {
|
|
101192
101651
|
notice(MISSING_KEY_MESSAGE);
|
|
101193
101652
|
return;
|
|
@@ -101229,7 +101688,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101229
101688
|
try {
|
|
101230
101689
|
apiKey = await resolveApiKey({
|
|
101231
101690
|
env: process.env,
|
|
101232
|
-
readFile: (p) =>
|
|
101691
|
+
readFile: (p) => readFile6(p, "utf-8"),
|
|
101233
101692
|
homedir: homedir2
|
|
101234
101693
|
});
|
|
101235
101694
|
} catch (err) {
|
|
@@ -101265,15 +101724,23 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101265
101724
|
try {
|
|
101266
101725
|
profilePromiseRef.current ??= prepareProfile({
|
|
101267
101726
|
env: process.env,
|
|
101268
|
-
readFile: (p) =>
|
|
101269
|
-
writeFile: (p, d) =>
|
|
101727
|
+
readFile: (p) => readFile6(p, "utf-8"),
|
|
101728
|
+
writeFile: (p, d) => writeFile4(p, d, { encoding: "utf-8", mode: 384 }),
|
|
101270
101729
|
mkdir: async (p) => {
|
|
101271
|
-
await
|
|
101730
|
+
await mkdir4(p, { recursive: true });
|
|
101272
101731
|
},
|
|
101273
101732
|
homedir: homedir2,
|
|
101274
101733
|
fetch,
|
|
101275
101734
|
cwd: process.cwd(),
|
|
101276
|
-
gitRoot: gitTopLevel
|
|
101735
|
+
gitRoot: gitTopLevel,
|
|
101736
|
+
importGate: async () => {
|
|
101737
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join6(homedir2(), ".spacecode");
|
|
101738
|
+
const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
|
|
101739
|
+
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
101740
|
+
rulesRef.current = loaded.rules;
|
|
101741
|
+
if (loaded.defaultMode && !yolo2) permissions.set(loaded.defaultMode);
|
|
101742
|
+
return [...gate, ...loaded.statusLines];
|
|
101743
|
+
}
|
|
101277
101744
|
});
|
|
101278
101745
|
profileRef.current = await profilePromiseRef.current;
|
|
101279
101746
|
const toolset = createToolset();
|
|
@@ -101400,6 +101867,8 @@ var init_useReplController = __esm({
|
|
|
101400
101867
|
init_node_mentions();
|
|
101401
101868
|
init_api_key();
|
|
101402
101869
|
init_profile_runtime();
|
|
101870
|
+
init_import_gate();
|
|
101871
|
+
init_permission_rules_loader();
|
|
101403
101872
|
init_slash_commands();
|
|
101404
101873
|
init_useEventAccumulator();
|
|
101405
101874
|
gitTopLevel = () => new Promise((resolve2) => {
|
|
@@ -101984,7 +102453,7 @@ function clearOnResize(stdout) {
|
|
|
101984
102453
|
var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
|
|
101985
102454
|
if (process.argv[2] === "import-claude") {
|
|
101986
102455
|
const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
|
|
101987
|
-
const { readFile:
|
|
102456
|
+
const { readFile: readFile7, readdir: readdir3 } = await import("node:fs/promises");
|
|
101988
102457
|
const { execFile: execFile4 } = await import("node:child_process");
|
|
101989
102458
|
const gitRoot = () => new Promise((resolve2) => {
|
|
101990
102459
|
execFile4("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
|
|
@@ -101992,8 +102461,8 @@ if (process.argv[2] === "import-claude") {
|
|
|
101992
102461
|
const code = await runImportClaude2({
|
|
101993
102462
|
argv: process.argv.slice(2),
|
|
101994
102463
|
env: process.env,
|
|
101995
|
-
fs: { readFile: (p) =>
|
|
101996
|
-
readFile: (p) =>
|
|
102464
|
+
fs: { readFile: (p) => readFile7(p, "utf-8"), readdir: (p) => readdir3(p) },
|
|
102465
|
+
readFile: (p) => readFile7(p, "utf-8"),
|
|
101997
102466
|
homedir: os5.homedir,
|
|
101998
102467
|
cwd: process.cwd(),
|
|
101999
102468
|
gitRoot,
|