@cohortapp/agent-sdk 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/init-agent.md +104 -0
- package/.claude/commands/init-maestro.md +1187 -0
- package/.claude/settings.json +161 -0
- package/.env.example +216 -0
- package/README.md +632 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/communications/agent.md +96 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/archetypes/altitudes/c-suite.yaml +58 -0
- package/archetypes/altitudes/founder.yaml +68 -0
- package/archetypes/altitudes/senior-manager.yaml +63 -0
- package/archetypes/altitudes/svp.yaml +60 -0
- package/archetypes/altitudes/vp.yaml +50 -0
- package/archetypes/archetype.schema.json +77 -0
- package/archetypes/base.yaml +47 -0
- package/archetypes/capabilities/commercial-leader.yaml +159 -0
- package/archetypes/capabilities/compliance-officer.yaml +159 -0
- package/archetypes/capabilities/executive-operator.yaml +169 -0
- package/archetypes/capabilities/finance-leader.yaml +162 -0
- package/archetypes/capabilities/operations-leader.yaml +154 -0
- package/archetypes/capabilities/product-leader.yaml +148 -0
- package/archetypes/capabilities/technical-leader.yaml +146 -0
- package/archetypes/functions/commercial-leader.yaml +62 -0
- package/archetypes/functions/compliance-officer.yaml +64 -0
- package/archetypes/functions/executive-operator.yaml +70 -0
- package/archetypes/functions/finance-leader.yaml +70 -0
- package/archetypes/functions/operations-leader.yaml +62 -0
- package/archetypes/functions/product-leader.yaml +61 -0
- package/archetypes/functions/technical-leader.yaml +57 -0
- package/bin/cohort-mcp.mjs +81 -0
- package/bin/maestro.mjs +3516 -0
- package/bin/maestro.test.mjs +1015 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2239 -0
- package/docs/architecture/archetype-agent-factory.md +110 -0
- package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +585 -0
- package/docs/architecture/system-architecture.md +1272 -0
- package/docs/company-context/README.md +40 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/guides/agents-observe-setup.md +64 -0
- package/docs/guides/billing-console-keys.md +88 -0
- package/docs/guides/ccxray-diagnostics.md +65 -0
- package/docs/guides/channel-bus.md +127 -0
- package/docs/guides/claude-mem-setup.md +79 -0
- package/docs/guides/claude-pace-setup.md +56 -0
- package/docs/guides/claudraband-sessions.md +98 -0
- package/docs/guides/clawteam-swarm.md +116 -0
- package/docs/guides/code-review-graph-setup.md +86 -0
- package/docs/guides/email-setup.md +431 -0
- package/docs/guides/mac-mini.md +119 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/model-routing.md +162 -0
- package/docs/guides/observability-otel.md +265 -0
- package/docs/guides/org-onboarding.md +132 -0
- package/docs/guides/outbound-governance-setup.md +437 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +563 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/self-optimization-pattern.md +82 -0
- package/docs/guides/setup-wizard.md +178 -0
- package/docs/guides/slack-setup.md +350 -0
- package/docs/guides/telegram-setup.md +227 -0
- package/docs/guides/twilio-subaccounts-setup.md +223 -0
- package/docs/guides/verification.md +128 -0
- package/docs/guides/voice-mode.md +188 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/webhook-relay-setup.md +349 -0
- package/docs/guides/whatsapp-setup.md +288 -0
- package/docs/prompts/board-pack-cover-template.md +36 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/research/hallucinated-tool-output-investigation.md +151 -0
- package/docs/runbooks/backup-restore.md +205 -0
- package/docs/runbooks/cohort-cutover.md +129 -0
- package/docs/runbooks/fleet-operations.md +200 -0
- package/docs/runbooks/incident-response.md +226 -0
- package/docs/runbooks/mac-mini-bootstrap.md +431 -0
- package/docs/runbooks/perpetual-operations.md +509 -0
- package/docs/runbooks/recovery-and-failover.md +260 -0
- package/framework-features.json +267 -0
- package/ingest/README.md +87 -0
- package/lib/action-executor.js +689 -0
- package/lib/action-executor.test.mjs +871 -0
- package/lib/agent-root.mjs +37 -0
- package/lib/archetype.mjs +236 -0
- package/lib/archetype.test.mjs +132 -0
- package/lib/autonomy.mjs +114 -0
- package/lib/autonomy.test.mjs +66 -0
- package/lib/backlog.mjs +358 -0
- package/lib/backlog.test.mjs +266 -0
- package/lib/budget-guard.mjs +279 -0
- package/lib/budget-guard.test.mjs +291 -0
- package/lib/cadence-bus-schedule.test.mjs +194 -0
- package/lib/cadence-bus.mjs +1120 -0
- package/lib/cadence-bus.test.mjs +720 -0
- package/lib/cadences.mjs +205 -0
- package/lib/cadences.test.mjs +125 -0
- package/lib/capability.mjs +154 -0
- package/lib/capability.test.mjs +78 -0
- package/lib/channels/base-adapter.mjs +719 -0
- package/lib/channels/base-adapter.test.mjs +590 -0
- package/lib/channels/channel.mjs +128 -0
- package/lib/channels/channels.test.mjs +371 -0
- package/lib/channels/contract.mjs +215 -0
- package/lib/channels/contract.test.mjs +137 -0
- package/lib/channels/conversation-resolver.mjs +95 -0
- package/lib/channels/gmail/adapter.mjs +87 -0
- package/lib/channels/inbox-item.mjs +255 -0
- package/lib/channels/inbox-item.test.mjs +335 -0
- package/lib/channels/index.mjs +94 -0
- package/lib/channels/orgmail/adapter.mjs +353 -0
- package/lib/channels/orgmail/adapter.test.mjs +311 -0
- package/lib/channels/pairing.mjs +363 -0
- package/lib/channels/pairing.test.mjs +270 -0
- package/lib/channels/registry.mjs +164 -0
- package/lib/channels/slack/adapter.mjs +317 -0
- package/lib/channels/slack-adapter.test.mjs +212 -0
- package/lib/channels/sms/adapter.mjs +43 -0
- package/lib/channels/telegram/adapter.mjs +432 -0
- package/lib/channels/telegram-adapter.test.mjs +306 -0
- package/lib/channels/voice/adapter.mjs +301 -0
- package/lib/channels/voice/adapter.test.mjs +278 -0
- package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
- package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
- package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
- package/lib/charter.mjs +256 -0
- package/lib/charter.test.mjs +89 -0
- package/lib/claude-bin.mjs +134 -0
- package/lib/claude-bin.test.mjs +75 -0
- package/lib/collective/capture.mjs +185 -0
- package/lib/collective/capture.test.mjs +121 -0
- package/lib/collective/cards.mjs +201 -0
- package/lib/collective/cards.test.mjs +114 -0
- package/lib/collective/config.mjs +186 -0
- package/lib/collective/config.test.mjs +123 -0
- package/lib/collective/global-config.mjs +113 -0
- package/lib/collective/global-config.test.mjs +75 -0
- package/lib/collective/presence.mjs +201 -0
- package/lib/collective/presence.test.mjs +95 -0
- package/lib/collective/recall.mjs +215 -0
- package/lib/collective/recall.test.mjs +116 -0
- package/lib/comms/send-gate.mjs +554 -0
- package/lib/comms/send-gate.test.mjs +577 -0
- package/lib/comms.mjs +67 -0
- package/lib/comms.test.mjs +41 -0
- package/lib/diagnostics/alerts.mjs +424 -0
- package/lib/diagnostics/alerts.test.mjs +318 -0
- package/lib/diagnostics/backup-freshness.mjs +188 -0
- package/lib/diagnostics/backup-freshness.test.mjs +185 -0
- package/lib/diagnostics/counters.mjs +269 -0
- package/lib/diagnostics/counters.test.mjs +206 -0
- package/lib/diagnostics/events.mjs +188 -0
- package/lib/diagnostics/events.test.mjs +290 -0
- package/lib/diagnostics/otel.mjs +237 -0
- package/lib/diagnostics/otel.test.mjs +196 -0
- package/lib/diagnostics/trace.mjs +216 -0
- package/lib/diagnostics/trace.test.mjs +251 -0
- package/lib/env-compat.mjs +74 -0
- package/lib/env-compat.test.mjs +104 -0
- package/lib/feature-init.mjs +331 -0
- package/lib/fs-atomic.mjs +112 -0
- package/lib/fs-atomic.test.mjs +72 -0
- package/lib/fs-ownership.mjs +111 -0
- package/lib/fs-ownership.test.mjs +158 -0
- package/lib/hooks/bus.mjs +347 -0
- package/lib/hooks/bus.test.mjs +387 -0
- package/lib/index.js +16 -0
- package/lib/learning/config.mjs +106 -0
- package/lib/learning/config.test.mjs +75 -0
- package/lib/learning/counters.mjs +156 -0
- package/lib/learning/counters.test.mjs +69 -0
- package/lib/learning/curator-consolidate.test.mjs +238 -0
- package/lib/learning/curator.mjs +453 -0
- package/lib/learning/curator.test.mjs +106 -0
- package/lib/learning/log.mjs +40 -0
- package/lib/learning/reflect.mjs +534 -0
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.mjs +352 -0
- package/lib/learning/session-index.test.mjs +125 -0
- package/lib/learning/skill-writer.mjs +474 -0
- package/lib/learning/skill-writer.test.mjs +210 -0
- package/lib/mcp/server.mjs +328 -0
- package/lib/mcp/server.test.mjs +400 -0
- package/lib/model-router/auth-profiles.mjs +758 -0
- package/lib/model-router/auth-profiles.test.mjs +580 -0
- package/lib/model-router/catalog/anthropic.yaml +153 -0
- package/lib/model-router/catalog/deepseek.yaml +86 -0
- package/lib/model-router/catalog/moonshot.yaml +81 -0
- package/lib/model-router/catalog/qwen.yaml +114 -0
- package/lib/model-router/catalog.mjs +925 -0
- package/lib/model-router/catalog.test.mjs +385 -0
- package/lib/model-router/economics.mjs +564 -0
- package/lib/model-router/economics.test.mjs +344 -0
- package/lib/model-router/failover.mjs +298 -0
- package/lib/model-router/failover.test.mjs +439 -0
- package/lib/model-router/health.mjs +453 -0
- package/lib/model-router/health.test.mjs +338 -0
- package/lib/model-router/integration-coverage.test.mjs +829 -0
- package/lib/model-router/integration.test.mjs +564 -0
- package/lib/model-router/ledger.mjs +402 -0
- package/lib/model-router/ledger.test.mjs +382 -0
- package/lib/model-router/llm-task.mjs +515 -0
- package/lib/model-router/llm-task.test.mjs +392 -0
- package/lib/model-router/org-credentials.mjs +260 -0
- package/lib/model-router/org-credentials.test.mjs +265 -0
- package/lib/model-router/pricing-refresh.mjs +463 -0
- package/lib/model-router/pricing-refresh.test.mjs +286 -0
- package/lib/model-router/reconcile.mjs +429 -0
- package/lib/model-router/reconcile.test.mjs +316 -0
- package/lib/model-router/repair.mjs +471 -0
- package/lib/model-router/repair.test.mjs +180 -0
- package/lib/model-router/resolve.mjs +1206 -0
- package/lib/model-router/spawn.mjs +497 -0
- package/lib/model-router/spawn.test.mjs +425 -0
- package/lib/model-router/taxonomy.mjs +893 -0
- package/lib/model-router/taxonomy.test.mjs +410 -0
- package/lib/model-router.mjs +677 -0
- package/lib/model-router.test.mjs +907 -0
- package/lib/org/activity.mjs +211 -0
- package/lib/org/activity.test.mjs +134 -0
- package/lib/org/approvals.mjs +448 -0
- package/lib/org/approvals.test.mjs +216 -0
- package/lib/org/awareness.mjs +222 -0
- package/lib/org/awareness.test.mjs +159 -0
- package/lib/org/board.mjs +229 -0
- package/lib/org/board.test.mjs +177 -0
- package/lib/org/bootstrap-context.mjs +169 -0
- package/lib/org/bootstrap-context.test.mjs +153 -0
- package/lib/org/client.mjs +1628 -0
- package/lib/org/client.test.mjs +1107 -0
- package/lib/org/cohort-client.mjs +67 -0
- package/lib/org/cohort-client.test.mjs +126 -0
- package/lib/org/cost-sync.mjs +227 -0
- package/lib/org/cost-sync.test.mjs +153 -0
- package/lib/org/doctor.mjs +212 -0
- package/lib/org/doctor.test.mjs +212 -0
- package/lib/org/handoff.mjs +293 -0
- package/lib/org/handoff.test.mjs +269 -0
- package/lib/org/integration-tools.mjs +182 -0
- package/lib/org/integration-tools.test.mjs +160 -0
- package/lib/org/keys.mjs +131 -0
- package/lib/org/keys.test.mjs +92 -0
- package/lib/org/knowledge.mjs +463 -0
- package/lib/org/knowledge.test.mjs +319 -0
- package/lib/org/leases.mjs +335 -0
- package/lib/org/leases.test.mjs +235 -0
- package/lib/org/mesh-integration.test.mjs +127 -0
- package/lib/org/mesh.mjs +459 -0
- package/lib/org/mesh.test.mjs +345 -0
- package/lib/org/messaging.mjs +503 -0
- package/lib/org/messaging.test.mjs +238 -0
- package/lib/org/policy.mjs +345 -0
- package/lib/org/policy.test.mjs +237 -0
- package/lib/org/protocol.checksum +1 -0
- package/lib/org/protocol.checksum.test.mjs +90 -0
- package/lib/org/protocol.mjs +967 -0
- package/lib/org/protocol.test.mjs +264 -0
- package/lib/org/registry.mjs +194 -0
- package/lib/org/registry.test.mjs +100 -0
- package/lib/org/tool-surface-integration.test.mjs +120 -0
- package/lib/org/tool-surface.mjs +2535 -0
- package/lib/org/tool-surface.test.mjs +589 -0
- package/lib/org/ui-parity.mjs +3236 -0
- package/lib/org/ui-parity.test.mjs +348 -0
- package/lib/org/verify.mjs +176 -0
- package/lib/org/verify.test.mjs +194 -0
- package/lib/rag/embed.mjs +188 -0
- package/lib/rag/indexer.mjs +425 -0
- package/lib/rag/rag.test.mjs +505 -0
- package/lib/rag/search.mjs +475 -0
- package/lib/rate-guard.mjs +246 -0
- package/lib/rate-guard.test.mjs +201 -0
- package/lib/render.mjs +112 -0
- package/lib/render.test.mjs +68 -0
- package/lib/resource-governor.mjs +297 -0
- package/lib/resource-governor.test.mjs +262 -0
- package/lib/scheduling/dynamic-jobs.mjs +675 -0
- package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
- package/lib/scheduling/jitter.mjs +0 -0
- package/lib/scheduling/jitter.test.mjs +140 -0
- package/lib/secrets/broker.mjs +315 -0
- package/lib/secrets/broker.test.mjs +280 -0
- package/lib/secrets/providers.mjs +461 -0
- package/lib/secrets/providers.test.mjs +274 -0
- package/lib/security/audit-engine.mjs +684 -0
- package/lib/security/audit-engine.test.mjs +389 -0
- package/lib/security/coerce-args.mjs +552 -0
- package/lib/security/coerce-args.test.mjs +281 -0
- package/lib/security/dangerous-tools.mjs +97 -0
- package/lib/security/dangerous-tools.test.mjs +68 -0
- package/lib/security/external-content.mjs +145 -0
- package/lib/security/external-content.test.mjs +67 -0
- package/lib/security/redact.mjs +592 -0
- package/lib/security/redact.test.mjs +441 -0
- package/lib/security/secret-equal.mjs +73 -0
- package/lib/security/secret-equal.test.mjs +55 -0
- package/lib/session-permissions.mjs +101 -0
- package/lib/session-permissions.test.mjs +100 -0
- package/lib/setup/claude-probe.mjs +74 -0
- package/lib/setup/completeness.mjs +175 -0
- package/lib/setup/completeness.test.mjs +110 -0
- package/lib/setup/context-pack.mjs +173 -0
- package/lib/setup/context-pack.test.mjs +89 -0
- package/lib/setup/enrich.mjs +277 -0
- package/lib/setup/enrich.test.mjs +115 -0
- package/lib/setup/enroll-from-cohort.mjs +441 -0
- package/lib/setup/enroll-from-cohort.test.mjs +233 -0
- package/lib/setup/integration.test.mjs +162 -0
- package/lib/setup/io.mjs +360 -0
- package/lib/setup/io.test.mjs +77 -0
- package/lib/setup/run-generator.mjs +81 -0
- package/lib/setup/runner.mjs +244 -0
- package/lib/setup/runner.test.mjs +132 -0
- package/lib/setup/sections/comms.mjs +173 -0
- package/lib/setup/sections/company.mjs +120 -0
- package/lib/setup/sections/enrich.mjs +138 -0
- package/lib/setup/sections/identity.mjs +182 -0
- package/lib/setup/sections/identity.test.mjs +140 -0
- package/lib/setup/sections/learning.mjs +153 -0
- package/lib/setup/sections/learning.test.mjs +81 -0
- package/lib/setup/sections/messaging.mjs +219 -0
- package/lib/setup/sections/messaging.test.mjs +127 -0
- package/lib/setup/sections/model.mjs +102 -0
- package/lib/setup/sections/operating-model.mjs +78 -0
- package/lib/setup/sections/org.mjs +475 -0
- package/lib/setup/sections/org.test.mjs +313 -0
- package/lib/setup/sections/orgmail.mjs +173 -0
- package/lib/setup/sections/orgmail.test.mjs +118 -0
- package/lib/setup/sections/recovery.mjs +159 -0
- package/lib/setup/sections/recovery.test.mjs +98 -0
- package/lib/setup/sections/tools.mjs +132 -0
- package/lib/setup/sections/verify.mjs +97 -0
- package/lib/setup/sot.mjs +205 -0
- package/lib/setup/sot.test.mjs +81 -0
- package/lib/setup/state.mjs +151 -0
- package/lib/setup/state.test.mjs +92 -0
- package/lib/singleton.js +229 -0
- package/lib/singleton.test.mjs +135 -0
- package/lib/telemetry/alerts.mjs +216 -0
- package/lib/telemetry/alerts.test.mjs +109 -0
- package/lib/telemetry/collect.mjs +512 -0
- package/lib/telemetry/collect.test.mjs +202 -0
- package/lib/tool-definitions-integration.test.mjs +83 -0
- package/lib/tool-definitions.js +738 -0
- package/lib/tool-definitions.test.mjs +437 -0
- package/lib/util/fetch-timeout.mjs +136 -0
- package/lib/util/fetch-timeout.test.mjs +202 -0
- package/lib/util/reconnect.mjs +343 -0
- package/lib/util/reconnect.test.mjs +369 -0
- package/lib/util/unhandled.mjs +205 -0
- package/lib/util/unhandled.test.mjs +216 -0
- package/lib/voice/context-loader.mjs +466 -0
- package/lib/voice/index.mjs +100 -0
- package/lib/voice/openai-realtime.mjs +510 -0
- package/lib/voice/outbound.mjs +542 -0
- package/lib/voice/outbound.test.mjs +69 -0
- package/lib/voice/post-call-brief.mjs +428 -0
- package/lib/voice/provider.mjs +52 -0
- package/lib/voice/session-rotation.mjs +257 -0
- package/lib/voice/stt.mjs +161 -0
- package/lib/voice/stt.test.mjs +226 -0
- package/lib/voice/tool-bridge.mjs +370 -0
- package/lib/voice/tts.mjs +104 -0
- package/lib/voice/twilio-sip-bridge.mjs +288 -0
- package/lib/voice/voice.test.mjs +990 -0
- package/mcp/README.md +80 -0
- package/package.json +151 -0
- package/plugins/maestro-skills/plugin.json +139 -0
- package/plugins/maestro-skills/skills/agents-observe.md +110 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/books-close.md +77 -0
- package/plugins/maestro-skills/skills/brand-steward.md +121 -0
- package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
- package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
- package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
- package/plugins/maestro-skills/skills/claude-pace.md +61 -0
- package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
- package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/files-find.md +65 -0
- package/plugins/maestro-skills/skills/generative-ui.md +228 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/mail-triage.md +86 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
- package/plugins/maestro-skills/skills/org-board.md +133 -0
- package/plugins/maestro-skills/skills/org-credential.md +68 -0
- package/plugins/maestro-skills/skills/org-recall.md +81 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/router-why.md +78 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/session-search.md +71 -0
- package/plugins/maestro-skills/skills/set-reminder.md +93 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/team-activity.md +86 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +114 -0
- package/policies/ai-disclosure.yaml +294 -0
- package/policies/communication-style.md +139 -0
- package/policies/information-barriers.yaml +118 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/icon-dark.png +0 -0
- package/public/assets/icon-dark.svg +9 -0
- package/public/assets/icon-light.svg +9 -0
- package/public/assets/logo-dark.svg +15 -0
- package/public/assets/logo-light.svg +15 -0
- package/scaffold/.mcp.json +7 -0
- package/scaffold/CLAUDE.md +368 -0
- package/scaffold/config/agent.json +55 -0
- package/scaffold/config/agent.ts +76 -0
- package/scaffold/config/agent.ts.example +89 -0
- package/scaffold/config/alerts.yaml +23 -0
- package/scaffold/config/allowlist.yaml.example +25 -0
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scaffold/config/collective.yaml +49 -0
- package/scaffold/config/company.json +20 -0
- package/scaffold/config/known-agents.json +6 -0
- package/scaffold/config/learning.yaml +55 -0
- package/scaffold/config/model-routing.yaml.example +104 -0
- package/scaffold/config/org.yaml +25 -0
- package/scaffold/config/orgmail.yaml.example +19 -0
- package/scaffold/config/recovery.yaml +72 -0
- package/scaffold/config/secrets.yaml +27 -0
- package/scaffold/config/slack.yaml.example +35 -0
- package/scaffold/config/telegram.yaml.example +38 -0
- package/scaffold/config/voice.yaml.example +89 -0
- package/scaffold/config/whatsapp.yaml.example +39 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/brand-steward.md +72 -0
- package/schedules/triggers/daily-evening-wrap.md +159 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/directory-hygiene.md +81 -0
- package/schedules/triggers/dynamic-jobs.md +40 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/messaging-inbound.md +50 -0
- package/schedules/triggers/org-pulse.md +24 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +65 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/archive-email.sh +55 -0
- package/scripts/cadence/cadence-status.mjs +36 -0
- package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
- package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
- package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
- package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
- package/scripts/ci/check-docs-accuracy.mjs +493 -0
- package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
- package/scripts/ci/check-exports-exist.mjs +140 -0
- package/scripts/ci/check-files-exist.mjs +107 -0
- package/scripts/ci/check-no-build-artifacts.mjs +111 -0
- package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
- package/scripts/ci/check-no-confidential.mjs +198 -0
- package/scripts/ci/check-no-conflict-markers.mjs +169 -0
- package/scripts/ci/check-no-residual-identity.mjs +163 -0
- package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
- package/scripts/ci/check-tarball-fidelity.mjs +205 -0
- package/scripts/ci/check-unresolved-tokens.mjs +83 -0
- package/scripts/ci/check.mjs +109 -0
- package/scripts/ci/check.test.mjs +194 -0
- package/scripts/ci/run-coverage.mjs +82 -0
- package/scripts/ci/run-tests.mjs +71 -0
- package/scripts/cloud-relay/README.md +59 -0
- package/scripts/cloud-relay/index.mjs +233 -0
- package/scripts/cloud-relay/package.json +15 -0
- package/scripts/cloud-relay/railway.json +13 -0
- package/scripts/cloud-relay/voice/README.md +94 -0
- package/scripts/cloud-relay/voice/package-lock.json +39 -0
- package/scripts/cloud-relay/voice/package.json +16 -0
- package/scripts/cloud-relay/voice/railway.json +13 -0
- package/scripts/cloud-relay/voice/server.mjs +532 -0
- package/scripts/collective/hook-runner.mjs +211 -0
- package/scripts/collective/hook-runner.test.mjs +90 -0
- package/scripts/collective/org-pulse.mjs +72 -0
- package/scripts/collective/org-sync.mjs +61 -0
- package/scripts/collective/recall.mjs +45 -0
- package/scripts/collective/who.mjs +30 -0
- package/scripts/comms-monitor.sh +288 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +91 -0
- package/scripts/cost/fleet-digest.mjs +407 -0
- package/scripts/cost/fleet-digest.test.mjs +207 -0
- package/scripts/cost/track-claude-usage.mjs +169 -0
- package/scripts/daemon/agent-daemon.mjs +989 -0
- package/scripts/daemon/agent-daemon.test.mjs +525 -0
- package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
- package/scripts/daemon/cadence-consumer.mjs +1080 -0
- package/scripts/daemon/cadence-consumer.test.mjs +770 -0
- package/scripts/daemon/cadence-handlers.mjs +1121 -0
- package/scripts/daemon/cadence-handlers.test.mjs +617 -0
- package/scripts/daemon/classifier.mjs +704 -0
- package/scripts/daemon/classifier.test.mjs +238 -0
- package/scripts/daemon/classify-kind.mjs +54 -0
- package/scripts/daemon/classify-kind.test.mjs +40 -0
- package/scripts/daemon/context-compiler.mjs +605 -0
- package/scripts/daemon/context-compiler.test.mjs +300 -0
- package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
- package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
- package/scripts/daemon/dispatcher.mjs +1516 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/inbox-deferral.mjs +210 -0
- package/scripts/daemon/inbox-deferral.test.mjs +242 -0
- package/scripts/daemon/integration.test.mjs +149 -0
- package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
- package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
- package/scripts/daemon/launchd-wrapper.sh +91 -0
- package/scripts/daemon/lib/session-router.mjs +274 -0
- package/scripts/daemon/lib/session-router.test.mjs +295 -0
- package/scripts/daemon/maestro-daemon.mjs +275 -0
- package/scripts/daemon/prompt-builder.mjs +685 -0
- package/scripts/daemon/prompt-builder.test.mjs +213 -0
- package/scripts/daemon/responder.mjs +854 -0
- package/scripts/daemon/session-lock.mjs +721 -0
- package/scripts/daemon/session-lock.test.mjs +252 -0
- package/scripts/daemon/session-outcomes.mjs +640 -0
- package/scripts/daemon/session-outcomes.test.mjs +533 -0
- package/scripts/daemon/typing-registry.mjs +90 -0
- package/scripts/daemon/typing-registry.test.mjs +77 -0
- package/scripts/daemon/voice-webhook-server.mjs +804 -0
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-principal.html +52 -0
- package/scripts/email-signature.html +60 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +362 -0
- package/scripts/emergency-stop.sh +81 -0
- package/scripts/healthcheck.sh +116 -0
- package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +126 -0
- package/scripts/hooks/pre-send-audit.sh +174 -0
- package/scripts/hooks/pre-send-audit.test.mjs +215 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/hooks/session-start-banner.sh +115 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1229 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/learning/consolidate-skills.mjs +72 -0
- package/scripts/learning/session-search.mjs +125 -0
- package/scripts/llm_email_dedup.py +442 -0
- package/scripts/local-triggers/generate-plists.sh +432 -0
- package/scripts/local-triggers/generate-plists.test.mjs +413 -0
- package/scripts/local-triggers/install-all.sh +49 -0
- package/scripts/local-triggers/plists/.gitkeep +0 -0
- package/scripts/local-triggers/run-trigger.sh +63 -0
- package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
- package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
- package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
- package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/maintenance/health-check.sh +377 -0
- package/scripts/media-generation/README.md +105 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/org/send-orgmail.mjs +227 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +477 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +481 -0
- package/scripts/pdf-generation/README.md +63 -0
- package/scripts/pdf-generation/build-document.mjs +247 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +35 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +192 -0
- package/scripts/poller/imap-client.mjs +289 -0
- package/scripts/poller/inbox-scan-poller.mjs +156 -0
- package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +285 -0
- package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
- package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
- package/scripts/poller/secondary-gmail-poller.mjs +132 -0
- package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
- package/scripts/poller/slack-poller.mjs +854 -0
- package/scripts/poller/slack-socket-mode.mjs +917 -0
- package/scripts/poller/slack-socket-mode.test.mjs +753 -0
- package/scripts/poller/trigger.mjs +75 -0
- package/scripts/poller/utils.mjs +371 -0
- package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
- package/scripts/poller/voice-poller.mjs +236 -0
- package/scripts/poller-launchd/install.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +40 -0
- package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag/build-index.mjs +47 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/restore-from-backup.sh +248 -0
- package/scripts/restore-from-backup.test.mjs +178 -0
- package/scripts/resume-operations.sh +80 -0
- package/scripts/search-secondary-inbox.py +181 -0
- package/scripts/secondary-inbox-poller.py +437 -0
- package/scripts/self-optimization/compute-metrics.py +398 -0
- package/scripts/send-email-as-principal.py +369 -0
- package/scripts/send-email-threaded.py +392 -0
- package/scripts/send-email-with-attachment.py +377 -0
- package/scripts/send-email.sh +131 -0
- package/scripts/send-sms.sh +175 -0
- package/scripts/send-whatsapp.sh +292 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/boot-claude-session.sh +94 -0
- package/scripts/setup/configure-macos.sh +674 -0
- package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
- package/scripts/setup/configure-voice-tunnel.mjs +182 -0
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/generate-agent-package-json.mjs +222 -0
- package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
- package/scripts/setup/generate-autonomy.mjs +60 -0
- package/scripts/setup/generate-backlog.mjs +101 -0
- package/scripts/setup/generate-cadences.mjs +92 -0
- package/scripts/setup/generate-capability.mjs +162 -0
- package/scripts/setup/generate-charter.mjs +76 -0
- package/scripts/setup/generate-comms.mjs +59 -0
- package/scripts/setup/generate-company.mjs +92 -0
- package/scripts/setup/init-agent.sh +547 -0
- package/scripts/setup/init-agent.test.mjs +151 -0
- package/scripts/setup/init-archetype.mjs +74 -0
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-channel-bus.mjs +46 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-learning.mjs +70 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-model-router.mjs +124 -0
- package/scripts/setup/init-rag.mjs +174 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/scripts/setup/init-slack-socket-mode.mjs +260 -0
- package/scripts/setup/init-telegram.mjs +165 -0
- package/scripts/setup/init-voice-realtime.mjs +204 -0
- package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
- package/scripts/setup/install-dev-tools.sh +150 -0
- package/scripts/setup/lib/install-plist.mjs +95 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/setup/render-environment-yaml.mjs +133 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +1045 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +232 -0
- package/scripts/slack-send.sh +287 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +450 -0
- package/scripts/spawn-session.sh +120 -0
- package/scripts/sync-protocol.mjs +217 -0
- package/scripts/system-verify.sh +184 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +659 -0
- package/scripts/validate_outbound.py +1504 -0
- package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
- package/scripts/watchdog/force-reboot.sh +157 -0
- package/scripts/watchdog/memory-watchdog.sh +473 -0
- package/scripts/whatsapp-handler.mjs +538 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/backlog-executor.yaml +141 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +105 -0
- package/workflows/daily/morning-brief.yaml +164 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
- package/workflows/event-driven/pr-review.yaml +107 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/engineering-health.yaml +154 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- package/workflows/weekly/strategic-memo.yaml +79 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* integration.test.mjs — end-to-end `maestro setup` acceptance (WS1 done-bar).
|
|
3
|
+
*
|
|
4
|
+
* Creates a temp agent repo seeded from scaffold/config, runs the runner in
|
|
5
|
+
* --headless with a fixture answers file (maestroRoot = the real repo so the
|
|
6
|
+
* deterministic generators run for real), and asserts:
|
|
7
|
+
* 1. a GREEN completeness gate after the run,
|
|
8
|
+
* 2. config/agent.ts is a thin SoT wrapper,
|
|
9
|
+
* 3. re-running is a no-op (operating-model + identity skipped).
|
|
10
|
+
*
|
|
11
|
+
* The `claude --print` enrichment spawn is INJECTED (fake) so the test is
|
|
12
|
+
* hermetic — no network. Generators that only template from agent.json run real.
|
|
13
|
+
*
|
|
14
|
+
* Run: `node --test lib/setup/integration.test.mjs`
|
|
15
|
+
* @module lib/setup/integration.test
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
"use strict";
|
|
19
|
+
|
|
20
|
+
import { test } from "node:test";
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
import { mkdtempSync, mkdirSync, writeFileSync, copyFileSync, readFileSync, existsSync } from "node:fs";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { join, dirname, resolve } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { runSetup, loadSections } from "./runner.mjs";
|
|
27
|
+
import { assertComplete } from "./completeness.mjs";
|
|
28
|
+
import { isWrapper } from "./sot.mjs";
|
|
29
|
+
import { loadProgress } from "./state.mjs";
|
|
30
|
+
|
|
31
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const MAESTRO_ROOT = resolve(HERE, "..", "..");
|
|
33
|
+
|
|
34
|
+
/** A fixture answers file: everything the headless sections need. */
|
|
35
|
+
const ANSWERS = {
|
|
36
|
+
firstName: "Jacob", lastName: "Chen", title: "VP, Engineering", email: "jacob@acme.com",
|
|
37
|
+
function: "technical-leader", altitude: "vp",
|
|
38
|
+
principalName: "Dana Reyes", principalTitle: "CEO", principalEmail: "dana@acme.com",
|
|
39
|
+
companyName: "Acme", companyWebsite: "https://acme.com", companyDomain: "acme.com",
|
|
40
|
+
companyIndustry: "Developer tooling", companyStage: "Series B", companyTagline: "Ship faster",
|
|
41
|
+
provider: "subscription",
|
|
42
|
+
channels: ["slack", "gmail"],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Fake enrichment spawn — returns valid JSON for whatever keys are requested. */
|
|
46
|
+
function fakeEnrichSpawn(prompt) {
|
|
47
|
+
// The system prompt lists the keys as JSON; pick a sensible filler per key.
|
|
48
|
+
const m = prompt.system.match(/ONLY these keys:\s*(\[[^\]]*\])/);
|
|
49
|
+
let keys = [];
|
|
50
|
+
try { keys = m ? JSON.parse(m[1]) : []; } catch { keys = []; }
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const k of keys) {
|
|
53
|
+
if (k === "responsibilities" || k === "operatingPrinciples") {
|
|
54
|
+
out[k] = ["Own platform reliability and delivery confidence", "Keep the engineering org unblocked and accountable"];
|
|
55
|
+
} else if (k === "persona") {
|
|
56
|
+
out[k] = "A pragmatic engineering leader who keeps delivery honest and the team focused on outcomes.";
|
|
57
|
+
} else if (k === "description") {
|
|
58
|
+
out[k] = "Acme builds developer tooling that helps fintechs ship faster with confidence.";
|
|
59
|
+
} else {
|
|
60
|
+
out[k] = "Concrete, company-specific copy that reads like a colleague wrote it.";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Promise.resolve(JSON.stringify(out));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function seedAgentRepo() {
|
|
67
|
+
const root = mkdtempSync(join(tmpdir(), "maestro-int-"));
|
|
68
|
+
mkdirSync(join(root, "config"), { recursive: true });
|
|
69
|
+
// Seed agent.json from the scaffold (placeholder identity) + agent.ts standalone literal.
|
|
70
|
+
copyFileSync(join(MAESTRO_ROOT, "scaffold", "config", "agent.json"), join(root, "config", "agent.json"));
|
|
71
|
+
copyFileSync(join(MAESTRO_ROOT, "scaffold", "config", "agent.ts"), join(root, "config", "agent.ts"));
|
|
72
|
+
copyFileSync(join(MAESTRO_ROOT, "scaffold", "config", "company.json"), join(root, "config", "company.json"));
|
|
73
|
+
writeFileSync(join(root, "README.md"), "# Acme agent\nAutonomous engineering leader for Acme.\n");
|
|
74
|
+
writeFileSync(join(root, ".env"), "");
|
|
75
|
+
return root;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test("maestro setup --headless produces a green operating model + wrapper, re-run is a no-op", async () => {
|
|
79
|
+
const root = seedAgentRepo();
|
|
80
|
+
const sections = await loadSections();
|
|
81
|
+
|
|
82
|
+
// First run.
|
|
83
|
+
const res = await runSetup({
|
|
84
|
+
agentRoot: root,
|
|
85
|
+
maestroRoot: MAESTRO_ROOT,
|
|
86
|
+
sections,
|
|
87
|
+
flags: { resume: false, from: null, only: null, status: false, headless: true, answersFile: null, noEnrich: false, dryRun: false, verbose: false, help: false },
|
|
88
|
+
answers: ANSWERS,
|
|
89
|
+
enrichSpawn: fakeEnrichSpawn,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// identity + company + operating-model all ran.
|
|
93
|
+
assert.ok(res.ran.includes("identity"), "identity ran");
|
|
94
|
+
assert.ok(res.ran.includes("operating-model"), "operating-model ran");
|
|
95
|
+
|
|
96
|
+
// 1. Green completeness gate.
|
|
97
|
+
const gate = await assertComplete(root);
|
|
98
|
+
assert.equal(gate.ok, true, "completeness gate green; missing: " + JSON.stringify(gate.missing));
|
|
99
|
+
|
|
100
|
+
// 2. agent.ts is the thin wrapper (born unified).
|
|
101
|
+
assert.ok(isWrapper(readFileSync(join(root, "config", "agent.ts"), "utf-8")), "agent.ts is a SoT wrapper");
|
|
102
|
+
|
|
103
|
+
// identity was actually written.
|
|
104
|
+
const id = JSON.parse(readFileSync(join(root, "config", "agent.json"), "utf-8"));
|
|
105
|
+
assert.equal(id.firstName, "Jacob");
|
|
106
|
+
assert.equal(id.function, "technical-leader");
|
|
107
|
+
assert.equal(id.altitude, "vp");
|
|
108
|
+
// enriched prose landed.
|
|
109
|
+
assert.ok(Array.isArray(id.responsibilities) && id.responsibilities.length >= 1, "responsibilities enriched");
|
|
110
|
+
|
|
111
|
+
// verify section produced a result and it is ok (no hard failures).
|
|
112
|
+
assert.ok(res.verify, "verify ran");
|
|
113
|
+
|
|
114
|
+
// 3. Re-run is a no-op for the heavy sections.
|
|
115
|
+
const res2 = await runSetup({
|
|
116
|
+
agentRoot: root,
|
|
117
|
+
maestroRoot: MAESTRO_ROOT,
|
|
118
|
+
sections,
|
|
119
|
+
flags: { resume: true, from: null, only: null, status: false, headless: true, answersFile: null, noEnrich: false, dryRun: false, verbose: false, help: false },
|
|
120
|
+
answers: ANSWERS,
|
|
121
|
+
enrichSpawn: fakeEnrichSpawn,
|
|
122
|
+
});
|
|
123
|
+
assert.ok(res2.skipped.includes("identity"), "identity skipped on re-run");
|
|
124
|
+
assert.ok(res2.skipped.includes("operating-model"), "operating-model skipped on re-run");
|
|
125
|
+
assert.ok(res2.skipped.includes("company"), "company skipped on re-run");
|
|
126
|
+
|
|
127
|
+
// checkpoint recorded the completed sections.
|
|
128
|
+
const prog = loadProgress(root);
|
|
129
|
+
assert.equal(prog.sections["operating-model"].status, "complete");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("maestro setup --status runs detect on every section without writing", async () => {
|
|
133
|
+
const root = seedAgentRepo();
|
|
134
|
+
const sections = await loadSections();
|
|
135
|
+
const res = await runSetup({
|
|
136
|
+
agentRoot: root,
|
|
137
|
+
maestroRoot: MAESTRO_ROOT,
|
|
138
|
+
sections,
|
|
139
|
+
flags: { resume: false, from: null, only: null, status: true, headless: true, answersFile: null, noEnrich: false, dryRun: false, verbose: false, help: false },
|
|
140
|
+
answers: {},
|
|
141
|
+
});
|
|
142
|
+
assert.equal(res.status, true);
|
|
143
|
+
// status must not have generated the operating model.
|
|
144
|
+
assert.equal(existsSync(join(root, "config", "operating-charter.md")), false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("maestro setup --no-enrich leaves prose skeletons but still completes the gate", async () => {
|
|
148
|
+
const root = seedAgentRepo();
|
|
149
|
+
const sections = await loadSections();
|
|
150
|
+
await runSetup({
|
|
151
|
+
agentRoot: root,
|
|
152
|
+
maestroRoot: MAESTRO_ROOT,
|
|
153
|
+
sections,
|
|
154
|
+
flags: { resume: false, from: null, only: null, status: false, headless: true, answersFile: null, noEnrich: true, dryRun: false, verbose: false, help: false },
|
|
155
|
+
answers: ANSWERS,
|
|
156
|
+
});
|
|
157
|
+
// The deterministic operating-model artifacts still exist...
|
|
158
|
+
assert.ok(existsSync(join(root, "config", "agent-roster.yaml")));
|
|
159
|
+
// ...but the charter still carries TBD slots (not enriched), so the gate is NOT green.
|
|
160
|
+
const gate = await assertComplete(root);
|
|
161
|
+
assert.equal(gate.ok, false, "without enrichment, charter TBD slots keep the gate red");
|
|
162
|
+
});
|
package/lib/setup/io.mjs
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/setup/io.mjs — terminal prompt helpers for the `maestro setup` wizard.
|
|
3
|
+
*
|
|
4
|
+
* House style: Node built-ins only (`node:readline/promises` + raw-mode stdin),
|
|
5
|
+
* no new npm dependency. Every helper accepts a `{ headless, answers }` context;
|
|
6
|
+
* in headless/non-TTY mode each returns the pre-supplied answer
|
|
7
|
+
* (`answers[key]` → env → default) and NEVER blocks on input.
|
|
8
|
+
*
|
|
9
|
+
* Helpers:
|
|
10
|
+
* text(ctx, opts) → string
|
|
11
|
+
* select(ctx, opts) → value of the chosen choice
|
|
12
|
+
* multiselect(ctx, opts) → array of chosen values
|
|
13
|
+
* confirm(ctx, opts) → boolean
|
|
14
|
+
* secret(ctx, opts) → string (masked echo, never persisted by io)
|
|
15
|
+
* note(...) → void (stderr-free informational print)
|
|
16
|
+
*
|
|
17
|
+
* Pure helpers: no side effects beyond stdio. The wizard sections own all file
|
|
18
|
+
* writes; io only reads input and prints prompts.
|
|
19
|
+
*
|
|
20
|
+
* @module lib/setup/io
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
"use strict";
|
|
24
|
+
|
|
25
|
+
import { createInterface } from "node:readline/promises";
|
|
26
|
+
import { stdin as defaultIn, stdout as defaultOut } from "node:process";
|
|
27
|
+
|
|
28
|
+
/** ANSI escape helpers (kept tiny + dependency-free). */
|
|
29
|
+
const ESC = {
|
|
30
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
31
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
32
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
33
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
34
|
+
reset: "\x1b[0m",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Is this context running without an interactive TTY (so prompts must not
|
|
39
|
+
* block)? True when `headless` is set, when stdin lacks `isTTY`, or when raw
|
|
40
|
+
* mode is unavailable.
|
|
41
|
+
* @param {object} [ctx]
|
|
42
|
+
* @param {NodeJS.ReadStream} [input]
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
export function isHeadless(ctx = {}, input = defaultIn) {
|
|
46
|
+
if (ctx.headless) return true;
|
|
47
|
+
if (!input || !input.isTTY) return true;
|
|
48
|
+
return typeof input.setRawMode !== "function";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a pre-supplied answer for `key` from the context answers bag, then
|
|
53
|
+
* the environment, then the provided default. Returns `undefined` when nothing
|
|
54
|
+
* is available (caller decides whether that is fatal).
|
|
55
|
+
* @param {object} ctx
|
|
56
|
+
* @param {string} [key]
|
|
57
|
+
* @param {{env?:string, default?:*}} [opts]
|
|
58
|
+
*/
|
|
59
|
+
export function resolveAnswer(ctx, key, opts = {}) {
|
|
60
|
+
const answers = (ctx && ctx.answers) || {};
|
|
61
|
+
if (key && Object.prototype.hasOwnProperty.call(answers, key) && answers[key] !== undefined) {
|
|
62
|
+
return answers[key];
|
|
63
|
+
}
|
|
64
|
+
if (opts.env && process.env[opts.env] !== undefined && process.env[opts.env] !== "") {
|
|
65
|
+
return process.env[opts.env];
|
|
66
|
+
}
|
|
67
|
+
if (Object.prototype.hasOwnProperty.call(opts, "default")) return opts.default;
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Strip bracketed-paste markers a terminal may wrap pasted input with. */
|
|
72
|
+
export function stripBracketedPaste(s) {
|
|
73
|
+
if (typeof s !== "string") return s;
|
|
74
|
+
// ESC [ 200 ~ … ESC [ 201 ~ (and any bare occurrence of the markers).
|
|
75
|
+
return s.replace(/\x1b\[20[01]~/g, "");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Free-text prompt.
|
|
80
|
+
* @param {object} ctx wizard context ({ headless, answers })
|
|
81
|
+
* @param {{message:string, key?:string, env?:string, default?:string, trim?:boolean}} opts
|
|
82
|
+
* @returns {Promise<string>}
|
|
83
|
+
*/
|
|
84
|
+
export async function text(ctx, opts = {}) {
|
|
85
|
+
const trim = opts.trim !== false;
|
|
86
|
+
if (isHeadless(ctx)) {
|
|
87
|
+
const v = resolveAnswer(ctx, opts.key, opts);
|
|
88
|
+
const s = v === undefined || v === null ? "" : String(v);
|
|
89
|
+
return trim ? s.trim() : s;
|
|
90
|
+
}
|
|
91
|
+
const rl = createInterface({ input: defaultIn, output: defaultOut });
|
|
92
|
+
try {
|
|
93
|
+
const def = resolveAnswer(ctx, opts.key, opts);
|
|
94
|
+
const suffix = def ? ` ${ESC.dim(`(${def})`)}` : "";
|
|
95
|
+
const answer = await rl.question(`${opts.message}${suffix}: `);
|
|
96
|
+
const clean = stripBracketedPaste(answer);
|
|
97
|
+
const out = trim ? clean.trim() : clean;
|
|
98
|
+
if (!out && def !== undefined && def !== null) return trim ? String(def).trim() : String(def);
|
|
99
|
+
return out;
|
|
100
|
+
} finally {
|
|
101
|
+
rl.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Yes/no confirmation.
|
|
107
|
+
* @param {object} ctx
|
|
108
|
+
* @param {{message:string, key?:string, env?:string, default?:boolean}} opts
|
|
109
|
+
* @returns {Promise<boolean>}
|
|
110
|
+
*/
|
|
111
|
+
export async function confirm(ctx, opts = {}) {
|
|
112
|
+
const dflt = opts.default !== undefined ? !!opts.default : false;
|
|
113
|
+
if (isHeadless(ctx)) {
|
|
114
|
+
const v = resolveAnswer(ctx, opts.key, { ...opts, default: dflt });
|
|
115
|
+
return coerceBool(v, dflt);
|
|
116
|
+
}
|
|
117
|
+
const rl = createInterface({ input: defaultIn, output: defaultOut });
|
|
118
|
+
try {
|
|
119
|
+
const hint = dflt ? "Y/n" : "y/N";
|
|
120
|
+
const answer = (await rl.question(`${opts.message} ${ESC.dim(`(${hint})`)} `)).trim().toLowerCase();
|
|
121
|
+
if (!answer) return dflt;
|
|
122
|
+
return /^y(es)?$/.test(answer);
|
|
123
|
+
} finally {
|
|
124
|
+
rl.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function coerceBool(v, dflt) {
|
|
129
|
+
if (typeof v === "boolean") return v;
|
|
130
|
+
if (v === undefined || v === null || v === "") return dflt;
|
|
131
|
+
return /^(1|y|yes|true|on)$/i.test(String(v).trim());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Single-choice select. Choices: `[{ label, value }]` or bare strings.
|
|
136
|
+
* Interactive mode uses raw ↑/↓ + enter; headless/non-TTY returns the
|
|
137
|
+
* pre-supplied answer (matched against value or label) or the default.
|
|
138
|
+
* @param {object} ctx
|
|
139
|
+
* @param {{message:string, choices:Array, key?:string, env?:string, default?:*}} opts
|
|
140
|
+
* @returns {Promise<*>}
|
|
141
|
+
*/
|
|
142
|
+
export async function select(ctx, opts = {}) {
|
|
143
|
+
const choices = normalizeChoices(opts.choices);
|
|
144
|
+
if (choices.length === 0) throw new Error("select: choices must be non-empty");
|
|
145
|
+
|
|
146
|
+
if (isHeadless(ctx)) {
|
|
147
|
+
const v = resolveAnswer(ctx, opts.key, opts);
|
|
148
|
+
const match = matchChoice(choices, v);
|
|
149
|
+
if (match) return match.value;
|
|
150
|
+
return (matchChoice(choices, opts.default) || choices[0]).value;
|
|
151
|
+
}
|
|
152
|
+
const idx = await rawSelect(opts.message, choices, defaultIndex(choices, resolveAnswer(ctx, opts.key, opts)));
|
|
153
|
+
return choices[idx].value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Multi-choice select (space toggles, enter confirms). Headless returns the
|
|
158
|
+
* pre-supplied answer array (each matched against value/label) or default.
|
|
159
|
+
* @param {object} ctx
|
|
160
|
+
* @param {{message:string, choices:Array, key?:string, env?:string, default?:Array}} opts
|
|
161
|
+
* @returns {Promise<Array>}
|
|
162
|
+
*/
|
|
163
|
+
export async function multiselect(ctx, opts = {}) {
|
|
164
|
+
const choices = normalizeChoices(opts.choices);
|
|
165
|
+
if (choices.length === 0) return [];
|
|
166
|
+
|
|
167
|
+
if (isHeadless(ctx)) {
|
|
168
|
+
const raw = resolveAnswer(ctx, opts.key, opts);
|
|
169
|
+
const arr = Array.isArray(raw) ? raw : raw == null || raw === "" ? (opts.default || []) : [raw];
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const item of arr) {
|
|
172
|
+
const m = matchChoice(choices, item);
|
|
173
|
+
if (m) out.push(m.value);
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
const preselected = new Set();
|
|
178
|
+
const def = resolveAnswer(ctx, opts.key, opts);
|
|
179
|
+
if (Array.isArray(def)) for (const d of def) { const m = matchChoice(choices, d); if (m) preselected.add(choices.indexOf(m)); }
|
|
180
|
+
const chosen = await rawMultiSelect(opts.message, choices, preselected);
|
|
181
|
+
return chosen.map((i) => choices[i].value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Masked secret prompt. Echoes `*` per character; strips bracketed-paste
|
|
186
|
+
* markers. Falls back to a non-masked readline if raw mode is unavailable but
|
|
187
|
+
* stdin is still a TTY (rare). Headless returns the pre-supplied answer.
|
|
188
|
+
* `io` never persists secrets — the caller writes them.
|
|
189
|
+
* @param {object} ctx
|
|
190
|
+
* @param {{message:string, key?:string, env?:string, default?:string}} opts
|
|
191
|
+
* @returns {Promise<string>}
|
|
192
|
+
*/
|
|
193
|
+
export async function secret(ctx, opts = {}) {
|
|
194
|
+
if (isHeadless(ctx)) {
|
|
195
|
+
const v = resolveAnswer(ctx, opts.key, opts);
|
|
196
|
+
return v === undefined || v === null ? "" : String(v);
|
|
197
|
+
}
|
|
198
|
+
const input = defaultIn;
|
|
199
|
+
const output = defaultOut;
|
|
200
|
+
if (typeof input.setRawMode !== "function") {
|
|
201
|
+
// Non-raw TTY fallback (no masking possible) — still functional.
|
|
202
|
+
const rl = createInterface({ input, output });
|
|
203
|
+
try { return stripBracketedPaste((await rl.question(`${opts.message}: `)).trim()); }
|
|
204
|
+
finally { rl.close(); }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
output.write(`${opts.message}: `);
|
|
209
|
+
let buf = "";
|
|
210
|
+
const prevRaw = input.isRaw;
|
|
211
|
+
input.setRawMode(true);
|
|
212
|
+
input.resume();
|
|
213
|
+
input.setEncoding("utf8");
|
|
214
|
+
|
|
215
|
+
const cleanup = () => {
|
|
216
|
+
input.setRawMode(prevRaw || false);
|
|
217
|
+
input.pause();
|
|
218
|
+
input.removeListener("data", onData);
|
|
219
|
+
output.write("\n");
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const onData = (chunk) => {
|
|
223
|
+
const data = stripBracketedPaste(chunk);
|
|
224
|
+
for (const ch of data) {
|
|
225
|
+
if (ch === "\r" || ch === "\n") { cleanup(); resolve(buf); return; }
|
|
226
|
+
if (ch === "") { cleanup(); reject(new Error("cancelled")); return; } // Ctrl-C
|
|
227
|
+
if (ch === "" || ch === "\b") { // backspace
|
|
228
|
+
if (buf.length > 0) { buf = buf.slice(0, -1); output.write("\b \b"); }
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (ch < " ") continue; // ignore other control chars
|
|
232
|
+
buf += ch;
|
|
233
|
+
output.write("*");
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
input.on("data", onData);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Informational line — printed to stdout, never to stderr (not an error). */
|
|
241
|
+
export function note(message, { dim = false } = {}) {
|
|
242
|
+
defaultOut.write(`${dim ? ESC.dim(message) : message}\n`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// internals
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
function normalizeChoices(choices) {
|
|
250
|
+
if (!Array.isArray(choices)) return [];
|
|
251
|
+
return choices.map((c) =>
|
|
252
|
+
c && typeof c === "object" && "value" in c
|
|
253
|
+
? { label: c.label != null ? String(c.label) : String(c.value), value: c.value, hint: c.hint }
|
|
254
|
+
: { label: String(c), value: c }
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function matchChoice(choices, needle) {
|
|
259
|
+
if (needle === undefined || needle === null) return null;
|
|
260
|
+
const n = String(needle).trim().toLowerCase();
|
|
261
|
+
return (
|
|
262
|
+
choices.find((c) => String(c.value).toLowerCase() === n) ||
|
|
263
|
+
choices.find((c) => c.label.toLowerCase() === n) ||
|
|
264
|
+
null
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function defaultIndex(choices, needle) {
|
|
269
|
+
const m = matchChoice(choices, needle);
|
|
270
|
+
const i = m ? choices.indexOf(m) : 0;
|
|
271
|
+
return i < 0 ? 0 : i;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Raw-mode single select returning the chosen index. */
|
|
275
|
+
function rawSelect(message, choices, start = 0) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
const input = defaultIn;
|
|
278
|
+
const output = defaultOut;
|
|
279
|
+
let cur = start;
|
|
280
|
+
const prevRaw = input.isRaw;
|
|
281
|
+
input.setRawMode(true);
|
|
282
|
+
input.resume();
|
|
283
|
+
input.setEncoding("utf8");
|
|
284
|
+
|
|
285
|
+
const render = (first) => {
|
|
286
|
+
if (!first) output.write(`\x1b[${choices.length + 1}A`); // move cursor up to redraw
|
|
287
|
+
output.write(`${ESC.bold(message)} ${ESC.dim("(↑/↓, enter)")}\n`);
|
|
288
|
+
choices.forEach((c, i) => {
|
|
289
|
+
const marker = i === cur ? ESC.cyan("❯") : " ";
|
|
290
|
+
const label = i === cur ? ESC.cyan(c.label) : c.label;
|
|
291
|
+
const hint = c.hint ? ` ${ESC.dim(c.hint)}` : "";
|
|
292
|
+
output.write(`\x1b[2K${marker} ${label}${hint}\n`);
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const cleanup = () => {
|
|
297
|
+
input.setRawMode(prevRaw || false);
|
|
298
|
+
input.pause();
|
|
299
|
+
input.removeListener("data", onData);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const onData = (chunk) => {
|
|
303
|
+
const data = stripBracketedPaste(chunk);
|
|
304
|
+
if (data === "") { cleanup(); reject(new Error("cancelled")); return; }
|
|
305
|
+
if (data === "\r" || data === "\n") { cleanup(); resolve(cur); return; }
|
|
306
|
+
if (data === "[A" || data === "k") cur = (cur - 1 + choices.length) % choices.length;
|
|
307
|
+
else if (data === "[B" || data === "j") cur = (cur + 1) % choices.length;
|
|
308
|
+
else { const n = parseInt(data, 10); if (!Number.isNaN(n) && n >= 1 && n <= choices.length) cur = n - 1; }
|
|
309
|
+
render(false);
|
|
310
|
+
};
|
|
311
|
+
render(true);
|
|
312
|
+
input.on("data", onData);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Raw-mode multi select returning the chosen indices (sorted). */
|
|
317
|
+
function rawMultiSelect(message, choices, preselected = new Set()) {
|
|
318
|
+
return new Promise((resolve, reject) => {
|
|
319
|
+
const input = defaultIn;
|
|
320
|
+
const output = defaultOut;
|
|
321
|
+
let cur = 0;
|
|
322
|
+
const chosen = new Set(preselected);
|
|
323
|
+
const prevRaw = input.isRaw;
|
|
324
|
+
input.setRawMode(true);
|
|
325
|
+
input.resume();
|
|
326
|
+
input.setEncoding("utf8");
|
|
327
|
+
|
|
328
|
+
const render = (first) => {
|
|
329
|
+
if (!first) output.write(`\x1b[${choices.length + 1}A`);
|
|
330
|
+
output.write(`${ESC.bold(message)} ${ESC.dim("(↑/↓, space, enter)")}\n`);
|
|
331
|
+
choices.forEach((c, i) => {
|
|
332
|
+
const cursor = i === cur ? ESC.cyan("❯") : " ";
|
|
333
|
+
const box = chosen.has(i) ? ESC.green("◉") : "◯";
|
|
334
|
+
const label = i === cur ? ESC.cyan(c.label) : c.label;
|
|
335
|
+
const hint = c.hint ? ` ${ESC.dim(c.hint)}` : "";
|
|
336
|
+
output.write(`\x1b[2K${cursor} ${box} ${label}${hint}\n`);
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const cleanup = () => {
|
|
341
|
+
input.setRawMode(prevRaw || false);
|
|
342
|
+
input.pause();
|
|
343
|
+
input.removeListener("data", onData);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const onData = (chunk) => {
|
|
347
|
+
const data = stripBracketedPaste(chunk);
|
|
348
|
+
if (data === "") { cleanup(); reject(new Error("cancelled")); return; }
|
|
349
|
+
if (data === "\r" || data === "\n") { cleanup(); resolve([...chosen].sort((a, b) => a - b)); return; }
|
|
350
|
+
if (data === " ") { if (chosen.has(cur)) chosen.delete(cur); else chosen.add(cur); }
|
|
351
|
+
else if (data === "[A" || data === "k") cur = (cur - 1 + choices.length) % choices.length;
|
|
352
|
+
else if (data === "[B" || data === "j") cur = (cur + 1) % choices.length;
|
|
353
|
+
render(false);
|
|
354
|
+
};
|
|
355
|
+
render(true);
|
|
356
|
+
input.on("data", onData);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export default { text, select, multiselect, confirm, secret, note, isHeadless, resolveAnswer, stripBracketedPaste };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* io.test.mjs — headless-mode + helper tests for lib/setup/io.mjs.
|
|
3
|
+
* Interactive raw-mode paths are not exercised here (no TTY in CI); we test the
|
|
4
|
+
* pure helpers and the headless contract (no blocking, pre-supplied answers).
|
|
5
|
+
* Run: `node --test lib/setup/io.test.mjs`
|
|
6
|
+
* @module lib/setup/io.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
import { test } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import {
|
|
14
|
+
text, select, multiselect, confirm, secret,
|
|
15
|
+
isHeadless, resolveAnswer, stripBracketedPaste,
|
|
16
|
+
} from "./io.mjs";
|
|
17
|
+
|
|
18
|
+
const HL = { headless: true, answers: {} };
|
|
19
|
+
|
|
20
|
+
test("isHeadless is true when ctx.headless set", () => {
|
|
21
|
+
assert.equal(isHeadless({ headless: true }), true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("resolveAnswer prefers answers, then env, then default", () => {
|
|
25
|
+
assert.equal(resolveAnswer({ answers: { k: "a" } }, "k", { default: "d" }), "a");
|
|
26
|
+
process.env.__IO_TEST_ENV = "fromenv";
|
|
27
|
+
assert.equal(resolveAnswer({ answers: {} }, "k", { env: "__IO_TEST_ENV", default: "d" }), "fromenv");
|
|
28
|
+
delete process.env.__IO_TEST_ENV;
|
|
29
|
+
assert.equal(resolveAnswer({ answers: {} }, "k", { default: "d" }), "d");
|
|
30
|
+
assert.equal(resolveAnswer({ answers: {} }, "k", {}), undefined);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("stripBracketedPaste removes paste markers", () => {
|
|
34
|
+
assert.equal(stripBracketedPaste("\x1b[200~hello\x1b[201~"), "hello");
|
|
35
|
+
assert.equal(stripBracketedPaste("plain"), "plain");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("text returns pre-supplied answer in headless mode (never blocks)", async () => {
|
|
39
|
+
const v = await text({ ...HL, answers: { name: "Jacob" } }, { message: "Name?", key: "name" });
|
|
40
|
+
assert.equal(v, "Jacob");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("text falls back to default when no answer", async () => {
|
|
44
|
+
const v = await text(HL, { message: "Name?", key: "missing", default: "Anon" });
|
|
45
|
+
assert.equal(v, "Anon");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("confirm coerces headless answer strings", async () => {
|
|
49
|
+
assert.equal(await confirm({ ...HL, answers: { ok: "yes" } }, { message: "?", key: "ok" }), true);
|
|
50
|
+
assert.equal(await confirm({ ...HL, answers: { ok: false } }, { message: "?", key: "ok" }), false);
|
|
51
|
+
assert.equal(await confirm(HL, { message: "?", key: "x", default: true }), true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("select matches by value or label, falls back to first/default", async () => {
|
|
55
|
+
const choices = [{ label: "Sonnet", value: "sonnet" }, { label: "Haiku", value: "haiku" }];
|
|
56
|
+
assert.equal(await select({ ...HL, answers: { m: "haiku" } }, { message: "?", key: "m", choices }), "haiku");
|
|
57
|
+
assert.equal(await select({ ...HL, answers: { m: "Sonnet" } }, { message: "?", key: "m", choices }), "sonnet");
|
|
58
|
+
assert.equal(await select(HL, { message: "?", key: "m", choices, default: "haiku" }), "haiku");
|
|
59
|
+
assert.equal(await select(HL, { message: "?", key: "m", choices }), "sonnet"); // first
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("multiselect returns matched values, ignoring unknowns", async () => {
|
|
63
|
+
const choices = [{ label: "Slack", value: "slack" }, { label: "Email", value: "email" }, { label: "SMS", value: "sms" }];
|
|
64
|
+
const v = await multiselect({ ...HL, answers: { ch: ["slack", "sms", "nope"] } }, { message: "?", key: "ch", choices });
|
|
65
|
+
assert.deepEqual(v, ["slack", "sms"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("multiselect default applies when answer absent", async () => {
|
|
69
|
+
const choices = ["a", "b", "c"];
|
|
70
|
+
const v = await multiselect(HL, { message: "?", key: "ch", choices, default: ["b"] });
|
|
71
|
+
assert.deepEqual(v, ["b"]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("secret returns pre-supplied answer headless, never echoing", async () => {
|
|
75
|
+
const v = await secret({ ...HL, answers: { token: "xapp-123" } }, { message: "Token", key: "token" });
|
|
76
|
+
assert.equal(v, "xapp-123");
|
|
77
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/setup/run-generator.mjs — invoke a scripts/setup/*.mjs generator with the
|
|
3
|
+
* shared execution contract (cwd = agent repo; env MAESTRO_ROOT / AGENT_ROOT).
|
|
4
|
+
*
|
|
5
|
+
* Generators that only template from config/agent.json run for real. The runner
|
|
6
|
+
* surfaces failures to the caller, which decides whether they are fatal (the
|
|
7
|
+
* operating-model section treats a generator failure as fatal; verify reports it).
|
|
8
|
+
*
|
|
9
|
+
* @module lib/setup/run-generator
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
|
|
19
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const MAESTRO_ROOT = join(HERE, "..", "..");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run a generator script. Returns { ok, stdout, stderr, code }. Never throws on
|
|
24
|
+
* a non-zero exit (captured), but a spawn failure (ENOENT) is captured too.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} script filename under scripts/setup/ (e.g. "generate-charter.mjs")
|
|
27
|
+
* @param {string} agentRoot
|
|
28
|
+
* @param {object} [opts]
|
|
29
|
+
* @param {string} [opts.maestroRoot]
|
|
30
|
+
* @param {boolean} [opts.bash] run via /bin/bash (for .sh generators)
|
|
31
|
+
* @param {string[]} [opts.args]
|
|
32
|
+
* @returns {{ok:boolean, stdout:string, stderr:string, code:number}}
|
|
33
|
+
*/
|
|
34
|
+
export function runGenerator(script, agentRoot, opts = {}) {
|
|
35
|
+
const maestroRoot = opts.maestroRoot || MAESTRO_ROOT;
|
|
36
|
+
const scriptPath = join(maestroRoot, "scripts", "setup", script);
|
|
37
|
+
if (!existsSync(scriptPath)) {
|
|
38
|
+
return { ok: false, stdout: "", stderr: `generator not found: ${scriptPath}`, code: 127 };
|
|
39
|
+
}
|
|
40
|
+
const cmd = opts.bash ? "/bin/bash" : process.execPath;
|
|
41
|
+
const argv = opts.bash ? [scriptPath, ...(opts.args || [])] : [scriptPath, ...(opts.args || [])];
|
|
42
|
+
try {
|
|
43
|
+
const stdout = execFileSync(cmd, argv, {
|
|
44
|
+
cwd: agentRoot,
|
|
45
|
+
env: { ...process.env, MAESTRO_ROOT: maestroRoot, AGENT_ROOT: agentRoot, AGENT_DIR: agentRoot },
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
});
|
|
48
|
+
return { ok: true, stdout: stdout || "", stderr: "", code: 0 };
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
stdout: e && e.stdout ? String(e.stdout) : "",
|
|
53
|
+
stderr: e && e.stderr ? String(e.stderr) : (e && e.message ? e.message : String(e)),
|
|
54
|
+
code: e && typeof e.status === "number" ? e.status : 1,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Run a script that lives somewhere other than scripts/setup (absolute path). */
|
|
60
|
+
export function runScriptAbs(scriptPath, agentRoot, opts = {}) {
|
|
61
|
+
const maestroRoot = opts.maestroRoot || MAESTRO_ROOT;
|
|
62
|
+
if (!existsSync(scriptPath)) return { ok: false, stdout: "", stderr: `script not found: ${scriptPath}`, code: 127 };
|
|
63
|
+
const cmd = opts.bash ? "/bin/bash" : process.execPath;
|
|
64
|
+
try {
|
|
65
|
+
const stdout = execFileSync(cmd, [scriptPath, ...(opts.args || [])], {
|
|
66
|
+
cwd: agentRoot,
|
|
67
|
+
env: { ...process.env, MAESTRO_ROOT: maestroRoot, AGENT_ROOT: agentRoot, AGENT_DIR: agentRoot },
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
});
|
|
70
|
+
return { ok: true, stdout: stdout || "", stderr: "", code: 0 };
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
stdout: e && e.stdout ? String(e.stdout) : "",
|
|
75
|
+
stderr: e && e.stderr ? String(e.stderr) : (e && e.message ? e.message : String(e)),
|
|
76
|
+
code: e && typeof e.status === "number" ? e.status : 1,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { MAESTRO_ROOT as DEFAULT_MAESTRO_ROOT };
|