modelos-secure 0.3.3 → 0.3.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/assets/{login-DeAJGhiN.js → login-DeGa63BQ.js} +19 -9
- package/assets/login-Dz4o-1cM.js +1 -0
- package/cli.js +171 -47
- package/package.json +1 -1
- package/assets/login-CvPyhFJ7.js +0 -1
|
@@ -49,11 +49,26 @@ function step(name, detail, state = "done") {
|
|
|
49
49
|
const right = state === "done" ? "done" : "";
|
|
50
50
|
say(` ${bold(name.padEnd(STEP_WIDTH))}${detail}${right ? ` ${grey(right)}` : ""}`);
|
|
51
51
|
}
|
|
52
|
+
var RECOMMENDED = " (recommended)";
|
|
53
|
+
/**
|
|
54
|
+
* The choices as the lines they take on screen, one each and none wider than the window. The
|
|
55
|
+
* picker redraws by moving up exactly this many lines, so a line that wrapped would leave the one
|
|
56
|
+
* above it standing: seen on an 80-column window, where "Yes" appeared twice.
|
|
57
|
+
*/
|
|
58
|
+
function choiceLines(choices, index, columns) {
|
|
59
|
+
return choices.map((choice, i) => {
|
|
60
|
+
const tail = choice.recommended ? RECOMMENDED : "";
|
|
61
|
+
const room = Math.max(8, columns - 1 - 4 - tail.length);
|
|
62
|
+
const label = choice.label.length > room ? `${choice.label.slice(0, room - 1)}${unicode ? "…" : "~"}` : choice.label;
|
|
63
|
+
return ` ${i === index ? blue(pointer) : " "} ${label}${tail ? grey(tail) : ""}`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
52
66
|
/** One question, one reason, a default. Arrows, Enter and the first letter of each choice. */
|
|
53
67
|
async function ask(question, why, choices) {
|
|
54
68
|
const start = Math.max(0, choices.findIndex((c) => c.recommended));
|
|
55
69
|
say();
|
|
56
70
|
say(` ${question}`);
|
|
71
|
+
if (why) say(` ${grey(`Why we ask: ${why}`)}`);
|
|
57
72
|
say();
|
|
58
73
|
if (!interactive()) {
|
|
59
74
|
const chosen = choices[start];
|
|
@@ -63,14 +78,10 @@ async function ask(question, why, choices) {
|
|
|
63
78
|
let index = start;
|
|
64
79
|
const render = (first) => {
|
|
65
80
|
if (!first) process.stdout.write(`\u001b[${choices.length.toString()}A`);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
process.stdout.write(`\u001b[2K ${mark} ${choice.label}${choice.recommended ? grey(" (recommended)") : ""}\n`);
|
|
69
|
-
}
|
|
81
|
+
const columns = process.stdout.columns || 80;
|
|
82
|
+
for (const line of choiceLines(choices, index, columns)) process.stdout.write(`\u001b[2K${line}\n`);
|
|
70
83
|
};
|
|
71
84
|
render(true);
|
|
72
|
-
if (why) say(`\n ${grey(`Why we ask: ${why}`)}`);
|
|
73
|
-
if (why) process.stdout.write(`\u001b[2A`);
|
|
74
85
|
return new Promise((resolve, reject) => {
|
|
75
86
|
emitKeypressEvents(process.stdin);
|
|
76
87
|
process.stdin.setRawMode(true);
|
|
@@ -79,7 +90,6 @@ async function ask(question, why, choices) {
|
|
|
79
90
|
process.stdin.off("keypress", onKey);
|
|
80
91
|
process.stdin.setRawMode(false);
|
|
81
92
|
process.stdin.pause();
|
|
82
|
-
if (why) process.stdout.write(`\u001b[2B\n`);
|
|
83
93
|
};
|
|
84
94
|
const onKey = (text, key) => {
|
|
85
95
|
if (key.ctrl && key.name === "c") {
|
|
@@ -89,7 +99,7 @@ async function ask(question, why, choices) {
|
|
|
89
99
|
}
|
|
90
100
|
if (key.name === "up") index = (index - 1 + choices.length) % choices.length;
|
|
91
101
|
else if (key.name === "down") index = (index + 1) % choices.length;
|
|
92
|
-
else if (key.name === "return") {
|
|
102
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
93
103
|
done();
|
|
94
104
|
resolve(choices[index].value);
|
|
95
105
|
return;
|
|
@@ -120,7 +130,7 @@ async function askText(question, secret = false) {
|
|
|
120
130
|
reject(new Interrupted());
|
|
121
131
|
return;
|
|
122
132
|
}
|
|
123
|
-
if (key.name === "return") {
|
|
133
|
+
if (key.name === "return" || key.name === "enter") {
|
|
124
134
|
process.stdin.off("keypress", onKey);
|
|
125
135
|
process.stdin.setRawMode(false);
|
|
126
136
|
process.stdin.pause();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as loginCommand } from "./login-DeGa63BQ.js";
|
package/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as lastFinishedStep, a as apiClient, b as status, c as Interrupted, d as beforeStopping, f as blue, g as interactive, h as grey, i as IapRefusal, l as ask, m as finishBeforeStopping, o as atAddress, p as bold, r as ApiError$1, s as readConnection, t as loginCommand, u as askText, v as red, x as step, y as say } from "./assets/login-
|
|
2
|
+
import { _ as lastFinishedStep, a as apiClient, b as status, c as Interrupted, d as beforeStopping, f as blue, g as interactive, h as grey, i as IapRefusal, l as ask, m as finishBeforeStopping, o as atAddress, p as bold, r as ApiError$1, s as readConnection, t as loginCommand, u as askText, v as red, x as step, y as say } from "./assets/login-DeGa63BQ.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -23,7 +23,7 @@ if (existsSync(resolve(here, "wasm"))) {
|
|
|
23
23
|
}
|
|
24
24
|
// package.json
|
|
25
25
|
var name = "modelos-secure";
|
|
26
|
-
var version$1 = "0.3.
|
|
26
|
+
var version$1 = "0.3.4";
|
|
27
27
|
var type = "module";
|
|
28
28
|
var bin = { "modelos-secure": "dist/cli.js" };
|
|
29
29
|
var exports$1 = { "./hook": "./dist/hook/hook.mjs" };
|
|
@@ -424,8 +424,10 @@ async function walk$1(root) {
|
|
|
424
424
|
};
|
|
425
425
|
}
|
|
426
426
|
/**
|
|
427
|
-
* The folders whose manifests describe this one: itself,
|
|
428
|
-
* repository's root, for an app kept in a folder of a larger repository
|
|
427
|
+
* The folders whose manifests describe this one: itself, each folder above it up to the
|
|
428
|
+
* repository's root, for an app kept in a folder of a larger repository, and the folders below
|
|
429
|
+
* it that hold a manifest, for a repository whose app sits in `backend/` or `sidecar/`. Without
|
|
430
|
+
* the ones below, such a repository showed no library, no framework and no route. ADR 0094.
|
|
429
431
|
*/
|
|
430
432
|
async function manifestFolders(root) {
|
|
431
433
|
const folders = [root];
|
|
@@ -437,6 +439,23 @@ async function manifestFolders(root) {
|
|
|
437
439
|
folders.push(up);
|
|
438
440
|
at = up;
|
|
439
441
|
}
|
|
442
|
+
const below = async (dir, depth) => {
|
|
443
|
+
if (depth > 3) return;
|
|
444
|
+
for (const entry of await readdir(dir, { withFileTypes: true }).catch(() => [])) {
|
|
445
|
+
if (!entry.isDirectory() || SKIPPED.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
446
|
+
const full = join(dir, entry.name);
|
|
447
|
+
for (const manifest of [
|
|
448
|
+
"package.json",
|
|
449
|
+
"pyproject.toml",
|
|
450
|
+
"Pipfile"
|
|
451
|
+
]) if (await access(join(full, manifest)).then(() => true, () => false)) {
|
|
452
|
+
folders.push(full);
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
await below(full, depth + 1);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
await below(root, 1);
|
|
440
459
|
return folders;
|
|
441
460
|
}
|
|
442
461
|
/** A TOML file's sections by name, each with its body. Enough for the manifests read here. */
|
|
@@ -542,15 +561,25 @@ async function look(root) {
|
|
|
542
561
|
command: web,
|
|
543
562
|
from: "Procfile"
|
|
544
563
|
});
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
564
|
+
for (const name of [
|
|
565
|
+
"docker-compose.yml",
|
|
566
|
+
"docker-compose.yaml",
|
|
567
|
+
"compose.yml",
|
|
568
|
+
"compose.yaml"
|
|
569
|
+
]) {
|
|
570
|
+
const composed = (await read(name))?.match(/^\s*command:\s*(.+)$/m)?.[1];
|
|
571
|
+
if (composed) {
|
|
572
|
+
startCommands.push({
|
|
573
|
+
command: composed.replace(/^["']|["']$/g, ""),
|
|
574
|
+
from: name
|
|
575
|
+
});
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const target = (await read("Makefile"))?.match(/^(run|start|serve|dev|dev-api|api|server):[^\n]*\n\t(.+)$/m);
|
|
580
|
+
if (target) startCommands.push({
|
|
581
|
+
command: `make ${target[1]}`,
|
|
582
|
+
from: `Makefile, whose ${target[1]} target runs ${target[2].trim().split(/\s+/).slice(0, 3).join(" ")}`
|
|
554
583
|
});
|
|
555
584
|
const settingsFiles = [];
|
|
556
585
|
const settingNames = /* @__PURE__ */ new Set();
|
|
@@ -5655,29 +5684,94 @@ function dockerCommand(dockerfile) {
|
|
|
5655
5684
|
const full = [entrypoint, last("CMD")].filter((body) => body !== void 0).map((body) => listForm(body)?.join(" ") ?? body).join(" ");
|
|
5656
5685
|
return full && !full.startsWith("-") ? full : null;
|
|
5657
5686
|
}
|
|
5687
|
+
/**
|
|
5688
|
+
* Scripts beside the app, not the app: a main block in scripts/, tests/ or eval/, or in a file
|
|
5689
|
+
* named for a check, a benchmark or a one-off job, starts nothing a review could talk to.
|
|
5690
|
+
*/
|
|
5691
|
+
var ASIDE_FOLDERS = /(^|\/)(scripts?|tests?|evals?|benchmarks?|training|tools|migrations|examples?|notebooks?|docs?|bin|hooks|experiments?|utils?)(\/|$)/;
|
|
5692
|
+
var ASIDE_NAMES = /(^|\/)(benchmark|check|backfill|stress|verify|test|dump|audit|import|export|migrate|seed|setup|install|build|deploy|smoke|demo|debug|fixture|generate|convert|download|upload|sync|clean|classify|investigate|analy[sz]e|report)([_-][^/]*)?\.py$|(^|\/)conftest\.py$|_(test|tests|audit|smoke|check|benchmark|demo|gates?)\.py$/;
|
|
5693
|
+
function isAside(path) {
|
|
5694
|
+
return ASIDE_FOLDERS.test(path.replace(/\/[^/]+$/, "")) || ASIDE_NAMES.test(path);
|
|
5695
|
+
}
|
|
5696
|
+
/**
|
|
5697
|
+
* The folder a Python file's absolute imports resolve from, which is where the app runs: for
|
|
5698
|
+
* `from app.db import x` in backend/app/main.py, that is backend. Empty for the repository's
|
|
5699
|
+
* root, or when nothing it imports is here.
|
|
5700
|
+
*/
|
|
5701
|
+
function importRoot(path, imports, files) {
|
|
5702
|
+
const parts = path.split("/").slice(0, -1);
|
|
5703
|
+
const own = imports.filter((name) => !name.startsWith("."));
|
|
5704
|
+
for (let i = parts.length; i >= 0; i--) {
|
|
5705
|
+
const dir = parts.slice(0, i).join("/");
|
|
5706
|
+
const under = (rest) => dir ? `${dir}/${rest}` : rest;
|
|
5707
|
+
if (own.some((name) => {
|
|
5708
|
+
const head = name.split(".")[0];
|
|
5709
|
+
return files.has(under(`${head}.py`)) || files.has(under(`${head}/__init__.py`));
|
|
5710
|
+
})) return dir;
|
|
5711
|
+
}
|
|
5712
|
+
return "";
|
|
5713
|
+
}
|
|
5714
|
+
/** What is offered first: the framework's own way, then the build, the README, the scripts. */
|
|
5715
|
+
var RANK = [
|
|
5716
|
+
[/^the (FastAPI|Starlette|Flask) (app|factory) /, 0],
|
|
5717
|
+
[/^the Dockerfile's command$/, 1],
|
|
5718
|
+
[/^README/i, 2],
|
|
5719
|
+
[/^(package\.json|pyproject\.toml|Procfile|docker-compose|compose\.|Makefile)/, 3],
|
|
5720
|
+
[/^the main block at ([^/]+\/)*(main|app|server|api|cli|run|serve|wsgi|asgi)\.py:/, 4]
|
|
5721
|
+
];
|
|
5722
|
+
function rankStart(candidates) {
|
|
5723
|
+
const rank = (c) => RANK.find(([pattern]) => pattern.test(c.from))?.[1] ?? 5;
|
|
5724
|
+
return candidates.map((c, i) => ({
|
|
5725
|
+
c,
|
|
5726
|
+
i
|
|
5727
|
+
})).sort((a, b) => rank(a.c) - rank(b.c) || a.i - b.i).map(({ c }) => c);
|
|
5728
|
+
}
|
|
5658
5729
|
async function startCandidates(look, project) {
|
|
5659
5730
|
const files = new Set(project.files.map((f) => f.path));
|
|
5660
5731
|
const read = (file) => readFile(join(look.root, file), "utf8").catch(() => null);
|
|
5661
5732
|
const found = [];
|
|
5662
5733
|
for (const file of project.files) {
|
|
5663
5734
|
if (file.language !== "python") continue;
|
|
5664
|
-
const
|
|
5735
|
+
const symbols = symbolsOf(file);
|
|
5736
|
+
const appDir = importRoot(file.path, symbols.imports, files);
|
|
5737
|
+
const module = file.path.slice(appDir ? appDir.length + 1 : 0).replace(/\.py$/, "").replace(/\//g, ".");
|
|
5738
|
+
const dirOption = appDir ? ` --app-dir ${appDir}` : "";
|
|
5665
5739
|
const standsAlone = module.includes(".") || !/^from \.+\w*\s+import/m.test(file.text);
|
|
5666
|
-
|
|
5740
|
+
const made = /* @__PURE__ */ new Set();
|
|
5741
|
+
for (const d of symbols.declarations) {
|
|
5667
5742
|
const callee = d.callee.split(".").at(-1) ?? "";
|
|
5668
5743
|
if (!d.name) continue;
|
|
5669
5744
|
if (!standsAlone) continue;
|
|
5670
|
-
if (/^(FastAPI|Starlette)$/.test(callee))
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5745
|
+
if (/^(FastAPI|Starlette)$/.test(callee)) {
|
|
5746
|
+
made.add(callee);
|
|
5747
|
+
found.push({
|
|
5748
|
+
command: `uvicorn ${module}:${d.name}${dirOption}`,
|
|
5749
|
+
from: `the ${callee} app ${d.name} at ${file.path}:${d.line.toString()}`
|
|
5750
|
+
});
|
|
5751
|
+
} else if (callee === "Flask") {
|
|
5752
|
+
made.add(callee);
|
|
5753
|
+
found.push({
|
|
5754
|
+
command: `flask --app ${module}:${d.name} run${dirOption}`,
|
|
5755
|
+
from: `the Flask app ${d.name} at ${file.path}:${d.line.toString()}`
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
}
|
|
5759
|
+
const lines = file.text.split("\n");
|
|
5760
|
+
if (standsAlone) for (const s of symbols.symbols) {
|
|
5761
|
+
if (s.kind !== "function") continue;
|
|
5762
|
+
const body = lines.slice(s.line - 1, s.end).join("\n");
|
|
5763
|
+
const callee = /\b(FastAPI|Starlette|Flask)\(/.exec(body)?.[1];
|
|
5764
|
+
if (!callee || made.has(callee) || !/^\s+return\s/m.test(body)) continue;
|
|
5765
|
+
found.push(callee === "Flask" ? {
|
|
5766
|
+
command: `flask --app ${module}:${s.name} run${dirOption}`,
|
|
5767
|
+
from: `the Flask factory ${s.name} at ${file.path}:${s.line.toString()}`
|
|
5768
|
+
} : {
|
|
5769
|
+
command: `uvicorn ${module}:${s.name} --factory${dirOption}`,
|
|
5770
|
+
from: `the ${callee} factory ${s.name} at ${file.path}:${s.line.toString()}`
|
|
5677
5771
|
});
|
|
5678
5772
|
}
|
|
5679
|
-
const main =
|
|
5680
|
-
if (main >= 0) found.push({
|
|
5773
|
+
const main = lines.findIndex((l) => /^if __name__\s*==\s*["']__main__["']/.test(l));
|
|
5774
|
+
if (main >= 0 && !isAside(file.path)) found.push({
|
|
5681
5775
|
command: `python ${file.path}`,
|
|
5682
5776
|
from: `the main block at ${file.path}:${(main + 1).toString()}`
|
|
5683
5777
|
});
|
|
@@ -5707,8 +5801,10 @@ async function startCandidates(look, project) {
|
|
|
5707
5801
|
return found.filter((c) => {
|
|
5708
5802
|
if (seen.has(c.command)) return false;
|
|
5709
5803
|
seen.add(c.command);
|
|
5710
|
-
const file = c.command.split(/\s+/).find((w) => /\.(py|m?js|ts)$/.test(w));
|
|
5711
|
-
|
|
5804
|
+
const file = c.command.split(/\s+/).find((w) => /\.(py|m?js|ts)$/.test(w))?.replace(/^\.\//, "");
|
|
5805
|
+
if (!file) return true;
|
|
5806
|
+
if (/^README/i.test(c.from) && isAside(file)) return false;
|
|
5807
|
+
return files.has(file);
|
|
5712
5808
|
});
|
|
5713
5809
|
}
|
|
5714
5810
|
// ../../packages/scan/src/outline.ts
|
|
@@ -5749,7 +5845,8 @@ var where$1 = (f) => ({
|
|
|
5749
5845
|
file: f.file,
|
|
5750
5846
|
line: f.line
|
|
5751
5847
|
});
|
|
5752
|
-
async function buildOutline(look, facts, project) {
|
|
5848
|
+
async function buildOutline(look, facts, project, options = {}) {
|
|
5849
|
+
const budget = options.budget ?? BUDGET;
|
|
5753
5850
|
const outlines = project.files.map(symbolsOf);
|
|
5754
5851
|
const known = await localValues(look.root, look.settingsFiles);
|
|
5755
5852
|
const ranked = [...outlines].sort((a, b) => priority(b) - priority(a));
|
|
@@ -5758,7 +5855,7 @@ async function buildOutline(look, facts, project) {
|
|
|
5758
5855
|
let size = 0;
|
|
5759
5856
|
for (const file of ranked) {
|
|
5760
5857
|
const bytes = JSON.stringify(file).length;
|
|
5761
|
-
if (size + bytes >
|
|
5858
|
+
if (size + bytes > budget) omitted.push(file.file);
|
|
5762
5859
|
else {
|
|
5763
5860
|
code.push(file);
|
|
5764
5861
|
size += bytes;
|
|
@@ -6162,7 +6259,7 @@ function draftConfig(input) {
|
|
|
6162
6259
|
trigger: previous?.ways_in.find((w) => w.kind === "conversation")?.trigger ?? "Hello, can you help me?"
|
|
6163
6260
|
});
|
|
6164
6261
|
for (const fact of wayFacts) {
|
|
6165
|
-
const before = previous?.ways_in.find((w) => w.name === fact.name || w.function === fact.function);
|
|
6262
|
+
const before = previous?.ways_in.find((w) => w.name === fact.name || !!fact.function && w.function === fact.function);
|
|
6166
6263
|
const understood = understanding?.waysIn.find((w) => w.name === fact.name);
|
|
6167
6264
|
if (before) {
|
|
6168
6265
|
waysIn.push({
|
|
@@ -6395,11 +6492,15 @@ function resolveImport(from, name, known) {
|
|
|
6395
6492
|
base = up.slice(0, Math.max(0, up.length - (dots - 1))).join("/");
|
|
6396
6493
|
} else base = "";
|
|
6397
6494
|
const path = rest ? base ? `${base}/${rest}` : rest : base;
|
|
6398
|
-
|
|
6495
|
+
const parts = dir.split("/").filter(Boolean);
|
|
6496
|
+
const ancestors = parts.map((_, i) => `${parts.slice(0, parts.length - i).join("/")}/`);
|
|
6497
|
+
return first((dots > 0 ? [""] : [.../* @__PURE__ */ new Set([
|
|
6498
|
+
...ancestors,
|
|
6399
6499
|
"",
|
|
6400
6500
|
"src/",
|
|
6401
|
-
"app/"
|
|
6402
|
-
|
|
6501
|
+
"app/",
|
|
6502
|
+
...ancestors.map((a) => `${a}src/`)
|
|
6503
|
+
])]).flatMap((root) => [`${root}${path}.py`, `${root}${path}/__init__.py`]));
|
|
6403
6504
|
}
|
|
6404
6505
|
function normalise(path) {
|
|
6405
6506
|
const out = [];
|
|
@@ -6974,7 +7075,7 @@ async function answerQuestion(input) {
|
|
|
6974
7075
|
async function scanFolder(root) {
|
|
6975
7076
|
const found = await look(root);
|
|
6976
7077
|
const { facts, adapters, project } = await readFacts(found);
|
|
6977
|
-
found.startCommands
|
|
7078
|
+
found.startCommands = rankStart([...found.startCommands, ...await startCandidates(found, project)]);
|
|
6978
7079
|
return {
|
|
6979
7080
|
look: found,
|
|
6980
7081
|
facts,
|
|
@@ -20763,6 +20864,23 @@ async function scriptsIn(dir) {
|
|
|
20763
20864
|
return {};
|
|
20764
20865
|
}
|
|
20765
20866
|
}
|
|
20867
|
+
var SAME_INTERPRETER = {
|
|
20868
|
+
python: "python3",
|
|
20869
|
+
python3: "python",
|
|
20870
|
+
pip: "pip3",
|
|
20871
|
+
pip3: "pip"
|
|
20872
|
+
};
|
|
20873
|
+
/**
|
|
20874
|
+
* The same command under the interpreter's name this machine has: `python main.py` where only
|
|
20875
|
+
* `python3` is installed, as on most Linux and Mac machines, or the reverse on Windows. A command
|
|
20876
|
+
* whose first word is here already is returned as it is.
|
|
20877
|
+
*/
|
|
20878
|
+
async function interpreterHere(command) {
|
|
20879
|
+
const [first = "", ...rest] = command.trim().split(/\s+/);
|
|
20880
|
+
const other = SAME_INTERPRETER[first];
|
|
20881
|
+
if (!other || await onPath(first) || !await onPath(other)) return command;
|
|
20882
|
+
return [other, ...rest].join(" ");
|
|
20883
|
+
}
|
|
20766
20884
|
/**
|
|
20767
20885
|
* Why `command` cannot start the app in `dir`, in one sentence, or null when it can as far as can
|
|
20768
20886
|
* be told without running it.
|
|
@@ -21355,7 +21473,7 @@ function toFix(ctx, fix) {
|
|
|
21355
21473
|
}
|
|
21356
21474
|
};
|
|
21357
21475
|
case "run-command": return {
|
|
21358
|
-
label: `${fix.label}: ${fix.command}`,
|
|
21476
|
+
label: fix.label.includes(fix.command) ? fix.label : `${fix.label}: ${fix.command}`,
|
|
21359
21477
|
needsPerson: true,
|
|
21360
21478
|
apply: async () => {
|
|
21361
21479
|
if (!interactive()) return;
|
|
@@ -21400,7 +21518,7 @@ var DONT_KNOW = /^(i\s+)?(don'?t|do not)\s+know|^not\s+sure|^no\s+idea|^\?$/i;
|
|
|
21400
21518
|
*/
|
|
21401
21519
|
async function typeTheCommand(ctx) {
|
|
21402
21520
|
for (let tries = 0; tries < 3; tries++) {
|
|
21403
|
-
const typed = (await askText("How do you start the app?")).trim();
|
|
21521
|
+
const typed = await interpreterHere((await askText("How do you start the app?")).trim());
|
|
21404
21522
|
if (!typed || DONT_KNOW.test(typed)) return;
|
|
21405
21523
|
const why = await whyNotRunnable(ctx.dir, typed);
|
|
21406
21524
|
if (!why) {
|
|
@@ -21417,13 +21535,14 @@ async function typeTheCommand(ctx) {
|
|
|
21417
21535
|
async function candidateFixes(ctx) {
|
|
21418
21536
|
const fixes = [];
|
|
21419
21537
|
for (const c of ctx.startCommands) {
|
|
21420
|
-
|
|
21421
|
-
if (
|
|
21538
|
+
const command = await interpreterHere(c.command);
|
|
21539
|
+
if (command === ctx.config.start?.command) continue;
|
|
21540
|
+
if (await whyNotRunnable(ctx.dir, command)) continue;
|
|
21422
21541
|
fixes.push({
|
|
21423
|
-
label: `${
|
|
21542
|
+
label: `${command} (from ${c.from})`,
|
|
21424
21543
|
apply: () => void (ctx.config.start = {
|
|
21425
21544
|
...ctx.config.start,
|
|
21426
|
-
command
|
|
21545
|
+
command
|
|
21427
21546
|
})
|
|
21428
21547
|
});
|
|
21429
21548
|
}
|
|
@@ -21439,7 +21558,11 @@ var notKnown = {
|
|
|
21439
21558
|
/** Starts the pool, or fails with what happened and how to fix it. */
|
|
21440
21559
|
async function startApp(ctx, instances) {
|
|
21441
21560
|
const { config } = ctx;
|
|
21442
|
-
const command = config.start?.command;
|
|
21561
|
+
const command = config.start?.command ? await interpreterHere(config.start.command) : void 0;
|
|
21562
|
+
if (command && config.start && command !== config.start.command) config.start = {
|
|
21563
|
+
...config.start,
|
|
21564
|
+
command
|
|
21565
|
+
};
|
|
21443
21566
|
const typeIt = {
|
|
21444
21567
|
label: "Type the command",
|
|
21445
21568
|
apply: () => typeTheCommand(ctx)
|
|
@@ -22208,7 +22331,7 @@ async function scanCommand(dir, options) {
|
|
|
22208
22331
|
label: "Enter a token now",
|
|
22209
22332
|
recommended: true,
|
|
22210
22333
|
apply: async () => {
|
|
22211
|
-
const { loginCommand } = await import("./assets/login-
|
|
22334
|
+
const { loginCommand } = await import("./assets/login-Dz4o-1cM.js");
|
|
22212
22335
|
await loginCommand();
|
|
22213
22336
|
const fresh = await readConnection();
|
|
22214
22337
|
api = fresh ? apiClient(fresh) : null;
|
|
@@ -22326,7 +22449,7 @@ async function scanCommand(dir, options) {
|
|
|
22326
22449
|
config = treatToolAs(config, tool.name, state.answers[id]);
|
|
22327
22450
|
}
|
|
22328
22451
|
if (drafted.config.decided_for_you?.length) say(` ${grey(`I hit the limit of 7 questions, so I made the safer choice on ${plural(drafted.config.decided_for_you.length, "tool")} and recorded it for review in modelos-secure.yaml.`)}`);
|
|
22329
|
-
const candidates = [...new Map([...reading?.verified.start ?? [], ...result.look.startCommands].map((c) => [c.command, c])).values()];
|
|
22452
|
+
const candidates = [...new Map([...reading?.verified.start ?? [], ...result.look.startCommands].map((c) => [c.command, c])).values()].slice(0, 6);
|
|
22330
22453
|
if (!previous?.start?.command && candidates.length > 1 && !options.ci) {
|
|
22331
22454
|
const command = (state.answers.start ?? (options.yes ? candidates[0].command : await ask("How should the scan start the app?", "It starts the app on this machine, with the hooks attached, to check the connection.", [...candidates.map((c, i) => ({
|
|
22332
22455
|
label: `${c.command} (${c.from})`,
|
|
@@ -22378,12 +22501,13 @@ async function scanCommand(dir, options) {
|
|
|
22378
22501
|
const paths = (config.ways_in.length || 1) * (config.rules.length || 1);
|
|
22379
22502
|
say(`\n ${bold(`${config.system} is connected.`)}\n`);
|
|
22380
22503
|
say(` ${`${plural(config.ways_in.length, "way")} in`.padEnd(14)} ${config.ways_in.map((w) => w.name).join(", ")}`);
|
|
22381
|
-
|
|
22504
|
+
const heldNow = held(config);
|
|
22505
|
+
say(` ${plural(config.tools.length, "tool").padEnd(14)} ${config.tools.length === 0 ? "none to hold" : heldNow.length === 0 ? "none held during reviews" : `${heldNow.length.toString()} held during reviews: ${heldNow.join(", ")}`}`);
|
|
22382
22506
|
say(` ${plural(paths, "path").padEnd(14)} none tested yet\n`);
|
|
22383
22507
|
const url = connection ? `${connection.url}/systems/${system.id}` : "";
|
|
22384
22508
|
say(` Map: ${url}`);
|
|
22385
|
-
if (options.noReview || options.ci) return;
|
|
22386
|
-
if (
|
|
22509
|
+
if (options.noReview || options.ci || options.yes || !interactive()) return;
|
|
22510
|
+
if (await ask("Start a quick first review now? About 15 minutes, 1,200 attempts.", "It starts the app on this machine with the hooks attached, checks they hold what they should, then reviews it.", [{
|
|
22387
22511
|
label: "Yes, start it",
|
|
22388
22512
|
value: true,
|
|
22389
22513
|
recommended: true
|
|
@@ -22452,8 +22576,8 @@ async function sessionCommand(dir, run) {
|
|
|
22452
22576
|
for (;;) {
|
|
22453
22577
|
say();
|
|
22454
22578
|
const line = await askText(">");
|
|
22455
|
-
const word = line.toLowerCase();
|
|
22456
|
-
if (!line || word === "quit" || word === "exit") return;
|
|
22579
|
+
const word = line.toLowerCase().replace(/\(\)$/, "");
|
|
22580
|
+
if (!line || word === "quit" || word === "exit" || word === "q") return;
|
|
22457
22581
|
if (word === "help") {
|
|
22458
22582
|
run.help();
|
|
22459
22583
|
continue;
|
package/package.json
CHANGED
package/assets/login-CvPyhFJ7.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { t as loginCommand } from "./login-DeAJGhiN.js";
|