unbrowse 8.0.0 → 8.1.1

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.
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Thin wrapper — execs the compiled binary if available,
5
- * falls back to the package-managed Node launcher if not.
4
+ * Thin wrapper — runs an explicitly-provided compiled binary if present (the
5
+ * UNBROWSE_INSTALL_BINARY_PATH opt-in, used by CI smoke tests), else the package's
6
+ * readable, unsigned runtime via the launcher. There is NO auto-download fallback —
7
+ * the readable runtime is the default; the runtime IS the client.
6
8
  */
7
9
 
8
10
  import { existsSync, readFileSync } from "node:fs";
9
- import { createRequire } from "node:module";
10
11
  import { join, dirname } from "node:path";
11
12
  import { fileURLToPath } from "node:url";
12
13
  import { spawn } from "node:child_process";
@@ -14,21 +15,8 @@ import { unbrowseBinaryName } from "../scripts/release-assets.mjs";
14
15
 
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
17
  const packageRoot = join(__dirname, "..");
17
- // Windows ships `unbrowse.exe`; postinstall installs it under that name, so the
18
- // wrapper must look for the same name (a plain `unbrowse` never exists on win).
19
18
  const binaryPath = join(__dirname, unbrowseBinaryName(process.platform));
20
19
  const launcherPath = join(__dirname, "unbrowse.js");
21
- const require = createRequire(import.meta.url);
22
-
23
- const REQUIRED_FALLBACK_PACKAGES = [
24
- "tsx",
25
- "bs58",
26
- "@solana/kit",
27
- ];
28
-
29
- const KNOWN_BAD_FALLBACK_VERSIONS = new Set([
30
- "2.10.2",
31
- ]);
32
20
 
33
21
  function readInstalledVersion() {
34
22
  try {
@@ -54,18 +42,6 @@ function failInstall(reason, exitCode = 1) {
54
42
  process.exit(exitCode);
55
43
  }
56
44
 
57
- function missingFallbackPackages() {
58
- const missing = [];
59
- for (const specifier of REQUIRED_FALLBACK_PACKAGES) {
60
- try {
61
- require.resolve(specifier);
62
- } catch {
63
- missing.push(specifier);
64
- }
65
- }
66
- return missing;
67
- }
68
-
69
45
  function spawnEntrypoint(command, args) {
70
46
  const child = spawn(command, args, {
71
47
  stdio: "inherit",
@@ -91,19 +67,11 @@ if (process.argv.includes("--version") || process.argv.includes("-v")) {
91
67
  }
92
68
 
93
69
  if (existsSync(binaryPath)) {
70
+ // an explicitly-injected binary (UNBROWSE_INSTALL_BINARY_PATH) — not a fallback
94
71
  spawnEntrypoint(binaryPath, process.argv.slice(2));
95
72
  } else {
96
- const installedVersion = readInstalledVersion();
97
- if (KNOWN_BAD_FALLBACK_VERSIONS.has(installedVersion)) {
98
- failInstall(
99
- `Installed package version ${installedVersion} is known-bad in source fallback mode and shipped an incomplete runtime dependency set.`,
100
- );
101
- }
102
-
103
- const missing = missingFallbackPackages();
104
- if (missing.length > 0) {
105
- failInstall(`Fallback runtime is missing required packages: ${missing.join(", ")}.`);
106
- }
107
-
73
+ // the default: the readable, unsigned runtime via the launcher. The source IS the
74
+ // runtime, so the client is auditable on disk — security lives in the wallet-sealed
75
+ // crypto, not in hiding the client. The hole fills any internet gap.
108
76
  spawnEntrypoint(process.execPath, [launcherPath, ...process.argv.slice(2)]);
109
77
  }
package/bin/unbrowse.js CHANGED
@@ -1,50 +1,63 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Fallback stub. The real runtime is the native binary at bin/unbrowse,
4
- // downloaded by scripts/postinstall.mjs from the GitHub release. This file
5
- // only runs if the wrapper could not find that binary, which means either:
6
- // 1. The postinstall download was skipped (CI / UNBROWSE_SKIP_BINARY_DOWNLOAD).
7
- // 2. The download failed (network issue, GH release unavailable).
8
- // 3. The platform is not in SUPPORTED_TARGETS (rare).
9
- //
10
- // In all three cases, the npm tarball intentionally does NOT ship the JS
11
- // runtime, so there is nothing to fall back to. Print a clear repair message
12
- // and exit; users should re-run install or build from source.
13
-
14
- import { readFileSync } from "node:fs";
3
+ /**
4
+ * Auditable runtime launcher.
5
+ *
6
+ * The package ships a READABLE, UNSIGNED runtime at `runtime/cli.js` (an
7
+ * unminified Bun bundle of the source) and runs it here. The source IS the
8
+ * runtime, so the client including the zk/crypto verification path — is fully
9
+ * auditable on disk. There is nothing to hide: every secret is wallet-sealed
10
+ * (AES-GCM under a key only the holder derives), so reverse-engineering the
11
+ * client yields nothing. Security lives in the wallet, not in obfuscation.
12
+ *
13
+ * The runtime requires Bun. If a compiled binary was downloaded post-install,
14
+ * the wrapper runs that instead and never reaches this launcher.
15
+ */
16
+
17
+ import { existsSync } from "node:fs";
15
18
  import { dirname, join } from "node:path";
16
19
  import { fileURLToPath } from "node:url";
20
+ import { spawnSync } from "node:child_process";
21
+
22
+ const here = dirname(fileURLToPath(import.meta.url));
23
+ const runtime = join(here, "..", "runtime", "cli.js");
17
24
 
18
- const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
25
+ function fail(msg, code = 1) {
26
+ process.stderr.write(`[unbrowse] ${msg}\n`);
27
+ process.exit(code);
28
+ }
19
29
 
20
- function readVersion() {
21
- try {
22
- const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
23
- return typeof pkg.version === "string" ? pkg.version : "unknown";
24
- } catch {
25
- return "unknown";
30
+ function findBun() {
31
+ const candidates = [
32
+ process.env.UNBROWSE_BUN_BIN,
33
+ "bun",
34
+ join(process.env.HOME || "", ".bun", "bin", "bun"),
35
+ ].filter(Boolean);
36
+ for (const candidate of candidates) {
37
+ try {
38
+ const probe = spawnSync(candidate, ["--version"], { stdio: "ignore" });
39
+ if (probe.status === 0) return candidate;
40
+ } catch {
41
+ /* try the next candidate */
42
+ }
26
43
  }
44
+ return null;
45
+ }
46
+
47
+ if (!existsSync(runtime)) {
48
+ fail("readable runtime missing (runtime/cli.js). Reinstall: npm install -g unbrowse@latest");
49
+ }
50
+
51
+ const bun = findBun();
52
+ if (!bun) {
53
+ fail("this build runs on Bun. Install it from https://bun.sh, then re-run (or set UNBROWSE_BUN_BIN).");
27
54
  }
28
55
 
29
- const version = readVersion();
30
- const target = `${process.platform}-${process.arch}`;
31
-
32
- process.stderr.write(
33
- [
34
- "[unbrowse] native binary not installed",
35
- `[unbrowse] package version: ${version}`,
36
- `[unbrowse] platform: ${target}`,
37
- "",
38
- "[unbrowse] The npm package ships only the native binary downloaded on",
39
- "[unbrowse] postinstall. Possible causes:",
40
- "[unbrowse] - postinstall was skipped (CI / UNBROWSE_SKIP_BINARY_DOWNLOAD)",
41
- "[unbrowse] - download failed (check stderr from `npm install`)",
42
- "[unbrowse] - platform is not in the prebuilt set",
43
- "",
44
- "[unbrowse] Repair:",
45
- "[unbrowse] node packages/skill/scripts/postinstall.mjs # retry download",
46
- "[unbrowse] npm uninstall -g unbrowse && npm install -g unbrowse@latest",
47
- "[unbrowse] build from source: https://github.com/unbrowse-ai/unbrowse",
48
- ].join("\n") + "\n",
49
- );
50
- process.exit(1);
56
+ const child = spawnSync(bun, [runtime, ...process.argv.slice(2)], {
57
+ stdio: "inherit",
58
+ cwd: process.cwd(),
59
+ env: process.env,
60
+ });
61
+ if (child.error) fail(`failed to launch runtime: ${child.error.message}`);
62
+ if (child.signal) process.kill(process.pid, child.signal);
63
+ process.exit(child.status ?? 1);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * src/sdk/adapters/browser-use.ts — drop-in shape for the `browser-use` Agent.
3
+ *
4
+ * Same construction (`new Agent({ task, llm })`) and `.run()` — but the task is filled
5
+ * through the wallet-sealed unbrowse hole (resolve → execute → capture) instead of a
6
+ * driven headful browser. `llm` is accepted for signature compatibility; the hole
7
+ * decides when a real browser is actually needed (browser-open is the fallback, not the
8
+ * substrate).
9
+ */
10
+ import { type HoleItem, type HoleOptions } from "../hole.js";
11
+ export interface AgentOptions extends HoleOptions {
12
+ task: string;
13
+ llm?: unknown;
14
+ maxSteps?: number;
15
+ }
16
+ export interface AgentResult {
17
+ task: string;
18
+ done: boolean;
19
+ result: string;
20
+ items: HoleItem[];
21
+ }
22
+ export declare class Agent {
23
+ private readonly hole;
24
+ private readonly task;
25
+ constructor(options: AgentOptions);
26
+ run(): Promise<AgentResult>;
27
+ }
28
+ export default Agent;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * src/sdk/adapters/browser-use.ts — drop-in shape for the `browser-use` Agent.
3
+ *
4
+ * Same construction (`new Agent({ task, llm })`) and `.run()` — but the task is filled
5
+ * through the wallet-sealed unbrowse hole (resolve → execute → capture) instead of a
6
+ * driven headful browser. `llm` is accepted for signature compatibility; the hole
7
+ * decides when a real browser is actually needed (browser-open is the fallback, not the
8
+ * substrate).
9
+ */
10
+ import { createHole } from "../hole.js";
11
+ export class Agent {
12
+ hole;
13
+ task;
14
+ constructor(options) {
15
+ this.task = options.task;
16
+ this.hole = createHole(options);
17
+ }
18
+ async run() {
19
+ const r = await this.hole.fill({ intent: this.task });
20
+ return {
21
+ task: this.task,
22
+ done: r.ok,
23
+ result: r.answer ?? r.items[0]?.text ?? "",
24
+ items: r.items,
25
+ };
26
+ }
27
+ }
28
+ export default Agent;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * src/sdk/adapters/exa.ts — drop-in replacement for the `exa-js` client.
3
+ *
4
+ * Same construction (`new Exa(apiKey)`) and the same method shapes (`search`,
5
+ * `searchAndContents`, `getContents`, `answer`) returning exa-shaped results — but every
6
+ * call routes through the wallet-sealed unbrowse hole instead of Exa's API. Swap the
7
+ * import, keep your code. Inject a `transport`/`wallet` (HoleOptions) for tests or to
8
+ * bind the hole to a wallet.
9
+ */
10
+ import { type HoleOptions } from "../hole.js";
11
+ export interface ExaSearchOptions {
12
+ numResults?: number;
13
+ type?: string;
14
+ includeDomains?: string[];
15
+ startPublishedDate?: string;
16
+ contents?: {
17
+ text?: boolean;
18
+ highlights?: boolean;
19
+ summary?: boolean;
20
+ };
21
+ [key: string]: unknown;
22
+ }
23
+ export interface ExaResult {
24
+ title: string | null;
25
+ url: string;
26
+ publishedDate?: string;
27
+ score?: number;
28
+ text?: string;
29
+ highlights?: string[];
30
+ summary?: string;
31
+ }
32
+ export interface ExaSearchResponse {
33
+ results: ExaResult[];
34
+ autopromptString?: string;
35
+ }
36
+ export declare class Exa {
37
+ private readonly hole;
38
+ constructor(_apiKey?: string, opts?: HoleOptions);
39
+ search(query: string, options?: ExaSearchOptions): Promise<ExaSearchResponse>;
40
+ searchAndContents(query: string, options?: ExaSearchOptions): Promise<ExaSearchResponse>;
41
+ getContents(urls: string[], _options?: {
42
+ text?: boolean;
43
+ highlights?: boolean;
44
+ }): Promise<ExaSearchResponse>;
45
+ answer(question: string): Promise<{
46
+ answer: string;
47
+ }>;
48
+ }
49
+ export default Exa;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * src/sdk/adapters/exa.ts — drop-in replacement for the `exa-js` client.
3
+ *
4
+ * Same construction (`new Exa(apiKey)`) and the same method shapes (`search`,
5
+ * `searchAndContents`, `getContents`, `answer`) returning exa-shaped results — but every
6
+ * call routes through the wallet-sealed unbrowse hole instead of Exa's API. Swap the
7
+ * import, keep your code. Inject a `transport`/`wallet` (HoleOptions) for tests or to
8
+ * bind the hole to a wallet.
9
+ */
10
+ import { createHole } from "../hole.js";
11
+ function toExa(it) {
12
+ return {
13
+ title: it.title ?? null,
14
+ url: it.url ?? "",
15
+ publishedDate: it.publishedDate,
16
+ score: it.score,
17
+ text: typeof it.text === "string" ? it.text : undefined,
18
+ highlights: Array.isArray(it.highlights) ? it.highlights : undefined,
19
+ summary: typeof it.summary === "string" ? it.summary : undefined,
20
+ };
21
+ }
22
+ export class Exa {
23
+ hole;
24
+ constructor(_apiKey, opts = {}) {
25
+ this.hole = createHole(opts);
26
+ }
27
+ async search(query, options = {}) {
28
+ const r = await this.hole.fill({ intent: query, params: options });
29
+ const n = options.numResults ?? r.items.length;
30
+ return { results: r.items.slice(0, n).map(toExa) };
31
+ }
32
+ async searchAndContents(query, options = {}) {
33
+ return this.search(query, { ...options, contents: { text: true, ...(options.contents ?? {}) } });
34
+ }
35
+ async getContents(urls, _options = {}) {
36
+ const results = [];
37
+ for (const url of urls) {
38
+ const r = await this.hole.fill({ intent: `contents of ${url}`, url });
39
+ const first = r.items[0];
40
+ if (first)
41
+ results.push(toExa({ ...first, url }));
42
+ }
43
+ return { results };
44
+ }
45
+ async answer(question) {
46
+ const r = await this.hole.fill({ intent: question });
47
+ return { answer: r.answer ?? r.items[0]?.text ?? "" };
48
+ }
49
+ }
50
+ export default Exa;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * src/sdk/adapters/index.ts — drop-in client adapters over the unbrowse hole.
3
+ *
4
+ * Each adapter mirrors a popular client's construction + method shapes, so swapping the
5
+ * import is the only change needed:
6
+ *
7
+ * import Exa from "@unbrowse/sdk/adapters/exa"; // was: import Exa from "exa-js"
8
+ * import { tavily } from "@unbrowse/sdk/adapters/tavily"; // was: from "@tavily/core"
9
+ * import { Agent } from "@unbrowse/sdk/adapters/browser-use";
10
+ *
11
+ * All of them wrap the same wallet-sealed streaming hole (`../hole.ts`).
12
+ */
13
+ export { Exa, type ExaResult, type ExaSearchResponse, type ExaSearchOptions } from "./exa.js";
14
+ export { tavily, type TavilyClient, type TavilyResult, type TavilySearchResponse } from "./tavily.js";
15
+ export { Agent, type AgentOptions, type AgentResult } from "./browser-use.js";
16
+ export { createHole, Hole, type HoleRequest, type HoleResult, type HoleItem, type HoleOptions, type HoleTransport, type WalletSeal, } from "../hole.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * src/sdk/adapters/index.ts — drop-in client adapters over the unbrowse hole.
3
+ *
4
+ * Each adapter mirrors a popular client's construction + method shapes, so swapping the
5
+ * import is the only change needed:
6
+ *
7
+ * import Exa from "@unbrowse/sdk/adapters/exa"; // was: import Exa from "exa-js"
8
+ * import { tavily } from "@unbrowse/sdk/adapters/tavily"; // was: from "@tavily/core"
9
+ * import { Agent } from "@unbrowse/sdk/adapters/browser-use";
10
+ *
11
+ * All of them wrap the same wallet-sealed streaming hole (`../hole.ts`).
12
+ */
13
+ export { Exa } from "./exa.js";
14
+ export { tavily } from "./tavily.js";
15
+ export { Agent } from "./browser-use.js";
16
+ export { createHole, Hole, } from "../hole.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * src/sdk/adapters/tavily.ts — drop-in replacement for the `@tavily/core` client.
3
+ *
4
+ * Same construction (`tavily({ apiKey })`) and the same methods (`search`, `extract`)
5
+ * returning tavily-shaped responses — routed through the wallet-sealed unbrowse hole.
6
+ */
7
+ import { type HoleOptions } from "../hole.js";
8
+ export interface TavilyResult {
9
+ title: string;
10
+ url: string;
11
+ content: string;
12
+ score: number;
13
+ rawContent?: string;
14
+ }
15
+ export interface TavilySearchResponse {
16
+ query: string;
17
+ answer?: string;
18
+ results: TavilyResult[];
19
+ responseTime?: number;
20
+ }
21
+ export interface TavilyExtractResponse {
22
+ results: Array<{
23
+ url: string;
24
+ rawContent: string;
25
+ }>;
26
+ failedResults: string[];
27
+ }
28
+ export interface TavilyOptions extends HoleOptions {
29
+ apiKey?: string;
30
+ }
31
+ export interface TavilyClient {
32
+ search(query: string, options?: Record<string, unknown>): Promise<TavilySearchResponse>;
33
+ extract(urls: string[]): Promise<TavilyExtractResponse>;
34
+ }
35
+ export declare function tavily(options?: TavilyOptions): TavilyClient;
36
+ export default tavily;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * src/sdk/adapters/tavily.ts — drop-in replacement for the `@tavily/core` client.
3
+ *
4
+ * Same construction (`tavily({ apiKey })`) and the same methods (`search`, `extract`)
5
+ * returning tavily-shaped responses — routed through the wallet-sealed unbrowse hole.
6
+ */
7
+ import { createHole } from "../hole.js";
8
+ function toTavily(it) {
9
+ return {
10
+ title: it.title ?? "",
11
+ url: it.url ?? "",
12
+ content: typeof it.text === "string" ? it.text : "",
13
+ score: typeof it.score === "number" ? it.score : 0,
14
+ rawContent: typeof it.rawContent === "string" ? it.rawContent : undefined,
15
+ };
16
+ }
17
+ export function tavily(options = {}) {
18
+ const hole = createHole(options);
19
+ return {
20
+ async search(query, opts = {}) {
21
+ const r = await hole.fill({ intent: query, params: opts });
22
+ return { query, answer: r.answer, results: r.items.map(toTavily) };
23
+ },
24
+ async extract(urls) {
25
+ const results = [];
26
+ const failedResults = [];
27
+ for (const url of urls) {
28
+ const r = await hole.fill({ intent: `extract ${url}`, url });
29
+ const text = r.items[0]?.text;
30
+ if (typeof text === "string" && text)
31
+ results.push({ url, rawContent: text });
32
+ else
33
+ failedResults.push(url);
34
+ }
35
+ return { results, failedResults };
36
+ },
37
+ };
38
+ }
39
+ export default tavily;
@@ -0,0 +1,119 @@
1
+ import type { UnbrowseClientOptions } from "./types.js";
2
+ /** What the caller hands the hole: an intent, optionally scoped to a URL. */
3
+ export interface HoleRequest {
4
+ intent: string;
5
+ url?: string;
6
+ params?: Record<string, unknown>;
7
+ /** Perform an ACTION (execute / fill / submit) rather than just read/search. */
8
+ act?: boolean;
9
+ /** Override the hole's auto-index default for this one request. */
10
+ autoIndex?: boolean;
11
+ }
12
+ /** A reusable route the hole learned while filling a gap (what gets indexed). */
13
+ export interface HoleSkill {
14
+ domain?: string;
15
+ name?: string;
16
+ description?: string;
17
+ }
18
+ /** Payload handed to the index hook when a captured route is auto-indexed. */
19
+ export interface IndexInfo {
20
+ intent: string;
21
+ skill: HoleSkill;
22
+ items: HoleItem[];
23
+ }
24
+ /** One normalized result item — the lingua franca every adapter reshapes from. */
25
+ export interface HoleItem {
26
+ title?: string;
27
+ url?: string;
28
+ text?: string;
29
+ score?: number;
30
+ publishedDate?: string;
31
+ [key: string]: unknown;
32
+ }
33
+ /** A wallet attestation attached when the hole is sealed (the zk'd hole). */
34
+ export interface HoleSeal {
35
+ walletPubkey: string;
36
+ signature: string;
37
+ }
38
+ export interface HoleResult {
39
+ ok: boolean;
40
+ intent: string;
41
+ items: HoleItem[];
42
+ answer?: string;
43
+ source?: string;
44
+ raw?: unknown;
45
+ seal?: HoleSeal;
46
+ /** True when filling required a fresh capture (a new route was learned). */
47
+ captured?: boolean;
48
+ /** The learned route, when captured — what the auto-index hook receives. */
49
+ skill?: HoleSkill;
50
+ /** True when this fill auto-indexed the captured route. */
51
+ indexed?: boolean;
52
+ }
53
+ /** The pluggable backend: turn a request into normalized items. */
54
+ export type HoleTransport = (req: HoleRequest) => Promise<Omit<HoleResult, "seal">>;
55
+ /** Minimal wallet signer the hole seals to (any Ed25519-style signer fits). */
56
+ export interface WalletSeal {
57
+ sign(message: Uint8Array): Promise<{
58
+ signature: Uint8Array;
59
+ walletPubkey: Uint8Array;
60
+ }>;
61
+ }
62
+ export interface HoleOptions {
63
+ transport?: HoleTransport;
64
+ wallet?: WalletSeal;
65
+ client?: UnbrowseClientOptions;
66
+ /** Auto-index captured routes after a fill (default true). */
67
+ autoIndex?: boolean;
68
+ /**
69
+ * Generate a name + description for a captured route (default true). We generate
70
+ * descriptions for the user, not the other way round — a zero-cost deterministic
71
+ * baseline always, enriched by the `generate` hook (a cheap model) when provided.
72
+ * Set false only if you truly want routes indexed with no description.
73
+ */
74
+ describe?: boolean;
75
+ /**
76
+ * Where a captured route is sent to be indexed — the discover → publish loop.
77
+ * Wire this to the CLI's `queueBackgroundIndex`; left unset, fills still work but
78
+ * nothing is indexed (result.indexed = false).
79
+ */
80
+ index?: (info: IndexInfo) => Promise<void> | void;
81
+ /**
82
+ * Client-side LLM used to "index it nicely" — name + describe a captured route.
83
+ * Keeps generation ON THE CLIENT (the agent's own model); no server round-trip.
84
+ * Given the captured route, return a short "<name> — <description>".
85
+ */
86
+ generate?: (prompt: string) => Promise<string> | string;
87
+ }
88
+ /**
89
+ * Zero-cost deterministic name + description for a captured route. The always-on baseline
90
+ * so a learned route is never left undescribed and the USER never has to write one; the
91
+ * optional `generate` hook (a cheap model) enriches this when present.
92
+ */
93
+ export declare function defaultDescribe(intent: string, skill: HoleSkill, items: HoleItem[]): {
94
+ name: string;
95
+ description: string;
96
+ };
97
+ /** Stable bytes to sign — key order fixed so client and verifier agree. */
98
+ export declare function canonicalRequest(req: HoleRequest): Uint8Array;
99
+ /** The hole. One method that matters: `fill`. Stream with `stream`. */
100
+ export declare class Hole {
101
+ private readonly transport;
102
+ private readonly wallet?;
103
+ private readonly autoIndex;
104
+ private readonly describe;
105
+ private readonly index?;
106
+ private readonly generate?;
107
+ constructor(opts?: HoleOptions);
108
+ /**
109
+ * Fill the gap — READ (search) or ACT (execute/fill/submit, when `req.act`). When the
110
+ * hole is wallet-bound the result carries a seal. When the fill required a fresh
111
+ * capture, the route is auto-indexed (named/described by the client-side `generate`
112
+ * hook, then handed to `index`) — the discover → publish loop, on the client.
113
+ */
114
+ fill(req: HoleRequest): Promise<HoleResult>;
115
+ /** Stream the filled items one at a time (the "streaming shit into it" surface). */
116
+ stream(req: HoleRequest): AsyncIterable<HoleItem>;
117
+ }
118
+ /** Construct a hole. With `wallet`, it is the zk'd, wallet-protected hole. */
119
+ export declare function createHole(opts?: HoleOptions): Hole;