productfoo 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +338 -2
- package/bin/foo +3 -0
- package/bin/foo.mjs +42 -0
- package/commands/bug.md +31 -0
- package/commands/discovery.md +28 -0
- package/commands/ideate.md +27 -0
- package/commands/init.md +28 -0
- package/commands/pitch.md +27 -0
- package/commands/preview.md +25 -0
- package/commands/research.md +31 -0
- package/commands/roast.md +27 -0
- package/commands/spec.md +30 -0
- package/config/pricing.yaml +198 -0
- package/config/routing-policy.yaml +171 -0
- package/dist-public/src/activeModel.d.ts +83 -0
- package/dist-public/src/activeModel.js +331 -0
- package/dist-public/src/agentAdapters.d.ts +122 -0
- package/dist-public/src/agentAdapters.js +178 -0
- package/dist-public/src/agentState.d.ts +43 -0
- package/dist-public/src/agentState.js +177 -0
- package/dist-public/src/bugCapsule.d.ts +94 -0
- package/dist-public/src/bugCapsule.js +227 -0
- package/dist-public/src/bugCapture.d.ts +26 -0
- package/dist-public/src/bugCapture.js +251 -0
- package/dist-public/src/bugOwnerLog.d.ts +93 -0
- package/dist-public/src/bugOwnerLog.js +304 -0
- package/dist-public/src/bugReportStore.d.ts +48 -0
- package/dist-public/src/bugReportStore.js +860 -0
- package/dist-public/src/bugSpecContext.d.ts +7 -0
- package/dist-public/src/bugSpecContext.js +129 -0
- package/dist-public/src/channelFreshness.d.ts +25 -0
- package/dist-public/src/channelFreshness.js +194 -0
- package/dist-public/src/cli.d.ts +30 -0
- package/dist-public/src/cli.js +1667 -0
- package/dist-public/src/cliArgs.d.ts +7 -0
- package/dist-public/src/cliArgs.js +44 -0
- package/dist-public/src/codeCatalog.d.ts +41 -0
- package/dist-public/src/codeCatalog.js +197 -0
- package/dist-public/src/commandContract.d.ts +84 -0
- package/dist-public/src/commandContract.js +390 -0
- package/dist-public/src/commandUsage.d.ts +3 -0
- package/dist-public/src/commandUsage.js +30 -0
- package/dist-public/src/commands/agent.d.ts +60 -0
- package/dist-public/src/commands/agent.js +1353 -0
- package/dist-public/src/commands/asp.d.ts +8 -0
- package/dist-public/src/commands/asp.js +669 -0
- package/dist-public/src/commands/evalCmd.d.ts +4 -0
- package/dist-public/src/commands/evalCmd.js +405 -0
- package/dist-public/src/commands/login.d.ts +25 -0
- package/dist-public/src/commands/login.js +94 -0
- package/dist-public/src/commands/loop.d.ts +16 -0
- package/dist-public/src/commands/loop.js +619 -0
- package/dist-public/src/commands/reconcile.d.ts +41 -0
- package/dist-public/src/commands/reconcile.js +292 -0
- package/dist-public/src/commands/runtime.d.ts +1 -0
- package/dist-public/src/commands/runtime.js +36 -0
- package/dist-public/src/commands/scanRedact.d.ts +8 -0
- package/dist-public/src/commands/scanRedact.js +266 -0
- package/dist-public/src/commands/setup.d.ts +28 -0
- package/dist-public/src/commands/setup.js +398 -0
- package/dist-public/src/commands/shared.d.ts +17 -0
- package/dist-public/src/commands/shared.js +147 -0
- package/dist-public/src/commands/source.d.ts +1 -0
- package/dist-public/src/commands/source.js +134 -0
- package/dist-public/src/commands/specCmd.d.ts +24 -0
- package/dist-public/src/commands/specCmd.js +794 -0
- package/dist-public/src/commands/update.d.ts +61 -0
- package/dist-public/src/commands/update.js +244 -0
- package/dist-public/src/comprehensionSignal.d.ts +20 -0
- package/dist-public/src/comprehensionSignal.js +166 -0
- package/dist-public/src/conductor/diversity.d.ts +48 -0
- package/dist-public/src/conductor/diversity.js +423 -0
- package/dist-public/src/conductor/engine.d.ts +7 -0
- package/dist-public/src/conductor/engine.js +295 -0
- package/dist-public/src/conductor/families/ideate.d.ts +2 -0
- package/dist-public/src/conductor/families/ideate.js +255 -0
- package/dist-public/src/conductor/families/interview.d.ts +2 -0
- package/dist-public/src/conductor/families/interview.js +39 -0
- package/dist-public/src/conductor/families/specSwarm.d.ts +2 -0
- package/dist-public/src/conductor/families/specSwarm.js +146 -0
- package/dist-public/src/conductor/familyDelivery.d.ts +6 -0
- package/dist-public/src/conductor/familyDelivery.js +37 -0
- package/dist-public/src/conductor/familyPackArtifact.d.ts +4 -0
- package/dist-public/src/conductor/familyPackArtifact.js +260 -0
- package/dist-public/src/conductor/ideateDecision.d.ts +6 -0
- package/dist-public/src/conductor/ideateDecision.js +237 -0
- package/dist-public/src/conductor/pack.d.ts +26 -0
- package/dist-public/src/conductor/pack.js +75 -0
- package/dist-public/src/conductor/types.d.ts +111 -0
- package/dist-public/src/conductor/types.js +7 -0
- package/dist-public/src/configPath.d.ts +2 -0
- package/dist-public/src/configPath.js +7 -0
- package/dist-public/src/discoveryArtifacts.d.ts +54 -0
- package/dist-public/src/discoveryArtifacts.js +467 -0
- package/dist-public/src/discoveryCommand.d.ts +127 -0
- package/dist-public/src/discoveryCommand.js +2148 -0
- package/dist-public/src/discoveryConnections.d.ts +339 -0
- package/dist-public/src/discoveryConnections.js +613 -0
- package/dist-public/src/discoveryDocs.d.ts +129 -0
- package/dist-public/src/discoveryDocs.js +577 -0
- package/dist-public/src/discoveryEvidence.d.ts +59 -0
- package/dist-public/src/discoveryEvidence.js +198 -0
- package/dist-public/src/discoveryFireflies.d.ts +25 -0
- package/dist-public/src/discoveryFireflies.js +551 -0
- package/dist-public/src/discoveryGmail.d.ts +12 -0
- package/dist-public/src/discoveryGmail.js +466 -0
- package/dist-public/src/discoveryGong.d.ts +12 -0
- package/dist-public/src/discoveryGong.js +491 -0
- package/dist-public/src/discoveryLearningRouting.d.ts +90 -0
- package/dist-public/src/discoveryLearningRouting.js +405 -0
- package/dist-public/src/discoveryMatching.d.ts +26 -0
- package/dist-public/src/discoveryMatching.js +73 -0
- package/dist-public/src/discoveryOutlook.d.ts +12 -0
- package/dist-public/src/discoveryOutlook.js +492 -0
- package/dist-public/src/discoveryProcess.d.ts +221 -0
- package/dist-public/src/discoveryProcess.js +703 -0
- package/dist-public/src/discoveryProof.d.ts +143 -0
- package/dist-public/src/discoveryProof.js +529 -0
- package/dist-public/src/discoveryProviders.d.ts +152 -0
- package/dist-public/src/discoveryProviders.js +756 -0
- package/dist-public/src/discoveryReadiness.d.ts +219 -0
- package/dist-public/src/discoveryReadiness.js +672 -0
- package/dist-public/src/discoveryReconcile.d.ts +57 -0
- package/dist-public/src/discoveryReconcile.js +424 -0
- package/dist-public/src/discoverySlack.d.ts +12 -0
- package/dist-public/src/discoverySlack.js +452 -0
- package/dist-public/src/discoverySources.d.ts +56 -0
- package/dist-public/src/discoverySources.js +322 -0
- package/dist-public/src/discoverySync.d.ts +52 -0
- package/dist-public/src/discoverySync.js +151 -0
- package/dist-public/src/discoverySynthesis.d.ts +57 -0
- package/dist-public/src/discoverySynthesis.js +215 -0
- package/dist-public/src/discoveryTeams.d.ts +12 -0
- package/dist-public/src/discoveryTeams.js +516 -0
- package/dist-public/src/discoveryZoom.d.ts +12 -0
- package/dist-public/src/discoveryZoom.js +523 -0
- package/dist-public/src/elicitationSurface.d.ts +13 -0
- package/dist-public/src/elicitationSurface.js +77 -0
- package/dist-public/src/errorReport.d.ts +99 -0
- package/dist-public/src/errorReport.js +443 -0
- package/dist-public/src/errorTaxonomy.d.ts +84 -0
- package/dist-public/src/errorTaxonomy.js +189 -0
- package/dist-public/src/evalCalibration.d.ts +82 -0
- package/dist-public/src/evalCalibration.js +169 -0
- package/dist-public/src/evalChecks.d.ts +21 -0
- package/dist-public/src/evalChecks.js +295 -0
- package/dist-public/src/evalCompile.d.ts +56 -0
- package/dist-public/src/evalCompile.js +58 -0
- package/dist-public/src/evalImprove.d.ts +60 -0
- package/dist-public/src/evalImprove.js +175 -0
- package/dist-public/src/evalJudge.d.ts +47 -0
- package/dist-public/src/evalJudge.js +143 -0
- package/dist-public/src/evalLabels.d.ts +100 -0
- package/dist-public/src/evalLabels.js +287 -0
- package/dist-public/src/evalRecord.d.ts +68 -0
- package/dist-public/src/evalRecord.js +165 -0
- package/dist-public/src/evalRoast.d.ts +88 -0
- package/dist-public/src/evalRoast.js +249 -0
- package/dist-public/src/evidenceIntake.d.ts +66 -0
- package/dist-public/src/evidenceIntake.js +356 -0
- package/dist-public/src/feedback.d.ts +78 -0
- package/dist-public/src/feedback.js +294 -0
- package/dist-public/src/firefliesClient.d.ts +106 -0
- package/dist-public/src/firefliesClient.js +258 -0
- package/dist-public/src/firstMoveTensions.d.ts +10 -0
- package/dist-public/src/firstMoveTensions.js +93 -0
- package/dist-public/src/gitRun.d.ts +21 -0
- package/dist-public/src/gitRun.js +50 -0
- package/dist-public/src/githubReconcile.d.ts +94 -0
- package/dist-public/src/githubReconcile.js +205 -0
- package/dist-public/src/gmailNango.d.ts +131 -0
- package/dist-public/src/gmailNango.js +329 -0
- package/dist-public/src/gongNango.d.ts +118 -0
- package/dist-public/src/gongNango.js +308 -0
- package/dist-public/src/grammarGate.d.ts +13 -0
- package/dist-public/src/grammarGate.js +72 -0
- package/dist-public/src/harnessGates.d.ts +65 -0
- package/dist-public/src/harnessGates.js +168 -0
- package/dist-public/src/humanSurfaceEval.d.ts +19 -0
- package/dist-public/src/humanSurfaceEval.js +156 -0
- package/dist-public/src/ideate.d.ts +43 -0
- package/dist-public/src/ideate.js +255 -0
- package/dist-public/src/ideateContext.d.ts +11 -0
- package/dist-public/src/ideateContext.js +83 -0
- package/dist-public/src/index.d.ts +2 -0
- package/dist-public/src/index.js +4 -0
- package/dist-public/src/initCommand.d.ts +25 -0
- package/dist-public/src/initCommand.js +558 -0
- package/dist-public/src/interviewGate.d.ts +25 -0
- package/dist-public/src/interviewGate.js +126 -0
- package/dist-public/src/jsonFile.d.ts +2 -0
- package/dist-public/src/jsonFile.js +22 -0
- package/dist-public/src/loopBudget.d.ts +182 -0
- package/dist-public/src/loopBudget.js +494 -0
- package/dist-public/src/loopContracts.d.ts +35 -0
- package/dist-public/src/loopContracts.js +364 -0
- package/dist-public/src/machineState.d.ts +1 -0
- package/dist-public/src/machineState.js +9 -0
- package/dist-public/src/notionNango.d.ts +93 -0
- package/dist-public/src/notionNango.js +279 -0
- package/dist-public/src/orchestrationResult.d.ts +11 -0
- package/dist-public/src/orchestrationResult.js +73 -0
- package/dist-public/src/outlookNango.d.ts +124 -0
- package/dist-public/src/outlookNango.js +327 -0
- package/dist-public/src/packetCorrections.d.ts +40 -0
- package/dist-public/src/packetCorrections.js +172 -0
- package/dist-public/src/packetGlossary.d.ts +6 -0
- package/dist-public/src/packetGlossary.js +42 -0
- package/dist-public/src/packetIndex.d.ts +54 -0
- package/dist-public/src/packetIndex.js +744 -0
- package/dist-public/src/packetIndexEntry.d.ts +3 -0
- package/dist-public/src/packetIndexEntry.js +128 -0
- package/dist-public/src/packetIndexServe.d.ts +13 -0
- package/dist-public/src/packetIndexServe.js +113 -0
- package/dist-public/src/packetIndexWorker.d.ts +7 -0
- package/dist-public/src/packetIndexWorker.js +45 -0
- package/dist-public/src/packetLifecycle.d.ts +30 -0
- package/dist-public/src/packetLifecycle.js +127 -0
- package/dist-public/src/packetReconcile.d.ts +73 -0
- package/dist-public/src/packetReconcile.js +386 -0
- package/dist-public/src/packetStatus.d.ts +125 -0
- package/dist-public/src/packetStatus.js +770 -0
- package/dist-public/src/packetValidation.d.ts +17 -0
- package/dist-public/src/packetValidation.js +333 -0
- package/dist-public/src/packetVerify.d.ts +73 -0
- package/dist-public/src/packetVerify.js +303 -0
- package/dist-public/src/packetWrite.d.ts +52 -0
- package/dist-public/src/packetWrite.js +448 -0
- package/dist-public/src/pitch.d.ts +38 -0
- package/dist-public/src/pitch.js +154 -0
- package/dist-public/src/processMemo.d.ts +8 -0
- package/dist-public/src/processMemo.js +23 -0
- package/dist-public/src/productDetect.d.ts +30 -0
- package/dist-public/src/productDetect.js +376 -0
- package/dist-public/src/profile.d.ts +62 -0
- package/dist-public/src/profile.js +334 -0
- package/dist-public/src/questionModel.d.ts +46 -0
- package/dist-public/src/questionModel.js +74 -0
- package/dist-public/src/questionText.d.ts +7 -0
- package/dist-public/src/questionText.js +38 -0
- package/dist-public/src/redactConfig.d.ts +2 -0
- package/dist-public/src/redactConfig.js +11 -0
- package/dist-public/src/redactEngine.d.ts +59 -0
- package/dist-public/src/redactEngine.js +509 -0
- package/dist-public/src/redactGate.d.ts +63 -0
- package/dist-public/src/redactGate.js +209 -0
- package/dist-public/src/repoRead.d.ts +29 -0
- package/dist-public/src/repoRead.js +160 -0
- package/dist-public/src/research.d.ts +242 -0
- package/dist-public/src/research.js +1314 -0
- package/dist-public/src/researchCompletion.d.ts +34 -0
- package/dist-public/src/researchCompletion.js +123 -0
- package/dist-public/src/researchExecution.d.ts +47 -0
- package/dist-public/src/researchExecution.js +309 -0
- package/dist-public/src/researchFormat.d.ts +21 -0
- package/dist-public/src/researchFormat.js +77 -0
- package/dist-public/src/researchGate.d.ts +86 -0
- package/dist-public/src/researchGate.js +1036 -0
- package/dist-public/src/researchReceipt.d.ts +76 -0
- package/dist-public/src/researchReceipt.js +148 -0
- package/dist-public/src/researchRender.d.ts +2 -0
- package/dist-public/src/researchRender.js +267 -0
- package/dist-public/src/researchSigningKey.d.ts +19 -0
- package/dist-public/src/researchSigningKey.js +95 -0
- package/dist-public/src/riskClassify.d.ts +42 -0
- package/dist-public/src/riskClassify.js +149 -0
- package/dist-public/src/routines.d.ts +42 -0
- package/dist-public/src/routines.js +269 -0
- package/dist-public/src/routingPolicy.d.ts +172 -0
- package/dist-public/src/routingPolicy.js +646 -0
- package/dist-public/src/secretInput.d.ts +8 -0
- package/dist-public/src/secretInput.js +89 -0
- package/dist-public/src/selfCheck.d.ts +7 -0
- package/dist-public/src/selfCheck.js +473 -0
- package/dist-public/src/selfCheckCache.d.ts +29 -0
- package/dist-public/src/selfCheckCache.js +147 -0
- package/dist-public/src/severityClass.d.ts +15 -0
- package/dist-public/src/severityClass.js +22 -0
- package/dist-public/src/skillClientPolicy.d.ts +8 -0
- package/dist-public/src/skillClientPolicy.js +16 -0
- package/dist-public/src/skillLocal.d.ts +98 -0
- package/dist-public/src/skillLocal.js +463 -0
- package/dist-public/src/skillTreeDigest.d.ts +9 -0
- package/dist-public/src/skillTreeDigest.js +64 -0
- package/dist-public/src/slackNango.d.ts +62 -0
- package/dist-public/src/slackNango.js +208 -0
- package/dist-public/src/social/arxiv.d.ts +19 -0
- package/dist-public/src/social/arxiv.js +96 -0
- package/dist-public/src/social/github.d.ts +2 -0
- package/dist-public/src/social/github.js +78 -0
- package/dist-public/src/social/hackernews.d.ts +2 -0
- package/dist-public/src/social/hackernews.js +67 -0
- package/dist-public/src/social/index.d.ts +10 -0
- package/dist-public/src/social/index.js +79 -0
- package/dist-public/src/social/reddit.d.ts +2 -0
- package/dist-public/src/social/reddit.js +125 -0
- package/dist-public/src/social/types.d.ts +73 -0
- package/dist-public/src/social/types.js +61 -0
- package/dist-public/src/social/xml.d.ts +10 -0
- package/dist-public/src/social/xml.js +49 -0
- package/dist-public/src/social/youtube.d.ts +7 -0
- package/dist-public/src/social/youtube.js +217 -0
- package/dist-public/src/sourceIngress.d.ts +488 -0
- package/dist-public/src/sourceIngress.js +1651 -0
- package/dist-public/src/specAfter.d.ts +187 -0
- package/dist-public/src/specAfter.js +814 -0
- package/dist-public/src/specBudget.d.ts +124 -0
- package/dist-public/src/specBudget.js +424 -0
- package/dist-public/src/specDecisionSignals.d.ts +38 -0
- package/dist-public/src/specDecisionSignals.js +121 -0
- package/dist-public/src/specLinkedCommits.d.ts +7 -0
- package/dist-public/src/specLinkedCommits.js +107 -0
- package/dist-public/src/specSections.d.ts +105 -0
- package/dist-public/src/specSections.js +718 -0
- package/dist-public/src/statusGlyphs.d.ts +18 -0
- package/dist-public/src/statusGlyphs.js +75 -0
- package/dist-public/src/swarmCommand.d.ts +10 -0
- package/dist-public/src/swarmCommand.js +96 -0
- package/dist-public/src/teamsNango.d.ts +114 -0
- package/dist-public/src/teamsNango.js +301 -0
- package/dist-public/src/tokenStore.d.ts +10 -0
- package/dist-public/src/tokenStore.js +77 -0
- package/dist-public/src/trustEnvelope.d.ts +78 -0
- package/dist-public/src/trustEnvelope.js +392 -0
- package/dist-public/src/untrustedText.d.ts +6 -0
- package/dist-public/src/untrustedText.js +20 -0
- package/dist-public/src/updateNotice.d.ts +41 -0
- package/dist-public/src/updateNotice.js +290 -0
- package/dist-public/src/vendorYaml.d.ts +1 -0
- package/dist-public/src/vendorYaml.js +7374 -0
- package/dist-public/src/workflowRunActuals.d.ts +67 -0
- package/dist-public/src/workflowRunActuals.js +184 -0
- package/dist-public/src/zendeskDiscovery.d.ts +12 -0
- package/dist-public/src/zendeskDiscovery.js +513 -0
- package/dist-public/src/zendeskNango.d.ts +131 -0
- package/dist-public/src/zendeskNango.js +344 -0
- package/dist-public/src/zoomNango.d.ts +106 -0
- package/dist-public/src/zoomNango.js +351 -0
- package/docs/CHANGELOG.md +32 -0
- package/docs/THIRD_PARTY_NOTICES.md +36 -0
- package/package.json +71 -4
- package/schemas/bug-capsule-receipt.schema.json +54 -0
- package/schemas/bug-capsule.schema.json +189 -0
- package/schemas/discovery-evidence-envelope.schema.json +79 -0
- package/schemas/discovery-processing-receipt.schema.json +71 -0
- package/schemas/discovery-service-contract.schema.json +189 -0
- package/skills/AGENTS.md +32 -0
- package/skills/README.md +287 -0
- package/skills/bug/SKILL.md +49 -0
- package/skills/build-preview-spec/SKILL.md +103 -0
- package/skills/capture-external-artifact/SKILL.md +23 -0
- package/skills/capture-external-artifact/agents/openai.yaml +4 -0
- package/skills/capture-external-artifact/references/extractor-libraries.md +27 -0
- package/skills/check-spec-health/SKILL.md +45 -0
- package/skills/check-spec-health/agents/openai.yaml +4 -0
- package/skills/competitive-positioning/SKILL.md +86 -0
- package/skills/competitive-positioning/agents/openai.yaml +4 -0
- package/skills/cpo-lens/SKILL.md +398 -0
- package/skills/cpo-lens/agents/openai.yaml +4 -0
- package/skills/create-prd/SKILL.md +26 -0
- package/skills/create-prd/agents/openai.yaml +4 -0
- package/skills/customer-discovery/SKILL.md +192 -0
- package/skills/design-system/SKILL.md +49 -0
- package/skills/design-system/agents/openai.yaml +4 -0
- package/skills/discussion-guide/SKILL.md +75 -0
- package/skills/discussion-guide/agents/openai.yaml +4 -0
- package/skills/firecrawl-research-papers/SKILL.md +96 -0
- package/skills/firecrawl-research-papers/agents/openai.yaml +4 -0
- package/skills/foo-roast/SKILL.md +325 -0
- package/skills/generate-gtm-brief/SKILL.md +41 -0
- package/skills/generate-gtm-brief/agents/openai.yaml +4 -0
- package/skills/generate-gtm-brief/references/gtm-brief-template.md +56 -0
- package/skills/gong-quotes/SKILL.md +43 -0
- package/skills/gong-quotes/agents/openai.yaml +4 -0
- package/skills/grill-me/SKILL.md +27 -0
- package/skills/grill-me/agents/openai.yaml +4 -0
- package/skills/ideate/SKILL.md +180 -0
- package/skills/ideate/references/family-packs.md +5 -0
- package/skills/initiative-tracker/SKILL.md +23 -0
- package/skills/initiative-tracker/agents/openai.yaml +4 -0
- package/skills/interop-review/SKILL.md +25 -0
- package/skills/interop-review/agents/openai.yaml +4 -0
- package/skills/interview-me/SKILL.md +404 -0
- package/skills/interview-me/agents/openai.yaml +4 -0
- package/skills/loop-library-audit/SKILL.md +123 -0
- package/skills/pitch/SKILL.md +110 -0
- package/skills/pitch/references/opportunity-assessment.md +136 -0
- package/skills/pitch/references/prfaq-template.md +54 -0
- package/skills/pmm-release-briefing/SKILL.md +27 -0
- package/skills/pmm-release-briefing/agents/openai.yaml +4 -0
- package/skills/pmm-release-briefing/references/variant-generalized.md +16 -0
- package/skills/positioning-messaging/SKILL.md +70 -0
- package/skills/positioning-messaging/agents/openai.yaml +4 -0
- package/skills/positioning-messaging/references/guest-insights.md +1400 -0
- package/skills/preview-swarm/SKILL.md +418 -0
- package/skills/preview-swarm/agents/openai.yaml +4 -0
- package/skills/preview-swarm/references/html-effectiveness-build-previews.md +168 -0
- package/skills/product-copywriting/SKILL.md +127 -0
- package/skills/product-copywriting/agents/openai.yaml +4 -0
- package/skills/product-copywriting/references/foo-voice.md +26 -0
- package/skills/product-shape-interview/SKILL.md +219 -0
- package/skills/product-shape-interview/agents/openai.yaml +4 -0
- package/skills/ready-for-release/SKILL.md +39 -0
- package/skills/ready-for-release/agents/openai.yaml +4 -0
- package/skills/redact-transcripts/SKILL.md +23 -0
- package/skills/redact-transcripts/agents/openai.yaml +4 -0
- package/skills/research-brief/SKILL.md +48 -0
- package/skills/research-brief/agents/openai.yaml +4 -0
- package/skills/research-refresh/SKILL.md +63 -0
- package/skills/research-refresh/references/worked-cases.md +44 -0
- package/skills/review-loop/SKILL.md +200 -0
- package/skills/review-spec/SKILL.md +32 -0
- package/skills/review-spec/agents/openai.yaml +4 -0
- package/skills/rfc-from-research/SKILL.md +42 -0
- package/skills/rfc-from-research/agents/openai.yaml +4 -0
- package/skills/rfc-from-research/references/rfc-pattern.md +69 -0
- package/skills/skill-registry.yaml +810 -0
- package/skills/spec-cleanup/SKILL.md +103 -0
- package/skills/spec-cleanup/agents/openai.yaml +4 -0
- package/skills/spec-swarm/SKILL.md +87 -0
- package/skills/spec-swarm/agents/openai.yaml +4 -0
- package/skills/spec-swarm/template/SKILL.template.md +94 -0
- package/skills/spec-swarm/template/personas/accessibility-inclusion.md +40 -0
- package/skills/spec-swarm/template/personas/ai-technical-pm.md +69 -0
- package/skills/spec-swarm/template/personas/analytics-engineering.md +39 -0
- package/skills/spec-swarm/template/personas/applied-ai-eval.md +64 -0
- package/skills/spec-swarm/template/personas/business-gm-pm.md +67 -0
- package/skills/spec-swarm/template/personas/consumer-craft-pm.md +68 -0
- package/skills/spec-swarm/template/personas/content-design.md +40 -0
- package/skills/spec-swarm/template/personas/cpo.md +42 -0
- package/skills/spec-swarm/template/personas/data-science.md +42 -0
- package/skills/spec-swarm/template/personas/developer-experience.md +65 -0
- package/skills/spec-swarm/template/personas/dx-product.md +70 -0
- package/skills/spec-swarm/template/personas/field.md +43 -0
- package/skills/spec-swarm/template/personas/finance-cfo.md +40 -0
- package/skills/spec-swarm/template/personas/growth-pm.md +67 -0
- package/skills/spec-swarm/template/personas/platform-pm.md +67 -0
- package/skills/spec-swarm/template/personas/pmm.md +43 -0
- package/skills/spec-swarm/template/personas/principal-product-designer.md +42 -0
- package/skills/spec-swarm/template/personas/product-ops-program.md +41 -0
- package/skills/spec-swarm/template/personas/qa-release.md +41 -0
- package/skills/spec-swarm/template/personas/responsible-ai-safety.md +64 -0
- package/skills/spec-swarm/template/personas/security-privacy-legal.md +40 -0
- package/skills/spec-swarm/template/personas/strategy-competitive.md +41 -0
- package/skills/spec-swarm/template/personas/technical-architect.md +42 -0
- package/skills/spec-swarm/template/personas/technical-lead.md +43 -0
- package/skills/spec-swarm/template/personas/user-research.md +41 -0
- package/skills/spec-swarm-accessibility-inclusion/SKILL.md +95 -0
- package/skills/spec-swarm-accessibility-inclusion/agents/openai.yaml +4 -0
- package/skills/spec-swarm-ai-technical-pm/SKILL.md +124 -0
- package/skills/spec-swarm-analytics-engineering/SKILL.md +94 -0
- package/skills/spec-swarm-analytics-engineering/agents/openai.yaml +4 -0
- package/skills/spec-swarm-applied-ai-eval/SKILL.md +120 -0
- package/skills/spec-swarm-business-gm-pm/SKILL.md +122 -0
- package/skills/spec-swarm-consumer-craft-pm/SKILL.md +122 -0
- package/skills/spec-swarm-content-design/SKILL.md +95 -0
- package/skills/spec-swarm-content-design/agents/openai.yaml +4 -0
- package/skills/spec-swarm-cpo/SKILL.md +94 -0
- package/skills/spec-swarm-cpo/agents/openai.yaml +4 -0
- package/skills/spec-swarm-data-science/SKILL.md +95 -0
- package/skills/spec-swarm-data-science/agents/openai.yaml +4 -0
- package/skills/spec-swarm-developer-experience/SKILL.md +121 -0
- package/skills/spec-swarm-dx-product/SKILL.md +126 -0
- package/skills/spec-swarm-field/SKILL.md +96 -0
- package/skills/spec-swarm-field/agents/openai.yaml +4 -0
- package/skills/spec-swarm-finance-cfo/SKILL.md +95 -0
- package/skills/spec-swarm-finance-cfo/agents/openai.yaml +4 -0
- package/skills/spec-swarm-growth-pm/SKILL.md +122 -0
- package/skills/spec-swarm-platform-pm/SKILL.md +122 -0
- package/skills/spec-swarm-pmm/SKILL.md +96 -0
- package/skills/spec-swarm-pmm/agents/openai.yaml +4 -0
- package/skills/spec-swarm-principal-product-designer/SKILL.md +95 -0
- package/skills/spec-swarm-principal-product-designer/agents/openai.yaml +4 -0
- package/skills/spec-swarm-product-ops-program/SKILL.md +96 -0
- package/skills/spec-swarm-product-ops-program/agents/openai.yaml +4 -0
- package/skills/spec-swarm-qa-release/SKILL.md +96 -0
- package/skills/spec-swarm-qa-release/agents/openai.yaml +4 -0
- package/skills/spec-swarm-responsible-ai-safety/SKILL.md +120 -0
- package/skills/spec-swarm-security-privacy-legal/SKILL.md +95 -0
- package/skills/spec-swarm-security-privacy-legal/agents/openai.yaml +4 -0
- package/skills/spec-swarm-strategy-competitive/SKILL.md +96 -0
- package/skills/spec-swarm-strategy-competitive/agents/openai.yaml +4 -0
- package/skills/spec-swarm-technical-architect/SKILL.md +95 -0
- package/skills/spec-swarm-technical-architect/agents/openai.yaml +4 -0
- package/skills/spec-swarm-technical-lead/SKILL.md +96 -0
- package/skills/spec-swarm-technical-lead/agents/openai.yaml +4 -0
- package/skills/spec-swarm-user-research/SKILL.md +95 -0
- package/skills/spec-swarm-user-research/agents/openai.yaml +4 -0
- package/skills/strategy-check/SKILL.md +170 -0
- package/skills/strategy-check/agents/openai.yaml +4 -0
- package/skills/study-digest/SKILL.md +41 -0
- package/skills/synthetic-customer/SKILL.md +68 -0
- package/skills/update-cli-spec/SKILL.md +69 -0
- package/skills/update-cli-spec/agents/openai.yaml +4 -0
- package/skills/voc-researcher/SKILL.md +45 -0
- package/skills/voc-researcher/agents/openai.yaml +4 -0
- package/skills-lock.json +35 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FooState = "proven" | "claimed" | "doing" | "blocked" | "needs_input" | "todo" | "drift" | "classification" | "parked" | "killed";
|
|
2
|
+
export interface GlyphSpec {
|
|
3
|
+
glyph: string;
|
|
4
|
+
/** The word that carries the meaning when glyphs are off. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Maps to the web design tokens (web/tokens.css) for the eventual HTML skin. */
|
|
7
|
+
tone: "proof" | "attention" | "warn" | "neutral";
|
|
8
|
+
}
|
|
9
|
+
export declare const STATUS_GLYPHS: Record<FooState, GlyphSpec>;
|
|
10
|
+
export declare const STATUS_ORDER: FooState[];
|
|
11
|
+
export declare function withGlyph(state: FooState, text: string, glyphsOn: boolean): string;
|
|
12
|
+
export declare function glyphFor(state: FooState, glyphsOn: boolean): string;
|
|
13
|
+
export declare function legendLine(glyphsOn: boolean): string;
|
|
14
|
+
export declare function glyphsEnabled(opts: {
|
|
15
|
+
isTTY: boolean;
|
|
16
|
+
env: Record<string, string | undefined>;
|
|
17
|
+
args: string[];
|
|
18
|
+
}): boolean;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Friendly status-glyph vocabulary (specs/foo-status-glyphs-v0).
|
|
2
|
+
//
|
|
3
|
+
// One glyph per foo state — a scannable skin over the state model foo already
|
|
4
|
+
// has (proven/done/doing/blocked/open, plus needs-human, drift, classification).
|
|
5
|
+
// The friendliness comes from consistency, not decoration: the same glyph means
|
|
6
|
+
// the same thing on every surface, so it stays "checksum-like," never a badge pile.
|
|
7
|
+
//
|
|
8
|
+
// Two hard rules this module enforces by construction:
|
|
9
|
+
// 1. The glyph is always paired with its text word — the word carries the
|
|
10
|
+
// meaning, the emoji is decoration. So a screen reader, a grep, or a future
|
|
11
|
+
// reader without the legend still understands the state.
|
|
12
|
+
// 2. Human surfaces only. `glyphsEnabled` gates them (R-008): off for --json,
|
|
13
|
+
// NO_COLOR, and non-TTY/piped output, on for an interactive terminal.
|
|
14
|
+
// Machine-readable output (events.jsonl, tasks.yaml values, --json) never
|
|
15
|
+
// carries a glyph.
|
|
16
|
+
// The locked vocabulary. The internal `claimed` state renders as the actionable
|
|
17
|
+
// human label 🟡 done — proof pending, keeping productfoo's signature distinction
|
|
18
|
+
// legible — "done is a claim, proof is a receipt" — without two look-alike
|
|
19
|
+
// checkmarks. 🚧 (stuck on a thing) and ✋ (stuck on you) are a clean pair that
|
|
20
|
+
// can never be confused.
|
|
21
|
+
export const STATUS_GLYPHS = {
|
|
22
|
+
proven: { glyph: "✅", label: "proven", tone: "proof" },
|
|
23
|
+
claimed: { glyph: "🟡", label: "done — proof pending", tone: "neutral" },
|
|
24
|
+
doing: { glyph: "🔄", label: "doing", tone: "neutral" },
|
|
25
|
+
blocked: { glyph: "🚧", label: "blocked", tone: "warn" },
|
|
26
|
+
needs_input: { glyph: "✋", label: "needs your input", tone: "warn" },
|
|
27
|
+
todo: { glyph: "📋", label: "todo", tone: "neutral" },
|
|
28
|
+
drift: { glyph: "⚠️", label: "drift", tone: "attention" },
|
|
29
|
+
classification: { glyph: "🏷️", label: "classification", tone: "neutral" },
|
|
30
|
+
// Lifecycle verdicts (packet-lifecycle-v0): retired intent gets a visible,
|
|
31
|
+
// reasoned state instead of masquerading as backlog. ⏸️ may resume; 🪦 won't.
|
|
32
|
+
parked: { glyph: "⏸️", label: "parked", tone: "neutral" },
|
|
33
|
+
killed: { glyph: "🪦", label: "killed", tone: "neutral" },
|
|
34
|
+
};
|
|
35
|
+
// The order a legend or help screen should list them: proof first, then the
|
|
36
|
+
// in-flight states, then the human/attention markers.
|
|
37
|
+
export const STATUS_ORDER = [
|
|
38
|
+
"proven",
|
|
39
|
+
"claimed",
|
|
40
|
+
"doing",
|
|
41
|
+
"blocked",
|
|
42
|
+
"todo",
|
|
43
|
+
"needs_input",
|
|
44
|
+
"drift",
|
|
45
|
+
"classification",
|
|
46
|
+
"parked",
|
|
47
|
+
"killed",
|
|
48
|
+
];
|
|
49
|
+
// Prefix text with a state glyph when glyphs are on; return it untouched when
|
|
50
|
+
// off. The label stays in `text`, so the word always carries the meaning.
|
|
51
|
+
export function withGlyph(state, text, glyphsOn) {
|
|
52
|
+
return glyphsOn ? `${STATUS_GLYPHS[state].glyph} ${text}` : text;
|
|
53
|
+
}
|
|
54
|
+
// Just the glyph (or "" when off), for tight inline spots where the caller
|
|
55
|
+
// already prints the word.
|
|
56
|
+
export function glyphFor(state, glyphsOn) {
|
|
57
|
+
return glyphsOn ? STATUS_GLYPHS[state].glyph : "";
|
|
58
|
+
}
|
|
59
|
+
// A one-line legend, e.g. for `foo` help or a packet footer. Honors the gate:
|
|
60
|
+
// returns "" when glyphs are off so it never leaks into machine output.
|
|
61
|
+
export function legendLine(glyphsOn) {
|
|
62
|
+
if (!glyphsOn)
|
|
63
|
+
return "";
|
|
64
|
+
return STATUS_ORDER.map((s) => `${STATUS_GLYPHS[s].glyph} ${STATUS_GLYPHS[s].label}`).join(" ");
|
|
65
|
+
}
|
|
66
|
+
// The human-surface gate (R-008): glyphs decorate an interactive terminal view
|
|
67
|
+
// only, so they never corrupt machine-readable output. Off for --json, NO_COLOR,
|
|
68
|
+
// and any non-TTY/piped stdout; on for a plain interactive terminal.
|
|
69
|
+
export function glyphsEnabled(opts) {
|
|
70
|
+
if (opts.args.includes("--json"))
|
|
71
|
+
return false;
|
|
72
|
+
if (opts.env.NO_COLOR)
|
|
73
|
+
return false;
|
|
74
|
+
return Boolean(opts.isTTY);
|
|
75
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SwarmCommandResult {
|
|
2
|
+
/** Safe for a person's terminal and for public machine consumers. */
|
|
3
|
+
output: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
/** Private operating contract for the coding harness. Never JSON or record content. */
|
|
6
|
+
agentHandoff?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function runSwarmCommand(root: string, args: string[], options?: {
|
|
9
|
+
glyphs?: boolean;
|
|
10
|
+
}): Promise<SwarmCommandResult>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// methodology-compiler-v0 (T-004) → methodology-as-data: `foo swarm` — the
|
|
2
|
+
// spec-swarm conductor as a CLI adapter. Prepares a private agent handoff (lens
|
|
3
|
+
// roster, preflight protocol, budgets) and scaffolds the swarm record from a RESOLVED
|
|
4
|
+
// family pack (local in the verified package; missing packs refuse while remote
|
|
5
|
+
// delivery is unavailable), so the lens roster + protocol are never serialized wholesale into
|
|
6
|
+
// the record or placed on the public CLI/JSON surface. The CLI never reviews a
|
|
7
|
+
// spec itself and never answers for the user.
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { checkRecord, slugify } from "./conductor/engine.js";
|
|
11
|
+
import { resolveFamilyPack } from "./conductor/familyDelivery.js";
|
|
12
|
+
import { contractFromPack, recordFromPack } from "./conductor/pack.js";
|
|
13
|
+
import { markFailure } from "./errorTaxonomy.js";
|
|
14
|
+
import { withGlyph } from "./statusGlyphs.js";
|
|
15
|
+
const LEAN = (process.env.FOO_LEAN_CONTRACT ?? "").trim() === "1";
|
|
16
|
+
function preparedOutput(recordPath, glyphsOn) {
|
|
17
|
+
return [
|
|
18
|
+
withGlyph("doing", "doing — expert review prepared for your coding agent.", glyphsOn),
|
|
19
|
+
"productfoo will surface the recommendation, evidence, dissent, and any decisions that need you.",
|
|
20
|
+
...(recordPath ? [`Record: ${recordPath}`] : []),
|
|
21
|
+
"",
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
function agentHandoff(contract) {
|
|
25
|
+
return [
|
|
26
|
+
"productfoo private handoff — coding harness only. Execute this contract; do not quote, summarize, or reproduce its roster or choreography in user-facing output. Surface only the resulting recommendation, evidence, confidence, dissent, and decisions that need the user.",
|
|
27
|
+
"",
|
|
28
|
+
contract,
|
|
29
|
+
].join("\n");
|
|
30
|
+
}
|
|
31
|
+
export async function runSwarmCommand(root, args, options = {}) {
|
|
32
|
+
const json = args.includes("--json");
|
|
33
|
+
if (args[0] === "check") {
|
|
34
|
+
const target = args[1];
|
|
35
|
+
if (!target) {
|
|
36
|
+
markFailure({ errorClass: "usage_error", errorCode: "E_SWARM_TARGET_MISSING", message: "swarm check: no record given" });
|
|
37
|
+
return { output: "usage: foo swarm check <record.md> [--json]\n", exitCode: 1 };
|
|
38
|
+
}
|
|
39
|
+
const full = path.resolve(root, target);
|
|
40
|
+
if (!existsSync(full)) {
|
|
41
|
+
markFailure({ errorClass: "not_found", errorCode: "E_SPEC_NOT_FOUND", message: `swarm record not found: ${target}` });
|
|
42
|
+
return { output: `swarm record not found: ${target}\n`, exitCode: 1 };
|
|
43
|
+
}
|
|
44
|
+
const pack = await resolveFamilyPack("spec-swarm", "standard", LEAN);
|
|
45
|
+
const result = checkRecord({ gates: pack.gates, depths: pack.depths }, readFileSync(full, "utf8"));
|
|
46
|
+
if (json)
|
|
47
|
+
return { output: `${JSON.stringify({ schema_version: "0.1", ...result }, null, 2)}\n`, exitCode: result.ok ? 0 : 1 };
|
|
48
|
+
const lines = [`swarm check — ${target}`];
|
|
49
|
+
for (const f of result.findings)
|
|
50
|
+
lines.push(` ${f.ok ? "ok " : "FAIL"} ${f.check}: ${f.detail}`);
|
|
51
|
+
lines.push(result.ok ? "Record verifies: swarm is contract-complete." : "Record does not verify — fix the FAIL lines before carrying the swarm forward.");
|
|
52
|
+
return { output: `${lines.join("\n")}\n`, exitCode: result.ok ? 0 : 1 };
|
|
53
|
+
}
|
|
54
|
+
const depthIdx = args.indexOf("--depth");
|
|
55
|
+
const depth = (depthIdx === -1 ? "standard" : args[depthIdx + 1]);
|
|
56
|
+
if (!["fast", "standard", "deep"].includes(depth)) {
|
|
57
|
+
markFailure({ errorClass: "usage_error", errorCode: "E_BAD_FLAG", message: "swarm: --depth must be fast, standard, or deep" });
|
|
58
|
+
return { output: "--depth must be fast, standard, or deep\n", exitCode: 1 };
|
|
59
|
+
}
|
|
60
|
+
const target = args.filter((a, i) => !a.startsWith("--") && (depthIdx === -1 || i !== depthIdx + 1)).join(" ").trim();
|
|
61
|
+
if (!target) {
|
|
62
|
+
// `foo swarm` with no argument — one of the four "errors" on the owner's dashboard.
|
|
63
|
+
// It is a typo. It prints usage. It is not a defect and must never page anyone.
|
|
64
|
+
markFailure({ errorClass: "usage_error", errorCode: "E_SWARM_TARGET_MISSING", message: "swarm: no spec path or summary given" });
|
|
65
|
+
return { output: 'usage: foo swarm <spec-path-or-summary> [--depth fast|standard|deep] [--json]\n', exitCode: 1 };
|
|
66
|
+
}
|
|
67
|
+
const pack = await resolveFamilyPack("spec-swarm", depth, LEAN);
|
|
68
|
+
const at = new Date().toISOString();
|
|
69
|
+
// Scaffold from the pack: the engine's record path is dir/<date>-<slug>.md for
|
|
70
|
+
// EVERY family (renderContext), and the contract embeds that same path — so the
|
|
71
|
+
// file must land there for the two to agree byte-for-byte.
|
|
72
|
+
const recordPath = pack.recordDir ? path.posix.join(pack.recordDir, `${at.slice(0, 10)}-${slugify(target)}.md`) : null;
|
|
73
|
+
const rendered = recordFromPack(pack, target, at);
|
|
74
|
+
if (recordPath && rendered) {
|
|
75
|
+
const fullRec = path.resolve(root, recordPath);
|
|
76
|
+
if (existsSync(fullRec))
|
|
77
|
+
return { output: `spec-swarm record already exists: ${recordPath}\n`, exitCode: 1 };
|
|
78
|
+
mkdirSync(path.dirname(fullRec), { recursive: true });
|
|
79
|
+
writeFileSync(fullRec, [...rendered.frontmatter, ...rendered.body].join("\n"));
|
|
80
|
+
}
|
|
81
|
+
if (json) {
|
|
82
|
+
return {
|
|
83
|
+
output: `${JSON.stringify({ schema_version: "0.2", family: "spec-swarm", status: "prepared", target, depth, record_path: recordPath, decider: "human" }, null, 2)}\n`,
|
|
84
|
+
exitCode: 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// The compiled contract is an operating handoff for the coding harness, not
|
|
88
|
+
// a human status surface. Keeping it separate prevents callers from
|
|
89
|
+
// accidentally relaying the method when they only mean to print the result.
|
|
90
|
+
const contract = contractFromPack(pack, target, at).join("\n");
|
|
91
|
+
return {
|
|
92
|
+
output: preparedOutput(recordPath, Boolean(options.glyphs)),
|
|
93
|
+
agentHandoff: agentHandoff(contract),
|
|
94
|
+
exitCode: 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export type FetchLike = typeof globalThis.fetch;
|
|
2
|
+
export type TeamsRuntimeErrorCode = "not_connected" | "auth_expired" | "access_denied" | "provider_failure" | "no_transcript" | "network" | "provider_timeout" | "transcript_too_large" | "budget_exhausted";
|
|
3
|
+
export declare class TeamsRuntimeError extends Error {
|
|
4
|
+
readonly code: TeamsRuntimeErrorCode;
|
|
5
|
+
constructor(code: TeamsRuntimeErrorCode, message: string);
|
|
6
|
+
}
|
|
7
|
+
export type TeamsConnection = {
|
|
8
|
+
connection_ref: string;
|
|
9
|
+
broker: "nango";
|
|
10
|
+
credential: {
|
|
11
|
+
type: "oauth2";
|
|
12
|
+
access_token: string;
|
|
13
|
+
};
|
|
14
|
+
credential_type: "oauth2";
|
|
15
|
+
transcript_scope: "granted" | "missing" | "unknown";
|
|
16
|
+
tenant_domain: string | null;
|
|
17
|
+
};
|
|
18
|
+
export declare const TEAMS_POLICY: {
|
|
19
|
+
readonly policy_version: "teams-local-worker-0.1";
|
|
20
|
+
readonly max_response_bytes: number;
|
|
21
|
+
readonly fetch_timeout_ms: 90000;
|
|
22
|
+
readonly max_model_input_tokens: 100000;
|
|
23
|
+
readonly max_list_pages: 5;
|
|
24
|
+
readonly min_request_interval_ms: 350;
|
|
25
|
+
};
|
|
26
|
+
export type TeamsConnectionEnv = Record<string, string | undefined>;
|
|
27
|
+
export declare function teamsConnectionConfigured(env: TeamsConnectionEnv): boolean;
|
|
28
|
+
export declare function teamsCredentialBroker(env: TeamsConnectionEnv): "nango" | null;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the tenant's Teams credential. Nango is the only broker (R-002/R-011):
|
|
31
|
+
* it holds the tenant-owned credential consented by the Microsoft 365
|
|
32
|
+
* administrator, refreshes it, and is where that administrator revokes it.
|
|
33
|
+
* productfoo receives it for one command and never persists or prints it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveTeamsConnection(input: {
|
|
36
|
+
env: TeamsConnectionEnv;
|
|
37
|
+
fetchImpl: FetchLike;
|
|
38
|
+
}): Promise<TeamsConnection>;
|
|
39
|
+
export type TeamsListedMeeting = {
|
|
40
|
+
id: string;
|
|
41
|
+
subject?: string;
|
|
42
|
+
start?: string;
|
|
43
|
+
end?: string;
|
|
44
|
+
state?: string;
|
|
45
|
+
organizer?: string;
|
|
46
|
+
url?: string;
|
|
47
|
+
transcript_available?: boolean;
|
|
48
|
+
};
|
|
49
|
+
type TeamsClientOptions = {
|
|
50
|
+
fetchImpl: FetchLike;
|
|
51
|
+
baseUrl?: string;
|
|
52
|
+
sleep?: (ms: number) => Promise<void>;
|
|
53
|
+
now?: () => number;
|
|
54
|
+
fetchTimeoutMs?: number;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Minimal read-only Microsoft Graph client. Only documented read operations
|
|
58
|
+
* are reachable; any other method or path routes through the single
|
|
59
|
+
* write-refusal choke point (R-009) so the refusal is testable rather than
|
|
60
|
+
* scattered. Graph shapes are T-001-owned beliefs pinned by fixtures.
|
|
61
|
+
*/
|
|
62
|
+
export declare class TeamsGraphClient {
|
|
63
|
+
private readonly credential;
|
|
64
|
+
private readonly options;
|
|
65
|
+
readonly requests: {
|
|
66
|
+
list: number;
|
|
67
|
+
transcripts: number;
|
|
68
|
+
attendance: number;
|
|
69
|
+
transcript_content: number;
|
|
70
|
+
};
|
|
71
|
+
private lastRequestAt;
|
|
72
|
+
private readonly baseUrl;
|
|
73
|
+
private readonly sleep;
|
|
74
|
+
private readonly now;
|
|
75
|
+
private readonly fetchTimeoutMs;
|
|
76
|
+
constructor(credential: TeamsConnection["credential"], options: TeamsClientOptions);
|
|
77
|
+
private request;
|
|
78
|
+
/**
|
|
79
|
+
* Bounded metadata listing: one time window, a page ceiling, no content.
|
|
80
|
+
* Follows Graph's `@odata.nextLink` paging; stops closed at the policy
|
|
81
|
+
* ceiling instead of widening the read (R-004/R-010).
|
|
82
|
+
*/
|
|
83
|
+
listMeetings(window: {
|
|
84
|
+
from: string;
|
|
85
|
+
to: string;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
meetings: TeamsListedMeeting[];
|
|
88
|
+
pages: number;
|
|
89
|
+
truncated: boolean;
|
|
90
|
+
}>;
|
|
91
|
+
private static wellFormedId;
|
|
92
|
+
/** Transcript metadata for one meeting — never content. */
|
|
93
|
+
meetingTranscripts(meetingId: string): Promise<Array<{
|
|
94
|
+
id: string;
|
|
95
|
+
}>>;
|
|
96
|
+
/**
|
|
97
|
+
* Attendee display names and emails for one meeting, from its attendance
|
|
98
|
+
* report (a T-001-owned belief pinned by fixtures; the Teams analog of the
|
|
99
|
+
* gong parties read). Names feed the adapter's privacy-minimized
|
|
100
|
+
* participants; emails die into domain-only company hints in the adapter.
|
|
101
|
+
* A missing report is observed absence (an empty list), not an error.
|
|
102
|
+
*/
|
|
103
|
+
meetingAttendance(meetingId: string): Promise<Array<{
|
|
104
|
+
display_name?: string;
|
|
105
|
+
email?: string;
|
|
106
|
+
}>>;
|
|
107
|
+
/**
|
|
108
|
+
* Exactly one transcript content read (R-007): the caller passes one
|
|
109
|
+
* meeting id and one transcript id, and no request ever carries more. The
|
|
110
|
+
* content returns bounded into process memory; nothing is written.
|
|
111
|
+
*/
|
|
112
|
+
transcriptContent(meetingId: string, transcriptId: string): Promise<string>;
|
|
113
|
+
}
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Microsoft Teams through Nango (microsoft-teams-discovery-connector-v0,
|
|
2
|
+
// T-005 local worker mode — the gong broker/client port with Graph deltas).
|
|
3
|
+
//
|
|
4
|
+
// Nango is the credential broker only: productfoo asks Nango for the tenant's
|
|
5
|
+
// Microsoft Teams credential and then talks to Microsoft Graph directly, so
|
|
6
|
+
// transcript bytes never transit Nango. The connection is tenant-owned by
|
|
7
|
+
// administrator consent (R-002); there is no direct-credential fallback — a
|
|
8
|
+
// personal access token would undercut the tenant-owned connection contract.
|
|
9
|
+
// Everything here is content-free on every surface productfoo owns: the credential
|
|
10
|
+
// is held in process memory for one command, the Nango connection id is never
|
|
11
|
+
// printed (an opaque `connection_ref` stands in for it), and errors carry a
|
|
12
|
+
// typed code plus a bounded message that never embeds a provider response.
|
|
13
|
+
//
|
|
14
|
+
// Graph API shapes below are beliefs owned by the packet's feasibility task
|
|
15
|
+
// (T-001, OQ-002 records the license-coverage uncertainty) and pinned by
|
|
16
|
+
// fixtures in test/discoveryTeamsRuntime.test.ts; the live dogfood run (T-008)
|
|
17
|
+
// is what promotes them from belief to verified.
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { refuseSourceWrite } from "./sourceIngress.js";
|
|
20
|
+
export class TeamsRuntimeError extends Error {
|
|
21
|
+
code;
|
|
22
|
+
constructor(code, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.name = "TeamsRuntimeError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Versioned policy ceilings the worker enforces and reports, so a later change
|
|
29
|
+
// is explicit rather than silent (R-010). Fails closed (R-007).
|
|
30
|
+
export const TEAMS_POLICY = {
|
|
31
|
+
policy_version: "teams-local-worker-0.1",
|
|
32
|
+
max_response_bytes: 8 * 1024 * 1024,
|
|
33
|
+
fetch_timeout_ms: 90_000,
|
|
34
|
+
max_model_input_tokens: 100_000,
|
|
35
|
+
max_list_pages: 5,
|
|
36
|
+
min_request_interval_ms: 350,
|
|
37
|
+
};
|
|
38
|
+
// Belief owned by T-001/R-003: the least-privilege Graph scope whose presence
|
|
39
|
+
// in the consented token signals transcript-read permission. R-003 verifies
|
|
40
|
+
// the real scope set receipt-backed; the capability surface only ever reports
|
|
41
|
+
// what the token itself declares.
|
|
42
|
+
const TEAMS_TRANSCRIPT_SCOPE = "OnlineMeetingTranscript.Read.All";
|
|
43
|
+
const DEFAULT_NANGO_HOST = "https://api.nango.dev";
|
|
44
|
+
export function teamsConnectionConfigured(env) {
|
|
45
|
+
return Boolean(env.NANGO_SECRET_KEY?.trim() && env.FOO_TEAMS_NANGO_CONNECTION?.trim());
|
|
46
|
+
}
|
|
47
|
+
export function teamsCredentialBroker(env) {
|
|
48
|
+
return teamsConnectionConfigured(env) ? "nango" : null;
|
|
49
|
+
}
|
|
50
|
+
function opaqueConnectionRef(integration, connectionId) {
|
|
51
|
+
return `microsoft-teams:${createHash("sha256").update(`${integration}\n${connectionId}`).digest("hex").slice(0, 16)}`;
|
|
52
|
+
}
|
|
53
|
+
async function fetchWithTimeout(fetchImpl, url, init, timeoutMs) {
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
56
|
+
try {
|
|
57
|
+
return await fetchImpl(url, { ...init, signal: controller.signal });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (controller.signal.aborted)
|
|
61
|
+
throw new TeamsRuntimeError("provider_timeout", "the provider did not answer within the fetch ceiling");
|
|
62
|
+
throw new TeamsRuntimeError("network", `network failure reaching ${new URL(url).hostname}`);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Reads a response body while counting toward the byte ceiling, so an
|
|
69
|
+
// oversized transcript is dropped before it is ever materialized in full.
|
|
70
|
+
async function readBounded(response, maxBytes) {
|
|
71
|
+
const declared = Number(response.headers.get("content-length") ?? "0");
|
|
72
|
+
if (Number.isFinite(declared) && declared > maxBytes)
|
|
73
|
+
throw new TeamsRuntimeError("transcript_too_large", "the provider response exceeds the byte ceiling");
|
|
74
|
+
if (!response.body) {
|
|
75
|
+
const buffer = await response.arrayBuffer();
|
|
76
|
+
if (buffer.byteLength > maxBytes)
|
|
77
|
+
throw new TeamsRuntimeError("transcript_too_large", "the provider response exceeds the byte ceiling");
|
|
78
|
+
return new TextDecoder().decode(buffer);
|
|
79
|
+
}
|
|
80
|
+
const reader = response.body.getReader();
|
|
81
|
+
const chunks = [];
|
|
82
|
+
let total = 0;
|
|
83
|
+
for (;;) {
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
if (done)
|
|
86
|
+
break;
|
|
87
|
+
total += value.byteLength;
|
|
88
|
+
if (total > maxBytes) {
|
|
89
|
+
await reader.cancel().catch(() => undefined);
|
|
90
|
+
chunks.length = 0;
|
|
91
|
+
throw new TeamsRuntimeError("transcript_too_large", "the provider response exceeds the byte ceiling");
|
|
92
|
+
}
|
|
93
|
+
chunks.push(value);
|
|
94
|
+
}
|
|
95
|
+
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
96
|
+
}
|
|
97
|
+
function parseJson(text, label) {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(text);
|
|
100
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
101
|
+
throw new Error("not an object");
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new TeamsRuntimeError("provider_failure", `${label} returned a response productfoo could not read`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the tenant's Teams credential. Nango is the only broker (R-002/R-011):
|
|
110
|
+
* it holds the tenant-owned credential consented by the Microsoft 365
|
|
111
|
+
* administrator, refreshes it, and is where that administrator revokes it.
|
|
112
|
+
* productfoo receives it for one command and never persists or prints it.
|
|
113
|
+
*/
|
|
114
|
+
export async function resolveTeamsConnection(input) {
|
|
115
|
+
if (!teamsConnectionConfigured(input.env)) {
|
|
116
|
+
throw new TeamsRuntimeError("not_connected", "Microsoft Teams is not connected: a Microsoft 365 administrator connects the tenant-owned connection through Nango — set NANGO_SECRET_KEY and FOO_TEAMS_NANGO_CONNECTION (plus NANGO_HOST / FOO_TEAMS_NANGO_INTEGRATION when they differ from the defaults)");
|
|
117
|
+
}
|
|
118
|
+
const secret = input.env.NANGO_SECRET_KEY.trim();
|
|
119
|
+
const connectionId = input.env.FOO_TEAMS_NANGO_CONNECTION.trim();
|
|
120
|
+
const integration = input.env.FOO_TEAMS_NANGO_INTEGRATION?.trim() || "microsoft-teams";
|
|
121
|
+
const host = (input.env.NANGO_HOST?.trim() || DEFAULT_NANGO_HOST).replace(/\/+$/, "");
|
|
122
|
+
const url = `${host}/connection/${encodeURIComponent(connectionId)}?provider_config_key=${encodeURIComponent(integration)}`;
|
|
123
|
+
const response = await fetchWithTimeout(input.fetchImpl, url, { method: "GET", headers: { authorization: `Bearer ${secret}`, accept: "application/json" } }, TEAMS_POLICY.fetch_timeout_ms);
|
|
124
|
+
if (response.status === 401 || response.status === 403)
|
|
125
|
+
throw new TeamsRuntimeError("not_connected", "Nango refused the secret key; the Microsoft 365 administrator must reconnect Teams");
|
|
126
|
+
if (response.status === 404)
|
|
127
|
+
throw new TeamsRuntimeError("not_connected", "Nango has no Microsoft Teams connection with that reference; an administrator connects Teams first");
|
|
128
|
+
if (!response.ok)
|
|
129
|
+
throw new TeamsRuntimeError("provider_failure", `Nango answered ${response.status} while resolving the connection`);
|
|
130
|
+
const body = parseJson(await readBounded(response, 256 * 1024), "Nango");
|
|
131
|
+
const credentials = (body.credentials ?? {});
|
|
132
|
+
const type = String(credentials.type ?? "").toUpperCase();
|
|
133
|
+
const ref = opaqueConnectionRef(integration, connectionId);
|
|
134
|
+
if (type !== "OAUTH2")
|
|
135
|
+
throw new TeamsRuntimeError("not_connected", "the tenant-owned Teams connection uses a credential type productfoo does not support");
|
|
136
|
+
const token = String(credentials.access_token ?? "");
|
|
137
|
+
if (!token)
|
|
138
|
+
throw new TeamsRuntimeError("auth_expired", "the tenant-owned connection carries no usable Graph access token; refresh or reconnect");
|
|
139
|
+
const raw = (credentials.raw ?? {});
|
|
140
|
+
const scopeValue = typeof raw.scope === "string" ? raw.scope : typeof credentials.scope === "string" ? credentials.scope : "";
|
|
141
|
+
const scopes = scopeValue.split(/[\s,]+/).filter(Boolean);
|
|
142
|
+
const transcriptScope = scopes.length === 0 ? "unknown" : scopes.includes(TEAMS_TRANSCRIPT_SCOPE) ? "granted" : "missing";
|
|
143
|
+
const metadata = (body.metadata ?? {});
|
|
144
|
+
const tenantDomain = typeof metadata.tenant_domain === "string" && metadata.tenant_domain.trim() ? metadata.tenant_domain.trim().toLowerCase() : null;
|
|
145
|
+
return { connection_ref: ref, broker: "nango", credential: { type: "oauth2", access_token: token }, credential_type: "oauth2", transcript_scope: transcriptScope, tenant_domain: tenantDomain };
|
|
146
|
+
}
|
|
147
|
+
// Graph puts identifiers in the path, so the read whitelist is structural:
|
|
148
|
+
// the meetings listing plus its transcript metadata and content subpaths.
|
|
149
|
+
// Anything else — or any non-GET method — routes through the single
|
|
150
|
+
// write-refusal choke point (R-009).
|
|
151
|
+
const TEAMS_READ_PATH = /^\/v1\.0\/communications\/onlineMeetings(?:\/[^/]+)?(?:\/(?:transcripts(?:\/[^/]+)?(?:\/content)?|attendanceReports))?$/;
|
|
152
|
+
function isReadOnlyOperation(method, pathname) {
|
|
153
|
+
const base = pathname.split("?")[0];
|
|
154
|
+
// A dot segment would normalize the path out of the whitelisted subtree.
|
|
155
|
+
if (base.split("/").some((segment) => segment === "." || segment === ".."))
|
|
156
|
+
return false;
|
|
157
|
+
return method === "GET" && TEAMS_READ_PATH.test(base);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Minimal read-only Microsoft Graph client. Only documented read operations
|
|
161
|
+
* are reachable; any other method or path routes through the single
|
|
162
|
+
* write-refusal choke point (R-009) so the refusal is testable rather than
|
|
163
|
+
* scattered. Graph shapes are T-001-owned beliefs pinned by fixtures.
|
|
164
|
+
*/
|
|
165
|
+
export class TeamsGraphClient {
|
|
166
|
+
credential;
|
|
167
|
+
options;
|
|
168
|
+
requests = { list: 0, transcripts: 0, attendance: 0, transcript_content: 0 };
|
|
169
|
+
lastRequestAt = 0;
|
|
170
|
+
baseUrl;
|
|
171
|
+
sleep;
|
|
172
|
+
now;
|
|
173
|
+
fetchTimeoutMs;
|
|
174
|
+
constructor(credential, options) {
|
|
175
|
+
this.credential = credential;
|
|
176
|
+
this.options = options;
|
|
177
|
+
this.baseUrl = (options.baseUrl ?? "https://graph.microsoft.com").replace(/\/+$/, "");
|
|
178
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
179
|
+
this.now = options.now ?? (() => Date.now());
|
|
180
|
+
this.fetchTimeoutMs = options.fetchTimeoutMs ?? TEAMS_POLICY.fetch_timeout_ms;
|
|
181
|
+
}
|
|
182
|
+
async request(method, pathname, maxBytes = TEAMS_POLICY.max_response_bytes) {
|
|
183
|
+
if (!isReadOnlyOperation(method, pathname))
|
|
184
|
+
refuseSourceWrite(`microsoft-teams ${method} ${pathname}`);
|
|
185
|
+
const wait = this.lastRequestAt + TEAMS_POLICY.min_request_interval_ms - this.now();
|
|
186
|
+
if (wait > 0)
|
|
187
|
+
await this.sleep(wait);
|
|
188
|
+
this.lastRequestAt = this.now();
|
|
189
|
+
const response = await fetchWithTimeout(this.options.fetchImpl, `${this.baseUrl}${pathname}`, { method, headers: { authorization: `Bearer ${this.credential.access_token}`, accept: "application/json" } }, this.fetchTimeoutMs);
|
|
190
|
+
if (response.status === 401)
|
|
191
|
+
throw new TeamsRuntimeError("auth_expired", "Microsoft Graph rejected the credential; the Microsoft 365 administrator must refresh or reconnect");
|
|
192
|
+
if (response.status === 403)
|
|
193
|
+
throw new TeamsRuntimeError("access_denied", "Microsoft Graph denied this read; the tenant credential lacks the required permission");
|
|
194
|
+
if (response.status === 429)
|
|
195
|
+
throw new TeamsRuntimeError("budget_exhausted", "Graph throttling limit reached; no retry was attempted");
|
|
196
|
+
if (response.status === 404)
|
|
197
|
+
throw new TeamsRuntimeError("provider_failure", "Graph reported the requested record does not exist");
|
|
198
|
+
if (!response.ok)
|
|
199
|
+
throw new TeamsRuntimeError("provider_failure", `Microsoft Graph answered ${response.status}`);
|
|
200
|
+
return parseJson(await readBounded(response, maxBytes), "Microsoft Graph");
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Bounded metadata listing: one time window, a page ceiling, no content.
|
|
204
|
+
* Follows Graph's `@odata.nextLink` paging; stops closed at the policy
|
|
205
|
+
* ceiling instead of widening the read (R-004/R-010).
|
|
206
|
+
*/
|
|
207
|
+
async listMeetings(window) {
|
|
208
|
+
const meetings = [];
|
|
209
|
+
let next = `/v1.0/communications/onlineMeetings?startDateTime=${encodeURIComponent(window.from)}&endDateTime=${encodeURIComponent(window.to)}`;
|
|
210
|
+
let pages = 0;
|
|
211
|
+
for (;;) {
|
|
212
|
+
if (pages >= TEAMS_POLICY.max_list_pages)
|
|
213
|
+
return { meetings, pages, truncated: true };
|
|
214
|
+
this.requests.list += 1;
|
|
215
|
+
const body = await this.request("GET", next, 4 * 1024 * 1024);
|
|
216
|
+
pages += 1;
|
|
217
|
+
for (const entry of Array.isArray(body.value) ? body.value : []) {
|
|
218
|
+
if (!entry || typeof entry.id !== "string")
|
|
219
|
+
continue;
|
|
220
|
+
const organizer = (entry.organizer ?? null);
|
|
221
|
+
const info = (organizer?.information ?? null);
|
|
222
|
+
meetings.push({
|
|
223
|
+
id: entry.id,
|
|
224
|
+
subject: typeof entry.subject === "string" ? entry.subject : undefined,
|
|
225
|
+
start: typeof entry.startDateTime === "string" ? entry.startDateTime : undefined,
|
|
226
|
+
end: typeof entry.endDateTime === "string" ? entry.endDateTime : undefined,
|
|
227
|
+
state: typeof entry.state === "string" ? entry.state : undefined,
|
|
228
|
+
organizer: typeof info?.displayName === "string" ? info.displayName : undefined,
|
|
229
|
+
url: typeof entry.joinWebUrl === "string" ? entry.joinWebUrl : undefined,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const link = typeof body["@odata.nextLink"] === "string" ? body["@odata.nextLink"] : undefined;
|
|
233
|
+
if (!link)
|
|
234
|
+
return { meetings, pages, truncated: false };
|
|
235
|
+
const parsed = new URL(link);
|
|
236
|
+
next = `${parsed.pathname}${parsed.search}`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Traversal probes never reach the provider, encoded or not: identifiers
|
|
240
|
+
// are single opaque segments.
|
|
241
|
+
static wellFormedId(value, label) {
|
|
242
|
+
let decoded = value;
|
|
243
|
+
try {
|
|
244
|
+
decoded = decodeURIComponent(value);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
throw new TeamsRuntimeError("provider_failure", `a single well-formed ${label} identifier is required`);
|
|
248
|
+
}
|
|
249
|
+
if (!value || /\s/.test(value) || decoded.includes("/") || decoded.includes("\\") || /(^|\/)\.\.?(\/|$)/.test(decoded)) {
|
|
250
|
+
throw new TeamsRuntimeError("provider_failure", `a single well-formed ${label} identifier is required`);
|
|
251
|
+
}
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
/** Transcript metadata for one meeting — never content. */
|
|
255
|
+
async meetingTranscripts(meetingId) {
|
|
256
|
+
TeamsGraphClient.wellFormedId(meetingId, "meeting");
|
|
257
|
+
this.requests.transcripts += 1;
|
|
258
|
+
const body = await this.request("GET", `/v1.0/communications/onlineMeetings/${encodeURIComponent(meetingId)}/transcripts`, 1024 * 1024);
|
|
259
|
+
return (Array.isArray(body.value) ? body.value : [])
|
|
260
|
+
.filter((entry) => typeof entry.id === "string")
|
|
261
|
+
.map((entry) => ({ id: entry.id }));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Attendee display names and emails for one meeting, from its attendance
|
|
265
|
+
* report (a T-001-owned belief pinned by fixtures; the Teams analog of the
|
|
266
|
+
* gong parties read). Names feed the adapter's privacy-minimized
|
|
267
|
+
* participants; emails die into domain-only company hints in the adapter.
|
|
268
|
+
* A missing report is observed absence (an empty list), not an error.
|
|
269
|
+
*/
|
|
270
|
+
async meetingAttendance(meetingId) {
|
|
271
|
+
TeamsGraphClient.wellFormedId(meetingId, "meeting");
|
|
272
|
+
this.requests.attendance += 1;
|
|
273
|
+
const body = await this.request("GET", `/v1.0/communications/onlineMeetings/${encodeURIComponent(meetingId)}/attendanceReports`, 1024 * 1024);
|
|
274
|
+
const records = [];
|
|
275
|
+
for (const report of Array.isArray(body.value) ? body.value : []) {
|
|
276
|
+
for (const record of Array.isArray(report.attendanceRecords) ? report.attendanceRecords : []) {
|
|
277
|
+
const address = (record.emailAddress ?? null);
|
|
278
|
+
const display_name = typeof address?.name === "string" && address.name.trim() ? address.name.trim() : undefined;
|
|
279
|
+
const email = typeof address?.address === "string" && address.address.trim() ? address.address.trim() : undefined;
|
|
280
|
+
if (!display_name && !email)
|
|
281
|
+
continue;
|
|
282
|
+
if (!records.some((entry) => entry.display_name === display_name && entry.email === email))
|
|
283
|
+
records.push({ ...(display_name ? { display_name } : {}), ...(email ? { email } : {}) });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return records;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Exactly one transcript content read (R-007): the caller passes one
|
|
290
|
+
* meeting id and one transcript id, and no request ever carries more. The
|
|
291
|
+
* content returns bounded into process memory; nothing is written.
|
|
292
|
+
*/
|
|
293
|
+
async transcriptContent(meetingId, transcriptId) {
|
|
294
|
+
TeamsGraphClient.wellFormedId(meetingId, "meeting");
|
|
295
|
+
TeamsGraphClient.wellFormedId(transcriptId, "transcript");
|
|
296
|
+
this.requests.transcript_content += 1;
|
|
297
|
+
const body = await this.request("GET", `/v1.0/communications/onlineMeetings/${encodeURIComponent(meetingId)}/transcripts/${encodeURIComponent(transcriptId)}/content`, TEAMS_POLICY.max_response_bytes);
|
|
298
|
+
const content = body.content ?? body.value ?? body;
|
|
299
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function credentialsHome(env?: NodeJS.ProcessEnv): string;
|
|
2
|
+
export declare function credentialsPath(env?: NodeJS.ProcessEnv): string;
|
|
3
|
+
export declare function readStoredFirecrawlKey(env?: NodeJS.ProcessEnv): string | null;
|
|
4
|
+
export declare function writeStoredFirecrawlKey(key: string, env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
export declare function clearStoredFirecrawlKey(env?: NodeJS.ProcessEnv): boolean;
|
|
6
|
+
export type TokenSource = "env" | "stored" | "none";
|
|
7
|
+
export declare function resolveFirecrawlKey(env?: NodeJS.ProcessEnv): {
|
|
8
|
+
key: string | null;
|
|
9
|
+
source: TokenSource;
|
|
10
|
+
};
|