@ia-qa/qa-discovery 0.1.0
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 +378 -0
- package/ROADMAP.md +321 -0
- package/dist/ai/classify.d.ts +170 -0
- package/dist/ai/classify.js +431 -0
- package/dist/ai/classify.js.map +1 -0
- package/dist/browser/extractPage.d.ts +41 -0
- package/dist/browser/extractPage.js +427 -0
- package/dist/browser/extractPage.js.map +1 -0
- package/dist/browser/openables.d.ts +19 -0
- package/dist/browser/openables.js +127 -0
- package/dist/browser/openables.js.map +1 -0
- package/dist/capture/page.d.ts +128 -0
- package/dist/capture/page.js +78 -0
- package/dist/capture/page.js.map +1 -0
- package/dist/citations.d.ts +80 -0
- package/dist/citations.js +197 -0
- package/dist/citations.js.map +1 -0
- package/dist/classificationView.d.ts +118 -0
- package/dist/classificationView.js +178 -0
- package/dist/classificationView.js.map +1 -0
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +38 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/history.d.ts +1 -0
- package/dist/cli/history.js +35 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +172 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.js +179 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/scan.d.ts +11 -0
- package/dist/cli/scan.js +246 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli-ai/index.d.ts +7 -0
- package/dist/cli-ai/index.js +360 -0
- package/dist/cli-ai/index.js.map +1 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +178 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +12 -0
- package/dist/diff.js +43 -0
- package/dist/diff.js.map +1 -0
- package/dist/explore.d.ts +59 -0
- package/dist/explore.js +119 -0
- package/dist/explore.js.map +1 -0
- package/dist/healingLink.d.ts +30 -0
- package/dist/healingLink.js +127 -0
- package/dist/healingLink.js.map +1 -0
- package/dist/history.d.ts +38 -0
- package/dist/history.js +120 -0
- package/dist/history.js.map +1 -0
- package/dist/htmlReport.d.ts +2 -0
- package/dist/htmlReport.js +371 -0
- package/dist/htmlReport.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/sandbox.d.ts +9 -0
- package/dist/mcp/sandbox.js +82 -0
- package/dist/mcp/sandbox.js.map +1 -0
- package/dist/mcp/server.d.ts +120 -0
- package/dist/mcp/server.js +315 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/network.d.ts +60 -0
- package/dist/network.js +99 -0
- package/dist/network.js.map +1 -0
- package/dist/overview.d.ts +40 -0
- package/dist/overview.js +370 -0
- package/dist/overview.js.map +1 -0
- package/dist/overviewFile.d.ts +31 -0
- package/dist/overviewFile.js +101 -0
- package/dist/overviewFile.js.map +1 -0
- package/dist/scan.d.ts +159 -0
- package/dist/scan.js +402 -0
- package/dist/scan.js.map +1 -0
- package/dist/sharedCalls.d.ts +28 -0
- package/dist/sharedCalls.js +61 -0
- package/dist/sharedCalls.js.map +1 -0
- package/dist/taxonomy.d.ts +77 -0
- package/dist/taxonomy.js +153 -0
- package/dist/taxonomy.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runLogin = runLogin;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const readline = __importStar(require("readline/promises"));
|
|
40
|
+
const self_healing_1 = require("@ia-qa/self-healing");
|
|
41
|
+
const config_1 = require("../config");
|
|
42
|
+
/**
|
|
43
|
+
* `ia-qa-discover login` — open a browser, let a human log in, keep the session.
|
|
44
|
+
*
|
|
45
|
+
* Same reasoning as `ia-qa-heal login`, and for the same reason: any
|
|
46
|
+
* declarative `auth` block models ONE shape of login (a username field, a
|
|
47
|
+
* password field, a submit button), so federated SSO, MFA, a consent screen, a
|
|
48
|
+
* magic link or a device check are each out of reach of every selector list.
|
|
49
|
+
* This verb models nothing at all — the human does whatever the app asks — so
|
|
50
|
+
* it is the general answer to a login wall.
|
|
51
|
+
*
|
|
52
|
+
* Measured need: on a real target, an anonymous scan reached 3 of 11 routes;
|
|
53
|
+
* everything worth discovering was behind the wall, and the app's catch-all
|
|
54
|
+
* route served the login page at every unknown URL with HTTP 200 — so the scan
|
|
55
|
+
* reported "3 pages captured" and looked like a success.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately not automatic, and never part of a pipeline:
|
|
58
|
+
* - The only caller here that opens a VISIBLE browser. Every capture path
|
|
59
|
+
* stays headless: a window in CI is a hung build.
|
|
60
|
+
* - Refuses without a TTY and under CI — it waits for a person twice over (at
|
|
61
|
+
* the browser, then at the keyboard), so a pipeline that runs it just hangs.
|
|
62
|
+
* That refusal is aimed squarely at an agent driving this CLI: it cannot
|
|
63
|
+
* perform this step, and must ask the human to.
|
|
64
|
+
* - What it writes is a SECRET (live cookies). The path and that fact are
|
|
65
|
+
* announced BEFORE the browser opens, and `.ia-qa-discovery/.gitignore` is
|
|
66
|
+
* written on the way out. Nothing here uploads it.
|
|
67
|
+
*
|
|
68
|
+
* Not an MCP tool, on purpose. See `mcp/server.ts`.
|
|
69
|
+
*/
|
|
70
|
+
async function runLogin(args) {
|
|
71
|
+
if (process.env.CI) {
|
|
72
|
+
console.error(`❌ Refusing to open a login browser under CI.\n\n` +
|
|
73
|
+
` It waits for a person — at the browser, then at the keyboard — so a pipeline\n` +
|
|
74
|
+
` that runs it hangs. For an automated run, point at a storageState your own\n` +
|
|
75
|
+
` setup already produces: ia-qa-discover scan --session <file>`);
|
|
76
|
+
process.exitCode = 2;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!process.stdin.isTTY) {
|
|
80
|
+
console.error(`❌ \`ia-qa-discover login\` needs a terminal: it opens a browser, then waits for you to\n` +
|
|
81
|
+
` say you are logged in. With stdin closed it would wait forever.\n` +
|
|
82
|
+
` Run it in your own shell — an agent cannot do this step for you.`);
|
|
83
|
+
process.exitCode = 2;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const config = (0, config_1.configExists)() ? (0, config_1.loadConfig)() : undefined;
|
|
87
|
+
const urlIdx = args.indexOf('--url');
|
|
88
|
+
const wanted = urlIdx !== -1 ? args[urlIdx + 1] : undefined;
|
|
89
|
+
if (urlIdx !== -1 && (!wanted || wanted.startsWith('-'))) {
|
|
90
|
+
console.error('❌ --url needs a URL or a path (e.g. --url /dashboard).');
|
|
91
|
+
process.exitCode = 2;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!wanted && !config?.baseUrl) {
|
|
95
|
+
console.error(`❌ No URL to open. This project has no ${config_1.CONFIG_DIR}/config.json yet.\n` +
|
|
96
|
+
` Pass one: ia-qa-discover login --url https://app.example.com\n` +
|
|
97
|
+
` Or save a baseUrl: ia-qa-discover scan https://app.example.com --save`);
|
|
98
|
+
process.exitCode = 2;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const target = new URL(wanted || '/', config?.baseUrl ?? wanted).toString();
|
|
102
|
+
const sessionIdx = args.indexOf('--session');
|
|
103
|
+
const explicit = sessionIdx !== -1 ? args[sessionIdx + 1] : undefined;
|
|
104
|
+
if (sessionIdx !== -1 && (!explicit || explicit.startsWith('-'))) {
|
|
105
|
+
console.error('❌ --session needs a file path.');
|
|
106
|
+
process.exitCode = 2;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const file = (0, config_1.sessionTarget)(config, explicit).file;
|
|
110
|
+
console.log(`\n🔑 ia-qa-discover login`);
|
|
111
|
+
console.log(` Opens a visible browser at ${target}.`);
|
|
112
|
+
console.log(` Log in there however your app asks — SSO, MFA, a consent screen, all of it.`);
|
|
113
|
+
console.log(` Nothing is typed for you, and no credential is read or stored by this tool.\n`);
|
|
114
|
+
console.log(` When you say you are done, the browser session is saved to:`);
|
|
115
|
+
console.log(` ${file}`);
|
|
116
|
+
console.log(` That file holds live session cookies: anyone who has it is logged in as you.`);
|
|
117
|
+
console.log(` It stays on this machine — nothing here uploads it — and`);
|
|
118
|
+
console.log(` \`${config_1.CONFIG_DIR}/.gitignore\` is written so a commit cannot carry it.\n`);
|
|
119
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
120
|
+
try {
|
|
121
|
+
const go = (await rl.question('Open the browser now? [Y/n] ')).trim().toLowerCase();
|
|
122
|
+
if (go === 'n' || go === 'no') {
|
|
123
|
+
console.log('Nothing opened, nothing written.');
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const { browser, using } = await (0, self_healing_1.launchBrowser)({ headed: true });
|
|
128
|
+
console.log(`🌐 Using ${using} (visible window)`);
|
|
129
|
+
try {
|
|
130
|
+
const locale = config?.locale || process.env.IAQA_LOCALE;
|
|
131
|
+
const context = await browser.newContext(locale ? { locale } : {});
|
|
132
|
+
const page = await context.newPage();
|
|
133
|
+
await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
134
|
+
await rl.question('Logged in? (leave the browser open — press Enter here when the app is showing) ');
|
|
135
|
+
// Saved as-is, on the human's word. Nothing here can tell "logged in" from
|
|
136
|
+
// "still on step 2 of the SSO" for an arbitrary app, and claiming otherwise
|
|
137
|
+
// would be the false green this package exists to avoid. `scan` finds out
|
|
138
|
+
// for real — it warns when the pages it captured still show a login form.
|
|
139
|
+
const landed = page.url();
|
|
140
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
141
|
+
const state = await context.storageState({ path: file });
|
|
142
|
+
writeSessionGitignore();
|
|
143
|
+
const origins = state.origins?.length || 0;
|
|
144
|
+
console.log(`\n✅ Session saved — ${state.cookies.length} cookies, ${origins} ${origins === 1 ? 'origin' : 'origins'} with local storage.`);
|
|
145
|
+
console.log(` ${file}`);
|
|
146
|
+
console.log(` Browser was on ${landed} when it was taken.`);
|
|
147
|
+
console.log(` \`ia-qa-discover scan\` will reuse it automatically. A saved session is a`);
|
|
148
|
+
console.log(` local artefact with a shelf life — when it expires, scan sees the login`);
|
|
149
|
+
console.log(` wall again and says to re-run this.`);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await browser.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
rl.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* `.ia-qa-discovery/.gitignore`, scoped to the one file in there that is a
|
|
161
|
+
* secret. Written inside this package's own state directory rather than the
|
|
162
|
+
* project's `.gitignore`: editing a file the user owns is not a side effect a
|
|
163
|
+
* verb earns by being run. Idempotent.
|
|
164
|
+
*/
|
|
165
|
+
function writeSessionGitignore() {
|
|
166
|
+
const file = path.join((0, config_1.baseDir)(), config_1.CONFIG_DIR, '.gitignore');
|
|
167
|
+
try {
|
|
168
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
169
|
+
if (existing.split(/\r?\n/).some((l) => l.trim() === config_1.SESSION_FILENAME))
|
|
170
|
+
return;
|
|
171
|
+
const sep = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
172
|
+
fs.writeFileSync(file, existing + sep + config_1.SESSION_FILENAME + '\n', 'utf8');
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.warn(` ⚠ Could not write ${file} (${err instanceof Error ? err.message : String(err)}).\n` +
|
|
176
|
+
` Add "${config_1.CONFIG_DIR}/${config_1.SESSION_FILENAME}" to your .gitignore by hand — it is a live session.`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/cli/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function firstPositional(args: string[], valueFlags: string[]): string | undefined;
|
|
2
|
+
export declare function stringArg(args: string[], flag: string): string | undefined;
|
|
3
|
+
export declare function intArg(args: string[], flag: string, max: number): number | undefined;
|
|
4
|
+
export declare function floatArg(args: string[], flag: string): number | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* `--report` with an optional filename: `--report` alone uses the default,
|
|
7
|
+
* `--report out.html` names it. The token after the flag is only a value when
|
|
8
|
+
* it is not itself a flag and not the positional URL.
|
|
9
|
+
*/
|
|
10
|
+
export declare function reportPath(args: string[]): string | undefined;
|
|
11
|
+
export declare function runScan(args: string[]): Promise<void>;
|
package/dist/cli/scan.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.firstPositional = firstPositional;
|
|
37
|
+
exports.stringArg = stringArg;
|
|
38
|
+
exports.intArg = intArg;
|
|
39
|
+
exports.floatArg = floatArg;
|
|
40
|
+
exports.reportPath = reportPath;
|
|
41
|
+
exports.runScan = runScan;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const scan_1 = require("../scan");
|
|
46
|
+
const htmlReport_1 = require("../htmlReport");
|
|
47
|
+
const classificationView_1 = require("../classificationView");
|
|
48
|
+
const healingLink_1 = require("../healingLink");
|
|
49
|
+
function firstPositional(args, valueFlags) {
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
const a = args[i];
|
|
52
|
+
if (valueFlags.includes(a)) {
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (a.startsWith('-'))
|
|
57
|
+
continue;
|
|
58
|
+
return a;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
function stringArg(args, flag) {
|
|
63
|
+
const i = args.indexOf(flag);
|
|
64
|
+
if (i === -1)
|
|
65
|
+
return undefined;
|
|
66
|
+
const next = args[i + 1];
|
|
67
|
+
if (!next || next.startsWith('-'))
|
|
68
|
+
throw new Error(`${flag} needs a value.`);
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
function intArg(args, flag, max) {
|
|
72
|
+
const i = args.indexOf(flag);
|
|
73
|
+
if (i === -1)
|
|
74
|
+
return undefined;
|
|
75
|
+
const raw = args[i + 1];
|
|
76
|
+
const n = Number(raw);
|
|
77
|
+
if (!raw || !Number.isInteger(n) || n < 1 || n > max) {
|
|
78
|
+
throw new Error(`${flag} needs a whole number between 1 and ${max}.`);
|
|
79
|
+
}
|
|
80
|
+
return n;
|
|
81
|
+
}
|
|
82
|
+
function floatArg(args, flag) {
|
|
83
|
+
const i = args.indexOf(flag);
|
|
84
|
+
if (i === -1)
|
|
85
|
+
return undefined;
|
|
86
|
+
const raw = args[i + 1];
|
|
87
|
+
const n = Number(raw);
|
|
88
|
+
if (!raw || !Number.isFinite(n) || n < 0 || n > 1) {
|
|
89
|
+
throw new Error(`${flag} needs a number between 0 and 1.`);
|
|
90
|
+
}
|
|
91
|
+
return n;
|
|
92
|
+
}
|
|
93
|
+
const VALUE_FLAGS = ['--depth', '--max', '--session', '--network-threshold', '--report', '--deep-budget'];
|
|
94
|
+
const DEFAULT_REPORT = 'ia-qa-discover-report.html';
|
|
95
|
+
/**
|
|
96
|
+
* `--report` with an optional filename: `--report` alone uses the default,
|
|
97
|
+
* `--report out.html` names it. The token after the flag is only a value when
|
|
98
|
+
* it is not itself a flag and not the positional URL.
|
|
99
|
+
*/
|
|
100
|
+
function reportPath(args) {
|
|
101
|
+
const i = args.indexOf('--report');
|
|
102
|
+
if (i === -1)
|
|
103
|
+
return undefined;
|
|
104
|
+
const next = args[i + 1];
|
|
105
|
+
if (!next || next.startsWith('-') || /^https?:\/\//i.test(next))
|
|
106
|
+
return DEFAULT_REPORT;
|
|
107
|
+
return next;
|
|
108
|
+
}
|
|
109
|
+
/** Open a local file in the default browser. Never under CI — nothing there to look at. */
|
|
110
|
+
function openInBrowser(file) {
|
|
111
|
+
if (process.env.CI)
|
|
112
|
+
return;
|
|
113
|
+
const cmd = process.platform === 'win32' ? 'cmd' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
114
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', file] : [file];
|
|
115
|
+
try {
|
|
116
|
+
(0, child_process_1.spawn)(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
/* opening is a convenience, never a failure */
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function runScan(args) {
|
|
123
|
+
const url = firstPositional(args, VALUE_FLAGS);
|
|
124
|
+
const json = args.includes('--json');
|
|
125
|
+
// Captured before scanApp runs: it temporarily redirects the global
|
|
126
|
+
// console.log (to keep third-party stdout writes from corrupting --json
|
|
127
|
+
// output), so the progress callback must hold its own reference rather than
|
|
128
|
+
// calling console.log dynamically — that would call the redirected version.
|
|
129
|
+
const log = console.log.bind(console);
|
|
130
|
+
const result = await (0, scan_1.scanApp)({
|
|
131
|
+
url,
|
|
132
|
+
maxDepth: intArg(args, '--depth', 10),
|
|
133
|
+
maxPages: intArg(args, '--max', 1000),
|
|
134
|
+
reveal: args.includes('--no-reveal') ? false : undefined,
|
|
135
|
+
strictHost: args.includes('--strict-host') ? true : undefined,
|
|
136
|
+
session: stringArg(args, '--session'),
|
|
137
|
+
network: args.includes('--no-network') ? false : undefined,
|
|
138
|
+
sitemap: args.includes('--no-sitemap') ? false : undefined,
|
|
139
|
+
deep: args.includes('--deep') ? true : undefined,
|
|
140
|
+
deepBudget: intArg(args, '--deep-budget', 500),
|
|
141
|
+
networkThreshold: floatArg(args, '--network-threshold'),
|
|
142
|
+
save: args.includes('--save'),
|
|
143
|
+
onProgress: json ? undefined : log,
|
|
144
|
+
});
|
|
145
|
+
const classification = (0, classificationView_1.classificationFor)(result.pages);
|
|
146
|
+
const wantedReport = reportPath(args);
|
|
147
|
+
let writtenReport;
|
|
148
|
+
if (wantedReport) {
|
|
149
|
+
const file = path.resolve(process.cwd(), wantedReport);
|
|
150
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
151
|
+
fs.writeFileSync(file, (0, htmlReport_1.renderScanReport)({
|
|
152
|
+
manifest: result.manifest,
|
|
153
|
+
pages: result.pages,
|
|
154
|
+
shared: result.sharedCalls,
|
|
155
|
+
loginWall: result.loginWall,
|
|
156
|
+
hrefBlind: result.hrefBlind,
|
|
157
|
+
droppedByCap: result.discovery.droppedByCap,
|
|
158
|
+
sitemapDeclared: result.discovery.sitemap?.retained ?? null,
|
|
159
|
+
...(result.session ? { session: result.session } : {}),
|
|
160
|
+
// Same rule as `_overview.md`: an existing classification is shown,
|
|
161
|
+
// marked stale, rather than quietly dropped from the one artifact
|
|
162
|
+
// people share. `write: false` means it is read but nothing is written.
|
|
163
|
+
...(classification ? { classification } : {}),
|
|
164
|
+
}), 'utf8');
|
|
165
|
+
writtenReport = file;
|
|
166
|
+
if (args.includes('--open'))
|
|
167
|
+
openInBrowser(file);
|
|
168
|
+
}
|
|
169
|
+
if (json) {
|
|
170
|
+
console.log(JSON.stringify({ ...result, ...(writtenReport ? { report: writtenReport } : {}) }, null, 2));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
console.log('');
|
|
174
|
+
console.log(`✔ ${result.pagesCaptured} of ${result.pagesFound} page(s) captured` +
|
|
175
|
+
(result.failed.length > 0 ? `, ${result.failed.length} failed` : ''));
|
|
176
|
+
if (result.diff.added.length > 0)
|
|
177
|
+
console.log(` + added: ${result.diff.added.join(', ')}`);
|
|
178
|
+
if (result.diff.removed.length > 0)
|
|
179
|
+
console.log(` - removed: ${result.diff.removed.join(', ')}`);
|
|
180
|
+
if (result.diff.changed.length > 0)
|
|
181
|
+
console.log(` ~ changed: ${result.diff.changed.join(', ')}`);
|
|
182
|
+
if (result.diff.added.length === 0 && result.diff.removed.length === 0 && result.diff.changed.length === 0) {
|
|
183
|
+
console.log(' (no change since the last scan)');
|
|
184
|
+
}
|
|
185
|
+
for (const f of result.failed) {
|
|
186
|
+
console.log(` ✘ ${f.name} (${f.url}): ${f.error}`);
|
|
187
|
+
}
|
|
188
|
+
// A page count is not coverage — three different ways, so three messages.
|
|
189
|
+
if (result.hrefBlind) {
|
|
190
|
+
console.log(`\n⚠ No internal links were found, so discovery could not see past the entry URL.`);
|
|
191
|
+
console.log(` "${result.pagesCaptured} of ${result.pagesFound} found" here means ONE page — not the whole app.`);
|
|
192
|
+
console.log(` Discovery follows <a href>. An app that navigates programmatically (React Router's`);
|
|
193
|
+
console.log(` navigate(), a button with an onClick handler) exposes no href for a crawl to follow.`);
|
|
194
|
+
console.log(` Declare the routes you know in ${'`.ia-qa-discovery/config.json`'} → "pages", and scan again.`);
|
|
195
|
+
}
|
|
196
|
+
else if (result.loginWall.length > 1) {
|
|
197
|
+
// Several "pages" all showing a login form: an app whose catch-all route
|
|
198
|
+
// renders the login page answers HTTP 200 at every URL, so the page count
|
|
199
|
+
// looks like success while the same wall was captured N times.
|
|
200
|
+
console.log(`\n⚠ ${result.loginWall.length} of ${result.pagesCaptured} captured page(s) still show a login form ` +
|
|
201
|
+
`(${result.loginWall.join(', ')}).`);
|
|
202
|
+
console.log(` You are very likely looking at the login wall, not the app — a page count is not coverage.`);
|
|
203
|
+
console.log(result.session
|
|
204
|
+
? ` The saved session (${result.session}) did not get past it: it may have expired. Re-run \`ia-qa-discover login\`.`
|
|
205
|
+
: ` Run \`ia-qa-discover login\` to log in by hand once, then scan again.`);
|
|
206
|
+
}
|
|
207
|
+
else if (result.loginWall.length === 1 && !result.session) {
|
|
208
|
+
// Exactly one login page and no session: this is a well-formed public
|
|
209
|
+
// surface, and everything behind the wall was never reached. Saying
|
|
210
|
+
// nothing here would let "N pages captured" read as "the app".
|
|
211
|
+
console.log(`\n⚠ This app has a login (${result.loginWall[0]}), and no session was used.`);
|
|
212
|
+
console.log(` What you have is the PUBLIC surface only — whatever is behind the login was never reached.`);
|
|
213
|
+
console.log(` Run \`ia-qa-discover login\` to log in by hand once, then scan again.`);
|
|
214
|
+
}
|
|
215
|
+
else if (result.session) {
|
|
216
|
+
console.log(`\n🔓 Scanned with the saved session (${result.session}).`);
|
|
217
|
+
}
|
|
218
|
+
// A loaded page is one state of a page. Where the input surface hides behind
|
|
219
|
+
// clicks, the field count above is honest only if it says so.
|
|
220
|
+
const openable = result.pages.reduce((n, p) => n + (p.exploration?.openable ?? 0), 0);
|
|
221
|
+
const explored = result.pages.some((p) => p.exploration?.explored);
|
|
222
|
+
if (!explored && openable > 0) {
|
|
223
|
+
console.log(`\n⚠ Loaded state only: ${openable} control(s) here can reveal more (menus, tabs, dialogs).`);
|
|
224
|
+
console.log(` The field count above is the input surface AT REST, not the whole one.`);
|
|
225
|
+
console.log(` Run \`ia-qa-discover scan --deep\` to open them and capture what they reveal.`);
|
|
226
|
+
}
|
|
227
|
+
else if (explored) {
|
|
228
|
+
const revealed = result.pages.reduce((n, p) => n + (p.exploration?.revealed ?? 0), 0);
|
|
229
|
+
const collided = result.pages.reduce((n, p) => n + (p.exploration?.collided ?? 0), 0);
|
|
230
|
+
const truncated = result.pages.filter((p) => p.exploration?.truncated).length;
|
|
231
|
+
console.log(`\n🔍 --deep opened ${openable} control(s): ${revealed} field(s) found that are not visible at load.`);
|
|
232
|
+
if (collided > 0) {
|
|
233
|
+
console.log(` ⚠ ${collided} reached but not captured — the selector already named another element in a different state.`);
|
|
234
|
+
}
|
|
235
|
+
if (truncated > 0)
|
|
236
|
+
console.log(` ⚠ ${truncated} page(s) hit the click budget, so their exploration is partial.`);
|
|
237
|
+
}
|
|
238
|
+
console.log(`\nWritten to ${result.surfacePath}`);
|
|
239
|
+
console.log(`Readable index: ${path.join(path.dirname(result.surfacePath), '_overview.md')}`);
|
|
240
|
+
if (writtenReport)
|
|
241
|
+
console.log(`HTML report: ${writtenReport}`);
|
|
242
|
+
// Silent unless this project already runs `ia-qa-heal` — see healingLink.ts.
|
|
243
|
+
for (const line of (0, healingLink_1.healingLinkLines)((0, healingLink_1.healingOverlap)(result.pages)))
|
|
244
|
+
console.log(line);
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/cli/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { CLASSIFICATION_FILENAME, CLASSIFICATION_SCHEMA } from '../ai/classify';
|
|
3
|
+
export { CLASSIFICATION_SCHEMA, CLASSIFICATION_FILENAME };
|
|
4
|
+
declare const HELP = "ia-qa-discover-ai \u2014 optional BYOK classification for a discovery capture\n\nWhat each entry point your app exposes is FOR, and how sensitive what it handles\nis. Reads .ia-qa-discovery/capture/ offline \u2014 it never opens a browser and never\ntouches your application. Your key, your model, your machine.\n\nEvery classification carries a sentence you read and a path into the capture that\nthis tool resolves and checks before keeping it. A claim that cites nothing, cites\nsomething that does not exist, or misdescribes what it cites, is DROPPED and the\ndrop is reported. That check proves the premise, never the conclusion: only you\ncan say whether the reading follows.\n\nNothing here is a gate. `ia-qa-discover` works fully without a key.\n\nUsage:\n ia-qa-discover-ai classify Classify every entry point in the capture.\n --page <name> just this page\n --json machine-readable output\n --dry-run show what would be sent, call nothing\n ia-qa-discover-ai models List the providers and models you can configure\n ia-qa-discover-ai version Print the installed version\n ia-qa-discover-ai help Show this help\n\nConfigure it in .ia-qa-discovery/config.json:\n\n \"ai\": {\n \"provider\": \"anthropic\",\n \"model\": \"claude-haiku-4-5\",\n \"apiKey\": { \"source\": \"env\", \"key\": \"ANTHROPIC_API_KEY\" }\n }\n\nThe key itself never goes in that file \u2014 only the name of the variable holding it.\n\nGlobal:\n --config <dir> Resolve .ia-qa-discovery/ under <dir> instead of the current directory\n";
|
|
5
|
+
declare function runClassify(args: string[]): Promise<void>;
|
|
6
|
+
declare function main(): Promise<void>;
|
|
7
|
+
export { HELP, main, runClassify };
|