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,551 @@
|
|
|
1
|
+
// `foo discovery fireflies …` — the local worker for a per-user Fireflies
|
|
2
|
+
// connection (fireflies-discovery-connector-v0, D-001 keyed direct client
|
|
3
|
+
// under the gong worker contract).
|
|
4
|
+
//
|
|
5
|
+
// The gong worker is the process template (R-001): list bounded transcript
|
|
6
|
+
// metadata inside an approved study or inbox scope, normalize it through the
|
|
7
|
+
// Fireflies adapter, hand the closed session shape to `foo discovery match`,
|
|
8
|
+
// open exactly one proof-authorized transcript into process memory, reduce it
|
|
9
|
+
// to the sanitized envelope in one model request, and apply locally. The
|
|
10
|
+
// transcript never reaches argv, stdin, a file, Git, a log, an error, or the
|
|
11
|
+
// host agent's context; every surface this module writes is content-free.
|
|
12
|
+
//
|
|
13
|
+
// What differs from gong, honestly: the credential is the user's own Fireflies
|
|
14
|
+
// API key (connection_owner "user", credential_broker "direct" — R-010/D-002),
|
|
15
|
+
// not a Nango-brokered company connection; there is no administrator over a
|
|
16
|
+
// personal key, so member isolation is a recorded gap, not a provided control.
|
|
17
|
+
//
|
|
18
|
+
// Nothing here claims live Fireflies access. Fireflies stays contract_only
|
|
19
|
+
// until the feasibility task (T-001) and the dogfood run (T-008) close with
|
|
20
|
+
// receipts; the provider API shapes the client pins are beliefs owned by
|
|
21
|
+
// T-001, held up by fixtures only.
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { hasFlag } from "./cliArgs.js";
|
|
25
|
+
import { loadDiscoveryRun } from "./discoveryArtifacts.js";
|
|
26
|
+
import { discoveryTargetDir, inboxScope, isInboxTarget, localRoot, localTargetName, runDiscoveryCommand, } from "./discoveryCommand.js";
|
|
27
|
+
import { openAuthorizedTranscript } from "./discoveryProof.js";
|
|
28
|
+
import { firefliesDiscoveryAdapter, firefliesInternalOnly } from "./discoveryProviders.js";
|
|
29
|
+
import { planDiscoverySync } from "./discoverySync.js";
|
|
30
|
+
import { anthropicSynthesisTransport, defaultSynthesisModel, synthesisCredentialPresent, synthesizeDiscoveryEnvelope } from "./discoverySynthesis.js";
|
|
31
|
+
import { firefliesConnectionReady } from "./sourceIngress.js";
|
|
32
|
+
import { FirefliesClient, FirefliesRuntimeError, firefliesConnectionConfigured, firefliesCredentialBroker, resolveFirefliesConnection, FIREFLIES_POLICY } from "./firefliesClient.js";
|
|
33
|
+
const MATCHABLE_STUDY_STATES = new Set(["awaiting_match", "ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof"]);
|
|
34
|
+
const DEFAULT_LOOKBACK_DAYS = 14;
|
|
35
|
+
function result(status, options = {}) {
|
|
36
|
+
return { schema_version: "0.1", status, writes: [], errors: [], handoff: null, next_actions: [], coverage: {}, ...options };
|
|
37
|
+
}
|
|
38
|
+
function exitCodeFor(status) {
|
|
39
|
+
return ["ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof", "invalid", "budget_exhausted"].includes(status) ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
function outcome(value, json) {
|
|
42
|
+
const exitCode = exitCodeFor(value.status);
|
|
43
|
+
if (json)
|
|
44
|
+
return { output: `${JSON.stringify(value, null, 2)}\n`, exitCode, result: value };
|
|
45
|
+
const lines = [`Discovery (fireflies): ${value.status}.`];
|
|
46
|
+
for (const error of value.errors)
|
|
47
|
+
lines.push(`- ${error.message}`);
|
|
48
|
+
if (value.handoff)
|
|
49
|
+
lines.push(`Handoff: ${value.handoff}`);
|
|
50
|
+
for (const action of value.next_actions)
|
|
51
|
+
lines.push(`Next: ${action}`);
|
|
52
|
+
return { output: `${lines.join("\n")}\n`, exitCode, result: value };
|
|
53
|
+
}
|
|
54
|
+
function invalid(message, json, code = "E_DISCOVERY_VALIDATION", targetId, coverage = {}) {
|
|
55
|
+
return outcome(result("invalid", { study_id: targetId, errors: [{ code, message }], coverage }), json);
|
|
56
|
+
}
|
|
57
|
+
// Typed provider failures map to the discovery command's closed status set.
|
|
58
|
+
// Messages are productfoo's own words: no provider body, id, or credential.
|
|
59
|
+
function fromRuntimeError(error, json, targetId, coverage) {
|
|
60
|
+
const base = { study_id: targetId, coverage: { ...coverage, provider_error: error.code, mode: FIREFLIES_MODE } };
|
|
61
|
+
switch (error.code) {
|
|
62
|
+
case "not_connected":
|
|
63
|
+
return outcome(result("needs_provider_connector", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_CONNECTOR", message: error.message }], handoff: "Connect your own Fireflies account by setting FOO_FIREFLIES_API_KEY to your Fireflies API key for this runtime; a Fireflies connection covers only your own meetings." }), json);
|
|
64
|
+
case "auth_expired":
|
|
65
|
+
case "access_denied":
|
|
66
|
+
return outcome(result("needs_provider_access", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: error.message }], handoff: "Check that the Fireflies API key is valid and its access grants include the meeting, then rerun." }), json);
|
|
67
|
+
case "budget_exhausted":
|
|
68
|
+
// A rate-limit stop is the quota half of R-004: the run ends, nothing
|
|
69
|
+
// widens, nothing retries. The next run needs a narrower window.
|
|
70
|
+
return outcome(result("budget_exhausted", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: "The agreed page or quota ceiling stopped this run; narrow the window or selectors and rerun — productfoo never widens the window or auto-retries." }), json);
|
|
71
|
+
default:
|
|
72
|
+
return outcome(result("invalid", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: error.code === "transcript_too_large" || error.code === "provider_timeout" ? "Nothing was written and no retry was attempted; a new attempt needs a fresh match." : null }), json);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function isoDaysAgo(now, days) {
|
|
76
|
+
return new Date(Date.parse(now) - days * 86_400_000).toISOString();
|
|
77
|
+
}
|
|
78
|
+
function loadTarget(root, targetId, env, now, phase = "list") {
|
|
79
|
+
if (isInboxTarget(targetId)) {
|
|
80
|
+
const scope = inboxScope(root, targetId);
|
|
81
|
+
if (scope.provider.toLowerCase() !== "fireflies")
|
|
82
|
+
throw new Error("this inbox scope is not a Fireflies scope");
|
|
83
|
+
if (scope.state !== "approved")
|
|
84
|
+
throw new Error("this inbox scope is not approved");
|
|
85
|
+
// R-004: validation is pre-flight — an invalid scope never reaches the
|
|
86
|
+
// provider.
|
|
87
|
+
const scopeError = firefliesScopeValidationError(firefliesSelectorsFromInboxScope(scope));
|
|
88
|
+
if (scopeError)
|
|
89
|
+
throw new Error(scopeError);
|
|
90
|
+
const from = new Date(scope.time_window.start);
|
|
91
|
+
const to = new Date(scope.time_window.end);
|
|
92
|
+
return { target_id: targetId, lane: "unassigned_call_evidence", dir: discoveryTargetDir(root, targetId), connection_alias: scope.connection_alias, window: { from: from.toISOString(), to: to.toISOString() } };
|
|
93
|
+
}
|
|
94
|
+
const dir = discoveryTargetDir(root, targetId);
|
|
95
|
+
const run = loadDiscoveryRun(dir);
|
|
96
|
+
if (run.source.provider.toLowerCase() !== "fireflies")
|
|
97
|
+
throw new Error("this study is not a Fireflies study");
|
|
98
|
+
// Listing needs a matchable study; processing needs the state `match` left
|
|
99
|
+
// behind (ready_to_synthesize) — the proof check decides the rest.
|
|
100
|
+
const allowed = phase === "process" ? run.state === "ready_to_synthesize" || MATCHABLE_STUDY_STATES.has(run.state) : MATCHABLE_STUDY_STATES.has(run.state);
|
|
101
|
+
if (!allowed)
|
|
102
|
+
throw new Error(`study ${targetId} is not ready for provider reads (state ${run.state})`);
|
|
103
|
+
let from;
|
|
104
|
+
let to;
|
|
105
|
+
if (run.match.expected_local_date) {
|
|
106
|
+
const day = Date.parse(`${run.match.expected_local_date}T00:00:00Z`);
|
|
107
|
+
if (!Number.isFinite(day))
|
|
108
|
+
throw new Error("the study's expected date is invalid");
|
|
109
|
+
from = new Date(day - 86_400_000).toISOString();
|
|
110
|
+
to = new Date(day + 2 * 86_400_000).toISOString();
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const lookback = Number(env.FOO_FIREFLIES_LOOKBACK_DAYS ?? DEFAULT_LOOKBACK_DAYS);
|
|
114
|
+
if (!Number.isFinite(lookback) || lookback <= 0 || lookback > 90)
|
|
115
|
+
throw new Error("FOO_FIREFLIES_LOOKBACK_DAYS must be between 1 and 90");
|
|
116
|
+
from = isoDaysAgo(now, lookback);
|
|
117
|
+
to = now;
|
|
118
|
+
}
|
|
119
|
+
// R-004 applies to study scopes too: a fireflies study names its customer —
|
|
120
|
+
// the study target is the named external participant or company, and the
|
|
121
|
+
// match constraints narrow further. A study with neither is invalid.
|
|
122
|
+
const selectorError = firefliesScopeValidationError({
|
|
123
|
+
window: { start: from, end: to },
|
|
124
|
+
external_names: [...(run.match?.required_participants ?? []), ...(run.target?.name?.trim() ? [run.target.name.trim()] : [])],
|
|
125
|
+
external_domains: run.target?.organization?.trim() ? [run.target.organization.trim()] : [],
|
|
126
|
+
title_tokens: run.match?.required_title_tokens ?? [],
|
|
127
|
+
});
|
|
128
|
+
if (selectorError)
|
|
129
|
+
throw new Error(selectorError);
|
|
130
|
+
return { target_id: targetId, lane: "study_linked", dir, connection_alias: run.source.connection_alias, window: { from, to } };
|
|
131
|
+
}
|
|
132
|
+
// R-005: the canonical Fireflies link is path-based, built from the transcript
|
|
133
|
+
// id under the registry-allowlisted host; the adapter re-validates every link
|
|
134
|
+
// through the shared sanitizer, so a malformed id degrades to no link.
|
|
135
|
+
function firefliesLink(id) {
|
|
136
|
+
return `https://app.fireflies.ai/transcript/${id}`;
|
|
137
|
+
}
|
|
138
|
+
// The connection and the session record describe the same fact with two
|
|
139
|
+
// vocabularies: a connection that has not yet read a transcript is "unknown"
|
|
140
|
+
// at the connection level and "missing" (not observed) at the record level.
|
|
141
|
+
// Only an observed successful read upgrades either to "granted" (R-005).
|
|
142
|
+
function observedRecordAccess(connection) {
|
|
143
|
+
return connection.transcript_access === "granted" ? "granted" : "missing";
|
|
144
|
+
}
|
|
145
|
+
function toRecord(listed, connection) {
|
|
146
|
+
if (!listed.started_at)
|
|
147
|
+
return null;
|
|
148
|
+
return {
|
|
149
|
+
id: listed.id,
|
|
150
|
+
title: listed.title?.trim() || "Untitled transcript",
|
|
151
|
+
started_at: listed.started_at,
|
|
152
|
+
ended: listed.ended,
|
|
153
|
+
status: listed.status,
|
|
154
|
+
participants: listed.participants ?? [],
|
|
155
|
+
url: firefliesLink(listed.id),
|
|
156
|
+
transcript_access: observedRecordAccess(connection),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function connect(deps) {
|
|
160
|
+
const connection = resolveFirefliesConnection({ env: deps.env });
|
|
161
|
+
const client = new FirefliesClient(connection.credential, { fetchImpl: deps.fetchImpl, baseUrl: deps.env.FOO_FIREFLIES_API_BASE?.trim() || undefined, sleep: deps.sleep });
|
|
162
|
+
// R-005's observed grant, keyed-direct edition: a personal Fireflies key has
|
|
163
|
+
// no scope system, so the one observable fact is the presented key answering
|
|
164
|
+
// for its owner. One identity query observes it per invocation (gong observes
|
|
165
|
+
// its grant the same way, via OAuth-scope introspection at resolve time).
|
|
166
|
+
// A transcript read that still fails maps to needs_provider_access — the
|
|
167
|
+
// claim is never faked, only grounded where the provider allows.
|
|
168
|
+
await client.probe();
|
|
169
|
+
connection.transcript_access = "granted";
|
|
170
|
+
return { connection, client };
|
|
171
|
+
}
|
|
172
|
+
async function listSessions(target, connection, client) {
|
|
173
|
+
const listing = await client.listTranscripts({ from: target.window.from, to: target.window.to });
|
|
174
|
+
// A ceiling stop is final for the run (R-004/R-010): no further provider
|
|
175
|
+
// reads happen — the run never waits for a wider window.
|
|
176
|
+
if (listing.truncated)
|
|
177
|
+
return { sessions: [], listed: listing.transcripts.length, pages: listing.pages, truncated: true, internal_only_excluded: 0, incomplete: 0 };
|
|
178
|
+
const sessions = [];
|
|
179
|
+
let internalExcluded = 0;
|
|
180
|
+
let incomplete = 0;
|
|
181
|
+
for (const entry of listing.transcripts) {
|
|
182
|
+
const provisional = { id: entry.id, title: entry.title?.trim() || "Untitled transcript", started_at: entry.started_at ?? "", participants: entry.participants ?? [], url: firefliesLink(entry.id), transcript_access: observedRecordAccess(connection) };
|
|
183
|
+
if (firefliesInternalOnly(provisional)) {
|
|
184
|
+
internalExcluded += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const record = toRecord(entry, connection);
|
|
188
|
+
if (!record) {
|
|
189
|
+
incomplete += 1;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const session = firefliesDiscoveryAdapter.normalizeSession(record, target.connection_alias);
|
|
193
|
+
if (session.completion_state !== "completed") {
|
|
194
|
+
incomplete += 1;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Transcript access is observed, never assumed (R-005): connect() watched
|
|
198
|
+
// the presented key answer for its owner, so the session reports available
|
|
199
|
+
// access; until that observation the session would honestly say unknown.
|
|
200
|
+
sessions.push({ ...session, access: connection.transcript_access === "granted" ? "available" : "unknown" });
|
|
201
|
+
}
|
|
202
|
+
return { sessions, listed: listing.transcripts.length, pages: listing.pages, truncated: listing.truncated, internal_only_excluded: internalExcluded, incomplete };
|
|
203
|
+
}
|
|
204
|
+
async function readyCommand(root, json, deps) {
|
|
205
|
+
const env = deps.env;
|
|
206
|
+
const coverage = {
|
|
207
|
+
provider: "fireflies",
|
|
208
|
+
mode: FIREFLIES_MODE,
|
|
209
|
+
support_state: FIREFLIES_SUPPORT_STATE,
|
|
210
|
+
// R-010/D-002: the connection is the user's own key; there is no
|
|
211
|
+
// administrator over it, and member isolation is not provided.
|
|
212
|
+
connection_owner: "user",
|
|
213
|
+
configured: firefliesConnectionConfigured(env),
|
|
214
|
+
credential_broker: firefliesCredentialBroker(env),
|
|
215
|
+
synthesis: { available: synthesisCredentialPresent(env), model: defaultSynthesisModel(env) },
|
|
216
|
+
policy: { ...FIREFLIES_POLICY },
|
|
217
|
+
};
|
|
218
|
+
try {
|
|
219
|
+
const { connection, client } = await connect(deps);
|
|
220
|
+
// connect() already ran the one identity probe that observes the grant
|
|
221
|
+
// (R-005); readiness adds no further provider read.
|
|
222
|
+
coverage.connection = connection.connection_ref;
|
|
223
|
+
coverage.credential_broker = connection.broker;
|
|
224
|
+
coverage.capabilities = { list_sessions: true, open_transcript: connection.transcript_access === "granted", stable_link: true, timestamps: true, transcript_access: connection.transcript_access };
|
|
225
|
+
coverage.provider_calls = client.requests;
|
|
226
|
+
coverage.readiness = firefliesConnectionReady({ connection_ref: connection.connection_ref });
|
|
227
|
+
return outcome(result("ok", { coverage, next_actions: [
|
|
228
|
+
"A personal Fireflies key sees only its owner's meetings: there is no administrator over it, and productfoo does not provide member isolation between users (OQ-001 workspace-owned access stays open).",
|
|
229
|
+
"Approve a bounded Fireflies scope, then run foo discovery fireflies list <study-id|inbox:scope-id>.",
|
|
230
|
+
...(coverage.synthesis && !coverage.synthesis.available ? ["Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) before processing a transcript."] : []),
|
|
231
|
+
] }), json);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (error instanceof FirefliesRuntimeError)
|
|
235
|
+
return fromRuntimeError(error, json, undefined, coverage);
|
|
236
|
+
return invalid("Fireflies readiness failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", undefined, coverage);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function listCommand(root, targetId, json, deps) {
|
|
240
|
+
if (!targetId)
|
|
241
|
+
return invalid("Usage: foo discovery fireflies list <study-id|inbox:scope-id>", json);
|
|
242
|
+
const now = deps.now();
|
|
243
|
+
let target;
|
|
244
|
+
try {
|
|
245
|
+
target = loadTarget(root, targetId, deps.env, now);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
249
|
+
}
|
|
250
|
+
const coverage = { mode: FIREFLIES_MODE, window: target.window, lane: target.lane, transcript_opened: false };
|
|
251
|
+
try {
|
|
252
|
+
const { connection, client } = await connect(deps);
|
|
253
|
+
coverage.connection = connection.connection_ref;
|
|
254
|
+
const listed = await listSessions(target, connection, client);
|
|
255
|
+
Object.assign(coverage, { transcripts_listed: listed.listed, pages: listed.pages, truncated: listed.truncated, internal_only_excluded: listed.internal_only_excluded, incomplete_excluded: listed.incomplete, candidates_evaluated: listed.sessions.length, provider_calls: client.requests });
|
|
256
|
+
if (listed.truncated) {
|
|
257
|
+
return outcome(result("budget_exhausted", {
|
|
258
|
+
study_id: targetId,
|
|
259
|
+
coverage: { ...coverage, provider_error: "budget_exhausted" },
|
|
260
|
+
errors: [{ code: "E_DISCOVERY_VALIDATION", message: `The window holds more transcripts than the ${FIREFLIES_POLICY.max_list_pages}-page ceiling; narrow the window or selectors instead of widening the read.` }],
|
|
261
|
+
handoff: "Narrow the approved window or selectors and list again; the run never widens the window and never retries.",
|
|
262
|
+
}), json);
|
|
263
|
+
}
|
|
264
|
+
if (listed.sessions.length === 0)
|
|
265
|
+
return outcome(result("awaiting_match", { study_id: targetId, coverage, next_actions: ["No completed transcript with an external party was listed inside the approved window."] }), json);
|
|
266
|
+
if (target.lane === "study_linked") {
|
|
267
|
+
const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify(listed.sessions), now);
|
|
268
|
+
return outcome({ ...matched.result, coverage: { ...matched.result.coverage, ...coverage }, next_actions: [...matched.result.next_actions, ...(matched.result.status === "ok" ? [`Run foo discovery fireflies process ${targetId} to open the one matched transcript and apply the sanitized evidence.`] : [])] }, json);
|
|
269
|
+
}
|
|
270
|
+
// Inbox: one session at a time so each source gets its own one-time proof.
|
|
271
|
+
let lastMiss = null;
|
|
272
|
+
let evaluated = 0;
|
|
273
|
+
for (const session of listed.sessions) {
|
|
274
|
+
evaluated += 1;
|
|
275
|
+
const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify([session]), now);
|
|
276
|
+
if (matched.result.status === "awaiting_match") {
|
|
277
|
+
lastMiss = matched.result;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const remaining = listed.sessions.length - evaluated;
|
|
281
|
+
return outcome({
|
|
282
|
+
...matched.result,
|
|
283
|
+
coverage: { ...matched.result.coverage, ...coverage, candidates_evaluated: evaluated, candidates_remaining: remaining },
|
|
284
|
+
next_actions: [
|
|
285
|
+
...matched.result.next_actions,
|
|
286
|
+
...(matched.result.status === "ok" ? [`Run foo discovery fireflies process ${targetId} to open this one transcript and apply the sanitized evidence.`] : []),
|
|
287
|
+
...(remaining > 0 ? [`${remaining} more listed transcript(s) were not evaluated; run list again after this one is processed.`] : []),
|
|
288
|
+
],
|
|
289
|
+
}, json);
|
|
290
|
+
}
|
|
291
|
+
return outcome(result("awaiting_match", { study_id: targetId, coverage: { ...coverage, candidates_evaluated: evaluated }, next_actions: lastMiss?.next_actions ?? ["No listed transcript matched the approved scope; nothing was opened."] }), json);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
if (error instanceof FirefliesRuntimeError)
|
|
295
|
+
return fromRuntimeError(error, json, targetId, coverage);
|
|
296
|
+
return invalid("Fireflies listing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function readMatch(root, targetId) {
|
|
300
|
+
const file = path.join(localRoot(root), localTargetName(targetId), "match.json");
|
|
301
|
+
if (!existsSync(file))
|
|
302
|
+
return null;
|
|
303
|
+
try {
|
|
304
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function studyHypotheses(dir) {
|
|
311
|
+
const file = path.join(dir, "study.md");
|
|
312
|
+
if (!existsSync(file))
|
|
313
|
+
return [];
|
|
314
|
+
const out = [];
|
|
315
|
+
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
316
|
+
const match = /^\s*(?:[-*]|\d+\.)?\s*\**(H-\d{3})\**\s*[:—–-]?\s*(.+?)\s*$/.exec(line);
|
|
317
|
+
if (match && !out.some((entry) => entry.id === match[1]))
|
|
318
|
+
out.push({ id: match[1], text: match[2] });
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
function mmss(ms) {
|
|
323
|
+
const total = Math.max(0, Math.floor((ms ?? 0) / 1000));
|
|
324
|
+
return `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
|
|
325
|
+
}
|
|
326
|
+
// Neutral speaker ordinals, never names: Fireflies metadata carries no
|
|
327
|
+
// affiliation (OQ-002), so the render cannot honestly say Customer or Company
|
|
328
|
+
// representative — and keeping names out of the model text is stricter than
|
|
329
|
+
// gong's role labels. Timestamps render relative to the first sentence when
|
|
330
|
+
// the provider supplies them.
|
|
331
|
+
function renderFirefliesTranscript(content) {
|
|
332
|
+
const starts = content.sentences?.map((sentence) => Date.parse(sentence.start_date ?? "")).filter((value) => Number.isFinite(value)) ?? [];
|
|
333
|
+
const first = starts.length ? Math.min(...starts) : null;
|
|
334
|
+
const lines = [];
|
|
335
|
+
const ordinals = new Map();
|
|
336
|
+
for (const sentence of content.sentences ?? []) {
|
|
337
|
+
let label = "Speaker";
|
|
338
|
+
if (sentence.speaker_id) {
|
|
339
|
+
if (!ordinals.has(sentence.speaker_id))
|
|
340
|
+
ordinals.set(sentence.speaker_id, `Speaker ${ordinals.size + 1}`);
|
|
341
|
+
label = ordinals.get(sentence.speaker_id);
|
|
342
|
+
}
|
|
343
|
+
const start = Date.parse(sentence.start_date ?? "");
|
|
344
|
+
const offset = first !== null && Number.isFinite(start) ? mmss(start - first) : "00:00";
|
|
345
|
+
lines.push(`[${offset}] ${label}: ${sentence.text}`);
|
|
346
|
+
}
|
|
347
|
+
return lines.join("\n");
|
|
348
|
+
}
|
|
349
|
+
function containsIdentity(envelopeText, identities) {
|
|
350
|
+
const haystack = envelopeText.toLowerCase();
|
|
351
|
+
return identities.some((identity) => identity.length >= 4 && haystack.includes(identity.toLowerCase()));
|
|
352
|
+
}
|
|
353
|
+
async function processCommand(root, targetId, json, deps) {
|
|
354
|
+
if (!targetId)
|
|
355
|
+
return invalid("Usage: foo discovery fireflies process <study-id|inbox:scope-id>", json);
|
|
356
|
+
const now = deps.now();
|
|
357
|
+
let target;
|
|
358
|
+
try {
|
|
359
|
+
target = loadTarget(root, targetId, deps.env, now, "process");
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
363
|
+
}
|
|
364
|
+
const match = readMatch(root, targetId);
|
|
365
|
+
if (!match || !Array.isArray(match.candidates) || match.candidates.length === 0)
|
|
366
|
+
return invalid("No matched Fireflies transcript is recorded for this target; run foo discovery fireflies list first.", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
367
|
+
if (match.candidates.length > 1)
|
|
368
|
+
return outcome(result("ambiguous_match", { study_id: targetId, errors: [{ code: "E_DISCOVERY_AMBIGUOUS_MATCH", message: "Several transcripts matched; a person must select one before any transcript is opened." }], handoff: `foo discovery review ${targetId} select-source --fingerprint <sha256> --by <person>` }), json);
|
|
369
|
+
const candidate = match.candidates[0];
|
|
370
|
+
const session = candidate.session;
|
|
371
|
+
const transcriptId = session.runtime_handle;
|
|
372
|
+
if (!synthesisCredentialPresent(deps.env))
|
|
373
|
+
return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "No model credential is available for server-side synthesis, so the transcript was not opened." }], handoff: "Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) for this runtime, then rerun process." }), json);
|
|
374
|
+
const model = defaultSynthesisModel(deps.env);
|
|
375
|
+
const coverage = { mode: FIREFLIES_MODE, lane: target.lane, policy_version: FIREFLIES_POLICY.policy_version, model };
|
|
376
|
+
const hypotheses = target.lane === "study_linked" ? studyHypotheses(target.dir) : [];
|
|
377
|
+
if (target.lane === "study_linked" && hypotheses.length === 0)
|
|
378
|
+
return invalid("The study records no H-### hypotheses in study.md; study-linked evidence needs one to test.", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
379
|
+
let client;
|
|
380
|
+
let connection;
|
|
381
|
+
try {
|
|
382
|
+
const connected = await connect(deps);
|
|
383
|
+
client = connected.client;
|
|
384
|
+
connection = connected.connection;
|
|
385
|
+
coverage.connection = connection.connection_ref;
|
|
386
|
+
// Identities for the post-synthesis leak screen: the names and domain
|
|
387
|
+
// labels the provider's own metadata carries (raw emails are long gone —
|
|
388
|
+
// the adapter stripped them).
|
|
389
|
+
const identities = [...(session.participants ?? [])];
|
|
390
|
+
let openError;
|
|
391
|
+
let transcriptText;
|
|
392
|
+
try {
|
|
393
|
+
const content = await openAuthorizedTranscript({ study_dir: target.dir, local_root: localRoot(root), study_id: targetId, source_fingerprint: candidate.source_fingerprint, now }, async () => {
|
|
394
|
+
try {
|
|
395
|
+
return await client.transcript(transcriptId);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
if (error instanceof FirefliesRuntimeError)
|
|
399
|
+
openError = error;
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
// Observed, never assumed (R-005): the connection just read a
|
|
404
|
+
// transcript, so transcript access is now a fact of this connection.
|
|
405
|
+
connection.transcript_access = "granted";
|
|
406
|
+
for (const speaker of content.speakers ?? []) {
|
|
407
|
+
if (speaker.name?.trim())
|
|
408
|
+
identities.push(speaker.name.trim());
|
|
409
|
+
}
|
|
410
|
+
transcriptText = renderFirefliesTranscript(content);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
if (openError)
|
|
414
|
+
return fromRuntimeError(openError, json, targetId, { ...coverage, transcript_body_requests: client.requests.transcript });
|
|
415
|
+
return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "The transcript was not opened: the discovery proof is missing, expired, already used, or no longer matches the approval." }], handoff: `Run foo discovery fireflies list ${targetId} again to re-match and mint a fresh proof.`, coverage }), json);
|
|
416
|
+
}
|
|
417
|
+
coverage.transcript_body_requests = client.requests.transcript;
|
|
418
|
+
const estimatedTokens = Math.ceil(transcriptText.length / 4);
|
|
419
|
+
if (estimatedTokens > FIREFLIES_POLICY.max_model_input_tokens) {
|
|
420
|
+
transcriptText = null;
|
|
421
|
+
return fromRuntimeError(new FirefliesRuntimeError("transcript_too_large", "the transcript exceeds the model-input ceiling; nothing was synthesized"), json, targetId, coverage);
|
|
422
|
+
}
|
|
423
|
+
// OQ-002 is stated inside the envelope, not silently dropped: the model
|
|
424
|
+
// sees neutral speaker ordinals because Fireflies carries no affiliation.
|
|
425
|
+
const caveats = [
|
|
426
|
+
"Speaker roles were not attributed: Fireflies metadata carries no affiliation (OQ-002), so speakers are neutral ordinals.",
|
|
427
|
+
...(session.stable_url ? [] : ["The provider returned no canonical transcript link for this transcript."]),
|
|
428
|
+
];
|
|
429
|
+
let synthesized;
|
|
430
|
+
try {
|
|
431
|
+
synthesized = await synthesizeDiscoveryEnvelope({
|
|
432
|
+
target_id: targetId,
|
|
433
|
+
lane: target.lane,
|
|
434
|
+
source: { provider: "fireflies", ...(session.stable_url ? { stable_url: session.stable_url } : {}), source_fingerprint: candidate.source_fingerprint, completed_at: session.completed_at, locator_capability: session.capabilities.timestamps ? "timestamp" : "none" },
|
|
435
|
+
title: session.title,
|
|
436
|
+
hypotheses,
|
|
437
|
+
transcript_text: transcriptText,
|
|
438
|
+
transcript_complete: true,
|
|
439
|
+
caveats,
|
|
440
|
+
}, { model, transport: deps.transport });
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return invalid("Synthesis did not produce a valid closed envelope; nothing was written and the transcript was discarded.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
transcriptText = null;
|
|
447
|
+
}
|
|
448
|
+
coverage.synthesis_usage = synthesized.usage ?? null;
|
|
449
|
+
// Privacy screen beyond the envelope validator: participant names, email
|
|
450
|
+
// domains, and speaker names from the provider's own metadata must not
|
|
451
|
+
// appear in derived text.
|
|
452
|
+
const derivedText = [
|
|
453
|
+
...synthesized.envelope.findings.flatMap((finding) => [finding.headline, finding.paraphrase, finding.dissent]),
|
|
454
|
+
...synthesized.envelope.coverage.caveats,
|
|
455
|
+
].join("\n");
|
|
456
|
+
if (containsIdentity(derivedText, identities))
|
|
457
|
+
return invalid("Synthesis leaked a participant identity into derived text; nothing was written.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
|
|
458
|
+
const applied = await runDiscoveryCommand(root, ["apply", targetId, "--evidence-stdin", "--json"], JSON.stringify(synthesized.envelope), now);
|
|
459
|
+
return outcome({ ...applied.result, coverage: { ...applied.result.coverage, ...coverage } }, json);
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
if (error instanceof FirefliesRuntimeError)
|
|
463
|
+
return fromRuntimeError(error, json, targetId, { ...coverage, transcript_body_requests: client?.requests.transcript ?? 0 });
|
|
464
|
+
return invalid("Fireflies processing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function syncCommand(root, json, deps) {
|
|
468
|
+
const now = deps.now();
|
|
469
|
+
const plan = planDiscoverySync(root, now);
|
|
470
|
+
const lanes = plan.lanes.filter((lane) => lane.provider.toLowerCase() === "fireflies");
|
|
471
|
+
if (lanes.length === 0)
|
|
472
|
+
return outcome(result("clean_no_op", { errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })), next_actions: ["No approved Fireflies lane is active; approve a study source or an inbox scope first."], coverage: { mode: FIREFLIES_MODE, lanes: 0, connector_reads: 0 } }), json);
|
|
473
|
+
const results = [];
|
|
474
|
+
// Metadata and match only: a transcript is opened only by an explicit
|
|
475
|
+
// `process` on one target. The loop stays unarmed (R-010).
|
|
476
|
+
for (const lane of lanes) {
|
|
477
|
+
const targetId = lane.kind === "inbox" ? `inbox:${lane.scope_id}` : lane.study_id;
|
|
478
|
+
const listed = await listCommand(root, targetId, true, deps);
|
|
479
|
+
results.push({ lane_id: lane.lane_id, status: listed.result.status, handoff: listed.result.handoff });
|
|
480
|
+
}
|
|
481
|
+
const ready = results.filter((entry) => entry.status === "ok").map((entry) => entry.lane_id);
|
|
482
|
+
return outcome(result("ok", {
|
|
483
|
+
errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })),
|
|
484
|
+
next_actions: [
|
|
485
|
+
...ready.map((laneId) => `${laneId}: one transcript matched — run foo discovery fireflies process ${laneId.replace(/^study:/, "")} to open it.`),
|
|
486
|
+
...results.filter((entry) => entry.status !== "ok").map((entry) => `${entry.lane_id}: ${entry.status}${entry.handoff ? ` — ${entry.handoff}` : ""}`),
|
|
487
|
+
],
|
|
488
|
+
coverage: { mode: FIREFLIES_MODE, lanes: results.length, transcripts_opened: 0, results },
|
|
489
|
+
}), json);
|
|
490
|
+
}
|
|
491
|
+
export async function runFirefliesDiscovery(root, args, deps = {}) {
|
|
492
|
+
const json = hasFlag(args, "--json");
|
|
493
|
+
const env = deps.env ?? process.env;
|
|
494
|
+
const resolved = {
|
|
495
|
+
fetchImpl: deps.fetchImpl ?? globalThis.fetch,
|
|
496
|
+
env,
|
|
497
|
+
now: deps.now ?? (() => new Date().toISOString()),
|
|
498
|
+
sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
499
|
+
transport: deps.transport ?? anthropicSynthesisTransport({ fetchImpl: deps.fetchImpl, env }),
|
|
500
|
+
};
|
|
501
|
+
const command = args[0];
|
|
502
|
+
const target = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
|
|
503
|
+
try {
|
|
504
|
+
if (command === "ready")
|
|
505
|
+
return await readyCommand(root, json, resolved);
|
|
506
|
+
if (command === "list")
|
|
507
|
+
return await listCommand(root, target, json, resolved);
|
|
508
|
+
if (command === "process")
|
|
509
|
+
return await processCommand(root, target, json, resolved);
|
|
510
|
+
if (command === "sync")
|
|
511
|
+
return await syncCommand(root, json, resolved);
|
|
512
|
+
return invalid("Try: ready | list <study-id|inbox:scope-id> | process <study-id|inbox:scope-id> | sync", json);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return invalid("Fireflies discovery failed closed without exposing private source content.", json);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
export const FIREFLIES_SUPPORT_STATE = "contract_only";
|
|
519
|
+
export const FIREFLIES_MODE = "local_worker";
|
|
520
|
+
export function firefliesScopeValidationError(scope) {
|
|
521
|
+
const start = Date.parse(scope.window?.start ?? "");
|
|
522
|
+
const end = Date.parse(scope.window?.end ?? "");
|
|
523
|
+
if (!Number.isFinite(start) || !Number.isFinite(end))
|
|
524
|
+
return "a fireflies scope needs a bounded time window with a valid start and end";
|
|
525
|
+
if (start > end)
|
|
526
|
+
return "a fireflies scope's time window must end after it starts";
|
|
527
|
+
if (scope.include_internal_only)
|
|
528
|
+
return "a fireflies scope cannot include internal-only meetings in V0: they are excluded by default (OQ-001 leaves the workspace-archive question open)";
|
|
529
|
+
const selectorKinds = [
|
|
530
|
+
["named external participants", scope.external_names],
|
|
531
|
+
["external companies or email domains", scope.external_domains],
|
|
532
|
+
["title tokens", scope.title_tokens],
|
|
533
|
+
];
|
|
534
|
+
const hasSelector = selectorKinds.some(([, list]) => Array.isArray(list) && list.some((entry) => typeof entry === "string" && entry.trim().length > 0));
|
|
535
|
+
if (!hasSelector) {
|
|
536
|
+
return "a fireflies scope needs at least one customer selector (a named external participant, an external company or email domain, or a title token); a time window alone is never enough";
|
|
537
|
+
}
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
// The inbox scope's generic fields map onto the Fireflies selector kinds; the
|
|
541
|
+
// study lane maps its own (required participants, target organization, title
|
|
542
|
+
// tokens) the same way gong's worker does. A scope whose provider is not
|
|
543
|
+
// fireflies never reaches validation.
|
|
544
|
+
export function firefliesSelectorsFromInboxScope(scope) {
|
|
545
|
+
return {
|
|
546
|
+
window: { start: scope.time_window?.start, end: scope.time_window?.end },
|
|
547
|
+
external_names: scope.participant_allowlist,
|
|
548
|
+
external_domains: scope.domain_allowlist,
|
|
549
|
+
title_tokens: scope.title_tokens,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DiscoveryCommandOutcome } from "./discoveryCommand.js";
|
|
2
|
+
import { type SynthesisTransport } from "./discoverySynthesis.js";
|
|
3
|
+
import { type FetchLike } from "./gmailNango.js";
|
|
4
|
+
export type GmailDiscoveryDeps = {
|
|
5
|
+
fetchImpl?: FetchLike;
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
now?: () => string;
|
|
8
|
+
sleep?: (ms: number) => Promise<void>;
|
|
9
|
+
transport?: SynthesisTransport;
|
|
10
|
+
fetchTimeoutMs?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function runGmailDiscovery(root: string, args: string[], deps?: GmailDiscoveryDeps): Promise<DiscoveryCommandOutcome>;
|