@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.
- package/bun.lock +62 -349
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/keychain-broker.md +94 -29
- package/docs/architecture/security.md +2 -2
- package/knip.json +7 -29
- package/package.json +2 -9
- package/src/__tests__/agent-loop.test.ts +1 -1
- package/src/__tests__/app-git-history.test.ts +0 -2
- package/src/__tests__/app-git-service.test.ts +1 -6
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -6
- package/src/__tests__/call-domain.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -4
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +0 -1
- package/src/__tests__/checker.test.ts +13 -11
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
- package/src/__tests__/config-loader-backfill.test.ts +0 -3
- package/src/__tests__/config-schema.test.ts +3 -9
- package/src/__tests__/config-watcher.test.ts +11 -3
- package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
- package/src/__tests__/credential-broker-server-use.test.ts +60 -24
- package/src/__tests__/credential-security-e2e.test.ts +1 -6
- package/src/__tests__/credential-security-invariants.test.ts +13 -8
- package/src/__tests__/credential-vault-unit.test.ts +28 -12
- package/src/__tests__/credential-vault.test.ts +40 -28
- package/src/__tests__/credentials-cli.test.ts +1 -21
- package/src/__tests__/email-invite-adapter.test.ts +0 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
- package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
- package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
- package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
- package/src/__tests__/guardian-action-store.test.ts +0 -57
- package/src/__tests__/guardian-outbound-http.test.ts +1 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
- package/src/__tests__/hooks-blocking.test.ts +1 -1
- package/src/__tests__/hooks-config.test.ts +5 -29
- package/src/__tests__/hooks-discovery.test.ts +1 -1
- package/src/__tests__/hooks-integration.test.ts +1 -1
- package/src/__tests__/hooks-manager.test.ts +1 -1
- package/src/__tests__/hooks-runner.test.ts +1 -23
- package/src/__tests__/hooks-settings.test.ts +1 -1
- package/src/__tests__/hooks-templates.test.ts +1 -1
- package/src/__tests__/integration-status.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +0 -3
- package/src/__tests__/llm-usage-store.test.ts +50 -0
- package/src/__tests__/managed-proxy-context.test.ts +41 -41
- package/src/__tests__/media-generate-image.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
- package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
- package/src/__tests__/memory-regressions.test.ts +27 -27
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
- package/src/__tests__/notification-decision-fallback.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +1 -4
- package/src/__tests__/oauth-store.test.ts +1 -3
- package/src/__tests__/openai-provider.test.ts +7 -7
- package/src/__tests__/platform.test.ts +14 -4
- package/src/__tests__/pricing.test.ts +0 -223
- package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
- package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
- package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
- package/src/__tests__/provider-registry-ollama.test.ts +3 -3
- package/src/__tests__/public-ingress-urls.test.ts +1 -1
- package/src/__tests__/registry.test.ts +3 -103
- package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
- package/src/__tests__/secret-onetime-send.test.ts +1 -6
- package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
- package/src/__tests__/secure-keys.test.ts +241 -229
- package/src/__tests__/session-abort-tool-results.test.ts +0 -1
- package/src/__tests__/session-confirmation-signals.test.ts +0 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
- package/src/__tests__/session-pre-run-repair.test.ts +0 -1
- package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/session-queue.test.ts +2 -4
- package/src/__tests__/session-slash-known.test.ts +0 -1
- package/src/__tests__/session-slash-queue.test.ts +0 -1
- package/src/__tests__/session-slash-unknown.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +1 -7
- package/src/__tests__/swarm-recursion.test.ts +0 -1
- package/src/__tests__/swarm-session-integration.test.ts +0 -1
- package/src/__tests__/swarm-tool.test.ts +0 -1
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
- package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
- package/src/__tests__/tool-executor.test.ts +1 -1
- package/src/__tests__/trust-store.test.ts +3 -82
- package/src/__tests__/twilio-config.test.ts +0 -1
- package/src/__tests__/twilio-provider.test.ts +0 -5
- package/src/__tests__/twilio-routes.test.ts +0 -1
- package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
- package/src/calls/guardian-question-copy.ts +1 -1
- package/src/cli/commands/doctor.ts +10 -34
- package/src/cli/commands/memory.ts +3 -5
- package/src/cli/commands/sessions.ts +1 -1
- package/src/cli/commands/usage.ts +359 -0
- package/src/cli/http-client.ts +22 -12
- package/src/cli/program.ts +2 -0
- package/src/cli/reference.ts +1 -0
- package/src/cli.ts +251 -181
- package/src/config/assistant-feature-flags.ts +0 -7
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +0 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -1
- package/src/config/bundled-skills/sequences/SKILL.md +0 -1
- package/src/config/env.ts +13 -0
- package/src/config/feature-flag-registry.json +9 -41
- package/src/config/schemas/security.ts +1 -2
- package/src/config/skills.ts +1 -1
- package/src/contacts/contact-store.ts +0 -50
- package/src/daemon/approved-devices-store.ts +0 -44
- package/src/daemon/classifier.ts +1 -1
- package/src/daemon/config-watcher.ts +12 -6
- package/src/daemon/handlers/config-model.ts +1 -1
- package/src/daemon/handlers/sessions.ts +4 -116
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/lifecycle.ts +13 -15
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +19 -3
- package/src/daemon/session-slash.ts +2 -2
- package/src/daemon/shutdown-handlers.ts +15 -0
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/guardrails.ts +1 -1
- package/src/email/service.ts +0 -5
- package/src/hooks/templates.ts +1 -1
- package/src/media/app-icon-generator.ts +2 -2
- package/src/media/avatar-router.ts +2 -2
- package/src/media/gemini-image-service.ts +5 -5
- package/src/memory/admin.ts +2 -2
- package/src/memory/app-git-service.ts +0 -7
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-title-service.ts +2 -2
- package/src/memory/embedding-backend.ts +30 -26
- package/src/memory/external-conversation-store.ts +0 -30
- package/src/memory/guardian-action-store.ts +0 -31
- package/src/memory/guardian-approvals.ts +1 -56
- package/src/memory/indexer.ts +4 -3
- package/src/memory/items-extractor.ts +1 -1
- package/src/memory/job-handlers/backfill.ts +5 -2
- package/src/memory/job-handlers/index-maintenance.ts +2 -2
- package/src/memory/job-handlers/media-processing.ts +2 -2
- package/src/memory/job-handlers/summarization.ts +1 -1
- package/src/memory/job-utils.ts +1 -2
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/llm-usage-store.ts +57 -11
- package/src/memory/media-store.ts +4 -535
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
- package/src/memory/migrations/110-channel-guardian.ts +0 -1
- package/src/memory/published-pages-store.ts +0 -83
- package/src/memory/qdrant-circuit-breaker.ts +0 -8
- package/src/memory/retriever.ts +1 -1
- package/src/memory/search/semantic.ts +1 -8
- package/src/memory/shared-app-links-store.ts +0 -15
- package/src/messaging/registry.ts +0 -5
- package/src/messaging/style-analyzer.ts +1 -1
- package/src/notifications/copy-composer.ts +5 -13
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/deliveries-store.ts +0 -39
- package/src/notifications/guardian-question-mode.ts +6 -10
- package/src/notifications/preference-extractor.ts +1 -1
- package/src/oauth/byo-connection.test.ts +29 -20
- package/src/oauth/provider-behaviors.ts +1 -1
- package/src/permissions/checker.ts +1 -1
- package/src/permissions/shell-identity.ts +0 -5
- package/src/permissions/trust-store.ts +0 -37
- package/src/prompts/system-prompt.ts +3 -3
- package/src/providers/managed-proxy/constants.ts +8 -10
- package/src/providers/managed-proxy/context.ts +14 -9
- package/src/providers/provider-send-message.ts +4 -52
- package/src/providers/registry.ts +16 -50
- package/src/runtime/actor-token-store.ts +0 -23
- package/src/runtime/http-router.ts +5 -1
- package/src/runtime/http-server.ts +101 -4
- package/src/runtime/invite-instruction-generator.ts +25 -51
- package/src/runtime/invite-service.ts +0 -20
- package/src/runtime/routes/attachment-routes.ts +1 -1
- package/src/runtime/routes/brain-graph-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +32 -11
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +2 -2
- package/src/runtime/routes/documents-routes.ts +3 -3
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
- package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
- package/src/runtime/routes/secret-routes.ts +4 -4
- package/src/runtime/routes/trust-rules-routes.ts +1 -1
- package/src/security/credential-backend.ts +148 -0
- package/src/security/oauth2.ts +1 -1
- package/src/security/secret-allowlist.ts +1 -1
- package/src/security/secure-keys.ts +98 -160
- package/src/security/token-manager.ts +0 -7
- package/src/sequence/guardrails.ts +0 -4
- package/src/sequence/store.ts +1 -20
- package/src/sequence/types.ts +1 -36
- package/src/signals/cancel.ts +69 -0
- package/src/signals/conversation-undo.ts +127 -0
- package/src/signals/trust-rule.ts +174 -0
- package/src/skills/clawhub.ts +5 -5
- package/src/skills/managed-store.ts +4 -4
- package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
- package/src/telemetry/usage-telemetry-reporter.ts +181 -0
- package/src/tools/claude-code/claude-code.ts +2 -2
- package/src/tools/credentials/vault.ts +8 -4
- package/src/tools/memory/handlers.test.ts +24 -26
- package/src/tools/memory/handlers.ts +1 -13
- package/src/tools/registry.ts +5 -100
- package/src/tools/terminal/parser.ts +34 -4
- package/src/tools/tool-manifest.ts +0 -10
- package/src/usage/actors.ts +0 -12
- package/src/util/canonicalize-identity.ts +0 -9
- package/src/util/errors.ts +0 -3
- package/src/util/platform.ts +24 -7
- package/src/util/pricing.ts +0 -38
- package/src/watcher/constants.ts +0 -7
- package/src/watcher/providers/linear.ts +1 -1
- package/src/work-items/work-item-store.ts +4 -4
- package/src/workspace/commit-message-provider.ts +1 -1
- package/src/workspace/git-service.ts +44 -1
- package/src/workspace/provider-commit-message-generator.ts +1 -1
- package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
- package/src/browser-extension-relay/client.ts +0 -155
- package/src/contacts/index.ts +0 -18
- package/src/daemon/tls-certs.ts +0 -270
- package/src/errors.ts +0 -41
- package/src/events/index.ts +0 -18
- package/src/followups/index.ts +0 -10
- package/src/playbooks/index.ts +0 -10
- package/src/runtime/auth/index.ts +0 -44
- package/src/tasks/candidate-store.ts +0 -95
- package/src/tools/browser/api-map.ts +0 -313
- package/src/tools/browser/auto-navigate.ts +0 -469
- package/src/tools/browser/headless-browser.ts +0 -590
- package/src/tools/browser/recording-store.ts +0 -75
- package/src/tools/computer-use/registry.ts +0 -21
- 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
|
-
}
|