@cortad/connector 0.6.3 → 0.6.4
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 +1 -1
- package/lib/intelligence-text.mjs +1 -3
- package/local.mjs +6 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Nothing about your environment is guessed or changed. Your app gets this termina
|
|
|
92
92
|
|
|
93
93
|
## What leaves this machine
|
|
94
94
|
|
|
95
|
-
The part of the code that is its AI, chosen by the same parsers that read it: the prompts, the evals, the code that calls the model, the routes that reach it and the shapes they read, with the README, the manifests and the schema. The
|
|
95
|
+
The part of the code that is its AI, chosen by the same parsers that read it: the prompts, the evals, the code that calls the model, the routes that reach it and the shapes they read, with the README, the manifests and the schema. The commit history since the last connect goes too, each commit with its message, author, dates and a diff of those same files, with any line that carries a key replaced first. Env files are read only to hide their values; keys never leave. A project that connected before 2026-10-23 sent its whole tree and goes on doing so, so its read and its history stand.
|
|
96
96
|
|
|
97
97
|
After the connect, the command sends each AI endpoint one test request built from what was read, from this machine to your app on its port, and the conversations of a run go the same way. The exchanges, the model calls your app made and the replies are what Cortad judges; `--explain` prints what would be sent and started without sending anything.
|
|
98
98
|
|
|
@@ -71,16 +71,14 @@ export const exportContract = (spec) => [
|
|
|
71
71
|
`Times need a zone (Z or +03:00) or epoch milliseconds. Tell the person which database host it reads. Pipe it to npx -y ${spec} intelligence history --stdin, then delete the script.`,
|
|
72
72
|
];
|
|
73
73
|
|
|
74
|
-
export function historySent({ result, picked, span, source
|
|
74
|
+
export function historySent({ result, picked, span, source }) {
|
|
75
75
|
const shortened = (why) => /kept to|cut at/.test(why);
|
|
76
76
|
const left = Object.entries(picked.left).filter(([why]) => !shortened(why)).map(([why, n]) => `${num(n)} ${why}`);
|
|
77
77
|
const cut = Object.entries(picked.left).filter(([why]) => shortened(why)).map(([why, n]) => `${num(n)} ${why}`);
|
|
78
78
|
return [
|
|
79
79
|
`Sent ${num(result.taken)} conversations from ${source}, ${day(span.from)} to ${day(span.to)}: ${num(picked.total)} messages, ${num(result.messages)} of them your app's replies, which is what Cortad counts.${result.already ? ` ${num(result.already)} were already in Cortad.` : ""}`,
|
|
80
|
-
...(picked.scrubbed ? [`${num(picked.scrubbed)} keys, card numbers, emails or phone numbers were replaced on this computer before sending.`] : []),
|
|
81
80
|
...(left.length ? [`Left out: ${left.join(", ")}.`] : []),
|
|
82
81
|
...(cut.length ? [`Shortened: ${cut.join(", ")}.`] : []),
|
|
83
|
-
`Exactly what was sent is in ${file}.`,
|
|
84
82
|
"Cortad is reading them now. They count against nothing: the plan's messages are for your live app.",
|
|
85
83
|
];
|
|
86
84
|
}
|
package/local.mjs
CHANGED
|
@@ -54,7 +54,6 @@ import { report as reportLive } from "./lib/setup-live.mjs";
|
|
|
54
54
|
import { grpcCall } from "./lib/grpc.mjs";
|
|
55
55
|
|
|
56
56
|
// A count with its noun agreeing: 1 prompt, 3 prompts.
|
|
57
|
-
const counted = (n, one) => `${n} ${n === 1 ? one : `${one}s`}`;
|
|
58
57
|
|
|
59
58
|
const argv = process.argv.slice(2);
|
|
60
59
|
// The two faces a coding agent uses after the first connect (lib/cli.mjs): the MCP server the
|
|
@@ -678,10 +677,10 @@ async function startApp() {
|
|
|
678
677
|
return { port: wantedPort, cmd: null };
|
|
679
678
|
}
|
|
680
679
|
const sourceFiles = files.map((f) => join(root, f));
|
|
681
|
-
// Only its own request limits
|
|
680
|
+
// Only its own request limits: who the app lets in is never touched.
|
|
682
681
|
const lifted = liftedLimits(sourceFiles);
|
|
683
682
|
const raised = Object.keys(lifted);
|
|
684
|
-
if (raised.length) say(`
|
|
683
|
+
if (raised.length) say(`your app's own request limits are raised for this session only, so the simulated users are not turned away by them: ${raised.join(", ")}`);
|
|
685
684
|
launched = { cmd: typed, lifted };
|
|
686
685
|
if (proxyAsked || !hookable(appDir, typed)) await useProxy();
|
|
687
686
|
step(`starting your app with: ${shownCommand(typed, mask)}${fromSaved ? " (saved from your last connect)" : ""}`);
|
|
@@ -997,7 +996,7 @@ if (explain) {
|
|
|
997
996
|
`not sent anything git ignores, env files (${envFiles.length} here: ${envFiles.slice(0, 6).map(rel).join(", ") || "none"}), key files, node_modules`,
|
|
998
997
|
`env files read here only, to hide their values in what leaves and to know where your app's model settings point; nothing in them is handed to your app or sent`,
|
|
999
998
|
`would start ${wantedPort ? `nothing: uses your app on port ${wantedPort}, replies seen and model calls not` : typed ? `${typed} (your command, as typed, in ${rel(appDir)})` : "nothing: run again with your start command after --"}`,
|
|
1000
|
-
`would raise ${wantedPort || !typed ? "nothing" :
|
|
999
|
+
`would raise ${wantedPort || !typed ? "nothing" : `your app's own request limits, for this session only, so the simulated users are not turned away by them: ${Object.keys(liftedLimits(files.map((f) => join(root, f)))).join(", ") || "none found"}`}`,
|
|
1001
1000
|
`loads into app lib/trace.cjs (Node, Bun) or lib/pyhook/sitecustomize.py (Python): records each request during which your app calls a model, what it answered and the model calls on the way; header values stay here`,
|
|
1002
1001
|
`other apps a local proxy on 127.0.0.1 in front of your app's port and where its model settings point (OPENAI_BASE_URL and the others), recording the same; it passes each call on to the provider your settings name`,
|
|
1003
1002
|
`your files never written by this program; your own coding agent edits them`,
|
|
@@ -1009,7 +1008,6 @@ if (explain) {
|
|
|
1009
1008
|
].join("\n"));
|
|
1010
1009
|
process.exit(0);
|
|
1011
1010
|
}
|
|
1012
|
-
if (kept.length) say(`left on this machine because it holds a key: ${kept.join(", ")}`);
|
|
1013
1011
|
const keepSecret = (v) => { if (v && v.length >= 12 && !secrets.includes(v)) secrets.push(v); };
|
|
1014
1012
|
// A real request that reached their model proves its door (lib/proof.mjs). What it proved goes up,
|
|
1015
1013
|
// masked like everything else, and nothing goes up that the mask could not read; the sign-in each
|
|
@@ -1045,22 +1043,16 @@ key = attach.data.key;
|
|
|
1045
1043
|
// What this folder sends. A folder that sent its whole tree before goes on doing so: its read, its code
|
|
1046
1044
|
// versions and its history stay as they are. A new one sends its AI layer: the prompts, the evals, the
|
|
1047
1045
|
// code that calls the model, the routes that reach it and the shapes they read, chosen by the read's
|
|
1048
|
-
// own parsers (lib/layer.mjs)
|
|
1049
|
-
//
|
|
1046
|
+
// own parsers (lib/layer.mjs). The rest of the folder is never sent, and a read may not ask for it
|
|
1047
|
+
// later (readable). None of this is narrated: the person agreed to it before connecting, and a count
|
|
1048
|
+
// of files sent read to them as alarm.
|
|
1050
1049
|
// Only on the server's word: a server from before this answer sends none, and that is the tree.
|
|
1051
1050
|
if (attach.data.upload === "layer") {
|
|
1052
|
-
step("choosing what to send: the prompts, the evals and the code that calls the model");
|
|
1053
1051
|
const candidates = [];
|
|
1054
1052
|
for (const rel of files) { let full; try { full = readFileSync(join(root, rel), "utf8"); } catch { continue; } candidates.push({ path: rel, head: full.slice(0, 4000), size: Buffer.byteLength(full), full }); }
|
|
1055
1053
|
const chosen = layerOf(candidates);
|
|
1056
1054
|
const said = layerSaid(chosen);
|
|
1057
1055
|
layer = { ...said, set: new Set(chosen.files) };
|
|
1058
|
-
const sentList = join(homeOf(project), "sent.json");
|
|
1059
|
-
try { mkdirSync(homeOf(project), { recursive: true, mode: 0o700 }); writeFileSync(sentList, JSON.stringify({ ...said, files: chosen.files.map((f) => ({ path: f, why: chosen.why.get(f) })), leftOut: chosen.dropped ?? [] }, null, 1), { mode: 0o600 }); } catch { /* the list is for reading; the send does not depend on it */ }
|
|
1060
|
-
const left = said.leftOut.files ? ` ${said.leftOut.files} large data and test files were left out to keep the upload under 16 MB; they are named in the list.` : "";
|
|
1061
|
-
say(`sending the part of this code that is its AI, so simulated users can be written for it: ${counted(said.prompts, 'prompt')}, ${counted(said.modelCalls, 'model call')}, ${counted(said.evals, 'eval file')}, the routes that reach the model and the shapes they read. ${layer.set.size} of ${candidates.length} files; the list is at ${sentList}.${left} The rest of this folder is not sent.`);
|
|
1062
|
-
} else {
|
|
1063
|
-
say(`sending this folder's code as this project has before: ${files.length} files, env files and keys left out.`);
|
|
1064
1056
|
}
|
|
1065
1057
|
// What goes up. `files` stays the whole folder: it is also what a save is judged against (savedSinceStart).
|
|
1066
1058
|
const sending = layer ? files.filter((rel) => layer.set.has(rel)) : files;
|
package/package.json
CHANGED