unbrowse 12.1.0 → 12.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +82 -4
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -98,6 +98,43 @@ class Unbrowse {
|
|
|
98
98
|
cancel(runId) {
|
|
99
99
|
return this.req(`/runs/${runId}/cancel`, { method: "POST" });
|
|
100
100
|
}
|
|
101
|
+
async runOnClient(request, opts = {}) {
|
|
102
|
+
const send = opts.fetch ?? ((...a) => globalThis.fetch(...a));
|
|
103
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 600000);
|
|
104
|
+
let step = await this.run({ ...request, egress: "client" });
|
|
105
|
+
while (isEgressStep(step)) {
|
|
106
|
+
if (Date.now() > deadline) {
|
|
107
|
+
await this.closeEgress(step.egressId).catch(() => {
|
|
108
|
+
return;
|
|
109
|
+
});
|
|
110
|
+
throw new UnbrowseError("client-egress run did not finish in time", 408, "timeout");
|
|
111
|
+
}
|
|
112
|
+
const { egressId } = step;
|
|
113
|
+
for (const req of step.requests) {
|
|
114
|
+
let answer;
|
|
115
|
+
try {
|
|
116
|
+
if (await opts.onRequest?.(req) === false)
|
|
117
|
+
throw new Error("refused by onRequest");
|
|
118
|
+
answer = { response: await sendEgress(send, req) };
|
|
119
|
+
} catch (err) {
|
|
120
|
+
answer = { error: err instanceof Error ? err.message : String(err) };
|
|
121
|
+
}
|
|
122
|
+
step = await this.answerEgress(egressId, req.id, answer);
|
|
123
|
+
if (!isEgressStep(step))
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return step;
|
|
128
|
+
}
|
|
129
|
+
egress(egressId) {
|
|
130
|
+
return this.req(`/egress/${egressId}`);
|
|
131
|
+
}
|
|
132
|
+
answerEgress(egressId, requestId, answer) {
|
|
133
|
+
return this.post(`/egress/${egressId}`, { requestId, ...answer });
|
|
134
|
+
}
|
|
135
|
+
closeEgress(egressId) {
|
|
136
|
+
return this.req(`/egress/${egressId}`, { method: "DELETE" });
|
|
137
|
+
}
|
|
101
138
|
discover(query) {
|
|
102
139
|
return this.post("/capabilities/search", { query });
|
|
103
140
|
}
|
|
@@ -159,6 +196,32 @@ function normalizeHost(site) {
|
|
|
159
196
|
host = new URL(host).hostname;
|
|
160
197
|
return host.replace(/\/.*$/, "").replace(/^www\./, "");
|
|
161
198
|
}
|
|
199
|
+
function isEgressStep(v) {
|
|
200
|
+
return !!v && typeof v === "object" && v.status === "egress_required";
|
|
201
|
+
}
|
|
202
|
+
async function sendEgress(send, req) {
|
|
203
|
+
const body = req.body === undefined ? undefined : req.bodyEncoding === "base64" ? base64ToBytes(req.body) : req.body;
|
|
204
|
+
const res = await send(req.url, { method: req.method, headers: req.headers, redirect: req.redirect, ...body !== undefined ? { body } : {} });
|
|
205
|
+
const headers = [];
|
|
206
|
+
res.headers.forEach((value, name) => headers.push([name, value]));
|
|
207
|
+
const cookies = res.headers.getSetCookie?.();
|
|
208
|
+
const pairs = cookies?.length ? [...headers.filter(([n]) => n.toLowerCase() !== "set-cookie"), ...cookies.map((c) => ["set-cookie", c])] : headers;
|
|
209
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
210
|
+
return { status: res.status, headers: pairs, body: bytesToBase64(bytes), bodyEncoding: "base64", ...res.url ? { url: res.url } : {} };
|
|
211
|
+
}
|
|
212
|
+
function base64ToBytes(b64) {
|
|
213
|
+
const bin = atob(b64);
|
|
214
|
+
const out = new Uint8Array(bin.length);
|
|
215
|
+
for (let i = 0;i < bin.length; i++)
|
|
216
|
+
out[i] = bin.charCodeAt(i);
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
function bytesToBase64(bytes) {
|
|
220
|
+
let bin = "";
|
|
221
|
+
for (let i = 0;i < bytes.length; i += 32768)
|
|
222
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
223
|
+
return btoa(bin);
|
|
224
|
+
}
|
|
162
225
|
var DEFAULT_BASE_URL = "https://unbrowse.ai/api/v1", UnbrowseError, env = (name) => (typeof process !== "undefined" ? process.env?.[name] : undefined) || undefined;
|
|
163
226
|
var init_client = __esm(() => {
|
|
164
227
|
UnbrowseError = class UnbrowseError extends Error {
|
|
@@ -377,6 +440,16 @@ function save(stored) {
|
|
|
377
440
|
function clear() {
|
|
378
441
|
rmSync(configPath(), { force: true });
|
|
379
442
|
}
|
|
443
|
+
function v1Key(baseUrl) {
|
|
444
|
+
if (!/^https:\/\/(www\.|api\.|v3\.)?unbrowse\.ai\/?$/.test(baseUrl))
|
|
445
|
+
return;
|
|
446
|
+
try {
|
|
447
|
+
const key = JSON.parse(readFileSync(join(process.env.HOME || homedir(), ".unbrowse", "config.json"), "utf8")).api_key;
|
|
448
|
+
return typeof key === "string" && /^ubr_[0-9a-f]{48}$/.test(key) ? key : undefined;
|
|
449
|
+
} catch {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
380
453
|
async function currentToken(baseUrl, fetchImpl = fetch) {
|
|
381
454
|
if (process.env.UNBROWSE_API_KEY)
|
|
382
455
|
return process.env.UNBROWSE_API_KEY;
|
|
@@ -387,7 +460,7 @@ async function currentToken(baseUrl, fetchImpl = fetch) {
|
|
|
387
460
|
return stored.apiKey;
|
|
388
461
|
const o = stored.oauth;
|
|
389
462
|
if (!o)
|
|
390
|
-
return;
|
|
463
|
+
return v1Key(baseUrl);
|
|
391
464
|
if (o.expiresAt && o.refreshToken && Date.now() > o.expiresAt - 60000) {
|
|
392
465
|
try {
|
|
393
466
|
const fresh = await tokenRequest(baseUrl, { grant_type: "refresh_token", refresh_token: o.refreshToken, client_id: o.clientId, resource: apiResource(baseUrl) }, fetchImpl);
|
|
@@ -409,6 +482,9 @@ function describe(baseUrl) {
|
|
|
409
482
|
return `API key ${mask(stored.apiKey)} in ${configPath()}`;
|
|
410
483
|
if (stored.oauth)
|
|
411
484
|
return `OAuth sign-in in ${configPath()}`;
|
|
485
|
+
const v1 = !stored.baseUrl ? v1Key(baseUrl) : undefined;
|
|
486
|
+
if (v1)
|
|
487
|
+
return `v1 API key ${mask(v1)} from ~/.unbrowse/config.json (still works; \`unbrowse login\` replaces it)`;
|
|
412
488
|
return "not signed in";
|
|
413
489
|
}
|
|
414
490
|
function mask(key) {
|
|
@@ -493,7 +569,7 @@ ${url.toString()}`);
|
|
|
493
569
|
|
|
494
570
|
// src/cli.ts
|
|
495
571
|
init_src();
|
|
496
|
-
var VERSION = "12.1.
|
|
572
|
+
var VERSION = "12.1.2";
|
|
497
573
|
var DEFAULT_ORIGIN = "https://unbrowse.ai";
|
|
498
574
|
var HELP = `unbrowse ${VERSION} — call websites as APIs through the Unbrowse API
|
|
499
575
|
|
|
@@ -505,6 +581,7 @@ var HELP = `unbrowse ${VERSION} — call websites as APIs through the Unbrowse A
|
|
|
505
581
|
discover <query> Your capabilities first, then the public registry
|
|
506
582
|
run <task…> Run a task and wait for a verified result
|
|
507
583
|
[--capability ID] [--url URL] [--set key=value]… [--input JSON] [--unattended] [--no-wait]
|
|
584
|
+
[--from-here] send the site requests from this machine (your IP); Unbrowse decides and reads them
|
|
508
585
|
inspect <runId> A run's status, requirements and result
|
|
509
586
|
resume <runId> key=value… Answer open requirements on the same run
|
|
510
587
|
cancel <runId> Stop a run; prints the effect receipt
|
|
@@ -609,14 +686,15 @@ async function main(argv, io = stdio) {
|
|
|
609
686
|
if (!task && !capability)
|
|
610
687
|
throw new UsageError("usage: unbrowse run <task…> | --capability ID");
|
|
611
688
|
const input = { ...flag("input") ? JSON.parse(flag("input")) : {}, ...mapValues(args.sets) };
|
|
612
|
-
const
|
|
689
|
+
const request = {
|
|
613
690
|
...task ? { task } : {},
|
|
614
691
|
...capability ? { capability } : {},
|
|
615
692
|
...flag("url") ? { targetUrl: flag("url") } : {},
|
|
616
693
|
...Object.keys(input).length ? { input } : {},
|
|
617
694
|
...args.flags.unattended ? { interactionMode: "unattended" } : {},
|
|
618
695
|
idempotencyKey: flag("idempotency-key") ?? randomUUID()
|
|
619
|
-
}
|
|
696
|
+
};
|
|
697
|
+
const view = args.flags["from-here"] ? await ub.runOnClient(request, { onRequest: (r) => void io.err(`→ ${r.method} ${r.url}`) }) : await ub.run(request);
|
|
620
698
|
return settle(ub, view, args, io, print);
|
|
621
699
|
}
|
|
622
700
|
case "inspect":
|