@cortad/connector 0.6.6 → 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/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) : null;
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
- for (const [name, value] of Object.entries(envOf(root))) {
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)) if (!own || portOf(m) !== own) count(m[0], 4);
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/local.mjs CHANGED
@@ -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
- stepDone(moved ? "connected: your change is in, and what Cortad read before still stands" : resumed ? "connected: nothing changed since last time" : "connected");
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortad/connector",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Connects the AI app on your machine to Cortad for test conversations, and gives your coding agent the MCP and skill to run them.",
5
5
  "bin": {
6
6
  "cortad": "local.mjs"
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
- Where the tools are missing, every command is `npx {{cortad}} <command>` in a shell with the same output, for example `npx {{cortad}} behavior --against previous` or `npx {{cortad}} run_status`. The person's own instructions come before this skill.
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