@jaimevalasek/aioson 1.7.2 → 1.8.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/CHANGELOG.md +35 -0
- package/README.md +153 -10
- package/docs/en/cli-reference.md +56 -1
- package/docs/en/i18n.md +18 -18
- package/docs/en/schemas/index.json +10 -0
- package/docs/en/schemas/parallel-assign.schema.json +9 -0
- package/docs/en/schemas/parallel-doctor.schema.json +36 -0
- package/docs/en/schemas/parallel-guard.schema.json +63 -0
- package/docs/en/schemas/parallel-merge.schema.json +84 -0
- package/docs/en/schemas/parallel-status.schema.json +91 -1
- package/docs/integrations/apps-publish-marketplace.md +94 -0
- package/docs/pt/README.md +9 -0
- package/docs/pt/agentes.md +324 -3
- package/docs/pt/clientes-ai.md +7 -3
- package/docs/pt/comandos-cli.md +160 -13
- package/docs/pt/compress-agents.md +304 -0
- package/docs/pt/design-docs-governance.md +59 -0
- package/docs/pt/feature-archive.md +191 -0
- package/docs/pt/genome-3.0-spec.md +115 -4
- package/docs/pt/genome-distribution.md +232 -0
- package/docs/pt/inicio-rapido.md +1 -0
- package/docs/pt/motor-hardening.md +492 -0
- package/docs/pt/runner-system.md +113 -0
- package/package.json +2 -1
- package/src/agent-manifests.js +66 -0
- package/src/agents.js +27 -7
- package/src/autonomy-policy.js +139 -0
- package/src/brain-query.js +161 -0
- package/src/cli.js +1377 -1099
- package/src/commands/agents.js +102 -7
- package/src/commands/artifact-validate.js +33 -4
- package/src/commands/auth.js +272 -0
- package/src/commands/brain-query.js +44 -0
- package/src/commands/briefing.js +344 -0
- package/src/commands/commit-prepare.js +547 -0
- package/src/commands/compress-agents.js +416 -0
- package/src/commands/context-health.js +4 -2
- package/src/commands/context-trim.js +17 -11
- package/src/commands/design-hybrid-options.js +3 -3
- package/src/commands/devlog-process.js +6 -4
- package/src/commands/dossier.js +423 -0
- package/src/commands/feature-archive.js +513 -0
- package/src/commands/feature-close.js +123 -18
- package/src/commands/gate-approve.js +198 -0
- package/src/commands/gate-check.js +24 -5
- package/src/commands/genome-doctor.js +166 -9
- package/src/commands/git-guard.js +170 -0
- package/src/commands/harness.js +121 -0
- package/src/commands/implementation-plan.js +47 -20
- package/src/commands/init.js +6 -2
- package/src/commands/install.js +6 -2
- package/src/commands/live.js +497 -56
- package/src/commands/locale-apply.js +9 -6
- package/src/commands/locale-diff.js +11 -112
- package/src/commands/mcp-doctor.js +2 -1
- package/src/commands/mcp-init.js +4 -10
- package/src/commands/memory.js +234 -0
- package/src/commands/parallel-assign.js +107 -27
- package/src/commands/parallel-doctor.js +416 -3
- package/src/commands/parallel-guard.js +241 -0
- package/src/commands/parallel-init.js +66 -4
- package/src/commands/parallel-merge.js +299 -0
- package/src/commands/parallel-status.js +147 -3
- package/src/commands/preflight.js +63 -4
- package/src/commands/qa-init.js +10 -5
- package/src/commands/revision.js +235 -0
- package/src/commands/scaffold-complete.js +188 -0
- package/src/commands/security-audit.js +275 -0
- package/src/commands/security-scan.js +376 -0
- package/src/commands/self-implement-loop.js +46 -2
- package/src/commands/setup-context.js +11 -10
- package/src/commands/squad-agent-create.js +51 -9
- package/src/commands/squad-investigate.js +53 -0
- package/src/commands/squad-plan.js +33 -1
- package/src/commands/squad-scaffold.js +4 -3
- package/src/commands/squad-score.js +71 -14
- package/src/commands/squad-status.js +22 -1
- package/src/commands/squad-validate.js +93 -2
- package/src/commands/store-genome.js +304 -0
- package/src/commands/store-skill.js +247 -0
- package/src/commands/store-squad.js +431 -0
- package/src/commands/store-system.js +392 -0
- package/src/commands/tool-capabilities.js +63 -0
- package/src/commands/update.js +3 -3
- package/src/commands/verify-gate.js +40 -0
- package/src/commands/workflow-execute.js +644 -155
- package/src/commands/workflow-harden.js +231 -0
- package/src/commands/workflow-heal.js +136 -0
- package/src/commands/workflow-next.js +460 -22
- package/src/commands/workflow-status.js +328 -138
- package/src/commands/workspace.js +144 -0
- package/src/constants.js +42 -75
- package/src/context-memory.js +133 -4
- package/src/context-writer.js +2 -1
- package/src/context.js +32 -2
- package/src/doctor.js +46 -6
- package/src/dossier/codemap-store.js +267 -0
- package/src/dossier/dossier-bootstrap.js +222 -0
- package/src/dossier/dossier-compact.js +159 -0
- package/src/dossier/lock.js +128 -0
- package/src/dossier/revision-store.js +313 -0
- package/src/dossier/schema.js +155 -0
- package/src/dossier/store.js +400 -0
- package/src/execution-gateway.js +3 -0
- package/src/friction-scanner.js +202 -0
- package/src/genome-schema.js +24 -1
- package/src/genomes.js +33 -0
- package/src/handoff-contract.js +363 -0
- package/src/handoff-validator.js +45 -0
- package/src/harness/circuit-breaker.js +135 -0
- package/src/i18n/messages/en.js +317 -22
- package/src/i18n/messages/es.js +259 -18
- package/src/i18n/messages/fr.js +260 -18
- package/src/i18n/messages/pt-BR.js +313 -22
- package/src/install-profile.js +0 -16
- package/src/installer.js +70 -6
- package/src/lib/git-commit-guard.js +691 -0
- package/src/lib/security/artifact-reader.js +167 -0
- package/src/lib/security/exit-codes.js +51 -0
- package/src/lib/security/findings-writer.js +176 -0
- package/src/lib/security/runtime-events.js +77 -0
- package/src/lib/security/secrets-regex.js +115 -0
- package/src/lib/store/security-scan.js +173 -0
- package/src/lib/terminal-checkbox.js +130 -0
- package/src/lib/tmux-launcher.js +163 -0
- package/src/lib/tool-capabilities.js +102 -0
- package/src/locales.js +12 -8
- package/src/parallel-workspace.js +756 -0
- package/src/parser.js +8 -1
- package/src/path-guard.js +47 -0
- package/src/preflight-engine.js +237 -26
- package/src/self-healing.js +142 -0
- package/src/session-handoff.js +111 -1
- package/src/squad/squad-scaffold.js +183 -19
- package/src/test-briefing.js +226 -0
- package/src/updater.js +1 -1
- package/src/utils.js +3 -0
- package/src/workflow-gates.js +185 -0
- package/template/.aioson/agents/analyst.md +76 -130
- package/template/.aioson/agents/architect.md +53 -86
- package/template/.aioson/agents/committer.md +161 -0
- package/template/.aioson/agents/cypher.md +252 -0
- package/template/.aioson/agents/dev.md +112 -628
- package/template/.aioson/agents/deyvin.md +33 -236
- package/template/.aioson/agents/discover.md +235 -0
- package/template/.aioson/agents/discovery-design-doc.md +17 -252
- package/template/.aioson/agents/genome.md +76 -26
- package/template/.aioson/agents/manifests/analyst.manifest.json +26 -0
- package/template/.aioson/agents/manifests/architect.manifest.json +23 -0
- package/template/.aioson/agents/manifests/committer.manifest.json +23 -0
- package/template/.aioson/agents/manifests/dev.manifest.json +37 -0
- package/template/.aioson/agents/manifests/orchestrator.manifest.json +30 -0
- package/template/.aioson/agents/manifests/pentester.manifest.json +39 -0
- package/template/.aioson/agents/manifests/pm.manifest.json +26 -0
- package/template/.aioson/agents/manifests/product.manifest.json +23 -0
- package/template/.aioson/agents/manifests/qa.manifest.json +25 -0
- package/template/.aioson/agents/manifests/setup.manifest.json +20 -0
- package/template/.aioson/agents/manifests/ux-ui.manifest.json +24 -0
- package/template/.aioson/agents/neo.md +5 -7
- package/template/.aioson/agents/orache.md +2 -6
- package/template/.aioson/agents/orchestrator.md +81 -182
- package/template/.aioson/agents/pentester.md +235 -0
- package/template/.aioson/agents/pm.md +40 -104
- package/template/.aioson/agents/product.md +99 -344
- package/template/.aioson/agents/profiler-enricher.md +57 -6
- package/template/.aioson/agents/profiler-forge.md +17 -7
- package/template/.aioson/agents/profiler-researcher.md +29 -6
- package/template/.aioson/agents/qa.md +168 -514
- package/template/.aioson/agents/setup.md +52 -278
- package/template/.aioson/agents/sheldon.md +122 -754
- package/template/.aioson/agents/site-forge.md +111 -1583
- package/template/.aioson/agents/squad.md +139 -2010
- package/template/.aioson/agents/tester.md +10 -0
- package/template/.aioson/agents/ux-ui.md +104 -812
- package/template/.aioson/agents/validator.md +69 -0
- package/template/.aioson/brains/scripts/query.js +5 -1
- package/template/.aioson/config/autonomy-protocol.json +43 -0
- package/template/.aioson/config.md +43 -15
- package/template/.aioson/constitution.md +36 -33
- package/template/.aioson/context/design-doc.md +136 -0
- package/template/.aioson/context/project-map.md +57 -0
- package/template/.aioson/design-docs/code-reuse.md +48 -0
- package/template/.aioson/design-docs/componentization.md +47 -0
- package/template/.aioson/design-docs/file-size.md +52 -0
- package/template/.aioson/design-docs/folder-structure.md +51 -0
- package/template/.aioson/design-docs/naming.md +54 -0
- package/template/.aioson/docs/LAYERS.md +12 -2
- package/template/.aioson/docs/dev/execution-discipline.md +106 -0
- package/template/.aioson/docs/dev/stack-conventions.md +83 -0
- package/template/.aioson/docs/deyvin/continuity-recovery.md +57 -0
- package/template/.aioson/docs/deyvin/debugging-escalation.md +30 -0
- package/template/.aioson/docs/deyvin/pair-execution.md +44 -0
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +36 -0
- package/template/.aioson/docs/product/conversation-playbook.md +116 -0
- package/template/.aioson/docs/product/prd-contract.md +107 -0
- package/template/.aioson/docs/product/quality-lens.md +57 -0
- package/template/.aioson/docs/product/research-loop.md +65 -0
- package/template/.aioson/docs/sheldon/enrichment-paths.md +134 -0
- package/template/.aioson/docs/sheldon/quality-lens.md +57 -0
- package/template/.aioson/docs/sheldon/research-loop.md +56 -0
- package/template/.aioson/docs/sheldon/web-intelligence.md +75 -0
- package/template/.aioson/docs/site-forge-build.md +195 -0
- package/template/.aioson/docs/site-forge-extraction.md +135 -0
- package/template/.aioson/docs/site-forge-qa.md +155 -0
- package/template/.aioson/docs/site-forge-recon.md +434 -0
- package/template/.aioson/docs/site-forge-transform.md +249 -0
- package/template/.aioson/docs/squad/content-output.md +91 -0
- package/template/.aioson/docs/squad/creation-flow.md +135 -0
- package/template/.aioson/docs/squad/domain-classification.md +117 -0
- package/template/.aioson/docs/squad/genome-bindings.md +47 -0
- package/template/.aioson/docs/squad/package-contract.md +234 -0
- package/template/.aioson/docs/squad/quality-lens.md +56 -0
- package/template/.aioson/docs/squad/research-loop.md +59 -0
- package/template/.aioson/docs/squad/session-operations.md +117 -0
- package/template/.aioson/docs/squad/workflow-quality.md +165 -0
- package/template/.aioson/docs/ux-ui/accessibility-audit.md +55 -0
- package/template/.aioson/docs/ux-ui/audit-mode.md +86 -0
- package/template/.aioson/docs/ux-ui/component-map.md +35 -0
- package/template/.aioson/docs/ux-ui/design-execution.md +111 -0
- package/template/.aioson/docs/ux-ui/design-gate.md +27 -0
- package/template/.aioson/docs/ux-ui/research-mode.md +39 -0
- package/template/.aioson/docs/ux-ui/site-delivery.md +156 -0
- package/template/.aioson/docs/ux-ui/token-contract.md +57 -0
- package/template/.aioson/genomes/copywriting.meta.json +48 -0
- package/template/.aioson/git-guard.json +11 -0
- package/template/.aioson/mcp/servers.md +0 -1
- package/template/.aioson/rules/agent-language-policy.md +93 -0
- package/template/.aioson/rules/aioson-context-boundary.md +63 -0
- package/template/.aioson/rules/canonical-path-contract.md +47 -0
- package/template/.aioson/rules/data-format-convention.md +24 -86
- package/template/.aioson/rules/disk-first-artifacts.md +44 -0
- package/template/.aioson/rules/output-brevity.md +44 -0
- package/template/.aioson/rules/prd-section-ownership.md +49 -0
- package/template/.aioson/rules/security-baseline.md +139 -0
- package/template/.aioson/rules/spec-level-ownership.md +61 -0
- package/template/.aioson/rules/squad-driver-pattern.md +81 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +24 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +30 -0
- package/template/.aioson/skills/process/secure-tdd/SKILL.md +97 -0
- package/template/.aioson/skills/process/secure-tdd/references/nextjs.md +81 -0
- package/template/.aioson/skills/process/secure-tdd/references/node-express.md +91 -0
- package/template/.aioson/skills/process/secure-tdd/references/planned-stacks.md +33 -0
- package/template/.aioson/skills/static/harness-validate/SKILL.md +46 -0
- package/template/.aioson/skills/static/web-research-cache.md +3 -0
- package/template/.aioson/tasks/squad-create.md +35 -8
- package/template/.aioson/tasks/squad-design.md +50 -2
- package/template/.aioson/tasks/squad-investigate.md +14 -1
- package/template/.claude/commands/aioson/agent/committer.md +5 -0
- package/template/.claude/commands/aioson/agent/copywriter.md +5 -0
- package/template/.claude/commands/aioson/agent/cypher.md +5 -0
- package/template/.claude/commands/aioson/agent/pair.md +5 -0
- package/template/.claude/commands/aioson/agent/validator.md +5 -0
- package/template/.gemini/commands/aios-analyst.toml +6 -3
- package/template/.gemini/commands/aios-architect.toml +7 -6
- package/template/.gemini/commands/aios-committer.toml +7 -0
- package/template/.gemini/commands/aios-copywriter.toml +7 -0
- package/template/.gemini/commands/aios-cypher.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -7
- package/template/.gemini/commands/aios-deyvin.toml +6 -5
- package/template/.gemini/commands/aios-discovery-design-doc.toml +6 -3
- package/template/.gemini/commands/aios-genome.toml +7 -0
- package/template/.gemini/commands/aios-neo.toml +5 -3
- package/template/.gemini/commands/aios-orache.toml +7 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -7
- package/template/.gemini/commands/aios-pair.toml +6 -5
- package/template/.gemini/commands/aios-pm.toml +8 -7
- package/template/.gemini/commands/aios-product.toml +5 -3
- package/template/.gemini/commands/aios-qa.toml +6 -5
- package/template/.gemini/commands/aios-setup.toml +5 -2
- package/template/.gemini/commands/aios-sheldon.toml +7 -0
- package/template/.gemini/commands/aios-site-forge.toml +7 -0
- package/template/.gemini/commands/aios-squad.toml +7 -0
- package/template/.gemini/commands/aios-tester.toml +6 -5
- package/template/.gemini/commands/aios-ux-ui.toml +8 -7
- package/template/.gemini/commands/aios-validator.toml +7 -0
- package/template/AGENTS.md +12 -1
- package/template/CLAUDE.md +5 -1
- package/template/.aioson/locales/en/agents/analyst.md +0 -244
- package/template/.aioson/locales/en/agents/architect.md +0 -245
- package/template/.aioson/locales/en/agents/dev.md +0 -397
- package/template/.aioson/locales/en/agents/deyvin.md +0 -137
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +0 -27
- package/template/.aioson/locales/en/agents/genome.md +0 -212
- package/template/.aioson/locales/en/agents/neo.md +0 -8
- package/template/.aioson/locales/en/agents/orache.md +0 -6
- package/template/.aioson/locales/en/agents/orchestrator.md +0 -189
- package/template/.aioson/locales/en/agents/pair.md +0 -5
- package/template/.aioson/locales/en/agents/pm.md +0 -84
- package/template/.aioson/locales/en/agents/product.md +0 -378
- package/template/.aioson/locales/en/agents/profiler-enricher.md +0 -5
- package/template/.aioson/locales/en/agents/profiler-forge.md +0 -5
- package/template/.aioson/locales/en/agents/profiler-researcher.md +0 -5
- package/template/.aioson/locales/en/agents/qa.md +0 -270
- package/template/.aioson/locales/en/agents/setup.md +0 -421
- package/template/.aioson/locales/en/agents/sheldon.md +0 -455
- package/template/.aioson/locales/en/agents/squad.md +0 -449
- package/template/.aioson/locales/en/agents/tester.md +0 -6
- package/template/.aioson/locales/en/agents/ux-ui.md +0 -668
- package/template/.aioson/locales/es/agents/analyst.md +0 -225
- package/template/.aioson/locales/es/agents/architect.md +0 -245
- package/template/.aioson/locales/es/agents/dev.md +0 -370
- package/template/.aioson/locales/es/agents/deyvin.md +0 -99
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +0 -21
- package/template/.aioson/locales/es/agents/genome.md +0 -104
- package/template/.aioson/locales/es/agents/neo.md +0 -50
- package/template/.aioson/locales/es/agents/orache.md +0 -105
- package/template/.aioson/locales/es/agents/orchestrator.md +0 -194
- package/template/.aioson/locales/es/agents/pair.md +0 -7
- package/template/.aioson/locales/es/agents/pm.md +0 -90
- package/template/.aioson/locales/es/agents/product.md +0 -372
- package/template/.aioson/locales/es/agents/profiler-enricher.md +0 -7
- package/template/.aioson/locales/es/agents/profiler-forge.md +0 -7
- package/template/.aioson/locales/es/agents/profiler-researcher.md +0 -7
- package/template/.aioson/locales/es/agents/qa.md +0 -198
- package/template/.aioson/locales/es/agents/setup.md +0 -405
- package/template/.aioson/locales/es/agents/sheldon.md +0 -309
- package/template/.aioson/locales/es/agents/squad.md +0 -532
- package/template/.aioson/locales/es/agents/tester.md +0 -9
- package/template/.aioson/locales/es/agents/ux-ui.md +0 -212
- package/template/.aioson/locales/fr/agents/analyst.md +0 -225
- package/template/.aioson/locales/fr/agents/architect.md +0 -245
- package/template/.aioson/locales/fr/agents/dev.md +0 -370
- package/template/.aioson/locales/fr/agents/deyvin.md +0 -99
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +0 -21
- package/template/.aioson/locales/fr/agents/genome.md +0 -104
- package/template/.aioson/locales/fr/agents/neo.md +0 -50
- package/template/.aioson/locales/fr/agents/orache.md +0 -106
- package/template/.aioson/locales/fr/agents/orchestrator.md +0 -194
- package/template/.aioson/locales/fr/agents/pair.md +0 -7
- package/template/.aioson/locales/fr/agents/pm.md +0 -90
- package/template/.aioson/locales/fr/agents/product.md +0 -372
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +0 -7
- package/template/.aioson/locales/fr/agents/profiler-forge.md +0 -7
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +0 -7
- package/template/.aioson/locales/fr/agents/qa.md +0 -198
- package/template/.aioson/locales/fr/agents/setup.md +0 -405
- package/template/.aioson/locales/fr/agents/sheldon.md +0 -309
- package/template/.aioson/locales/fr/agents/squad.md +0 -532
- package/template/.aioson/locales/fr/agents/tester.md +0 -9
- package/template/.aioson/locales/fr/agents/ux-ui.md +0 -212
- package/template/.aioson/locales/pt-BR/agents/analyst.md +0 -319
- package/template/.aioson/locales/pt-BR/agents/architect.md +0 -284
- package/template/.aioson/locales/pt-BR/agents/dev.md +0 -483
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +0 -184
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +0 -198
- package/template/.aioson/locales/pt-BR/agents/genome.md +0 -297
- package/template/.aioson/locales/pt-BR/agents/neo.md +0 -208
- package/template/.aioson/locales/pt-BR/agents/orache.md +0 -137
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +0 -324
- package/template/.aioson/locales/pt-BR/agents/pair.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/pm.md +0 -182
- package/template/.aioson/locales/pt-BR/agents/product.md +0 -466
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/qa.md +0 -300
- package/template/.aioson/locales/pt-BR/agents/setup.md +0 -533
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +0 -323
- package/template/.aioson/locales/pt-BR/agents/squad.md +0 -1330
- package/template/.aioson/locales/pt-BR/agents/tester.md +0 -449
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +0 -669
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
|
|
5
|
+
// ── Extension allowlists ─────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const ALLOWED_EXTENSIONS = {
|
|
8
|
+
genome: new Set(['.md', '.json']),
|
|
9
|
+
skill: new Set(['.md', '.json', '.yaml', '.yml', '.txt']),
|
|
10
|
+
squad: new Set(['.md', '.json', '.yaml', '.yml', '.txt'])
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// ── Size limits ──────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const MAX_FILE_BYTES = 512 * 1024; // 512 KB per file
|
|
16
|
+
const MAX_PACKAGE_BYTES = 5 * 1024 * 1024; // 5 MB total
|
|
17
|
+
|
|
18
|
+
// ── Malicious pattern detection ──────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Patterns that flag a file for review.
|
|
22
|
+
* Each entry: { id, pattern, description }
|
|
23
|
+
* All patterns are applied to raw file content (string).
|
|
24
|
+
*/
|
|
25
|
+
const THREAT_PATTERNS = [
|
|
26
|
+
// Shell execution
|
|
27
|
+
{ id: 'shell_exec', pattern: /\$\(.*\)|`[^`]{0,200}`/, description: 'shell command substitution' },
|
|
28
|
+
{ id: 'curl_pipe', pattern: /curl\s+.*\|\s*(ba)?sh/i, description: 'curl pipe to shell' },
|
|
29
|
+
{ id: 'wget_pipe', pattern: /wget\s+.*-O\s*-\s*\|\s*(ba)?sh/i, description: 'wget pipe to shell' },
|
|
30
|
+
{ id: 'exec_call', pattern: /\bexec\s*\(/, description: 'exec() call' },
|
|
31
|
+
{ id: 'eval_call', pattern: /\beval\s*\(/, description: 'eval() call' },
|
|
32
|
+
{ id: 'spawn_call', pattern: /child_process|\.spawn\s*\(|\.execSync\s*\(/, description: 'child_process / spawn' },
|
|
33
|
+
|
|
34
|
+
// Encoded payloads
|
|
35
|
+
{ id: 'long_base64', pattern: /[A-Za-z0-9+/]{200,}={0,2}/, description: 'long base64 blob (potential encoded payload)' },
|
|
36
|
+
{ id: 'hex_payload', pattern: /\\x[0-9a-fA-F]{2}(\\x[0-9a-fA-F]{2}){15,}/, description: 'hex-encoded payload sequence' },
|
|
37
|
+
|
|
38
|
+
// Network calls inside markdown/config
|
|
39
|
+
{ id: 'js_fetch', pattern: /\bfetch\s*\(\s*['"`]https?:\/\//, description: 'fetch() to external URL' },
|
|
40
|
+
{ id: 'require_http', pattern: /require\s*\(\s*['"`]https?:\/\//, description: 'require() from HTTP URL' },
|
|
41
|
+
{ id: 'dynamic_import', pattern: /import\s*\(\s*['"`]https?:\/\//, description: 'dynamic import from HTTP URL' },
|
|
42
|
+
|
|
43
|
+
// Filesystem destructive ops
|
|
44
|
+
{ id: 'rm_rf', pattern: /rm\s+-rf?\s+[/~]/, description: 'rm -rf on root or home' },
|
|
45
|
+
{ id: 'mkfs', pattern: /\bmkfs\b|\bdd\s+if=\/dev\//, description: 'mkfs or dd on device' },
|
|
46
|
+
|
|
47
|
+
// Crypto mining markers
|
|
48
|
+
{ id: 'miner_pool', pattern: /stratum\+tcp:\/\/|minexmr\.com|xmrig/i, description: 'crypto mining pool reference' },
|
|
49
|
+
|
|
50
|
+
// Exfiltration patterns
|
|
51
|
+
{ id: 'env_exfil', pattern: /process\.env\b.*\b(fetch|axios|request)\b/, description: 'env vars sent over network' },
|
|
52
|
+
{ id: 'ssh_key_path', pattern: /\.ssh\/id_rsa|\.ssh\/id_ed25519/, description: 'SSH private key path reference' },
|
|
53
|
+
|
|
54
|
+
// Obfuscation
|
|
55
|
+
{ id: 'atob_chain', pattern: /atob\s*\(\s*atob/, description: 'double-decoded base64 (obfuscation)' },
|
|
56
|
+
{ id: 'charcode_concat', pattern: /String\.fromCharCode\s*\(\s*\d[\d,\s]{30,}\)/, description: 'String.fromCharCode long sequence' }
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Scan a single file's content for threat patterns.
|
|
61
|
+
* Returns array of { id, description, line } for each match.
|
|
62
|
+
*/
|
|
63
|
+
function scanContent(content) {
|
|
64
|
+
const findings = [];
|
|
65
|
+
const lines = content.split('\n');
|
|
66
|
+
|
|
67
|
+
for (const { id, pattern, description } of THREAT_PATTERNS) {
|
|
68
|
+
// Check full content first (for multi-char patterns)
|
|
69
|
+
if (!pattern.test(content)) continue;
|
|
70
|
+
|
|
71
|
+
// Find which line triggered it
|
|
72
|
+
let lineNum = null;
|
|
73
|
+
for (let i = 0; i < lines.length; i++) {
|
|
74
|
+
if (pattern.test(lines[i])) {
|
|
75
|
+
lineNum = i + 1;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
findings.push({ id, description, line: lineNum });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return findings;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Scan a files map { relPath: content } for security issues.
|
|
90
|
+
*
|
|
91
|
+
* @param {Record<string, string>} files
|
|
92
|
+
* @param {'genome'|'skill'|'squad'} packageType
|
|
93
|
+
* @returns {{ ok: boolean, errors: string[], warnings: string[], hash: string }}
|
|
94
|
+
*/
|
|
95
|
+
function scanPackage(files, packageType) {
|
|
96
|
+
const allowedExts = ALLOWED_EXTENSIONS[packageType] ?? ALLOWED_EXTENSIONS.squad;
|
|
97
|
+
const errors = [];
|
|
98
|
+
const warnings = [];
|
|
99
|
+
let totalBytes = 0;
|
|
100
|
+
|
|
101
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
102
|
+
if (typeof content !== 'string') continue;
|
|
103
|
+
|
|
104
|
+
const ext = relPath.includes('.') ? `.${relPath.split('.').pop().toLowerCase()}` : '';
|
|
105
|
+
const bytes = Buffer.byteLength(content, 'utf8');
|
|
106
|
+
totalBytes += bytes;
|
|
107
|
+
|
|
108
|
+
// Extension check
|
|
109
|
+
if (!allowedExts.has(ext)) {
|
|
110
|
+
errors.push(`Blocked file type: "${relPath}" (extension "${ext}" not allowed for ${packageType} packages)`);
|
|
111
|
+
continue; // don't scan content of blocked files
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Per-file size check
|
|
115
|
+
if (bytes > MAX_FILE_BYTES) {
|
|
116
|
+
errors.push(`File too large: "${relPath}" (${(bytes / 1024).toFixed(0)} KB, limit is ${MAX_FILE_BYTES / 1024} KB)`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Threat patterns
|
|
120
|
+
const findings = scanContent(content);
|
|
121
|
+
for (const finding of findings) {
|
|
122
|
+
const loc = finding.line ? `:${finding.line}` : '';
|
|
123
|
+
warnings.push(`Suspicious pattern in "${relPath}"${loc} — ${finding.description} [${finding.id}]`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Total package size
|
|
128
|
+
if (totalBytes > MAX_PACKAGE_BYTES) {
|
|
129
|
+
errors.push(`Package too large: ${(totalBytes / 1024 / 1024).toFixed(2)} MB (limit is ${MAX_PACKAGE_BYTES / 1024 / 1024} MB)`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Deterministic hash of the full package contents
|
|
133
|
+
const hash = hashPackage(files);
|
|
134
|
+
|
|
135
|
+
return { ok: errors.length === 0, errors, warnings, hash, totalBytes };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compute a deterministic SHA-256 of the package contents.
|
|
140
|
+
* Files are sorted by path before hashing.
|
|
141
|
+
*/
|
|
142
|
+
function hashPackage(files) {
|
|
143
|
+
const h = crypto.createHash('sha256');
|
|
144
|
+
const sortedPaths = Object.keys(files).sort();
|
|
145
|
+
for (const p of sortedPaths) {
|
|
146
|
+
h.update(`\0${p}\0`);
|
|
147
|
+
h.update(files[p] ?? '');
|
|
148
|
+
}
|
|
149
|
+
return h.digest('hex');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Format scan results for CLI output.
|
|
154
|
+
* Returns lines to log.
|
|
155
|
+
*/
|
|
156
|
+
function formatScanReport(scanResult, logger) {
|
|
157
|
+
const lines = [];
|
|
158
|
+
|
|
159
|
+
if (scanResult.errors.length > 0) {
|
|
160
|
+
lines.push('Security scan FAILED:');
|
|
161
|
+
for (const e of scanResult.errors) lines.push(` [ERROR] ${e}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (scanResult.warnings.length > 0) {
|
|
165
|
+
lines.push('Security scan warnings:');
|
|
166
|
+
for (const w of scanResult.warnings) lines.push(` [WARN] ${w}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const line of lines) logger.log(line);
|
|
170
|
+
return lines;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { scanPackage, hashPackage, formatScanReport, ALLOWED_EXTENSIONS, MAX_FILE_BYTES, MAX_PACKAGE_BYTES };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight terminal checkbox UI using raw mode and keypress events.
|
|
5
|
+
* No external dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const readline = require('node:readline');
|
|
9
|
+
|
|
10
|
+
function countVisualLines(text, cols) {
|
|
11
|
+
return text.split('\n').reduce((acc, line) => {
|
|
12
|
+
return acc + Math.max(1, Math.ceil((line.length || 1) / cols));
|
|
13
|
+
}, 0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function render(items, selectedIndex, hint) {
|
|
17
|
+
const lines = items.map((item, index) => {
|
|
18
|
+
const marker = item.checked ? '[x]' : '[ ]';
|
|
19
|
+
const prefix = index === selectedIndex ? '> ' : ' ';
|
|
20
|
+
return `${prefix}${marker} ${item.label}`;
|
|
21
|
+
});
|
|
22
|
+
lines.push('');
|
|
23
|
+
lines.push(hint);
|
|
24
|
+
return lines.join('\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function promptCheckbox(items, hint = '↑/↓ navegar | Espaço selecionar | Enter confirmar | Esc cancelar | a=todos | n=limpar') {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const state = items.map((label) => ({ label, checked: true }));
|
|
30
|
+
let selectedIndex = 0;
|
|
31
|
+
let renderedLines = 0;
|
|
32
|
+
|
|
33
|
+
const stdin = process.stdin;
|
|
34
|
+
const stdout = process.stdout;
|
|
35
|
+
|
|
36
|
+
const wasRaw = stdin.isRaw;
|
|
37
|
+
if (stdin.setRawMode) {
|
|
38
|
+
stdin.setRawMode(true);
|
|
39
|
+
}
|
|
40
|
+
stdin.resume();
|
|
41
|
+
readline.emitKeypressEvents(stdin);
|
|
42
|
+
|
|
43
|
+
function draw() {
|
|
44
|
+
const cols = stdout.columns || 80;
|
|
45
|
+
if (renderedLines > 0) {
|
|
46
|
+
stdout.write(`\x1B[${renderedLines}A\x1B[J`);
|
|
47
|
+
}
|
|
48
|
+
const output = render(state, selectedIndex, hint);
|
|
49
|
+
renderedLines = countVisualLines(output, cols);
|
|
50
|
+
stdout.write(output + '\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function cleanup() {
|
|
54
|
+
if (stdin.setRawMode) {
|
|
55
|
+
stdin.setRawMode(wasRaw);
|
|
56
|
+
}
|
|
57
|
+
stdin.pause();
|
|
58
|
+
stdin.removeListener('keypress', onKeypress);
|
|
59
|
+
const cols = stdout.columns || 80;
|
|
60
|
+
if (renderedLines > 0) {
|
|
61
|
+
stdout.write(`\x1B[${renderedLines}A\x1B[J`);
|
|
62
|
+
}
|
|
63
|
+
renderedLines = 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function confirm() {
|
|
67
|
+
cleanup();
|
|
68
|
+
resolve(state.filter((s) => s.checked).map((s) => s.label));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cancel() {
|
|
72
|
+
cleanup();
|
|
73
|
+
resolve(null);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onKeypress(str, key) {
|
|
77
|
+
if (!key) return;
|
|
78
|
+
|
|
79
|
+
if (key.name === 'c' && key.ctrl) {
|
|
80
|
+
cancel();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (key.name === 'escape') {
|
|
85
|
+
cancel();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
90
|
+
confirm();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (key.name === 'up') {
|
|
95
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : state.length - 1;
|
|
96
|
+
draw();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (key.name === 'down') {
|
|
101
|
+
selectedIndex = selectedIndex < state.length - 1 ? selectedIndex + 1 : 0;
|
|
102
|
+
draw();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (key.name === 'space') {
|
|
107
|
+
state[selectedIndex].checked = !state[selectedIndex].checked;
|
|
108
|
+
draw();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (str === 'a' || str === 'A') {
|
|
113
|
+
state.forEach((s) => { s.checked = true; });
|
|
114
|
+
draw();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (str === 'n' || str === 'N') {
|
|
119
|
+
state.forEach((s) => { s.checked = false; });
|
|
120
|
+
draw();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
stdin.on('keypress', onKeypress);
|
|
126
|
+
draw();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { promptCheckbox };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
async function isTmuxAvailable() {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const child = spawn('which', ['tmux'], { stdio: 'pipe' });
|
|
9
|
+
child.on('close', (code) => resolve(code === 0));
|
|
10
|
+
child.on('error', () => resolve(false));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildSessionName(targetDir, agentName) {
|
|
15
|
+
const base = targetDir.split(/[\\/]/).pop() || 'aioson';
|
|
16
|
+
const agent = String(agentName || '').replace(/^@/, '');
|
|
17
|
+
const sanitized = `${base}-${agent}`.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 50);
|
|
18
|
+
return sanitized;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveAiosonBinary() {
|
|
22
|
+
if (process.argv[1]) {
|
|
23
|
+
return path.resolve(process.argv[1]);
|
|
24
|
+
}
|
|
25
|
+
return 'aioson';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function hasSession(sessionName) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const child = spawn('tmux', ['has-session', '-t', sessionName], { stdio: 'ignore' });
|
|
31
|
+
child.on('close', (code) => resolve(code === 0));
|
|
32
|
+
child.on('error', () => resolve(false));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function killSession(sessionName) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const kill = spawn('tmux', ['kill-session', '-t', sessionName], { stdio: 'ignore' });
|
|
39
|
+
kill.on('close', () => resolve());
|
|
40
|
+
kill.on('error', () => resolve());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function createTmuxSession(sessionName, targetDir, binaryPath, toolArgs) {
|
|
45
|
+
const args = [
|
|
46
|
+
'new-session',
|
|
47
|
+
'-d',
|
|
48
|
+
'-s', sessionName,
|
|
49
|
+
'-c', targetDir,
|
|
50
|
+
binaryPath,
|
|
51
|
+
...toolArgs
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn('tmux', args, { stdio: 'ignore' });
|
|
56
|
+
child.on('error', (err) => reject(err));
|
|
57
|
+
child.on('close', (code) => {
|
|
58
|
+
if (code !== 0) {
|
|
59
|
+
reject(new Error(`tmux new-session exited with code ${code}`));
|
|
60
|
+
} else {
|
|
61
|
+
resolve();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function configureTmuxSession(sessionName) {
|
|
68
|
+
const commands = [
|
|
69
|
+
// Mouse on so scroll works in the main pane; the bottom pane is 1 line
|
|
70
|
+
// and non-interactive, so accidental focus is harmless
|
|
71
|
+
['set-option', '-t', sessionName, 'mouse', 'on'],
|
|
72
|
+
// Hide tmux status line (we use our own pane)
|
|
73
|
+
['set-option', '-t', sessionName, 'status', 'off'],
|
|
74
|
+
// Hide pane borders for a clean look
|
|
75
|
+
['set-option', '-t', sessionName, 'pane-border-status', 'off'],
|
|
76
|
+
// Prevent focus events from shifting to the bottom pane
|
|
77
|
+
['set-option', '-t', sessionName, 'focus-events', 'off']
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const cmd of commands) {
|
|
81
|
+
await new Promise((resolve, reject) => {
|
|
82
|
+
const child = spawn('tmux', cmd, { stdio: 'ignore' });
|
|
83
|
+
child.on('error', (err) => reject(err));
|
|
84
|
+
child.on('close', () => resolve());
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function createStatusPane(sessionName, targetDir) {
|
|
90
|
+
const aiosonBin = resolveAiosonBinary();
|
|
91
|
+
const safeDir = String(targetDir || '.').replace(/"/g, '\\"');
|
|
92
|
+
const safeBin = String(aiosonBin).replace(/"/g, '\\"');
|
|
93
|
+
|
|
94
|
+
// Command runs directly in the pane (no interactive bash)
|
|
95
|
+
// printTmuxBar writes WITHOUT newline; we clear the line before each refresh
|
|
96
|
+
const cmd = `while true; do printf '\\033[2K\\r'; AIOSON_TMUX_BAR=1 "${safeBin}" live:status "${safeDir}" --format=tmux-bar; sleep 2; done`;
|
|
97
|
+
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const split = spawn('tmux', [
|
|
100
|
+
'split-window',
|
|
101
|
+
'-v',
|
|
102
|
+
'-t', `${sessionName}:0.0`,
|
|
103
|
+
'-c', targetDir,
|
|
104
|
+
'-l', '1',
|
|
105
|
+
cmd
|
|
106
|
+
], { stdio: 'ignore' });
|
|
107
|
+
split.on('error', (err) => reject(err));
|
|
108
|
+
split.on('close', (code) => {
|
|
109
|
+
if (code !== 0) {
|
|
110
|
+
reject(new Error(`tmux split-window exited with code ${code}`));
|
|
111
|
+
} else {
|
|
112
|
+
resolve();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function focusTopPane(sessionName) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const focus = spawn('tmux', [
|
|
121
|
+
'select-pane',
|
|
122
|
+
'-t', `${sessionName}:0.0`
|
|
123
|
+
], { stdio: 'ignore' });
|
|
124
|
+
focus.on('error', (err) => reject(err));
|
|
125
|
+
focus.on('close', () => resolve());
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function attachSession(sessionName) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const attach = spawn('tmux', ['attach', '-t', sessionName], { stdio: 'inherit' });
|
|
132
|
+
attach.on('error', (err) => reject(err));
|
|
133
|
+
attach.on('close', () => resolve());
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function launchTmuxSession(options) {
|
|
138
|
+
const { targetDir, agentName, tool, binaryPath, toolArgs = [] } = options;
|
|
139
|
+
const sessionName = buildSessionName(targetDir, agentName);
|
|
140
|
+
|
|
141
|
+
await killSession(sessionName);
|
|
142
|
+
await createTmuxSession(sessionName, targetDir, binaryPath, toolArgs);
|
|
143
|
+
await configureTmuxSession(sessionName);
|
|
144
|
+
await createStatusPane(sessionName, targetDir);
|
|
145
|
+
await focusTopPane(sessionName);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await attachSession(sessionName);
|
|
149
|
+
} finally {
|
|
150
|
+
await killSession(sessionName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { ok: true, sessionName };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
isTmuxAvailable,
|
|
158
|
+
launchTmuxSession,
|
|
159
|
+
buildSessionName,
|
|
160
|
+
hasSession,
|
|
161
|
+
attachSession,
|
|
162
|
+
resolveAiosonBinary
|
|
163
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Capabilities map for the AI CLIs that AIOSON can spawn via `aioson live:start`.
|
|
4
|
+
// Each CLI persists conversation history in its own per-cwd location, so
|
|
5
|
+
// "continue last conversation" is achieved by passing the right resume flag
|
|
6
|
+
// at spawn time — AIOSON never has to track an internal session ID.
|
|
7
|
+
//
|
|
8
|
+
// Used by:
|
|
9
|
+
// - `aioson live:start --resume[=last|<id>]` to map to the correct argv
|
|
10
|
+
// - `aioson tool:capabilities` to expose this map as JSON to UI clients
|
|
11
|
+
// (e.g. AIOSON Play) so they don't duplicate the lookup.
|
|
12
|
+
//
|
|
13
|
+
// Keep entries minimal and source-of-truth here. Adding a new CLI = one entry.
|
|
14
|
+
const TOOL_CAPS = {
|
|
15
|
+
claude: {
|
|
16
|
+
install_command: 'npm install -g @anthropic-ai/claude-code',
|
|
17
|
+
binary: 'claude',
|
|
18
|
+
supports_resume: true,
|
|
19
|
+
resume_last: ['--continue'],
|
|
20
|
+
supports_session_id: true,
|
|
21
|
+
resume_session_id: ['--resume', '<id>'],
|
|
22
|
+
supports_session_picker: true,
|
|
23
|
+
session_picker: ['--resume'],
|
|
24
|
+
},
|
|
25
|
+
codex: {
|
|
26
|
+
install_command: 'npm install -g @openai/codex',
|
|
27
|
+
binary: 'codex',
|
|
28
|
+
supports_resume: true,
|
|
29
|
+
resume_last: ['resume', '--last'],
|
|
30
|
+
supports_session_id: true,
|
|
31
|
+
resume_session_id: ['resume', '<id>'],
|
|
32
|
+
supports_session_picker: true,
|
|
33
|
+
session_picker: ['resume'],
|
|
34
|
+
},
|
|
35
|
+
opencode: {
|
|
36
|
+
install_command: 'npm install -g opencode-ai',
|
|
37
|
+
binary: 'opencode',
|
|
38
|
+
supports_resume: true,
|
|
39
|
+
resume_last: ['--continue'],
|
|
40
|
+
supports_session_id: true,
|
|
41
|
+
resume_session_id: ['--session', '<id>'],
|
|
42
|
+
supports_session_picker: false,
|
|
43
|
+
session_picker: null,
|
|
44
|
+
},
|
|
45
|
+
gemini: {
|
|
46
|
+
install_command: 'npm install -g @google/gemini-cli',
|
|
47
|
+
binary: 'gemini',
|
|
48
|
+
supports_resume: false,
|
|
49
|
+
resume_last: null,
|
|
50
|
+
supports_session_id: false,
|
|
51
|
+
resume_session_id: null,
|
|
52
|
+
supports_session_picker: false,
|
|
53
|
+
session_picker: null,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function getToolCapabilities(tool) {
|
|
58
|
+
const key = String(tool || '').trim().toLowerCase();
|
|
59
|
+
if (!key) return null;
|
|
60
|
+
return TOOL_CAPS[key] || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function listSupportedTools() {
|
|
64
|
+
return Object.keys(TOOL_CAPS).sort();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Resolve the argv prefix to add to the CLI spawn so it resumes a conversation.
|
|
68
|
+
// `resumeOpt` accepted shapes:
|
|
69
|
+
// - true → resume last
|
|
70
|
+
// - 'last' / 'true' → resume last
|
|
71
|
+
// - '' / undefined / null / false → no resume
|
|
72
|
+
// - any other string → treat as session id
|
|
73
|
+
// Returns [] when the tool doesn't support resume or resumeOpt is falsy.
|
|
74
|
+
function resolveResumeArgs(tool, resumeOpt) {
|
|
75
|
+
if (resumeOpt === undefined || resumeOpt === null || resumeOpt === '' || resumeOpt === false) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const caps = getToolCapabilities(tool);
|
|
79
|
+
if (!caps || !caps.supports_resume) return [];
|
|
80
|
+
|
|
81
|
+
const wantsLast =
|
|
82
|
+
resumeOpt === true ||
|
|
83
|
+
resumeOpt === 'last' ||
|
|
84
|
+
String(resumeOpt).toLowerCase() === 'true';
|
|
85
|
+
|
|
86
|
+
if (wantsLast) {
|
|
87
|
+
return Array.isArray(caps.resume_last) ? [...caps.resume_last] : [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (caps.supports_session_id && Array.isArray(caps.resume_session_id)) {
|
|
91
|
+
return caps.resume_session_id.map((part) => (part === '<id>' ? String(resumeOpt) : part));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Array.isArray(caps.resume_last) ? [...caps.resume_last] : [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
TOOL_CAPS,
|
|
99
|
+
getToolCapabilities,
|
|
100
|
+
listSupportedTools,
|
|
101
|
+
resolveResumeArgs,
|
|
102
|
+
};
|
package/src/locales.js
CHANGED
|
@@ -3,18 +3,20 @@
|
|
|
3
3
|
const fs = require('node:fs/promises');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { AGENT_DEFINITIONS } = require('./constants');
|
|
6
|
+
const { TEMPLATE_DIR } = require('./installer');
|
|
6
7
|
const { exists, ensureDir } = require('./utils');
|
|
8
|
+
const { normalizeLanguageTag } = require('./context');
|
|
7
9
|
|
|
8
10
|
const SUPPORTED_AGENT_LOCALES = ['en', 'pt-BR', 'es', 'fr'];
|
|
9
11
|
|
|
10
|
-
function normalizeLanguageTag(value) {
|
|
11
|
-
return String(value || '').trim();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
12
|
function localeForPath(value) {
|
|
15
13
|
return String(value || '').replace(/_/g, '-');
|
|
16
14
|
}
|
|
17
15
|
|
|
16
|
+
function normalizeInteractionLanguage(languageTag, fallback = 'en') {
|
|
17
|
+
return normalizeLanguageTag(languageTag, fallback);
|
|
18
|
+
}
|
|
19
|
+
|
|
18
20
|
function resolveAgentLocale(languageTag) {
|
|
19
21
|
const tag = normalizeLanguageTag(languageTag);
|
|
20
22
|
if (!tag) return 'en';
|
|
@@ -43,15 +45,15 @@ function getActiveAgentPath(agentId) {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
async function applyAgentLocale(targetDir, locale, options = {}) {
|
|
46
|
-
const
|
|
48
|
+
const interactionLanguage = normalizeInteractionLanguage(locale || 'en');
|
|
47
49
|
const dryRun = Boolean(options.dryRun);
|
|
48
50
|
const copied = [];
|
|
49
51
|
const missing = [];
|
|
50
52
|
|
|
51
53
|
for (const agent of AGENT_DEFINITIONS) {
|
|
52
|
-
const sourceRel =
|
|
54
|
+
const sourceRel = getActiveAgentPath(agent.id);
|
|
55
|
+
const sourceAbs = path.join(TEMPLATE_DIR, sourceRel);
|
|
53
56
|
const destRel = getActiveAgentPath(agent.id);
|
|
54
|
-
const sourceAbs = path.join(targetDir, sourceRel);
|
|
55
57
|
const destAbs = path.join(targetDir, destRel);
|
|
56
58
|
|
|
57
59
|
if (!(await exists(sourceAbs))) {
|
|
@@ -67,7 +69,8 @@ async function applyAgentLocale(targetDir, locale, options = {}) {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
return {
|
|
70
|
-
locale:
|
|
72
|
+
locale: interactionLanguage,
|
|
73
|
+
promptLocale: 'en',
|
|
71
74
|
copied,
|
|
72
75
|
missing,
|
|
73
76
|
dryRun
|
|
@@ -77,6 +80,7 @@ async function applyAgentLocale(targetDir, locale, options = {}) {
|
|
|
77
80
|
module.exports = {
|
|
78
81
|
SUPPORTED_AGENT_LOCALES,
|
|
79
82
|
normalizeLanguageTag,
|
|
83
|
+
normalizeInteractionLanguage,
|
|
80
84
|
resolveAgentLocale,
|
|
81
85
|
getLocalizedAgentPath,
|
|
82
86
|
getActiveAgentPath,
|