@ia-qa/pal 0.1.0 → 0.3.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 +54 -14
- package/TUTORIAL.md +375 -0
- package/dist/cli/index.js +181 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +89 -0
- package/dist/mcp/server.js +250 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +2 -0
- package/dist/plan.js.map +1 -1
- package/dist/report.js +71 -19
- package/dist/report.js.map +1 -1
- package/dist/run.d.ts +65 -0
- package/dist/run.js +171 -0
- package/dist/run.js.map +1 -0
- package/dist/setup.d.ts +45 -0
- package/dist/setup.js +210 -0
- package/dist/setup.js.map +1 -0
- package/dist/state.d.ts +3 -0
- package/dist/state.js.map +1 -1
- package/dist/suite.d.ts +63 -0
- package/dist/suite.js +145 -0
- package/dist/suite.js.map +1 -0
- package/dist/tour.d.ts +44 -5
- package/dist/tour.js +92 -10
- package/dist/tour.js.map +1 -1
- package/package.json +6 -4
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const
|
|
37
|
+
const readline = __importStar(require("readline/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
5
39
|
const report_1 = require("../report");
|
|
40
|
+
const setup_1 = require("../setup");
|
|
41
|
+
const run_1 = require("../run");
|
|
6
42
|
const VERSION = require('../../package.json').version;
|
|
7
43
|
const HELP = `ia-qa-pal — a QA pal next to your team
|
|
8
44
|
|
|
@@ -13,23 +49,51 @@ Runs entirely on your machine with a local browser. No LLM, nothing uploaded.
|
|
|
13
49
|
[FOR AI AGENTS] You are driving this for a human. Read the whole report, the
|
|
14
50
|
"Not seen" section included: a clean tour over part of an app is not a clean
|
|
15
51
|
app, and you must say which part was not seen. A first tour establishes the
|
|
16
|
-
baseline — never report it as a pass.
|
|
17
|
-
|
|
52
|
+
baseline — never report it as a pass. Set a project up with flags only
|
|
53
|
+
(\`setup --url … --login … --yes\`), with answers the human gave you — never
|
|
54
|
+
guessed. Never run \`ia-qa-heal login\`: it waits for a human at a browser; ask
|
|
55
|
+
them. Never apply the repairs a tour proposes without the human's review.
|
|
18
56
|
|
|
19
57
|
Usage:
|
|
20
58
|
ia-qa-pal tour First run: every page on the surface, the first
|
|
21
59
|
batch in depth, checks, baseline.
|
|
22
60
|
Next runs: re-capture, compare, check.
|
|
61
|
+
With a test suite declared: its selectors are
|
|
62
|
+
read, repairs are proposed (never applied).
|
|
63
|
+
No config yet: asks the setup questions first
|
|
64
|
+
(in a terminal), otherwise says what is missing.
|
|
23
65
|
--more explore the next batch in depth instead
|
|
66
|
+
--suite also run the testCommand from config.json once,
|
|
67
|
+
with the capture hook, to learn which pages
|
|
68
|
+
the suite never visits
|
|
69
|
+
--background start the tour detached and return at once;
|
|
70
|
+
follow it with \`ia-qa-pal status\`
|
|
24
71
|
--batch <n> pages per depth batch (default 10)
|
|
25
72
|
--json the report as one JSON document on stdout
|
|
26
73
|
--session <file> a Playwright storageState to reuse
|
|
27
74
|
--config <dir> project holding .ia-qa/ (default: here)
|
|
28
75
|
|
|
29
|
-
|
|
30
|
-
|
|
76
|
+
ia-qa-pal status The tour running now (phase, progress) or the
|
|
77
|
+
last one (its report).
|
|
78
|
+
--json as one JSON document
|
|
79
|
+
--config <dir> project folder (default: here)
|
|
80
|
+
|
|
81
|
+
ia-qa-pal setup Write .ia-qa/config.json: the app address, how it
|
|
82
|
+
logs in, its pages (from sitemap.xml, else by
|
|
83
|
+
following its links), and your test suite.
|
|
84
|
+
Never overwrites a config.
|
|
85
|
+
--url <https://…> the app
|
|
86
|
+
--login none|browser|session none; by hand in a browser (then run
|
|
87
|
+
${setup_1.LOGIN_COMMAND});
|
|
88
|
+
or a storageState your suite already writes
|
|
89
|
+
--session <file> that storageState (with --login session)
|
|
90
|
+
--tests <dir>[,<dir>…] folders of your end-to-end suite
|
|
91
|
+
--test-command "<cmd>" the command that runs it (else detected)
|
|
92
|
+
--yes write without asking
|
|
93
|
+
--config <dir> project folder (default: here)
|
|
31
94
|
|
|
32
|
-
|
|
95
|
+
Exit codes: 0 nothing to decide · 1 something broke or needs a decision ·
|
|
96
|
+
2 could not answer (not set up, nothing mapped, refused, already running).
|
|
33
97
|
`;
|
|
34
98
|
function valueOf(args, flag) {
|
|
35
99
|
const i = args.indexOf(flag);
|
|
@@ -40,6 +104,61 @@ function valueOf(args, flag) {
|
|
|
40
104
|
throw new Error(`${flag} needs a value.`);
|
|
41
105
|
return v;
|
|
42
106
|
}
|
|
107
|
+
const interactive = () => !!process.stdin.isTTY && !!process.stderr.isTTY && !process.env.CI;
|
|
108
|
+
async function withPrompt(fn) {
|
|
109
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
110
|
+
try {
|
|
111
|
+
return await fn((q) => rl.question(q));
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
rl.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function loginFlag(args) {
|
|
118
|
+
const v = valueOf(args, '--login');
|
|
119
|
+
if (v === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
if (v !== 'none' && v !== 'browser' && v !== 'session')
|
|
122
|
+
throw new Error('--login takes none, browser or session.');
|
|
123
|
+
return v;
|
|
124
|
+
}
|
|
125
|
+
function printSetup(outcome, json) {
|
|
126
|
+
if (json) {
|
|
127
|
+
process.stdout.write(`${JSON.stringify({ schema: 'ia-qa-pal/setup@1', ...outcome }, null, 2)}\n`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!outcome.written) {
|
|
131
|
+
for (const p of outcome.problems)
|
|
132
|
+
process.stderr.write(`❌ ${p}\n`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
process.stderr.write(`\n✅ Written: ${outcome.file}\n`);
|
|
136
|
+
if (outcome.next === setup_1.LOGIN_COMMAND) {
|
|
137
|
+
process.stderr.write(`\nNext, by a person at a browser (an agent cannot do this):\n ${setup_1.LOGIN_COMMAND}\nthen:\n ia-qa-pal tour\n`);
|
|
138
|
+
if (outcome.pagesFrom !== 'sitemap') {
|
|
139
|
+
process.stderr.write('\nThe pages were found without logging in. Once logged in, list the ones behind the login:\n' +
|
|
140
|
+
' npx -p @ia-qa/self-healing ia-qa-heal discover --crawl --apply\n');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
process.stderr.write(`\nNext:\n ia-qa-pal tour\n`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function renderStatus(run, dir) {
|
|
148
|
+
if (!run)
|
|
149
|
+
return 'No tour has run on this project yet. Start one: ia-qa-pal tour\n';
|
|
150
|
+
if (run.status === 'running') {
|
|
151
|
+
const p = run.progress;
|
|
152
|
+
const where = p ? `${p.phase}${p.total ? ` ${p.done ?? 0}/${p.total}` : ''}${p.page ? ` · ${p.page}` : ''}` : 'starting';
|
|
153
|
+
return `⏳ A tour is running (pid ${run.pid}, since ${run.startedAt}) — ${where}\n log: ${(0, run_1.logFile)(dir)}\n`;
|
|
154
|
+
}
|
|
155
|
+
if (run.status === 'interrupted') {
|
|
156
|
+
return `⚠ The last tour stopped before finishing (pid ${run.pid}, started ${run.startedAt}, last seen ${run.updatedAt}). Nothing it did after that is reported. Run it again: ia-qa-pal tour\n`;
|
|
157
|
+
}
|
|
158
|
+
if (run.status === 'failed')
|
|
159
|
+
return `❌ The last tour failed (${run.finishedAt}): ${run.error}\n`;
|
|
160
|
+
return `Last tour finished ${run.finishedAt}.\n\n${run.report ? (0, report_1.renderReport)(run.report) : ''}`;
|
|
161
|
+
}
|
|
43
162
|
async function main() {
|
|
44
163
|
const args = process.argv.slice(2);
|
|
45
164
|
const command = args[0];
|
|
@@ -51,32 +170,74 @@ async function main() {
|
|
|
51
170
|
process.stdout.write(`${VERSION}\n`);
|
|
52
171
|
return;
|
|
53
172
|
}
|
|
54
|
-
if (command !== 'tour') {
|
|
55
|
-
process.stderr.write(`Unknown command "${command}". Run ia-qa-pal help.\n`);
|
|
56
|
-
process.exitCode = 2;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
173
|
const json = args.includes('--json');
|
|
60
174
|
try {
|
|
175
|
+
const dir = path.resolve(valueOf(args, '--config') ?? process.cwd());
|
|
176
|
+
if (command === 'setup') {
|
|
177
|
+
const tests = valueOf(args, '--tests');
|
|
178
|
+
const options = {
|
|
179
|
+
dir,
|
|
180
|
+
url: valueOf(args, '--url'),
|
|
181
|
+
login: loginFlag(args),
|
|
182
|
+
session: valueOf(args, '--session'),
|
|
183
|
+
tests: tests ? tests.split(',') : undefined,
|
|
184
|
+
testCommand: valueOf(args, '--test-command'),
|
|
185
|
+
yes: args.includes('--yes'),
|
|
186
|
+
};
|
|
187
|
+
const outcome = interactive() && !json ? await withPrompt((ask) => (0, setup_1.runSetup)({ ...options, ask })) : await (0, setup_1.runSetup)(options);
|
|
188
|
+
printSetup(outcome, json);
|
|
189
|
+
process.exitCode = outcome.written ? 0 : 2;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (command === 'status') {
|
|
193
|
+
const run = (0, run_1.readRun)(dir);
|
|
194
|
+
process.stdout.write(json ? `${JSON.stringify({ schema: 'ia-qa-pal/status@1', run }, null, 2)}\n` : renderStatus(run, dir));
|
|
195
|
+
process.exitCode = run?.status === 'done' ? run.exitCode ?? 0 : run ? 0 : 2;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (command !== 'tour') {
|
|
199
|
+
process.stderr.write(`Unknown command "${command}". Run ia-qa-pal help.\n`);
|
|
200
|
+
process.exitCode = 2;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
61
203
|
const batchRaw = valueOf(args, '--batch');
|
|
62
204
|
const batch = batchRaw === undefined ? undefined : Number(batchRaw);
|
|
63
205
|
if (batch !== undefined && (!Number.isInteger(batch) || batch < 1))
|
|
64
206
|
throw new Error('--batch needs a positive whole number.');
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
207
|
+
const tourOptions = { more: args.includes('--more'), suite: args.includes('--suite'), batch, session: valueOf(args, '--session') };
|
|
208
|
+
if (args.includes('--background')) {
|
|
209
|
+
const started = (0, run_1.startBackground)(dir, tourOptions);
|
|
210
|
+
if (json)
|
|
211
|
+
process.stdout.write(`${JSON.stringify({ schema: 'ia-qa-pal/background@1', ...started }, null, 2)}\n`);
|
|
212
|
+
else if (started.started)
|
|
213
|
+
process.stderr.write(`⏳ Tour started in the background (pid ${started.pid}). Follow it: ia-qa-pal status\n log: ${started.log}\n`);
|
|
214
|
+
else
|
|
215
|
+
process.stderr.write(`❌ ${started.reason}\n`);
|
|
216
|
+
process.exitCode = started.started ? 0 : 2;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!(0, setup_1.configExists)(dir) && interactive() && !json) {
|
|
220
|
+
process.stderr.write('This project is not set up yet — a few questions first.\n\n');
|
|
221
|
+
const outcome = await withPrompt((ask) => (0, setup_1.runSetup)({ dir, session: tourOptions.session, ask }));
|
|
222
|
+
printSetup(outcome, false);
|
|
223
|
+
if (!outcome.written || outcome.next !== 'ia-qa-pal tour') {
|
|
224
|
+
process.exitCode = outcome.written ? 0 : 2;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
process.stderr.write('\n');
|
|
228
|
+
}
|
|
229
|
+
const result = await (0, run_1.trackedTour)({ dir, ...tourOptions });
|
|
71
230
|
process.stdout.write(json ? `${JSON.stringify(result, null, 2)}\n` : (0, report_1.renderReport)(result));
|
|
72
231
|
process.exitCode = result.exitCode;
|
|
73
232
|
}
|
|
74
233
|
catch (err) {
|
|
75
234
|
const message = err instanceof Error ? err.message : String(err);
|
|
76
|
-
if (json)
|
|
77
|
-
process.stdout.write(`${JSON.stringify({
|
|
78
|
-
|
|
235
|
+
if (json) {
|
|
236
|
+
process.stdout.write(`${JSON.stringify({ error: message, ...(err instanceof run_1.TourAlreadyRunning ? { running: err.run } : {}), exitCode: 2 }, null, 2)}\n`);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
79
239
|
process.stderr.write(`❌ ${message}\n`);
|
|
240
|
+
}
|
|
80
241
|
process.exitCode = 2;
|
|
81
242
|
}
|
|
82
243
|
}
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { runTour } from './tour';
|
|
2
|
-
export type { TourOptions, TourResult } from './tour';
|
|
2
|
+
export type { TourOptions, TourResult, TourEvent } from './tour';
|
|
3
|
+
export { trackedTour, startBackground, readRun, activeRun, TourAlreadyRunning } from './run';
|
|
4
|
+
export type { RunRecord, RunView, BackgroundStart } from './run';
|
|
5
|
+
export type { SuiteInventory, RepairPlan, SuiteAudit, SuiteRun } from './suite';
|
|
3
6
|
export { renderReport } from './report';
|
|
7
|
+
export { runSetup, LOGIN_COMMAND } from './setup';
|
|
8
|
+
export type { SetupOptions, SetupOutcome, PalLogin } from './setup';
|
|
4
9
|
export { selectBatch, needingDepth, estimate, splitFindings, exitCodeFor, DEFAULT_BATCH } from './plan';
|
|
5
10
|
export type { Estimate, Pace, CheckSplit } from './plan';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_BATCH = exports.exitCodeFor = exports.splitFindings = exports.estimate = exports.needingDepth = exports.selectBatch = exports.renderReport = exports.runTour = void 0;
|
|
3
|
+
exports.DEFAULT_BATCH = exports.exitCodeFor = exports.splitFindings = exports.estimate = exports.needingDepth = exports.selectBatch = exports.LOGIN_COMMAND = exports.runSetup = exports.renderReport = exports.TourAlreadyRunning = exports.activeRun = exports.readRun = exports.startBackground = exports.trackedTour = exports.runTour = void 0;
|
|
4
4
|
var tour_1 = require("./tour");
|
|
5
5
|
Object.defineProperty(exports, "runTour", { enumerable: true, get: function () { return tour_1.runTour; } });
|
|
6
|
+
var run_1 = require("./run");
|
|
7
|
+
Object.defineProperty(exports, "trackedTour", { enumerable: true, get: function () { return run_1.trackedTour; } });
|
|
8
|
+
Object.defineProperty(exports, "startBackground", { enumerable: true, get: function () { return run_1.startBackground; } });
|
|
9
|
+
Object.defineProperty(exports, "readRun", { enumerable: true, get: function () { return run_1.readRun; } });
|
|
10
|
+
Object.defineProperty(exports, "activeRun", { enumerable: true, get: function () { return run_1.activeRun; } });
|
|
11
|
+
Object.defineProperty(exports, "TourAlreadyRunning", { enumerable: true, get: function () { return run_1.TourAlreadyRunning; } });
|
|
6
12
|
var report_1 = require("./report");
|
|
7
13
|
Object.defineProperty(exports, "renderReport", { enumerable: true, get: function () { return report_1.renderReport; } });
|
|
14
|
+
var setup_1 = require("./setup");
|
|
15
|
+
Object.defineProperty(exports, "runSetup", { enumerable: true, get: function () { return setup_1.runSetup; } });
|
|
16
|
+
Object.defineProperty(exports, "LOGIN_COMMAND", { enumerable: true, get: function () { return setup_1.LOGIN_COMMAND; } });
|
|
8
17
|
var plan_1 = require("./plan");
|
|
9
18
|
Object.defineProperty(exports, "selectBatch", { enumerable: true, get: function () { return plan_1.selectBatch; } });
|
|
10
19
|
Object.defineProperty(exports, "needingDepth", { enumerable: true, get: function () { return plan_1.needingDepth; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+BAAiC;AAAxB,+FAAA,OAAO,OAAA;AAEhB,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AACrB,+BAAwG;AAA/F,mGAAA,WAAW,OAAA;AAAE,oGAAA,YAAY,OAAA;AAAE,gGAAA,QAAQ,OAAA;AAAE,qGAAA,aAAa,OAAA;AAAE,mGAAA,WAAW,OAAA;AAAE,qGAAA,aAAa,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+BAAiC;AAAxB,+FAAA,OAAO,OAAA;AAEhB,6BAA6F;AAApF,kGAAA,WAAW,OAAA;AAAE,sGAAA,eAAe,OAAA;AAAE,8FAAA,OAAO,OAAA;AAAE,gGAAA,SAAS,OAAA;AAAE,yGAAA,kBAAkB,OAAA;AAG7E,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AACrB,iCAAkD;AAAzC,iGAAA,QAAQ,OAAA;AAAE,sGAAA,aAAa,OAAA;AAEhC,+BAAwG;AAA/F,mGAAA,WAAW,OAAA;AAAE,oGAAA,YAAY,OAAA;AAAE,gGAAA,QAAQ,OAAA;AAAE,qGAAA,aAAa,OAAA;AAAE,mGAAA,WAAW,OAAA;AAAE,qGAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare const TOOLS: ({
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
project_dir: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
url?: undefined;
|
|
13
|
+
login?: undefined;
|
|
14
|
+
session_file?: undefined;
|
|
15
|
+
tests?: undefined;
|
|
16
|
+
more?: undefined;
|
|
17
|
+
suite?: undefined;
|
|
18
|
+
batch?: undefined;
|
|
19
|
+
};
|
|
20
|
+
required?: undefined;
|
|
21
|
+
};
|
|
22
|
+
} | {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: string;
|
|
27
|
+
properties: {
|
|
28
|
+
url: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
login: {
|
|
33
|
+
type: string;
|
|
34
|
+
enum: string[];
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
session_file: {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
tests: {
|
|
42
|
+
type: string;
|
|
43
|
+
items: {
|
|
44
|
+
type: string;
|
|
45
|
+
};
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
project_dir: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
52
|
+
more?: undefined;
|
|
53
|
+
suite?: undefined;
|
|
54
|
+
batch?: undefined;
|
|
55
|
+
};
|
|
56
|
+
required: string[];
|
|
57
|
+
};
|
|
58
|
+
} | {
|
|
59
|
+
name: string;
|
|
60
|
+
description: string;
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: string;
|
|
63
|
+
properties: {
|
|
64
|
+
more: {
|
|
65
|
+
type: string;
|
|
66
|
+
description: string;
|
|
67
|
+
};
|
|
68
|
+
suite: {
|
|
69
|
+
type: string;
|
|
70
|
+
description: string;
|
|
71
|
+
};
|
|
72
|
+
batch: {
|
|
73
|
+
type: string;
|
|
74
|
+
description: string;
|
|
75
|
+
};
|
|
76
|
+
project_dir: {
|
|
77
|
+
type: string;
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
url?: undefined;
|
|
81
|
+
login?: undefined;
|
|
82
|
+
session_file?: undefined;
|
|
83
|
+
tests?: undefined;
|
|
84
|
+
};
|
|
85
|
+
required?: undefined;
|
|
86
|
+
};
|
|
87
|
+
})[];
|
|
88
|
+
export declare function projectDir(given: unknown, root?: string): string;
|
|
89
|
+
export declare function callTool(name: string, args: Record<string, any>, root?: string): Promise<unknown>;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.TOOLS = void 0;
|
|
38
|
+
exports.projectDir = projectDir;
|
|
39
|
+
exports.callTool = callTool;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const setup_1 = require("../setup");
|
|
43
|
+
const run_1 = require("../run");
|
|
44
|
+
const report_1 = require("../report");
|
|
45
|
+
/**
|
|
46
|
+
* @ia-qa/pal — local stdio MCP server.
|
|
47
|
+
*
|
|
48
|
+
* Dependency-free JSON-RPC 2.0 over stdio, the same shape as `@ia-qa/self-healing` and
|
|
49
|
+
* `@ia-qa/qa-discovery`. It exposes what the CLI exposes, so a person and an agent get one
|
|
50
|
+
* contract. Three restraints are load-bearing:
|
|
51
|
+
*
|
|
52
|
+
* - **A tour never blocks the call.** `pal_tour` starts it in the background and returns at
|
|
53
|
+
* once; `pal_status` reads progress and, when done, the report. A tour takes minutes, and a
|
|
54
|
+
* client that times out mid-call would leave the agent believing nothing ran.
|
|
55
|
+
* - **No tool takes a command.** The test command comes from config.json, written by a person.
|
|
56
|
+
* An agent steered by content it read cannot make this server run something else.
|
|
57
|
+
* - **No login tool.** `ia-qa-heal login` waits for a person at a browser.
|
|
58
|
+
*
|
|
59
|
+
* stdout carries the protocol only; all logs go to stderr.
|
|
60
|
+
*/
|
|
61
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
62
|
+
const SERVER_INFO = { name: 'ia-qa-pal', version: require('../../package.json').version };
|
|
63
|
+
const CLI_ENTRY = path.join(__dirname, '..', 'cli', 'index.js');
|
|
64
|
+
const PROJECT_DIR = {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Project folder holding .ia-qa/, relative to where this server runs. Default: the server’s own folder.',
|
|
67
|
+
};
|
|
68
|
+
exports.TOOLS = [
|
|
69
|
+
{
|
|
70
|
+
name: 'pal_project',
|
|
71
|
+
description: 'Where this project stands with ia-qa-pal: whether it is set up, its level (0 public pages, 1 with a login, 2 with its test suite), how many pages are configured, whether a baseline exists, and whether a tour is running. Read-only. Call it first.',
|
|
72
|
+
inputSchema: { type: 'object', properties: { project_dir: PROJECT_DIR } },
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'pal_setup',
|
|
76
|
+
description: 'Write .ia-qa/config.json for a project that has none: the app address, how it logs in, its pages (read from sitemap.xml, else by following its links — same host, GET only, at most 50 pages), and optionally the folders of its end-to-end suite. Never overwrites an existing config. Call it ONLY with answers the human gave you — ask them where the app lives and how it logs in; never guess. Login "browser" means the human must then run `' +
|
|
77
|
+
setup_1.LOGIN_COMMAND +
|
|
78
|
+
'` themselves: you cannot, it waits for a person at a browser. The command that runs the suite is detected from package.json or set by the human with the CLI — this tool never takes one.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
url: { type: 'string', description: 'The app, https://…' },
|
|
83
|
+
login: { type: 'string', enum: ['none', 'browser', 'session'], description: 'none; browser (the human logs in by hand); session (a Playwright storageState the suite already writes).' },
|
|
84
|
+
session_file: { type: 'string', description: 'With login "session": path of that storageState file, inside the project. Only its path is written.' },
|
|
85
|
+
tests: { type: 'array', items: { type: 'string' }, description: 'Folders of the end-to-end suite, relative to the project.' },
|
|
86
|
+
project_dir: PROJECT_DIR,
|
|
87
|
+
},
|
|
88
|
+
required: ['url', 'login'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'pal_tour',
|
|
93
|
+
description: 'Start a tour in the background and return at once — it takes minutes. First tour: every page on the surface, a batch explored in depth (menus, tabs, dialogs; every non-GET request blocked), checks, baseline. Later tours: re-capture, compare with the baseline, check; with a test suite declared, repairs to its selectors are proposed and never applied. Only one tour runs per project. Follow it with pal_status. Report the whole result to the human, including "Not seen": a first tour is a baseline, never a pass.',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
more: { type: 'boolean', description: 'Explore the next batch in depth instead of a regression tour. Refused after a BLOCK.' },
|
|
98
|
+
suite: { type: 'boolean', description: 'Also run the test command from config.json once, with the capture hook, to learn which pages the suite never visits. It runs the human’s own suite on this machine — ask before setting it.' },
|
|
99
|
+
batch: { type: 'integer', description: 'Pages per depth batch (default 10).' },
|
|
100
|
+
project_dir: PROJECT_DIR,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'pal_status',
|
|
106
|
+
description: 'The tour running now — phase and progress — or the last one: its full report once done, as JSON and as the text a human reads. A tour whose process died before finishing is reported as interrupted, never as running. Read-only.',
|
|
107
|
+
inputSchema: { type: 'object', properties: { project_dir: PROJECT_DIR } },
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
function projectDir(given, root = process.cwd()) {
|
|
111
|
+
const candidate = typeof given === 'string' && given.trim() ? given.trim() : '.';
|
|
112
|
+
const resolved = path.resolve(root, candidate);
|
|
113
|
+
const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
114
|
+
if (resolved !== root && !resolved.startsWith(rootWithSep)) {
|
|
115
|
+
throw new Error(`project_dir "${candidate}" resolves outside "${root}". Refusing: an MCP call may be steered by content the agent read.`);
|
|
116
|
+
}
|
|
117
|
+
return resolved;
|
|
118
|
+
}
|
|
119
|
+
function readJson(file) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function callTool(name, args, root = process.cwd()) {
|
|
128
|
+
const dir = projectDir(args.project_dir, root);
|
|
129
|
+
const config = readJson(path.join(dir, '.ia-qa', 'config.json'));
|
|
130
|
+
if (name === 'pal_project') {
|
|
131
|
+
const baseline = path.join(dir, '.ia-qa', 'baseline');
|
|
132
|
+
const run = (0, run_1.readRun)(dir);
|
|
133
|
+
return {
|
|
134
|
+
setUp: !!config,
|
|
135
|
+
...(config
|
|
136
|
+
? {
|
|
137
|
+
baseUrl: config.baseUrl,
|
|
138
|
+
pages: (config.pages ?? []).length,
|
|
139
|
+
level: (config.testPaths ?? []).length > 0 ? 2 : config.session || config.auth || fs.existsSync(path.join(dir, '.ia-qa', 'session.json')) ? 1 : 0,
|
|
140
|
+
testPaths: config.testPaths ?? [],
|
|
141
|
+
testCommand: config.testCommand ?? null,
|
|
142
|
+
baseline: fs.existsSync(baseline) && fs.readdirSync(baseline).some((f) => f.endsWith('.json')),
|
|
143
|
+
}
|
|
144
|
+
: { next: 'Ask the human where the app lives and how it logs in, then call pal_setup with their answers.' }),
|
|
145
|
+
tour: run ? { status: run.status, startedAt: run.startedAt, finishedAt: run.finishedAt ?? null } : null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (name === 'pal_setup') {
|
|
149
|
+
const login = args.login;
|
|
150
|
+
if (!['none', 'browser', 'session'].includes(login))
|
|
151
|
+
throw new Error('login must be none, browser or session.');
|
|
152
|
+
const session = typeof args.session_file === 'string' && args.session_file ? path.relative(dir, projectDir(args.session_file, dir)) : undefined;
|
|
153
|
+
const tests = Array.isArray(args.tests) ? args.tests.map(String) : undefined;
|
|
154
|
+
const lines = [];
|
|
155
|
+
const outcome = await (0, setup_1.runSetup)({ dir, url: String(args.url ?? ''), login, session, tests, yes: true, onProgress: (l) => lines.push(l) });
|
|
156
|
+
return { ...outcome, progress: lines };
|
|
157
|
+
}
|
|
158
|
+
if (name === 'pal_tour') {
|
|
159
|
+
const batch = Number.isInteger(args.batch) && args.batch > 0 ? args.batch : undefined;
|
|
160
|
+
const started = (0, run_1.startBackground)(dir, { more: !!args.more, suite: !!args.suite, batch }, CLI_ENTRY);
|
|
161
|
+
return started.started
|
|
162
|
+
? { ...started, next: 'Call pal_status to follow it. A tour takes minutes.' }
|
|
163
|
+
: { ...started, ...(!config ? { next: 'Not set up: ask the human where the app lives and how it logs in, then call pal_setup.' } : {}) };
|
|
164
|
+
}
|
|
165
|
+
if (name === 'pal_status') {
|
|
166
|
+
const run = (0, run_1.readRun)(dir);
|
|
167
|
+
if (!run)
|
|
168
|
+
return { run: null, text: 'No tour has run on this project yet. Call pal_tour.' };
|
|
169
|
+
const text = run.status === 'done' && run.report ? (0, report_1.renderReport)(run.report) : undefined;
|
|
170
|
+
return { run, log: (0, run_1.logFile)(dir), ...(text ? { text } : {}) };
|
|
171
|
+
}
|
|
172
|
+
throw new Error(`Unknown tool "${name}". Call tools/list to see available tools.`);
|
|
173
|
+
}
|
|
174
|
+
function send(message) {
|
|
175
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
176
|
+
}
|
|
177
|
+
async function handle(req) {
|
|
178
|
+
const { id, method, params } = req;
|
|
179
|
+
const isNotification = id === undefined || id === null;
|
|
180
|
+
try {
|
|
181
|
+
if (method === 'initialize') {
|
|
182
|
+
send({ jsonrpc: '2.0', id, result: { protocolVersion: params?.protocolVersion || PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: SERVER_INFO } });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (method === 'notifications/initialized' || method === 'initialized')
|
|
186
|
+
return;
|
|
187
|
+
if (method === 'ping') {
|
|
188
|
+
send({ jsonrpc: '2.0', id, result: {} });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (method === 'tools/list') {
|
|
192
|
+
send({ jsonrpc: '2.0', id, result: { tools: exports.TOOLS } });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (method === 'tools/call') {
|
|
196
|
+
try {
|
|
197
|
+
const result = await callTool(params?.name, params?.arguments || {});
|
|
198
|
+
send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } });
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
202
|
+
send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: ${message}` }], isError: true } });
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!isNotification)
|
|
207
|
+
send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
211
|
+
if (!isNotification)
|
|
212
|
+
send({ jsonrpc: '2.0', id, error: { code: -32603, message } });
|
|
213
|
+
else
|
|
214
|
+
process.stderr.write(`[ia-qa-pal] error handling ${method}: ${message}\n`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function main() {
|
|
218
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
219
|
+
process.stdout.write(SERVER_INFO.version + '\n');
|
|
220
|
+
process.exit(0);
|
|
221
|
+
}
|
|
222
|
+
// The setup verbs print; stdout is the protocol.
|
|
223
|
+
console.log = console.info = console.warn = (...a) => console.error(...a);
|
|
224
|
+
process.stderr.write('[ia-qa-pal] MCP server ready (stdio)\n');
|
|
225
|
+
let buffer = '';
|
|
226
|
+
process.stdin.setEncoding('utf8');
|
|
227
|
+
process.stdin.on('data', (chunk) => {
|
|
228
|
+
buffer += chunk;
|
|
229
|
+
let nl;
|
|
230
|
+
while ((nl = buffer.indexOf('\n')) !== -1) {
|
|
231
|
+
const line = buffer.slice(0, nl).trim();
|
|
232
|
+
buffer = buffer.slice(nl + 1);
|
|
233
|
+
if (!line)
|
|
234
|
+
continue;
|
|
235
|
+
let req;
|
|
236
|
+
try {
|
|
237
|
+
req = JSON.parse(line);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
process.stderr.write(`[ia-qa-pal] non-JSON line ignored: ${line.slice(0, 80)}\n`);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
void handle(req);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
process.stdin.on('end', () => process.exit(0));
|
|
247
|
+
}
|
|
248
|
+
if (require.main === module)
|
|
249
|
+
main();
|
|
250
|
+
//# sourceMappingURL=server.js.map
|