scoutline 0.10.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +99 -44
  2. package/dist/capabilities/diagnostics.d.ts +75 -0
  3. package/dist/capabilities/diagnostics.d.ts.map +1 -1
  4. package/dist/capabilities/diagnostics.js.map +1 -1
  5. package/dist/capabilities/quota.d.ts +64 -1
  6. package/dist/capabilities/quota.d.ts.map +1 -1
  7. package/dist/capabilities/quota.js.map +1 -1
  8. package/dist/capabilities/vision.d.ts +4 -3
  9. package/dist/capabilities/vision.d.ts.map +1 -1
  10. package/dist/capabilities/vision.js +7 -5
  11. package/dist/capabilities/vision.js.map +1 -1
  12. package/dist/commands/code.d.ts +9 -1
  13. package/dist/commands/code.d.ts.map +1 -1
  14. package/dist/commands/code.js +4 -4
  15. package/dist/commands/code.js.map +1 -1
  16. package/dist/commands/crawl.d.ts.map +1 -1
  17. package/dist/commands/crawl.js +14 -3
  18. package/dist/commands/crawl.js.map +1 -1
  19. package/dist/commands/doctor.d.ts +71 -1
  20. package/dist/commands/doctor.d.ts.map +1 -1
  21. package/dist/commands/doctor.js +96 -15
  22. package/dist/commands/doctor.js.map +1 -1
  23. package/dist/commands/init.d.ts +186 -0
  24. package/dist/commands/init.d.ts.map +1 -0
  25. package/dist/commands/init.js +1221 -0
  26. package/dist/commands/init.js.map +1 -0
  27. package/dist/commands/map.d.ts.map +1 -1
  28. package/dist/commands/map.js +16 -3
  29. package/dist/commands/map.js.map +1 -1
  30. package/dist/commands/quota.d.ts +32 -0
  31. package/dist/commands/quota.d.ts.map +1 -1
  32. package/dist/commands/quota.js +165 -24
  33. package/dist/commands/quota.js.map +1 -1
  34. package/dist/commands/read.d.ts.map +1 -1
  35. package/dist/commands/read.js +9 -3
  36. package/dist/commands/read.js.map +1 -1
  37. package/dist/commands/repo.d.ts.map +1 -1
  38. package/dist/commands/repo.js +5 -3
  39. package/dist/commands/repo.js.map +1 -1
  40. package/dist/commands/research.d.ts +35 -7
  41. package/dist/commands/research.d.ts.map +1 -1
  42. package/dist/commands/research.js +93 -15
  43. package/dist/commands/research.js.map +1 -1
  44. package/dist/commands/tools.d.ts +8 -0
  45. package/dist/commands/tools.d.ts.map +1 -1
  46. package/dist/commands/tools.js +4 -4
  47. package/dist/commands/tools.js.map +1 -1
  48. package/dist/commands/vision.d.ts +10 -0
  49. package/dist/commands/vision.d.ts.map +1 -1
  50. package/dist/commands/vision.js +25 -2
  51. package/dist/commands/vision.js.map +1 -1
  52. package/dist/index.d.ts +136 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +894 -298
  55. package/dist/index.js.map +1 -1
  56. package/dist/lib/api-client.d.ts +1 -1
  57. package/dist/lib/api-client.d.ts.map +1 -1
  58. package/dist/lib/api-client.js +6 -2
  59. package/dist/lib/api-client.js.map +1 -1
  60. package/dist/lib/cache.d.ts +9 -2
  61. package/dist/lib/cache.d.ts.map +1 -1
  62. package/dist/lib/cache.js +9 -2
  63. package/dist/lib/cache.js.map +1 -1
  64. package/dist/lib/code-mode.d.ts +8 -0
  65. package/dist/lib/code-mode.d.ts.map +1 -1
  66. package/dist/lib/code-mode.js +3 -1
  67. package/dist/lib/code-mode.js.map +1 -1
  68. package/dist/lib/config-store.d.ts +165 -0
  69. package/dist/lib/config-store.d.ts.map +1 -0
  70. package/dist/lib/config-store.js +332 -0
  71. package/dist/lib/config-store.js.map +1 -0
  72. package/dist/lib/config.d.ts +2 -2
  73. package/dist/lib/config.d.ts.map +1 -1
  74. package/dist/lib/config.js +19 -11
  75. package/dist/lib/config.js.map +1 -1
  76. package/dist/lib/consumption.d.ts +144 -0
  77. package/dist/lib/consumption.d.ts.map +1 -0
  78. package/dist/lib/consumption.js +161 -0
  79. package/dist/lib/consumption.js.map +1 -0
  80. package/dist/lib/errors.d.ts +11 -0
  81. package/dist/lib/errors.d.ts.map +1 -1
  82. package/dist/lib/errors.js +14 -0
  83. package/dist/lib/errors.js.map +1 -1
  84. package/dist/lib/execution.d.ts +32 -4
  85. package/dist/lib/execution.d.ts.map +1 -1
  86. package/dist/lib/execution.js +156 -13
  87. package/dist/lib/execution.js.map +1 -1
  88. package/dist/lib/mcp-client.d.ts +12 -0
  89. package/dist/lib/mcp-client.d.ts.map +1 -1
  90. package/dist/lib/mcp-client.js +16 -7
  91. package/dist/lib/mcp-client.js.map +1 -1
  92. package/dist/lib/mcp-config.d.ts +10 -0
  93. package/dist/lib/mcp-config.d.ts.map +1 -1
  94. package/dist/lib/mcp-config.js +13 -7
  95. package/dist/lib/mcp-config.js.map +1 -1
  96. package/dist/lib/monitor-client.d.ts +19 -3
  97. package/dist/lib/monitor-client.d.ts.map +1 -1
  98. package/dist/lib/monitor-client.js +21 -5
  99. package/dist/lib/monitor-client.js.map +1 -1
  100. package/dist/lib/provider-fallback.d.ts +150 -0
  101. package/dist/lib/provider-fallback.d.ts.map +1 -0
  102. package/dist/lib/provider-fallback.js +510 -0
  103. package/dist/lib/provider-fallback.js.map +1 -0
  104. package/dist/lib/quota-mapping.d.ts +402 -0
  105. package/dist/lib/quota-mapping.d.ts.map +1 -0
  106. package/dist/lib/quota-mapping.js +539 -0
  107. package/dist/lib/quota-mapping.js.map +1 -0
  108. package/dist/lib/quota-store.d.ts +271 -0
  109. package/dist/lib/quota-store.d.ts.map +1 -0
  110. package/dist/lib/quota-store.js +540 -0
  111. package/dist/lib/quota-store.js.map +1 -0
  112. package/dist/lib/trigger-detection.d.ts +149 -0
  113. package/dist/lib/trigger-detection.d.ts.map +1 -0
  114. package/dist/lib/trigger-detection.js +149 -0
  115. package/dist/lib/trigger-detection.js.map +1 -0
  116. package/dist/lib/tty.d.ts +8 -1
  117. package/dist/lib/tty.d.ts.map +1 -1
  118. package/dist/lib/tty.js +53 -1
  119. package/dist/lib/tty.js.map +1 -1
  120. package/dist/providers/brave/adapter.d.ts +26 -0
  121. package/dist/providers/brave/adapter.d.ts.map +1 -1
  122. package/dist/providers/brave/adapter.js +66 -4
  123. package/dist/providers/brave/adapter.js.map +1 -1
  124. package/dist/providers/brave/client.d.ts +46 -2
  125. package/dist/providers/brave/client.d.ts.map +1 -1
  126. package/dist/providers/brave/client.js +55 -3
  127. package/dist/providers/brave/client.js.map +1 -1
  128. package/dist/providers/exa/adapter.d.ts.map +1 -1
  129. package/dist/providers/exa/adapter.js +10 -1
  130. package/dist/providers/exa/adapter.js.map +1 -1
  131. package/dist/providers/firecrawl/adapter.d.ts.map +1 -1
  132. package/dist/providers/firecrawl/adapter.js +13 -3
  133. package/dist/providers/firecrawl/adapter.js.map +1 -1
  134. package/dist/providers/minimax/adapter.d.ts.map +1 -1
  135. package/dist/providers/minimax/adapter.js +4 -0
  136. package/dist/providers/minimax/adapter.js.map +1 -1
  137. package/dist/providers/selection.d.ts +114 -1
  138. package/dist/providers/selection.d.ts.map +1 -1
  139. package/dist/providers/selection.js +99 -3
  140. package/dist/providers/selection.js.map +1 -1
  141. package/dist/providers/tavily/adapter.d.ts.map +1 -1
  142. package/dist/providers/tavily/adapter.js +2 -0
  143. package/dist/providers/tavily/adapter.js.map +1 -1
  144. package/dist/providers/types.d.ts +33 -1
  145. package/dist/providers/types.d.ts.map +1 -1
  146. package/dist/providers/types.js +8 -0
  147. package/dist/providers/types.js.map +1 -1
  148. package/dist/providers/zai/adapter.d.ts.map +1 -1
  149. package/dist/providers/zai/adapter.js +18 -5
  150. package/dist/providers/zai/adapter.js.map +1 -1
  151. package/package.json +2 -1
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Trigger detection (T3b — Plan A, Option B).
3
+ *
4
+ * When the user runs a credentialed command without ever having gone
5
+ * through `scoutline init`, we want one of three classified outcomes
6
+ * rather than a confusing per-handler stack trace:
7
+ *
8
+ * - **env-only setup**: a key for the effective Provider is present
9
+ * in the injected env, but no Provider key is stored in
10
+ * `config.json`. We emit a ONE-TIME stderr hint pointing at
11
+ * `scoutline init` (so the user learns the wizard exists), persist
12
+ * `config.json.hintShown`, and then run the command normally. The
13
+ * command's natural output and exit code are preserved.
14
+ * - **missing credential everywhere**: the effective Provider has no
15
+ * key in env OR in `config.json`. We emit remediation and surface
16
+ * the existing `CONFIGURATION_ERROR` exit 3. A refused operation
17
+ * is NEVER exit 0 — scripting users must not treat refusal as
18
+ * success.
19
+ * - **credential-free commands** (`--help`, `--version`, `cache`,
20
+ * `init`, `<command> --help`): no config read at all. The T2a
21
+ * short-circuit already covers help/version/cache/init; this
22
+ * module exposes a helper to recognise per-command help so the
23
+ * trigger layer can route around it too (command help must remain
24
+ * usable under a corrupt config — see review item 9).
25
+ *
26
+ * Doctor and quota are observational: they report per-Provider state
27
+ * rather than depending on a single effective credential, so trigger
28
+ * detection does NOT classify them. They keep their existing per-handler
29
+ * configuration-error behavior (Doctor reports skipped/error states;
30
+ * quota surfaces its existing CONFIGURATION_ERROR when nothing is
31
+ * configured).
32
+ *
33
+ * Raw Z.AI commands (`tools`, `tool`, `call`, `code`) ARE credentialed
34
+ * and Z.AI-only, so missing-key remediation applies to them. They do
35
+ * NOT gain Provider selection or fallback (still Z.AI-only).
36
+ *
37
+ * All functions here are pure (they take their inputs as arguments and
38
+ * touch no module-level state). The dispatcher in `src/index.ts` wires
39
+ * the real `HintShownStore`, the injected env, the descriptors, and the
40
+ * stderr sink.
41
+ */
42
+ import type { ProviderDescriptor } from "../providers/types.js";
43
+ import type { ScoutlineConfig } from "./config-store.js";
44
+ import { ConfigurationError } from "./errors.js";
45
+ /**
46
+ * Commands that are observational rather than credentialed. They are
47
+ * exempt from trigger detection because they already report per-Provider
48
+ * state instead of depending on a single effective credential.
49
+ */
50
+ export declare const OBSERVATIONAL_COMMANDS: ReadonlySet<string>;
51
+ /**
52
+ * Commands that are credential-free at the dispatcher level (handled
53
+ * before the credentialed config read). Listed here for documentation
54
+ * completeness; the dispatcher short-circuits them before reaching the
55
+ * trigger layer.
56
+ */
57
+ export declare const DISPATCHER_CREDENTIAL_FREE: ReadonlySet<string>;
58
+ /**
59
+ * Commands that are always Z.AI-only (raw Z.AI tools + Code Mode).
60
+ * Trigger detection treats them as credentialed; their effective
61
+ * Provider is always `zai` regardless of `--provider`.
62
+ */
63
+ export declare const ZAI_ONLY_COMMANDS: ReadonlySet<string>;
64
+ /**
65
+ * The classified credential state for the command being dispatched.
66
+ * The dispatcher consumes this to decide whether to emit the hint,
67
+ * refuse, or proceed normally. The classification is computed across
68
+ * ALL descriptors (not just the effective Provider) so that Provider
69
+ * fallback can route to a configured candidate even when the effective
70
+ * is unconfigured — "missing" only fires when NO provider can serve
71
+ * the request.
72
+ */
73
+ export type CredentialState = {
74
+ readonly kind: "env-only";
75
+ } | {
76
+ readonly kind: "file-configured";
77
+ } | {
78
+ readonly kind: "env-and-file";
79
+ } | {
80
+ readonly kind: "missing";
81
+ };
82
+ /**
83
+ * Inputs to {@link classifyCredentialState}. Kept explicit so the
84
+ * classifier stays a pure function — no module-level reads, no
85
+ * descriptor lookup beyond what the caller passes in.
86
+ */
87
+ export interface CredentialClassificationInput {
88
+ /** The full provider registry (so fallback candidates are considered). */
89
+ readonly descriptors: readonly ProviderDescriptor[];
90
+ /** The injected environment view (env vars only, no file keys). */
91
+ readonly env: NodeJS.ProcessEnv;
92
+ /**
93
+ * The resolved environment view (env + file keys layered in by
94
+ * `resolveEnvFromConfig`). When this differs from `env`, a file
95
+ * key is present.
96
+ */
97
+ readonly resolvedEnv: NodeJS.ProcessEnv;
98
+ /** The loaded config (used to detect a file key directly). */
99
+ readonly config: ScoutlineConfig;
100
+ }
101
+ /**
102
+ * Classify the credential state across the whole registry.
103
+ *
104
+ * - **missing**: NO descriptor is configured through env OR file.
105
+ * The command cannot succeed (no fallback candidate exists), so
106
+ * the dispatcher refuses with `CONFIGURATION_ERROR` exit 3.
107
+ * - **env-only**: at least one descriptor is configured through env,
108
+ * and NO descriptor has a file key. The user is running purely on
109
+ * environment variables; the one-time hint points them at `init`.
110
+ * - **env-and-file**: env keys and file keys both exist. Env
111
+ * precedence means the env value wins at runtime for the providers
112
+ * that have both; the file keys cover the rest. No hint needed
113
+ * (the user has been through onboarding).
114
+ * - **file-configured**: keys exist ONLY in the file (the normal
115
+ * post-onboarding steady state). No hint needed.
116
+ *
117
+ * The "env-only" classification is the trigger for the one-time hint;
118
+ * "missing" is the trigger for remediation + exit 3. The other two
119
+ * states proceed normally.
120
+ */
121
+ export declare function classifyCredentialState(input: CredentialClassificationInput): CredentialState;
122
+ /**
123
+ * Format the one-time env-only hint. Sent to stderr so stdout stays
124
+ * data-only. The hint names the wizard and points at it; it does NOT
125
+ * repeat on subsequent runs because the dispatcher persists
126
+ * `config.json.hintShown` after emitting it.
127
+ */
128
+ export declare function formatEnvOnlyHint(): string;
129
+ /**
130
+ * Build the missing-credential error. Reuses the existing
131
+ * `CONFIGURATION_ERROR` / exit-3 contract so a refused operation is
132
+ * NEVER confused with success. The help text lists the canonical env
133
+ * vars across all providers so the user can pick one to set, and
134
+ * points at `init` as the interactive alternative.
135
+ */
136
+ export declare function missingCredentialError(descriptors: readonly ProviderDescriptor[]): ConfigurationError;
137
+ /**
138
+ * Detect whether the command being dispatched should bypass trigger
139
+ * detection because it is rendering command-local help. Command help
140
+ * (`<command> --help` / `-h`) must remain usable even when config.json
141
+ * is corrupt or absent — rendering help never needs a credential.
142
+ *
143
+ * This is a shallow peek at the arg list: it only looks for the help
144
+ * flag, mirroring the per-handler short-circuit (`flags.help ||
145
+ * flags.h`). It does NOT consume the args; the handler still parses
146
+ * them itself.
147
+ */
148
+ export declare function isCommandHelpInvocation(commandArgs: readonly string[]): boolean;
149
+ //# sourceMappingURL=trigger-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-detection.d.ts","sourceRoot":"","sources":["../../src/lib/trigger-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAc,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAgC,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,CAA8B,CAAC;AAE1F;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAA8C,CAAC;AAEjG;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GACpC;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjC;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACpD,mEAAmE;IACnE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IACxC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,6BAA6B,GAAG,eAAe,CAqB7F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAO1C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,SAAS,kBAAkB,EAAE,GACzC,kBAAkB,CAapB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE/E"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Trigger detection (T3b — Plan A, Option B).
3
+ *
4
+ * When the user runs a credentialed command without ever having gone
5
+ * through `scoutline init`, we want one of three classified outcomes
6
+ * rather than a confusing per-handler stack trace:
7
+ *
8
+ * - **env-only setup**: a key for the effective Provider is present
9
+ * in the injected env, but no Provider key is stored in
10
+ * `config.json`. We emit a ONE-TIME stderr hint pointing at
11
+ * `scoutline init` (so the user learns the wizard exists), persist
12
+ * `config.json.hintShown`, and then run the command normally. The
13
+ * command's natural output and exit code are preserved.
14
+ * - **missing credential everywhere**: the effective Provider has no
15
+ * key in env OR in `config.json`. We emit remediation and surface
16
+ * the existing `CONFIGURATION_ERROR` exit 3. A refused operation
17
+ * is NEVER exit 0 — scripting users must not treat refusal as
18
+ * success.
19
+ * - **credential-free commands** (`--help`, `--version`, `cache`,
20
+ * `init`, `<command> --help`): no config read at all. The T2a
21
+ * short-circuit already covers help/version/cache/init; this
22
+ * module exposes a helper to recognise per-command help so the
23
+ * trigger layer can route around it too (command help must remain
24
+ * usable under a corrupt config — see review item 9).
25
+ *
26
+ * Doctor and quota are observational: they report per-Provider state
27
+ * rather than depending on a single effective credential, so trigger
28
+ * detection does NOT classify them. They keep their existing per-handler
29
+ * configuration-error behavior (Doctor reports skipped/error states;
30
+ * quota surfaces its existing CONFIGURATION_ERROR when nothing is
31
+ * configured).
32
+ *
33
+ * Raw Z.AI commands (`tools`, `tool`, `call`, `code`) ARE credentialed
34
+ * and Z.AI-only, so missing-key remediation applies to them. They do
35
+ * NOT gain Provider selection or fallback (still Z.AI-only).
36
+ *
37
+ * All functions here are pure (they take their inputs as arguments and
38
+ * touch no module-level state). The dispatcher in `src/index.ts` wires
39
+ * the real `HintShownStore`, the injected env, the descriptors, and the
40
+ * stderr sink.
41
+ */
42
+ import { ConfigurationError } from "./errors.js";
43
+ /**
44
+ * Commands that are observational rather than credentialed. They are
45
+ * exempt from trigger detection because they already report per-Provider
46
+ * state instead of depending on a single effective credential.
47
+ */
48
+ export const OBSERVATIONAL_COMMANDS = new Set(["doctor", "quota"]);
49
+ /**
50
+ * Commands that are credential-free at the dispatcher level (handled
51
+ * before the credentialed config read). Listed here for documentation
52
+ * completeness; the dispatcher short-circuits them before reaching the
53
+ * trigger layer.
54
+ */
55
+ export const DISPATCHER_CREDENTIAL_FREE = new Set(["cache", "init"]);
56
+ /**
57
+ * Commands that are always Z.AI-only (raw Z.AI tools + Code Mode).
58
+ * Trigger detection treats them as credentialed; their effective
59
+ * Provider is always `zai` regardless of `--provider`.
60
+ */
61
+ export const ZAI_ONLY_COMMANDS = new Set(["tools", "tool", "call", "code"]);
62
+ /**
63
+ * Classify the credential state across the whole registry.
64
+ *
65
+ * - **missing**: NO descriptor is configured through env OR file.
66
+ * The command cannot succeed (no fallback candidate exists), so
67
+ * the dispatcher refuses with `CONFIGURATION_ERROR` exit 3.
68
+ * - **env-only**: at least one descriptor is configured through env,
69
+ * and NO descriptor has a file key. The user is running purely on
70
+ * environment variables; the one-time hint points them at `init`.
71
+ * - **env-and-file**: env keys and file keys both exist. Env
72
+ * precedence means the env value wins at runtime for the providers
73
+ * that have both; the file keys cover the rest. No hint needed
74
+ * (the user has been through onboarding).
75
+ * - **file-configured**: keys exist ONLY in the file (the normal
76
+ * post-onboarding steady state). No hint needed.
77
+ *
78
+ * The "env-only" classification is the trigger for the one-time hint;
79
+ * "missing" is the trigger for remediation + exit 3. The other two
80
+ * states proceed normally.
81
+ */
82
+ export function classifyCredentialState(input) {
83
+ const { descriptors, env, resolvedEnv, config } = input;
84
+ const anyEnvConfigured = descriptors.some((d) => d.isConfigured(env));
85
+ const anyResolvedConfigured = descriptors.some((d) => d.isConfigured(resolvedEnv));
86
+ const anyFileConfigured = Object.values(config.providers).some((provider) => {
87
+ const key = provider?.apiKey;
88
+ return typeof key === "string" && key.trim().length > 0;
89
+ });
90
+ if (!anyResolvedConfigured) {
91
+ return { kind: "missing" };
92
+ }
93
+ if (anyEnvConfigured && !anyFileConfigured) {
94
+ return { kind: "env-only" };
95
+ }
96
+ if (anyEnvConfigured && anyFileConfigured) {
97
+ return { kind: "env-and-file" };
98
+ }
99
+ // !anyEnvConfigured && anyFileConfigured — the resolved env picked up
100
+ // the file key; this is the normal post-onboarding steady state.
101
+ return { kind: "file-configured" };
102
+ }
103
+ /**
104
+ * Format the one-time env-only hint. Sent to stderr so stdout stays
105
+ * data-only. The hint names the wizard and points at it; it does NOT
106
+ * repeat on subsequent runs because the dispatcher persists
107
+ * `config.json.hintShown` after emitting it.
108
+ */
109
+ export function formatEnvOnlyHint() {
110
+ return ("scoutline: using credentials from the environment. " +
111
+ "Run `scoutline init` to record them in ~/.scoutline/config.json " +
112
+ "(enables fallback preferences, verification tracking, and re-config). " +
113
+ "This is a one-time hint; it will not repeat.\n");
114
+ }
115
+ /**
116
+ * Build the missing-credential error. Reuses the existing
117
+ * `CONFIGURATION_ERROR` / exit-3 contract so a refused operation is
118
+ * NEVER confused with success. The help text lists the canonical env
119
+ * vars across all providers so the user can pick one to set, and
120
+ * points at `init` as the interactive alternative.
121
+ */
122
+ export function missingCredentialError(descriptors) {
123
+ const vars = [];
124
+ for (const descriptor of descriptors) {
125
+ if (descriptor.credentialEnvVars && descriptor.credentialEnvVars.length > 0) {
126
+ vars.push(descriptor.credentialEnvVars[0]);
127
+ }
128
+ }
129
+ const help = vars.length > 0
130
+ ? `Set one of ${vars.join(", ")} (or run \`scoutline init\` to configure interactively).`
131
+ : "Run `scoutline init` to configure a Provider, or set the required API key in the environment.";
132
+ const message = "No Provider credential is configured (neither env nor ~/.scoutline/config.json)";
133
+ return new ConfigurationError(message, help);
134
+ }
135
+ /**
136
+ * Detect whether the command being dispatched should bypass trigger
137
+ * detection because it is rendering command-local help. Command help
138
+ * (`<command> --help` / `-h`) must remain usable even when config.json
139
+ * is corrupt or absent — rendering help never needs a credential.
140
+ *
141
+ * This is a shallow peek at the arg list: it only looks for the help
142
+ * flag, mirroring the per-handler short-circuit (`flags.help ||
143
+ * flags.h`). It does NOT consume the args; the handler still parses
144
+ * them itself.
145
+ */
146
+ export function isCommandHelpInvocation(commandArgs) {
147
+ return commandArgs.includes("--help") || commandArgs.includes("-h");
148
+ }
149
+ //# sourceMappingURL=trigger-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-detection.js","sourceRoot":"","sources":["../../src/lib/trigger-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1F;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAqCjG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAoC;IAC1E,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACxD,MAAM,gBAAgB,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,qBAAqB,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;IACnF,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC1E,MAAM,GAAG,GAAG,QAAQ,EAAE,MAAM,CAAC;QAC7B,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC7B,CAAC;IACD,IAAI,gBAAgB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC9B,CAAC;IACD,IAAI,gBAAgB,IAAI,iBAAiB,EAAE,CAAC;QAC1C,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAClC,CAAC;IACD,sEAAsE;IACtE,iEAAiE;IACjE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CACL,qDAAqD;QACrD,kEAAkE;QAClE,wEAAwE;QACxE,gDAAgD,CACjD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAA0C;IAE1C,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,iBAAiB,IAAI,UAAU,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5E,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GACR,IAAI,CAAC,MAAM,GAAG,CAAC;QACb,CAAC,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,0DAA0D;QACzF,CAAC,CAAC,+FAA+F,CAAC;IACtG,MAAM,OAAO,GAAG,iFAAiF,CAAC;IAClG,OAAO,IAAI,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAA8B;IACpE,OAAO,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC"}
package/dist/lib/tty.d.ts CHANGED
@@ -41,7 +41,14 @@ import type { QuotaDashboard } from "../capabilities/quota.js";
41
41
  * Provider-neutral TTY rendering of a {@link QuotaDashboard}. Each
42
42
  * Provider entry is labelled with its Provider id and each category by
43
43
  * its normalized name; progress bars represent the REMAINING percentage.
44
+ *
45
+ * PB-T5: a successful row carries a `quotaSource` label rendered as a
46
+ * separate `source` line beneath the categories (snapshot/live, fresh/
47
+ * stale). The new `"none"` status (no quota Capability — Exa) renders
48
+ * a single dim line; it never appears as a failure. The `now` argument
49
+ * defaults to `Date.now()` so existing callers keep working; tests
50
+ * inject a fixed clock for deterministic age labels.
44
51
  */
45
- export declare function formatQuotaDashboard(dashboard: QuotaDashboard): string;
52
+ export declare function formatQuotaDashboard(dashboard: QuotaDashboard, now?: number): string;
46
53
  export {};
47
54
  //# sourceMappingURL=tty.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tty.d.ts","sourceRoot":"","sources":["../../src/lib/tty.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,KAAK,OAAO,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAUrC,eAAO,MAAM,KAAK;IAChB,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,KAAK;IACL,MAAM;IACN,GAAG;IACH,OAAO;IACP,IAAI;CACL,CAAC;AAEF,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAClD,KAAK,GAAG,SAAS,CAKnB;AAED,UAAU,gBAAgB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAe7E;AAED,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAsE/D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAkBtE"}
1
+ {"version":3,"file":"tty.d.ts","sourceRoot":"","sources":["../../src/lib/tty.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,KAAK,OAAO,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAUrC,eAAO,MAAM,KAAK;IAChB,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,KAAK;IACL,MAAM;IACN,GAAG;IACH,OAAO;IACP,IAAI;CACL,CAAC;AAEF,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAClD,KAAK,GAAG,SAAS,CAKnB;AAED,UAAU,gBAAgB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAe7E;AAED,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAyH/D;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,CAyBhG"}
package/dist/lib/tty.js CHANGED
@@ -96,12 +96,56 @@ function renderCategory(category, lines) {
96
96
  lines.push(` ${color.gray("weekly")} ${remainingBar(w.remainingPercent)} ${w.remainingPercent}%`);
97
97
  }
98
98
  }
99
+ /**
100
+ * Format a relative-age label for an epoch-ms timestamp (PB-T5).
101
+ * Returns a short "Xm"/"Xh"/"Xd" string suitable for a TTY row. Used
102
+ * to surface `observedAt` age so a user can judge whether a selection
103
+ * pick was made against stale or fresh data. The `now` parameter is
104
+ * injected by the caller so a deterministic test clock produces a
105
+ * deterministic label.
106
+ */
107
+ function formatObservedAge(observedAt, now) {
108
+ const diff = Math.max(0, now - observedAt);
109
+ const sec = Math.floor(diff / 1000);
110
+ if (sec < 60)
111
+ return `${sec}s`;
112
+ const min = Math.floor(sec / 60);
113
+ if (min < 60)
114
+ return `${min}m`;
115
+ const hr = Math.floor(min / 60);
116
+ if (hr < 24)
117
+ return `${hr}h`;
118
+ const day = Math.floor(hr / 24);
119
+ return `${day}d`;
120
+ }
121
+ /**
122
+ * Render the quotaSource label for a successful row (PB-T5). Mirrors
123
+ * the public {@link QuotaSourceLabel} shape: source/observedAt/
124
+ * authoritative. A non-authoritative row is dimmed so a user can see
125
+ * at a glance that the displayed numbers may be stale. The age label
126
+ * is computed against the injected clock so tests stay deterministic.
127
+ */
128
+ function renderSourceLabel(source, lines, now) {
129
+ const age = formatObservedAge(source.observedAt, now);
130
+ const authority = source.authoritative
131
+ ? color.green("fresh")
132
+ : color.yellow("stale · non-authoritative");
133
+ const sourceTxt = source.source === "snapshot" ? color.gray("snapshot") : color.cyan("live");
134
+ lines.push(` ${color.gray("source")} ${sourceTxt} · ${authority} ${color.gray(`(${age} ago`)}`);
135
+ }
99
136
  /**
100
137
  * Provider-neutral TTY rendering of a {@link QuotaDashboard}. Each
101
138
  * Provider entry is labelled with its Provider id and each category by
102
139
  * its normalized name; progress bars represent the REMAINING percentage.
140
+ *
141
+ * PB-T5: a successful row carries a `quotaSource` label rendered as a
142
+ * separate `source` line beneath the categories (snapshot/live, fresh/
143
+ * stale). The new `"none"` status (no quota Capability — Exa) renders
144
+ * a single dim line; it never appears as a failure. The `now` argument
145
+ * defaults to `Date.now()` so existing callers keep working; tests
146
+ * inject a fixed clock for deterministic age labels.
103
147
  */
104
- export function formatQuotaDashboard(dashboard) {
148
+ export function formatQuotaDashboard(dashboard, now = Date.now()) {
105
149
  const lines = [""];
106
150
  for (const entry of dashboard.providers) {
107
151
  if (entry.status === "ok") {
@@ -111,6 +155,14 @@ export function formatQuotaDashboard(dashboard) {
111
155
  for (const category of success.categories) {
112
156
  renderCategory(category, lines);
113
157
  }
158
+ if (success.quotaSource) {
159
+ renderSourceLabel(success.quotaSource, lines, now);
160
+ }
161
+ }
162
+ else if (entry.status === "none") {
163
+ const none = entry;
164
+ lines.push(` ${color.bold(none.provider)} ${color.dim("(no quota)")}`);
165
+ lines.push(` ${color.gray("no quota capability — skipping probe")}`);
114
166
  }
115
167
  else {
116
168
  const failure = entry;
@@ -1 +1 @@
1
- {"version":3,"file":"tty.js","sourceRoot":"","sources":["../../src/lib/tty.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tty.js","sourceRoot":"","sources":["../../src/lib/tty.ts"],"names":[],"mappings":"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"}
@@ -18,14 +18,34 @@
18
18
  */
19
19
  import type { ProviderDescriptor } from "../types.js";
20
20
  import { type BraveTransportDeps } from "./client.js";
21
+ import type { BraveRateLimitHeaders } from "./client.js";
21
22
  /**
22
23
  * Dependencies the Brave Adapter accepts. The unified `transport`
23
24
  * seam carries `fetch` and timer injection; the Search Capability
24
25
  * threads it through to the direct-HTTP transport.
26
+ *
27
+ * `onRateLimitHarvest` (PB-T1) is the passive-harvest callback invoked
28
+ * with Brave's `X-RateLimit-*` headers after every successful web
29
+ * search. Production wires a default that normalizes the headers via
30
+ * `normalizeBraveQuota` and writes a raw-category snapshot to
31
+ * `~/.scoutline/state.json` through `writeQuotaSnapshot`; the write is
32
+ * awaited (so it survives the bin's immediate `process.exit`) and
33
+ * fail-open (a write error is isolated to a stderr warning and never
34
+ * converts the search success into a fallback). Tests inject a double
35
+ * to keep harvest assertions hermetic. `undefined` disables the
36
+ * harvest (used by tests that only exercise search-result
37
+ * normalization).
25
38
  */
26
39
  export interface BraveAdapterDependencies {
27
40
  /** Optional transport injection (fetch, timers, env). */
28
41
  readonly transport?: BraveTransportDeps;
42
+ /**
43
+ * Optional passive-harvest callback fired with Brave's
44
+ * `X-RateLimit-*` headers after a successful web search. Production
45
+ * wires a default (see {@link createDefaultBraveHarvest}); tests
46
+ * inject a recorder or `undefined` to disable.
47
+ */
48
+ readonly onRateLimitHarvest?: (headers: BraveRateLimitHeaders) => Promise<void> | void;
29
49
  }
30
50
  /**
31
51
  * Build the Brave Provider Descriptor. The descriptor advertises the
@@ -37,6 +57,12 @@ export interface BraveAdapterDependencies {
37
57
  * (typically a fake-fetch wrapper); production uses the no-argument
38
58
  * factory which resolves to the global `fetch` and timers inside the
39
59
  * transport Module.
60
+ *
61
+ * PB-T1: the no-argument factory wires the production passive-harvest
62
+ * callback ({@link createDefaultBraveHarvest}) so every successful web
63
+ * search writes a rate-limit snapshot to `state.json`. Tests inject
64
+ * `onRateLimitHarvest` (or `undefined` to disable) through
65
+ * `BraveAdapterDependencies`.
40
66
  */
41
67
  export declare function createBraveDescriptor(dependencies?: BraveAdapterDependencies): ProviderDescriptor;
42
68
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/brave/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAIV,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAqBrB,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAIrB;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAweD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAqBjG"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/brave/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAIV,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAqBrB,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxF;AA6iBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CA4BjG"}
@@ -22,7 +22,8 @@ import { applySearchTopic } from "../../lib/search-topic.js";
22
22
  import { requireBraveApiKey, isBraveConfigured } from "./credentials.js";
23
23
  import { fetchBraveSearch, fetchBraveNewsSearch, fetchBraveVideoSearch, fetchBraveLlmContext, } from "./client.js";
24
24
  import { createBraveDiagnosticsCapability } from "./diagnostics.js";
25
- import { createBraveQuotaCapability } from "./quota.js";
25
+ import { createBraveQuotaCapability, normalizeBraveQuota } from "./quota.js";
26
+ import { writeQuotaSnapshot } from "../../lib/quota-store.js";
26
27
  // ---------------------------------------------------------------------------
27
28
  // Provider-owned credential fingerprint
28
29
  // ---------------------------------------------------------------------------
@@ -382,7 +383,7 @@ function normalizeBraveError(error) {
382
383
  return new ApiError("Brave request failed", inferStatusCode(lower));
383
384
  }
384
385
  function createBraveSearchCapability(options) {
385
- const { env, transport } = options;
386
+ const { env, transport, onRateLimitHarvest } = options;
386
387
  const capability = {
387
388
  validate(request) {
388
389
  if (!request || typeof request.query !== "string" || request.query.trim() === "") {
@@ -450,7 +451,7 @@ function createBraveSearchCapability(options) {
450
451
  ? await fetchBraveLlmContext(apiKey, effectiveQuery, params, transport)
451
452
  : isNews
452
453
  ? await fetchBraveNewsSearch(apiKey, effectiveQuery, params, transport)
453
- : await fetchBraveSearch(apiKey, effectiveQuery, params, transport);
454
+ : await fetchBraveSearch(apiKey, effectiveQuery, params, transport, onRateLimitHarvest);
454
455
  return isVideo
455
456
  ? normalizeBraveTopLevelResults(raw)
456
457
  : isHigh
@@ -467,6 +468,54 @@ function createBraveSearchCapability(options) {
467
468
  return capability;
468
469
  }
469
470
  // ---------------------------------------------------------------------------
471
+ // Passive harvest default (PB-T1)
472
+ // ---------------------------------------------------------------------------
473
+ /**
474
+ * Build the production passive-harvest callback for Brave rate-limit
475
+ * headers. Normalizes the four `X-RateLimit-*` headers via the SAME
476
+ * `normalizeBraveQuota` the explicit quota probe uses (so the
477
+ * harvested snapshot matches the probed snapshot's category shape
478
+ * exactly), then writes a raw-category snapshot to the quota store
479
+ * through {@link writeQuotaSnapshot}. The write advances `observedAt`
480
+ * only; `locallyUpdatedAt` is preserved by the store (PB-T2 advances
481
+ * it).
482
+ *
483
+ * Fail-open: any error (header parse failure, store write failure) is
484
+ * caught and routed to a stderr notice. The returned callback never
485
+ * rejects, so the transport's `try/catch` around the callback is
486
+ * belt-and-suspenders — a harvest failure cannot convert a search
487
+ * success into a fallback.
488
+ *
489
+ * `now` is injectable so tests can assert deterministic `observedAt` /
490
+ * `resetsAt` values without touching `Date.now`.
491
+ */
492
+ function createDefaultBraveHarvest(now = Date.now) {
493
+ return async (headers) => {
494
+ // Skip silently when no rate-limit headers are present — Brave
495
+ // does not always attach them (e.g. metered plans, proxy
496
+ // gateways). An absent header set is the common case, not an
497
+ // error worth a stderr notice.
498
+ if (headers.policy === null &&
499
+ headers.limit === null &&
500
+ headers.remaining === null &&
501
+ headers.reset === null) {
502
+ return;
503
+ }
504
+ try {
505
+ const result = normalizeBraveQuota(headers, now);
506
+ const snapshot = {
507
+ observedAt: now(),
508
+ categories: result.categories,
509
+ };
510
+ await writeQuotaSnapshot("brave", snapshot);
511
+ }
512
+ catch (error) {
513
+ const message = error instanceof Error ? error.message : String(error);
514
+ process.stderr.write(`scoutline: brave quota harvest failed: ${message}\n`);
515
+ }
516
+ };
517
+ }
518
+ // ---------------------------------------------------------------------------
470
519
  // Descriptor factory
471
520
  // ---------------------------------------------------------------------------
472
521
  /**
@@ -479,9 +528,16 @@ function createBraveSearchCapability(options) {
479
528
  * (typically a fake-fetch wrapper); production uses the no-argument
480
529
  * factory which resolves to the global `fetch` and timers inside the
481
530
  * transport Module.
531
+ *
532
+ * PB-T1: the no-argument factory wires the production passive-harvest
533
+ * callback ({@link createDefaultBraveHarvest}) so every successful web
534
+ * search writes a rate-limit snapshot to `state.json`. Tests inject
535
+ * `onRateLimitHarvest` (or `undefined` to disable) through
536
+ * `BraveAdapterDependencies`.
482
537
  */
483
538
  export function createBraveDescriptor(dependencies) {
484
539
  const transport = dependencies?.transport;
540
+ const onRateLimitHarvest = dependencies?.onRateLimitHarvest ?? createDefaultBraveHarvest();
485
541
  return {
486
542
  id: "brave",
487
543
  isConfigured(env) {
@@ -494,11 +550,17 @@ export function createBraveDescriptor(dependencies) {
494
550
  return new Set(["search", "diagnostics", "quota"]);
495
551
  },
496
552
  create(context) {
497
- const search = createBraveSearchCapability({ env: context.env, transport });
553
+ const search = createBraveSearchCapability({
554
+ env: context.env,
555
+ transport,
556
+ onRateLimitHarvest,
557
+ });
498
558
  const diagnostics = createBraveDiagnosticsCapability({ env: context.env, transport });
499
559
  const quota = createBraveQuotaCapability({ env: context.env, transport });
500
560
  return { id: "brave", search, diagnostics, quota };
501
561
  },
562
+ // Provider-fallback Ticket 02 — see ProviderDescriptor.credentialEnvVars.
563
+ credentialEnvVars: ["BRAVE_SEARCH_API_KEY"],
502
564
  };
503
565
  }
504
566
  //# sourceMappingURL=adapter.js.map