unbrowse 3.1.0 → 3.2.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 (107) hide show
  1. package/dist/cli.js +455 -96
  2. package/dist/index.js +2 -6
  3. package/dist/mcp.js +695 -46
  4. package/dist/server.js +25811 -0
  5. package/package.json +1 -2
  6. package/vendor/kuri/darwin-arm64/kuri +0 -0
  7. package/vendor/kuri/darwin-x64/kuri +0 -0
  8. package/vendor/kuri/linux-arm64/kuri +0 -0
  9. package/vendor/kuri/linux-x64/kuri +0 -0
  10. package/vendor/kuri/manifest.json +7 -10
  11. package/runtime-src/agent-outcome.ts +0 -166
  12. package/runtime-src/analytics-session.ts +0 -55
  13. package/runtime-src/api/browse-index.ts +0 -317
  14. package/runtime-src/api/browse-session.ts +0 -572
  15. package/runtime-src/api/browse-submit-prereqs.ts +0 -48
  16. package/runtime-src/api/browse-submit.ts +0 -1184
  17. package/runtime-src/api/routes.ts +0 -1823
  18. package/runtime-src/auth/browser-cookies.ts +0 -423
  19. package/runtime-src/auth/index.ts +0 -535
  20. package/runtime-src/auth/runtime.ts +0 -116
  21. package/runtime-src/browser/index.ts +0 -659
  22. package/runtime-src/browser/types.ts +0 -41
  23. package/runtime-src/build-info.generated.ts +0 -6
  24. package/runtime-src/capture/index.ts +0 -1794
  25. package/runtime-src/capture/prefetch.ts +0 -95
  26. package/runtime-src/capture/rsc.ts +0 -45
  27. package/runtime-src/cli/shortcuts.ts +0 -273
  28. package/runtime-src/cli.ts +0 -1572
  29. package/runtime-src/client/graph-client.ts +0 -100
  30. package/runtime-src/client/index.ts +0 -1425
  31. package/runtime-src/debug-trace.ts +0 -18
  32. package/runtime-src/domain.ts +0 -38
  33. package/runtime-src/execution/index.ts +0 -3397
  34. package/runtime-src/execution/retry.ts +0 -46
  35. package/runtime-src/execution/robots.ts +0 -167
  36. package/runtime-src/execution/search-forms.ts +0 -188
  37. package/runtime-src/extraction/index.ts +0 -1507
  38. package/runtime-src/foundry/publish-bundle.ts +0 -392
  39. package/runtime-src/graph/agent-augment.ts +0 -315
  40. package/runtime-src/graph/index.ts +0 -1524
  41. package/runtime-src/graph/local-fixtures.ts +0 -393
  42. package/runtime-src/graph/local-harness.ts +0 -646
  43. package/runtime-src/graph/planner.ts +0 -411
  44. package/runtime-src/graph/session.ts +0 -294
  45. package/runtime-src/graph/trace-store.ts +0 -136
  46. package/runtime-src/index.ts +0 -24
  47. package/runtime-src/indexer/index.ts +0 -465
  48. package/runtime-src/intent-match.ts +0 -1515
  49. package/runtime-src/kuri/client.ts +0 -1839
  50. package/runtime-src/logger.ts +0 -30
  51. package/runtime-src/marketplace/index.ts +0 -103
  52. package/runtime-src/mcp.ts +0 -1747
  53. package/runtime-src/orchestrator/browser-agent.ts +0 -374
  54. package/runtime-src/orchestrator/dag-advisor.ts +0 -59
  55. package/runtime-src/orchestrator/dag-feedback.ts +0 -257
  56. package/runtime-src/orchestrator/first-pass-action.ts +0 -403
  57. package/runtime-src/orchestrator/index.ts +0 -4480
  58. package/runtime-src/orchestrator/passive-publish.ts +0 -187
  59. package/runtime-src/orchestrator/timing-economics.ts +0 -80
  60. package/runtime-src/payments/cascade.ts +0 -137
  61. package/runtime-src/payments/index.ts +0 -270
  62. package/runtime-src/payments/lobster-pay.ts +0 -182
  63. package/runtime-src/payments/wallet.ts +0 -98
  64. package/runtime-src/publish/review-context.ts +0 -93
  65. package/runtime-src/publish/sanitize.ts +0 -197
  66. package/runtime-src/publish/schema-review.ts +0 -192
  67. package/runtime-src/publish-admission.ts +0 -388
  68. package/runtime-src/ratelimit/index.ts +0 -23
  69. package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
  70. package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
  71. package/runtime-src/reverse-engineer/index.ts +0 -1551
  72. package/runtime-src/router.ts +0 -17
  73. package/runtime-src/routing-telemetry.ts +0 -395
  74. package/runtime-src/runtime/browser-access.ts +0 -11
  75. package/runtime-src/runtime/browser-auth.ts +0 -12
  76. package/runtime-src/runtime/browser-host.ts +0 -48
  77. package/runtime-src/runtime/lifecycle.ts +0 -17
  78. package/runtime-src/runtime/local-server.ts +0 -311
  79. package/runtime-src/runtime/paths.ts +0 -99
  80. package/runtime-src/runtime/setup.ts +0 -251
  81. package/runtime-src/runtime/supervisor.ts +0 -69
  82. package/runtime-src/runtime/update-hints.ts +0 -351
  83. package/runtime-src/server.ts +0 -100
  84. package/runtime-src/session-logs.ts +0 -142
  85. package/runtime-src/settings.ts +0 -221
  86. package/runtime-src/single-binary.ts +0 -143
  87. package/runtime-src/site-policy.ts +0 -54
  88. package/runtime-src/stale-cleanup-runner.ts +0 -144
  89. package/runtime-src/stale-cleanup.ts +0 -133
  90. package/runtime-src/telemetry-attribution.ts +0 -120
  91. package/runtime-src/telemetry.ts +0 -253
  92. package/runtime-src/template-params.ts +0 -141
  93. package/runtime-src/transform/drift.ts +0 -60
  94. package/runtime-src/transform/index.ts +0 -277
  95. package/runtime-src/types/index.ts +0 -1
  96. package/runtime-src/types/skill.ts +0 -912
  97. package/runtime-src/vault/index.ts +0 -196
  98. package/runtime-src/verification/auth-gate.ts +0 -8
  99. package/runtime-src/verification/candidates.ts +0 -27
  100. package/runtime-src/verification/index.ts +0 -120
  101. package/runtime-src/verification/matrix.ts +0 -30
  102. package/runtime-src/version.ts +0 -148
  103. package/runtime-src/workflow/artifact.ts +0 -161
  104. package/runtime-src/workflow/compile.ts +0 -808
  105. package/runtime-src/workflow/publish.ts +0 -225
  106. package/runtime-src/workflow/runtime.ts +0 -213
  107. package/vendor/kuri/win-x64/kuri.exe +0 -0
@@ -1,182 +0,0 @@
1
- /**
2
- * Lobster.cash x402 payment bridge.
3
- *
4
- * Handles the pay-and-retry cycle:
5
- * 1. Receives x402 payment terms from a 402 response
6
- * 2. Calls `lobstercash x402 fetch <url>` to pay + retry
7
- * 3. Returns the paid response body
8
- *
9
- * Delegation boundary:
10
- * - Unbrowse owns: detecting 402, passing the URL, using the result
11
- * - Lobster owns: wallet signing, transaction broadcast, proof construction
12
- */
13
-
14
- import { execFile, execFileSync } from "node:child_process";
15
- import { existsSync } from "node:fs";
16
- import { homedir } from "node:os";
17
- import { join } from "node:path";
18
-
19
- const LOBSTER_PAY_TIMEOUT_MS = 30_000;
20
-
21
- /**
22
- * Resolve the lobster CLI command. Prefers the direct binary if
23
- * installed globally, otherwise falls back to npx.
24
- */
25
- function getLobsterCommand(): { cmd: string; prefix: string[] } | null {
26
- // 1. Direct binary in PATH
27
- try {
28
- execFileSync("lobstercash", ["--version"], { stdio: "ignore", timeout: 3_000 });
29
- return { cmd: "lobstercash", prefix: [] };
30
- } catch (_e) { /* not in PATH */ }
31
-
32
- // 2. Check npm global bin (often not in agent PATH)
33
- try {
34
- const npmPrefix = execFileSync("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5_000 }).trim();
35
- const lobsterPath = join(npmPrefix, "bin", "lobstercash");
36
- if (existsSync(lobsterPath)) {
37
- execFileSync(lobsterPath, ["--version"], { stdio: "ignore", timeout: 3_000 });
38
- return { cmd: lobsterPath, prefix: [] };
39
- }
40
- } catch (_e) { /* npm not available */ }
41
-
42
- return null;
43
- }
44
-
45
- let cachedCommand: { cmd: string; prefix: string[] } | null | undefined = undefined;
46
- function lobsterCmd(): { cmd: string; prefix: string[] } | null {
47
- if (cachedCommand === undefined) cachedCommand = getLobsterCommand();
48
- return cachedCommand;
49
- }
50
-
51
- export interface LobsterPayResult {
52
- success: boolean;
53
- body: string;
54
- statusCode?: number;
55
- error?: string;
56
- }
57
-
58
- /**
59
- * Check if the lobster CLI is available and wallet is configured.
60
- */
61
- export function isLobsterAvailable(): boolean {
62
- const agentsPath = join(process.env.HOME || homedir(), ".lobster", "agents.json");
63
- return existsSync(agentsPath);
64
- }
65
-
66
- /**
67
- * Pay for an x402-gated URL via lobster.cash and return the response.
68
- *
69
- * Shells out to `lobstercash x402 fetch <url>` which handles:
70
- * - Reading 402 + payment terms
71
- * - Signing + broadcasting the USDC transfer
72
- * - Retrying with PAYMENT-SIGNATURE header
73
- * - Returning the paid response
74
- */
75
- export function lobsterX402Fetch(
76
- url: string,
77
- options?: {
78
- jsonBody?: string;
79
- headers?: Record<string, string>;
80
- timeoutMs?: number;
81
- },
82
- ): Promise<LobsterPayResult> {
83
- return new Promise((resolve) => {
84
- const resolved = lobsterCmd();
85
- if (!resolved) {
86
- resolve({ success: false, body: "", error: "lobstercash CLI not in PATH" });
87
- return;
88
- }
89
- const { cmd, prefix } = resolved;
90
- const args = [...prefix, "x402", "fetch", url, "--debug"];
91
-
92
- if (options?.jsonBody) {
93
- args.push("--json", options.jsonBody);
94
- }
95
-
96
- if (options?.headers) {
97
- for (const [key, value] of Object.entries(options.headers)) {
98
- args.push("--header", `${key}:${value}`);
99
- }
100
- }
101
-
102
- const timeout = options?.timeoutMs ?? LOBSTER_PAY_TIMEOUT_MS;
103
- args.push("--timeout", String(timeout));
104
-
105
- execFile(cmd, args, { timeout: timeout + 5_000, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
106
- if (err) {
107
- const msg = stderr?.trim() || err.message;
108
- console.warn(`[lobster-pay] x402 fetch failed: ${msg}`);
109
- resolve({ success: false, body: "", error: msg });
110
- return;
111
- }
112
-
113
- if (stderr) {
114
- for (const line of stderr.split("\n").filter(Boolean)) {
115
- console.log(`[lobster-pay] ${line}`);
116
- }
117
- }
118
-
119
- // Parse status from stdout header line: "Status: 200"
120
- const statusMatch = stdout.match(/^Status:\s*(\d+)/m);
121
- const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : undefined;
122
-
123
- if (statusCode && statusCode >= 400) {
124
- resolve({ success: false, body: stdout, statusCode, error: `HTTP ${statusCode}` });
125
- return;
126
- }
127
-
128
- resolve({ success: true, body: stdout, statusCode });
129
- });
130
- });
131
- }
132
-
133
- /**
134
- * Pay for an x402-gated API request and return the parsed JSON response.
135
- *
136
- * This is the high-level entry point for the pay-and-retry loop.
137
- * Returns null if payment fails or lobster is unavailable.
138
- */
139
- export async function payAndRetry<T = unknown>(
140
- fullUrl: string,
141
- options?: {
142
- body?: unknown;
143
- headers?: Record<string, string>;
144
- },
145
- ): Promise<{ data: T; paid: true } | null> {
146
- if (!isLobsterAvailable()) {
147
- console.log("[lobster-pay] lobster.cash not configured — skipping payment");
148
- return null;
149
- }
150
-
151
- console.log(`[lobster-pay] attempting x402 payment for ${fullUrl}`);
152
-
153
- const result = await lobsterX402Fetch(fullUrl, {
154
- jsonBody: options?.body ? JSON.stringify(options.body) : undefined,
155
- headers: options?.headers,
156
- });
157
-
158
- if (!result.success) {
159
- console.warn(`[lobster-pay] payment failed: ${result.error}`);
160
- return null;
161
- }
162
- try {
163
- // lobstercash x402 fetch outputs header lines before the JSON body:
164
- // x402 FETCH <url>
165
- // Status: 200
166
- // Content-Type: application/json
167
- // <blank line>
168
- // {"actual":"json",...}
169
- // Extract the JSON portion by finding the first '{' or '['
170
- const raw = result.body;
171
- const jsonStart = Math.min(
172
- ...[raw.indexOf("{"), raw.indexOf("[")].filter((i) => i >= 0),
173
- );
174
- const jsonStr = jsonStart >= 0 ? raw.slice(jsonStart) : raw;
175
- const data = JSON.parse(jsonStr) as T;
176
- console.log("[lobster-pay] payment successful — got paid response");
177
- return { data, paid: true };
178
- } catch (_e) {
179
- console.warn("[lobster-pay] paid response was not valid JSON");
180
- return null;
181
- }
182
- }
@@ -1,98 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- /**
6
- * Wallet precheck — lobster.cash compatible.
7
- *
8
- * This module only checks whether the agent has a wallet configured.
9
- * It does NOT generate wallets, manage keys, or call wallet APIs.
10
- * Wallet provisioning and transaction execution are owned by
11
- * the agent's wallet plugin (e.g. lobster.cash).
12
- */
13
-
14
- export type WalletCheckResult = {
15
- configured: boolean;
16
- provider?: string;
17
- };
18
-
19
- type WalletContext = {
20
- wallet_address?: string;
21
- wallet_provider?: string;
22
- };
23
-
24
- type LobsterAgentRecord = {
25
- walletAddress?: unknown;
26
- wallet_address?: unknown;
27
- authorizedWallets?: {
28
- solana?: unknown;
29
- [key: string]: unknown;
30
- };
31
- };
32
-
33
- type LobsterAgentsFile = {
34
- activeAgentId?: unknown;
35
- agents?: Record<string, LobsterAgentRecord> | LobsterAgentRecord[];
36
- };
37
-
38
- function asNonEmptyString(value: unknown): string | undefined {
39
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
40
- }
41
-
42
- function getLobsterWalletFromLocalConfig(): string | undefined {
43
- const agentsPath = join(process.env.HOME || homedir(), ".lobster", "agents.json");
44
- if (!existsSync(agentsPath)) return undefined;
45
-
46
- try {
47
- const raw = JSON.parse(readFileSync(agentsPath, "utf8")) as LobsterAgentsFile;
48
- const activeAgentId = asNonEmptyString(raw.activeAgentId);
49
- const activeAgent = Array.isArray(raw.agents)
50
- ? raw.agents.find((agent) => asNonEmptyString((agent as { id?: unknown }).id) === activeAgentId)
51
- : activeAgentId
52
- ? raw.agents?.[activeAgentId]
53
- : undefined;
54
-
55
- return asNonEmptyString(activeAgent?.authorizedWallets?.solana)
56
- ?? asNonEmptyString(activeAgent?.walletAddress)
57
- ?? asNonEmptyString(activeAgent?.wallet_address);
58
- } catch {
59
- return undefined;
60
- }
61
- }
62
-
63
- export function getWalletContext(): WalletContext {
64
- const lobsterWallet = asNonEmptyString(process.env.LOBSTER_WALLET_ADDRESS);
65
- if (lobsterWallet) {
66
- return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
67
- }
68
-
69
- const genericWallet = asNonEmptyString(process.env.AGENT_WALLET_ADDRESS);
70
- if (genericWallet) {
71
- return {
72
- wallet_address: genericWallet,
73
- wallet_provider: asNonEmptyString(process.env.AGENT_WALLET_PROVIDER),
74
- };
75
- }
76
-
77
- const localLobsterWallet = getLobsterWalletFromLocalConfig();
78
- if (localLobsterWallet) {
79
- return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
80
- }
81
-
82
- return {};
83
- }
84
-
85
- /**
86
- * Check if the agent has a wallet configured.
87
- *
88
- * Looks for wallet context signals that a wallet plugin would set.
89
- * Does NOT create or modify wallet state.
90
- */
91
- export function checkWalletConfigured(): WalletCheckResult {
92
- const wallet = getWalletContext();
93
- if (!wallet.wallet_address) return { configured: false };
94
- return {
95
- configured: true,
96
- provider: wallet.wallet_provider ?? "unknown",
97
- };
98
- }
@@ -1,93 +0,0 @@
1
- import { buildSkillOperationGraph } from "../graph/index.js";
2
- import type { SkillManifest } from "../types/index.js";
3
- import { readWorkflowArtifact } from "../workflow/artifact.js";
4
- import { buildSafeParameterSpecView, flattenResponseSchemaFields } from "./schema-review.js";
5
-
6
- function operationTitle(actionKind: string, resourceKind: string): string {
7
- return `${actionKind.replace(/_/g, " ")} ${resourceKind.replace(/_/g, " ")}`.trim();
8
- }
9
-
10
- export function buildEndpointReviewContext(skill: SkillManifest, endpointId: string): Record<string, unknown> | null {
11
- const graph = skill.operation_graph ?? buildSkillOperationGraph(skill.endpoints);
12
- const operation = graph.operations.find((candidate) => candidate.endpoint_id === endpointId);
13
- const endpoint = skill.endpoints.find((candidate) => candidate.endpoint_id === endpointId);
14
- if (!operation || !endpoint) return null;
15
- const workflowArtifact = readWorkflowArtifact(skill.skill_id);
16
- const recipe = workflowArtifact?.recipes.find((candidate) => candidate.endpoint_id === endpointId) ?? null;
17
-
18
- const dependencies = graph.edges
19
- .filter((edge) => edge.to_operation_id === operation.operation_id)
20
- .map((edge) => {
21
- const source = graph.operations.find((candidate) => candidate.operation_id === edge.from_operation_id);
22
- if (!source) return null;
23
- return {
24
- endpoint_id: source.endpoint_id,
25
- operation_id: source.operation_id,
26
- title: operationTitle(source.action_kind, source.resource_kind),
27
- binding_key: edge.binding_key,
28
- kind: edge.kind,
29
- confidence: edge.confidence,
30
- description_out: source.description_out,
31
- };
32
- })
33
- .filter((entry): entry is NonNullable<typeof entry> => !!entry);
34
-
35
- const unlocks = graph.edges
36
- .filter((edge) => edge.from_operation_id === operation.operation_id)
37
- .map((edge) => {
38
- const target = graph.operations.find((candidate) => candidate.operation_id === edge.to_operation_id);
39
- if (!target) return null;
40
- return {
41
- endpoint_id: target.endpoint_id,
42
- operation_id: target.operation_id,
43
- title: operationTitle(target.action_kind, target.resource_kind),
44
- binding_key: edge.binding_key,
45
- kind: edge.kind,
46
- confidence: edge.confidence,
47
- description_out: target.description_out,
48
- };
49
- })
50
- .filter((entry): entry is NonNullable<typeof entry> => !!entry);
51
-
52
- const triggerSiblings = skill.endpoints
53
- .filter((candidate) => candidate.endpoint_id !== endpointId && candidate.trigger_url && candidate.trigger_url === endpoint.trigger_url)
54
- .slice(0, 5)
55
- .map((candidate) => ({
56
- endpoint_id: candidate.endpoint_id,
57
- method: candidate.method,
58
- description: candidate.description ?? candidate.semantic?.description_out ?? "",
59
- dom_extraction: !!candidate.dom_extraction,
60
- trigger_url: candidate.trigger_url,
61
- }));
62
-
63
- return {
64
- operation_id: operation.operation_id,
65
- title: operationTitle(operation.action_kind, operation.resource_kind),
66
- action_kind: operation.action_kind,
67
- resource_kind: operation.resource_kind,
68
- description_in: operation.description_in,
69
- response_summary: operation.response_summary,
70
- requires: operation.requires ?? [],
71
- provides: operation.provides ?? [],
72
- negative_tags: operation.negative_tags ?? [],
73
- provenance: endpoint.dom_extraction
74
- ? "dom_extraction"
75
- : endpoint.method === "WS"
76
- ? "websocket"
77
- : "http_replay",
78
- trigger_url: endpoint.trigger_url ?? null,
79
- verification_status: endpoint.verification_status,
80
- reliability_score: endpoint.reliability_score,
81
- request_schema: recipe
82
- ? recipe.replay_contract.parameter_specs.map((spec) => buildSafeParameterSpecView(spec))
83
- : [],
84
- response_schema: flattenResponseSchemaFields(endpoint.response_schema),
85
- prerequisites: recipe?.replay_contract.prerequisite_specs ?? [],
86
- token_bindings: recipe?.token_bindings ?? [],
87
- next_state: recipe?.replay_contract.next_state ?? [],
88
- path_binding_candidates: endpoint._path_binding_candidates ?? [],
89
- dependencies,
90
- unlocks,
91
- trigger_siblings: triggerSiblings,
92
- };
93
- }
@@ -1,197 +0,0 @@
1
- import type { EndpointDescriptor } from "../types/index.js";
2
-
3
- const SECRET_VALUE_PATTERNS = [
4
- /^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
5
- /^Bearer\s+\S+/i,
6
- /^Basic\s+[A-Za-z0-9+/=]+/i,
7
- /^ghp_[A-Za-z0-9]{36}/,
8
- /^sk-[A-Za-z0-9]{20,}/,
9
- /^pk_(live|test)_[A-Za-z0-9]+/,
10
- /^xox[bsrp]-[A-Za-z0-9-]+/,
11
- /^AKIA[A-Z0-9]{16}/,
12
- /^[A-Za-z0-9+/]{40,}={0,2}$/,
13
- /^v2\.[A-Za-z0-9_-]{20,}/,
14
- ];
15
-
16
- const SECRET_KEY_PATTERNS = /^(api[_-]?key|access[_-]?token|auth[_-]?token|secret[_-]?key|private[_-]?key|password|passwd|session[_-]?id|session[_-]?token|csrf[_-]?token|client[_-]?secret|bearer|refresh[_-]?token|id[_-]?token|jwt|nonce|otp|pin|ssn|credit[_-]?card)$/i;
17
- const EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
18
- const PHONE_PATTERN = /(?<!\w)(?:\+?\d[\d\s-]{7,}\d)(?!\w)/g;
19
- const UUID_PATTERN = /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
20
- const URL_PATTERN = /\bhttps?:\/\/[^\s"'<>]+/gi;
21
- const JWT_PATTERN = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+\b/g;
22
- const CREDIT_CARD_PATTERN = /\b(?:\d[ -]*?){13,19}\b/g;
23
- const LONG_ID_PATTERN = /\b[A-Za-z0-9_-]{20,}\b/g;
24
-
25
- export function looksLikeSecret(key: string, value: unknown): boolean {
26
- if (typeof value !== "string" || value.length < 8) return false;
27
- if (SECRET_KEY_PATTERNS.test(key)) return true;
28
- return SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value));
29
- }
30
-
31
- export function sanitizeAgentVisibleText(text: string): string {
32
- return text
33
- .replace(JWT_PATTERN, "[secret-token]")
34
- .replace(EMAIL_PATTERN, "user@example.com")
35
- .replace(PHONE_PATTERN, "[phone]")
36
- .replace(UUID_PATTERN, "00000000-0000-0000-0000-000000000000")
37
- .replace(CREDIT_CARD_PATTERN, "[sensitive-number]")
38
- .replace(URL_PATTERN, (value) => {
39
- if (/^https?:\/\/example\.com/i.test(value)) return value;
40
- return "https://example.com/resource";
41
- })
42
- .replace(LONG_ID_PATTERN, (value) => {
43
- if (looksLikeSecret("", value)) return "[secret-token]";
44
- return value;
45
- });
46
- }
47
-
48
- export function sanitizeAgentVisibleValue(key: string, value: unknown): unknown {
49
- if (value == null) return value;
50
- if (typeof value === "string") {
51
- return synthesizePlaceholder(key, sanitizeAgentVisibleText(value));
52
- }
53
- if (Array.isArray(value)) return value.slice(0, 3).map((entry) => sanitizeAgentVisibleValue(key, entry));
54
- if (typeof value === "object") {
55
- const out: Record<string, unknown> = {};
56
- for (const [childKey, childValue] of Object.entries(value as Record<string, unknown>)) {
57
- out[childKey] = sanitizeAgentVisibleValue(childKey, childValue);
58
- }
59
- return out;
60
- }
61
- return synthesizePlaceholder(key, value);
62
- }
63
-
64
- export function redactSecrets(obj: unknown, parentKey = ""): unknown {
65
- if (obj === null || obj === undefined) return obj;
66
- if (typeof obj === "string") {
67
- return looksLikeSecret(parentKey, obj) ? "[REDACTED]" : obj;
68
- }
69
- if (Array.isArray(obj)) {
70
- return obj.map((item) => redactSecrets(item, parentKey));
71
- }
72
- if (typeof obj === "object") {
73
- const result: Record<string, unknown> = {};
74
- for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
75
- result[key] = redactSecrets(value, key);
76
- }
77
- return result;
78
- }
79
- return obj;
80
- }
81
-
82
- function synthesizePlaceholder(key: string, value: unknown): unknown {
83
- if (value === null || value === undefined) return value;
84
- if (typeof value === "number") return Number.isInteger(value) ? 12345 : 99.99;
85
- if (typeof value === "boolean") return value;
86
- if (typeof value === "string") {
87
- if (looksLikeSecret(key, value)) return "[REDACTED]";
88
- if (/@/.test(value)) return "user@example.com";
89
- if (/^https?:\/\//.test(value)) return "https://example.com/item/123";
90
- if (/^[0-9a-f]{8}-[0-9a-f]{4}/.test(value)) return "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
91
- if (/^\d+$/.test(value)) return "12345";
92
- if (/^\d{4}-\d{2}-\d{2}/.test(value)) return "2026-01-15T00:00:00Z";
93
- if (value.length <= 8) return "abc123";
94
- if (value.length > 100) return "Example description text for this item.";
95
- return "example-value";
96
- }
97
- if (Array.isArray(value)) {
98
- return value.length > 0 ? [synthesizeExample(value[0], 0)] : [];
99
- }
100
- if (typeof value === "object") {
101
- return synthesizeExample(value, 0);
102
- }
103
- return value;
104
- }
105
-
106
- export function synthesizeExample(obj: unknown, depth = 0): unknown {
107
- if (depth > 5) return null;
108
- if (obj === null || obj === undefined) return obj;
109
- if (typeof obj !== "object") return synthesizePlaceholder("", obj);
110
- if (Array.isArray(obj)) {
111
- return obj.slice(0, 2).map((item) => synthesizeExample(item, depth + 1));
112
- }
113
- const result: Record<string, unknown> = {};
114
- for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
115
- result[key] = typeof value === "object" && value !== null
116
- ? synthesizeExample(value, depth + 1)
117
- : synthesizePlaceholder(key, value);
118
- }
119
- return result;
120
- }
121
-
122
- export function sanitizeForPublish(endpoints: EndpointDescriptor[]): EndpointDescriptor[] {
123
- return endpoints.map((endpoint) => {
124
- const clean = { ...endpoint };
125
-
126
- if (clean.headers_template) {
127
- clean.headers_template = redactSecrets(clean.headers_template) as Record<string, string>;
128
- }
129
- if (clean.query) {
130
- clean.query = redactSecrets(clean.query) as Record<string, unknown>;
131
- }
132
- if (clean.body) {
133
- clean.body = redactSecrets(clean.body) as Record<string, unknown>;
134
- }
135
- if (clean.query) {
136
- clean.query = Object.fromEntries(
137
- Object.entries(clean.query).map(([key, value]) => [key, typeof value === "string" ? "example" : value]),
138
- );
139
- }
140
- if (clean.path_params) {
141
- clean.path_params = Object.fromEntries(
142
- Object.keys(clean.path_params).map((key) => [key, "example"]),
143
- );
144
- }
145
- if (clean.body) clean.body = synthesizeExample(clean.body) as Record<string, unknown>;
146
- if (clean.body_params) clean.body_params = synthesizeExample(clean.body_params) as Record<string, unknown>;
147
-
148
- if (clean.headers_template) {
149
- clean.headers_template = Object.fromEntries(
150
- Object.keys(clean.headers_template).map((key) => [key, ""]),
151
- );
152
- }
153
-
154
- if (clean.trigger_url) {
155
- try {
156
- const parsed = new URL(clean.trigger_url);
157
- clean.trigger_url = parsed.origin + parsed.pathname;
158
- } catch {
159
- /* keep original */
160
- }
161
- }
162
-
163
- if (clean.semantic) {
164
- const semantic = { ...clean.semantic };
165
- if (semantic.example_response_compact) {
166
- semantic.example_response_compact = synthesizeExample(semantic.example_response_compact);
167
- }
168
- if (semantic.example_request) {
169
- semantic.example_request = synthesizeExample(semantic.example_request);
170
- }
171
- if (semantic.sample_request_url) {
172
- try {
173
- const parsed = new URL(semantic.sample_request_url);
174
- for (const key of parsed.searchParams.keys()) parsed.searchParams.set(key, "example");
175
- semantic.sample_request_url = parsed.toString();
176
- } catch {
177
- delete semantic.sample_request_url;
178
- }
179
- }
180
- if (semantic.requires) {
181
- semantic.requires = semantic.requires.map((binding) => {
182
- const { example_value: _exampleValue, ...rest } = binding;
183
- return rest;
184
- });
185
- }
186
- if (semantic.provides) {
187
- semantic.provides = semantic.provides.map((binding) => {
188
- const { example_value: _exampleValue, ...rest } = binding;
189
- return rest;
190
- });
191
- }
192
- clean.semantic = semantic;
193
- }
194
-
195
- return clean;
196
- });
197
- }