unbrowse 2.12.2 → 2.12.7

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 (64) hide show
  1. package/README.md +8 -44
  2. package/dist/cli.js +514 -20723
  3. package/package.json +4 -10
  4. package/runtime-src/api/routes.ts +15 -801
  5. package/runtime-src/auth/index.ts +32 -142
  6. package/runtime-src/capture/index.ts +101 -436
  7. package/runtime-src/cli.ts +371 -956
  8. package/runtime-src/client/index.ts +29 -622
  9. package/runtime-src/execution/index.ts +85 -345
  10. package/runtime-src/graph/index.ts +10 -128
  11. package/runtime-src/intent-match.ts +27 -27
  12. package/runtime-src/kuri/client.ts +82 -543
  13. package/runtime-src/orchestrator/index.ts +462 -2246
  14. package/runtime-src/reverse-engineer/index.ts +22 -220
  15. package/runtime-src/runtime/local-server.ts +16 -149
  16. package/runtime-src/runtime/paths.ts +5 -9
  17. package/runtime-src/runtime/setup.ts +1 -52
  18. package/runtime-src/server.ts +11 -6
  19. package/runtime-src/transform/schema-hints.ts +358 -0
  20. package/runtime-src/types/skill.ts +2 -49
  21. package/runtime-src/verification/index.ts +0 -15
  22. package/runtime-src/version.ts +13 -13
  23. package/vendor/kuri/darwin-arm64/kuri +0 -0
  24. package/vendor/kuri/darwin-x64/kuri +0 -0
  25. package/vendor/kuri/linux-arm64/kuri +0 -0
  26. package/vendor/kuri/linux-x64/kuri +0 -0
  27. package/bin/unbrowse-wrapper.mjs +0 -39
  28. package/bin/unbrowse.js +0 -38
  29. package/runtime-src/analytics-session.ts +0 -33
  30. package/runtime-src/api/browse-index.ts +0 -254
  31. package/runtime-src/api/browse-session.ts +0 -179
  32. package/runtime-src/api/browse-submit.ts +0 -455
  33. package/runtime-src/auth/runtime.ts +0 -116
  34. package/runtime-src/browser/index.ts +0 -635
  35. package/runtime-src/browser/types.ts +0 -41
  36. package/runtime-src/capture/prefetch.ts +0 -122
  37. package/runtime-src/capture/rsc.ts +0 -45
  38. package/runtime-src/cli/shortcuts.ts +0 -273
  39. package/runtime-src/client/graph-client.ts +0 -99
  40. package/runtime-src/execution/robots.ts +0 -167
  41. package/runtime-src/execution/search-forms.ts +0 -188
  42. package/runtime-src/graph/planner.ts +0 -411
  43. package/runtime-src/graph/session.ts +0 -294
  44. package/runtime-src/graph/trace-store.ts +0 -136
  45. package/runtime-src/indexer/index.ts +0 -480
  46. package/runtime-src/orchestrator/browser-agent.ts +0 -374
  47. package/runtime-src/orchestrator/dag-advisor.ts +0 -59
  48. package/runtime-src/orchestrator/dag-feedback.ts +0 -256
  49. package/runtime-src/orchestrator/first-pass-action.ts +0 -362
  50. package/runtime-src/orchestrator/passive-publish.ts +0 -152
  51. package/runtime-src/orchestrator/timing-economics.ts +0 -80
  52. package/runtime-src/payments/cascade.ts +0 -137
  53. package/runtime-src/payments/index.ts +0 -268
  54. package/runtime-src/payments/wallet.ts +0 -33
  55. package/runtime-src/reverse-engineer/description-prompt.ts +0 -132
  56. package/runtime-src/router.ts +0 -17
  57. package/runtime-src/runtime/browser-access.ts +0 -11
  58. package/runtime-src/runtime/browser-host.ts +0 -48
  59. package/runtime-src/runtime/lifecycle.ts +0 -17
  60. package/runtime-src/runtime/supervisor.ts +0 -69
  61. package/runtime-src/single-binary.ts +0 -141
  62. package/runtime-src/telemetry.ts +0 -253
  63. package/runtime-src/verification/matrix.ts +0 -30
  64. package/scripts/postinstall.mjs +0 -81
@@ -1,39 +1,15 @@
1
1
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
2
2
  import { join } from "path";
3
3
  import { homedir, hostname } from "os";
4
- import { randomBytes, createHash } from "crypto";
4
+ import { randomBytes } from "crypto";
5
5
  import { createInterface } from "readline";
6
6
  import type { AgentSkillChunkView, EndpointStats, ExecutionTrace, OrchestrationTiming, SkillManifest, ValidationResult } from "../types/index.js";
7
- import { ensureCascadeSplitForSkill } from "../payments/cascade.js";
8
- import { attributeLifecycle } from "../runtime/lifecycle.js";
9
- import type { LifecycleEvent } from "../runtime/lifecycle.js";
10
- import { detectHostEnvironment } from "../runtime/browser-host.js";
11
7
 
12
8
  const API_URL = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
13
9
  const PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
14
10
  const recentLocalSkills = new Map<string, SkillManifest>();
15
11
  const LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
16
12
 
17
- function decodeBase64Json(value: string): unknown {
18
- try {
19
- if (typeof globalThis !== "undefined" && typeof globalThis.atob === "function") {
20
- const binary = globalThis.atob(value);
21
- const bytes = new Uint8Array(binary.length);
22
- for (let i = 0; i < binary.length; i++) {
23
- bytes[i] = binary.charCodeAt(i);
24
- }
25
- return JSON.parse(new TextDecoder("utf-8").decode(bytes));
26
- }
27
- return JSON.parse(Buffer.from(value, "base64").toString("utf8"));
28
- } catch {
29
- return undefined;
30
- }
31
- }
32
-
33
- export function isX402Error(err: unknown): err is Error & { x402: true; terms?: unknown; status?: number } {
34
- return !!err && typeof err === "object" && (err as { x402?: unknown }).x402 === true;
35
- }
36
-
37
13
  function scopedSkillKey(skillId: string, scopeId?: string): string {
38
14
  return scopeId ? `${scopeId}:${skillId}` : skillId;
39
15
  }
@@ -53,10 +29,6 @@ function getConfigPath(): string {
53
29
  return join(getConfigDir(), "config.json");
54
30
  }
55
31
 
56
- function getInstallTelemetryPath(): string {
57
- return join(getConfigDir(), "install-state.json");
58
- }
59
-
60
32
  function sanitizeProfileName(value: string): string {
61
33
  return value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
62
34
  }
@@ -76,20 +48,8 @@ interface UnbrowseConfig {
76
48
  registered_at: string;
77
49
  tos_accepted_version: string | null;
78
50
  tos_accepted_at: string | null;
79
- wallet_address?: string;
80
- wallet_provider?: string;
81
51
  }
82
52
 
83
- interface InstallTelemetryState {
84
- install_id: string;
85
- first_seen_at: string;
86
- cli_first_seen_reported_at?: string;
87
- }
88
-
89
- type TelemetryHostType = "cli" | "codex" | "openclaw" | "mcp" | "native" | "unknown";
90
- type InstallTelemetrySource = "host" | "setup" | "cli-first-seen";
91
- type FunnelTelemetrySource = "host" | "setup" | "cli-first-seen" | "cli" | "agent" | "server";
92
-
93
53
  type ApiKeySource = "env" | "config";
94
54
  type ApiKeyValidationStatus = "ok" | "missing_profile" | "invalid" | "offline";
95
55
 
@@ -115,147 +75,6 @@ function saveConfig(config: UnbrowseConfig): void {
115
75
  writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
116
76
  }
117
77
 
118
- function loadInstallTelemetryState(): InstallTelemetryState | null {
119
- try {
120
- const statePath = getInstallTelemetryPath();
121
- if (existsSync(statePath)) {
122
- return JSON.parse(readFileSync(statePath, "utf-8")) as InstallTelemetryState;
123
- }
124
- } catch {}
125
- return null;
126
- }
127
-
128
- function saveInstallTelemetryState(state: InstallTelemetryState): void {
129
- const configDir = getConfigDir();
130
- const statePath = getInstallTelemetryPath();
131
- if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
132
- writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 0o600 });
133
- }
134
-
135
- function createInstallTelemetryState(): InstallTelemetryState {
136
- return {
137
- install_id: `install_${randomBytes(8).toString("hex")}`,
138
- first_seen_at: new Date().toISOString(),
139
- };
140
- }
141
-
142
- function getOrCreateInstallTelemetryState(): InstallTelemetryState {
143
- const existing = loadInstallTelemetryState();
144
- if (existing?.install_id) return existing;
145
- const created = createInstallTelemetryState();
146
- saveInstallTelemetryState(created);
147
- return created;
148
- }
149
-
150
- export function getInstallId(): string {
151
- return getOrCreateInstallTelemetryState().install_id;
152
- }
153
-
154
- export function detectTelemetryHostType(): TelemetryHostType {
155
- switch (detectHostEnvironment()) {
156
- case "openai":
157
- return "codex";
158
- case "openclaw":
159
- return "openclaw";
160
- case "mcp":
161
- return "mcp";
162
- case "native":
163
- return "native";
164
- case "unknown":
165
- default:
166
- return "cli";
167
- }
168
- }
169
-
170
- async function postTelemetry(path: string, body: Record<string, unknown>): Promise<boolean> {
171
- if (LOCAL_ONLY) return false;
172
-
173
- try {
174
- const key = getApiKey();
175
- const res = await fetch(`${API_URL}${path}`, {
176
- method: "POST",
177
- headers: {
178
- "Content-Type": "application/json",
179
- "Accept-Encoding": "gzip, deflate",
180
- ...(key ? { Authorization: `Bearer ${key}` } : {}),
181
- },
182
- body: JSON.stringify(body),
183
- });
184
- return res.ok;
185
- } catch {
186
- return false;
187
- }
188
- }
189
-
190
- export async function ensureCliInstallTracked(hostType = detectTelemetryHostType()): Promise<void> {
191
- const state = getOrCreateInstallTelemetryState();
192
- if (state.cli_first_seen_reported_at) return;
193
-
194
- const createdAt = new Date().toISOString();
195
- const ok = await postTelemetry("/v1/telemetry/install", {
196
- install_id: state.install_id,
197
- source: "cli-first-seen",
198
- host_type: hostType,
199
- skill: "unbrowse",
200
- status: "installed",
201
- created_at: createdAt,
202
- properties: {
203
- profile: getActiveProfile(),
204
- first_seen_at: state.first_seen_at,
205
- },
206
- });
207
-
208
- if (!ok) return;
209
- state.cli_first_seen_reported_at = createdAt;
210
- saveInstallTelemetryState(state);
211
- }
212
-
213
- export async function recordInstallTelemetryEvent(
214
- source: InstallTelemetrySource,
215
- options?: {
216
- hostType?: TelemetryHostType;
217
- status?: string;
218
- createdAt?: string;
219
- properties?: Record<string, unknown>;
220
- skill?: string;
221
- skillVersion?: string;
222
- },
223
- ): Promise<void> {
224
- const createdAt = options?.createdAt ?? new Date().toISOString();
225
- await postTelemetry("/v1/telemetry/install", {
226
- install_id: getInstallId(),
227
- source,
228
- host_type: options?.hostType ?? detectTelemetryHostType(),
229
- skill: options?.skill ?? "unbrowse",
230
- skill_version: options?.skillVersion,
231
- status: options?.status ?? "installed",
232
- created_at: createdAt,
233
- properties: options?.properties,
234
- });
235
- }
236
-
237
- export async function recordFunnelTelemetryEvent(
238
- name: string,
239
- options?: {
240
- source?: FunnelTelemetrySource;
241
- hostType?: TelemetryHostType;
242
- createdAt?: string;
243
- sessionId?: string;
244
- properties?: Record<string, unknown>;
245
- },
246
- ): Promise<void> {
247
- const createdAt = options?.createdAt ?? new Date().toISOString();
248
- await postTelemetry("/v1/telemetry/events", {
249
- install_id: getInstallId(),
250
- session_id: options?.sessionId,
251
- name,
252
- source: options?.source ?? "cli",
253
- host_type: options?.hostType ?? detectTelemetryHostType(),
254
- created_at: createdAt,
255
- properties: options?.properties,
256
- });
257
- }
258
-
259
78
  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/i;
260
79
 
261
80
  export function normalizeAgentEmail(value: string): string {
@@ -275,23 +94,6 @@ export function resolveAgentName(preferredEmail: string | undefined, fallbackNam
275
94
  return isValidAgentEmail(normalized) ? normalized : fallbackName;
276
95
  }
277
96
 
278
- export function getLocalWalletContext(): { wallet_address?: string; wallet_provider?: string } {
279
- const lobsterWallet = process.env.LOBSTER_WALLET_ADDRESS?.trim();
280
- if (lobsterWallet) {
281
- return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
282
- }
283
-
284
- const genericWallet = process.env.AGENT_WALLET_ADDRESS?.trim();
285
- if (genericWallet) {
286
- return {
287
- wallet_address: genericWallet,
288
- wallet_provider: process.env.AGENT_WALLET_PROVIDER?.trim() || undefined,
289
- };
290
- }
291
-
292
- return {};
293
- }
294
-
295
97
  export function getApiKey(): string {
296
98
  if (LOCAL_ONLY) return "local-only";
297
99
  // Env var takes priority, then cached config
@@ -304,27 +106,7 @@ export function getApiKey(): string {
304
106
  return "";
305
107
  }
306
108
 
307
- /**
308
- * Derive a stable, privacy-safe indexer identifier from the raw API key.
309
- * Returns a hex SHA-256 hash, or "" for empty / local-only keys.
310
- */
311
- export function hashApiKey(key: string): string {
312
- if (!key || key === "local-only") return "";
313
- return createHash("sha256").update(key).digest("hex");
314
- }
315
-
316
- /**
317
- * Return the locally registered agent_id, or null if not registered.
318
- * Used as the default indexer_id for Tier 1 attribution when the skill
319
- * manifest doesn't already carry one.
320
- */
321
- export function getAgentId(): string | null {
322
- const config = loadConfig();
323
- return config?.agent_id ?? null;
324
- }
325
-
326
109
  const API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
327
- const PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
328
110
 
329
111
  async function validateApiKey(key: string): Promise<ApiKeyValidationResult> {
330
112
  const controller = new AbortController();
@@ -387,15 +169,10 @@ async function findUsableApiKey(): Promise<{ key: string; source: ApiKeySource }
387
169
  return null;
388
170
  }
389
171
 
390
- async function apiRequest<T = unknown>(
391
- method: string,
392
- path: string,
393
- body?: unknown,
394
- opts?: { noAuth?: boolean; timeoutMs?: number },
395
- ): Promise<{ data: T; headers: Headers }> {
172
+ async function api<T = unknown>(method: string, path: string, body?: unknown, opts?: { noAuth?: boolean }): Promise<T> {
396
173
  const key = opts?.noAuth ? "" : getApiKey();
397
174
  const controller = new AbortController();
398
- const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? API_TIMEOUT_MS);
175
+ const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
399
176
  let res: Response;
400
177
  try {
401
178
  res = await fetch(`${API_URL}${path}`, {
@@ -428,32 +205,11 @@ async function apiRequest<T = unknown>(
428
205
  throw new Error("ToS update required. Restart unbrowse to accept new terms.");
429
206
  }
430
207
 
431
- // Handle x402 payment required — surface payment terms to the caller
432
- if (res.status === 402) {
433
- const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
434
- const legacyPaymentTerms = res.headers.get("X-Payment-Required");
435
- const terms = paymentRequired
436
- ? decodeBase64Json(paymentRequired)
437
- : legacyPaymentTerms
438
- ? JSON.parse(legacyPaymentTerms)
439
- : (data as Record<string, unknown>).terms;
440
- const err = new Error(`Payment required: ${(data as Record<string, unknown>).error ?? "This skill requires payment"}`);
441
- (err as Error & { x402: boolean; terms: unknown; status: number }).x402 = true;
442
- (err as Error & { terms: unknown }).terms = terms;
443
- (err as Error & { status: number }).status = 402;
444
- throw err;
445
- }
446
-
447
208
  if (!res.ok) {
448
209
  const errData = data as { error?: string; details?: string[] };
449
210
  const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
450
211
  throw new Error(msg);
451
212
  }
452
- return { data: data as T, headers: res.headers };
453
- }
454
-
455
- async function api<T = unknown>(method: string, path: string, body?: unknown, opts?: { noAuth?: boolean; timeoutMs?: number }): Promise<T> {
456
- const { data } = await apiRequest<T>(method, path, body, opts);
457
213
  return data;
458
214
  }
459
215
 
@@ -518,8 +274,7 @@ async function promptAgentEmail(defaultName: string): Promise<string> {
518
274
  }
519
275
  }
520
276
 
521
- async function checkTosStatus(options?: { exitOnFailure?: boolean }): Promise<boolean> {
522
- const exitOnFailure = options?.exitOnFailure ?? true;
277
+ async function checkTosStatus(): Promise<void> {
523
278
  const config = loadConfig();
524
279
 
525
280
  let tosInfo: { version: string; summary: string; url: string };
@@ -528,11 +283,11 @@ async function checkTosStatus(options?: { exitOnFailure?: boolean }): Promise<bo
528
283
  } catch {
529
284
  // Offline — allow usage with whatever ToS was previously accepted.
530
285
  // Backend will enforce on next actual API call anyway.
531
- return true;
286
+ return;
532
287
  }
533
288
 
534
289
  if (config?.tos_accepted_version === tosInfo.version) {
535
- return true; // Already accepted current version
290
+ return; // Already accepted current version
536
291
  }
537
292
 
538
293
  // Need re-acceptance
@@ -540,8 +295,7 @@ async function checkTosStatus(options?: { exitOnFailure?: boolean }): Promise<bo
540
295
  const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
541
296
  if (!accepted) {
542
297
  console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
543
- if (exitOnFailure) process.exit(1);
544
- return false;
298
+ process.exit(1);
545
299
  }
546
300
 
547
301
  // Call accept-tos endpoint
@@ -559,27 +313,17 @@ async function checkTosStatus(options?: { exitOnFailure?: boolean }): Promise<bo
559
313
  console.warn(`Failed to record ToS acceptance: ${(err as Error).message}`);
560
314
  // Don't block — backend will enforce on next call
561
315
  }
562
- return true;
563
316
  }
564
317
 
565
318
  /** Auto-register with the backend if no API key is configured. Persists to ~/.unbrowse/config.json. */
566
- export async function ensureRegistered(options?: { promptForEmail?: boolean; exitOnFailure?: boolean }): Promise<void> {
319
+ export async function ensureRegistered(options?: { promptForEmail?: boolean }): Promise<void> {
567
320
  if (LOCAL_ONLY) return;
568
- const exitOnFailure = options?.exitOnFailure ?? true;
569
321
  const usableKey = await findUsableApiKey();
570
322
  if (usableKey) {
571
323
  if (usableKey.source === "config") {
572
324
  console.log("[unbrowse] Restored saved registration.");
573
325
  }
574
- const accepted = await checkTosStatus({ exitOnFailure });
575
- if (!accepted) return;
576
- try {
577
- const profile = await getMyProfile();
578
- const wallet = getLocalWalletContext();
579
- if (wallet.wallet_address && profile.wallet_address !== wallet.wallet_address) {
580
- await syncAgentWallet(wallet);
581
- }
582
- } catch { /* non-fatal */ }
326
+ await checkTosStatus();
583
327
  return;
584
328
  }
585
329
 
@@ -597,8 +341,7 @@ export async function ensureRegistered(options?: { promptForEmail?: boolean; exi
597
341
  const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
598
342
  if (!accepted) {
599
343
  console.log("You must accept the Terms of Service to use Unbrowse.");
600
- if (exitOnFailure) process.exit(1);
601
- return;
344
+ process.exit(1);
602
345
  }
603
346
 
604
347
  // Step 3: Register with ToS version
@@ -607,9 +350,8 @@ export async function ensureRegistered(options?: { promptForEmail?: boolean; exi
607
350
  console.log(`Registering as "${name}"...`);
608
351
 
609
352
  try {
610
- const wallet = getLocalWalletContext();
611
353
  const { agent_id, api_key } = await api<{ agent_id: string; api_key: string }>(
612
- "POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet }
354
+ "POST", "/v1/agents/register", { name, tos_version: tosInfo.version }
613
355
  );
614
356
 
615
357
  process.env.UNBROWSE_API_KEY = api_key;
@@ -620,52 +362,14 @@ export async function ensureRegistered(options?: { promptForEmail?: boolean; exi
620
362
  registered_at: new Date().toISOString(),
621
363
  tos_accepted_version: tosInfo.version,
622
364
  tos_accepted_at: new Date().toISOString(),
623
- ...wallet,
624
- });
625
-
626
- await recordFunnelTelemetryEvent("registration_succeeded", {
627
- source: "cli",
628
- properties: {
629
- prompt_for_email: options?.promptForEmail === true,
630
- },
631
365
  });
632
366
 
633
367
  console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
634
368
  } catch (err) {
635
369
  console.warn(`Registration failed: ${(err as Error).message}`);
636
370
  console.warn("Set UNBROWSE_API_KEY manually or try again.");
637
- if (exitOnFailure) process.exit(1);
638
- }
639
- }
640
-
641
- let backgroundRegistrationPromise: Promise<void> | null = null;
642
-
643
- export function startBackgroundRegistration(options?: { promptForEmail?: boolean }): Promise<void> {
644
- if (LOCAL_ONLY) return Promise.resolve();
645
- if (backgroundRegistrationPromise) return backgroundRegistrationPromise;
646
- backgroundRegistrationPromise = ensureRegistered({
647
- promptForEmail: options?.promptForEmail,
648
- exitOnFailure: false,
649
- })
650
- .catch((err) => {
651
- console.warn(`[unbrowse] Background registration failed: ${(err as Error).message}`);
652
- })
653
- .finally(() => {
654
- backgroundRegistrationPromise = null;
655
- });
656
- return backgroundRegistrationPromise;
657
- }
658
-
659
- export async function waitForBackgroundRegistration(timeoutMs = 0): Promise<void> {
660
- if (!backgroundRegistrationPromise) return;
661
- if (timeoutMs <= 0) {
662
- await backgroundRegistrationPromise;
663
- return;
371
+ process.exit(1);
664
372
  }
665
- await Promise.race([
666
- backgroundRegistrationPromise,
667
- new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),
668
- ]);
669
373
  }
670
374
 
671
375
  // --- Skill CRUD ---
@@ -834,29 +538,7 @@ export async function publishSkill(
834
538
  } as SkillManifest & { warnings: string[] };
835
539
  }
836
540
  if (LOCAL_ONLY) throw new Error("local-only mode");
837
- const wallet = getLocalWalletContext();
838
- const published = await api<SkillManifest & { warnings: string[] }>("POST", "/v1/skills", {
839
- ...draft,
840
- ...(wallet.wallet_address ? wallet : {}),
841
- }, { timeoutMs: PUBLISH_TIMEOUT_MS });
842
-
843
- const cascade = await ensureCascadeSplitForSkill(published).catch((err) => ({
844
- warning: `cascade_split_failed:${(err as Error).message}`,
845
- }));
846
- const warnings = [...(published.warnings ?? [])];
847
- if (cascade.warning) warnings.push(cascade.warning);
848
-
849
- if (cascade.split_config && cascade.split_config !== published.split_config) {
850
- const updated = await api<SkillManifest>("PATCH", `/v1/skills/${published.skill_id}`, {
851
- split_config: cascade.split_config,
852
- });
853
- return {
854
- ...updated,
855
- warnings,
856
- };
857
- }
858
-
859
- return { ...published, warnings };
541
+ return api("POST", "/v1/skills", draft);
860
542
  }
861
543
 
862
544
  export async function deprecateSkill(skillId: string): Promise<void> {
@@ -929,11 +611,10 @@ export async function searchIntentResolve(
929
611
  domain_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
930
612
  global_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
931
613
  skipped_global: boolean;
932
- actual_cost_uc?: number;
933
614
  }> {
934
615
  if (LOCAL_ONLY) return { domain_results: [], global_results: [], skipped_global: false };
935
616
  try {
936
- const { data, headers } = await apiRequest<{
617
+ return await api<{
937
618
  domain_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
938
619
  global_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
939
620
  skipped_global: boolean;
@@ -943,24 +624,12 @@ export async function searchIntentResolve(
943
624
  domain_k: domainK,
944
625
  global_k: globalK,
945
626
  });
946
- const actualCostHeader = headers.get("X-Unbrowse-Cost-Uc");
947
- const actualCostUc = actualCostHeader && /^\d+$/.test(actualCostHeader)
948
- ? Number(actualCostHeader)
949
- : undefined;
950
- return actualCostUc != null ? { ...data, actual_cost_uc: actualCostUc } : data;
951
- } catch (err) {
952
- if (isX402Error(err)) throw err;
627
+ } catch {
953
628
  const [domain_results, global_results] = await Promise.all([
954
629
  domain
955
- ? searchIntentInDomain(intent, domain, domainK).catch((fallbackErr) => {
956
- if (isX402Error(fallbackErr)) throw fallbackErr;
957
- return [] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
958
- })
630
+ ? searchIntentInDomain(intent, domain, domainK).catch(() => [] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>)
959
631
  : Promise.resolve([] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>),
960
- searchIntent(intent, globalK).catch((fallbackErr) => {
961
- if (isX402Error(fallbackErr)) throw fallbackErr;
962
- return [] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
963
- }),
632
+ searchIntent(intent, globalK).catch(() => [] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>),
964
633
  ]);
965
634
  return { domain_results, global_results, skipped_global: false };
966
635
  }
@@ -968,81 +637,21 @@ export async function searchIntentResolve(
968
637
 
969
638
  // --- Stats ---
970
639
 
971
- /** Execution payload sent to POST /v1/stats/execution */
972
- export interface ExecutionPayload {
973
- skill_id: string;
974
- endpoint_id: string;
975
- trace: Omit<ExecutionTrace, "result">;
976
- indexer_id?: string;
977
- }
978
-
979
- export interface AnalyticsSessionPayload {
980
- session_id: string;
981
- started_at: string;
982
- completed_at?: string;
983
- trace_version?: string;
984
- api_calls: number;
985
- discovery_queries?: number;
986
- cached_skill_calls?: number;
987
- fresh_index_calls?: number;
988
- browser_mode?: "default" | "replaced" | "manual" | "unknown";
989
- }
990
-
991
- /**
992
- * Build the POST body for /v1/stats/execution.
993
- * Pure function — no I/O, fully testable.
994
- *
995
- * Derives indexer_id from:
996
- * 1. Explicit override (opts.indexer_id)
997
- * 2. skill.indexer_id (set by the backend at publish time)
998
- * 3. undefined (backend will fall back to its own lookup)
999
- */
1000
- export function buildExecutionPayload(
640
+ export async function recordExecution(
1001
641
  skillId: string,
1002
642
  endpointId: string,
1003
- trace: ExecutionTrace,
1004
- skill?: Pick<SkillManifest, "indexer_id"> | null,
1005
- opts?: { indexer_id?: string },
1006
- ): ExecutionPayload {
643
+ trace: ExecutionTrace
644
+ ): Promise<void> {
645
+ if (LOCAL_ONLY) return;
646
+ // Strip actual API response data — only send metadata for scoring
1007
647
  const { result: _result, ...metadata } = trace;
1008
- const indexer_id = opts?.indexer_id ?? skill?.indexer_id ?? (hashApiKey(getApiKey()) || undefined);
1009
- const payload: ExecutionPayload = {
648
+ await api("POST", "/v1/stats/execution", {
1010
649
  skill_id: skillId,
1011
650
  endpoint_id: endpointId,
1012
651
  trace: metadata,
1013
- };
1014
- if (indexer_id) payload.indexer_id = indexer_id;
1015
- return payload;
1016
- }
1017
- export async function recordExecution(
1018
- skillId: string,
1019
- endpointId: string,
1020
- trace: ExecutionTrace,
1021
- skill?: Pick<SkillManifest, "indexer_id"> | null,
1022
- ): Promise<void> {
1023
- if (LOCAL_ONLY) return;
1024
- const payload = buildExecutionPayload(skillId, endpointId, trace, skill);
1025
- await api("POST", "/v1/stats/execution", payload);
1026
- }
1027
-
1028
- export async function recordAnalyticsSession(payload: AnalyticsSessionPayload): Promise<void> {
1029
- if (LOCAL_ONLY) return;
1030
- await api("POST", "/v1/analytics/sessions", payload);
652
+ });
1031
653
  }
1032
654
 
1033
- /** Record a payment transaction for a paid skill execution. Fire-and-forget. */
1034
- export async function recordTransaction(params: {
1035
- transaction_id: string;
1036
- consumer_id: string;
1037
- creator_id?: string;
1038
- skill_id: string;
1039
- endpoint_id?: string;
1040
- price_usd: number;
1041
- payment_proof?: string;
1042
- }): Promise<void> {
1043
- if (LOCAL_ONLY) return;
1044
- await api("POST", "/v1/transactions", params);
1045
- }
1046
655
  export async function recordFeedback(
1047
656
  skillId: string,
1048
657
  endpointId: string,
@@ -1076,23 +685,7 @@ export async function recordDiagnostics(
1076
685
 
1077
686
  export async function recordOrchestrationPerf(timing: OrchestrationTiming): Promise<void> {
1078
687
  if (LOCAL_ONLY) return;
1079
- const lifecycleSource: LifecycleEvent["source"] =
1080
- timing.source === "marketplace" ? "marketplace"
1081
- : timing.source === "live-capture" ? "live-capture"
1082
- : "cache";
1083
- const now = new Date().toISOString();
1084
- const events: LifecycleEvent[] = [];
1085
- if (timing.search_ms > 0) {
1086
- events.push({ phase: "discover", skill_id: timing.skill_id ?? "", timestamp: now, duration_ms: timing.search_ms, source: lifecycleSource });
1087
- }
1088
- if (timing.get_skill_ms > 0) {
1089
- events.push({ phase: "resolve", skill_id: timing.skill_id ?? "", timestamp: now, duration_ms: timing.get_skill_ms, source: lifecycleSource });
1090
- }
1091
- if (timing.execute_ms > 0) {
1092
- events.push({ phase: "execute", skill_id: timing.skill_id ?? "", timestamp: now, duration_ms: timing.execute_ms, source: lifecycleSource });
1093
- }
1094
- const phaseTotals = Object.fromEntries(attributeLifecycle(events));
1095
- await api("POST", "/v1/stats/perf", { ...timing, phase_totals_ms: phaseTotals });
688
+ await api("POST", "/v1/stats/perf", timing);
1096
689
  }
1097
690
 
1098
691
  // --- Validation ---
@@ -1102,121 +695,13 @@ export async function validateManifest(manifest: unknown): Promise<ValidationRes
1102
695
  return api<ValidationResult>("POST", "/v1/validate", manifest);
1103
696
  }
1104
697
 
1105
- // --- Graph Edge Publishing ---
1106
-
1107
- /**
1108
- * Publish operation graph edges to the dedicated graph endpoint.
1109
- * Fire-and-forget: logs errors but does not throw.
1110
- */
1111
- export async function publishGraphEdges(
1112
- domain: string,
1113
- node: { endpoint_id: string; method: string; url_template: string },
1114
- edges: Array<{ target_endpoint_id: string; kind: string; confidence: number }>
1115
- ): Promise<void> {
1116
- if (LOCAL_ONLY) return;
1117
- try {
1118
- await api("POST", "/v1/graph/edges", { domain, node, edges });
1119
- } catch (err) {
1120
- console.error(`[graph] failed to publish edges for ${domain}: ${(err as Error).message}`);
1121
- }
1122
- }
1123
-
1124
- // ---------------------------------------------------------------------------
1125
- // Auto-file GitHub issues from accumulated agent errors
1126
- // ---------------------------------------------------------------------------
1127
-
1128
- export interface AutoFilePayload {
1129
- skill_id: string;
1130
- endpoint_id: string;
1131
- domain: string;
1132
- intent: string;
1133
- url?: string;
1134
- error: string;
1135
- failure_count: number;
1136
- first_seen: string;
1137
- last_seen: string;
1138
- kuri_version: string;
1139
- }
1140
-
1141
- /**
1142
- * Auto-file a GitHub issue via the backend. Fire-and-forget — failures
1143
- * are logged but never thrown.
1144
- */
1145
- export async function autoFileIssue(payload: AutoFilePayload): Promise<void> {
1146
- if (isLocalOnlyMode()) {
1147
- console.log(`[auto-file] skipped (local-only mode): ${payload.skill_id}:${payload.endpoint_id}`);
1148
- return;
1149
- }
1150
- try {
1151
- await api("POST", "/v1/issues/auto-file", payload);
1152
- console.log(`[auto-file] issue filed for ${payload.skill_id}:${payload.endpoint_id} (${payload.failure_count} failures)`);
1153
- } catch (err) {
1154
- console.warn(`[auto-file] failed: ${(err as Error).message}`);
1155
- }
1156
- }
1157
-
1158
- // --- Cross-Agent Discovery Diagnostics ---
1159
-
1160
- /**
1161
- * Diagnostic function: polls marketplace search to verify a skill is discoverable.
1162
- * Not called in production flow -- used for verifying cross-agent discovery within 60s.
1163
- */
1164
- export async function verifyMarketplaceDiscovery(
1165
- skillId: string,
1166
- intent: string,
1167
- maxWaitMs = 60000
1168
- ): Promise<{ found: boolean; latency_ms: number }> {
1169
- const start = Date.now();
1170
- const pollInterval = 2000;
1171
-
1172
- while (Date.now() - start < maxWaitMs) {
1173
- try {
1174
- const results = await searchIntent(intent, 10);
1175
- for (const result of results) {
1176
- const meta = result.metadata ?? {};
1177
- let foundId: string | undefined;
1178
- // Check metadata.skill_id directly
1179
- if (typeof meta.skill_id === "string") {
1180
- foundId = meta.skill_id;
1181
- }
1182
- // Try parsing metadata.content as JSON for skill_id
1183
- if (!foundId && typeof meta.content === "string") {
1184
- try {
1185
- const parsed = JSON.parse(meta.content);
1186
- if (typeof parsed.skill_id === "string") foundId = parsed.skill_id;
1187
- } catch { /* not JSON */ }
1188
- }
1189
- if (foundId === skillId) {
1190
- return { found: true, latency_ms: Date.now() - start };
1191
- }
1192
- }
1193
- } catch { /* search failed, retry */ }
1194
-
1195
- await new Promise(resolve => setTimeout(resolve, pollInterval));
1196
- }
1197
-
1198
- return { found: false, latency_ms: Date.now() - start };
1199
- }
1200
-
1201
698
  // --- Agent Registration ---
1202
699
 
1203
- export async function registerAgent(
1204
- name: string,
1205
- wallet: { wallet_address?: string; wallet_provider?: string } = getLocalWalletContext(),
1206
- ): Promise<{ agent_id: string; api_key: string }> {
1207
- return api<{ agent_id: string; api_key: string }>("POST", "/v1/agents/register", { name, ...wallet });
700
+ export async function registerAgent(name: string): Promise<{ agent_id: string; api_key: string }> {
701
+ return api<{ agent_id: string; api_key: string }>("POST", "/v1/agents/register", { name });
1208
702
  }
1209
703
 
1210
- export async function getAgent(agentId: string): Promise<{
1211
- agent_id: string;
1212
- name: string;
1213
- created_at: string;
1214
- wallet_address?: string | null;
1215
- wallet_provider?: string | null;
1216
- skills_discovered: string[];
1217
- total_executions: number;
1218
- total_feedback_given: number;
1219
- } | null> {
704
+ export async function getAgent(agentId: string): Promise<{ agent_id: string; name: string; created_at: string; skills_discovered: string[]; total_executions: number; total_feedback_given: number } | null> {
1220
705
  try {
1221
706
  return await api("GET", `/v1/agents/${agentId}`);
1222
707
  } catch {
@@ -1224,84 +709,6 @@ export async function getAgent(agentId: string): Promise<{
1224
709
  }
1225
710
  }
1226
711
 
1227
- export async function getMyProfile(): Promise<{
1228
- agent_id: string;
1229
- name: string;
1230
- created_at: string;
1231
- wallet_address?: string | null;
1232
- wallet_provider?: string | null;
1233
- skills_discovered: string[];
1234
- total_executions: number;
1235
- total_feedback_given: number;
1236
- }> {
712
+ export async function getMyProfile(): Promise<{ agent_id: string; name: string; created_at: string; skills_discovered: string[]; total_executions: number; total_feedback_given: number }> {
1237
713
  return api("GET", "/v1/agents/me", undefined);
1238
714
  }
1239
-
1240
- export async function syncAgentWallet(wallet = getLocalWalletContext()): Promise<void> {
1241
- if (!wallet.wallet_address) return;
1242
- await api("POST", "/v1/agents/wallet", wallet);
1243
- const config = loadConfig();
1244
- if (!config) return;
1245
- saveConfig({ ...config, ...wallet });
1246
- }
1247
-
1248
-
1249
- // --- Transaction Visibility ---
1250
-
1251
- /** Get consumer payment history for an agent. */
1252
- export async function getTransactionHistory(agentId: string): Promise<{
1253
- ledger: {
1254
- agent_id: string;
1255
- total_spent_uc: number;
1256
- total_spent_usd: number;
1257
- transaction_count: number;
1258
- first_transaction_at: string;
1259
- last_transaction_at: string;
1260
- } | null;
1261
- transactions: Array<{
1262
- transaction_id: string;
1263
- consumer_id: string;
1264
- creator_id: string;
1265
- skill_id: string;
1266
- price_usd: number;
1267
- price_uc: number;
1268
- status: string;
1269
- created_at: string;
1270
- }>;
1271
- }> {
1272
- return api("GET", `/v1/transactions/consumer/${agentId}`);
1273
- }
1274
-
1275
- /** Get creator earnings history for an agent/indexer. */
1276
- export async function getCreatorEarnings(agentId: string): Promise<{
1277
- ledger: {
1278
- agent_id: string;
1279
- total_earned_uc: number;
1280
- total_earned_usd: number;
1281
- total_fees_uc: number;
1282
- transaction_count: number;
1283
- first_transaction_at: string;
1284
- last_transaction_at: string;
1285
- } | null;
1286
- transactions: Array<{
1287
- transaction_id: string;
1288
- consumer_id: string;
1289
- creator_id: string;
1290
- skill_id: string;
1291
- price_usd: number;
1292
- creator_payout_uc: number;
1293
- status: string;
1294
- created_at: string;
1295
- }>;
1296
- }> {
1297
- return api("GET", `/v1/transactions/creator/${agentId}`);
1298
- }
1299
-
1300
- /** Set the base price for a skill (requires auth as skill owner). */
1301
- export async function setSkillPrice(skillId: string, priceUsd: number): Promise<unknown> {
1302
- return api("PATCH", `/v1/skills/${skillId}`, { base_price_usd: priceUsd });
1303
- }
1304
-
1305
- export async function setSkillSplitConfig(skillId: string, splitConfig: string | null): Promise<unknown> {
1306
- return api("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
1307
- }