@jaimevalasek/aioson 1.4.0 → 1.5.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/CHANGELOG.md +31 -1
- package/LICENSE +661 -21
- package/README.md +3 -1
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- package/docs/pt/agentes.md +124 -10
- package/docs/pt/cenarios.md +46 -2
- package/docs/pt/comandos-cli.md +60 -1
- package/docs/pt/inicio-rapido.md +18 -2
- package/docs/pt/squad-dashboard.md +373 -0
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +9 -9
- package/package.json +2 -2
- package/src/backup-local.js +74 -0
- package/src/cli.js +98 -0
- package/src/commands/backup-local-cmd.js +25 -0
- package/src/commands/runtime.js +242 -0
- package/src/commands/setup-context.js +7 -2
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +52 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +37 -1
- package/src/commands/squad-validate.js +62 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/constants.js +8 -0
- package/src/context-writer.js +45 -1
- package/src/i18n/messages/en.js +127 -1
- package/src/i18n/messages/es.js +117 -0
- package/src/i18n/messages/fr.js +117 -0
- package/src/i18n/messages/pt-BR.js +126 -1
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/runtime-store.js +259 -2
- package/src/squad/external-session.js +180 -0
- package/src/squad/inter-squad.js +74 -0
- package/src/squad/recovery-context.js +201 -0
- package/src/squad/worktree-manager.js +114 -0
- package/src/squad-daemon.js +490 -0
- package/src/squad-dashboard/api.js +223 -0
- package/src/squad-dashboard/attachment-handler.js +93 -0
- package/src/squad-dashboard/context-monitor.js +157 -0
- package/src/squad-dashboard/execution-logs.js +115 -0
- package/src/squad-dashboard/hunk-review.js +209 -0
- package/src/squad-dashboard/metrics.js +133 -0
- package/src/squad-dashboard/process-monitor.js +125 -0
- package/src/squad-dashboard/renderer.js +858 -0
- package/src/squad-dashboard/server.js +232 -0
- package/src/squad-dashboard/styles.js +525 -0
- package/src/squad-dashboard/token-tracker.js +99 -0
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +4 -0
- package/template/.aioson/agents/architect.md +4 -0
- package/template/.aioson/agents/dev.md +120 -11
- package/template/.aioson/agents/deyvin.md +8 -0
- package/template/.aioson/agents/neo.md +152 -0
- package/template/.aioson/agents/orache.md +17 -0
- package/template/.aioson/agents/orchestrator.md +26 -0
- package/template/.aioson/agents/product.md +60 -12
- package/template/.aioson/agents/qa.md +1 -0
- package/template/.aioson/agents/setup.md +63 -19
- package/template/.aioson/agents/sheldon.md +603 -0
- package/template/.aioson/agents/squad.md +191 -0
- package/template/.aioson/agents/tester.md +254 -0
- package/template/.aioson/agents/ux-ui.md +12 -0
- package/template/.aioson/config.md +6 -0
- package/template/.aioson/locales/en/agents/analyst.md +8 -0
- package/template/.aioson/locales/en/agents/architect.md +8 -0
- package/template/.aioson/locales/en/agents/dev.md +66 -7
- package/template/.aioson/locales/en/agents/deyvin.md +8 -0
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/en/agents/qa.md +49 -0
- package/template/.aioson/locales/en/agents/setup.md +2 -1
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/es/agents/analyst.md +8 -0
- package/template/.aioson/locales/es/agents/architect.md +8 -0
- package/template/.aioson/locales/es/agents/dev.md +66 -7
- package/template/.aioson/locales/es/agents/deyvin.md +8 -0
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/qa.md +26 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -1
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +63 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/fr/agents/analyst.md +8 -0
- package/template/.aioson/locales/fr/agents/architect.md +8 -0
- package/template/.aioson/locales/fr/agents/dev.md +66 -7
- package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/qa.md +26 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +63 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
- package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +2 -1
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
- package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
- package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
- package/template/.aioson/skills/squad/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +19 -0
- package/template/.aioson/tasks/squad-design.md +28 -0
- package/template/.aioson/tasks/squad-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.claude/commands/aioson/agent/neo.md +5 -0
- package/template/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +1 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +3 -0
- package/template/CLAUDE.md +5 -2
- package/template/OPENCODE.md +2 -0
|
@@ -4,6 +4,34 @@ const fs = require('node:fs/promises');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { flattenGenomeBindings, mergeGenomeBindings } = require('../genomes/bindings');
|
|
6
6
|
|
|
7
|
+
const TIER_COSTS = {
|
|
8
|
+
powerful: { inputPer1k: 0.015, outputPer1k: 0.075 },
|
|
9
|
+
balanced: { inputPer1k: 0.003, outputPer1k: 0.015 },
|
|
10
|
+
fast: { inputPer1k: 0.0008, outputPer1k: 0.004 },
|
|
11
|
+
none: { inputPer1k: 0, outputPer1k: 0 }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function estimateRunCost(executors) {
|
|
15
|
+
if (!Array.isArray(executors) || executors.length === 0) return null;
|
|
16
|
+
let total = 0;
|
|
17
|
+
for (const ex of executors) {
|
|
18
|
+
const tier = ex.modelTier || (ex.usesLLM === false ? 'none' : 'balanced');
|
|
19
|
+
const cost = TIER_COSTS[tier] || TIER_COSTS.balanced;
|
|
20
|
+
total += (cost.inputPer1k * 2) + (cost.outputPer1k * 1);
|
|
21
|
+
}
|
|
22
|
+
return Math.round(total * 1000) / 1000;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildTierSummary(executors) {
|
|
26
|
+
if (!Array.isArray(executors) || executors.length === 0) return null;
|
|
27
|
+
const counts = {};
|
|
28
|
+
for (const ex of executors) {
|
|
29
|
+
const tier = ex.modelTier || (ex.usesLLM === false ? 'none' : 'balanced');
|
|
30
|
+
counts[tier] = (counts[tier] || 0) + 1;
|
|
31
|
+
}
|
|
32
|
+
return Object.entries(counts).map(([t, n]) => `${t}×${n}`).join(' / ');
|
|
33
|
+
}
|
|
34
|
+
|
|
7
35
|
const SQUADS_DIR = '.aioson/squads';
|
|
8
36
|
const AGENTS_ROOT = 'agents';
|
|
9
37
|
const OUTPUT_ROOT = 'output';
|
|
@@ -278,7 +306,9 @@ async function buildSquadRecordFromPackageDir(targetDir, slug) {
|
|
|
278
306
|
: outputExists && (await pathExists(path.join(targetDir, outputDir, 'session.html')))
|
|
279
307
|
? normalizeRel(path.join(outputDir, 'session.html'))
|
|
280
308
|
: '—',
|
|
281
|
-
mtime: latestHtml?.mtime || manifestStat?.mtime || null
|
|
309
|
+
mtime: latestHtml?.mtime || manifestStat?.mtime || null,
|
|
310
|
+
tierSummary: buildTierSummary(manifest.executors),
|
|
311
|
+
estimatedCost: estimateRunCost(manifest.executors)
|
|
282
312
|
};
|
|
283
313
|
}
|
|
284
314
|
|
|
@@ -410,6 +440,12 @@ async function runSquadStatus({ args, logger, t }) {
|
|
|
410
440
|
agent_count: squad.agentGenomes.length
|
|
411
441
|
})
|
|
412
442
|
);
|
|
443
|
+
if (squad.tierSummary) {
|
|
444
|
+
logger.log(t('squad_status.model_tiers', { value: squad.tierSummary }));
|
|
445
|
+
}
|
|
446
|
+
if (squad.estimatedCost != null) {
|
|
447
|
+
logger.log(t('squad_status.estimated_cost', { value: squad.estimatedCost.toFixed(3) }));
|
|
448
|
+
}
|
|
413
449
|
if (i < squads.length - 1) logger.log('');
|
|
414
450
|
}
|
|
415
451
|
|
|
@@ -64,6 +64,18 @@ async function validateStructure(projectDir, slug, manifest) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Check api_endpoints workers
|
|
68
|
+
const apiEndpoints = Array.isArray(manifest.api_endpoints) ? manifest.api_endpoints : [];
|
|
69
|
+
for (const ep of apiEndpoints) {
|
|
70
|
+
if (ep.worker) {
|
|
71
|
+
const workerDir = path.join(squadDir, 'workers', ep.worker);
|
|
72
|
+
if (!(await pathExists(workerDir))) {
|
|
73
|
+
const rel = path.relative(projectDir, workerDir).replace(/\\/g, '/');
|
|
74
|
+
errors.push(`api_endpoints: worker "${ep.worker}" not found at ${rel}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
// Check output dir (warning only)
|
|
68
80
|
const outputDir = path.join(projectDir, 'output', slug);
|
|
69
81
|
if (!(await pathExists(outputDir))) {
|
|
@@ -197,7 +209,56 @@ async function validateSemanticDeep(projectDir, slug, manifest) {
|
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
|
|
200
|
-
// 6.
|
|
212
|
+
// 6. Task decomposition validation
|
|
213
|
+
for (const exec of executors) {
|
|
214
|
+
const tasks = Array.isArray(exec.tasks) ? exec.tasks : [];
|
|
215
|
+
if (tasks.length > 0) {
|
|
216
|
+
const orders = tasks.map(t => t.order).sort((a, b) => a - b);
|
|
217
|
+
for (let i = 0; i < orders.length; i++) {
|
|
218
|
+
if (orders[i] !== i + 1) {
|
|
219
|
+
warnings.push(`Executor "${exec.slug}": task order is not sequential (expected ${i + 1}, got ${orders[i]})`);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 6b. Model tiering validation
|
|
227
|
+
for (const exec of executors) {
|
|
228
|
+
if (exec.usesLLM === false && exec.modelTier && exec.modelTier !== 'none') {
|
|
229
|
+
warnings.push(`Executor "${exec.slug}": usesLLM is false but modelTier is "${exec.modelTier}" (expected "none")`);
|
|
230
|
+
}
|
|
231
|
+
if (exec.type === 'worker' && exec.modelTier && exec.modelTier !== 'none') {
|
|
232
|
+
warnings.push(`Executor "${exec.slug}": type is "worker" but modelTier is "${exec.modelTier}" (expected "none")`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 7. Review loop validation
|
|
237
|
+
const workflows = Array.isArray(manifest.workflows) ? manifest.workflows : [];
|
|
238
|
+
const executorSlugs = executors.map(e => e.slug);
|
|
239
|
+
for (const wf of workflows) {
|
|
240
|
+
const phases = Array.isArray(wf.phases) ? wf.phases : [];
|
|
241
|
+
const phaseIds = phases.map(p => p.id);
|
|
242
|
+
for (const phase of phases) {
|
|
243
|
+
if (phase.review) {
|
|
244
|
+
const rv = phase.review;
|
|
245
|
+
if (rv.reviewer && !executorSlugs.includes(rv.reviewer)) {
|
|
246
|
+
errors.push(`Workflow "${wf.slug}" phase "${phase.id}": reviewer "${rv.reviewer}" is not a declared executor`);
|
|
247
|
+
}
|
|
248
|
+
if (rv.onReject && !phaseIds.includes(rv.onReject)) {
|
|
249
|
+
errors.push(`Workflow "${wf.slug}" phase "${phase.id}": onReject target "${rv.onReject}" is not a valid phase ID`);
|
|
250
|
+
}
|
|
251
|
+
if (rv.reviewer && rv.reviewer === phase.executor) {
|
|
252
|
+
warnings.push(`Workflow "${wf.slug}" phase "${phase.id}": reviewer should not be the same as the creator executor`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (phase.review && (!phase.vetoConditions || phase.vetoConditions.length === 0)) {
|
|
256
|
+
warnings.push(`Workflow "${wf.slug}" phase "${phase.id}": has review but no vetoConditions — consider adding veto guards`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 7. Readiness não contradiz blockers
|
|
201
262
|
if (manifest.readiness) {
|
|
202
263
|
for (const [dim, val] of Object.entries(manifest.readiness)) {
|
|
203
264
|
if (val && val.status === 'ready' && val.blocker) {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
|
+
const { WebhookServer } = require('../lib/webhook-server');
|
|
6
|
+
const { getCliVersionSync } = require('../version');
|
|
7
|
+
const { listWorkers, runWorker } = require('../worker-runner');
|
|
8
|
+
const {
|
|
9
|
+
loadSession,
|
|
10
|
+
appendTurn,
|
|
11
|
+
buildContextualInput,
|
|
12
|
+
cleanExpiredSessions
|
|
13
|
+
} = require('../squad/external-session');
|
|
14
|
+
|
|
15
|
+
const SQUADS_DIR = path.join('.aioson', 'squads');
|
|
16
|
+
|
|
17
|
+
async function discoverSquads(projectDir) {
|
|
18
|
+
const squadsDir = path.join(projectDir, SQUADS_DIR);
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = await fs.readdir(squadsDir, { withFileTypes: true });
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function executeSquad(projectDir, squadSlug, contextualInput) {
|
|
29
|
+
const workers = await listWorkers(projectDir, squadSlug);
|
|
30
|
+
if (workers.length === 0) {
|
|
31
|
+
throw new Error(`No workers found in squad "${squadSlug}"`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Run the first eligible worker as the squad entry point
|
|
35
|
+
for (const worker of workers) {
|
|
36
|
+
const result = await runWorker(
|
|
37
|
+
projectDir,
|
|
38
|
+
squadSlug,
|
|
39
|
+
worker.slug,
|
|
40
|
+
{ input: contextualInput },
|
|
41
|
+
{ triggerType: 'webhook' }
|
|
42
|
+
);
|
|
43
|
+
if (result.ok) {
|
|
44
|
+
const out = result.output;
|
|
45
|
+
return typeof out === 'string' ? out : JSON.stringify(out);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error(`All workers in squad "${squadSlug}" failed`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function handleStart(projectDir, options, { logger }) {
|
|
53
|
+
const port = parseInt(options.port || process.env.AIOSON_WEBHOOK_PORT || '3210', 10);
|
|
54
|
+
const token = options.token || process.env.AIOSON_WEBHOOK_TOKEN || '';
|
|
55
|
+
const ttlHours = parseInt(options.sessionTtl || process.env.AIOSON_SESSION_TTL_HOURS || '24', 10);
|
|
56
|
+
const version = getCliVersionSync();
|
|
57
|
+
|
|
58
|
+
if (!token) {
|
|
59
|
+
logger.error('Warning: AIOSON_WEBHOOK_TOKEN not set — server running without authentication.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Clean up expired sessions on startup
|
|
63
|
+
const cleaned = await cleanExpiredSessions(projectDir, ttlHours);
|
|
64
|
+
if (cleaned > 0) logger.log(`Cleaned ${cleaned} expired session(s).`);
|
|
65
|
+
|
|
66
|
+
const slugs = await discoverSquads(projectDir);
|
|
67
|
+
const squads = slugs.map(name => ({ name, timeout_ms: 120000 }));
|
|
68
|
+
|
|
69
|
+
const server = new WebhookServer({
|
|
70
|
+
port,
|
|
71
|
+
token,
|
|
72
|
+
squads,
|
|
73
|
+
version,
|
|
74
|
+
onTrigger: async ({ squad, input, session_id, metadata }) => {
|
|
75
|
+
// Load prior session context (if any)
|
|
76
|
+
const session = session_id
|
|
77
|
+
? await loadSession(projectDir, session_id, ttlHours)
|
|
78
|
+
: null;
|
|
79
|
+
|
|
80
|
+
const contextualInput = buildContextualInput(session, input);
|
|
81
|
+
|
|
82
|
+
// Save user turn before execution
|
|
83
|
+
if (session_id) {
|
|
84
|
+
await appendTurn(projectDir, session_id, 'user', input, metadata || {}, ttlHours);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const response = await executeSquad(projectDir, squad, contextualInput);
|
|
88
|
+
|
|
89
|
+
// Save assistant turn after execution
|
|
90
|
+
if (session_id) {
|
|
91
|
+
await appendTurn(projectDir, session_id, 'assistant', response, {}, ttlHours);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { response };
|
|
95
|
+
},
|
|
96
|
+
onQuery: async ({ squad, query, max_results }) => {
|
|
97
|
+
const raw = await executeSquad(projectDir, squad, query);
|
|
98
|
+
// Best-effort: if the output is JSON, parse and slice; otherwise wrap as single result
|
|
99
|
+
let results;
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
results = Array.isArray(parsed) ? parsed.slice(0, max_results) : [parsed];
|
|
103
|
+
} catch {
|
|
104
|
+
results = [{ text: raw }];
|
|
105
|
+
}
|
|
106
|
+
return { results };
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await server.start();
|
|
111
|
+
|
|
112
|
+
logger.log(`AIOSON squad webhook server started on port ${port}`);
|
|
113
|
+
if (squads.length > 0) {
|
|
114
|
+
logger.log(`Available squads: ${squads.map(s => s.name).join(', ')}`);
|
|
115
|
+
} else {
|
|
116
|
+
logger.log('No squads discovered in .aioson/squads/');
|
|
117
|
+
}
|
|
118
|
+
logger.log('Endpoints: POST /trigger GET /status/:run_id GET /health');
|
|
119
|
+
logger.log('Press Ctrl+C to stop.');
|
|
120
|
+
|
|
121
|
+
process.on('SIGINT', async () => {
|
|
122
|
+
await server.stop();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
});
|
|
125
|
+
process.on('SIGTERM', async () => {
|
|
126
|
+
await server.stop();
|
|
127
|
+
process.exit(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Keep the process alive until signal
|
|
131
|
+
await new Promise(() => {});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function handleConfig(options, { logger }) {
|
|
135
|
+
const channel = options.channel || 'whatsapp';
|
|
136
|
+
const squad = options.squad || 'atendimento';
|
|
137
|
+
const port = options.port || '3210';
|
|
138
|
+
|
|
139
|
+
logger.log(`OpenClaw configuration for squad "${squad}" via ${channel}:\n`);
|
|
140
|
+
logger.log(`hooks:\n ${channel}:\n auto_reply:\n - pattern: ".*"\n action: webhook\n url: "http://SEU_SERVIDOR:${port}/trigger"\n headers:\n Authorization: "Bearer \${AIOSON_WEBHOOK_TOKEN}"\n body_template: |\n {\n "squad": "${squad}",\n "input": "{{message.text}}",\n "session_id": "${channel}:{{message.from}}",\n "callback_url": "{{openclaw.callback_url}}",\n "metadata": {\n "channel": "${channel}",\n "phone": "{{message.from}}",\n "user_name": "{{message.contact_name}}"\n }\n }`);
|
|
141
|
+
|
|
142
|
+
return { ok: true };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function runSquadWebhook({ args, options, logger }) {
|
|
146
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
147
|
+
const sub = options.sub || 'start';
|
|
148
|
+
|
|
149
|
+
switch (sub) {
|
|
150
|
+
case 'start':
|
|
151
|
+
return handleStart(targetDir, options, { logger });
|
|
152
|
+
case 'config':
|
|
153
|
+
return handleConfig(options, { logger });
|
|
154
|
+
default:
|
|
155
|
+
logger.error(`Unknown subcommand: "${sub}". Use: start, config`);
|
|
156
|
+
return { ok: false };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { runSquadWebhook };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const {
|
|
5
|
+
loadWorkerConfig,
|
|
6
|
+
listWorkers,
|
|
7
|
+
runWorker,
|
|
8
|
+
scaffoldWorker
|
|
9
|
+
} = require('../worker-runner');
|
|
10
|
+
const {
|
|
11
|
+
openRuntimeDb,
|
|
12
|
+
insertWorkerRun,
|
|
13
|
+
listWorkerRuns
|
|
14
|
+
} = require('../runtime-store');
|
|
15
|
+
|
|
16
|
+
async function handleList(projectDir, squadSlug, { logger, t }) {
|
|
17
|
+
if (!squadSlug) {
|
|
18
|
+
logger.error(t('squad_worker.squad_required'));
|
|
19
|
+
return { ok: false };
|
|
20
|
+
}
|
|
21
|
+
const workers = await listWorkers(projectDir, squadSlug);
|
|
22
|
+
if (workers.length === 0) {
|
|
23
|
+
logger.log(t('squad_worker.no_workers'));
|
|
24
|
+
return { ok: true, workers: [] };
|
|
25
|
+
}
|
|
26
|
+
logger.log(`Workers for squad "${squadSlug}" (${workers.length}):`);
|
|
27
|
+
for (const w of workers) {
|
|
28
|
+
logger.log(` ${w.slug} [${w.type || 'manual'}] - ${w.name || w.slug}`);
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, workers };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function handleRun(projectDir, squadSlug, workerSlug, inputStr, { logger, t }) {
|
|
34
|
+
if (!squadSlug || !workerSlug) {
|
|
35
|
+
logger.error(t('squad_worker.run_usage'));
|
|
36
|
+
return { ok: false };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let inputPayload = {};
|
|
40
|
+
if (inputStr) {
|
|
41
|
+
try {
|
|
42
|
+
inputPayload = JSON.parse(inputStr);
|
|
43
|
+
} catch {
|
|
44
|
+
logger.error(t('squad_worker.invalid_input'));
|
|
45
|
+
return { ok: false, error: 'Invalid JSON input' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
logger.log(`Running worker "${workerSlug}" on squad "${squadSlug}"...`);
|
|
50
|
+
const result = await runWorker(projectDir, squadSlug, workerSlug, inputPayload, { triggerType: 'manual' });
|
|
51
|
+
|
|
52
|
+
// Log to runtime store
|
|
53
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: false });
|
|
54
|
+
if (handle) {
|
|
55
|
+
const { db } = handle;
|
|
56
|
+
try {
|
|
57
|
+
insertWorkerRun(db, {
|
|
58
|
+
squadSlug,
|
|
59
|
+
workerSlug,
|
|
60
|
+
triggerType: 'manual',
|
|
61
|
+
inputJson: JSON.stringify(inputPayload),
|
|
62
|
+
outputJson: result.ok ? JSON.stringify(result.output) : null,
|
|
63
|
+
status: result.ok ? 'completed' : 'failed',
|
|
64
|
+
errorMessage: result.ok ? null : result.error,
|
|
65
|
+
durationMs: result.durationMs || 0,
|
|
66
|
+
attempt: result.attempt || 1
|
|
67
|
+
});
|
|
68
|
+
} finally {
|
|
69
|
+
db.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (result.ok) {
|
|
74
|
+
logger.log(t('squad_worker.run_success', { worker: workerSlug }));
|
|
75
|
+
logger.log(JSON.stringify(result.output, null, 2));
|
|
76
|
+
} else {
|
|
77
|
+
logger.error(t('squad_worker.run_failed', { worker: workerSlug, error: result.error }));
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function handleTest(projectDir, squadSlug, workerSlug, { logger, t }) {
|
|
83
|
+
if (!squadSlug || !workerSlug) {
|
|
84
|
+
logger.error(t('squad_worker.test_usage'));
|
|
85
|
+
return { ok: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const config = await loadWorkerConfig(projectDir, squadSlug, workerSlug);
|
|
89
|
+
if (!config) {
|
|
90
|
+
logger.error(t('squad_worker.not_found', { worker: workerSlug }));
|
|
91
|
+
return { ok: false };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Build mock input from schema
|
|
95
|
+
const mockInput = {};
|
|
96
|
+
for (const [key, spec] of Object.entries(config.inputs || {})) {
|
|
97
|
+
if (spec.type === 'string') mockInput[key] = `test-${key}`;
|
|
98
|
+
else if (spec.type === 'number') mockInput[key] = 0;
|
|
99
|
+
else mockInput[key] = `test-${key}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
logger.log(`Testing worker "${workerSlug}" with mock input:`);
|
|
103
|
+
logger.log(JSON.stringify(mockInput, null, 2));
|
|
104
|
+
|
|
105
|
+
const result = await runWorker(projectDir, squadSlug, workerSlug, mockInput, {
|
|
106
|
+
triggerType: 'manual',
|
|
107
|
+
noRetry: true
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (result.ok) {
|
|
111
|
+
logger.log(t('squad_worker.test_passed', { worker: workerSlug }));
|
|
112
|
+
logger.log(JSON.stringify(result.output, null, 2));
|
|
113
|
+
} else {
|
|
114
|
+
logger.error(t('squad_worker.test_failed', { worker: workerSlug, error: result.error }));
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function handleLogs(projectDir, squadSlug, { logger, t }) {
|
|
120
|
+
if (!squadSlug) {
|
|
121
|
+
logger.error(t('squad_worker.squad_required'));
|
|
122
|
+
return { ok: false };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
126
|
+
if (!handle) {
|
|
127
|
+
logger.error(t('squad_worker.no_runtime'));
|
|
128
|
+
return { ok: false };
|
|
129
|
+
}
|
|
130
|
+
const { db } = handle;
|
|
131
|
+
try {
|
|
132
|
+
const runs = listWorkerRuns(db, squadSlug);
|
|
133
|
+
if (runs.length === 0) {
|
|
134
|
+
logger.log(t('squad_worker.no_logs'));
|
|
135
|
+
return { ok: true, runs: [] };
|
|
136
|
+
}
|
|
137
|
+
logger.log(`Worker runs for squad "${squadSlug}" (${runs.length}):`);
|
|
138
|
+
for (const run of runs) {
|
|
139
|
+
const icon = run.status === 'completed' ? '[ok]' : run.status === 'failed' ? '[!!]' : '[..]';
|
|
140
|
+
const duration = run.duration_ms ? `${run.duration_ms}ms` : '-';
|
|
141
|
+
logger.log(` ${icon} ${run.worker_slug} (${run.trigger_type}) ${duration} - ${run.created_at}`);
|
|
142
|
+
if (run.error_message) logger.log(` Error: ${run.error_message}`);
|
|
143
|
+
}
|
|
144
|
+
return { ok: true, runs };
|
|
145
|
+
} finally {
|
|
146
|
+
db.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function handleScaffold(projectDir, squadSlug, workerSlug, options, { logger, t }) {
|
|
151
|
+
if (!squadSlug || !workerSlug) {
|
|
152
|
+
logger.error(t('squad_worker.scaffold_usage'));
|
|
153
|
+
return { ok: false };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = await scaffoldWorker(projectDir, squadSlug, workerSlug, {
|
|
157
|
+
name: options.name || workerSlug,
|
|
158
|
+
triggerType: options.trigger || 'manual',
|
|
159
|
+
inputs: options.inputs ? options.inputs.split(',') : [],
|
|
160
|
+
outputs: options.outputs ? options.outputs.split(',') : [],
|
|
161
|
+
env: options.env ? options.env.split(',') : []
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
logger.log(t('squad_worker.scaffold_created', { worker: workerSlug, path: result.workerDir }));
|
|
165
|
+
return { ok: true, ...result };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function runSquadWorker({ args, options, logger, t }) {
|
|
169
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
170
|
+
const sub = options.sub || 'list';
|
|
171
|
+
const squadSlug = options.squad;
|
|
172
|
+
const workerSlug = options.worker;
|
|
173
|
+
|
|
174
|
+
switch (sub) {
|
|
175
|
+
case 'list':
|
|
176
|
+
return handleList(targetDir, squadSlug, { logger, t });
|
|
177
|
+
case 'run':
|
|
178
|
+
return handleRun(targetDir, squadSlug, workerSlug, options.input, { logger, t });
|
|
179
|
+
case 'test':
|
|
180
|
+
return handleTest(targetDir, squadSlug, workerSlug, { logger, t });
|
|
181
|
+
case 'logs':
|
|
182
|
+
return handleLogs(targetDir, squadSlug, { logger, t });
|
|
183
|
+
case 'scaffold':
|
|
184
|
+
return handleScaffold(targetDir, squadSlug, workerSlug, options, { logger, t });
|
|
185
|
+
default:
|
|
186
|
+
logger.error(t('squad_worker.unknown_sub', { sub }));
|
|
187
|
+
return { ok: false };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { runSquadWorker };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createWorktree, mergeWorktree, cleanupWorktree, listWorktrees } = require('../squad/worktree-manager');
|
|
5
|
+
|
|
6
|
+
async function runSquadWorktrees({ args, options, logger }) {
|
|
7
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
8
|
+
const squadSlug = options.squad || args[1];
|
|
9
|
+
|
|
10
|
+
if (!squadSlug) {
|
|
11
|
+
logger.error('Squad slug required. Use --squad <slug>');
|
|
12
|
+
return { ok: false };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// --cleanup [agent]
|
|
16
|
+
if (options.cleanup !== undefined) {
|
|
17
|
+
const agentSlug = typeof options.cleanup === 'string' ? options.cleanup : null;
|
|
18
|
+
if (agentSlug) {
|
|
19
|
+
const result = cleanupWorktree(projectDir, squadSlug, agentSlug, true);
|
|
20
|
+
logger.log(result.ok ? `Worktree for "${agentSlug}" removed.` : `Failed to remove worktree for "${agentSlug}".`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
// cleanup all for squad
|
|
24
|
+
const worktrees = listWorktrees(projectDir, squadSlug);
|
|
25
|
+
if (worktrees.length === 0) {
|
|
26
|
+
logger.log(`No worktrees found for squad "${squadSlug}".`);
|
|
27
|
+
return { ok: true };
|
|
28
|
+
}
|
|
29
|
+
for (const wt of worktrees) {
|
|
30
|
+
const result = cleanupWorktree(projectDir, squadSlug, wt.agentSlug, false);
|
|
31
|
+
logger.log(result.ok ? ` Removed: ${wt.agentSlug}` : ` Failed: ${wt.agentSlug}`);
|
|
32
|
+
}
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// List
|
|
37
|
+
const worktrees = listWorktrees(projectDir, squadSlug);
|
|
38
|
+
if (worktrees.length === 0) {
|
|
39
|
+
logger.log(`No worktrees found for squad "${squadSlug}".`);
|
|
40
|
+
return { ok: true, worktrees: [] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.log(`Worktrees for "${squadSlug}" (${worktrees.length}):`);
|
|
44
|
+
for (const wt of worktrees) {
|
|
45
|
+
logger.log(` ${wt.agentSlug} branch:${wt.branch} path:${wt.path}`);
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, worktrees };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runSquadMerge({ args, options, logger }) {
|
|
51
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
52
|
+
const squadSlug = options.squad || args[1];
|
|
53
|
+
const agentSlug = options.agent || args[2];
|
|
54
|
+
|
|
55
|
+
if (!squadSlug || !agentSlug) {
|
|
56
|
+
logger.error('Usage: aioson squad:merge <project> --squad <squad> --agent <agent>');
|
|
57
|
+
return { ok: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const autoMerge = options['no-auto'] !== true;
|
|
61
|
+
const result = mergeWorktree(projectDir, squadSlug, agentSlug, autoMerge);
|
|
62
|
+
|
|
63
|
+
if (result.ok) {
|
|
64
|
+
logger.log(`Merged branch ${result.branch} successfully.`);
|
|
65
|
+
} else if (result.reason === 'merge_conflict') {
|
|
66
|
+
logger.error(`Merge conflict on ${result.branch}. Resolve manually.`);
|
|
67
|
+
if (result.detail) logger.error(result.detail);
|
|
68
|
+
} else if (result.reason === 'auto_merge_disabled') {
|
|
69
|
+
logger.log(`Auto-merge disabled. Branch ready: ${result.branch}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { runSquadWorktrees, runSquadMerge };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { mapWebsite } = require('../web');
|
|
5
|
+
|
|
6
|
+
function normalizeBoolean(value, fallback = false) {
|
|
7
|
+
if (typeof value === 'boolean') return value;
|
|
8
|
+
if (value === undefined || value === null) return fallback;
|
|
9
|
+
const text = String(value).trim().toLowerCase();
|
|
10
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(text)) return true;
|
|
11
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(text)) return false;
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseInteger(value, fallback) {
|
|
16
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runWebMap({ args, options = {}, logger, t }) {
|
|
21
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
22
|
+
const url = String(options.url || '').trim();
|
|
23
|
+
if (!url) {
|
|
24
|
+
logger.error(t('web_map.url_missing'));
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return { ok: false, error: 'url_missing' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const maxDepth = Math.max(0, parseInteger(options.depth, 2));
|
|
30
|
+
const maxPages = Math.max(1, parseInteger(options['max-pages'], 25));
|
|
31
|
+
const sameOriginOnly = !normalizeBoolean(options['include-external'], false);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
logger.log(t('web_map.starting', { url }));
|
|
35
|
+
const result = await mapWebsite(url, { maxDepth, maxPages, sameOriginOnly });
|
|
36
|
+
const output = {
|
|
37
|
+
ok: true,
|
|
38
|
+
targetDir,
|
|
39
|
+
url: result.startUrl,
|
|
40
|
+
maxDepth,
|
|
41
|
+
maxPages,
|
|
42
|
+
sameOriginOnly,
|
|
43
|
+
pageCount: result.pageCount,
|
|
44
|
+
urls: result.urls,
|
|
45
|
+
pages: result.pages
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (options.json) return output;
|
|
49
|
+
|
|
50
|
+
logger.log(t('web_map.pages_found', { count: result.pageCount }));
|
|
51
|
+
for (const page of result.pages) {
|
|
52
|
+
logger.log(t('web_map.page_line', {
|
|
53
|
+
url: page.url,
|
|
54
|
+
depth: page.depth,
|
|
55
|
+
status: page.statusCode,
|
|
56
|
+
links: page.linkCount
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
logger.log(t('web_map.done'));
|
|
60
|
+
return output;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error(t('web_map.failed', { error: error.message }));
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return { ok: false, error: 'map_failed', detail: error.message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
runWebMap
|
|
70
|
+
};
|