@thecolonylab/hivemind 0.1.3 → 0.2.1
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/dist/commands/coverage.js +4 -2
- package/dist/commands/coverageLocal.js +1 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/explore.js +5 -2
- package/dist/commands/init.js +18 -3
- package/dist/commands/run.js +4 -0
- package/dist/commands/runLocal.js +2 -0
- package/dist/report/html.js +19 -0
- package/dist/serverClient.js +6 -6
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { checkCoverageLocally } from './coverageLocal.js';
|
|
|
6
6
|
import { saveTestFile, slugify } from '../testFile/write.js';
|
|
7
7
|
import { loadAllTestsFull } from '../testFile/loadAllTests.js';
|
|
8
8
|
import { renderCoveragePrComment } from '../report/prComment.js';
|
|
9
|
+
import { loadConfig } from '../config.js';
|
|
9
10
|
/**
|
|
10
11
|
* The actual diff-aware check: reads the real code diff (not just filenames), sends it — plus
|
|
11
12
|
* every local test with its full steps — to the server. The server figures out which known
|
|
@@ -23,6 +24,7 @@ export async function coverageCommand(opts) {
|
|
|
23
24
|
}
|
|
24
25
|
const diff = getFullDiff(cwd, base);
|
|
25
26
|
const localTests = await loadAllTestsFull(cwd);
|
|
27
|
+
const config = await loadConfig(cwd);
|
|
26
28
|
const serverUrl = process.env.HIVEMIND_SERVER_URL || 'https://app.thecolonylab.com';
|
|
27
29
|
console.log(`🐝 Scout reading the diff (${changedFiles.length} file${changedFiles.length === 1 ? '' : 's'} changed vs ${base})...`);
|
|
28
30
|
if (opts.local) {
|
|
@@ -31,8 +33,8 @@ export async function coverageCommand(opts) {
|
|
|
31
33
|
let result;
|
|
32
34
|
try {
|
|
33
35
|
result = opts.local
|
|
34
|
-
? await checkCoverageLocally({ serverUrl, diff, localTests, headed: opts.headed })
|
|
35
|
-
: await coverageOnServer(diff, localTests);
|
|
36
|
+
? await checkCoverageLocally({ serverUrl, diff, localTests, headed: opts.headed, projectId: config.projectId, projectName: config.projectName })
|
|
37
|
+
: await coverageOnServer(diff, localTests, config.projectId, config.projectName);
|
|
36
38
|
}
|
|
37
39
|
catch (err) {
|
|
38
40
|
console.error(err.message);
|
|
@@ -21,7 +21,7 @@ export async function checkCoverageLocally(opts) {
|
|
|
21
21
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
22
22
|
const socket = new WebSocket(wsUrl, { headers });
|
|
23
23
|
socket.on('open', () => {
|
|
24
|
-
socket.send(JSON.stringify({ type: 'start', diff: opts.diff, localTests: opts.localTests }));
|
|
24
|
+
socket.send(JSON.stringify({ type: 'start', diff: opts.diff, localTests: opts.localTests, projectId: opts.projectId, projectName: opts.projectName }));
|
|
25
25
|
});
|
|
26
26
|
socket.on('message', async (raw) => {
|
|
27
27
|
let msg;
|
package/dist/commands/create.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function createCommand(request, opts) {
|
|
|
10
10
|
const accountLabels = Object.keys(config.accounts || {});
|
|
11
11
|
let proposed;
|
|
12
12
|
try {
|
|
13
|
-
proposed = await createOnServer(request, opts.url, existingTests, accountLabels);
|
|
13
|
+
proposed = await createOnServer(request, opts.url, existingTests, accountLabels, config.projectId);
|
|
14
14
|
}
|
|
15
15
|
catch (err) {
|
|
16
16
|
console.error(err.message);
|
package/dist/commands/explore.js
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { exploreOnServer } from '../serverClient.js';
|
|
3
3
|
import { saveTestFile, slugify } from '../testFile/write.js';
|
|
4
4
|
import { listExistingTests } from '../testFile/listExisting.js';
|
|
5
|
+
import { loadConfig } from '../config.js';
|
|
5
6
|
/**
|
|
6
7
|
* A real multi-page crawl, not a one-shot snapshot — the server maps pages, follows doors, and
|
|
7
8
|
* proposes starter tests along the way. Existing tests are sent along (same pattern `create`
|
|
@@ -9,10 +10,12 @@ import { listExistingTests } from '../testFile/listExisting.js';
|
|
|
9
10
|
*/
|
|
10
11
|
export async function exploreCommand(url, opts) {
|
|
11
12
|
console.log(`🐝 Scout dispatched to ${url}...`);
|
|
12
|
-
const
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const existingTests = await listExistingTests(cwd);
|
|
15
|
+
const config = await loadConfig(cwd);
|
|
13
16
|
let proposed;
|
|
14
17
|
try {
|
|
15
|
-
proposed = await exploreOnServer(url, existingTests);
|
|
18
|
+
proposed = await exploreOnServer(url, existingTests, config.projectId, config.projectName);
|
|
16
19
|
}
|
|
17
20
|
catch (err) {
|
|
18
21
|
console.error(err.message);
|
package/dist/commands/init.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
2
|
+
import { dirname, join, basename } from 'node:path';
|
|
3
3
|
import { mkdir, readFile, writeFile, copyFile, access } from 'node:fs/promises';
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { randomBytes } from 'node:crypto';
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
// dist/commands/init.js -> package root (skill/ ships alongside dist/, see package.json "files")
|
|
@@ -70,16 +71,30 @@ export async function initCommand() {
|
|
|
70
71
|
await ensureGitignore(cwd);
|
|
71
72
|
const configPath = join(cwd, 'hivemind.config.json');
|
|
72
73
|
if (await exists(configPath)) {
|
|
73
|
-
|
|
74
|
+
// Patch in a projectId even for a config that predates this field — without it, every
|
|
75
|
+
// request to the server would look like it belongs to no project at all (or worse, get
|
|
76
|
+
// lumped in with other projects on the same account that also lack one).
|
|
77
|
+
const existing = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
78
|
+
if (existing.projectId) {
|
|
79
|
+
console.log('- hivemind.config.json already exists, left untouched');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
existing.projectId = randomBytes(8).toString('hex');
|
|
83
|
+
existing.projectName ??= basename(cwd);
|
|
84
|
+
await writeFile(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
|
|
85
|
+
console.log(`✓ hivemind.config.json — added a projectId (project name: "${existing.projectName}")`);
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
88
|
else {
|
|
76
89
|
const starter = {
|
|
90
|
+
projectId: randomBytes(8).toString('hex'),
|
|
91
|
+
projectName: basename(cwd),
|
|
77
92
|
environments: { default: 'http://localhost:3000' },
|
|
78
93
|
accounts: {},
|
|
79
94
|
areas: {},
|
|
80
95
|
};
|
|
81
96
|
await writeFile(configPath, JSON.stringify(starter, null, 2) + '\n', 'utf-8');
|
|
82
|
-
console.log(
|
|
97
|
+
console.log(`✓ hivemind.config.json created (project name: "${starter.projectName}") — edit environments.default to match your app`);
|
|
83
98
|
}
|
|
84
99
|
const agentsPath = join(cwd, 'AGENTS.md');
|
|
85
100
|
const template = await readFile(join(packageRoot, 'skill', 'AGENTS.md.template'), 'utf-8');
|
package/dist/commands/run.js
CHANGED
|
@@ -115,6 +115,8 @@ export async function runCommand(target, opts) {
|
|
|
115
115
|
memoryContext,
|
|
116
116
|
credential,
|
|
117
117
|
headed: opts.headed,
|
|
118
|
+
projectId: config.projectId,
|
|
119
|
+
projectName: config.projectName,
|
|
118
120
|
});
|
|
119
121
|
await writeFile(join(cwd, 'hive', 'last-run.json'), JSON.stringify({ reportUrl: reportPath, name: test.name }, null, 2)).catch(() => { });
|
|
120
122
|
results[index] = { name: test.name, result, reportUrl: reportPath };
|
|
@@ -130,6 +132,8 @@ export async function runCommand(target, opts) {
|
|
|
130
132
|
steps: resolvedSteps,
|
|
131
133
|
memoryContext,
|
|
132
134
|
credential,
|
|
135
|
+
projectId: config.projectId,
|
|
136
|
+
projectName: config.projectName,
|
|
133
137
|
});
|
|
134
138
|
const reportUrl = `${serverUrl}${reportPath}`;
|
|
135
139
|
await writeLocalMemory(cwd, updatedMemory).catch(() => { });
|
package/dist/report/html.js
CHANGED
|
@@ -35,6 +35,17 @@ function renderTraceRow(l) {
|
|
|
35
35
|
</span>
|
|
36
36
|
</li>`;
|
|
37
37
|
}
|
|
38
|
+
function renderBrowserLogRow(b) {
|
|
39
|
+
const isBad = b.level === 'error' || b.level === 'failed' || Number(b.level) >= 400;
|
|
40
|
+
return `
|
|
41
|
+
<li class="trace-row ${isBad ? 'fail' : 'ok'}">
|
|
42
|
+
<span class="trace-status">${b.type === 'network' ? '⇄' : '»'}</span>
|
|
43
|
+
<span class="trace-body">
|
|
44
|
+
<span class="trace-tool">${escapeHtml(b.type)} · ${escapeHtml(b.level)}</span>
|
|
45
|
+
<span class="trace-reasoning">${escapeHtml(b.text)}</span>
|
|
46
|
+
</span>
|
|
47
|
+
</li>`;
|
|
48
|
+
}
|
|
38
49
|
export function renderHtmlReport(testName, result) {
|
|
39
50
|
const passed = result.status === 'passed';
|
|
40
51
|
const statusWord = passed ? 'PASS' : result.status === 'error' ? 'ERROR' : 'FAIL';
|
|
@@ -48,6 +59,7 @@ export function renderHtmlReport(testName, result) {
|
|
|
48
59
|
const firstLabel = result.steps.find((s) => s.screenshot != null)?.text || result.screenshots[0]?.label || '';
|
|
49
60
|
const stepsHtml = result.steps.map((s, i) => renderStepRow(s, i === 0)).join('\n');
|
|
50
61
|
const traceHtml = result.toolLog.map(renderTraceRow).join('\n');
|
|
62
|
+
const browserLogHtml = result.browserLogs.map(renderBrowserLogRow).join('\n');
|
|
51
63
|
const galleryHtml = result.screenshots
|
|
52
64
|
.map((sc, i) => `<button class="thumb" data-shot="${i}" data-label="${escapeHtml(sc.label || '')}"><img src="${dataUris[i]}" loading="lazy" /></button>`)
|
|
53
65
|
.join('\n');
|
|
@@ -226,6 +238,13 @@ export function renderHtmlReport(testName, result) {
|
|
|
226
238
|
</details>`
|
|
227
239
|
: ''}
|
|
228
240
|
|
|
241
|
+
${result.browserLogs.length
|
|
242
|
+
? `<details class="trace">
|
|
243
|
+
<summary>Console & network (${result.browserLogs.length}) — errors and failed requests seen on the page during this run</summary>
|
|
244
|
+
<ul class="trace-list">${browserLogHtml}</ul>
|
|
245
|
+
</details>`
|
|
246
|
+
: ''}
|
|
247
|
+
|
|
229
248
|
<script>
|
|
230
249
|
const SHOTS = ${JSON.stringify(dataUris)};
|
|
231
250
|
const LABELS = ${JSON.stringify(result.screenshots.map((s) => s.label || ''))};
|
package/dist/serverClient.js
CHANGED
|
@@ -29,14 +29,14 @@ async function post(path, body) {
|
|
|
29
29
|
export async function runOnServer(input) {
|
|
30
30
|
return post('/api/runs', input);
|
|
31
31
|
}
|
|
32
|
-
export async function createOnServer(request, url, existingTests, accountLabels) {
|
|
33
|
-
return post('/api/create', { request, url, existingTests, accountLabels });
|
|
32
|
+
export async function createOnServer(request, url, existingTests, accountLabels, projectId) {
|
|
33
|
+
return post('/api/create', { request, url, existingTests, accountLabels, projectId });
|
|
34
34
|
}
|
|
35
|
-
export async function exploreOnServer(url, existingTests) {
|
|
36
|
-
return post('/api/explore', { url, existingTests });
|
|
35
|
+
export async function exploreOnServer(url, existingTests, projectId, projectName) {
|
|
36
|
+
return post('/api/explore', { url, existingTests, projectId, projectName });
|
|
37
37
|
}
|
|
38
|
-
export async function coverageOnServer(diff, localTests) {
|
|
39
|
-
return post('/api/coverage', { diff, localTests });
|
|
38
|
+
export async function coverageOnServer(diff, localTests, projectId, projectName) {
|
|
39
|
+
return post('/api/coverage', { diff, localTests, projectId, projectName });
|
|
40
40
|
}
|
|
41
41
|
export async function getDraftTest(code) {
|
|
42
42
|
const apiKey = await readApiKey();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thecolonylab/hivemind",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Forager — the CLI that runs on your machine and executes for the Hivemind brain. Describe a flow in plain English, an AI agent drives a real browser and reports pass/fail. No LLM key needed on this machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|