@apitap/core 1.0.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/LICENSE +60 -0
- package/README.md +362 -0
- package/SKILL.md +270 -0
- package/dist/auth/crypto.d.ts +31 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/handoff.d.ts +29 -0
- package/dist/auth/handoff.js +180 -0
- package/dist/auth/handoff.js.map +1 -0
- package/dist/auth/manager.d.ts +46 -0
- package/dist/auth/manager.js +127 -0
- package/dist/auth/manager.js.map +1 -0
- package/dist/auth/oauth-refresh.d.ts +16 -0
- package/dist/auth/oauth-refresh.js +91 -0
- package/dist/auth/oauth-refresh.js.map +1 -0
- package/dist/auth/refresh.d.ts +43 -0
- package/dist/auth/refresh.js +217 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/capture/anti-bot.d.ts +15 -0
- package/dist/capture/anti-bot.js +43 -0
- package/dist/capture/anti-bot.js.map +1 -0
- package/dist/capture/blocklist.d.ts +6 -0
- package/dist/capture/blocklist.js +70 -0
- package/dist/capture/blocklist.js.map +1 -0
- package/dist/capture/body-diff.d.ts +8 -0
- package/dist/capture/body-diff.js +102 -0
- package/dist/capture/body-diff.js.map +1 -0
- package/dist/capture/body-variables.d.ts +13 -0
- package/dist/capture/body-variables.js +142 -0
- package/dist/capture/body-variables.js.map +1 -0
- package/dist/capture/domain.d.ts +8 -0
- package/dist/capture/domain.js +34 -0
- package/dist/capture/domain.js.map +1 -0
- package/dist/capture/entropy.d.ts +33 -0
- package/dist/capture/entropy.js +100 -0
- package/dist/capture/entropy.js.map +1 -0
- package/dist/capture/filter.d.ts +11 -0
- package/dist/capture/filter.js +49 -0
- package/dist/capture/filter.js.map +1 -0
- package/dist/capture/graphql.d.ts +21 -0
- package/dist/capture/graphql.js +99 -0
- package/dist/capture/graphql.js.map +1 -0
- package/dist/capture/idle.d.ts +23 -0
- package/dist/capture/idle.js +44 -0
- package/dist/capture/idle.js.map +1 -0
- package/dist/capture/monitor.d.ts +26 -0
- package/dist/capture/monitor.js +183 -0
- package/dist/capture/monitor.js.map +1 -0
- package/dist/capture/oauth-detector.d.ts +18 -0
- package/dist/capture/oauth-detector.js +96 -0
- package/dist/capture/oauth-detector.js.map +1 -0
- package/dist/capture/pagination.d.ts +9 -0
- package/dist/capture/pagination.js +40 -0
- package/dist/capture/pagination.js.map +1 -0
- package/dist/capture/parameterize.d.ts +17 -0
- package/dist/capture/parameterize.js +63 -0
- package/dist/capture/parameterize.js.map +1 -0
- package/dist/capture/scrubber.d.ts +5 -0
- package/dist/capture/scrubber.js +38 -0
- package/dist/capture/scrubber.js.map +1 -0
- package/dist/capture/session.d.ts +46 -0
- package/dist/capture/session.js +445 -0
- package/dist/capture/session.js.map +1 -0
- package/dist/capture/token-detector.d.ts +16 -0
- package/dist/capture/token-detector.js +62 -0
- package/dist/capture/token-detector.js.map +1 -0
- package/dist/capture/verifier.d.ts +17 -0
- package/dist/capture/verifier.js +147 -0
- package/dist/capture/verifier.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +930 -0
- package/dist/cli.js.map +1 -0
- package/dist/discovery/auth.d.ts +17 -0
- package/dist/discovery/auth.js +81 -0
- package/dist/discovery/auth.js.map +1 -0
- package/dist/discovery/fetch.d.ts +17 -0
- package/dist/discovery/fetch.js +59 -0
- package/dist/discovery/fetch.js.map +1 -0
- package/dist/discovery/frameworks.d.ts +11 -0
- package/dist/discovery/frameworks.js +249 -0
- package/dist/discovery/frameworks.js.map +1 -0
- package/dist/discovery/index.d.ts +21 -0
- package/dist/discovery/index.js +219 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/openapi.d.ts +13 -0
- package/dist/discovery/openapi.js +175 -0
- package/dist/discovery/openapi.js.map +1 -0
- package/dist/discovery/probes.d.ts +9 -0
- package/dist/discovery/probes.js +70 -0
- package/dist/discovery/probes.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/report.d.ts +52 -0
- package/dist/inspect/report.js +191 -0
- package/dist/inspect/report.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.js +526 -0
- package/dist/mcp.js.map +1 -0
- package/dist/orchestration/browse.d.ts +38 -0
- package/dist/orchestration/browse.js +198 -0
- package/dist/orchestration/browse.js.map +1 -0
- package/dist/orchestration/cache.d.ts +15 -0
- package/dist/orchestration/cache.js +24 -0
- package/dist/orchestration/cache.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.js +158 -0
- package/dist/plugin.js.map +1 -0
- package/dist/read/decoders/deepwiki.d.ts +2 -0
- package/dist/read/decoders/deepwiki.js +148 -0
- package/dist/read/decoders/deepwiki.js.map +1 -0
- package/dist/read/decoders/grokipedia.d.ts +2 -0
- package/dist/read/decoders/grokipedia.js +210 -0
- package/dist/read/decoders/grokipedia.js.map +1 -0
- package/dist/read/decoders/hackernews.d.ts +2 -0
- package/dist/read/decoders/hackernews.js +168 -0
- package/dist/read/decoders/hackernews.js.map +1 -0
- package/dist/read/decoders/index.d.ts +2 -0
- package/dist/read/decoders/index.js +12 -0
- package/dist/read/decoders/index.js.map +1 -0
- package/dist/read/decoders/reddit.d.ts +2 -0
- package/dist/read/decoders/reddit.js +142 -0
- package/dist/read/decoders/reddit.js.map +1 -0
- package/dist/read/decoders/twitter.d.ts +12 -0
- package/dist/read/decoders/twitter.js +187 -0
- package/dist/read/decoders/twitter.js.map +1 -0
- package/dist/read/decoders/wikipedia.d.ts +2 -0
- package/dist/read/decoders/wikipedia.js +66 -0
- package/dist/read/decoders/wikipedia.js.map +1 -0
- package/dist/read/decoders/youtube.d.ts +2 -0
- package/dist/read/decoders/youtube.js +69 -0
- package/dist/read/decoders/youtube.js.map +1 -0
- package/dist/read/extract.d.ts +25 -0
- package/dist/read/extract.js +320 -0
- package/dist/read/extract.js.map +1 -0
- package/dist/read/index.d.ts +14 -0
- package/dist/read/index.js +66 -0
- package/dist/read/index.js.map +1 -0
- package/dist/read/peek.d.ts +9 -0
- package/dist/read/peek.js +137 -0
- package/dist/read/peek.js.map +1 -0
- package/dist/read/types.d.ts +44 -0
- package/dist/read/types.js +3 -0
- package/dist/read/types.js.map +1 -0
- package/dist/replay/engine.d.ts +53 -0
- package/dist/replay/engine.js +441 -0
- package/dist/replay/engine.js.map +1 -0
- package/dist/replay/truncate.d.ts +16 -0
- package/dist/replay/truncate.js +92 -0
- package/dist/replay/truncate.js.map +1 -0
- package/dist/serve.d.ts +31 -0
- package/dist/serve.js +149 -0
- package/dist/serve.js.map +1 -0
- package/dist/skill/generator.d.ts +44 -0
- package/dist/skill/generator.js +419 -0
- package/dist/skill/generator.js.map +1 -0
- package/dist/skill/importer.d.ts +26 -0
- package/dist/skill/importer.js +80 -0
- package/dist/skill/importer.js.map +1 -0
- package/dist/skill/search.d.ts +19 -0
- package/dist/skill/search.js +51 -0
- package/dist/skill/search.js.map +1 -0
- package/dist/skill/signing.d.ts +16 -0
- package/dist/skill/signing.js +34 -0
- package/dist/skill/signing.js.map +1 -0
- package/dist/skill/ssrf.d.ts +27 -0
- package/dist/skill/ssrf.js +210 -0
- package/dist/skill/ssrf.js.map +1 -0
- package/dist/skill/store.d.ts +7 -0
- package/dist/skill/store.js +93 -0
- package/dist/skill/store.js.map +1 -0
- package/dist/stats/report.d.ts +26 -0
- package/dist/stats/report.js +157 -0
- package/dist/stats/report.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- package/src/auth/crypto.ts +92 -0
- package/src/auth/handoff.ts +229 -0
- package/src/auth/manager.ts +140 -0
- package/src/auth/oauth-refresh.ts +120 -0
- package/src/auth/refresh.ts +300 -0
- package/src/capture/anti-bot.ts +63 -0
- package/src/capture/blocklist.ts +75 -0
- package/src/capture/body-diff.ts +109 -0
- package/src/capture/body-variables.ts +156 -0
- package/src/capture/domain.ts +34 -0
- package/src/capture/entropy.ts +121 -0
- package/src/capture/filter.ts +56 -0
- package/src/capture/graphql.ts +124 -0
- package/src/capture/idle.ts +45 -0
- package/src/capture/monitor.ts +224 -0
- package/src/capture/oauth-detector.ts +106 -0
- package/src/capture/pagination.ts +49 -0
- package/src/capture/parameterize.ts +68 -0
- package/src/capture/scrubber.ts +49 -0
- package/src/capture/session.ts +502 -0
- package/src/capture/token-detector.ts +76 -0
- package/src/capture/verifier.ts +171 -0
- package/src/cli.ts +1031 -0
- package/src/discovery/auth.ts +99 -0
- package/src/discovery/fetch.ts +85 -0
- package/src/discovery/frameworks.ts +231 -0
- package/src/discovery/index.ts +256 -0
- package/src/discovery/openapi.ts +230 -0
- package/src/discovery/probes.ts +76 -0
- package/src/index.ts +26 -0
- package/src/inspect/report.ts +247 -0
- package/src/mcp.ts +618 -0
- package/src/orchestration/browse.ts +250 -0
- package/src/orchestration/cache.ts +37 -0
- package/src/plugin.ts +188 -0
- package/src/read/decoders/deepwiki.ts +180 -0
- package/src/read/decoders/grokipedia.ts +246 -0
- package/src/read/decoders/hackernews.ts +198 -0
- package/src/read/decoders/index.ts +15 -0
- package/src/read/decoders/reddit.ts +158 -0
- package/src/read/decoders/twitter.ts +211 -0
- package/src/read/decoders/wikipedia.ts +75 -0
- package/src/read/decoders/youtube.ts +75 -0
- package/src/read/extract.ts +396 -0
- package/src/read/index.ts +78 -0
- package/src/read/peek.ts +175 -0
- package/src/read/types.ts +37 -0
- package/src/replay/engine.ts +559 -0
- package/src/replay/truncate.ts +116 -0
- package/src/serve.ts +189 -0
- package/src/skill/generator.ts +473 -0
- package/src/skill/importer.ts +107 -0
- package/src/skill/search.ts +76 -0
- package/src/skill/signing.ts +36 -0
- package/src/skill/ssrf.ts +238 -0
- package/src/skill/store.ts +107 -0
- package/src/stats/report.ts +208 -0
- package/src/types.ts +233 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// src/stats/report.ts
|
|
2
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
const DEFAULT_SKILLS_DIR = join(homedir(), '.apitap', 'skills');
|
|
6
|
+
/** Default browser token estimate when no measurement available (conservative). */
|
|
7
|
+
const DEFAULT_DOM_BYTES = 400_000; // ~100K tokens
|
|
8
|
+
export async function generateStatsReport(skillsDir = DEFAULT_SKILLS_DIR) {
|
|
9
|
+
let files;
|
|
10
|
+
try {
|
|
11
|
+
files = (await readdir(skillsDir)).filter(f => f.endsWith('.json'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return emptyReport();
|
|
15
|
+
}
|
|
16
|
+
const domains = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const filePath = join(skillsDir, file);
|
|
19
|
+
try {
|
|
20
|
+
const content = await readFile(filePath, 'utf-8');
|
|
21
|
+
const skill = JSON.parse(content);
|
|
22
|
+
const fileStats = await stat(filePath);
|
|
23
|
+
const skillFileBytes = fileStats.size;
|
|
24
|
+
// Browser cost: use measured if available, else default
|
|
25
|
+
const domBytes = skill.metadata.browserCost?.domBytes ?? DEFAULT_DOM_BYTES;
|
|
26
|
+
const totalNetworkBytes = skill.metadata.browserCost?.totalNetworkBytes ?? 0;
|
|
27
|
+
// Replay cost: skill file + response data
|
|
28
|
+
const totalResponseBytes = skill.endpoints.reduce((sum, ep) => sum + (ep.responseBytes ?? 0), 0);
|
|
29
|
+
const browserTokens = Math.round(domBytes / 4);
|
|
30
|
+
const replayTokens = Math.round((skillFileBytes + totalResponseBytes) / 4);
|
|
31
|
+
const savingsPercent = browserTokens > 0
|
|
32
|
+
? Math.round((1 - replayTokens / browserTokens) * 1000) / 10
|
|
33
|
+
: 0;
|
|
34
|
+
const replayable = skill.endpoints.filter(ep => {
|
|
35
|
+
const tier = ep.replayability?.tier;
|
|
36
|
+
return tier === 'green' || tier === 'yellow';
|
|
37
|
+
}).length;
|
|
38
|
+
domains.push({
|
|
39
|
+
domain: skill.domain,
|
|
40
|
+
endpoints: skill.endpoints.length,
|
|
41
|
+
replayable,
|
|
42
|
+
domBytes,
|
|
43
|
+
totalNetworkBytes,
|
|
44
|
+
skillFileBytes,
|
|
45
|
+
totalResponseBytes,
|
|
46
|
+
browserTokens,
|
|
47
|
+
replayTokens,
|
|
48
|
+
savingsPercent,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Skip invalid files
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const totals = {
|
|
56
|
+
domains: domains.length,
|
|
57
|
+
endpoints: domains.reduce((s, d) => s + d.endpoints, 0),
|
|
58
|
+
replayable: domains.reduce((s, d) => s + d.replayable, 0),
|
|
59
|
+
totalDomBytes: domains.reduce((s, d) => s + d.domBytes, 0),
|
|
60
|
+
browserTokens: domains.reduce((s, d) => s + d.browserTokens, 0),
|
|
61
|
+
replayTokens: domains.reduce((s, d) => s + d.replayTokens, 0),
|
|
62
|
+
savingsPercent: 0,
|
|
63
|
+
};
|
|
64
|
+
totals.savingsPercent = totals.browserTokens > 0
|
|
65
|
+
? Math.round((1 - totals.replayTokens / totals.browserTokens) * 1000) / 10
|
|
66
|
+
: 0;
|
|
67
|
+
return { domains, totals };
|
|
68
|
+
}
|
|
69
|
+
export function formatStatsHuman(report) {
|
|
70
|
+
if (report.domains.length === 0) {
|
|
71
|
+
return ' No skill files found. Run `apitap capture <url>` first.';
|
|
72
|
+
}
|
|
73
|
+
const lines = [
|
|
74
|
+
'',
|
|
75
|
+
' ApiTap — Usage Report',
|
|
76
|
+
' ' + '═'.repeat(22),
|
|
77
|
+
'',
|
|
78
|
+
` Skill files: ${report.totals.domains} domains`,
|
|
79
|
+
` Total endpoints: ${report.totals.endpoints}`,
|
|
80
|
+
` Replayable: ${report.totals.replayable} (green/yellow tier)`,
|
|
81
|
+
'',
|
|
82
|
+
' Token savings (measured):',
|
|
83
|
+
];
|
|
84
|
+
// Table
|
|
85
|
+
const domCol = 24;
|
|
86
|
+
const hdr = [
|
|
87
|
+
' ' + pad('Domain', domCol) + pad('DOM size', 10) + pad('Browser', 10) + pad('ApiTap', 10) + 'Saved',
|
|
88
|
+
];
|
|
89
|
+
const sep = ' ' + '─'.repeat(domCol) + '─'.repeat(10) + '─'.repeat(10) + '─'.repeat(10) + '─'.repeat(8);
|
|
90
|
+
lines.push(sep);
|
|
91
|
+
lines.push(...hdr);
|
|
92
|
+
lines.push(sep);
|
|
93
|
+
for (const d of report.domains) {
|
|
94
|
+
const domName = d.domain.length > domCol - 2
|
|
95
|
+
? d.domain.slice(0, domCol - 3) + '…'
|
|
96
|
+
: d.domain;
|
|
97
|
+
const hasMeasured = d.domBytes !== DEFAULT_DOM_BYTES;
|
|
98
|
+
const browserLabel = hasMeasured
|
|
99
|
+
? formatTokens(d.browserTokens)
|
|
100
|
+
: `~${formatTokens(d.browserTokens)}`;
|
|
101
|
+
lines.push(' ' +
|
|
102
|
+
pad(domName, domCol) +
|
|
103
|
+
pad(formatBytes(d.domBytes), 10) +
|
|
104
|
+
pad(browserLabel, 10) +
|
|
105
|
+
pad(formatTokens(d.replayTokens), 10) +
|
|
106
|
+
`${d.savingsPercent}%`);
|
|
107
|
+
}
|
|
108
|
+
lines.push(sep);
|
|
109
|
+
const hasMeasuredTotals = report.domains.some(d => d.domBytes !== DEFAULT_DOM_BYTES);
|
|
110
|
+
const totalBrowserLabel = hasMeasuredTotals
|
|
111
|
+
? formatTokens(report.totals.browserTokens)
|
|
112
|
+
: `~${formatTokens(report.totals.browserTokens)}`;
|
|
113
|
+
lines.push(' ' +
|
|
114
|
+
pad('Total', domCol) +
|
|
115
|
+
pad(formatBytes(report.totals.totalDomBytes), 10) +
|
|
116
|
+
pad(totalBrowserLabel, 10) +
|
|
117
|
+
pad(formatTokens(report.totals.replayTokens), 10) +
|
|
118
|
+
`${report.totals.savingsPercent}%`);
|
|
119
|
+
lines.push(sep);
|
|
120
|
+
lines.push('');
|
|
121
|
+
if (hasMeasuredTotals) {
|
|
122
|
+
lines.push(' Browser: measured DOM size during capture (page.content().length / 4)');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
lines.push(' Browser: estimated ~100K tokens/site (re-capture for measured values)');
|
|
126
|
+
}
|
|
127
|
+
lines.push(' ApiTap: measured skill file + API response sizes');
|
|
128
|
+
lines.push('');
|
|
129
|
+
return lines.join('\n');
|
|
130
|
+
}
|
|
131
|
+
function pad(s, width) {
|
|
132
|
+
return s.length >= width ? s : s + ' '.repeat(width - s.length);
|
|
133
|
+
}
|
|
134
|
+
function formatBytes(bytes) {
|
|
135
|
+
if (bytes >= 1_000_000)
|
|
136
|
+
return `${(bytes / 1_000_000).toFixed(1)} MB`;
|
|
137
|
+
if (bytes >= 1_000)
|
|
138
|
+
return `${(bytes / 1_000).toFixed(0)} KB`;
|
|
139
|
+
return `${bytes} B`;
|
|
140
|
+
}
|
|
141
|
+
function formatTokens(tokens) {
|
|
142
|
+
if (tokens >= 1_000_000)
|
|
143
|
+
return `${(tokens / 1_000_000).toFixed(2)}M tk`;
|
|
144
|
+
if (tokens >= 1_000)
|
|
145
|
+
return `${(tokens / 1_000).toFixed(1)}K tk`;
|
|
146
|
+
return `${tokens} tk`;
|
|
147
|
+
}
|
|
148
|
+
function emptyReport() {
|
|
149
|
+
return {
|
|
150
|
+
domains: [],
|
|
151
|
+
totals: {
|
|
152
|
+
domains: 0, endpoints: 0, replayable: 0,
|
|
153
|
+
totalDomBytes: 0, browserTokens: 0, replayTokens: 0, savingsPercent: 0,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/stats/report.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/** A captured HTTP request/response pair from the browser */
|
|
2
|
+
export interface CapturedExchange {
|
|
3
|
+
request: {
|
|
4
|
+
url: string;
|
|
5
|
+
method: string;
|
|
6
|
+
headers: Record<string, string>;
|
|
7
|
+
postData?: string;
|
|
8
|
+
};
|
|
9
|
+
response: {
|
|
10
|
+
status: number;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
body: string;
|
|
13
|
+
contentType: string;
|
|
14
|
+
};
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
/** Stored auth credentials for a domain */
|
|
18
|
+
export interface StoredAuth {
|
|
19
|
+
type: 'bearer' | 'api-key' | 'cookie' | 'custom';
|
|
20
|
+
header: string;
|
|
21
|
+
value: string;
|
|
22
|
+
tokens?: Record<string, StoredToken>;
|
|
23
|
+
session?: StoredSession;
|
|
24
|
+
refreshToken?: string;
|
|
25
|
+
clientSecret?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extended auth storage for v0.8 token refresh.
|
|
29
|
+
*/
|
|
30
|
+
/** Stored token with refresh metadata */
|
|
31
|
+
export interface StoredToken {
|
|
32
|
+
value: string;
|
|
33
|
+
refreshedAt: string;
|
|
34
|
+
expiresAt?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Cached browser session for warm restarts */
|
|
37
|
+
export interface StoredSession {
|
|
38
|
+
cookies: PlaywrightCookie[];
|
|
39
|
+
localStorage?: Record<string, string>;
|
|
40
|
+
savedAt: string;
|
|
41
|
+
maxAgeMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/** Playwright cookie shape (subset of full type) */
|
|
44
|
+
export interface PlaywrightCookie {
|
|
45
|
+
name: string;
|
|
46
|
+
value: string;
|
|
47
|
+
domain: string;
|
|
48
|
+
path: string;
|
|
49
|
+
expires?: number;
|
|
50
|
+
httpOnly?: boolean;
|
|
51
|
+
secure?: boolean;
|
|
52
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
53
|
+
}
|
|
54
|
+
/** OAuth configuration (non-secret, lives in shareable skill file) */
|
|
55
|
+
export interface OAuthConfig {
|
|
56
|
+
tokenEndpoint: string;
|
|
57
|
+
clientId: string;
|
|
58
|
+
grantType: 'refresh_token' | 'client_credentials';
|
|
59
|
+
scope?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Top-level auth config on SkillFile */
|
|
62
|
+
export interface SkillAuth {
|
|
63
|
+
refreshUrl?: string;
|
|
64
|
+
browserMode: 'headless' | 'visible';
|
|
65
|
+
captchaRisk: boolean;
|
|
66
|
+
ttlHint?: number;
|
|
67
|
+
oauthConfig?: OAuthConfig;
|
|
68
|
+
}
|
|
69
|
+
/** Replay difficulty classification for an endpoint */
|
|
70
|
+
export interface Replayability {
|
|
71
|
+
tier: 'green' | 'yellow' | 'orange' | 'red' | 'unknown';
|
|
72
|
+
verified: boolean;
|
|
73
|
+
signals: string[];
|
|
74
|
+
}
|
|
75
|
+
/** Detected pagination pattern */
|
|
76
|
+
export interface PaginationInfo {
|
|
77
|
+
type: 'offset' | 'cursor' | 'page';
|
|
78
|
+
paramName: string;
|
|
79
|
+
limitParam?: string;
|
|
80
|
+
}
|
|
81
|
+
/** Request body template for POST/PUT/PATCH endpoints */
|
|
82
|
+
export interface RequestBody {
|
|
83
|
+
contentType: string;
|
|
84
|
+
template: string | Record<string, unknown>;
|
|
85
|
+
variables?: string[];
|
|
86
|
+
refreshableTokens?: string[];
|
|
87
|
+
}
|
|
88
|
+
/** A single API endpoint in a skill file */
|
|
89
|
+
export interface SkillEndpoint {
|
|
90
|
+
id: string;
|
|
91
|
+
method: string;
|
|
92
|
+
path: string;
|
|
93
|
+
queryParams: Record<string, {
|
|
94
|
+
type: string;
|
|
95
|
+
example: string;
|
|
96
|
+
}>;
|
|
97
|
+
headers: Record<string, string>;
|
|
98
|
+
responseShape: {
|
|
99
|
+
type: string;
|
|
100
|
+
fields?: string[];
|
|
101
|
+
};
|
|
102
|
+
examples: {
|
|
103
|
+
request: {
|
|
104
|
+
url: string;
|
|
105
|
+
headers: Record<string, string>;
|
|
106
|
+
};
|
|
107
|
+
responsePreview: unknown;
|
|
108
|
+
};
|
|
109
|
+
replayability?: Replayability;
|
|
110
|
+
pagination?: PaginationInfo;
|
|
111
|
+
requestBody?: RequestBody;
|
|
112
|
+
responseBytes?: number;
|
|
113
|
+
}
|
|
114
|
+
/** The full skill file written to disk */
|
|
115
|
+
export interface SkillFile {
|
|
116
|
+
version: string;
|
|
117
|
+
domain: string;
|
|
118
|
+
capturedAt: string;
|
|
119
|
+
baseUrl: string;
|
|
120
|
+
endpoints: SkillEndpoint[];
|
|
121
|
+
metadata: {
|
|
122
|
+
captureCount: number;
|
|
123
|
+
filteredCount: number;
|
|
124
|
+
toolVersion: string;
|
|
125
|
+
browserCost?: {
|
|
126
|
+
domBytes: number;
|
|
127
|
+
totalNetworkBytes: number;
|
|
128
|
+
totalRequests: number;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
provenance: 'self' | 'imported' | 'unsigned';
|
|
132
|
+
signature?: string;
|
|
133
|
+
auth?: SkillAuth;
|
|
134
|
+
}
|
|
135
|
+
/** Interactive element on a page, for agent targeting */
|
|
136
|
+
export interface PageElement {
|
|
137
|
+
ref: string;
|
|
138
|
+
tag: string;
|
|
139
|
+
role?: string;
|
|
140
|
+
text: string;
|
|
141
|
+
name?: string;
|
|
142
|
+
placeholder?: string;
|
|
143
|
+
href?: string;
|
|
144
|
+
type?: string;
|
|
145
|
+
disabled?: boolean;
|
|
146
|
+
}
|
|
147
|
+
/** Structured page snapshot returned after every interaction */
|
|
148
|
+
export interface PageSnapshot {
|
|
149
|
+
url: string;
|
|
150
|
+
title: string;
|
|
151
|
+
elements: PageElement[];
|
|
152
|
+
endpointsCaptured: number;
|
|
153
|
+
totalRequests: number;
|
|
154
|
+
filteredRequests: number;
|
|
155
|
+
recentEndpoints: string[];
|
|
156
|
+
}
|
|
157
|
+
/** Result from a capture session interaction */
|
|
158
|
+
export interface InteractionResult {
|
|
159
|
+
success: boolean;
|
|
160
|
+
error?: string;
|
|
161
|
+
snapshot: PageSnapshot;
|
|
162
|
+
}
|
|
163
|
+
/** Result from finishing a capture session */
|
|
164
|
+
export interface FinishResult {
|
|
165
|
+
aborted: boolean;
|
|
166
|
+
domains: {
|
|
167
|
+
domain: string;
|
|
168
|
+
endpointCount: number;
|
|
169
|
+
tiers: Record<string, number>;
|
|
170
|
+
skillFile: string;
|
|
171
|
+
}[];
|
|
172
|
+
}
|
|
173
|
+
/** Summary returned by `apitap list` */
|
|
174
|
+
export interface SkillSummary {
|
|
175
|
+
domain: string;
|
|
176
|
+
skillFile: string;
|
|
177
|
+
endpointCount: number;
|
|
178
|
+
capturedAt: string;
|
|
179
|
+
provenance: 'self' | 'imported' | 'unsigned';
|
|
180
|
+
}
|
|
181
|
+
/** Detected web framework with predicted API patterns */
|
|
182
|
+
export interface DetectedFramework {
|
|
183
|
+
name: string;
|
|
184
|
+
confidence: 'high' | 'medium' | 'low';
|
|
185
|
+
apiPatterns: string[];
|
|
186
|
+
}
|
|
187
|
+
/** Discovered API spec */
|
|
188
|
+
export interface DiscoveredSpec {
|
|
189
|
+
type: 'openapi' | 'swagger' | 'graphql-introspection';
|
|
190
|
+
url: string;
|
|
191
|
+
version?: string;
|
|
192
|
+
endpointCount?: number;
|
|
193
|
+
}
|
|
194
|
+
/** Result from probing a common API path */
|
|
195
|
+
export interface ProbeResult {
|
|
196
|
+
method: string;
|
|
197
|
+
path: string;
|
|
198
|
+
status: number;
|
|
199
|
+
contentType: string;
|
|
200
|
+
isApi: boolean;
|
|
201
|
+
}
|
|
202
|
+
/** Result from the discovery pipeline */
|
|
203
|
+
export interface DiscoveryResult {
|
|
204
|
+
confidence: 'high' | 'medium' | 'low' | 'none';
|
|
205
|
+
skillFile?: SkillFile;
|
|
206
|
+
hints?: string[];
|
|
207
|
+
frameworks?: DetectedFramework[];
|
|
208
|
+
specs?: DiscoveredSpec[];
|
|
209
|
+
probes?: ProbeResult[];
|
|
210
|
+
duration: number;
|
|
211
|
+
authRequired?: boolean;
|
|
212
|
+
authSignals?: string[];
|
|
213
|
+
loginUrl?: string;
|
|
214
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,eAAe"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@apitap/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Intercept web API traffic during browsing. Generate portable skill files so AI agents can call APIs directly instead of scraping.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"apitap": "dist/cli.js",
|
|
9
|
+
"apitap-mcp": "dist/mcp.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsx src/cli.ts",
|
|
14
|
+
"test": "node --import tsx --test 'test/**/*.test.ts'",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"license": "BSL-1.1",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/n1byn1kt/apitap.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/n1byn1kt/apitap#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/n1byn1kt/apitap/issues"
|
|
25
|
+
},
|
|
26
|
+
"author": "ApiTap Contributors",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"api",
|
|
29
|
+
"interception",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"mcp",
|
|
32
|
+
"browser-automation",
|
|
33
|
+
"web-scraping",
|
|
34
|
+
"playwright",
|
|
35
|
+
"skill-file",
|
|
36
|
+
"api-discovery"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist/",
|
|
43
|
+
"src/",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"README.md",
|
|
46
|
+
"SKILL.md"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
50
|
+
"playwright": "^1.58.1",
|
|
51
|
+
"zod": "^4.3.6"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^25.2.0",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^5.9.3"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/auth/crypto.ts
|
|
2
|
+
import {
|
|
3
|
+
createCipheriv,
|
|
4
|
+
createDecipheriv,
|
|
5
|
+
randomBytes,
|
|
6
|
+
pbkdf2Sync,
|
|
7
|
+
createHmac,
|
|
8
|
+
timingSafeEqual,
|
|
9
|
+
} from 'node:crypto';
|
|
10
|
+
|
|
11
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
12
|
+
const KEY_LENGTH = 32; // 256 bits
|
|
13
|
+
const IV_LENGTH = 16;
|
|
14
|
+
const PBKDF2_ITERATIONS = 100_000;
|
|
15
|
+
const PBKDF2_SALT = 'apitap-v0.2-key-derivation';
|
|
16
|
+
|
|
17
|
+
export interface EncryptedData {
|
|
18
|
+
salt: string;
|
|
19
|
+
iv: string;
|
|
20
|
+
ciphertext: string;
|
|
21
|
+
tag: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Derive a 256-bit key from a machine identifier using PBKDF2.
|
|
26
|
+
* Uses a fixed application salt — the entropy comes from the machine ID
|
|
27
|
+
* being stretched through 100K iterations.
|
|
28
|
+
*/
|
|
29
|
+
export function deriveKey(machineId: string): Buffer {
|
|
30
|
+
return pbkdf2Sync(machineId, PBKDF2_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Encrypt plaintext using AES-256-GCM.
|
|
35
|
+
* Each call generates a random IV for semantic security.
|
|
36
|
+
*/
|
|
37
|
+
export function encrypt(plaintext: string, key: Buffer): EncryptedData {
|
|
38
|
+
const iv = randomBytes(IV_LENGTH);
|
|
39
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
40
|
+
|
|
41
|
+
let ciphertext = cipher.update(plaintext, 'utf8', 'hex');
|
|
42
|
+
ciphertext += cipher.final('hex');
|
|
43
|
+
const tag = cipher.getAuthTag();
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
salt: PBKDF2_SALT,
|
|
47
|
+
iv: iv.toString('hex'),
|
|
48
|
+
ciphertext,
|
|
49
|
+
tag: tag.toString('hex'),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Decrypt ciphertext using AES-256-GCM.
|
|
55
|
+
* Throws if key is wrong or data was tampered with.
|
|
56
|
+
*/
|
|
57
|
+
export function decrypt(data: EncryptedData, key: Buffer): string {
|
|
58
|
+
const decipher = createDecipheriv(
|
|
59
|
+
ALGORITHM,
|
|
60
|
+
key,
|
|
61
|
+
Buffer.from(data.iv, 'hex'),
|
|
62
|
+
);
|
|
63
|
+
decipher.setAuthTag(Buffer.from(data.tag, 'hex'));
|
|
64
|
+
|
|
65
|
+
let plaintext = decipher.update(data.ciphertext, 'hex', 'utf8');
|
|
66
|
+
plaintext += decipher.final('utf8');
|
|
67
|
+
return plaintext;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create an HMAC-SHA256 signature.
|
|
72
|
+
* Returns a prefixed string: "hmac-sha256:<hex>"
|
|
73
|
+
*/
|
|
74
|
+
export function hmacSign(data: string, key: Buffer): string {
|
|
75
|
+
const hmac = createHmac('sha256', key);
|
|
76
|
+
hmac.update(data);
|
|
77
|
+
return `hmac-sha256:${hmac.digest('hex')}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Verify an HMAC-SHA256 signature using timing-safe comparison.
|
|
82
|
+
*/
|
|
83
|
+
export function hmacVerify(data: string, signature: string, key: Buffer): boolean {
|
|
84
|
+
if (!signature.startsWith('hmac-sha256:')) return false;
|
|
85
|
+
|
|
86
|
+
const expected = hmacSign(data, key);
|
|
87
|
+
const sigBuf = Buffer.from(signature);
|
|
88
|
+
const expBuf = Buffer.from(expected);
|
|
89
|
+
|
|
90
|
+
if (sigBuf.length !== expBuf.length) return false;
|
|
91
|
+
return timingSafeEqual(sigBuf, expBuf);
|
|
92
|
+
}
|