@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,208 @@
|
|
|
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
|
+
import type { SkillFile } from '../types.js';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_SKILLS_DIR = join(homedir(), '.apitap', 'skills');
|
|
8
|
+
|
|
9
|
+
export interface DomainStats {
|
|
10
|
+
domain: string;
|
|
11
|
+
endpoints: number;
|
|
12
|
+
replayable: number;
|
|
13
|
+
domBytes: number;
|
|
14
|
+
totalNetworkBytes: number;
|
|
15
|
+
skillFileBytes: number;
|
|
16
|
+
totalResponseBytes: number;
|
|
17
|
+
browserTokens: number;
|
|
18
|
+
replayTokens: number;
|
|
19
|
+
savingsPercent: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StatsReport {
|
|
23
|
+
domains: DomainStats[];
|
|
24
|
+
totals: {
|
|
25
|
+
domains: number;
|
|
26
|
+
endpoints: number;
|
|
27
|
+
replayable: number;
|
|
28
|
+
totalDomBytes: number;
|
|
29
|
+
browserTokens: number;
|
|
30
|
+
replayTokens: number;
|
|
31
|
+
savingsPercent: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Default browser token estimate when no measurement available (conservative). */
|
|
36
|
+
const DEFAULT_DOM_BYTES = 400_000; // ~100K tokens
|
|
37
|
+
|
|
38
|
+
export async function generateStatsReport(
|
|
39
|
+
skillsDir: string = DEFAULT_SKILLS_DIR,
|
|
40
|
+
): Promise<StatsReport> {
|
|
41
|
+
let files: string[];
|
|
42
|
+
try {
|
|
43
|
+
files = (await readdir(skillsDir)).filter(f => f.endsWith('.json'));
|
|
44
|
+
} catch {
|
|
45
|
+
return emptyReport();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const domains: DomainStats[] = [];
|
|
49
|
+
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const filePath = join(skillsDir, file);
|
|
52
|
+
try {
|
|
53
|
+
const content = await readFile(filePath, 'utf-8');
|
|
54
|
+
const skill = JSON.parse(content) as SkillFile;
|
|
55
|
+
const fileStats = await stat(filePath);
|
|
56
|
+
const skillFileBytes = fileStats.size;
|
|
57
|
+
|
|
58
|
+
// Browser cost: use measured if available, else default
|
|
59
|
+
const domBytes = skill.metadata.browserCost?.domBytes ?? DEFAULT_DOM_BYTES;
|
|
60
|
+
const totalNetworkBytes = skill.metadata.browserCost?.totalNetworkBytes ?? 0;
|
|
61
|
+
|
|
62
|
+
// Replay cost: skill file + response data
|
|
63
|
+
const totalResponseBytes = skill.endpoints.reduce(
|
|
64
|
+
(sum, ep) => sum + (ep.responseBytes ?? 0), 0,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const browserTokens = Math.round(domBytes / 4);
|
|
68
|
+
const replayTokens = Math.round((skillFileBytes + totalResponseBytes) / 4);
|
|
69
|
+
const savingsPercent = browserTokens > 0
|
|
70
|
+
? Math.round((1 - replayTokens / browserTokens) * 1000) / 10
|
|
71
|
+
: 0;
|
|
72
|
+
|
|
73
|
+
const replayable = skill.endpoints.filter(ep => {
|
|
74
|
+
const tier = ep.replayability?.tier;
|
|
75
|
+
return tier === 'green' || tier === 'yellow';
|
|
76
|
+
}).length;
|
|
77
|
+
|
|
78
|
+
domains.push({
|
|
79
|
+
domain: skill.domain,
|
|
80
|
+
endpoints: skill.endpoints.length,
|
|
81
|
+
replayable,
|
|
82
|
+
domBytes,
|
|
83
|
+
totalNetworkBytes,
|
|
84
|
+
skillFileBytes,
|
|
85
|
+
totalResponseBytes,
|
|
86
|
+
browserTokens,
|
|
87
|
+
replayTokens,
|
|
88
|
+
savingsPercent,
|
|
89
|
+
});
|
|
90
|
+
} catch {
|
|
91
|
+
// Skip invalid files
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const totals = {
|
|
96
|
+
domains: domains.length,
|
|
97
|
+
endpoints: domains.reduce((s, d) => s + d.endpoints, 0),
|
|
98
|
+
replayable: domains.reduce((s, d) => s + d.replayable, 0),
|
|
99
|
+
totalDomBytes: domains.reduce((s, d) => s + d.domBytes, 0),
|
|
100
|
+
browserTokens: domains.reduce((s, d) => s + d.browserTokens, 0),
|
|
101
|
+
replayTokens: domains.reduce((s, d) => s + d.replayTokens, 0),
|
|
102
|
+
savingsPercent: 0,
|
|
103
|
+
};
|
|
104
|
+
totals.savingsPercent = totals.browserTokens > 0
|
|
105
|
+
? Math.round((1 - totals.replayTokens / totals.browserTokens) * 1000) / 10
|
|
106
|
+
: 0;
|
|
107
|
+
|
|
108
|
+
return { domains, totals };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function formatStatsHuman(report: StatsReport): string {
|
|
112
|
+
if (report.domains.length === 0) {
|
|
113
|
+
return ' No skill files found. Run `apitap capture <url>` first.';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const lines: string[] = [
|
|
117
|
+
'',
|
|
118
|
+
' ApiTap — Usage Report',
|
|
119
|
+
' ' + '═'.repeat(22),
|
|
120
|
+
'',
|
|
121
|
+
` Skill files: ${report.totals.domains} domains`,
|
|
122
|
+
` Total endpoints: ${report.totals.endpoints}`,
|
|
123
|
+
` Replayable: ${report.totals.replayable} (green/yellow tier)`,
|
|
124
|
+
'',
|
|
125
|
+
' Token savings (measured):',
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
// Table
|
|
129
|
+
const domCol = 24;
|
|
130
|
+
const hdr = [
|
|
131
|
+
' ' + pad('Domain', domCol) + pad('DOM size', 10) + pad('Browser', 10) + pad('ApiTap', 10) + 'Saved',
|
|
132
|
+
];
|
|
133
|
+
const sep = ' ' + '─'.repeat(domCol) + '─'.repeat(10) + '─'.repeat(10) + '─'.repeat(10) + '─'.repeat(8);
|
|
134
|
+
|
|
135
|
+
lines.push(sep);
|
|
136
|
+
lines.push(...hdr);
|
|
137
|
+
lines.push(sep);
|
|
138
|
+
|
|
139
|
+
for (const d of report.domains) {
|
|
140
|
+
const domName = d.domain.length > domCol - 2
|
|
141
|
+
? d.domain.slice(0, domCol - 3) + '…'
|
|
142
|
+
: d.domain;
|
|
143
|
+
const hasMeasured = d.domBytes !== DEFAULT_DOM_BYTES;
|
|
144
|
+
const browserLabel = hasMeasured
|
|
145
|
+
? formatTokens(d.browserTokens)
|
|
146
|
+
: `~${formatTokens(d.browserTokens)}`;
|
|
147
|
+
lines.push(
|
|
148
|
+
' ' +
|
|
149
|
+
pad(domName, domCol) +
|
|
150
|
+
pad(formatBytes(d.domBytes), 10) +
|
|
151
|
+
pad(browserLabel, 10) +
|
|
152
|
+
pad(formatTokens(d.replayTokens), 10) +
|
|
153
|
+
`${d.savingsPercent}%`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
lines.push(sep);
|
|
158
|
+
const hasMeasuredTotals = report.domains.some(d => d.domBytes !== DEFAULT_DOM_BYTES);
|
|
159
|
+
const totalBrowserLabel = hasMeasuredTotals
|
|
160
|
+
? formatTokens(report.totals.browserTokens)
|
|
161
|
+
: `~${formatTokens(report.totals.browserTokens)}`;
|
|
162
|
+
lines.push(
|
|
163
|
+
' ' +
|
|
164
|
+
pad('Total', domCol) +
|
|
165
|
+
pad(formatBytes(report.totals.totalDomBytes), 10) +
|
|
166
|
+
pad(totalBrowserLabel, 10) +
|
|
167
|
+
pad(formatTokens(report.totals.replayTokens), 10) +
|
|
168
|
+
`${report.totals.savingsPercent}%`,
|
|
169
|
+
);
|
|
170
|
+
lines.push(sep);
|
|
171
|
+
lines.push('');
|
|
172
|
+
|
|
173
|
+
if (hasMeasuredTotals) {
|
|
174
|
+
lines.push(' Browser: measured DOM size during capture (page.content().length / 4)');
|
|
175
|
+
} else {
|
|
176
|
+
lines.push(' Browser: estimated ~100K tokens/site (re-capture for measured values)');
|
|
177
|
+
}
|
|
178
|
+
lines.push(' ApiTap: measured skill file + API response sizes');
|
|
179
|
+
lines.push('');
|
|
180
|
+
|
|
181
|
+
return lines.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function pad(s: string, width: number): string {
|
|
185
|
+
return s.length >= width ? s : s + ' '.repeat(width - s.length);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function formatBytes(bytes: number): string {
|
|
189
|
+
if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`;
|
|
190
|
+
if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(0)} KB`;
|
|
191
|
+
return `${bytes} B`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function formatTokens(tokens: number): string {
|
|
195
|
+
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(2)}M tk`;
|
|
196
|
+
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K tk`;
|
|
197
|
+
return `${tokens} tk`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function emptyReport(): StatsReport {
|
|
201
|
+
return {
|
|
202
|
+
domains: [],
|
|
203
|
+
totals: {
|
|
204
|
+
domains: 0, endpoints: 0, replayable: 0,
|
|
205
|
+
totalDomBytes: 0, browserTokens: 0, replayTokens: 0, savingsPercent: 0,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
|
|
3
|
+
/** A captured HTTP request/response pair from the browser */
|
|
4
|
+
export interface CapturedExchange {
|
|
5
|
+
request: {
|
|
6
|
+
url: string;
|
|
7
|
+
method: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
postData?: string; // POST/PUT/PATCH body
|
|
10
|
+
};
|
|
11
|
+
response: {
|
|
12
|
+
status: number;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
body: string;
|
|
15
|
+
contentType: string;
|
|
16
|
+
};
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Stored auth credentials for a domain */
|
|
21
|
+
export interface StoredAuth {
|
|
22
|
+
type: 'bearer' | 'api-key' | 'cookie' | 'custom';
|
|
23
|
+
header: string;
|
|
24
|
+
value: string;
|
|
25
|
+
// v0.8: refreshable tokens (body-based, like CSRF)
|
|
26
|
+
tokens?: Record<string, StoredToken>;
|
|
27
|
+
// v0.8: cached browser session for faster refresh
|
|
28
|
+
session?: StoredSession;
|
|
29
|
+
// v0.9: OAuth credentials (stored encrypted, never in skill file)
|
|
30
|
+
refreshToken?: string;
|
|
31
|
+
clientSecret?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extended auth storage for v0.8 token refresh.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** Stored token with refresh metadata */
|
|
39
|
+
export interface StoredToken {
|
|
40
|
+
value: string;
|
|
41
|
+
refreshedAt: string; // ISO timestamp
|
|
42
|
+
expiresAt?: string; // computed from ttlHint
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Cached browser session for warm restarts */
|
|
46
|
+
export interface StoredSession {
|
|
47
|
+
cookies: PlaywrightCookie[];
|
|
48
|
+
localStorage?: Record<string, string>;
|
|
49
|
+
savedAt: string;
|
|
50
|
+
maxAgeMs?: number; // when to consider session stale, default 24h
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Playwright cookie shape (subset of full type) */
|
|
54
|
+
export interface PlaywrightCookie {
|
|
55
|
+
name: string;
|
|
56
|
+
value: string;
|
|
57
|
+
domain: string;
|
|
58
|
+
path: string;
|
|
59
|
+
expires?: number;
|
|
60
|
+
httpOnly?: boolean;
|
|
61
|
+
secure?: boolean;
|
|
62
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** OAuth configuration (non-secret, lives in shareable skill file) */
|
|
66
|
+
export interface OAuthConfig {
|
|
67
|
+
tokenEndpoint: string;
|
|
68
|
+
clientId: string;
|
|
69
|
+
grantType: 'refresh_token' | 'client_credentials';
|
|
70
|
+
scope?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Top-level auth config on SkillFile */
|
|
74
|
+
export interface SkillAuth {
|
|
75
|
+
refreshUrl?: string; // URL to navigate for refresh, defaults to baseUrl
|
|
76
|
+
browserMode: 'headless' | 'visible';
|
|
77
|
+
captchaRisk: boolean;
|
|
78
|
+
ttlHint?: number; // estimated seconds until expiry
|
|
79
|
+
oauthConfig?: OAuthConfig; // v0.9: OAuth token endpoint config
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Replay difficulty classification for an endpoint */
|
|
83
|
+
export interface Replayability {
|
|
84
|
+
tier: 'green' | 'yellow' | 'orange' | 'red' | 'unknown';
|
|
85
|
+
verified: boolean;
|
|
86
|
+
signals: string[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Detected pagination pattern */
|
|
90
|
+
export interface PaginationInfo {
|
|
91
|
+
type: 'offset' | 'cursor' | 'page';
|
|
92
|
+
paramName: string;
|
|
93
|
+
limitParam?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Request body template for POST/PUT/PATCH endpoints */
|
|
97
|
+
export interface RequestBody {
|
|
98
|
+
contentType: string;
|
|
99
|
+
template: string | Record<string, unknown>;
|
|
100
|
+
variables?: string[]; // JSON paths of substitutable fields (user-provided params)
|
|
101
|
+
refreshableTokens?: string[]; // v0.8: system-refreshed tokens
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A single API endpoint in a skill file */
|
|
105
|
+
export interface SkillEndpoint {
|
|
106
|
+
id: string;
|
|
107
|
+
method: string;
|
|
108
|
+
path: string;
|
|
109
|
+
queryParams: Record<string, { type: string; example: string }>;
|
|
110
|
+
headers: Record<string, string>;
|
|
111
|
+
responseShape: { type: string; fields?: string[] };
|
|
112
|
+
examples: {
|
|
113
|
+
request: { url: string; headers: Record<string, string> };
|
|
114
|
+
responsePreview: unknown;
|
|
115
|
+
};
|
|
116
|
+
replayability?: Replayability;
|
|
117
|
+
pagination?: PaginationInfo;
|
|
118
|
+
requestBody?: RequestBody;
|
|
119
|
+
responseBytes?: number; // v1.0: response body size in bytes
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The full skill file written to disk */
|
|
123
|
+
export interface SkillFile {
|
|
124
|
+
version: string;
|
|
125
|
+
domain: string;
|
|
126
|
+
capturedAt: string;
|
|
127
|
+
baseUrl: string;
|
|
128
|
+
endpoints: SkillEndpoint[];
|
|
129
|
+
metadata: {
|
|
130
|
+
captureCount: number;
|
|
131
|
+
filteredCount: number;
|
|
132
|
+
toolVersion: string;
|
|
133
|
+
browserCost?: { // v1.0: measured browser cost during capture
|
|
134
|
+
domBytes: number; // page.content().length
|
|
135
|
+
totalNetworkBytes: number; // sum of ALL response body sizes
|
|
136
|
+
totalRequests: number;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
provenance: 'self' | 'imported' | 'unsigned';
|
|
140
|
+
signature?: string;
|
|
141
|
+
auth?: SkillAuth; // v0.8: top-level auth config
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Interactive element on a page, for agent targeting */
|
|
145
|
+
export interface PageElement {
|
|
146
|
+
ref: string; // "e0", "e1", etc. — agent uses this to target clicks/types
|
|
147
|
+
tag: string; // "button", "a", "input", "select"
|
|
148
|
+
role?: string; // ARIA role
|
|
149
|
+
text: string; // visible text (truncated 200 chars)
|
|
150
|
+
name?: string; // input name
|
|
151
|
+
placeholder?: string; // input placeholder
|
|
152
|
+
href?: string; // link href
|
|
153
|
+
type?: string; // input type
|
|
154
|
+
disabled?: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Structured page snapshot returned after every interaction */
|
|
158
|
+
export interface PageSnapshot {
|
|
159
|
+
url: string;
|
|
160
|
+
title: string;
|
|
161
|
+
elements: PageElement[];
|
|
162
|
+
endpointsCaptured: number;
|
|
163
|
+
totalRequests: number;
|
|
164
|
+
filteredRequests: number;
|
|
165
|
+
recentEndpoints: string[]; // last 5 discovered (e.g. "GET /api/search")
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Result from a capture session interaction */
|
|
169
|
+
export interface InteractionResult {
|
|
170
|
+
success: boolean;
|
|
171
|
+
error?: string;
|
|
172
|
+
snapshot: PageSnapshot;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Result from finishing a capture session */
|
|
176
|
+
export interface FinishResult {
|
|
177
|
+
aborted: boolean;
|
|
178
|
+
domains: {
|
|
179
|
+
domain: string;
|
|
180
|
+
endpointCount: number;
|
|
181
|
+
tiers: Record<string, number>;
|
|
182
|
+
skillFile: string;
|
|
183
|
+
}[];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Summary returned by `apitap list` */
|
|
187
|
+
export interface SkillSummary {
|
|
188
|
+
domain: string;
|
|
189
|
+
skillFile: string;
|
|
190
|
+
endpointCount: number;
|
|
191
|
+
capturedAt: string;
|
|
192
|
+
provenance: 'self' | 'imported' | 'unsigned';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// --- Discovery types (Milestone 2: Smart Discovery) ---
|
|
196
|
+
|
|
197
|
+
/** Detected web framework with predicted API patterns */
|
|
198
|
+
export interface DetectedFramework {
|
|
199
|
+
name: string;
|
|
200
|
+
confidence: 'high' | 'medium' | 'low';
|
|
201
|
+
apiPatterns: string[]; // predicted API paths (e.g. "/wp-json/wp/v2/posts")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Discovered API spec */
|
|
205
|
+
export interface DiscoveredSpec {
|
|
206
|
+
type: 'openapi' | 'swagger' | 'graphql-introspection';
|
|
207
|
+
url: string;
|
|
208
|
+
version?: string;
|
|
209
|
+
endpointCount?: number;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Result from probing a common API path */
|
|
213
|
+
export interface ProbeResult {
|
|
214
|
+
method: string;
|
|
215
|
+
path: string;
|
|
216
|
+
status: number;
|
|
217
|
+
contentType: string;
|
|
218
|
+
isApi: boolean;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Result from the discovery pipeline */
|
|
222
|
+
export interface DiscoveryResult {
|
|
223
|
+
confidence: 'high' | 'medium' | 'low' | 'none';
|
|
224
|
+
skillFile?: SkillFile;
|
|
225
|
+
hints?: string[];
|
|
226
|
+
frameworks?: DetectedFramework[];
|
|
227
|
+
specs?: DiscoveredSpec[];
|
|
228
|
+
probes?: ProbeResult[];
|
|
229
|
+
duration: number; // ms
|
|
230
|
+
authRequired?: boolean; // true if site appears to need login
|
|
231
|
+
authSignals?: string[]; // reasons auth was detected
|
|
232
|
+
loginUrl?: string; // detected login page URL
|
|
233
|
+
}
|