cortad 0.1.4 → 0.1.6
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/local.mjs +76 -6
- package/package.json +1 -1
package/local.mjs
CHANGED
|
@@ -149,8 +149,13 @@ function envOrigins(envFiles) {
|
|
|
149
149
|
const THROUGHPUT = /(RATE_?LIMIT|DAILY_LIMIT|HOURLY_LIMIT|MINUTE_LIMIT|REQUESTS_PER|TOKEN_BUDGET|MAX_SSE|MAX_CONCURRENT|THROTTLE|_RPM$|_RPS$|_QPS$)/;
|
|
150
150
|
const GUARDED = /(AUTH|LOGIN|PASSWORD|BREAKER|LOCKOUT|ATTEMPT|FAIL|BAN|BLOCK)/;
|
|
151
151
|
const SWITCH = /_(?:ENABLED|DISABLED|ENABLE|DISABLE)$|^(?:ENABLE|DISABLE)_/;
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
// Limit-shaped names the code itself reads (process.env.GUEST_DAILY_LIMIT, os.environ.get("RATE_LIMIT_RPM"))
|
|
153
|
+
// count too: a limit with a default in code and no line in .env is the one that closes on a run.
|
|
154
|
+
const ENV_READ = /(?:process\.env(?:\.|\[\s*['"])|os\.(?:environ\.get|getenv)\(\s*['"]|os\.environ\[\s*['"]|\benv\(\s*['"]|Deno\.env\.get\(\s*['"])([A-Z][A-Z0-9_]*)/g;
|
|
155
|
+
const lifted = (name) => (/(TOKEN_BUDGET|TOKENS)/.test(name) ? "1000000000" : "1000000");
|
|
156
|
+
const liftable = (name) => THROUGHPUT.test(name) && !GUARDED.test(name) && !SWITCH.test(name);
|
|
157
|
+
function liftedLimits(envFiles, sources = []) {
|
|
158
|
+
const out = {};
|
|
154
159
|
for (const file of envFiles) {
|
|
155
160
|
let text = "";
|
|
156
161
|
try { text = readFileSync(file, "utf8"); } catch { continue; }
|
|
@@ -159,10 +164,16 @@ function liftedLimits(envFiles) {
|
|
|
159
164
|
if (!m) continue;
|
|
160
165
|
const [, name, raw] = m;
|
|
161
166
|
const value = raw.trim().replace(/^(['"])(.*)\1$/, "$2");
|
|
162
|
-
if (
|
|
167
|
+
if (liftable(name) && /^\d+$/.test(value)) out[name] = lifted(name);
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
|
-
|
|
170
|
+
for (const file of sources) {
|
|
171
|
+
if (!/\.(?:[cm]?[jt]sx?|py|go|rb|php|rs)$/.test(file)) continue;
|
|
172
|
+
let text = "";
|
|
173
|
+
try { if (statSync(file).size > 512_000) continue; text = readFileSync(file, "utf8"); } catch { continue; }
|
|
174
|
+
for (const m of text.matchAll(ENV_READ)) if (liftable(m[1]) && !(m[1] in out)) out[m[1]] = lifted(m[1]);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
166
177
|
}
|
|
167
178
|
let secrets = [];
|
|
168
179
|
let identities = null;
|
|
@@ -335,6 +346,9 @@ async function startApp() {
|
|
|
335
346
|
const wanted = Number(flag("--port"));
|
|
336
347
|
if (wanted) {
|
|
337
348
|
if (!(await answers(wanted))) fail(`nothing is answering on port ${wanted}. Start your app first, then run this again.`);
|
|
349
|
+
const took = await takeOver(wanted);
|
|
350
|
+
if (took) return took;
|
|
351
|
+
say("your app was already running, so its request limits stay as they are; if it answers 429, stop it and run this without --port and they are raised for the session");
|
|
338
352
|
return { port: wanted, cmd: null };
|
|
339
353
|
}
|
|
340
354
|
const plan = startPlan({ root, typed: flag("--start"), onPath });
|
|
@@ -343,7 +357,7 @@ async function startApp() {
|
|
|
343
357
|
appDir = plan?.cwd ?? root;
|
|
344
358
|
pinned = pinnedNode();
|
|
345
359
|
if (plan?.within) say(`your app is in ${plan.within}, started there with: ${cmd}`);
|
|
346
|
-
const lifted = liftedLimits(envFiles);
|
|
360
|
+
const lifted = liftedLimits(envFiles, files.map((f) => join(root, f)));
|
|
347
361
|
if (Object.keys(lifted).length) say(`higher request limits for this session: ${Object.keys(lifted).join(", ")}`);
|
|
348
362
|
launched = { cmd, lifted };
|
|
349
363
|
step(`starting your app: ${cmd}`);
|
|
@@ -354,7 +368,13 @@ async function startApp() {
|
|
|
354
368
|
if (up.exited !== null && /EADDRINUSE|address already in use|port.{0,40}(?:in use|already used|is taken|unavailable)/i.test(up.tail)) {
|
|
355
369
|
const ports = [...up.tail.matchAll(/(?::|port\s*[:=]?\s*)(\d{4,5})\b/gi)].map((m) => Number(m[1]));
|
|
356
370
|
for (const port of new Set(ports)) {
|
|
357
|
-
if (await answers(port)) {
|
|
371
|
+
if (await answers(port)) {
|
|
372
|
+
launched = null; child = null;
|
|
373
|
+
const took = await takeOver(port);
|
|
374
|
+
if (took) return took;
|
|
375
|
+
say(`your app is already running on port ${port}, so that one is used; its request limits stay as they are`);
|
|
376
|
+
return { port, cmd: null };
|
|
377
|
+
}
|
|
358
378
|
}
|
|
359
379
|
}
|
|
360
380
|
if (up.tail) console.error(up.tail);
|
|
@@ -362,6 +382,55 @@ async function startApp() {
|
|
|
362
382
|
return { port: null, said: up.tail, why: `${up.exited !== null ? "your app stopped before it answered" : "your app did not answer within three minutes"}; what it said is above.${wrongNode}` };
|
|
363
383
|
}
|
|
364
384
|
|
|
385
|
+
// An app the person started keeps the limits it started with, and a run needs more than a person
|
|
386
|
+
// uses in a month. With their yes, the command stops that app and starts it the way it would have,
|
|
387
|
+
// limits raised for the session. Only in a terminal, only when the code reads a limit, and only
|
|
388
|
+
// when the command knows how to start the app.
|
|
389
|
+
async function takeOver(port) {
|
|
390
|
+
const lifted = liftedLimits(envFiles, files.map((f) => join(root, f)));
|
|
391
|
+
const names = Object.keys(lifted);
|
|
392
|
+
if (!names.length || !process.stdin.isTTY) return null;
|
|
393
|
+
const plan = startPlan({ root, typed: flag("--start"), onPath });
|
|
394
|
+
if (!plan?.cmd) return null;
|
|
395
|
+
const pid = await listenerOn(port);
|
|
396
|
+
if (!pid) return null;
|
|
397
|
+
const answer = await ask(`your app is running with its own request limits (${names.join(", ")}). Restart it with them raised for this session? [Y/n] `);
|
|
398
|
+
if (answer && !/^y(?:es)?$/i.test(answer)) return null;
|
|
399
|
+
const top = await supervisorOf(pid);
|
|
400
|
+
step(`stopping your app (pid ${top}) to start it with higher limits`);
|
|
401
|
+
await stopApp(top);
|
|
402
|
+
for (let i = 0; i < 50 && (await answers(port)); i++) await new Promise((r) => setTimeout(r, 200));
|
|
403
|
+
if (await answers(port)) { say("your app did not stop, so it is used as it is"); return null; }
|
|
404
|
+
appDir = plan.cwd ?? root;
|
|
405
|
+
pinned = pinnedNode();
|
|
406
|
+
if (plan.within) say(`your app is in ${plan.within}, started there with: ${plan.cmd}`);
|
|
407
|
+
say(`higher request limits for this session: ${names.join(", ")}`);
|
|
408
|
+
launched = { cmd: plan.cmd, lifted };
|
|
409
|
+
step(`starting your app: ${plan.cmd}`);
|
|
410
|
+
const up = await launch(180_000);
|
|
411
|
+
if (up.port) return { port: up.port, cmd: plan.cmd, lifted: names };
|
|
412
|
+
if (up.tail) console.error(up.tail);
|
|
413
|
+
fail("your app did not come back after the restart. Start it yourself, then run this again with --port.");
|
|
414
|
+
}
|
|
415
|
+
// The process listening on a port.
|
|
416
|
+
async function listenerOn(port) {
|
|
417
|
+
try { return Number((await exec("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"])).stdout.trim().split("\n")[0]) || null; } catch { return null; }
|
|
418
|
+
}
|
|
419
|
+
// The top of the chain that runs the app: nodemon, npm, the sh -c under it. Climbs from the listener
|
|
420
|
+
// while the parent is a runner, never into the person's own shell or terminal.
|
|
421
|
+
const RUNNER = /^(?:\S*\/)?(?:node|npm|npx|pnpm|yarn|bun|deno|python[\d.]*|uvicorn|gunicorn|flask|tsx|ts-node|nodemon|concurrently|pm2)(?:\s|$)|^(?:\/bin\/)?sh -c\b/;
|
|
422
|
+
async function supervisorOf(pid) {
|
|
423
|
+
const rows = (await exec("ps", ["-axo", "pid=,ppid=,args="])).stdout.trim().split("\n").map((l) => l.trim());
|
|
424
|
+
const table = new Map(rows.map((l) => { const m = /^(\d+)\s+(\d+)\s+(.*)$/.exec(l); return m ? [Number(m[1]), { ppid: Number(m[2]), args: m[3] }] : [0, null]; }));
|
|
425
|
+
let top = pid;
|
|
426
|
+
for (let i = 0; i < 8; i++) {
|
|
427
|
+
const parent = table.get(table.get(top)?.ppid ?? 0);
|
|
428
|
+
if (!parent || !RUNNER.test(parent.args)) break;
|
|
429
|
+
top = table.get(top).ppid;
|
|
430
|
+
}
|
|
431
|
+
return top;
|
|
432
|
+
}
|
|
433
|
+
|
|
365
434
|
// Your app, started the way you start it, and watched until one of its own ports answers.
|
|
366
435
|
let launched = null;
|
|
367
436
|
// The Node this project pins, when the shell that ran this command has another. Strapi refuses
|
|
@@ -480,6 +549,7 @@ if (explain) {
|
|
|
480
549
|
`not sent anything git ignores, env files (${envFiles.length} here: ${envFiles.slice(0, 6).map(rel).join(", ") || "none"}), key files, data files, node_modules, .git`,
|
|
481
550
|
`env files read here only: to hide their values in replies, and to sign in a test account`,
|
|
482
551
|
`would start ${flag("--port") ? `nothing: uses your app on port ${flag("--port")}` : plan ? `${plan.cmd} (in ${rel(plan.cwd)})` : "asks you how your app starts"}`,
|
|
552
|
+
`would raise ${flag("--port") ? "nothing: your app's own request limits stay as they are" : `${Object.keys(liftedLimits(envFiles, files.map((f) => join(root, f)))).join(", ") || "no request limits found"} (for this session only)`}`,
|
|
483
553
|
`loads into app lib/trace.cjs (Node) or lib/pyhook/sitecustomize.py (Python): records the one request during which your app calls a model`,
|
|
484
554
|
`agent edits in your files, each with an undo kept in ~/.cortad/checkpoints; git is never touched`,
|
|
485
555
|
`agent shell confined by the OS: your project and toolchains only, writes to temp and build folders, localhost only`,
|