/** * Trigger detection (T3b — Plan A, Option B). * * When the user runs a credentialed command without ever having gone * through `scoutline init`, we want one of three classified outcomes * rather than a confusing per-handler stack trace: * * - **env-only setup**: a key for the effective Provider is present * in the injected env, but no Provider key is stored in * `config.json`. We emit a ONE-TIME stderr hint pointing at * `scoutline init` (so the user learns the wizard exists), persist * `config.json.hintShown`, and then run the command normally. The * command's natural output and exit code are preserved. * - **missing credential everywhere**: the effective Provider has no * key in env OR in `config.json`. We emit remediation and surface * the existing `CONFIGURATION_ERROR` exit 3. A refused operation * is NEVER exit 0 — scripting users must not treat refusal as * success. * - **credential-free commands** (`--help`, `--version`, `cache`, * `init`, ` --help`): no config read at all. The T2a * short-circuit already covers help/version/cache/init; this * module exposes a helper to recognise per-command help so the * trigger layer can route around it too (command help must remain * usable under a corrupt config — see review item 9). * * Doctor and quota are observational: they report per-Provider state * rather than depending on a single effective credential, so trigger * detection does NOT classify them. They keep their existing per-handler * configuration-error behavior (Doctor reports skipped/error states; * quota surfaces its existing CONFIGURATION_ERROR when nothing is * configured). * * Raw Z.AI commands (`tools`, `tool`, `call`, `code`) ARE credentialed * and Z.AI-only, so missing-key remediation applies to them. They do * NOT gain Provider selection or fallback (still Z.AI-only). * * All functions here are pure (they take their inputs as arguments and * touch no module-level state). The dispatcher in `src/index.ts` wires * the real `HintShownStore`, the injected env, the descriptors, and the * stderr sink. */ import type { ProviderDescriptor } from "../providers/types.js"; import type { ScoutlineConfig } from "./config-store.js"; import { ConfigurationError } from "./errors.js"; /** * Commands that are observational rather than credentialed. They are * exempt from trigger detection because they already report per-Provider * state instead of depending on a single effective credential. */ export declare const OBSERVATIONAL_COMMANDS: ReadonlySet; /** * Commands that are credential-free at the dispatcher level (handled * before the credentialed config read). Listed here for documentation * completeness; the dispatcher short-circuits them before reaching the * trigger layer. */ export declare const DISPATCHER_CREDENTIAL_FREE: ReadonlySet; /** * Commands that are always Z.AI-only (raw Z.AI tools + Code Mode). * Trigger detection treats them as credentialed; their effective * Provider is always `zai` regardless of `--provider`. */ export declare const ZAI_ONLY_COMMANDS: ReadonlySet; /** * The classified credential state for the command being dispatched. * The dispatcher consumes this to decide whether to emit the hint, * refuse, or proceed normally. The classification is computed across * ALL descriptors (not just the effective Provider) so that Provider * fallback can route to a configured candidate even when the effective * is unconfigured — "missing" only fires when NO provider can serve * the request. A keyless provider (isConfigured with no credential * recorded anywhere) counts as serviceable, so a registry whose only * serviceable provider is keyless classifies as "keyless", not * "missing". */ export type CredentialState = { readonly kind: "env-only"; } | { readonly kind: "file-configured"; } | { readonly kind: "env-and-file"; } | { readonly kind: "keyless"; } | { readonly kind: "missing"; }; /** * Inputs to {@link classifyCredentialState}. Kept explicit so the * classifier stays a pure function — no module-level reads, no * descriptor lookup beyond what the caller passes in. */ export interface CredentialClassificationInput { /** The full provider registry (so fallback candidates are considered). */ readonly descriptors: readonly ProviderDescriptor[]; /** The injected environment view (env vars only, no file keys). */ readonly env: NodeJS.ProcessEnv; /** * The resolved environment view (env + file keys layered in by * `resolveEnvFromConfig`). When this differs from `env`, a file * key is present. */ readonly resolvedEnv: NodeJS.ProcessEnv; /** The loaded config (used to detect a file key directly). */ readonly config: ScoutlineConfig; } /** * Classify the credential state across the whole registry. * * - **missing**: NO descriptor is configured through env OR file. * The command cannot succeed (no fallback candidate exists), so * the dispatcher refuses with `CONFIGURATION_ERROR` exit 3. * - **env-only**: at least one descriptor is configured through env, * and NO descriptor has a file key. The user is running purely on * environment variables; the one-time hint points them at `init`. * - **env-and-file**: env keys and file keys both exist. Env * precedence means the env value wins at runtime for the providers * that have both; the file keys cover the rest. No hint needed * (the user has been through onboarding). * - **file-configured**: keys exist ONLY in the file (the normal * post-onboarding steady state). No hint needed. * - **keyless**: some descriptor reports `isConfigured` with NO * credential recorded anywhere — a keyless provider (e.g. Jina's * Reader) can serve the request without one. This is NOT "missing" * (a provider CAN serve, so no refusal) and NOT "file-configured" * (no keys exist anywhere, so nothing came from the file); the * command proceeds normally with no hint (there is no credential * to record via `init`). * * The "env-only" classification is the trigger for the one-time hint; * "missing" is the trigger for remediation + exit 3. The other states * proceed normally. */ export declare function classifyCredentialState(input: CredentialClassificationInput): CredentialState; /** * Format the one-time env-only hint. Sent to stderr so stdout stays * data-only. The hint names the wizard and points at it; it does NOT * repeat on subsequent runs because the dispatcher persists * `config.json.hintShown` after emitting it. */ export declare function formatEnvOnlyHint(): string; /** * Build the missing-credential error. Reuses the existing * `CONFIGURATION_ERROR` / exit-3 contract so a refused operation is * NEVER confused with success. The help text lists the canonical env * vars across all providers so the user can pick one to set, and * points at `init` as the interactive alternative. */ export declare function missingCredentialError(descriptors: readonly ProviderDescriptor[]): ConfigurationError; /** * Detect whether the command being dispatched should bypass trigger * detection because it is rendering command-local help. Command help * (` --help` / `-h`) must remain usable even when config.json * is corrupt or absent — rendering help never needs a credential. * * This is a shallow peek at the arg list: it only looks for the help * flag, mirroring the per-handler short-circuit (`flags.help || * flags.h`). It does NOT consume the args; the handler still parses * them itself. */ export declare function isCommandHelpInvocation(commandArgs: readonly string[]): boolean; /** * Batch dry-run invocations (`batch --dry-run` and the * `vision batch --dry-run` wrapper) promise a NO-TRANSPORT * preview: no descriptor.create(), no cache reads/writes, no * consumption (batch-runner DESIGN D7/D10). The after-command quota * due-refresh in `main` is cadence-gated but live-probes stale * providers, so it must be skipped for these invocations (review fix). * * Same classification-only contract as isCommandHelpInvocation: this * never consumes the args — the handler still parses them itself. The * token walk mirrors the private parseArgs in index.ts (dash-prefixed * keys consume the next non-empty, non-dash token as their value; * `--no-x` consumes nothing) so detection is flag-order independent: * `vision --out o batch 'shots/*.png' --dry-run` is detected exactly * like `vision batch 'shots/*.png' --dry-run` (review fix: a * first-token-only check missed flags-first invocations). A valued * `--dry-run` consumes its value here AND fails the wrapper's * boolean-only guard, so it is never classified as a preview. The walk * must stay in sync with parseArgs. */ export declare function isDryRunBatchInvocation(command: string, commandArgs: readonly string[]): boolean; //# sourceMappingURL=trigger-detection.d.ts.map