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.
- package/dist/cli.js +455 -96
- package/dist/index.js +2 -6
- package/dist/mcp.js +695 -46
- package/dist/server.js +25811 -0
- package/package.json +1 -2
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +7 -10
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -317
- package/runtime-src/api/browse-session.ts +0 -572
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/api/routes.ts +0 -1823
- package/runtime-src/auth/browser-cookies.ts +0 -423
- package/runtime-src/auth/index.ts +0 -535
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -659
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -6
- package/runtime-src/capture/index.ts +0 -1794
- package/runtime-src/capture/prefetch.ts +0 -95
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/cli.ts +0 -1572
- package/runtime-src/client/graph-client.ts +0 -100
- package/runtime-src/client/index.ts +0 -1425
- package/runtime-src/debug-trace.ts +0 -18
- package/runtime-src/domain.ts +0 -38
- package/runtime-src/execution/index.ts +0 -3397
- package/runtime-src/execution/retry.ts +0 -46
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/extraction/index.ts +0 -1507
- package/runtime-src/foundry/publish-bundle.ts +0 -392
- package/runtime-src/graph/agent-augment.ts +0 -315
- package/runtime-src/graph/index.ts +0 -1524
- package/runtime-src/graph/local-fixtures.ts +0 -393
- package/runtime-src/graph/local-harness.ts +0 -646
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/index.ts +0 -24
- package/runtime-src/indexer/index.ts +0 -465
- package/runtime-src/intent-match.ts +0 -1515
- package/runtime-src/kuri/client.ts +0 -1839
- package/runtime-src/logger.ts +0 -30
- package/runtime-src/marketplace/index.ts +0 -103
- package/runtime-src/mcp.ts +0 -1747
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -257
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/index.ts +0 -4480
- package/runtime-src/orchestrator/passive-publish.ts +0 -187
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -270
- package/runtime-src/payments/lobster-pay.ts +0 -182
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/review-context.ts +0 -93
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish/schema-review.ts +0 -192
- package/runtime-src/publish-admission.ts +0 -388
- package/runtime-src/ratelimit/index.ts +0 -23
- package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/reverse-engineer/index.ts +0 -1551
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/local-server.ts +0 -311
- package/runtime-src/runtime/paths.ts +0 -99
- package/runtime-src/runtime/setup.ts +0 -251
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/server.ts +0 -100
- package/runtime-src/session-logs.ts +0 -142
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -143
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/template-params.ts +0 -141
- package/runtime-src/transform/drift.ts +0 -60
- package/runtime-src/transform/index.ts +0 -277
- package/runtime-src/types/index.ts +0 -1
- package/runtime-src/types/skill.ts +0 -912
- package/runtime-src/vault/index.ts +0 -196
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/index.ts +0 -120
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/version.ts +0 -148
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -225
- package/runtime-src/workflow/runtime.ts +0 -213
- 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
|
-
}
|