cortad 0.3.1-rc.9 → 0.3.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/README.md +8 -5
- package/lib/app-port.mjs +9 -0
- package/lib/auto-reach.mjs +88 -0
- package/lib/backing.mjs +230 -0
- package/lib/calls.mjs +23 -0
- package/lib/cli.mjs +115 -30
- package/lib/contact-text.mjs +167 -0
- package/lib/data.mjs +16 -22
- package/lib/dissection-text.mjs +232 -0
- package/lib/document.mjs +75 -0
- package/lib/env-secrets.mjs +48 -0
- package/lib/failed-text.mjs +36 -0
- package/lib/fresh.mjs +33 -12
- package/lib/health.mjs +61 -0
- package/lib/held.mjs +67 -0
- package/lib/home.mjs +55 -17
- package/lib/hosted.mjs +201 -0
- package/lib/identities.mjs +1 -1
- package/lib/keeping.mjs +162 -0
- package/lib/ledger.mjs +20 -0
- package/lib/lock.mjs +6 -1
- package/lib/mcp.mjs +52 -18
- package/lib/mint.mjs +179 -38
- package/lib/mongo.mjs +74 -0
- package/lib/numbers-text.mjs +68 -0
- package/lib/pg.mjs +118 -0
- package/lib/proc.mjs +100 -0
- package/lib/proof.mjs +1014 -0
- package/lib/proxy.mjs +184 -0
- package/lib/pyhook/cortad_sites.py +218 -0
- package/lib/pyhook/cortad_tied.py +291 -0
- package/lib/pyhook/sitecustomize.py +1175 -166
- package/lib/qdrant.mjs +47 -0
- package/lib/reach.mjs +223 -0
- package/lib/read-text.mjs +78 -24
- package/lib/redis.mjs +130 -0
- package/lib/register.mjs +36 -27
- package/lib/replay.mjs +464 -34
- package/lib/runner.mjs +85 -0
- package/lib/service.mjs +25 -0
- package/lib/spec.mjs +7 -4
- package/lib/start.mjs +33 -2
- package/lib/stick.mjs +65 -25
- package/lib/stores.mjs +347 -0
- package/lib/switches.mjs +36 -0
- package/lib/tether.mjs +9 -0
- package/lib/text.mjs +314 -179
- package/lib/tied.cjs +217 -0
- package/lib/trace.cjs +386 -514
- package/lib/verbs.mjs +155 -38
- package/lib/wire.cjs +882 -0
- package/lib/words.mjs +2 -0
- package/lib/writes.mjs +2 -2
- package/local.mjs +661 -297
- package/package.json +31 -1
- package/skill/SKILL.md +49 -42
- package/skill/references/results.md +279 -102
package/lib/register.mjs
CHANGED
|
@@ -31,52 +31,54 @@ export function detectClients({ home = homedir(), env = process.env } = {}) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// The clients that now know Cortad, and the files under the home folder this call wrote, relative to
|
|
35
|
+
// it: a connect says what it changed only when it changed something.
|
|
35
36
|
export async function registerAll({ home = homedir(), env = process.env, run = exec, skillSrc = SKILL_SRC, spec = cliSpec({ env }) } = {}) {
|
|
36
37
|
const found = detectClients({ home, env });
|
|
37
38
|
const server = serverFor(spec);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
39
|
+
const clients = [];
|
|
40
|
+
const changed = [];
|
|
41
|
+
const wrote = (rel, did) => { if (did) changed.push(rel); };
|
|
42
|
+
const skill = (rel) => wrote(rel, installSkill(join(home, rel), { src: skillSrc, spec }));
|
|
40
43
|
if (found.claude) {
|
|
41
|
-
await registerClaude({ home, env, run, server });
|
|
42
|
-
skill(join(
|
|
43
|
-
|
|
44
|
+
wrote(".claude.json", await registerClaude({ home, env, run, server }));
|
|
45
|
+
skill(join(".claude", "skills", "cortad"));
|
|
46
|
+
clients.push("Claude Code");
|
|
44
47
|
}
|
|
45
48
|
if (found.codex) {
|
|
46
|
-
await registerCodex({ home, env, run, server });
|
|
47
|
-
|
|
49
|
+
wrote(join(".codex", "config.toml"), await registerCodex({ home, env, run, server }));
|
|
50
|
+
clients.push("Codex");
|
|
48
51
|
}
|
|
49
52
|
if (found.cursor) {
|
|
50
|
-
registerJson(join(home, ".cursor", "mcp.json"), { type: "stdio", ...server });
|
|
51
|
-
|
|
53
|
+
wrote(join(".cursor", "mcp.json"), registerJson(join(home, ".cursor", "mcp.json"), { type: "stdio", ...server }));
|
|
54
|
+
clients.push("Cursor");
|
|
52
55
|
}
|
|
53
56
|
if (found.copilot) {
|
|
54
57
|
// Copilot CLI keeps its servers in its own home, in its own shape: a "local" server with every tool.
|
|
55
|
-
registerJson(join(home, ".copilot", "mcp-config.json"), { type: "local", ...server, tools: ["*"] });
|
|
56
|
-
skill(join(
|
|
57
|
-
|
|
58
|
+
wrote(join(".copilot", "mcp-config.json"), registerJson(join(home, ".copilot", "mcp-config.json"), { type: "local", ...server, tools: ["*"] }));
|
|
59
|
+
skill(join(".copilot", "skills", "cortad"));
|
|
60
|
+
clients.push("Copilot");
|
|
58
61
|
}
|
|
59
62
|
// The one skills folder Codex, Cursor and Copilot all read.
|
|
60
|
-
if (found.codex || found.cursor || found.copilot) skill(join(
|
|
61
|
-
return
|
|
63
|
+
if (found.codex || found.cursor || found.copilot) skill(join(".agents", "skills", "cortad"));
|
|
64
|
+
return { clients, changed };
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
// The client's own command when it is here, so its config is written the way it writes it; the file
|
|
65
|
-
// itself only when the folder exists without the binary on this PATH, or the command failed.
|
|
68
|
+
// itself only when the folder exists without the binary on this PATH, or the command failed. Each
|
|
69
|
+
// register step says whether it wrote.
|
|
66
70
|
async function registerClaude({ home, env, run, server }) {
|
|
67
71
|
const file = join(home, ".claude.json");
|
|
68
72
|
if (onPath("claude", env)) {
|
|
69
73
|
const add = () => run("claude", ["mcp", "add", "--scope", "user", "cortad", "--", server.command, ...server.args], { env });
|
|
70
|
-
try { await add(); return; } catch (err) {
|
|
74
|
+
try { await add(); return true; } catch (err) {
|
|
71
75
|
if (/already exists/i.test(String(err?.stderr ?? err?.message))) {
|
|
72
|
-
if (same(readJson(file)?.mcpServers?.cortad, server)) return;
|
|
73
|
-
try { await run("claude", ["mcp", "remove", "--scope", "user", "cortad"], { env }); await add(); return; } catch { /* the file below */ }
|
|
76
|
+
if (same(readJson(file)?.mcpServers?.cortad, server)) return false;
|
|
77
|
+
try { await run("claude", ["mcp", "remove", "--scope", "user", "cortad"], { env }); await add(); return true; } catch { /* the file below */ }
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
|
-
|
|
78
|
-
config.mcpServers = { ...(config.mcpServers ?? {}), cortad: { type: "stdio", ...server } };
|
|
79
|
-
writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
81
|
+
return registerJson(file, { type: "stdio", ...server });
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
const CODEX_BLOCK = /^\[mcp_servers\.cortad\]\n(?:(?!\[).*(?:\n|$))*/m;
|
|
@@ -87,12 +89,12 @@ async function registerCodex({ home, env, run, server }) {
|
|
|
87
89
|
const block = text.match(CODEX_BLOCK)?.[0] ?? "";
|
|
88
90
|
const args = block.match(/^args\s*=\s*(\[.*\])\s*$/m)?.[1];
|
|
89
91
|
const current = (() => { try { return args ? { command: block.match(/^command\s*=\s*"(.*)"\s*$/m)?.[1], args: JSON.parse(args) } : null; } catch { return null; } })();
|
|
90
|
-
if (same(current, server)) return;
|
|
92
|
+
if (same(current, server)) return false;
|
|
91
93
|
if (onPath("codex", env)) {
|
|
92
94
|
try {
|
|
93
95
|
if (block) await run("codex", ["mcp", "remove", "cortad"], { env });
|
|
94
96
|
await run("codex", ["mcp", "add", "cortad", "--", server.command, ...server.args], { env });
|
|
95
|
-
return;
|
|
97
|
+
return true;
|
|
96
98
|
} catch { /* the file below */ }
|
|
97
99
|
}
|
|
98
100
|
const entry = `[mcp_servers.cortad]\ncommand = "${server.command}"\nargs = ${JSON.stringify(server.args)}\n`;
|
|
@@ -100,23 +102,30 @@ async function registerCodex({ home, env, run, server }) {
|
|
|
100
102
|
// The blank lines after the old block stay, so the rest of the file keeps its layout.
|
|
101
103
|
const replaced = () => text.replace(CODEX_BLOCK, (old) => `${entry}${(old.match(/\n+$/)?.[0] ?? "\n").slice(1)}`);
|
|
102
104
|
writeFileSync(file, block ? replaced() : `${text}${text && !text.endsWith("\n") ? "\n" : ""}\n${entry}`);
|
|
105
|
+
return true;
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
function registerJson(file, entry) {
|
|
106
109
|
const config = readJson(file) ?? {};
|
|
107
|
-
if (same(config.mcpServers?.cortad, entry)) return;
|
|
110
|
+
if (same(config.mcpServers?.cortad, entry)) return false;
|
|
108
111
|
config.mcpServers = { ...(config.mcpServers ?? {}), cortad: entry };
|
|
109
112
|
mkdirSync(dirname(file), { recursive: true });
|
|
110
113
|
writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
114
|
+
return true;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
// SKILL.md and its references, copied whole so a newer package refreshes the words, with the
|
|
114
|
-
// shell examples naming the spec this machine runs.
|
|
118
|
+
// shell examples naming the spec this machine runs. Says whether a file changed.
|
|
115
119
|
export function installSkill(dir, { src = SKILL_SRC, spec = cliSpec() } = {}) {
|
|
116
120
|
mkdirSync(join(dir, "references"), { recursive: true });
|
|
121
|
+
let wrote = false;
|
|
117
122
|
for (const rel of ["SKILL.md", join("references", "results.md")]) {
|
|
118
|
-
|
|
123
|
+
const text = readFileSync(join(src, rel), "utf8").replaceAll(PLACEHOLDER, spec);
|
|
124
|
+
if (existsSync(join(dir, rel)) && readFileSync(join(dir, rel), "utf8") === text) continue;
|
|
125
|
+
writeFileSync(join(dir, rel), text);
|
|
126
|
+
wrote = true;
|
|
119
127
|
}
|
|
128
|
+
return wrote;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
function readJson(file) {
|