@vellumai/assistant 0.4.53 → 0.4.54

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 (247) hide show
  1. package/bun.lock +62 -349
  2. package/docs/architecture/integrations.md +1 -1
  3. package/docs/architecture/keychain-broker.md +94 -29
  4. package/docs/architecture/security.md +2 -2
  5. package/knip.json +7 -29
  6. package/package.json +2 -9
  7. package/src/__tests__/agent-loop.test.ts +1 -1
  8. package/src/__tests__/app-git-history.test.ts +0 -2
  9. package/src/__tests__/app-git-service.test.ts +1 -6
  10. package/src/__tests__/approval-cascade.test.ts +0 -1
  11. package/src/__tests__/avatar-e2e.test.ts +0 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +1 -6
  13. package/src/__tests__/call-domain.test.ts +0 -1
  14. package/src/__tests__/call-routes-http.test.ts +0 -1
  15. package/src/__tests__/channel-guardian.test.ts +4 -4
  16. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  17. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  18. package/src/__tests__/checker.test.ts +13 -11
  19. package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +0 -3
  22. package/src/__tests__/config-schema.test.ts +3 -9
  23. package/src/__tests__/config-watcher.test.ts +11 -3
  24. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  25. package/src/__tests__/credential-broker-server-use.test.ts +60 -24
  26. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  27. package/src/__tests__/credential-security-invariants.test.ts +13 -8
  28. package/src/__tests__/credential-vault-unit.test.ts +28 -12
  29. package/src/__tests__/credential-vault.test.ts +40 -28
  30. package/src/__tests__/credentials-cli.test.ts +1 -21
  31. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  32. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  34. package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
  35. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  36. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  37. package/src/__tests__/guardian-action-store.test.ts +0 -57
  38. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  39. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  40. package/src/__tests__/hooks-blocking.test.ts +1 -1
  41. package/src/__tests__/hooks-config.test.ts +5 -29
  42. package/src/__tests__/hooks-discovery.test.ts +1 -1
  43. package/src/__tests__/hooks-integration.test.ts +1 -1
  44. package/src/__tests__/hooks-manager.test.ts +1 -1
  45. package/src/__tests__/hooks-runner.test.ts +1 -23
  46. package/src/__tests__/hooks-settings.test.ts +1 -1
  47. package/src/__tests__/hooks-templates.test.ts +1 -1
  48. package/src/__tests__/integration-status.test.ts +0 -1
  49. package/src/__tests__/invite-routes-http.test.ts +0 -3
  50. package/src/__tests__/llm-usage-store.test.ts +50 -0
  51. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  52. package/src/__tests__/media-generate-image.test.ts +2 -2
  53. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
  54. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  55. package/src/__tests__/memory-regressions.test.ts +27 -27
  56. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  57. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  58. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  59. package/src/__tests__/oauth-cli.test.ts +1 -4
  60. package/src/__tests__/oauth-store.test.ts +1 -3
  61. package/src/__tests__/openai-provider.test.ts +7 -7
  62. package/src/__tests__/platform.test.ts +14 -4
  63. package/src/__tests__/pricing.test.ts +0 -223
  64. package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
  65. package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
  66. package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
  67. package/src/__tests__/provider-registry-ollama.test.ts +3 -3
  68. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  69. package/src/__tests__/registry.test.ts +3 -103
  70. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  71. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  72. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
  73. package/src/__tests__/secure-keys.test.ts +241 -229
  74. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  75. package/src/__tests__/session-confirmation-signals.test.ts +0 -1
  76. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  77. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  78. package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
  79. package/src/__tests__/session-queue.test.ts +2 -4
  80. package/src/__tests__/session-slash-known.test.ts +0 -1
  81. package/src/__tests__/session-slash-queue.test.ts +0 -1
  82. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  83. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  84. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  85. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  86. package/src/__tests__/slack-channel-config.test.ts +1 -7
  87. package/src/__tests__/swarm-recursion.test.ts +0 -1
  88. package/src/__tests__/swarm-session-integration.test.ts +0 -1
  89. package/src/__tests__/swarm-tool.test.ts +0 -1
  90. package/src/__tests__/task-compiler.test.ts +1 -1
  91. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  92. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  93. package/src/__tests__/tool-executor.test.ts +1 -1
  94. package/src/__tests__/trust-store.test.ts +3 -82
  95. package/src/__tests__/twilio-config.test.ts +0 -1
  96. package/src/__tests__/twilio-provider.test.ts +0 -5
  97. package/src/__tests__/twilio-routes.test.ts +0 -1
  98. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  99. package/src/calls/guardian-question-copy.ts +1 -1
  100. package/src/cli/commands/doctor.ts +10 -34
  101. package/src/cli/commands/memory.ts +3 -5
  102. package/src/cli/commands/sessions.ts +1 -1
  103. package/src/cli/commands/usage.ts +359 -0
  104. package/src/cli/http-client.ts +22 -12
  105. package/src/cli/program.ts +2 -0
  106. package/src/cli/reference.ts +1 -0
  107. package/src/cli.ts +251 -181
  108. package/src/config/assistant-feature-flags.ts +0 -7
  109. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  110. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  111. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  112. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  113. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  114. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  115. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  116. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  117. package/src/config/env.ts +13 -0
  118. package/src/config/feature-flag-registry.json +9 -41
  119. package/src/config/schemas/security.ts +1 -2
  120. package/src/config/skills.ts +1 -1
  121. package/src/contacts/contact-store.ts +0 -50
  122. package/src/daemon/approved-devices-store.ts +0 -44
  123. package/src/daemon/classifier.ts +1 -1
  124. package/src/daemon/config-watcher.ts +12 -6
  125. package/src/daemon/handlers/config-model.ts +1 -1
  126. package/src/daemon/handlers/sessions.ts +4 -116
  127. package/src/daemon/handlers/skills.ts +1 -1
  128. package/src/daemon/lifecycle.ts +13 -15
  129. package/src/daemon/providers-setup.ts +1 -1
  130. package/src/daemon/server.ts +19 -3
  131. package/src/daemon/session-slash.ts +2 -2
  132. package/src/daemon/shutdown-handlers.ts +15 -0
  133. package/src/daemon/watch-handler.ts +2 -2
  134. package/src/email/guardrails.ts +1 -1
  135. package/src/email/service.ts +0 -5
  136. package/src/hooks/templates.ts +1 -1
  137. package/src/media/app-icon-generator.ts +2 -2
  138. package/src/media/avatar-router.ts +2 -2
  139. package/src/media/gemini-image-service.ts +5 -5
  140. package/src/memory/admin.ts +2 -2
  141. package/src/memory/app-git-service.ts +0 -7
  142. package/src/memory/conversation-crud.ts +1 -1
  143. package/src/memory/conversation-title-service.ts +2 -2
  144. package/src/memory/embedding-backend.ts +30 -26
  145. package/src/memory/external-conversation-store.ts +0 -30
  146. package/src/memory/guardian-action-store.ts +0 -31
  147. package/src/memory/guardian-approvals.ts +1 -56
  148. package/src/memory/indexer.ts +4 -3
  149. package/src/memory/items-extractor.ts +1 -1
  150. package/src/memory/job-handlers/backfill.ts +5 -2
  151. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  152. package/src/memory/job-handlers/media-processing.ts +2 -2
  153. package/src/memory/job-handlers/summarization.ts +1 -1
  154. package/src/memory/job-utils.ts +1 -2
  155. package/src/memory/jobs-worker.ts +2 -2
  156. package/src/memory/llm-usage-store.ts +57 -11
  157. package/src/memory/media-store.ts +4 -535
  158. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  159. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  160. package/src/memory/published-pages-store.ts +0 -83
  161. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  162. package/src/memory/retriever.ts +1 -1
  163. package/src/memory/search/semantic.ts +1 -8
  164. package/src/memory/shared-app-links-store.ts +0 -15
  165. package/src/messaging/registry.ts +0 -5
  166. package/src/messaging/style-analyzer.ts +1 -1
  167. package/src/notifications/copy-composer.ts +5 -13
  168. package/src/notifications/decision-engine.ts +2 -2
  169. package/src/notifications/deliveries-store.ts +0 -39
  170. package/src/notifications/guardian-question-mode.ts +6 -10
  171. package/src/notifications/preference-extractor.ts +1 -1
  172. package/src/oauth/byo-connection.test.ts +29 -20
  173. package/src/oauth/provider-behaviors.ts +1 -1
  174. package/src/permissions/checker.ts +1 -1
  175. package/src/permissions/shell-identity.ts +0 -5
  176. package/src/permissions/trust-store.ts +0 -37
  177. package/src/prompts/system-prompt.ts +3 -3
  178. package/src/providers/managed-proxy/constants.ts +8 -10
  179. package/src/providers/managed-proxy/context.ts +14 -9
  180. package/src/providers/provider-send-message.ts +4 -52
  181. package/src/providers/registry.ts +16 -50
  182. package/src/runtime/actor-token-store.ts +0 -23
  183. package/src/runtime/http-router.ts +5 -1
  184. package/src/runtime/http-server.ts +101 -4
  185. package/src/runtime/invite-instruction-generator.ts +25 -51
  186. package/src/runtime/invite-service.ts +0 -20
  187. package/src/runtime/routes/attachment-routes.ts +1 -1
  188. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  189. package/src/runtime/routes/call-routes.ts +1 -1
  190. package/src/runtime/routes/conversation-routes.ts +32 -11
  191. package/src/runtime/routes/debug-routes.ts +1 -1
  192. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  193. package/src/runtime/routes/documents-routes.ts +3 -3
  194. package/src/runtime/routes/global-search-routes.ts +1 -1
  195. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  196. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  197. package/src/runtime/routes/secret-routes.ts +4 -4
  198. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  199. package/src/security/credential-backend.ts +148 -0
  200. package/src/security/oauth2.ts +1 -1
  201. package/src/security/secret-allowlist.ts +1 -1
  202. package/src/security/secure-keys.ts +98 -160
  203. package/src/security/token-manager.ts +0 -7
  204. package/src/sequence/guardrails.ts +0 -4
  205. package/src/sequence/store.ts +1 -20
  206. package/src/sequence/types.ts +1 -36
  207. package/src/signals/cancel.ts +69 -0
  208. package/src/signals/conversation-undo.ts +127 -0
  209. package/src/signals/trust-rule.ts +174 -0
  210. package/src/skills/clawhub.ts +5 -5
  211. package/src/skills/managed-store.ts +4 -4
  212. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  213. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  214. package/src/tools/claude-code/claude-code.ts +2 -2
  215. package/src/tools/credentials/vault.ts +8 -4
  216. package/src/tools/memory/handlers.test.ts +24 -26
  217. package/src/tools/memory/handlers.ts +1 -13
  218. package/src/tools/registry.ts +5 -100
  219. package/src/tools/terminal/parser.ts +34 -4
  220. package/src/tools/tool-manifest.ts +0 -10
  221. package/src/usage/actors.ts +0 -12
  222. package/src/util/canonicalize-identity.ts +0 -9
  223. package/src/util/errors.ts +0 -3
  224. package/src/util/platform.ts +24 -7
  225. package/src/util/pricing.ts +0 -38
  226. package/src/watcher/constants.ts +0 -7
  227. package/src/watcher/providers/linear.ts +1 -1
  228. package/src/work-items/work-item-store.ts +4 -4
  229. package/src/workspace/commit-message-provider.ts +1 -1
  230. package/src/workspace/git-service.ts +44 -1
  231. package/src/workspace/provider-commit-message-generator.ts +1 -1
  232. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  233. package/src/browser-extension-relay/client.ts +0 -155
  234. package/src/contacts/index.ts +0 -18
  235. package/src/daemon/tls-certs.ts +0 -270
  236. package/src/errors.ts +0 -41
  237. package/src/events/index.ts +0 -18
  238. package/src/followups/index.ts +0 -10
  239. package/src/playbooks/index.ts +0 -10
  240. package/src/runtime/auth/index.ts +0 -44
  241. package/src/tasks/candidate-store.ts +0 -95
  242. package/src/tools/browser/api-map.ts +0 -313
  243. package/src/tools/browser/auto-navigate.ts +0 -469
  244. package/src/tools/browser/headless-browser.ts +0 -590
  245. package/src/tools/browser/recording-store.ts +0 -75
  246. package/src/tools/computer-use/registry.ts +0 -21
  247. package/src/tools/tasks/index.ts +0 -27
@@ -1,313 +0,0 @@
1
- /**
2
- * Recording analyzer that processes NetworkRecordedEntry[] into a deduplicated
3
- * API map. Collapses ID-like path segments into {id} placeholders so repeated
4
- * calls to the same endpoint are grouped together.
5
- */
6
-
7
- import { mkdirSync, writeFileSync } from "fs";
8
- import { join } from "path";
9
-
10
- import { getCliLogger } from "../../util/logger.js";
11
- import { getDataDir } from "../../util/platform.js";
12
- import type { NetworkRecordedEntry } from "./network-recording-types.js";
13
-
14
- const log = getCliLogger("api-map");
15
-
16
- // ---------------------------------------------------------------------------
17
- // Types
18
- // ---------------------------------------------------------------------------
19
-
20
- export interface ApiEndpoint {
21
- method: string;
22
- urlPattern: string;
23
- exampleUrl: string;
24
- queryParams: string[];
25
- requestBodyKeys: string[];
26
- responseStatus: number[];
27
- responseBodyKeys: string[];
28
- count: number;
29
- }
30
-
31
- export interface ApiMapResult {
32
- domain: string;
33
- analyzedAt: number;
34
- totalRequests: number;
35
- endpoints: ApiEndpoint[];
36
- }
37
-
38
- // ---------------------------------------------------------------------------
39
- // Helpers
40
- // ---------------------------------------------------------------------------
41
-
42
- const UUID_RE =
43
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
- const NUMERIC_RE = /^\d+$/;
45
- const HEX_HASH_RE = /^[0-9a-f]{8,}$/i;
46
- const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
47
-
48
- /** URL path patterns that indicate non-API noise. */
49
- const NOISE_PATH_PATTERNS = [
50
- /\/web-translations\//,
51
- /\/cdn-cgi\//,
52
- /\.properties$/,
53
- /\.js$/,
54
- /\.css$/,
55
- /\.woff2?$/,
56
- /\.png$/,
57
- /\.jpg$/,
58
- /\.svg$/,
59
- /\.ico$/,
60
- /\.map$/,
61
- /\/preference\//,
62
- /\/userpreference-service\//,
63
- ];
64
-
65
- /** Returns true when a path segment looks like a dynamic ID. */
66
- function isIdSegment(segment: string): boolean {
67
- if (NUMERIC_RE.test(segment)) return true;
68
- if (UUID_RE.test(segment)) return true;
69
- if (HEX_HASH_RE.test(segment)) return true;
70
- if (DATE_RE.test(segment)) return true;
71
- return false;
72
- }
73
-
74
- /** Replace ID-like path segments with `{id}`. */
75
- function normalizePathSegments(pathname: string): string {
76
- return pathname
77
- .split("/")
78
- .map((seg) => (isIdSegment(seg) ? "{id}" : seg))
79
- .join("/");
80
- }
81
-
82
- /** Safely parse JSON, returning undefined on failure. */
83
- function tryParseJson(
84
- text: string | undefined,
85
- ): Record<string, unknown> | undefined {
86
- if (!text) return undefined;
87
- try {
88
- const parsed = JSON.parse(text);
89
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
90
- return parsed as Record<string, unknown>;
91
- }
92
- } catch {
93
- // not JSON
94
- }
95
- return undefined;
96
- }
97
-
98
- /** Extract GraphQL operation name from request body. */
99
- function extractGraphQLOperationName(
100
- postData: string | undefined,
101
- ): string | null {
102
- if (!postData) return null;
103
- const body = tryParseJson(postData);
104
- if (!body) return null;
105
- if (typeof body.operationName === "string" && body.operationName)
106
- return body.operationName;
107
- // Try extracting from query string: "query FooBar { ..." or "mutation FooBar { ..."
108
- if (typeof body.query === "string") {
109
- const named = body.query.match(/(?:query|mutation|subscription)\s+(\w+)/);
110
- if (named) return named[1];
111
- // Unnamed query — extract the first field name: "query{fooBar(" or "query { fooBar {"
112
- const firstField = body.query.match(
113
- /(?:query|mutation|subscription)\s*\{?\s*(\w+)/,
114
- );
115
- if (firstField) return firstField[1];
116
- }
117
- return null;
118
- }
119
-
120
- // ---------------------------------------------------------------------------
121
- // Core analysis
122
- // ---------------------------------------------------------------------------
123
-
124
- interface GroupData {
125
- method: string;
126
- urlPattern: string;
127
- exampleUrl: string;
128
- queryParams: Set<string>;
129
- requestBodyKeys: Set<string>;
130
- responseStatus: Set<number>;
131
- responseBodyKeys: Set<string>;
132
- count: number;
133
- }
134
-
135
- export function analyzeApiMap(
136
- entries: NetworkRecordedEntry[],
137
- domain: string,
138
- ): ApiMapResult {
139
- const groups = new Map<string, GroupData>();
140
-
141
- for (const entry of entries) {
142
- const { request, response } = entry;
143
- let parsed: URL;
144
- try {
145
- parsed = new URL(request.url);
146
- } catch {
147
- continue;
148
- }
149
-
150
- // Skip non-API noise
151
- if (NOISE_PATH_PATTERNS.some((p) => p.test(parsed.pathname))) continue;
152
-
153
- // Skip non-JSON responses
154
- const mimeType = response?.mimeType ?? "";
155
- if (response && !mimeType.includes("json") && !mimeType.includes("graphql"))
156
- continue;
157
-
158
- const method = request.method.toUpperCase();
159
- const normalizedPath = normalizePathSegments(parsed.pathname);
160
- const basePattern = `${parsed.hostname}${normalizedPath}`;
161
-
162
- // For GraphQL endpoints, split by operation name
163
- let urlPattern = basePattern;
164
- const isGraphQL = normalizedPath.includes("graphql");
165
- if (isGraphQL && method === "POST") {
166
- const opName = extractGraphQLOperationName(request.postData);
167
- if (opName) {
168
- urlPattern = `${basePattern} → ${opName}`;
169
- }
170
- }
171
-
172
- const key = `${method} ${urlPattern}`;
173
-
174
- let group = groups.get(key);
175
- if (!group) {
176
- group = {
177
- method,
178
- urlPattern,
179
- exampleUrl: request.url,
180
- queryParams: new Set(),
181
- requestBodyKeys: new Set(),
182
- responseStatus: new Set(),
183
- responseBodyKeys: new Set(),
184
- count: 0,
185
- };
186
- groups.set(key, group);
187
- }
188
-
189
- group.count++;
190
-
191
- for (const paramKey of parsed.searchParams.keys()) {
192
- group.queryParams.add(paramKey);
193
- }
194
-
195
- if (["POST", "PUT", "PATCH"].includes(method)) {
196
- const body = tryParseJson(request.postData);
197
- if (body) {
198
- for (const k of Object.keys(body)) {
199
- if (k !== "query" && k !== "operationName" && k !== "extensions") {
200
- group.requestBodyKeys.add(k);
201
- }
202
- }
203
- }
204
- }
205
-
206
- if (response) {
207
- group.responseStatus.add(response.status);
208
- const resBody = tryParseJson(response.body);
209
- if (resBody) {
210
- for (const k of Object.keys(resBody)) {
211
- group.responseBodyKeys.add(k);
212
- }
213
- }
214
- }
215
- }
216
-
217
- const endpoints: ApiEndpoint[] = Array.from(groups.values()).map((g) => ({
218
- method: g.method,
219
- urlPattern: g.urlPattern,
220
- exampleUrl: g.exampleUrl,
221
- queryParams: Array.from(g.queryParams).sort(),
222
- requestBodyKeys: Array.from(g.requestBodyKeys).sort(),
223
- responseStatus: Array.from(g.responseStatus).sort((a, b) => a - b),
224
- responseBodyKeys: Array.from(g.responseBodyKeys).sort(),
225
- count: g.count,
226
- }));
227
-
228
- // Sort: data endpoints first (low count = unique pages), then boilerplate
229
- // Within each tier, sort alphabetically by pattern for readability
230
- endpoints.sort((a, b) => {
231
- const aIsBoilerplate = a.count > 15;
232
- const bIsBoilerplate = b.count > 15;
233
- if (aIsBoilerplate !== bIsBoilerplate) return aIsBoilerplate ? 1 : -1;
234
- return a.urlPattern.localeCompare(b.urlPattern);
235
- });
236
-
237
- const totalApiRequests = endpoints.reduce((sum, ep) => sum + ep.count, 0);
238
-
239
- return {
240
- domain,
241
- analyzedAt: Date.now(),
242
- totalRequests: totalApiRequests,
243
- endpoints,
244
- };
245
- }
246
-
247
- // ---------------------------------------------------------------------------
248
- // Persistence
249
- // ---------------------------------------------------------------------------
250
-
251
- export function saveApiMap(domain: string, result: ApiMapResult): string {
252
- const dir = join(getDataDir(), "api-maps");
253
- mkdirSync(dir, { recursive: true });
254
-
255
- const timestamp = Date.now();
256
- const filePath = join(dir, `${domain}-${timestamp}.json`);
257
- writeFileSync(filePath, JSON.stringify(result, null, 2));
258
- return filePath;
259
- }
260
-
261
- // ---------------------------------------------------------------------------
262
- // Pretty-print
263
- // ---------------------------------------------------------------------------
264
-
265
- export function printApiMapTable(result: ApiMapResult): void {
266
- const dataEndpoints = result.endpoints.filter((ep) => ep.count <= 15);
267
- const boilerplate = result.endpoints.filter((ep) => ep.count > 15);
268
-
269
- log.info(
270
- `\nAPI Map for ${result.domain} — ${result.endpoints.length} endpoints discovered\n`,
271
- );
272
-
273
- const stripDomain = (pattern: string) => {
274
- const idx = pattern.indexOf("/");
275
- return idx >= 0 ? pattern.slice(idx) : pattern;
276
- };
277
-
278
- const printSection = (title: string, eps: ApiEndpoint[]) => {
279
- if (eps.length === 0) return;
280
- log.info(` ${title} (${eps.length})\n`);
281
-
282
- const header = ["Method", "Endpoint", "Hits", "Response Keys"];
283
- const rows = eps.map((ep) => [
284
- ep.method,
285
- stripDomain(ep.urlPattern),
286
- String(ep.count),
287
- ep.responseBodyKeys.slice(0, 5).join(", ") || "-",
288
- ]);
289
-
290
- const widths = header.map((h, i) =>
291
- Math.min(
292
- i === 1 ? 72 : i === 3 ? 50 : 200,
293
- Math.max(h.length, ...rows.map((r) => r[i].length)),
294
- ),
295
- );
296
-
297
- const sep = widths.map((w) => "-".repeat(w)).join(" | ");
298
- const fmt = (row: string[]) =>
299
- row
300
- .map((cell, i) => cell.slice(0, widths[i]).padEnd(widths[i]))
301
- .join(" | ");
302
-
303
- log.info(` ${fmt(header)}`);
304
- log.info(` ${sep}`);
305
- for (const row of rows) {
306
- log.info(` ${fmt(row)}`);
307
- }
308
- log.info("");
309
- };
310
-
311
- printSection("DATA ENDPOINTS", dataEndpoints);
312
- printSection("PAGE-LOAD BOILERPLATE", boilerplate);
313
- }