@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,248 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# restore-from-backup.sh — executable dead-Mac-mini restore runbook (G16).
|
|
3
|
+
#
|
|
4
|
+
# Rebuilds a Maestro agent's durable state onto replacement hardware from a single
|
|
5
|
+
# backup archive produced by scripts/maintenance/backup-to-cloud.sh. This is the
|
|
6
|
+
# runbook in docs/runbooks/backup-restore.md made EXECUTABLE: instead of a human
|
|
7
|
+
# hand-copying state/, logs/, and config/ from a tarball and trying to remember
|
|
8
|
+
# the re-pair + secret-recovery steps, this script does the mechanical parts and
|
|
9
|
+
# PRINTS the irreducibly-manual ones (secret recovery, Cohort re-pair approval).
|
|
10
|
+
#
|
|
11
|
+
# What it does:
|
|
12
|
+
# 1. Validates the archive (and client-side decrypts it if it is encrypted —
|
|
13
|
+
# .gpg / .age — using a passphrase from $RESTORE_PASSPHRASE).
|
|
14
|
+
# 2. Unpacks into a scratch dir and shows what the archive contains.
|
|
15
|
+
# 3. Restores config/, state/, and logs/ into the agent repo (existing dirs are
|
|
16
|
+
# backed up to <dir>.pre-restore.<ts> first, so a re-run never destroys what
|
|
17
|
+
# it already placed — idempotent).
|
|
18
|
+
# 4. Re-pairs with Cohort (prints the operator-station approval command; mints
|
|
19
|
+
# a FRESH device token rather than restoring the dead one — never restore an
|
|
20
|
+
# identity credential).
|
|
21
|
+
# 5. Prints the remaining manual steps: secret recovery via `maestro secrets
|
|
22
|
+
# sync`, .env lockdown, doctor, resume.
|
|
23
|
+
#
|
|
24
|
+
# Idempotent: safe to re-run. --dry-run lists every step WITHOUT touching the
|
|
25
|
+
# filesystem, decrypting, or pairing. bash -n clean; shellcheck-friendly.
|
|
26
|
+
#
|
|
27
|
+
# Usage:
|
|
28
|
+
# scripts/restore-from-backup.sh --archive <path> [--agent-dir <dir>] [--dry-run]
|
|
29
|
+
# scripts/restore-from-backup.sh --archive backup.tar.gz.gpg --dry-run
|
|
30
|
+
#
|
|
31
|
+
# Env:
|
|
32
|
+
# RESTORE_PASSPHRASE passphrase for an encrypted (.gpg/.age) archive
|
|
33
|
+
# AGENT_DIR override the target agent repo (default: repo root)
|
|
34
|
+
# COHORT_AGENT_ID agent id used in the printed re-pair command
|
|
35
|
+
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Defaults + arg parsing
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
43
|
+
DEFAULT_AGENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
44
|
+
|
|
45
|
+
ARCHIVE=""
|
|
46
|
+
AGENT_DIR="${AGENT_DIR:-$DEFAULT_AGENT_DIR}"
|
|
47
|
+
DRY_RUN=0
|
|
48
|
+
TS="$(date -u +"%Y%m%dT%H%M%SZ")"
|
|
49
|
+
|
|
50
|
+
usage() {
|
|
51
|
+
cat <<'USAGE'
|
|
52
|
+
restore-from-backup.sh — dead-Mac-mini restore from a backup archive.
|
|
53
|
+
|
|
54
|
+
--archive <path> backup archive to restore (.tar.gz, .tgz, .tar.gz.gpg, .age)
|
|
55
|
+
--agent-dir <dir> target agent repo (default: this repo's root)
|
|
56
|
+
--dry-run print the plan; touch nothing, decrypt nothing, pair nothing
|
|
57
|
+
-h, --help this help
|
|
58
|
+
|
|
59
|
+
Encrypted archives (.gpg/.age) need RESTORE_PASSPHRASE in the environment.
|
|
60
|
+
USAGE
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
while [ "$#" -gt 0 ]; do
|
|
64
|
+
case "$1" in
|
|
65
|
+
--archive)
|
|
66
|
+
ARCHIVE="${2:-}"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
--agent-dir)
|
|
70
|
+
AGENT_DIR="${2:-}"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
--dry-run)
|
|
74
|
+
DRY_RUN=1
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
-h|--help)
|
|
78
|
+
usage
|
|
79
|
+
exit 0
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
echo "ERROR: unknown argument: $1" >&2
|
|
83
|
+
usage >&2
|
|
84
|
+
exit 2
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Output helpers
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
step() { echo "[restore] $1"; }
|
|
94
|
+
note() { echo " $1"; }
|
|
95
|
+
run_or_echo() {
|
|
96
|
+
# In dry-run, print the command prefixed with DRY-RUN and do nothing.
|
|
97
|
+
# Otherwise execute it.
|
|
98
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
99
|
+
echo " DRY-RUN: $*"
|
|
100
|
+
else
|
|
101
|
+
"$@"
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Validate inputs
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
if [ -z "$ARCHIVE" ]; then
|
|
110
|
+
echo "ERROR: --archive is required." >&2
|
|
111
|
+
usage >&2
|
|
112
|
+
exit 2
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# In a real run the archive must exist; in --dry-run we tolerate a missing path
|
|
116
|
+
# so the operator can rehearse the plan before the archive is downloaded.
|
|
117
|
+
if [ ! -f "$ARCHIVE" ] && [ "$DRY_RUN" -eq 0 ]; then
|
|
118
|
+
echo "ERROR: archive not found: $ARCHIVE" >&2
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Determine whether the archive is encrypted from its extension.
|
|
123
|
+
ENCRYPTED=0
|
|
124
|
+
case "$ARCHIVE" in
|
|
125
|
+
*.gpg|*.age) ENCRYPTED=1 ;;
|
|
126
|
+
*) ENCRYPTED=0 ;;
|
|
127
|
+
esac
|
|
128
|
+
|
|
129
|
+
step "Restore plan for agent dir: $AGENT_DIR"
|
|
130
|
+
note "archive: $ARCHIVE"
|
|
131
|
+
note "encrypted: $([ "$ENCRYPTED" -eq 1 ] && echo yes || echo no)"
|
|
132
|
+
note "mode: $([ "$DRY_RUN" -eq 1 ] && echo DRY-RUN || echo APPLY)"
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# 1. Decrypt (if encrypted)
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
SCRATCH=""
|
|
139
|
+
TARBALL="$ARCHIVE"
|
|
140
|
+
|
|
141
|
+
step "Step 1/5: validate + decrypt archive"
|
|
142
|
+
if [ "$ENCRYPTED" -eq 1 ]; then
|
|
143
|
+
note "archive is encrypted — client-side decrypt required"
|
|
144
|
+
if [ "$DRY_RUN" -eq 0 ] && [ -z "${RESTORE_PASSPHRASE:-}" ]; then
|
|
145
|
+
echo "ERROR: encrypted archive but RESTORE_PASSPHRASE is unset." >&2
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/maestro-restore.${TS}.XXXXXX")"
|
|
149
|
+
TARBALL="$SCRATCH/decrypted.tar.gz"
|
|
150
|
+
case "$ARCHIVE" in
|
|
151
|
+
*.gpg)
|
|
152
|
+
run_or_echo gpg --batch --yes --passphrase "${RESTORE_PASSPHRASE:-}" \
|
|
153
|
+
--decrypt --output "$TARBALL" "$ARCHIVE"
|
|
154
|
+
;;
|
|
155
|
+
*.age)
|
|
156
|
+
run_or_echo age --decrypt --output "$TARBALL" "$ARCHIVE"
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
else
|
|
160
|
+
note "archive is plaintext — no decrypt step"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# 2. Unpack into scratch + inspect
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
step "Step 2/5: unpack archive into a scratch dir and inspect"
|
|
168
|
+
if [ -z "$SCRATCH" ]; then
|
|
169
|
+
SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/maestro-restore.${TS}.XXXXXX" 2>/dev/null || echo "/tmp/maestro-restore.${TS}")"
|
|
170
|
+
fi
|
|
171
|
+
UNPACK="$SCRATCH/unpacked"
|
|
172
|
+
run_or_echo mkdir -p "$UNPACK"
|
|
173
|
+
run_or_echo tar -xzf "$TARBALL" -C "$UNPACK"
|
|
174
|
+
note "inspect before overwriting: ls $UNPACK"
|
|
175
|
+
if [ "$DRY_RUN" -eq 0 ] && [ -d "$UNPACK" ]; then
|
|
176
|
+
ls "$UNPACK" | sed 's/^/ contains: /' || true
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# 3. Restore config/, state/, logs/ — idempotent (back up existing first)
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
step "Step 3/5: restore config/, state/, logs/ into $AGENT_DIR"
|
|
184
|
+
for dir in config state logs; do
|
|
185
|
+
SRC="$UNPACK/$dir"
|
|
186
|
+
DEST="$AGENT_DIR/$dir"
|
|
187
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
188
|
+
note "DRY-RUN: would restore $dir/ (existing $DEST → $DEST.pre-restore.$TS, then copy from archive)"
|
|
189
|
+
continue
|
|
190
|
+
fi
|
|
191
|
+
if [ ! -d "$SRC" ]; then
|
|
192
|
+
note "skip $dir/ — not present in archive"
|
|
193
|
+
continue
|
|
194
|
+
fi
|
|
195
|
+
# Idempotent: move any existing dir aside (timestamped) so a re-run never
|
|
196
|
+
# clobbers a prior restore and the operator can diff/rollback.
|
|
197
|
+
if [ -e "$DEST" ]; then
|
|
198
|
+
run_or_echo mv "$DEST" "$DEST.pre-restore.$TS"
|
|
199
|
+
note "preserved existing $dir/ → $dir.pre-restore.$TS"
|
|
200
|
+
fi
|
|
201
|
+
run_or_echo mkdir -p "$AGENT_DIR"
|
|
202
|
+
run_or_echo cp -R "$SRC" "$DEST"
|
|
203
|
+
note "restored $dir/"
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
# Lock down secret-bearing files if they came back in the archive.
|
|
207
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
208
|
+
note "DRY-RUN: would chmod 600 $AGENT_DIR/.env if present"
|
|
209
|
+
elif [ -f "$AGENT_DIR/.env" ]; then
|
|
210
|
+
run_or_echo chmod 600 "$AGENT_DIR/.env"
|
|
211
|
+
note "locked down .env (0600)"
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# 4. Re-pair with Cohort (fresh device token — never restore the old one)
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
step "Step 4/5: re-establish org-server (Cohort) identity"
|
|
219
|
+
AGENT_ID="${COHORT_AGENT_ID:-<agentId>}"
|
|
220
|
+
note "Do NOT restore the dead device token. Mint a fresh one on the operator station:"
|
|
221
|
+
note " cohort --db \"\$COHORT_DB\" pair approve $AGENT_ID --scopes <scopes>"
|
|
222
|
+
note "Then write the printed token into $AGENT_DIR/.env and chmod 600 .env."
|
|
223
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
224
|
+
note "DRY-RUN: pairing is a manual operator-station action; nothing executed here."
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
# 5. Remaining manual steps
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
step "Step 5/5: remaining MANUAL steps (this script cannot do these for you)"
|
|
232
|
+
note "1. Recover secrets: maestro secrets sync"
|
|
233
|
+
note " (re-issue anything unrecoverable at its source; treat theft as full rotation)"
|
|
234
|
+
note "2. Confirm .env: chmod 600 $AGENT_DIR/.env && stat -f '%Sp' $AGENT_DIR/.env (expect -rw-------)"
|
|
235
|
+
note "3. Verify posture: maestro doctor (green before going live; backup freshness should clear)"
|
|
236
|
+
note "4. Resume ops: $AGENT_DIR/scripts/resume-operations.sh"
|
|
237
|
+
note "5. Re-arm backup: maestro init backup-replication --apply (a rebuilt box with no backup is the next dead box)"
|
|
238
|
+
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
# Cleanup (real runs only; leave scratch for inspection in dry-run is moot —
|
|
241
|
+
# nothing was created)
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
if [ "$DRY_RUN" -eq 0 ] && [ -n "$SCRATCH" ] && [ -d "$SCRATCH" ]; then
|
|
245
|
+
rm -rf "$SCRATCH" 2>/dev/null || true
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
step "Restore $([ "$DRY_RUN" -eq 1 ] && echo "plan complete (dry-run — nothing changed)" || echo "complete — finish the manual steps above")"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* restore-from-backup.test.mjs — coverage for scripts/restore-from-backup.sh.
|
|
3
|
+
*
|
|
4
|
+
* Drives the script with --dry-run over a tmp fixture (a real .tar.gz of a fake
|
|
5
|
+
* config/state/logs tree) and asserts:
|
|
6
|
+
* - `bash -n` is clean (no syntax errors)
|
|
7
|
+
* - --dry-run lists every restore step and the manual follow-ups
|
|
8
|
+
* - --dry-run touches NOTHING on disk (no dirs moved/created in the agent dir)
|
|
9
|
+
* - the required-arg + unknown-arg + help paths exit with the right codes
|
|
10
|
+
* - an encrypted (.gpg) archive surfaces the decrypt step in the plan
|
|
11
|
+
*
|
|
12
|
+
* No live decrypt / pairing happens — --dry-run prints the plan only.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { test } from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import {
|
|
18
|
+
mkdtempSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
readdirSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
existsSync,
|
|
24
|
+
} from "node:fs";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { join, resolve, dirname } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { spawnSync } from "node:child_process";
|
|
29
|
+
|
|
30
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const SCRIPT = resolve(__dirname, "restore-from-backup.sh");
|
|
32
|
+
|
|
33
|
+
/** Build a tmp dir + a real .tar.gz containing config/, state/, logs/. */
|
|
34
|
+
function makeArchive() {
|
|
35
|
+
const root = mkdtempSync(join(tmpdir(), "restore-test-"));
|
|
36
|
+
const src = join(root, "src");
|
|
37
|
+
for (const d of ["config", "state", "logs"]) {
|
|
38
|
+
mkdirSync(join(src, d), { recursive: true });
|
|
39
|
+
writeFileSync(join(src, d, "marker.txt"), `restored-${d}\n`);
|
|
40
|
+
}
|
|
41
|
+
const archive = join(root, "backup.tar.gz");
|
|
42
|
+
const tar = spawnSync("tar", ["-czf", archive, "-C", src, "config", "state", "logs"], {
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
});
|
|
45
|
+
assert.equal(tar.status, 0, `tar failed: ${tar.stderr}`);
|
|
46
|
+
return { root, archive };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function run(args, opts = {}) {
|
|
50
|
+
return spawnSync("bash", [SCRIPT, ...args], { encoding: "utf-8", ...opts });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function cleanup(p) {
|
|
54
|
+
rmSync(p, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test("bash -n: the script has no syntax errors", () => {
|
|
58
|
+
const r = spawnSync("bash", ["-n", SCRIPT], { encoding: "utf-8" });
|
|
59
|
+
assert.equal(r.status, 0, `bash -n failed: ${r.stderr}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("--dry-run lists every restore step and the manual follow-ups", () => {
|
|
63
|
+
const { root, archive } = makeArchive();
|
|
64
|
+
const agentDir = join(root, "agent");
|
|
65
|
+
mkdirSync(agentDir, { recursive: true });
|
|
66
|
+
try {
|
|
67
|
+
const r = run(["--archive", archive, "--agent-dir", agentDir, "--dry-run"]);
|
|
68
|
+
assert.equal(r.status, 0, `nonzero exit: ${r.stderr}`);
|
|
69
|
+
const out = r.stdout;
|
|
70
|
+
|
|
71
|
+
// The five numbered steps are all present.
|
|
72
|
+
assert.match(out, /Step 1\/5: validate \+ decrypt/);
|
|
73
|
+
assert.match(out, /Step 2\/5: unpack/);
|
|
74
|
+
assert.match(out, /Step 3\/5: restore config\/, state\/, logs\//);
|
|
75
|
+
assert.match(out, /Step 4\/5: re-establish org-server \(Cohort\) identity/);
|
|
76
|
+
assert.match(out, /Step 5\/5: remaining MANUAL steps/);
|
|
77
|
+
|
|
78
|
+
// Each of the three dirs is named in the plan.
|
|
79
|
+
assert.match(out, /would restore config\//);
|
|
80
|
+
assert.match(out, /would restore state\//);
|
|
81
|
+
assert.match(out, /would restore logs\//);
|
|
82
|
+
|
|
83
|
+
// The irreducibly-manual follow-ups: secret recovery + cohort re-pair.
|
|
84
|
+
assert.match(out, /maestro secrets sync/);
|
|
85
|
+
assert.match(out, /cohort .* pair approve/);
|
|
86
|
+
assert.match(out, /resume-operations\.sh/);
|
|
87
|
+
assert.match(out, /init backup-replication/);
|
|
88
|
+
|
|
89
|
+
// Dry-run banner.
|
|
90
|
+
assert.match(out, /dry-run — nothing changed/);
|
|
91
|
+
} finally {
|
|
92
|
+
cleanup(root);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("--dry-run touches NOTHING in the agent dir", () => {
|
|
97
|
+
const { root, archive } = makeArchive();
|
|
98
|
+
const agentDir = join(root, "agent");
|
|
99
|
+
mkdirSync(agentDir, { recursive: true });
|
|
100
|
+
// Seed an existing state/ to prove dry-run does not move it aside.
|
|
101
|
+
mkdirSync(join(agentDir, "state"), { recursive: true });
|
|
102
|
+
writeFileSync(join(agentDir, "state", "pre-existing.txt"), "keep me\n");
|
|
103
|
+
const before = readdirSync(agentDir).sort();
|
|
104
|
+
try {
|
|
105
|
+
const r = run(["--archive", archive, "--agent-dir", agentDir, "--dry-run"]);
|
|
106
|
+
assert.equal(r.status, 0);
|
|
107
|
+
const after = readdirSync(agentDir).sort();
|
|
108
|
+
assert.deepEqual(after, before, "dry-run must not create/move any dir");
|
|
109
|
+
// No .pre-restore.* backup was created.
|
|
110
|
+
assert.ok(
|
|
111
|
+
!after.some((n) => n.includes("pre-restore")),
|
|
112
|
+
"dry-run must not produce a pre-restore backup"
|
|
113
|
+
);
|
|
114
|
+
assert.ok(existsSync(join(agentDir, "state", "pre-existing.txt")), "pre-existing state untouched");
|
|
115
|
+
} finally {
|
|
116
|
+
cleanup(root);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("encrypted (.gpg) archive surfaces the decrypt step in the dry-run plan", () => {
|
|
121
|
+
const root = mkdtempSync(join(tmpdir(), "restore-enc-"));
|
|
122
|
+
const agentDir = join(root, "agent");
|
|
123
|
+
mkdirSync(agentDir, { recursive: true });
|
|
124
|
+
try {
|
|
125
|
+
// A .gpg path need not exist for a dry-run rehearsal.
|
|
126
|
+
const r = run(["--archive", join(root, "backup.tar.gz.gpg"), "--agent-dir", agentDir, "--dry-run"]);
|
|
127
|
+
assert.equal(r.status, 0, r.stderr);
|
|
128
|
+
assert.match(r.stdout, /encrypted: yes/);
|
|
129
|
+
assert.match(r.stdout, /client-side decrypt required/);
|
|
130
|
+
assert.match(r.stdout, /DRY-RUN: gpg .*--decrypt/);
|
|
131
|
+
} finally {
|
|
132
|
+
cleanup(root);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("missing --archive exits 2 with usage", () => {
|
|
137
|
+
const r = run(["--dry-run"]);
|
|
138
|
+
assert.equal(r.status, 2);
|
|
139
|
+
assert.match(r.stderr, /--archive is required/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("unknown argument exits 2", () => {
|
|
143
|
+
const r = run(["--archive", "x", "--bogus"]);
|
|
144
|
+
assert.equal(r.status, 2);
|
|
145
|
+
assert.match(r.stderr, /unknown argument/);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("--help exits 0 and prints usage", () => {
|
|
149
|
+
const r = run(["--help"]);
|
|
150
|
+
assert.equal(r.status, 0);
|
|
151
|
+
assert.match(r.stdout, /dead-Mac-mini restore/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("a real (non-dry) restore is idempotent: re-run preserves the prior restore", () => {
|
|
155
|
+
const { root, archive } = makeArchive();
|
|
156
|
+
const agentDir = join(root, "agent");
|
|
157
|
+
mkdirSync(agentDir, { recursive: true });
|
|
158
|
+
try {
|
|
159
|
+
// First restore.
|
|
160
|
+
const r1 = run(["--archive", archive, "--agent-dir", agentDir]);
|
|
161
|
+
assert.equal(r1.status, 0, `first restore failed: ${r1.stderr}`);
|
|
162
|
+
assert.ok(existsSync(join(agentDir, "state", "marker.txt")), "state restored");
|
|
163
|
+
assert.ok(existsSync(join(agentDir, "config", "marker.txt")), "config restored");
|
|
164
|
+
assert.ok(existsSync(join(agentDir, "logs", "marker.txt")), "logs restored");
|
|
165
|
+
|
|
166
|
+
// Second restore must not fail and must preserve the prior dirs aside.
|
|
167
|
+
const r2 = run(["--archive", archive, "--agent-dir", agentDir]);
|
|
168
|
+
assert.equal(r2.status, 0, `second restore failed: ${r2.stderr}`);
|
|
169
|
+
const after = readdirSync(agentDir);
|
|
170
|
+
assert.ok(
|
|
171
|
+
after.some((n) => n.startsWith("state.pre-restore.")),
|
|
172
|
+
"re-run preserved the prior state/ as a timestamped backup"
|
|
173
|
+
);
|
|
174
|
+
assert.ok(existsSync(join(agentDir, "state", "marker.txt")), "state restored again");
|
|
175
|
+
} finally {
|
|
176
|
+
cleanup(root);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Resume Operations — Restarts Maestro agent after an emergency stop.
|
|
3
|
+
# Usage: ./scripts/resume-operations.sh
|
|
4
|
+
#
|
|
5
|
+
# Reverses emergency-stop.sh:
|
|
6
|
+
# 1. Verifies health check passes.
|
|
7
|
+
# 2. Removes the .emergency-stop flag.
|
|
8
|
+
# 3. Reloads every installed `ai.maestro.<agent>-*` (and legacy
|
|
9
|
+
# `ai.adaptic.<agent>-*`) launchd job.
|
|
10
|
+
#
|
|
11
|
+
# Agent first-name slug resolved from config/agent.json (SOT).
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
AGENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
17
|
+
LOG_FILE="$AGENT_DIR/logs/operations.log"
|
|
18
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
19
|
+
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
|
|
20
|
+
|
|
21
|
+
# Resolve agent first-name slug.
|
|
22
|
+
AGENT_FIRST=""
|
|
23
|
+
if [ -f "$AGENT_DIR/config/agent.json" ]; then
|
|
24
|
+
if command -v jq >/dev/null 2>&1; then
|
|
25
|
+
AGENT_FIRST=$(jq -r '.firstName // empty' "$AGENT_DIR/config/agent.json" | tr '[:upper:]' '[:lower:]')
|
|
26
|
+
else
|
|
27
|
+
AGENT_FIRST=$(awk -F'"' '/"firstName"[[:space:]]*:/ { print tolower($4); exit }' "$AGENT_DIR/config/agent.json")
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
[ -z "$AGENT_FIRST" ] && AGENT_FIRST=$(basename "$AGENT_DIR" | sed 's/-ai$//')
|
|
31
|
+
|
|
32
|
+
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
|
|
33
|
+
# Match the current ai.maestro.* namespace AND the legacy ai.adaptic.* one
|
|
34
|
+
# (deployed agents whose plists predate the rename). The loops guard with [ -f ].
|
|
35
|
+
PLIST_GLOB="$LAUNCH_AGENTS_DIR/ai.maestro.${AGENT_FIRST}-*.plist $LAUNCH_AGENTS_DIR/ai.adaptic.${AGENT_FIRST}-*.plist"
|
|
36
|
+
|
|
37
|
+
# Check if stop flag exists.
|
|
38
|
+
if [ ! -f "$AGENT_DIR/.emergency-stop" ]; then
|
|
39
|
+
echo "No emergency stop flag found — system is already running."
|
|
40
|
+
# Still verify launchd jobs are loaded; if not, load them.
|
|
41
|
+
loaded=0
|
|
42
|
+
for plist in $PLIST_GLOB; do
|
|
43
|
+
[ -f "$plist" ] || continue
|
|
44
|
+
label=$(basename "$plist" .plist)
|
|
45
|
+
if ! launchctl list | grep -q "$label"; then
|
|
46
|
+
launchctl load "$plist" 2>/dev/null && loaded=$((loaded + 1))
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
[ "$loaded" -gt 0 ] && echo "Loaded $loaded missing launchd job(s)"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
echo "[$TIMESTAMP] RESUMING OPERATIONS (agent=$AGENT_FIRST)" | tee -a "$LOG_FILE"
|
|
54
|
+
|
|
55
|
+
# 1. Run health check first.
|
|
56
|
+
echo "Running health check..."
|
|
57
|
+
if ! "$SCRIPT_DIR/healthcheck.sh"; then
|
|
58
|
+
echo "ERROR: Health check failed. Fix issues before resuming."
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# 2. Remove stop flag.
|
|
63
|
+
rm "$AGENT_DIR/.emergency-stop"
|
|
64
|
+
echo "[$TIMESTAMP] Stop flag removed" >> "$LOG_FILE"
|
|
65
|
+
|
|
66
|
+
# 3. Reload installed launchd jobs.
|
|
67
|
+
echo "Reloading installed launchd jobs ($PLIST_GLOB)..."
|
|
68
|
+
loaded=0
|
|
69
|
+
for plist in $PLIST_GLOB; do
|
|
70
|
+
if [ -f "$plist" ]; then
|
|
71
|
+
# Unload first in case it was left in a half-loaded state.
|
|
72
|
+
launchctl unload "$plist" 2>/dev/null || true
|
|
73
|
+
launchctl load "$plist" 2>/dev/null || true
|
|
74
|
+
echo "[$TIMESTAMP] Loaded: $(basename "$plist")" >> "$LOG_FILE"
|
|
75
|
+
loaded=$((loaded + 1))
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
echo "[$TIMESTAMP] Loaded $loaded launchd job(s)" >> "$LOG_FILE"
|
|
79
|
+
|
|
80
|
+
echo "[$TIMESTAMP] OPERATIONS RESUMED" | tee -a "$LOG_FILE"
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Search Alex's Gmail inbox via IMAP for correspondence matching a query.
|
|
3
|
+
|
|
4
|
+
Usage: python3 search-secondary-inbox.py "<search_query>" [--max N] [--full]
|
|
5
|
+
--max N : Maximum messages to return (default: 20)
|
|
6
|
+
--full : Include full message bodies (default: headers + snippet)
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
python3 search-secondary-inbox.py "from:difc.ae"
|
|
10
|
+
python3 search-secondary-inbox.py "difc fitout" --max 50 --full
|
|
11
|
+
"""
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
import imaplib
|
|
15
|
+
import email
|
|
16
|
+
from email.header import decode_header
|
|
17
|
+
import json
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
|
|
20
|
+
PRINCIPAL_EMAIL = "alex@example.com"
|
|
21
|
+
MEHRAN_PASSWORD = os.environ.get("MEHRAN_GMAIL_APP_PASSWORD", "")
|
|
22
|
+
|
|
23
|
+
# Load from .env if not in environment
|
|
24
|
+
if not MEHRAN_PASSWORD:
|
|
25
|
+
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env")
|
|
26
|
+
if os.path.exists(env_path):
|
|
27
|
+
with open(env_path) as f:
|
|
28
|
+
for line in f:
|
|
29
|
+
if line.startswith("MEHRAN_GMAIL_APP_PASSWORD="):
|
|
30
|
+
MEHRAN_PASSWORD = line.strip().split("=", 1)[1].strip()
|
|
31
|
+
break
|
|
32
|
+
|
|
33
|
+
if not MEHRAN_PASSWORD:
|
|
34
|
+
print("ERROR: MEHRAN_GMAIL_APP_PASSWORD not set", file=sys.stderr)
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def decode_header_value(value):
|
|
39
|
+
"""Decode email header value handling various encodings."""
|
|
40
|
+
if not value:
|
|
41
|
+
return ""
|
|
42
|
+
decoded_parts = decode_header(value)
|
|
43
|
+
result = []
|
|
44
|
+
for part, encoding in decoded_parts:
|
|
45
|
+
if isinstance(part, bytes):
|
|
46
|
+
result.append(part.decode(encoding or "utf-8", errors="replace"))
|
|
47
|
+
else:
|
|
48
|
+
result.append(str(part))
|
|
49
|
+
return " ".join(result)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_body(msg):
|
|
53
|
+
"""Extract text body from email message."""
|
|
54
|
+
body = ""
|
|
55
|
+
if msg.is_multipart():
|
|
56
|
+
for part in msg.walk():
|
|
57
|
+
content_type = part.get_content_type()
|
|
58
|
+
content_disposition = str(part.get("Content-Disposition", ""))
|
|
59
|
+
if content_type == "text/plain" and "attachment" not in content_disposition:
|
|
60
|
+
try:
|
|
61
|
+
payload = part.get_payload(decode=True)
|
|
62
|
+
if payload:
|
|
63
|
+
charset = part.get_content_charset() or "utf-8"
|
|
64
|
+
body += payload.decode(charset, errors="replace")
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
elif content_type == "text/html" and not body and "attachment" not in content_disposition:
|
|
68
|
+
try:
|
|
69
|
+
payload = part.get_payload(decode=True)
|
|
70
|
+
if payload:
|
|
71
|
+
charset = part.get_content_charset() or "utf-8"
|
|
72
|
+
body += payload.decode(charset, errors="replace")
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
else:
|
|
76
|
+
try:
|
|
77
|
+
payload = msg.get_payload(decode=True)
|
|
78
|
+
if payload:
|
|
79
|
+
charset = msg.get_content_charset() or "utf-8"
|
|
80
|
+
body = payload.decode(charset, errors="replace")
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
return body
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_attachments(msg):
|
|
87
|
+
"""List attachment filenames and sizes."""
|
|
88
|
+
attachments = []
|
|
89
|
+
if msg.is_multipart():
|
|
90
|
+
for part in msg.walk():
|
|
91
|
+
content_disposition = str(part.get("Content-Disposition", ""))
|
|
92
|
+
if "attachment" in content_disposition or (
|
|
93
|
+
part.get_filename() and part.get_content_type() != "text/plain"
|
|
94
|
+
):
|
|
95
|
+
filename = decode_header_value(part.get_filename() or "unnamed")
|
|
96
|
+
size = len(part.get_payload(decode=True) or b"")
|
|
97
|
+
attachments.append({"filename": filename, "size": size, "content_type": part.get_content_type()})
|
|
98
|
+
return attachments
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def search_inbox(query, max_results=20, full_body=False):
|
|
102
|
+
"""Search Alex's Gmail via IMAP."""
|
|
103
|
+
mail = imaplib.IMAP4_SSL("imap.gmail.com")
|
|
104
|
+
mail.login(PRINCIPAL_EMAIL, MEHRAN_PASSWORD)
|
|
105
|
+
|
|
106
|
+
# Use All Mail to search everything
|
|
107
|
+
mail.select('"[Gmail]/All Mail"')
|
|
108
|
+
|
|
109
|
+
# Build IMAP search criteria
|
|
110
|
+
# Gmail supports X-GM-RAW for full Gmail search syntax
|
|
111
|
+
status, message_ids = mail.search(None, f'X-GM-RAW "{query}"')
|
|
112
|
+
|
|
113
|
+
if status != "OK" or not message_ids[0]:
|
|
114
|
+
print(json.dumps({"query": query, "results": [], "total": 0}, indent=2))
|
|
115
|
+
mail.logout()
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
ids = message_ids[0].split()
|
|
119
|
+
total = len(ids)
|
|
120
|
+
# Get most recent N messages (IMAP returns oldest first)
|
|
121
|
+
ids = ids[-max_results:] if len(ids) > max_results else ids
|
|
122
|
+
ids.reverse() # newest first
|
|
123
|
+
|
|
124
|
+
results = []
|
|
125
|
+
for msg_id in ids:
|
|
126
|
+
try:
|
|
127
|
+
if full_body:
|
|
128
|
+
status, data = mail.fetch(msg_id, "(RFC822)")
|
|
129
|
+
else:
|
|
130
|
+
status, data = mail.fetch(msg_id, "(RFC822)")
|
|
131
|
+
|
|
132
|
+
if status != "OK" or not data[0]:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
raw_email = data[0][1]
|
|
136
|
+
msg = email.message_from_bytes(raw_email)
|
|
137
|
+
|
|
138
|
+
result = {
|
|
139
|
+
"from": decode_header_value(msg.get("From", "")),
|
|
140
|
+
"to": decode_header_value(msg.get("To", "")),
|
|
141
|
+
"cc": decode_header_value(msg.get("Cc", "")),
|
|
142
|
+
"subject": decode_header_value(msg.get("Subject", "")),
|
|
143
|
+
"date": msg.get("Date", ""),
|
|
144
|
+
"message_id": msg.get("Message-ID", ""),
|
|
145
|
+
"in_reply_to": msg.get("In-Reply-To", ""),
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
body = get_body(msg)
|
|
149
|
+
if full_body:
|
|
150
|
+
result["body"] = body
|
|
151
|
+
else:
|
|
152
|
+
result["snippet"] = body[:500] + ("..." if len(body) > 500 else "")
|
|
153
|
+
|
|
154
|
+
attachments = get_attachments(msg)
|
|
155
|
+
if attachments:
|
|
156
|
+
result["attachments"] = attachments
|
|
157
|
+
|
|
158
|
+
results.append(result)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
results.append({"error": str(e), "msg_id": msg_id.decode()})
|
|
161
|
+
|
|
162
|
+
mail.logout()
|
|
163
|
+
|
|
164
|
+
output = {
|
|
165
|
+
"query": query,
|
|
166
|
+
"total_matches": total,
|
|
167
|
+
"returned": len(results),
|
|
168
|
+
"results": results
|
|
169
|
+
}
|
|
170
|
+
print(json.dumps(output, indent=2, ensure_ascii=False, default=str))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
import argparse
|
|
175
|
+
parser = argparse.ArgumentParser(description="Search Alex's Gmail inbox via IMAP")
|
|
176
|
+
parser.add_argument("query", help="Gmail search query")
|
|
177
|
+
parser.add_argument("--max", type=int, default=20, help="Maximum results (default: 20)")
|
|
178
|
+
parser.add_argument("--full", action="store_true", help="Include full message bodies")
|
|
179
|
+
args = parser.parse_args()
|
|
180
|
+
|
|
181
|
+
search_inbox(args.query, max_results=args.max, full_body=args.full)
|