@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,562 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Disclosure Boundary Generator — Layer 1 module.
|
|
3
|
+
|
|
4
|
+
Generates dynamic disclosure boundary guidance for a given recipient by:
|
|
5
|
+
1. Loading the recipient's user profile from memory/profiles/users/{slug}.yaml
|
|
6
|
+
2. Loading recent provenance records from memory/provenance/index.jsonl
|
|
7
|
+
3. Cross-referencing provenance against the recipient's information_boundaries
|
|
8
|
+
4. Returning a structured dict with clearance summary, active conflicts,
|
|
9
|
+
recent sensitive context, and excluded domains active
|
|
10
|
+
|
|
11
|
+
Usage (CLI):
|
|
12
|
+
python3 scripts/disclosure_boundaries.py --recipient casey-morgan
|
|
13
|
+
python3 scripts/disclosure_boundaries.py --recipient alex-chen --hours 24
|
|
14
|
+
|
|
15
|
+
Exit codes:
|
|
16
|
+
0 = always (profile not found prints to stderr and exits 0)
|
|
17
|
+
|
|
18
|
+
Output: JSON to stdout.
|
|
19
|
+
|
|
20
|
+
Created: 2026-04-05 — Task 5, Layer 1 information barrier system
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import datetime, timezone, timedelta
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
# ─── Paths ───────────────────────────────────────────────────────────────────
|
|
32
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
33
|
+
REPO_ROOT = SCRIPT_DIR.parent
|
|
34
|
+
USER_PROFILES_DIR = REPO_ROOT / "memory" / "profiles" / "users"
|
|
35
|
+
PROVENANCE_INDEX = REPO_ROOT / "memory" / "provenance" / "index.jsonl"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ─── YAML Parser ─────────────────────────────────────────────────────────────
|
|
39
|
+
# Custom minimal YAML parser — no PyYAML dependency (consistent with repo pattern).
|
|
40
|
+
# Handles: top-level scalars, nested mappings, inline lists [a, b, c],
|
|
41
|
+
# multi-line lists (- item), lists of mappings (- key: val / key2: val2),
|
|
42
|
+
# folded block strings (>), and quoted strings.
|
|
43
|
+
|
|
44
|
+
def _parse_yaml(text: str) -> dict:
|
|
45
|
+
"""Parse a YAML text string into a Python dict.
|
|
46
|
+
|
|
47
|
+
Tries PyYAML first (if available), falls back to the custom parser.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
import yaml as _yaml
|
|
51
|
+
return _yaml.safe_load(text) or {}
|
|
52
|
+
except ImportError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
lines = text.split('\n')
|
|
56
|
+
result, _ = _parse_block(lines, 0, 0)
|
|
57
|
+
return result if isinstance(result, dict) else {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _next_content(lines, start):
|
|
61
|
+
"""Return (indent, index) for the next non-blank, non-comment line."""
|
|
62
|
+
i = start
|
|
63
|
+
while i < len(lines):
|
|
64
|
+
s = lines[i].lstrip()
|
|
65
|
+
if s and not s.startswith('#'):
|
|
66
|
+
return len(lines[i]) - len(lines[i].lstrip()), i
|
|
67
|
+
i += 1
|
|
68
|
+
return None, None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _parse_block(lines, start, base_indent):
|
|
72
|
+
"""Parse a YAML mapping block. Returns (dict, next_index)."""
|
|
73
|
+
result = {}
|
|
74
|
+
i = start
|
|
75
|
+
while i < len(lines):
|
|
76
|
+
raw = lines[i]
|
|
77
|
+
stripped = raw.lstrip()
|
|
78
|
+
|
|
79
|
+
if not stripped or stripped.startswith('#'):
|
|
80
|
+
i += 1
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
indent = len(raw) - len(raw.lstrip())
|
|
84
|
+
|
|
85
|
+
if indent < base_indent and i > start:
|
|
86
|
+
break
|
|
87
|
+
if indent > base_indent and i > start:
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
# List item encountered at mapping level — caller handles as value
|
|
91
|
+
if stripped.startswith('- '):
|
|
92
|
+
if not result:
|
|
93
|
+
lst, i = _parse_list(lines, i, indent)
|
|
94
|
+
return lst, i
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
if ':' in stripped and not stripped.startswith('-'):
|
|
98
|
+
colon_pos = stripped.index(':')
|
|
99
|
+
key = stripped[:colon_pos].strip()
|
|
100
|
+
rest = stripped[colon_pos + 1:].strip()
|
|
101
|
+
|
|
102
|
+
# Inline list: key: [a, b, c]
|
|
103
|
+
if rest.startswith('['):
|
|
104
|
+
result[key] = _parse_inline_list(rest)
|
|
105
|
+
i += 1
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
# Folded/literal block scalar (> or |)
|
|
109
|
+
if rest in ('>', '|', '>-', '|-', '>+', '|+'):
|
|
110
|
+
value, i = _parse_block_scalar(lines, i + 1, indent)
|
|
111
|
+
result[key] = value
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
if rest:
|
|
115
|
+
result[key] = _yaml_scalar(rest)
|
|
116
|
+
i += 1
|
|
117
|
+
else:
|
|
118
|
+
# Block value — peek to find child indent
|
|
119
|
+
child_indent, child_idx = _next_content(lines, i + 1)
|
|
120
|
+
if child_indent is not None and child_indent > indent:
|
|
121
|
+
child_stripped = lines[child_idx].lstrip()
|
|
122
|
+
if child_stripped.startswith('- '):
|
|
123
|
+
value, i = _parse_list(lines, child_idx, child_indent)
|
|
124
|
+
elif child_stripped.startswith('['):
|
|
125
|
+
# inline list as sole content on next line
|
|
126
|
+
value = _parse_inline_list(child_stripped)
|
|
127
|
+
i = child_idx + 1
|
|
128
|
+
else:
|
|
129
|
+
value, i = _parse_block(lines, child_idx, child_indent)
|
|
130
|
+
result[key] = value
|
|
131
|
+
else:
|
|
132
|
+
result[key] = None
|
|
133
|
+
i += 1
|
|
134
|
+
else:
|
|
135
|
+
i += 1
|
|
136
|
+
|
|
137
|
+
return result, i
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _parse_list(lines, start, base_indent):
|
|
141
|
+
"""Parse a YAML sequence block. Returns (list, next_index)."""
|
|
142
|
+
result = []
|
|
143
|
+
i = start
|
|
144
|
+
while i < len(lines):
|
|
145
|
+
raw = lines[i]
|
|
146
|
+
stripped = raw.lstrip()
|
|
147
|
+
|
|
148
|
+
if not stripped or stripped.startswith('#'):
|
|
149
|
+
i += 1
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
indent = len(raw) - len(raw.lstrip())
|
|
153
|
+
if indent < base_indent:
|
|
154
|
+
break
|
|
155
|
+
if indent > base_indent:
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
if not stripped.startswith('- '):
|
|
159
|
+
# Not a list item — we're done
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
item_content = stripped[2:].strip()
|
|
163
|
+
|
|
164
|
+
# Inline list item: - [a, b, c]
|
|
165
|
+
if item_content.startswith('['):
|
|
166
|
+
result.append(_parse_inline_list(item_content))
|
|
167
|
+
i += 1
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Folded block under a list item
|
|
171
|
+
if item_content in ('>', '|', '>-', '|-', '>+', '|+'):
|
|
172
|
+
value, i = _parse_block_scalar(lines, i + 1, indent)
|
|
173
|
+
result.append(value)
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# Mapping-style list item: - key: value
|
|
177
|
+
if ':' in item_content and not item_content.startswith('"') and not item_content.startswith("'"):
|
|
178
|
+
colon_pos = item_content.index(':')
|
|
179
|
+
k = item_content[:colon_pos].strip()
|
|
180
|
+
v = item_content[colon_pos + 1:].strip()
|
|
181
|
+
item_dict = {k: _parse_inline_list(v) if v.startswith('[') else (_yaml_scalar(v) if v else None)}
|
|
182
|
+
|
|
183
|
+
# Continuation keys at deeper indent
|
|
184
|
+
child_indent, child_idx = _next_content(lines, i + 1)
|
|
185
|
+
if child_indent is not None and child_indent > indent:
|
|
186
|
+
child_stripped = lines[child_idx].lstrip()
|
|
187
|
+
if child_stripped.startswith('- '):
|
|
188
|
+
# nested list under dict key — unusual but handle gracefully
|
|
189
|
+
i += 1
|
|
190
|
+
else:
|
|
191
|
+
extra, i = _parse_block(lines, child_idx, child_indent)
|
|
192
|
+
if isinstance(extra, dict):
|
|
193
|
+
item_dict.update(extra)
|
|
194
|
+
else:
|
|
195
|
+
i += 1
|
|
196
|
+
|
|
197
|
+
result.append(item_dict)
|
|
198
|
+
else:
|
|
199
|
+
result.append(_yaml_scalar(item_content))
|
|
200
|
+
i += 1
|
|
201
|
+
|
|
202
|
+
return result, i
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _parse_block_scalar(lines, start, owner_indent):
|
|
206
|
+
"""Parse a folded/literal block scalar (lines after > or |).
|
|
207
|
+
|
|
208
|
+
Returns (string, next_index).
|
|
209
|
+
"""
|
|
210
|
+
parts = []
|
|
211
|
+
i = start
|
|
212
|
+
while i < len(lines):
|
|
213
|
+
raw = lines[i]
|
|
214
|
+
stripped = raw.lstrip()
|
|
215
|
+
if not stripped:
|
|
216
|
+
i += 1
|
|
217
|
+
continue
|
|
218
|
+
indent = len(raw) - len(raw.lstrip())
|
|
219
|
+
if indent <= owner_indent:
|
|
220
|
+
break
|
|
221
|
+
parts.append(stripped)
|
|
222
|
+
i += 1
|
|
223
|
+
return ' '.join(parts), i
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _parse_inline_list(text: str):
|
|
227
|
+
"""Parse an inline YAML list like [a, b, c] or ["x", "y"].
|
|
228
|
+
|
|
229
|
+
Returns a Python list.
|
|
230
|
+
"""
|
|
231
|
+
text = text.strip()
|
|
232
|
+
if not (text.startswith('[') and text.endswith(']')):
|
|
233
|
+
return [_yaml_scalar(text)] if text else []
|
|
234
|
+
inner = text[1:-1].strip()
|
|
235
|
+
if not inner:
|
|
236
|
+
return []
|
|
237
|
+
# Split on commas not inside quotes
|
|
238
|
+
items = []
|
|
239
|
+
current = ''
|
|
240
|
+
in_quote = None
|
|
241
|
+
for ch in inner:
|
|
242
|
+
if ch in ('"', "'") and in_quote is None:
|
|
243
|
+
in_quote = ch
|
|
244
|
+
elif ch == in_quote:
|
|
245
|
+
in_quote = None
|
|
246
|
+
elif ch == ',' and in_quote is None:
|
|
247
|
+
items.append(_yaml_scalar(current.strip()))
|
|
248
|
+
current = ''
|
|
249
|
+
continue
|
|
250
|
+
current += ch
|
|
251
|
+
if current.strip():
|
|
252
|
+
items.append(_yaml_scalar(current.strip()))
|
|
253
|
+
return items
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _yaml_scalar(text: str):
|
|
257
|
+
"""Convert a YAML scalar string to an appropriate Python type."""
|
|
258
|
+
if text is None:
|
|
259
|
+
return None
|
|
260
|
+
text = text.strip()
|
|
261
|
+
if not text:
|
|
262
|
+
return None
|
|
263
|
+
# Quoted strings
|
|
264
|
+
if (text.startswith('"') and text.endswith('"')) or \
|
|
265
|
+
(text.startswith("'") and text.endswith("'")):
|
|
266
|
+
return text[1:-1]
|
|
267
|
+
# Strip inline comments
|
|
268
|
+
for sep in (' #', '\t#'):
|
|
269
|
+
if sep in text:
|
|
270
|
+
text = text[:text.index(sep)].strip()
|
|
271
|
+
# Boolean
|
|
272
|
+
if text.lower() in ('true', 'yes'):
|
|
273
|
+
return True
|
|
274
|
+
if text.lower() in ('false', 'no'):
|
|
275
|
+
return False
|
|
276
|
+
# Null
|
|
277
|
+
if text.lower() in ('null', '~'):
|
|
278
|
+
return None
|
|
279
|
+
# Integer
|
|
280
|
+
try:
|
|
281
|
+
return int(text)
|
|
282
|
+
except ValueError:
|
|
283
|
+
pass
|
|
284
|
+
# Float
|
|
285
|
+
try:
|
|
286
|
+
return float(text)
|
|
287
|
+
except ValueError:
|
|
288
|
+
pass
|
|
289
|
+
return text
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ─── Profile Loading ──────────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
def load_profile(slug: str) -> dict | None:
|
|
295
|
+
"""Load and parse a user profile YAML. Returns None if not found."""
|
|
296
|
+
path = USER_PROFILES_DIR / f"{slug}.yaml"
|
|
297
|
+
if not path.exists():
|
|
298
|
+
return None
|
|
299
|
+
text = path.read_text(encoding='utf-8')
|
|
300
|
+
return _parse_yaml(text)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ─── Provenance Loading ───────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
def load_recent_provenance(hours: int = 48) -> list[dict]:
|
|
306
|
+
"""Load provenance records within the last *hours* hours.
|
|
307
|
+
|
|
308
|
+
Returns list of dicts. Empty list if file absent or empty.
|
|
309
|
+
"""
|
|
310
|
+
if not PROVENANCE_INDEX.exists():
|
|
311
|
+
return []
|
|
312
|
+
|
|
313
|
+
cutoff = datetime.now(tz=timezone.utc) - timedelta(hours=hours)
|
|
314
|
+
records = []
|
|
315
|
+
|
|
316
|
+
with PROVENANCE_INDEX.open(encoding='utf-8') as f:
|
|
317
|
+
for line in f:
|
|
318
|
+
line = line.strip()
|
|
319
|
+
if not line:
|
|
320
|
+
continue
|
|
321
|
+
try:
|
|
322
|
+
rec = json.loads(line)
|
|
323
|
+
except json.JSONDecodeError:
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
ts_raw = rec.get('timestamp', '')
|
|
327
|
+
try:
|
|
328
|
+
# Handle ISO 8601 with or without Z suffix
|
|
329
|
+
ts_str = ts_raw.replace('Z', '+00:00')
|
|
330
|
+
ts = datetime.fromisoformat(ts_str)
|
|
331
|
+
if ts.tzinfo is None:
|
|
332
|
+
ts = ts.replace(tzinfo=timezone.utc)
|
|
333
|
+
except (ValueError, AttributeError):
|
|
334
|
+
# Unparseable timestamp — include conservatively
|
|
335
|
+
records.append(rec)
|
|
336
|
+
continue
|
|
337
|
+
|
|
338
|
+
if ts >= cutoff:
|
|
339
|
+
records.append(rec)
|
|
340
|
+
|
|
341
|
+
return records
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# ─── Cross-Reference Logic ────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
def _cross_reference(recipient_slug: str, boundaries: dict, records: list[dict]) -> list[dict]:
|
|
347
|
+
"""Flag provenance records that may expose sensitive information to the recipient.
|
|
348
|
+
|
|
349
|
+
A record is flagged when BOTH:
|
|
350
|
+
(a) recipient is NOT in the record's participants list
|
|
351
|
+
(b) the record's matter matches one of the recipient's conflicts
|
|
352
|
+
OR the record's context_domain is in the recipient's excluded_domains
|
|
353
|
+
|
|
354
|
+
Returns list of warning dicts with keys: source, warning.
|
|
355
|
+
"""
|
|
356
|
+
conflicts = boundaries.get('conflicts') or []
|
|
357
|
+
excluded_domains = boundaries.get('excluded_domains') or []
|
|
358
|
+
|
|
359
|
+
# Build sets for fast lookup
|
|
360
|
+
conflict_matters = set()
|
|
361
|
+
for c in conflicts:
|
|
362
|
+
if isinstance(c, dict):
|
|
363
|
+
m = c.get('matter')
|
|
364
|
+
if m:
|
|
365
|
+
conflict_matters.add(str(m).lower())
|
|
366
|
+
elif isinstance(c, str):
|
|
367
|
+
conflict_matters.add(c.lower())
|
|
368
|
+
|
|
369
|
+
excluded_set = {str(d).lower() for d in excluded_domains if d}
|
|
370
|
+
|
|
371
|
+
flagged = []
|
|
372
|
+
for rec in records:
|
|
373
|
+
participants = [str(p).lower() for p in (rec.get('participants') or [])]
|
|
374
|
+
if recipient_slug.lower() in participants:
|
|
375
|
+
# Recipient is already a participant — no disclosure risk from this record
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
matter = str(rec.get('matter') or '').lower()
|
|
379
|
+
domain = str(rec.get('context_domain') or '').lower()
|
|
380
|
+
source_id = rec.get('source_id', 'unknown')
|
|
381
|
+
sensitivity = rec.get('sensitivity_signals') or []
|
|
382
|
+
|
|
383
|
+
reasons = []
|
|
384
|
+
if matter and matter in conflict_matters:
|
|
385
|
+
# Find the nature note for this conflict
|
|
386
|
+
nature = ''
|
|
387
|
+
for c in conflicts:
|
|
388
|
+
if isinstance(c, dict) and str(c.get('matter', '')).lower() == matter:
|
|
389
|
+
nature = c.get('nature', '')
|
|
390
|
+
break
|
|
391
|
+
reasons.append(
|
|
392
|
+
f"matter '{rec.get('matter')}' is a declared conflict"
|
|
393
|
+
+ (f" ({nature})" if nature else "")
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
if domain and domain in excluded_set:
|
|
397
|
+
reasons.append(f"context domain '{rec.get('context_domain')}' is in excluded domains")
|
|
398
|
+
|
|
399
|
+
if not reasons:
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
# Build participant name list for the source description
|
|
403
|
+
participant_names = rec.get('participants') or []
|
|
404
|
+
source_desc = source_id
|
|
405
|
+
if participant_names:
|
|
406
|
+
source_desc += f" (participants: {', '.join(str(p) for p in participant_names)})"
|
|
407
|
+
|
|
408
|
+
warning_parts = [
|
|
409
|
+
f"Recipient {recipient_slug!r} is NOT a participant.",
|
|
410
|
+
*[f"Warning: {r}." for r in reasons],
|
|
411
|
+
]
|
|
412
|
+
if sensitivity:
|
|
413
|
+
warning_parts.append(f"Sensitivity signals: {', '.join(str(s) for s in sensitivity)}.")
|
|
414
|
+
|
|
415
|
+
flagged.append({
|
|
416
|
+
"source": source_desc,
|
|
417
|
+
"warning": " ".join(warning_parts),
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
return flagged
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# ─── Clearance Summary Builder ────────────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
def _build_clearance_summary(profile: dict, boundaries: dict) -> str:
|
|
426
|
+
"""Build a natural-language clearance summary from profile fields."""
|
|
427
|
+
name = profile.get('name') or profile.get('preferred_name') or 'Unknown'
|
|
428
|
+
role = profile.get('role') or ''
|
|
429
|
+
org = profile.get('organisation') or ''
|
|
430
|
+
relationship = profile.get('relationship_to_northwind') or ''
|
|
431
|
+
domains_of_interest = boundaries.get('domains_of_interest') or []
|
|
432
|
+
excluded_domains = boundaries.get('excluded_domains') or []
|
|
433
|
+
conflicts = boundaries.get('conflicts') or []
|
|
434
|
+
party_to_matters = boundaries.get('party_to_matters') or []
|
|
435
|
+
|
|
436
|
+
# Unrestricted access shortcut
|
|
437
|
+
if domains_of_interest == ['*']:
|
|
438
|
+
return (
|
|
439
|
+
f"{name} has unrestricted access — all information domains are permitted. "
|
|
440
|
+
f"Role: {role}. Organisation: {org}."
|
|
441
|
+
).strip()
|
|
442
|
+
|
|
443
|
+
parts = [f"Clearance summary for {name}."]
|
|
444
|
+
if role:
|
|
445
|
+
parts.append(f"Role: {role}.")
|
|
446
|
+
if org:
|
|
447
|
+
parts.append(f"Organisation: {org}.")
|
|
448
|
+
if relationship:
|
|
449
|
+
parts.append(f"Relationship to Northwind: {relationship}.")
|
|
450
|
+
|
|
451
|
+
if domains_of_interest:
|
|
452
|
+
parts.append(f"Permitted domains: {', '.join(str(d) for d in domains_of_interest)}.")
|
|
453
|
+
else:
|
|
454
|
+
parts.append("No permitted domains declared (default deny).")
|
|
455
|
+
|
|
456
|
+
if excluded_domains:
|
|
457
|
+
parts.append(f"Excluded domains ({len(excluded_domains)}): {', '.join(str(d) for d in excluded_domains)}.")
|
|
458
|
+
|
|
459
|
+
if party_to_matters:
|
|
460
|
+
matters_str = ', '.join(str(m) for m in party_to_matters)
|
|
461
|
+
parts.append(f"Party to matters: {matters_str}.")
|
|
462
|
+
|
|
463
|
+
if conflicts:
|
|
464
|
+
conflict_strs = []
|
|
465
|
+
for c in conflicts:
|
|
466
|
+
if isinstance(c, dict):
|
|
467
|
+
m = c.get('matter', '')
|
|
468
|
+
n = c.get('nature', '')
|
|
469
|
+
conflict_strs.append(f"{m} ({n})" if n else str(m))
|
|
470
|
+
else:
|
|
471
|
+
conflict_strs.append(str(c))
|
|
472
|
+
parts.append(f"Active conflicts ({len(conflicts)}): {'; '.join(conflict_strs)}.")
|
|
473
|
+
|
|
474
|
+
return ' '.join(parts)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# ─── Main Entry Point ─────────────────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
def generate_disclosure_boundaries(
|
|
480
|
+
recipient_slug: str,
|
|
481
|
+
recent_context_hours: int = 48,
|
|
482
|
+
) -> dict | None:
|
|
483
|
+
"""Generate disclosure boundary guidance for *recipient_slug*.
|
|
484
|
+
|
|
485
|
+
Returns a dict with keys:
|
|
486
|
+
- recipient_clearance_summary (str)
|
|
487
|
+
- active_conflicts (list)
|
|
488
|
+
- recent_sensitive_context (list of {source, warning} dicts)
|
|
489
|
+
- excluded_domains_active (list)
|
|
490
|
+
|
|
491
|
+
Returns None if no profile is found for the slug.
|
|
492
|
+
"""
|
|
493
|
+
profile = load_profile(recipient_slug)
|
|
494
|
+
if profile is None:
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
boundaries = profile.get('information_boundaries') or {}
|
|
498
|
+
domains_of_interest = boundaries.get('domains_of_interest') or []
|
|
499
|
+
|
|
500
|
+
# Unrestricted recipient — skip provenance cross-reference
|
|
501
|
+
if domains_of_interest == ['*']:
|
|
502
|
+
return {
|
|
503
|
+
"recipient_clearance_summary": _build_clearance_summary(profile, boundaries),
|
|
504
|
+
"active_conflicts": [],
|
|
505
|
+
"recent_sensitive_context": [],
|
|
506
|
+
"excluded_domains_active": [],
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
conflicts = boundaries.get('conflicts') or []
|
|
510
|
+
excluded_domains = boundaries.get('excluded_domains') or []
|
|
511
|
+
|
|
512
|
+
# Load and cross-reference recent provenance
|
|
513
|
+
recent_records = load_recent_provenance(hours=recent_context_hours)
|
|
514
|
+
sensitive_context = _cross_reference(recipient_slug, boundaries, recent_records)
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
"recipient_clearance_summary": _build_clearance_summary(profile, boundaries),
|
|
518
|
+
"active_conflicts": [
|
|
519
|
+
{
|
|
520
|
+
"matter": c.get('matter') if isinstance(c, dict) else str(c),
|
|
521
|
+
"nature": c.get('nature', '') if isinstance(c, dict) else '',
|
|
522
|
+
}
|
|
523
|
+
for c in conflicts
|
|
524
|
+
],
|
|
525
|
+
"recent_sensitive_context": sensitive_context,
|
|
526
|
+
"excluded_domains_active": list(excluded_domains),
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
# ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
531
|
+
|
|
532
|
+
def main():
|
|
533
|
+
parser = argparse.ArgumentParser(
|
|
534
|
+
description="Generate disclosure boundary guidance for a recipient."
|
|
535
|
+
)
|
|
536
|
+
parser.add_argument(
|
|
537
|
+
'--recipient',
|
|
538
|
+
required=True,
|
|
539
|
+
help="Recipient slug (e.g. casey-morgan)",
|
|
540
|
+
)
|
|
541
|
+
parser.add_argument(
|
|
542
|
+
'--hours',
|
|
543
|
+
type=int,
|
|
544
|
+
default=48,
|
|
545
|
+
help="Lookback window in hours for provenance records (default: 48)",
|
|
546
|
+
)
|
|
547
|
+
args = parser.parse_args()
|
|
548
|
+
|
|
549
|
+
result = generate_disclosure_boundaries(args.recipient, recent_context_hours=args.hours)
|
|
550
|
+
if result is None:
|
|
551
|
+
print(
|
|
552
|
+
f"No profile found for recipient: {args.recipient!r}",
|
|
553
|
+
file=sys.stderr,
|
|
554
|
+
)
|
|
555
|
+
sys.exit(0)
|
|
556
|
+
|
|
557
|
+
print(json.dumps(result, indent=2))
|
|
558
|
+
sys.exit(0)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
if __name__ == '__main__':
|
|
562
|
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<div dir="ltr">
|
|
2
|
+
<h2 style="color:rgb(0,0,0);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px;letter-spacing:0.2pt;text-transform:capitalize">
|
|
3
|
+
<font size="2"><br>Alex Chen<br></font>
|
|
4
|
+
<span style="font-weight:normal"><font face="arial, sans-serif" size="2">Chief Executive Officer</font></span>
|
|
5
|
+
</h2>
|
|
6
|
+
<h2 style="color:rgb(0,0,0);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px;letter-spacing:0.2pt;text-transform:capitalize">
|
|
7
|
+
<div style="color:rgb(34,34,34);font-size:small;font-weight:400;letter-spacing:normal;text-transform:none"><font size="2"><br></font></div>
|
|
8
|
+
<div style="color:rgb(34,34,34);font-size:small;font-weight:400;letter-spacing:normal;text-transform:none">
|
|
9
|
+
<img width="141" height="42" src="https://ci3.googleusercontent.com/mail-sig/AIorK4xNDnPN6a0MjSHV5urb9g11u2CzHIzwdJbwN21quGjzQFdbOdegCq1Wp6lq3NCqsARbi-ooqPfE9E1D" style="margin-right:0px"><br>
|
|
10
|
+
</div>
|
|
11
|
+
</h2>
|
|
12
|
+
<h2 style="color:rgb(34,34,34);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px">
|
|
13
|
+
<font face="arial, sans-serif" size="1">
|
|
14
|
+
<span style="font-weight:normal">
|
|
15
|
+
<span style="color:rgb(0,0,0);letter-spacing:0.2pt;text-transform:capitalize">
|
|
16
|
+
<br><a href="tel:+971585291799" style="color:rgb(85,85,85);text-transform:none;font-style:inherit;vertical-align:baseline;outline:0px;padding:0px;margin:0px;border:0px" target="_blank">+971 58 529 1799</a>
|
|
17
|
+
</span>
|
|
18
|
+
<span style="letter-spacing:0.2pt;text-transform:capitalize"><font color="#999999">(UAE)</font></span>
|
|
19
|
+
<span style="color:rgb(0,0,0);letter-spacing:0.2pt;text-transform:capitalize"> <br></span>
|
|
20
|
+
</span>
|
|
21
|
+
</font>
|
|
22
|
+
</h2>
|
|
23
|
+
<h2 style="vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px">
|
|
24
|
+
<font color="#555555" face="arial, sans-serif" size="1"><span style="font-weight:400;letter-spacing:0.266667px"><u>+1 646 696 5474</u></span></font>
|
|
25
|
+
<font face="arial, sans-serif" size="1">
|
|
26
|
+
<span style="color:rgb(0,0,0);font-weight:normal;letter-spacing:0.2pt;text-transform:capitalize"> </span>
|
|
27
|
+
<span style="color:rgb(34,34,34);font-weight:normal;letter-spacing:0.2pt;text-transform:capitalize"><font color="#999999">(USA)</font></span>
|
|
28
|
+
<br>
|
|
29
|
+
<font color="#000000"><span style="font-weight:400;letter-spacing:0.266667px;text-transform:capitalize"><br>Level 1, Innovation One, Dubai International Financial Centre, Dubai, UAE</span></font>
|
|
30
|
+
</font>
|
|
31
|
+
</h2>
|
|
32
|
+
<h2 style="color:rgb(0,0,0);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px;letter-spacing:0.2pt;text-transform:capitalize">
|
|
33
|
+
<div style="color:rgb(34,34,34);font-size:small;font-weight:400;letter-spacing:normal;text-transform:none;line-height:20px"></div>
|
|
34
|
+
<div style="color:rgb(34,34,34);font-weight:400;letter-spacing:normal;text-transform:none;font-size:12.8px">
|
|
35
|
+
<div dir="ltr">
|
|
36
|
+
<div dir="ltr">
|
|
37
|
+
<div dir="ltr">
|
|
38
|
+
<div style="font-size:small">
|
|
39
|
+
<font face="arial, sans-serif"><span style="color:rgb(153,153,153);font-size:x-small">_________________________________________</span><br></font>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div dir="ltr">
|
|
43
|
+
<div style="font-size:small"><font size="1" color="#999999" face="arial, sans-serif"><br></font></div>
|
|
44
|
+
<div style="font-size:small">
|
|
45
|
+
<font size="1" color="#999999"><font face="arial, sans-serif">This email and any files transmitted with it are confidential and intended solely for the use of the individual or entity to whom they are addressed. If you have received this email in error please notify the sender. This message contains confidential information and is intended only for the individual named. If you are not the named addressee you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. If you are not the intended recipient you are notified that disclosing, copying, distributing, or taking any action in reliance on the contents of this information is strictly prohibited.</font></font>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</h2>
|
|
52
|
+
</div>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Email signature template — Maestro framework
|
|
3
|
+
|
|
4
|
+
This file is rewritten by /init-maestro Sub-agent 4 with the new agent's
|
|
5
|
+
identity. The placeholders below are replaced verbatim:
|
|
6
|
+
|
|
7
|
+
{{AGENT_NAME}} e.g. "Lucas Ferreira"
|
|
8
|
+
{{AGENT_TITLE}} e.g. "VP, Regulatory & Licensing"
|
|
9
|
+
{{AGENT_EMAIL}} e.g. "lucas@example.com"
|
|
10
|
+
{{AGENT_PHONE}} e.g. "+61 478 964 324" (pretty form)
|
|
11
|
+
{{COMPANY_ADDRESS}} e.g. "Level 1, Innovation One, DIFC, Dubai, UAE"
|
|
12
|
+
|
|
13
|
+
If you see this file unchanged in a deployed agent's repo, something went
|
|
14
|
+
wrong with init-maestro Sub-agent 4 — re-run the wizard or rewrite by hand.
|
|
15
|
+
-->
|
|
16
|
+
<div
|
|
17
|
+
style="
|
|
18
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
19
|
+
font-size: 13px;
|
|
20
|
+
color: #333;
|
|
21
|
+
"
|
|
22
|
+
>
|
|
23
|
+
<p style="margin: 0; font-weight: bold; font-size: 14px">{{AGENT_NAME}}</p>
|
|
24
|
+
<p style="margin: 0; color: #666">{{AGENT_TITLE}}</p>
|
|
25
|
+
<br />
|
|
26
|
+
<a href="https://northwind.ai"
|
|
27
|
+
><img
|
|
28
|
+
src="https://northwind.ai/logo.png"
|
|
29
|
+
alt="Northwind"
|
|
30
|
+
width="120"
|
|
31
|
+
style="display: block; margin: 8px 0"
|
|
32
|
+
/></a>
|
|
33
|
+
<br />
|
|
34
|
+
<p style="margin: 0; font-size: 12px">{{AGENT_EMAIL}}</p>
|
|
35
|
+
<p style="margin: 0; font-size: 12px">{{AGENT_PHONE}}</p>
|
|
36
|
+
<br />
|
|
37
|
+
<p style="margin: 0; font-size: 12px">
|
|
38
|
+
{{COMPANY_ADDRESS}}
|
|
39
|
+
</p>
|
|
40
|
+
<p
|
|
41
|
+
style="
|
|
42
|
+
margin: 16px 0 0 0;
|
|
43
|
+
border-top: 1px solid #ccc;
|
|
44
|
+
padding-top: 8px;
|
|
45
|
+
font-size: 10px;
|
|
46
|
+
color: #999;
|
|
47
|
+
"
|
|
48
|
+
>
|
|
49
|
+
This email and any files transmitted with it are confidential and intended
|
|
50
|
+
solely for the use of the individual or entity to whom they are addressed.
|
|
51
|
+
If you have received this email in error please notify the sender. This
|
|
52
|
+
message contains confidential information and is intended only for the
|
|
53
|
+
individual named. If you are not the named addressee you should not
|
|
54
|
+
disseminate, distribute or copy this email. Please notify the sender
|
|
55
|
+
immediately by email if you have received this email by mistake and delete
|
|
56
|
+
this email from your system. If you are not the intended recipient you are
|
|
57
|
+
notified that disclosing, copying, distributing, or taking any action in
|
|
58
|
+
reliance on the contents of this information is strictly prohibited.
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|