@apitap/core 1.0.22 → 1.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/README.md +38 -2
- package/dist/bridge/client.d.ts +19 -0
- package/dist/bridge/client.js +52 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/cli.js +41 -1
- package/dist/cli.js.map +1 -1
- package/dist/extension/install.d.ts +13 -0
- package/dist/extension/install.js +53 -0
- package/dist/extension/install.js.map +1 -0
- package/dist/native-host.d.ts +24 -0
- package/dist/native-host.js +288 -0
- package/dist/native-host.js.map +1 -0
- package/dist/orchestration/browse.d.ts +5 -1
- package/dist/orchestration/browse.js +109 -0
- package/dist/orchestration/browse.js.map +1 -1
- package/package.json +2 -1
- package/src/bridge/client.ts +67 -0
- package/src/cli.ts +47 -1
- package/src/extension/install.ts +73 -0
- package/src/native-host.ts +354 -0
- package/src/orchestration/browse.ts +122 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ApiTap Native Messaging Host
|
|
3
|
+
// Receives skill files from the Chrome extension and saves to ~/.apitap/skills/
|
|
4
|
+
import { promises as fs } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import net from 'node:net';
|
|
8
|
+
import { signSkillFile } from './skill/signing.js';
|
|
9
|
+
import { deriveKey } from './auth/crypto.js';
|
|
10
|
+
import { getMachineId } from './auth/manager.js';
|
|
11
|
+
const SKILLS_DIR = path.join(os.homedir(), '.apitap', 'skills');
|
|
12
|
+
const VERSION = '1.0.0';
|
|
13
|
+
// Sign skill JSON using the CLI's HMAC signing infrastructure
|
|
14
|
+
async function signSkillJson(skillJson) {
|
|
15
|
+
try {
|
|
16
|
+
const skill = JSON.parse(skillJson);
|
|
17
|
+
const machineId = await getMachineId();
|
|
18
|
+
const key = deriveKey(machineId);
|
|
19
|
+
const signed = signSkillFile(skill, key);
|
|
20
|
+
return JSON.stringify(signed);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return skillJson; // Return unsigned if signing fails
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Domain validation — must match src/skill/store.ts conventions
|
|
27
|
+
function isValidDomain(domain) {
|
|
28
|
+
if (!domain || domain.length === 0 || domain.length > 253)
|
|
29
|
+
return false;
|
|
30
|
+
if (domain.includes('/') || domain.includes('\\'))
|
|
31
|
+
return false;
|
|
32
|
+
if (domain.includes('..'))
|
|
33
|
+
return false;
|
|
34
|
+
if (domain.startsWith('.') || domain.startsWith('-'))
|
|
35
|
+
return false;
|
|
36
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(domain))
|
|
37
|
+
return false;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
export async function handleNativeMessage(request, skillsDir = SKILLS_DIR) {
|
|
41
|
+
try {
|
|
42
|
+
if (request.action === 'ping') {
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
action: 'pong',
|
|
46
|
+
version: VERSION,
|
|
47
|
+
skillsDir,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (request.action === 'save_skill') {
|
|
51
|
+
if (!request.domain || !isValidDomain(request.domain)) {
|
|
52
|
+
return { success: false, error: `Invalid domain: ${request.domain}` };
|
|
53
|
+
}
|
|
54
|
+
if (!request.skillJson) {
|
|
55
|
+
return { success: false, error: 'Missing skillJson' };
|
|
56
|
+
}
|
|
57
|
+
// Validate JSON
|
|
58
|
+
try {
|
|
59
|
+
JSON.parse(request.skillJson);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { success: false, error: 'Invalid JSON in skillJson' };
|
|
63
|
+
}
|
|
64
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
65
|
+
const filePath = path.join(skillsDir, `${request.domain}.json`);
|
|
66
|
+
// Sign the skill file on receive (CLI is the signing authority)
|
|
67
|
+
const signed = await signSkillJson(request.skillJson);
|
|
68
|
+
await fs.writeFile(filePath, signed, 'utf-8');
|
|
69
|
+
return { success: true, path: filePath };
|
|
70
|
+
}
|
|
71
|
+
if (request.action === 'save_batch') {
|
|
72
|
+
if (!Array.isArray(request.skills) || request.skills.length === 0) {
|
|
73
|
+
return { success: false, error: 'Missing or empty skills array' };
|
|
74
|
+
}
|
|
75
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
76
|
+
const paths = [];
|
|
77
|
+
for (const { domain, skillJson } of request.skills) {
|
|
78
|
+
if (!isValidDomain(domain)) {
|
|
79
|
+
return { success: false, error: `Invalid domain: ${domain}` };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
JSON.parse(skillJson);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return { success: false, error: `Invalid JSON for domain ${domain}` };
|
|
86
|
+
}
|
|
87
|
+
const filePath = path.join(skillsDir, `${domain}.json`);
|
|
88
|
+
const signed = await signSkillJson(skillJson);
|
|
89
|
+
await fs.writeFile(filePath, signed, 'utf-8');
|
|
90
|
+
paths.push(filePath);
|
|
91
|
+
}
|
|
92
|
+
return { success: true, paths };
|
|
93
|
+
}
|
|
94
|
+
return { success: false, error: `Unknown action: ${request.action}` };
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return { success: false, error: String(err) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// --- Relay handler ---
|
|
101
|
+
// Actions handled locally by the native host (filesystem operations)
|
|
102
|
+
const LOCAL_ACTIONS = new Set(['save_skill', 'save_batch', 'ping']);
|
|
103
|
+
// Actions relayed to the extension (browser operations)
|
|
104
|
+
const EXTENSION_ACTIONS = new Set(['capture_request']);
|
|
105
|
+
export function createRelayHandler(sendToExtension, skillsDir = SKILLS_DIR) {
|
|
106
|
+
return async (message) => {
|
|
107
|
+
if (LOCAL_ACTIONS.has(message.action)) {
|
|
108
|
+
return handleNativeMessage(message, skillsDir);
|
|
109
|
+
}
|
|
110
|
+
if (EXTENSION_ACTIONS.has(message.action)) {
|
|
111
|
+
try {
|
|
112
|
+
return await sendToExtension(message);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return { success: false, error: String(err) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { success: false, error: `Unknown action: ${message.action}` };
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
let socketServer = null;
|
|
122
|
+
export async function startSocketServer(socketPath, handler) {
|
|
123
|
+
// Clean up stale socket
|
|
124
|
+
try {
|
|
125
|
+
await fs.unlink(socketPath);
|
|
126
|
+
}
|
|
127
|
+
catch { /* doesn't exist — fine */ }
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
socketServer = net.createServer((conn) => {
|
|
130
|
+
let buffer = '';
|
|
131
|
+
conn.on('data', (chunk) => {
|
|
132
|
+
buffer += chunk.toString();
|
|
133
|
+
const newlineIdx = buffer.indexOf('\n');
|
|
134
|
+
if (newlineIdx === -1)
|
|
135
|
+
return;
|
|
136
|
+
const line = buffer.slice(0, newlineIdx);
|
|
137
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
138
|
+
let request;
|
|
139
|
+
try {
|
|
140
|
+
request = JSON.parse(line);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
conn.end(JSON.stringify({ success: false, error: 'Invalid JSON' }) + '\n');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
handler(request).then((response) => conn.end(JSON.stringify(response) + '\n'), (err) => conn.end(JSON.stringify({ success: false, error: String(err) }) + '\n'));
|
|
147
|
+
});
|
|
148
|
+
conn.on('error', () => { });
|
|
149
|
+
});
|
|
150
|
+
socketServer.on('error', reject);
|
|
151
|
+
socketServer.listen(socketPath, () => resolve());
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
export async function stopSocketServer() {
|
|
155
|
+
if (!socketServer)
|
|
156
|
+
return;
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
socketServer.close(() => resolve());
|
|
159
|
+
socketServer = null;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// --- stdio framing (only runs when executed directly, not when imported for tests) ---
|
|
163
|
+
function readMessage() {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
const headerBuf = Buffer.alloc(4);
|
|
166
|
+
let headerRead = 0;
|
|
167
|
+
function onData(chunk) {
|
|
168
|
+
let offset = 0;
|
|
169
|
+
// Read header
|
|
170
|
+
if (headerRead < 4) {
|
|
171
|
+
const needed = 4 - headerRead;
|
|
172
|
+
const toCopy = Math.min(needed, chunk.length);
|
|
173
|
+
chunk.copy(headerBuf, headerRead, 0, toCopy);
|
|
174
|
+
headerRead += toCopy;
|
|
175
|
+
offset = toCopy;
|
|
176
|
+
if (headerRead < 4)
|
|
177
|
+
return; // need more data for header
|
|
178
|
+
}
|
|
179
|
+
const messageLength = headerBuf.readUInt32LE(0);
|
|
180
|
+
if (messageLength > 1024 * 1024) {
|
|
181
|
+
process.stderr.write(`Message too large: ${messageLength}\n`);
|
|
182
|
+
resolve(null);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Accumulate message body
|
|
186
|
+
const bodyBuf = Buffer.alloc(messageLength);
|
|
187
|
+
let bodyRead = 0;
|
|
188
|
+
if (offset < chunk.length) {
|
|
189
|
+
const remaining = chunk.subarray(offset);
|
|
190
|
+
const toCopy = Math.min(remaining.length, messageLength);
|
|
191
|
+
remaining.copy(bodyBuf, 0, 0, toCopy);
|
|
192
|
+
bodyRead = toCopy;
|
|
193
|
+
}
|
|
194
|
+
if (bodyRead >= messageLength) {
|
|
195
|
+
process.stdin.removeListener('data', onData);
|
|
196
|
+
try {
|
|
197
|
+
resolve(JSON.parse(bodyBuf.toString('utf-8')));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
resolve(null);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Need more data
|
|
205
|
+
function onMoreData(moreChunk) {
|
|
206
|
+
const toCopy = Math.min(moreChunk.length, messageLength - bodyRead);
|
|
207
|
+
moreChunk.copy(bodyBuf, bodyRead, 0, toCopy);
|
|
208
|
+
bodyRead += toCopy;
|
|
209
|
+
if (bodyRead >= messageLength) {
|
|
210
|
+
process.stdin.removeListener('data', onMoreData);
|
|
211
|
+
process.stdin.removeListener('data', onData);
|
|
212
|
+
try {
|
|
213
|
+
resolve(JSON.parse(bodyBuf.toString('utf-8')));
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
resolve(null);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
process.stdin.on('data', onMoreData);
|
|
221
|
+
}
|
|
222
|
+
process.stdin.on('data', onData);
|
|
223
|
+
process.stdin.on('end', () => resolve(null));
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function sendMessage(message) {
|
|
227
|
+
const json = Buffer.from(JSON.stringify(message), 'utf-8');
|
|
228
|
+
const header = Buffer.alloc(4);
|
|
229
|
+
header.writeUInt32LE(json.length, 0);
|
|
230
|
+
process.stdout.write(header);
|
|
231
|
+
process.stdout.write(json);
|
|
232
|
+
}
|
|
233
|
+
// Main loop — only runs when executed as a script
|
|
234
|
+
const isMainModule = process.argv[1] &&
|
|
235
|
+
(process.argv[1].endsWith('native-host.ts') || process.argv[1].endsWith('native-host.js'));
|
|
236
|
+
if (isMainModule) {
|
|
237
|
+
const bridgeDir = path.join(os.homedir(), '.apitap');
|
|
238
|
+
const socketPath = path.join(bridgeDir, 'bridge.sock');
|
|
239
|
+
// Pending CLI requests waiting for extension responses
|
|
240
|
+
const pendingRequests = new Map();
|
|
241
|
+
let requestCounter = 0;
|
|
242
|
+
// Send a message to the extension via stdout and wait for response
|
|
243
|
+
function sendToExtension(message) {
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
const id = String(++requestCounter);
|
|
246
|
+
const timer = setTimeout(() => {
|
|
247
|
+
pendingRequests.delete(id);
|
|
248
|
+
resolve({ success: false, error: 'approval_timeout' });
|
|
249
|
+
}, 60_000);
|
|
250
|
+
pendingRequests.set(id, { resolve, timer });
|
|
251
|
+
// Tag message with ID so we can match the response
|
|
252
|
+
sendMessage({ ...message, _relayId: id });
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const handler = createRelayHandler(sendToExtension);
|
|
256
|
+
(async () => {
|
|
257
|
+
// Ensure bridge directory exists
|
|
258
|
+
await fs.mkdir(bridgeDir, { recursive: true });
|
|
259
|
+
// Start socket server for CLI connections
|
|
260
|
+
await startSocketServer(socketPath, handler);
|
|
261
|
+
// Read messages from extension via stdin
|
|
262
|
+
while (true) {
|
|
263
|
+
const message = await readMessage();
|
|
264
|
+
if (!message)
|
|
265
|
+
break;
|
|
266
|
+
// Check if this is a response to a relayed request
|
|
267
|
+
const relayId = message._relayId;
|
|
268
|
+
if (relayId && pendingRequests.has(relayId)) {
|
|
269
|
+
const pending = pendingRequests.get(relayId);
|
|
270
|
+
clearTimeout(pending.timer);
|
|
271
|
+
pendingRequests.delete(relayId);
|
|
272
|
+
const { _relayId, ...response } = message;
|
|
273
|
+
pending.resolve(response);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// Otherwise, handle as a direct extension message (save_skill, etc.)
|
|
277
|
+
const response = await handleNativeMessage(message);
|
|
278
|
+
sendMessage(response);
|
|
279
|
+
}
|
|
280
|
+
// Extension disconnected — clean up
|
|
281
|
+
await stopSocketServer();
|
|
282
|
+
try {
|
|
283
|
+
await fs.unlink(socketPath);
|
|
284
|
+
}
|
|
285
|
+
catch { /* already gone */ }
|
|
286
|
+
})();
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=native-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-host.js","sourceRoot":"","sources":["../src/native-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,10 @@ export interface BrowseOptions {
|
|
|
8
8
|
maxBytes?: number;
|
|
9
9
|
/** @internal Skip SSRF check — for testing only */
|
|
10
10
|
_skipSsrfCheck?: boolean;
|
|
11
|
+
/** @internal Override bridge socket path — for testing only */
|
|
12
|
+
_bridgeSocketPath?: string;
|
|
13
|
+
/** @internal Override bridge timeout — for testing only */
|
|
14
|
+
_bridgeTimeout?: number;
|
|
11
15
|
}
|
|
12
16
|
export interface BrowseSuccess {
|
|
13
17
|
success: true;
|
|
@@ -16,7 +20,7 @@ export interface BrowseSuccess {
|
|
|
16
20
|
domain: string;
|
|
17
21
|
endpointId: string;
|
|
18
22
|
tier: string;
|
|
19
|
-
skillSource: 'disk' | 'discovered' | 'captured';
|
|
23
|
+
skillSource: 'disk' | 'discovered' | 'captured' | 'bridge';
|
|
20
24
|
capturedAt: string;
|
|
21
25
|
task?: string;
|
|
22
26
|
truncated?: boolean;
|
|
@@ -1,6 +1,99 @@
|
|
|
1
1
|
import { readSkillFile } from '../skill/store.js';
|
|
2
2
|
import { replayEndpoint } from '../replay/engine.js';
|
|
3
3
|
import { read } from '../read/index.js';
|
|
4
|
+
import { bridgeAvailable, requestBridgeCapture, DEFAULT_SOCKET } from '../bridge/client.js';
|
|
5
|
+
/**
|
|
6
|
+
* Try escalating to the Chrome extension bridge for authenticated capture.
|
|
7
|
+
* Returns a BrowseResult if the bridge handled it, or null to fall through.
|
|
8
|
+
*/
|
|
9
|
+
async function tryBridgeCapture(domain, fullUrl, options) {
|
|
10
|
+
const socketPath = options._bridgeSocketPath ?? DEFAULT_SOCKET;
|
|
11
|
+
if (!await bridgeAvailable(socketPath))
|
|
12
|
+
return null;
|
|
13
|
+
const result = await requestBridgeCapture(domain, socketPath, { timeout: options._bridgeTimeout });
|
|
14
|
+
if (result.success && result.skillFiles && result.skillFiles.length > 0) {
|
|
15
|
+
const skillFiles = result.skillFiles;
|
|
16
|
+
// Save each skill file to disk
|
|
17
|
+
try {
|
|
18
|
+
const { writeSkillFile: writeSF } = await import('../skill/store.js');
|
|
19
|
+
for (const skill of skillFiles) {
|
|
20
|
+
await writeSF(skill, options.skillsDir);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Saving failed — still have the data in memory
|
|
25
|
+
}
|
|
26
|
+
// Find the skill file matching the requested domain
|
|
27
|
+
const primarySkill = skillFiles.find((s) => s.domain === domain)
|
|
28
|
+
?? skillFiles[0];
|
|
29
|
+
if (primarySkill?.endpoints?.length > 0) {
|
|
30
|
+
// Pick the best endpoint and replay it
|
|
31
|
+
let urlPath = '/';
|
|
32
|
+
try {
|
|
33
|
+
urlPath = new URL(fullUrl).pathname;
|
|
34
|
+
}
|
|
35
|
+
catch { /* use default */ }
|
|
36
|
+
const endpoint = pickEndpoint(primarySkill, urlPath);
|
|
37
|
+
if (endpoint) {
|
|
38
|
+
try {
|
|
39
|
+
const replayResult = await replayEndpoint(primarySkill, endpoint.id, {
|
|
40
|
+
maxBytes: options.maxBytes,
|
|
41
|
+
_skipSsrfCheck: options._skipSsrfCheck,
|
|
42
|
+
});
|
|
43
|
+
if (replayResult.status >= 200 && replayResult.status < 300) {
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
data: replayResult.data,
|
|
47
|
+
status: replayResult.status,
|
|
48
|
+
domain,
|
|
49
|
+
endpointId: endpoint.id,
|
|
50
|
+
tier: endpoint.replayability?.tier ?? 'unknown',
|
|
51
|
+
skillSource: 'bridge',
|
|
52
|
+
capturedAt: primarySkill.capturedAt ?? new Date().toISOString(),
|
|
53
|
+
task: options.task,
|
|
54
|
+
...(replayResult.truncated ? { truncated: true } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Replay failed — but skill file is saved for next time
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Skill file saved but replay didn't work
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
reason: 'bridge_capture_saved',
|
|
67
|
+
suggestion: `Captured ${skillFiles.length} skill file(s) from browser. Replay failed — try 'apitap replay ${domain}'.`,
|
|
68
|
+
domain,
|
|
69
|
+
url: fullUrl,
|
|
70
|
+
task: options.task,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Bridge returned an error
|
|
74
|
+
if (result.error === 'user_denied') {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
reason: 'user_denied',
|
|
78
|
+
suggestion: `User denied browser access to ${domain}. Use 'apitap auth request ${domain}' for manual login instead.`,
|
|
79
|
+
domain,
|
|
80
|
+
url: fullUrl,
|
|
81
|
+
task: options.task,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (result.error === 'approval_timeout') {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
reason: 'approval_timeout',
|
|
88
|
+
suggestion: `User approval pending for ${domain}. Click Allow in the ApiTap extension and try again.`,
|
|
89
|
+
domain,
|
|
90
|
+
url: fullUrl,
|
|
91
|
+
task: options.task,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Other bridge errors — fall through to existing fallback
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
4
97
|
/**
|
|
5
98
|
* High-level browse: check cache → disk → discover → replay.
|
|
6
99
|
* Auto-escalates cheap steps. Returns guidance for expensive ones.
|
|
@@ -75,6 +168,10 @@ export async function browse(url, options = {}) {
|
|
|
75
168
|
catch {
|
|
76
169
|
// Read failed — fall through to capture_needed
|
|
77
170
|
}
|
|
171
|
+
// Try extension bridge before giving up
|
|
172
|
+
const bridgeResult1 = await tryBridgeCapture(domain, fullUrl, options);
|
|
173
|
+
if (bridgeResult1)
|
|
174
|
+
return bridgeResult1;
|
|
78
175
|
return {
|
|
79
176
|
success: false,
|
|
80
177
|
reason: 'no_replayable_endpoints',
|
|
@@ -113,6 +210,10 @@ export async function browse(url, options = {}) {
|
|
|
113
210
|
// Read failed — fall through to capture_needed
|
|
114
211
|
}
|
|
115
212
|
}
|
|
213
|
+
// Try extension bridge before giving up
|
|
214
|
+
const bridgeResult2 = await tryBridgeCapture(domain, fullUrl, options);
|
|
215
|
+
if (bridgeResult2)
|
|
216
|
+
return bridgeResult2;
|
|
116
217
|
return {
|
|
117
218
|
success: false,
|
|
118
219
|
reason: 'no_skill_file',
|
|
@@ -125,6 +226,10 @@ export async function browse(url, options = {}) {
|
|
|
125
226
|
// Step 4: Pick best endpoint
|
|
126
227
|
const endpoint = pickEndpoint(skill, urlPath);
|
|
127
228
|
if (!endpoint) {
|
|
229
|
+
// Try extension bridge before giving up
|
|
230
|
+
const bridgeResult3 = await tryBridgeCapture(domain, fullUrl, options);
|
|
231
|
+
if (bridgeResult3)
|
|
232
|
+
return bridgeResult3;
|
|
128
233
|
return {
|
|
129
234
|
success: false,
|
|
130
235
|
reason: 'no_replayable_endpoints',
|
|
@@ -165,6 +270,10 @@ export async function browse(url, options = {}) {
|
|
|
165
270
|
};
|
|
166
271
|
}
|
|
167
272
|
catch {
|
|
273
|
+
// Try extension bridge before giving up
|
|
274
|
+
const bridgeResult4 = await tryBridgeCapture(domain, fullUrl, options);
|
|
275
|
+
if (bridgeResult4)
|
|
276
|
+
return bridgeResult4;
|
|
168
277
|
return {
|
|
169
278
|
success: false,
|
|
170
279
|
reason: 'replay_failed',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browse.js","sourceRoot":"","sources":["../../src/orchestration/browse.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"browse.js","sourceRoot":"","sources":["../../src/orchestration/browse.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apitap/core",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Intercept web API traffic during browsing. Generate portable skill files so AI agents can call APIs directly instead of scraping.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
|
+
"postbuild": "chmod +x dist/native-host.js 2>/dev/null || true",
|
|
13
14
|
"dev": "tsx src/cli.ts",
|
|
14
15
|
"test": "node --import tsx --test 'test/**/*.test.ts'",
|
|
15
16
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_SOCKET = path.join(os.homedir(), '.apitap', 'bridge.sock');
|
|
7
|
+
|
|
8
|
+
export interface BridgeCaptureResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
skillFiles?: any[];
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fast check: does the bridge socket exist?
|
|
16
|
+
* Returns false if the file doesn't exist (costs ~0.1ms).
|
|
17
|
+
* Returns true if the file exists (doesn't verify it's connectable).
|
|
18
|
+
*/
|
|
19
|
+
export async function bridgeAvailable(socketPath: string = DEFAULT_SOCKET): Promise<boolean> {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(socketPath);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Send a capture_request to the extension via the native host bridge.
|
|
30
|
+
* Returns skill files on success, or a structured error.
|
|
31
|
+
*/
|
|
32
|
+
export async function requestBridgeCapture(
|
|
33
|
+
domain: string,
|
|
34
|
+
socketPath: string = DEFAULT_SOCKET,
|
|
35
|
+
options: { timeout?: number } = {},
|
|
36
|
+
): Promise<BridgeCaptureResult> {
|
|
37
|
+
const timeout = options.timeout ?? 120_000; // 2 minutes (capture can take time)
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
client.destroy();
|
|
42
|
+
resolve({ success: false, error: 'timeout' });
|
|
43
|
+
}, timeout);
|
|
44
|
+
|
|
45
|
+
const client = net.createConnection(socketPath, () => {
|
|
46
|
+
const message = JSON.stringify({ action: 'capture_request', domain }) + '\n';
|
|
47
|
+
client.write(message);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let data = '';
|
|
51
|
+
client.on('data', (chunk) => { data += chunk.toString(); });
|
|
52
|
+
|
|
53
|
+
client.on('end', () => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
try {
|
|
56
|
+
resolve(JSON.parse(data));
|
|
57
|
+
} catch {
|
|
58
|
+
resolve({ success: false, error: 'invalid response from bridge' });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
client.on('error', (err) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve({ success: false, error: `bridge connection failed: ${err.message}` });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { discover } from './discovery/index.js';
|
|
|
20
20
|
import { peek } from './read/peek.js';
|
|
21
21
|
import { read } from './read/index.js';
|
|
22
22
|
import { homedir } from 'node:os';
|
|
23
|
-
import { join } from 'node:path';
|
|
23
|
+
import { join, resolve, dirname } from 'node:path';
|
|
24
24
|
import { readFileSync } from 'node:fs';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import { createMcpServer } from './mcp.js';
|
|
@@ -80,6 +80,7 @@ function printUsage(): void {
|
|
|
80
80
|
apitap peek <url> Zero-cost triage (HEAD only)
|
|
81
81
|
apitap read <url> Extract content without a browser
|
|
82
82
|
apitap stats Show token savings report
|
|
83
|
+
apitap extension install Register native messaging host for Chrome
|
|
83
84
|
|
|
84
85
|
Discover options:
|
|
85
86
|
--json Output machine-readable JSON
|
|
@@ -983,6 +984,48 @@ async function handleRead(positional: string[], flags: Record<string, string | b
|
|
|
983
984
|
console.log();
|
|
984
985
|
}
|
|
985
986
|
|
|
987
|
+
async function handleExtension(positional: string[], flags: Record<string, string | boolean>): Promise<void> {
|
|
988
|
+
const subcommand = positional[0];
|
|
989
|
+
|
|
990
|
+
if (subcommand === 'install') {
|
|
991
|
+
const extensionId = flags['extension-id'];
|
|
992
|
+
if (!extensionId || typeof extensionId !== 'string') {
|
|
993
|
+
console.error('Usage: apitap extension install --extension-id <id>');
|
|
994
|
+
console.error('');
|
|
995
|
+
console.error('Find your extension ID at chrome://extensions (enable Developer mode)');
|
|
996
|
+
process.exit(1);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Resolve the native host script path (always use dist/ — compiled output)
|
|
1000
|
+
const hostPath = resolve(dirname(fileURLToPath(import.meta.url)), '../dist/native-host.js').replace(/\/src\/\.\.\/dist\//, '/dist/');
|
|
1001
|
+
|
|
1002
|
+
const { installNativeHost } = await import('./extension/install.js');
|
|
1003
|
+
const { installed, errors } = await installNativeHost(hostPath, extensionId);
|
|
1004
|
+
|
|
1005
|
+
if (installed.length > 0) {
|
|
1006
|
+
console.log('Native messaging host installed:');
|
|
1007
|
+
for (const p of installed) {
|
|
1008
|
+
console.log(` ✓ ${p}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (errors.length > 0) {
|
|
1012
|
+
console.error('Errors:');
|
|
1013
|
+
for (const e of errors) {
|
|
1014
|
+
console.error(` ✗ ${e}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (installed.length === 0) {
|
|
1018
|
+
console.error('No browsers found. Are you on Linux or macOS?');
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
console.error(`Unknown extension subcommand: ${subcommand}`);
|
|
1025
|
+
console.error('Usage: apitap extension install --extension-id <id>');
|
|
1026
|
+
process.exit(1);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
986
1029
|
async function main(): Promise<void> {
|
|
987
1030
|
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
988
1031
|
|
|
@@ -1041,6 +1084,9 @@ async function main(): Promise<void> {
|
|
|
1041
1084
|
case 'read':
|
|
1042
1085
|
await handleRead(positional, flags);
|
|
1043
1086
|
break;
|
|
1087
|
+
case 'extension':
|
|
1088
|
+
await handleExtension(positional, flags);
|
|
1089
|
+
break;
|
|
1044
1090
|
default:
|
|
1045
1091
|
printUsage();
|
|
1046
1092
|
}
|