@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.
Files changed (236) hide show
  1. package/LICENSE +60 -0
  2. package/README.md +362 -0
  3. package/SKILL.md +270 -0
  4. package/dist/auth/crypto.d.ts +31 -0
  5. package/dist/auth/crypto.js +66 -0
  6. package/dist/auth/crypto.js.map +1 -0
  7. package/dist/auth/handoff.d.ts +29 -0
  8. package/dist/auth/handoff.js +180 -0
  9. package/dist/auth/handoff.js.map +1 -0
  10. package/dist/auth/manager.d.ts +46 -0
  11. package/dist/auth/manager.js +127 -0
  12. package/dist/auth/manager.js.map +1 -0
  13. package/dist/auth/oauth-refresh.d.ts +16 -0
  14. package/dist/auth/oauth-refresh.js +91 -0
  15. package/dist/auth/oauth-refresh.js.map +1 -0
  16. package/dist/auth/refresh.d.ts +43 -0
  17. package/dist/auth/refresh.js +217 -0
  18. package/dist/auth/refresh.js.map +1 -0
  19. package/dist/capture/anti-bot.d.ts +15 -0
  20. package/dist/capture/anti-bot.js +43 -0
  21. package/dist/capture/anti-bot.js.map +1 -0
  22. package/dist/capture/blocklist.d.ts +6 -0
  23. package/dist/capture/blocklist.js +70 -0
  24. package/dist/capture/blocklist.js.map +1 -0
  25. package/dist/capture/body-diff.d.ts +8 -0
  26. package/dist/capture/body-diff.js +102 -0
  27. package/dist/capture/body-diff.js.map +1 -0
  28. package/dist/capture/body-variables.d.ts +13 -0
  29. package/dist/capture/body-variables.js +142 -0
  30. package/dist/capture/body-variables.js.map +1 -0
  31. package/dist/capture/domain.d.ts +8 -0
  32. package/dist/capture/domain.js +34 -0
  33. package/dist/capture/domain.js.map +1 -0
  34. package/dist/capture/entropy.d.ts +33 -0
  35. package/dist/capture/entropy.js +100 -0
  36. package/dist/capture/entropy.js.map +1 -0
  37. package/dist/capture/filter.d.ts +11 -0
  38. package/dist/capture/filter.js +49 -0
  39. package/dist/capture/filter.js.map +1 -0
  40. package/dist/capture/graphql.d.ts +21 -0
  41. package/dist/capture/graphql.js +99 -0
  42. package/dist/capture/graphql.js.map +1 -0
  43. package/dist/capture/idle.d.ts +23 -0
  44. package/dist/capture/idle.js +44 -0
  45. package/dist/capture/idle.js.map +1 -0
  46. package/dist/capture/monitor.d.ts +26 -0
  47. package/dist/capture/monitor.js +183 -0
  48. package/dist/capture/monitor.js.map +1 -0
  49. package/dist/capture/oauth-detector.d.ts +18 -0
  50. package/dist/capture/oauth-detector.js +96 -0
  51. package/dist/capture/oauth-detector.js.map +1 -0
  52. package/dist/capture/pagination.d.ts +9 -0
  53. package/dist/capture/pagination.js +40 -0
  54. package/dist/capture/pagination.js.map +1 -0
  55. package/dist/capture/parameterize.d.ts +17 -0
  56. package/dist/capture/parameterize.js +63 -0
  57. package/dist/capture/parameterize.js.map +1 -0
  58. package/dist/capture/scrubber.d.ts +5 -0
  59. package/dist/capture/scrubber.js +38 -0
  60. package/dist/capture/scrubber.js.map +1 -0
  61. package/dist/capture/session.d.ts +46 -0
  62. package/dist/capture/session.js +445 -0
  63. package/dist/capture/session.js.map +1 -0
  64. package/dist/capture/token-detector.d.ts +16 -0
  65. package/dist/capture/token-detector.js +62 -0
  66. package/dist/capture/token-detector.js.map +1 -0
  67. package/dist/capture/verifier.d.ts +17 -0
  68. package/dist/capture/verifier.js +147 -0
  69. package/dist/capture/verifier.js.map +1 -0
  70. package/dist/cli.d.ts +2 -0
  71. package/dist/cli.js +930 -0
  72. package/dist/cli.js.map +1 -0
  73. package/dist/discovery/auth.d.ts +17 -0
  74. package/dist/discovery/auth.js +81 -0
  75. package/dist/discovery/auth.js.map +1 -0
  76. package/dist/discovery/fetch.d.ts +17 -0
  77. package/dist/discovery/fetch.js +59 -0
  78. package/dist/discovery/fetch.js.map +1 -0
  79. package/dist/discovery/frameworks.d.ts +11 -0
  80. package/dist/discovery/frameworks.js +249 -0
  81. package/dist/discovery/frameworks.js.map +1 -0
  82. package/dist/discovery/index.d.ts +21 -0
  83. package/dist/discovery/index.js +219 -0
  84. package/dist/discovery/index.js.map +1 -0
  85. package/dist/discovery/openapi.d.ts +13 -0
  86. package/dist/discovery/openapi.js +175 -0
  87. package/dist/discovery/openapi.js.map +1 -0
  88. package/dist/discovery/probes.d.ts +9 -0
  89. package/dist/discovery/probes.js +70 -0
  90. package/dist/discovery/probes.js.map +1 -0
  91. package/dist/index.d.ts +25 -0
  92. package/dist/index.js +25 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/inspect/report.d.ts +52 -0
  95. package/dist/inspect/report.js +191 -0
  96. package/dist/inspect/report.js.map +1 -0
  97. package/dist/mcp.d.ts +8 -0
  98. package/dist/mcp.js +526 -0
  99. package/dist/mcp.js.map +1 -0
  100. package/dist/orchestration/browse.d.ts +38 -0
  101. package/dist/orchestration/browse.js +198 -0
  102. package/dist/orchestration/browse.js.map +1 -0
  103. package/dist/orchestration/cache.d.ts +15 -0
  104. package/dist/orchestration/cache.js +24 -0
  105. package/dist/orchestration/cache.js.map +1 -0
  106. package/dist/plugin.d.ts +17 -0
  107. package/dist/plugin.js +158 -0
  108. package/dist/plugin.js.map +1 -0
  109. package/dist/read/decoders/deepwiki.d.ts +2 -0
  110. package/dist/read/decoders/deepwiki.js +148 -0
  111. package/dist/read/decoders/deepwiki.js.map +1 -0
  112. package/dist/read/decoders/grokipedia.d.ts +2 -0
  113. package/dist/read/decoders/grokipedia.js +210 -0
  114. package/dist/read/decoders/grokipedia.js.map +1 -0
  115. package/dist/read/decoders/hackernews.d.ts +2 -0
  116. package/dist/read/decoders/hackernews.js +168 -0
  117. package/dist/read/decoders/hackernews.js.map +1 -0
  118. package/dist/read/decoders/index.d.ts +2 -0
  119. package/dist/read/decoders/index.js +12 -0
  120. package/dist/read/decoders/index.js.map +1 -0
  121. package/dist/read/decoders/reddit.d.ts +2 -0
  122. package/dist/read/decoders/reddit.js +142 -0
  123. package/dist/read/decoders/reddit.js.map +1 -0
  124. package/dist/read/decoders/twitter.d.ts +12 -0
  125. package/dist/read/decoders/twitter.js +187 -0
  126. package/dist/read/decoders/twitter.js.map +1 -0
  127. package/dist/read/decoders/wikipedia.d.ts +2 -0
  128. package/dist/read/decoders/wikipedia.js +66 -0
  129. package/dist/read/decoders/wikipedia.js.map +1 -0
  130. package/dist/read/decoders/youtube.d.ts +2 -0
  131. package/dist/read/decoders/youtube.js +69 -0
  132. package/dist/read/decoders/youtube.js.map +1 -0
  133. package/dist/read/extract.d.ts +25 -0
  134. package/dist/read/extract.js +320 -0
  135. package/dist/read/extract.js.map +1 -0
  136. package/dist/read/index.d.ts +14 -0
  137. package/dist/read/index.js +66 -0
  138. package/dist/read/index.js.map +1 -0
  139. package/dist/read/peek.d.ts +9 -0
  140. package/dist/read/peek.js +137 -0
  141. package/dist/read/peek.js.map +1 -0
  142. package/dist/read/types.d.ts +44 -0
  143. package/dist/read/types.js +3 -0
  144. package/dist/read/types.js.map +1 -0
  145. package/dist/replay/engine.d.ts +53 -0
  146. package/dist/replay/engine.js +441 -0
  147. package/dist/replay/engine.js.map +1 -0
  148. package/dist/replay/truncate.d.ts +16 -0
  149. package/dist/replay/truncate.js +92 -0
  150. package/dist/replay/truncate.js.map +1 -0
  151. package/dist/serve.d.ts +31 -0
  152. package/dist/serve.js +149 -0
  153. package/dist/serve.js.map +1 -0
  154. package/dist/skill/generator.d.ts +44 -0
  155. package/dist/skill/generator.js +419 -0
  156. package/dist/skill/generator.js.map +1 -0
  157. package/dist/skill/importer.d.ts +26 -0
  158. package/dist/skill/importer.js +80 -0
  159. package/dist/skill/importer.js.map +1 -0
  160. package/dist/skill/search.d.ts +19 -0
  161. package/dist/skill/search.js +51 -0
  162. package/dist/skill/search.js.map +1 -0
  163. package/dist/skill/signing.d.ts +16 -0
  164. package/dist/skill/signing.js +34 -0
  165. package/dist/skill/signing.js.map +1 -0
  166. package/dist/skill/ssrf.d.ts +27 -0
  167. package/dist/skill/ssrf.js +210 -0
  168. package/dist/skill/ssrf.js.map +1 -0
  169. package/dist/skill/store.d.ts +7 -0
  170. package/dist/skill/store.js +93 -0
  171. package/dist/skill/store.js.map +1 -0
  172. package/dist/stats/report.d.ts +26 -0
  173. package/dist/stats/report.js +157 -0
  174. package/dist/stats/report.js.map +1 -0
  175. package/dist/types.d.ts +214 -0
  176. package/dist/types.js +3 -0
  177. package/dist/types.js.map +1 -0
  178. package/package.json +58 -0
  179. package/src/auth/crypto.ts +92 -0
  180. package/src/auth/handoff.ts +229 -0
  181. package/src/auth/manager.ts +140 -0
  182. package/src/auth/oauth-refresh.ts +120 -0
  183. package/src/auth/refresh.ts +300 -0
  184. package/src/capture/anti-bot.ts +63 -0
  185. package/src/capture/blocklist.ts +75 -0
  186. package/src/capture/body-diff.ts +109 -0
  187. package/src/capture/body-variables.ts +156 -0
  188. package/src/capture/domain.ts +34 -0
  189. package/src/capture/entropy.ts +121 -0
  190. package/src/capture/filter.ts +56 -0
  191. package/src/capture/graphql.ts +124 -0
  192. package/src/capture/idle.ts +45 -0
  193. package/src/capture/monitor.ts +224 -0
  194. package/src/capture/oauth-detector.ts +106 -0
  195. package/src/capture/pagination.ts +49 -0
  196. package/src/capture/parameterize.ts +68 -0
  197. package/src/capture/scrubber.ts +49 -0
  198. package/src/capture/session.ts +502 -0
  199. package/src/capture/token-detector.ts +76 -0
  200. package/src/capture/verifier.ts +171 -0
  201. package/src/cli.ts +1031 -0
  202. package/src/discovery/auth.ts +99 -0
  203. package/src/discovery/fetch.ts +85 -0
  204. package/src/discovery/frameworks.ts +231 -0
  205. package/src/discovery/index.ts +256 -0
  206. package/src/discovery/openapi.ts +230 -0
  207. package/src/discovery/probes.ts +76 -0
  208. package/src/index.ts +26 -0
  209. package/src/inspect/report.ts +247 -0
  210. package/src/mcp.ts +618 -0
  211. package/src/orchestration/browse.ts +250 -0
  212. package/src/orchestration/cache.ts +37 -0
  213. package/src/plugin.ts +188 -0
  214. package/src/read/decoders/deepwiki.ts +180 -0
  215. package/src/read/decoders/grokipedia.ts +246 -0
  216. package/src/read/decoders/hackernews.ts +198 -0
  217. package/src/read/decoders/index.ts +15 -0
  218. package/src/read/decoders/reddit.ts +158 -0
  219. package/src/read/decoders/twitter.ts +211 -0
  220. package/src/read/decoders/wikipedia.ts +75 -0
  221. package/src/read/decoders/youtube.ts +75 -0
  222. package/src/read/extract.ts +396 -0
  223. package/src/read/index.ts +78 -0
  224. package/src/read/peek.ts +175 -0
  225. package/src/read/types.ts +37 -0
  226. package/src/replay/engine.ts +559 -0
  227. package/src/replay/truncate.ts +116 -0
  228. package/src/serve.ts +189 -0
  229. package/src/skill/generator.ts +473 -0
  230. package/src/skill/importer.ts +107 -0
  231. package/src/skill/search.ts +76 -0
  232. package/src/skill/signing.ts +36 -0
  233. package/src/skill/ssrf.ts +238 -0
  234. package/src/skill/store.ts +107 -0
  235. package/src/stats/report.ts +208 -0
  236. 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
+ }