@cortad/connector 0.6.5 → 0.6.8
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/origins.mjs +10 -6
- package/lib/read-text.mjs +1 -1
- package/local.mjs +7 -5
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ The hooks run after an edit and on your next message: the first names the prompt
|
|
|
85
85
|
--wait intelligence: return when the history is read
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Your app gets this terminal's environment and its
|
|
88
|
+
Your app gets this terminal's environment and its request limits raised while Cortad tests it, so its simulated users are not blocked. Test conversations write where your app writes, after your yes in the browser.
|
|
89
89
|
|
|
90
90
|
## Files it creates
|
|
91
91
|
|
package/lib/origins.mjs
CHANGED
|
@@ -16,6 +16,9 @@ const NEAR = /origin|cors|allow|client|frontend|web_?url|site_?url|app_?url|publ
|
|
|
16
16
|
const ENV_ORIGIN = /ORIGIN|CORS|FRONTEND|CLIENT_URL|DOMAIN_CLIENT|WEB_URL|SITE_URL|APP_URL|PUBLIC_URL/;
|
|
17
17
|
const ENV_KEY = /^(?:X_)?API_KEY$|^APP_API_KEY$|^INTERNAL_API_KEY$|^(?:X_)?ADMIN_KEY$|^API_TOKEN$|^AUTH_TOKEN$|^SERVICE_KEY$|^SERVER_KEY$|^ACCESS_CODE$/;
|
|
18
18
|
const ENV_FILES = [".env", ".env.local", ".env.development", ".env.development.local"];
|
|
19
|
+
// The port the server's own code listens on when nothing overrides it (`PORT ?? '3100'`,
|
|
20
|
+
// `.listen(3100`): its own address is never its client, even when this session moved it elsewhere.
|
|
21
|
+
const OWN_PORT = /\bPORT\b['"\]]*\s*(?:\?\?|\|\|)\s*['"]?(\d{2,5})|\.listen\(\s*(\d{2,5})\b/g;
|
|
19
22
|
const FILE_CAP = 512_000;
|
|
20
23
|
|
|
21
24
|
export const envOf = (root) => {
|
|
@@ -33,8 +36,7 @@ export function clientOriginOf(root, { port = null, limit = 4000 } = {}) {
|
|
|
33
36
|
const scores = new Map();
|
|
34
37
|
const count = (url, by) => scores.set(url, (scores.get(url) ?? 0) + by);
|
|
35
38
|
let seen = 0;
|
|
36
|
-
const own = port ? Number(port) :
|
|
37
|
-
const portOf = (m) => Number(m[1] ?? (m[0].startsWith("https") ? 443 : 80));
|
|
39
|
+
const own = new Set(port ? [Number(port)] : []);
|
|
38
40
|
const walk = (dir, depth) => {
|
|
39
41
|
if (depth > 6 || seen > limit) return;
|
|
40
42
|
let entries;
|
|
@@ -46,19 +48,21 @@ export function clientOriginOf(root, { port = null, limit = 4000 } = {}) {
|
|
|
46
48
|
const file = join(dir, e.name);
|
|
47
49
|
let text;
|
|
48
50
|
try { if (statSync(file).size > FILE_CAP) continue; text = readFileSync(file, "utf8"); } catch { continue; }
|
|
51
|
+
for (const m of text.matchAll(OWN_PORT)) own.add(Number(m[1] ?? m[2]));
|
|
49
52
|
for (const m of text.matchAll(LOCAL)) {
|
|
50
|
-
if (own && portOf(m) === own) continue;
|
|
51
53
|
const around = text.slice(Math.max(0, m.index - 100), m.index);
|
|
52
54
|
count(m[0], NEAR.test(around) ? 3 : 1);
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
};
|
|
56
58
|
walk(root, 0);
|
|
57
|
-
|
|
59
|
+
const env = envOf(root);
|
|
60
|
+
if (/^\d{2,5}$/.test(String(env["PORT"] ?? ""))) own.add(Number(env["PORT"]));
|
|
61
|
+
for (const [name, value] of Object.entries(env)) {
|
|
58
62
|
if (!ENV_ORIGIN.test(name)) continue;
|
|
59
|
-
for (const m of String(value).matchAll(LOCAL))
|
|
63
|
+
for (const m of String(value).matchAll(LOCAL)) count(m[0], 4);
|
|
60
64
|
}
|
|
61
|
-
const best = [...scores].sort((a, b) => b[1] - a[1])[0];
|
|
65
|
+
const best = [...scores].filter(([url]) => !own.has(Number(new URL(url).port || (url.startsWith("https") ? 443 : 80)))).sort((a, b) => b[1] - a[1])[0];
|
|
62
66
|
return best ? best[0] : null;
|
|
63
67
|
}
|
|
64
68
|
|
package/lib/read-text.mjs
CHANGED
|
@@ -109,7 +109,7 @@ export const connectedText = (f) => machineText(f).slice(0, 1);
|
|
|
109
109
|
// whole session was a terminal and a coding agent, which never had one open. Each opens on the state
|
|
110
110
|
// it is reporting, and the one place that session can act on is the agent.
|
|
111
111
|
export const CONNECTED = "connected: Cortad is watching your app. Next, your coding agent calls Cortad's status: it says what to tell you and what it needs. Ctrl-C disconnects and stops what your start command started.";
|
|
112
|
-
export const NOT_RUNNING = "your app is not running, so Cortad has nothing to test.
|
|
112
|
+
export const NOT_RUNNING = "your app is not running, so Cortad has nothing to test. The line above is your app's own error.";
|
|
113
113
|
|
|
114
114
|
// A payload with no read is not a read in progress, and saying so sent a customer's agent back to
|
|
115
115
|
// poll a reading that had already ended: fifteen minutes on a repository that calls no model, where
|
package/local.mjs
CHANGED
|
@@ -680,7 +680,7 @@ async function startApp() {
|
|
|
680
680
|
// Only its own request limits: who the app lets in is never touched.
|
|
681
681
|
const lifted = liftedLimits(sourceFiles);
|
|
682
682
|
const raised = Object.keys(lifted);
|
|
683
|
-
if (raised.length) say(`your app's
|
|
683
|
+
if (raised.length) say(`raising your app's request limits while Cortad tests it, so its simulated users are not blocked: ${raised.join(", ")}`);
|
|
684
684
|
launched = { cmd: typed, lifted };
|
|
685
685
|
if (proxyAsked || !hookable(appDir, typed)) await useProxy();
|
|
686
686
|
step(`starting your app with: ${shownCommand(typed, mask)}${fromSaved ? " (saved from your last connect)" : ""}`);
|
|
@@ -1064,7 +1064,7 @@ const head = (() => {
|
|
|
1064
1064
|
const firstStart = startApp();
|
|
1065
1065
|
firstStart.catch(() => { /* appLife reads the failure */ });
|
|
1066
1066
|
const archive = join(work, "tree.tgz");
|
|
1067
|
-
step("connecting");
|
|
1067
|
+
step("connecting to your Cortad account");
|
|
1068
1068
|
// COPYFILE_DISABLE: a Mac's tar adds no "._name" entries.
|
|
1069
1069
|
await exec("tar", ["-czf", archive, "-C", root, "-T", list], { env: { ...process.env, COPYFILE_DISABLE: "1" } });
|
|
1070
1070
|
const bytes = readFileSync(archive);
|
|
@@ -1078,7 +1078,7 @@ const parts = Math.max(1, Math.ceil(bytes.length / PART));
|
|
|
1078
1078
|
async function sendTree() {
|
|
1079
1079
|
for (let off = 0; off < bytes.length; off += PART) {
|
|
1080
1080
|
const last = off + PART >= bytes.length;
|
|
1081
|
-
step(parts > 1 ? `connecting, ${Math.floor(off / PART) + 1} of ${parts}` : "connecting");
|
|
1081
|
+
step(parts > 1 ? `connecting to your Cortad account, ${Math.floor(off / PART) + 1} of ${parts}` : "connecting to your Cortad account");
|
|
1082
1082
|
// The last part asks for this machine's key when none is stored yet: minted once the repository
|
|
1083
1083
|
// row exists, kept in ~/.cortad for the runs a coding agent asks for on later days. A connect by a
|
|
1084
1084
|
// code asks too, since the code may be another account's: the key kept from the account before
|
|
@@ -1121,7 +1121,9 @@ async function sendHistory() {
|
|
|
1121
1121
|
}
|
|
1122
1122
|
}
|
|
1123
1123
|
// Unchanged code has already been read: coming back says so instead of claiming a second read.
|
|
1124
|
-
|
|
1124
|
+
// Said of the account, never of the app: a bare "connected" here, above an app that could not take its
|
|
1125
|
+
// port, was read as the app being up (2026-11-12).
|
|
1126
|
+
stepDone(moved ? "connected to your Cortad account: your change is in, and what Cortad read before still stands" : resumed ? "connected to your Cortad account: your code has not changed since last time" : "connected to your Cortad account");
|
|
1125
1127
|
if (head) void sendHistory().catch((err) => { if (verbose) say(`commit history not sent: ${err?.message ?? err}`); });
|
|
1126
1128
|
// The coding agents on this machine learn about Cortad now, once: an MCP entry and a skill in each
|
|
1127
1129
|
// one's own home folder. A run started by an agent later comes back through lib/cli.mjs.
|
|
@@ -1130,7 +1132,7 @@ if (head) void sendHistory().catch((err) => { if (verbose) say(`commit history n
|
|
|
1130
1132
|
if (!viaToken && process.env.CORTAD_NO_REGISTER !== "1") {
|
|
1131
1133
|
const got = await registerAll().catch((err) => { if (verbose) say(`could not register with your coding agents: ${err?.message ?? err}`); return { clients: [], changed: [] }; });
|
|
1132
1134
|
const and = (list) => new Intl.ListFormat("en", { type: "conjunction" }).format(list);
|
|
1133
|
-
if (got.changed.length) say(`${and(got.clients)} can now use Cortad: its tools and skill were added to ${got.changed.map((f) => `~/${f}`).join(", ")}
|
|
1135
|
+
if (got.changed.length) say(`${and(got.clients)} can now use Cortad: its tools and skill were added to ${got.changed.map((f) => `~/${f}`).join(", ")}.`);
|
|
1134
1136
|
}
|
|
1135
1137
|
|
|
1136
1138
|
lock = await makeLock({ root, work });
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ Seven tools:
|
|
|
17
17
|
- `connect`: connect the live app, the same steps as `npx -y {{cortad}} intelligence`.
|
|
18
18
|
- `tell`: a note to the Cortad team; that a problem's question reads this app wrong (`findingId` and `why`); what counts as a good ending; behavioral questions to propose; an address for incidents.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The tools load with the coding agent's next session after the first connect. Until then, and wherever they are missing, every command is `npx {{cortad}} <command>` in a shell with the same output, the normal way to work in that session and nothing to mention to the person, for example `npx {{cortad}} behavior --against previous` or `npx {{cortad}} run_status`. The person's own instructions come before this skill.
|
|
21
21
|
|
|
22
22
|
## Talking to the person
|
|
23
23
|
|