owletto 1.2.0 → 1.3.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/dist/bin.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  parseGlobalFlags
4
- } from "./chunk-NG33L2S7.js";
4
+ } from "./chunk-ZDMROF24.js";
5
5
  import {
6
6
  CliError
7
- } from "./chunk-LPKFXYXP.js";
7
+ } from "./chunk-ANTW3P24.js";
8
8
  import {
9
9
  defineCommand,
10
10
  printError,
@@ -19,21 +19,22 @@ var main = defineCommand({
19
19
  description: "Unified CLI for Owletto"
20
20
  },
21
21
  subCommands: {
22
- version: () => import("./version-6NLY47MV.js").then((m) => m.default),
23
- context: () => import("./context-R36C4KTS.js").then((m) => m.default),
24
- login: () => import("./openclaw-NN7EIOSB.js").then((m) => m.login),
25
- token: () => import("./openclaw-NN7EIOSB.js").then((m) => m.token),
26
- health: () => import("./openclaw-NN7EIOSB.js").then((m) => m.health),
27
- configure: () => import("./openclaw-NN7EIOSB.js").then((m) => m.configure),
28
- db: () => import("./db-APU4VTB6.js").then((m) => m.default),
29
- dev: () => import("./dev-EVH2ITQ7.js").then((m) => m.default),
30
- doctor: () => import("./doctor-XONDHUND.js").then((m) => m.default),
31
- server: () => import("./server-5GYVVR2T.js").then((m) => m.default),
32
- worker: () => import("./worker-ETZD3I3B.js").then((m) => m.default),
33
- embeddings: () => import("./embeddings-AH3BLVFN.js").then((m) => m.default),
34
- connector: () => import("./connector-WVYEJLPU.js").then((m) => m.default),
35
- feed: () => import("./feed-QKU5LWE6.js").then((m) => m.default),
36
- mcp: () => import("./mcp-M2P7KK4C.js").then((m) => m.default)
22
+ version: () => import("./version-4EIVIAAI.js").then((m) => m.default),
23
+ context: () => import("./context-ERFLLCQK.js").then((m) => m.default),
24
+ login: () => import("./openclaw-Q6VSZHKD.js").then((m) => m.login),
25
+ token: () => import("./openclaw-Q6VSZHKD.js").then((m) => m.token),
26
+ health: () => import("./openclaw-Q6VSZHKD.js").then((m) => m.health),
27
+ configure: () => import("./openclaw-Q6VSZHKD.js").then((m) => m.configure),
28
+ db: () => import("./db-ETXQBGO2.js").then((m) => m.default),
29
+ dev: () => import("./dev-5HZQGSMK.js").then((m) => m.default),
30
+ doctor: () => import("./doctor-QZEC2GKR.js").then((m) => m.default),
31
+ server: () => import("./server-S66FQJBP.js").then((m) => m.default),
32
+ worker: () => import("./worker-WPTOEUR2.js").then((m) => m.default),
33
+ embeddings: () => import("./embeddings-425LPWPY.js").then((m) => m.default),
34
+ connector: () => import("./connector-26KT2II4.js").then((m) => m.default),
35
+ feed: () => import("./feed-SV333DHN.js").then((m) => m.default),
36
+ run: () => import("./run-RZZFXJNG.js").then((m) => m.default),
37
+ "browser-auth": () => import("./browser-auth-5NM6CIN7.js").then((m) => m.default)
37
38
  }
38
39
  });
39
40
 
@@ -0,0 +1,470 @@
1
+ import {
2
+ getProfile
3
+ } from "./chunk-ZDMROF24.js";
4
+ import "./chunk-ANTW3P24.js";
5
+ import {
6
+ defineCommand,
7
+ printText
8
+ } from "./chunk-TMPMAYY4.js";
9
+ import "./chunk-QGM4M3NI.js";
10
+
11
+ // src/commands/browser-auth.ts
12
+ import { execSync, spawn } from "child_process";
13
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "fs";
14
+ import { get as httpGet } from "http";
15
+ import { createServer } from "net";
16
+ import { join } from "path";
17
+ function getChromePaths() {
18
+ if (process.platform === "darwin") {
19
+ return {
20
+ binary: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
21
+ profileDir: join(process.env.HOME, "Library/Application Support/Google/Chrome")
22
+ };
23
+ }
24
+ if (process.platform === "linux") {
25
+ return {
26
+ binary: "/usr/bin/google-chrome",
27
+ profileDir: join(process.env.HOME, ".config/google-chrome")
28
+ };
29
+ }
30
+ throw new Error(`Unsupported platform: ${process.platform}`);
31
+ }
32
+ function listProfiles(profileDir) {
33
+ const localStatePath = join(profileDir, "Local State");
34
+ if (!existsSync(localStatePath)) {
35
+ throw new Error(`Chrome Local State not found at ${localStatePath}`);
36
+ }
37
+ const localState = JSON.parse(readFileSync(localStatePath, "utf8"));
38
+ const infoCache = localState.profile?.info_cache ?? {};
39
+ const lastUsed = localState.profile?.last_used ?? "";
40
+ return Object.entries(infoCache).map(([dir, info]) => ({
41
+ dir,
42
+ name: info.name ?? dir,
43
+ email: info.user_name ?? "",
44
+ isLastUsed: dir === lastUsed
45
+ }));
46
+ }
47
+ async function extractCookies(chromeBinary, profileDir, chromeProfileDir, domains) {
48
+ if (process.platform === "darwin") {
49
+ return extractCookiesMacOS(profileDir, chromeProfileDir, domains);
50
+ }
51
+ return extractCookiesCDP(chromeBinary, profileDir, chromeProfileDir, domains);
52
+ }
53
+ var BROWSER_KEYCHAIN = [
54
+ { service: "Chrome Safe Storage", account: "Chrome" },
55
+ { service: "Chromium Safe Storage", account: "Chromium" },
56
+ { service: "Brave Safe Storage", account: "Brave" },
57
+ { service: "Microsoft Edge Safe Storage", account: "Microsoft Edge" }
58
+ ];
59
+ async function extractCookiesMacOS(profileDir, chromeProfileDir, domains) {
60
+ const { pbkdf2Sync, createDecipheriv } = await import("crypto");
61
+ const { DatabaseSync } = await import("sqlite");
62
+ const cookiePath = join(profileDir, chromeProfileDir, "Cookies");
63
+ if (!existsSync(cookiePath)) {
64
+ throw new Error(`No Cookies file found in Chrome profile: ${chromeProfileDir}`);
65
+ }
66
+ const tmpDir = mkdtempSync("/tmp/owletto-auth-");
67
+ const tmpCookiePath = join(tmpDir, "Cookies");
68
+ copyFileSync(cookiePath, tmpCookiePath);
69
+ const journalSrc = join(profileDir, chromeProfileDir, "Cookies-journal");
70
+ if (existsSync(journalSrc)) {
71
+ copyFileSync(journalSrc, join(tmpDir, "Cookies-journal"));
72
+ }
73
+ try {
74
+ printText(
75
+ `macOS will ask to access your browser's cookie encryption key.
76
+ A system dialog from "security" will appear \u2014 click "Always Allow" to avoid future prompts.`
77
+ );
78
+ let keychainKey = null;
79
+ for (const { service, account } of BROWSER_KEYCHAIN) {
80
+ try {
81
+ keychainKey = execSync(
82
+ `security find-generic-password -w -s "${service}" -a "${account}"`,
83
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
84
+ ).trim();
85
+ if (keychainKey) break;
86
+ } catch {
87
+ }
88
+ }
89
+ if (!keychainKey) {
90
+ throw new Error(
91
+ 'Could not read browser encryption key from macOS Keychain.\nIf a system dialog appeared, click "Always Allow" and retry.\nIf no dialog appeared, your Keychain may be locked \u2014 run: security unlock-keychain'
92
+ );
93
+ }
94
+ const derivedKey = pbkdf2Sync(keychainKey, "saltysalt", 1003, 16, "sha1");
95
+ const domainClauses = domains.map((d) => {
96
+ const clean = d.replace(/^\./, "");
97
+ return `host_key = '.${clean}' OR host_key = '${clean}' OR host_key LIKE '%.${clean}'`;
98
+ }).join(" OR ");
99
+ const db = new DatabaseSync(tmpCookiePath, { readOnly: true });
100
+ const rows = db.prepare(
101
+ `SELECT name, host_key, path, encrypted_value, CAST(expires_utc AS TEXT) as expires_utc_text, is_httponly, is_secure, samesite
102
+ FROM cookies WHERE ${domainClauses}`
103
+ ).all();
104
+ db.close();
105
+ const cookies = [];
106
+ const chromeEpochOffset = 11644473600n;
107
+ const iv = Buffer.alloc(16, " ");
108
+ for (const row of rows) {
109
+ const raw = row.encrypted_value;
110
+ const encrypted = raw instanceof Buffer ? raw : Buffer.from(raw);
111
+ let value = "";
112
+ if (encrypted && encrypted.length > 3) {
113
+ const version = encrypted.slice(0, 3).toString("utf-8");
114
+ if (version === "v10" || version === "v11") {
115
+ const ciphertext = encrypted.slice(3);
116
+ try {
117
+ const decipher = createDecipheriv("aes-128-cbc", derivedKey, iv);
118
+ const dec = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
119
+ value = extractPrintableSuffix(dec);
120
+ } catch {
121
+ continue;
122
+ }
123
+ } else {
124
+ value = encrypted.toString("utf-8");
125
+ }
126
+ }
127
+ if (!value && !row.name) continue;
128
+ const expiresUtc = BigInt(row.expires_utc_text ?? "0");
129
+ const expiresUnix = expiresUtc > 0n ? Number(expiresUtc / 1000000n - chromeEpochOffset) : -1;
130
+ cookies.push({
131
+ name: row.name,
132
+ value,
133
+ domain: row.host_key,
134
+ path: row.path ?? "/",
135
+ expires: expiresUnix,
136
+ httpOnly: row.is_httponly === 1,
137
+ secure: row.is_secure === 1,
138
+ sameSite: row.samesite === 0 ? "None" : row.samesite === 1 ? "Lax" : "Strict"
139
+ });
140
+ }
141
+ return cookies;
142
+ } finally {
143
+ try {
144
+ rmSync(tmpDir, { recursive: true, force: true });
145
+ } catch {
146
+ }
147
+ }
148
+ }
149
+ function extractPrintableSuffix(buf) {
150
+ let boundary = buf.length;
151
+ for (let i = buf.length - 1; i >= 0; i--) {
152
+ const b = buf[i];
153
+ if (b >= 32 && b <= 126) {
154
+ boundary = i;
155
+ } else {
156
+ break;
157
+ }
158
+ }
159
+ return buf.slice(boundary).toString("utf-8");
160
+ }
161
+ async function extractCookiesCDP(chromeBinary, profileDir, chromeProfileDir, domains) {
162
+ const { chromium } = await import("playwright");
163
+ const port = await new Promise((resolve) => {
164
+ const srv = createServer();
165
+ srv.listen(0, () => {
166
+ const p = srv.address().port;
167
+ srv.close(() => resolve(p));
168
+ });
169
+ });
170
+ const tmpDir = mkdtempSync("/tmp/owletto-auth-");
171
+ mkdirSync(join(tmpDir, "Default"), { recursive: true });
172
+ const cookieSrc = join(profileDir, chromeProfileDir, "Cookies");
173
+ const journalSrc = join(profileDir, chromeProfileDir, "Cookies-journal");
174
+ if (!existsSync(cookieSrc)) {
175
+ rmSync(tmpDir, { recursive: true, force: true });
176
+ throw new Error(`No Cookies file found in Chrome profile: ${chromeProfileDir}`);
177
+ }
178
+ copyFileSync(cookieSrc, join(tmpDir, "Default/Cookies"));
179
+ if (existsSync(journalSrc)) {
180
+ copyFileSync(journalSrc, join(tmpDir, "Default/Cookies-journal"));
181
+ }
182
+ const chrome = spawn(
183
+ chromeBinary,
184
+ [
185
+ "--headless=new",
186
+ `--remote-debugging-port=${port}`,
187
+ "--remote-allow-origins=*",
188
+ "--user-data-dir=" + tmpDir,
189
+ "--no-first-run",
190
+ "--no-default-browser-check",
191
+ "--disable-background-networking",
192
+ "--disable-sync",
193
+ "--profile-directory=Default"
194
+ ],
195
+ { detached: true, stdio: "ignore" }
196
+ );
197
+ chrome.unref();
198
+ try {
199
+ for (let i = 0; i < 15; i++) {
200
+ try {
201
+ await new Promise((resolve, reject) => {
202
+ httpGet(`http://localhost:${port}/json/version`, (res) => {
203
+ let d = "";
204
+ res.on("data", (c) => d += c);
205
+ res.on("end", () => resolve());
206
+ }).on("error", reject);
207
+ });
208
+ break;
209
+ } catch {
210
+ await new Promise((r) => setTimeout(r, 1e3));
211
+ }
212
+ }
213
+ const browser = await chromium.connectOverCDP(`http://localhost:${port}`);
214
+ const context = browser.contexts()[0];
215
+ const page = context.pages()[0] || await context.newPage();
216
+ const primaryDomain = domains[0];
217
+ const url = primaryDomain.startsWith("http") ? primaryDomain : `https://${primaryDomain}`;
218
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 });
219
+ await page.waitForTimeout(2e3);
220
+ const cookieUrls = domains.map((d) => d.startsWith("http") ? d : `https://${d}`);
221
+ const cookies = await context.cookies(cookieUrls);
222
+ await browser.close();
223
+ return cookies;
224
+ } finally {
225
+ try {
226
+ process.kill(chrome.pid);
227
+ } catch {
228
+ }
229
+ await new Promise((r) => setTimeout(r, 500));
230
+ try {
231
+ rmSync(tmpDir, { recursive: true, force: true });
232
+ } catch {
233
+ }
234
+ }
235
+ }
236
+ function parseToolJson(text) {
237
+ if (!text.trim()) return {};
238
+ const normalized = text.trim().replace(/^```json\s*/, "").replace(/\s*```$/, "");
239
+ return JSON.parse(normalized);
240
+ }
241
+ function scoreAuthCookie(cookie) {
242
+ const name = cookie.name?.toLowerCase() ?? "";
243
+ if (!name) return Number.NEGATIVE_INFINITY;
244
+ if (/^(lang|li_theme|timezone|theme|locale|tz|visitor_id|guest_id)$/.test(name)) {
245
+ return Number.NEGATIVE_INFINITY;
246
+ }
247
+ let score = 0;
248
+ if (/(auth|token|session|sess|sid|jwt)/.test(name)) score += 100;
249
+ if (/_at$/.test(name)) score += 80;
250
+ if (cookie.httpOnly) score += 20;
251
+ if (cookie.secure) score += 10;
252
+ if ((cookie.value?.length ?? 0) >= 20) score += 5;
253
+ if ((cookie.expires ?? 0) > 0) score += 5;
254
+ return score;
255
+ }
256
+ function findLikelyAuthCookie(cookies) {
257
+ const sorted = [...cookies].sort((a, b) => scoreAuthCookie(b) - scoreAuthCookie(a));
258
+ const best = sorted[0];
259
+ return best && scoreAuthCookie(best) > 0 ? best : null;
260
+ }
261
+ async function resolveConnectorDomains(connectorKey, domainsOverride, cliProfile) {
262
+ if (domainsOverride) {
263
+ return domainsOverride.split(",").map((d) => d.trim());
264
+ }
265
+ const { resolveMcpEndpoint, mcpRpc } = await import("./mcp-3EIPOE4A.js");
266
+ const mcpUrl = resolveMcpEndpoint(cliProfile.config);
267
+ if (!mcpUrl) {
268
+ printText("No MCP URL configured. Use --domains to specify cookie domains manually.");
269
+ return null;
270
+ }
271
+ const result = await mcpRpc(mcpUrl, "tools/call", {
272
+ name: "manage_connections",
273
+ arguments: { action: "list_connector_definitions" }
274
+ });
275
+ const text = result?.content?.[0]?.text ?? "";
276
+ const parsed = parseToolJson(text);
277
+ const connectors = Array.isArray(parsed) ? parsed : parsed?.connector_definitions ?? parsed?.connectors ?? [];
278
+ const connector = connectors.find((c) => c.key === connectorKey);
279
+ if (!connector) {
280
+ printText(`Unknown connector: ${connectorKey}`);
281
+ return null;
282
+ }
283
+ const faviconDomain = connector.favicon_domain;
284
+ if (!faviconDomain) {
285
+ printText(
286
+ `Connector "${connectorKey}" has no favicon_domain. Use --domains to specify cookie domains manually.`
287
+ );
288
+ return null;
289
+ }
290
+ return [faviconDomain, `.${faviconDomain}`];
291
+ }
292
+ var browserAuth = defineCommand({
293
+ meta: {
294
+ name: "browser-auth",
295
+ description: "Capture auth cookies from your local Chrome browser for a connector"
296
+ },
297
+ args: {
298
+ connector: {
299
+ type: "string",
300
+ description: 'Connector key (e.g., "x")',
301
+ required: true
302
+ },
303
+ domains: {
304
+ type: "string",
305
+ description: 'Comma-separated cookie domains (overrides server lookup, e.g., "x.com,.x.com")'
306
+ },
307
+ chromeProfile: {
308
+ type: "string",
309
+ alias: "cp",
310
+ description: "Chrome profile name (interactive prompt if not specified)"
311
+ },
312
+ authProfileSlug: {
313
+ type: "string",
314
+ alias: "p",
315
+ description: "Browser auth profile slug to store cookies on"
316
+ },
317
+ check: {
318
+ type: "boolean",
319
+ description: "Check if stored cookies for a browser auth profile are still valid"
320
+ }
321
+ },
322
+ async run({ args }) {
323
+ const cliProfile = getProfile();
324
+ const connectorKey = args.connector;
325
+ if (args.check) {
326
+ if (!args.authProfileSlug) {
327
+ printText("--check requires --authProfileSlug");
328
+ process.exitCode = 1;
329
+ return;
330
+ }
331
+ const { resolveMcpEndpoint, mcpRpc } = await import("./mcp-3EIPOE4A.js");
332
+ const mcpUrl = resolveMcpEndpoint(cliProfile.config);
333
+ if (!mcpUrl) {
334
+ printText("No MCP URL configured.");
335
+ process.exitCode = 1;
336
+ return;
337
+ }
338
+ const result = await mcpRpc(mcpUrl, "tools/call", {
339
+ name: "manage_auth_profiles",
340
+ arguments: { action: "test_auth_profile", auth_profile_slug: args.authProfileSlug }
341
+ });
342
+ const text = result?.content?.[0]?.text ?? "";
343
+ const parsed = parseToolJson(text);
344
+ if (parsed?.error) {
345
+ printText(`Error: ${parsed.error}`);
346
+ process.exitCode = 1;
347
+ return;
348
+ }
349
+ if (parsed?.status === "ok") {
350
+ const expiresAt = parsed.expires_at ? new Date(parsed.expires_at) : null;
351
+ const daysLeft = expiresAt ? Math.floor((expiresAt.getTime() - Date.now()) / 864e5) : null;
352
+ printText(
353
+ `${parsed.auth_cookie_name || "Auth cookie"} valid${daysLeft !== null ? ` (expires in ${daysLeft} days)` : ""}.`
354
+ );
355
+ if (typeof parsed.cookie_count === "number") {
356
+ printText(`${parsed.cookie_count} cookies stored.`);
357
+ }
358
+ } else {
359
+ printText(parsed?.message || "Browser auth profile is not valid.");
360
+ process.exitCode = 1;
361
+ }
362
+ return;
363
+ }
364
+ const { binary, profileDir } = getChromePaths();
365
+ if (!existsSync(binary)) {
366
+ printText(`Chrome not found at ${binary}`);
367
+ process.exitCode = 1;
368
+ return;
369
+ }
370
+ const profiles = listProfiles(profileDir);
371
+ if (profiles.length === 0) {
372
+ printText("No Chrome profiles found");
373
+ process.exitCode = 1;
374
+ return;
375
+ }
376
+ let selectedProfile;
377
+ if (args.chromeProfile) {
378
+ const match = profiles.find(
379
+ (p) => p.name.toLowerCase() === args.chromeProfile.toLowerCase() || p.dir.toLowerCase() === args.chromeProfile.toLowerCase()
380
+ );
381
+ if (!match) {
382
+ printText(`Profile "${args.chromeProfile}" not found. Available:`);
383
+ for (const p of profiles) {
384
+ printText(` [${p.dir}] ${p.name} (${p.email})${p.isLastUsed ? " <- last used" : ""}`);
385
+ }
386
+ process.exitCode = 1;
387
+ return;
388
+ }
389
+ selectedProfile = match;
390
+ } else {
391
+ printText("Chrome Profiles:");
392
+ for (let i = 0; i < profiles.length; i++) {
393
+ const p = profiles[i];
394
+ printText(` [${i + 1}] ${p.name} (${p.email})${p.isLastUsed ? " <- last used" : ""}`);
395
+ }
396
+ const readline = await import("readline");
397
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
398
+ const answer = await new Promise((resolve) => {
399
+ rl.question("\nPick a profile: ", resolve);
400
+ });
401
+ rl.close();
402
+ const idx = parseInt(answer, 10) - 1;
403
+ if (Number.isNaN(idx) || idx < 0 || idx >= profiles.length) {
404
+ printText("Invalid selection");
405
+ process.exitCode = 1;
406
+ return;
407
+ }
408
+ selectedProfile = profiles[idx];
409
+ }
410
+ printText(`
411
+ Using profile: ${selectedProfile.name} (${selectedProfile.email})`);
412
+ printText("Resolving connector domains...");
413
+ const domains = await resolveConnectorDomains(connectorKey, args.domains, cliProfile);
414
+ if (!domains) {
415
+ process.exitCode = 1;
416
+ return;
417
+ }
418
+ printText(`Cookie domains: ${domains.join(", ")}`);
419
+ printText("Extracting cookies...");
420
+ const cookies = await extractCookies(binary, profileDir, selectedProfile.dir, domains);
421
+ if (cookies.length === 0) {
422
+ printText("No cookies found. Are you logged into the site in this Chrome profile?");
423
+ process.exitCode = 1;
424
+ return;
425
+ }
426
+ const authCookie = findLikelyAuthCookie(cookies);
427
+ if (!authCookie) {
428
+ printText("Warning: No likely auth cookie found. You may not be logged in.");
429
+ }
430
+ printText(`Captured ${cookies.length} cookies`);
431
+ if (args.authProfileSlug) {
432
+ printText("Saving cookies to auth profile...");
433
+ const { resolveMcpEndpoint, mcpRpc } = await import("./mcp-3EIPOE4A.js");
434
+ const mcpUrl = resolveMcpEndpoint(cliProfile.config);
435
+ if (!mcpUrl) {
436
+ printText("No MCP URL configured. Store cookies manually.");
437
+ } else {
438
+ const result = await mcpRpc(mcpUrl, "tools/call", {
439
+ name: "manage_auth_profiles",
440
+ arguments: {
441
+ action: "update_auth_profile",
442
+ auth_profile_slug: args.authProfileSlug,
443
+ auth_data: {
444
+ cookies,
445
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
446
+ captured_via: "cli",
447
+ browser_profile: selectedProfile.name
448
+ }
449
+ }
450
+ });
451
+ const responseText = result?.content?.[0]?.text ?? "";
452
+ const parsed = parseToolJson(responseText);
453
+ if (parsed?.error) {
454
+ printText(`Error: ${parsed.error}`);
455
+ } else {
456
+ printText(`Cookies stored on auth profile ${args.authProfileSlug}.`);
457
+ }
458
+ }
459
+ } else {
460
+ printText("\nCookies ready. To store on a browser auth profile:");
461
+ printText(
462
+ ` owletto browser-auth --connector ${connectorKey} --authProfileSlug <SLUG> --chromeProfile "${selectedProfile.name}"`
463
+ );
464
+ }
465
+ }
466
+ });
467
+ var browser_auth_default = browserAuth;
468
+ export {
469
+ browser_auth_default as default
470
+ };
@@ -1,21 +1,11 @@
1
- import {
2
- getProfile
3
- } from "./chunk-NG33L2S7.js";
4
1
  import {
5
2
  ApiError,
6
- ValidationError,
3
+ deriveMcpUrl,
7
4
  getUsableToken
8
- } from "./chunk-LPKFXYXP.js";
9
- import {
10
- defineCommand,
11
- isJson,
12
- printContextHeader,
13
- printJson,
14
- printText
15
- } from "./chunk-TMPMAYY4.js";
16
- import "./chunk-QGM4M3NI.js";
5
+ } from "./chunk-ANTW3P24.js";
17
6
 
18
7
  // src/commands/mcp.ts
8
+ var JSON_MCP_ACCEPT = "application/json";
19
9
  function uniqueStrings(values) {
20
10
  const seen = /* @__PURE__ */ new Set();
21
11
  const result = [];
@@ -62,19 +52,25 @@ async function fetchMcpWithFallback(mcpUrl, init) {
62
52
  }
63
53
  throw new Error(formatNetworkErrorMessage(lastError, candidates));
64
54
  }
65
- async function mcpRpc(apiUrl, method, params) {
66
- const body = {
67
- jsonrpc: "2.0",
68
- id: 1,
69
- method,
70
- params: params || {}
71
- };
55
+ function resolveMcpEndpoint(config) {
56
+ if (typeof config.mcpUrl === "string" && config.mcpUrl.trim().length > 0) {
57
+ return config.mcpUrl;
58
+ }
59
+ if (typeof config.apiUrl === "string" && config.apiUrl.trim().length > 0) {
60
+ return deriveMcpUrl(config.apiUrl);
61
+ }
62
+ return null;
63
+ }
64
+ async function mcpFetch(mcpUrl, body, sessionId) {
72
65
  const headers = { "Content-Type": "application/json" };
73
66
  const tokenResult = await getUsableToken();
74
67
  if (tokenResult) {
75
68
  headers.Authorization = `Bearer ${tokenResult.token}`;
76
69
  }
77
- const mcpUrl = `${apiUrl}/mcp`;
70
+ headers.Accept = JSON_MCP_ACCEPT;
71
+ if (sessionId) {
72
+ headers["mcp-session-id"] = sessionId;
73
+ }
78
74
  const { response: res, usedUrl } = await fetchMcpWithFallback(mcpUrl, {
79
75
  method: "POST",
80
76
  headers,
@@ -86,63 +82,57 @@ async function mcpRpc(apiUrl, method, params) {
86
82
  res.status
87
83
  );
88
84
  }
89
- const data = await res.json();
85
+ const raw = await res.text();
86
+ const data = raw.length > 0 ? JSON.parse(raw) : {};
87
+ return { data, usedUrl, response: res };
88
+ }
89
+ async function initializeMcpSession(mcpUrl) {
90
+ const { data, usedUrl, response } = await mcpFetch(mcpUrl, {
91
+ jsonrpc: "2.0",
92
+ id: "__init__",
93
+ method: "initialize",
94
+ params: {
95
+ protocolVersion: "2025-03-26",
96
+ capabilities: {},
97
+ clientInfo: { name: "owletto-cli", version: "1.0.0" }
98
+ }
99
+ });
90
100
  if (data.error) {
91
101
  throw new ApiError(`MCP error: ${data.error.message} (code ${data.error.code})`);
92
102
  }
93
- return data.result;
94
- }
95
- var tools = defineCommand({
96
- meta: { name: "tools", description: "List available MCP tools" },
97
- async run() {
98
- const profile = getProfile();
99
- printContextHeader(profile.name, "mcp tools");
100
- const apiUrl = profile.config.apiUrl;
101
- if (!apiUrl) throw new ValidationError("apiUrl required in profile");
102
- const result = await mcpRpc(apiUrl, "tools/list");
103
- const resultObj = result;
104
- const toolList = resultObj.tools ?? (Array.isArray(result) ? result : []);
105
- if (isJson()) {
106
- printJson({ tools: toolList }, profile);
107
- } else {
108
- for (const tool of toolList) {
109
- printText(` ${tool.name}${tool.description ? ` \u2014 ${tool.description}` : ""}`);
110
- }
111
- printText(`
112
- ${toolList.length} tool(s)`);
113
- }
103
+ const sessionId = response.headers.get("mcp-session-id");
104
+ if (!sessionId) {
105
+ throw new ApiError(`MCP initialize via ${usedUrl} did not return an mcp-session-id header`);
114
106
  }
115
- });
116
- var call = defineCommand({
117
- meta: { name: "call", description: "Call an MCP tool" },
118
- args: {
119
- tool: { type: "positional", description: "Tool name to call", required: true },
120
- params: { type: "string", description: "JSON parameters for the tool call" }
121
- },
122
- async run({ args }) {
123
- const profile = getProfile();
124
- printContextHeader(profile.name, `mcp call ${args.tool}`);
125
- const apiUrl = profile.config.apiUrl;
126
- if (!apiUrl) throw new ValidationError("apiUrl required in profile");
127
- const params = args.params ? JSON.parse(args.params) : {};
128
- const result = await mcpRpc(apiUrl, "tools/call", {
129
- name: args.tool,
130
- arguments: params
131
- });
132
- if (isJson()) {
133
- printJson({ result }, profile);
134
- } else {
135
- printText(JSON.stringify(result, null, 2));
136
- }
107
+ await mcpFetch(
108
+ mcpUrl,
109
+ {
110
+ jsonrpc: "2.0",
111
+ method: "notifications/initialized"
112
+ },
113
+ sessionId
114
+ );
115
+ return { sessionId, usedUrl };
116
+ }
117
+ async function mcpRpc(mcpUrl, method, params) {
118
+ const { sessionId } = await initializeMcpSession(mcpUrl);
119
+ const { data } = await mcpFetch(
120
+ mcpUrl,
121
+ {
122
+ jsonrpc: "2.0",
123
+ id: 1,
124
+ method,
125
+ params: params || {}
126
+ },
127
+ sessionId
128
+ );
129
+ if (data.error) {
130
+ throw new ApiError(`MCP error: ${data.error.message} (code ${data.error.code})`);
137
131
  }
138
- });
139
- var mcp_default = defineCommand({
140
- meta: {
141
- name: "mcp",
142
- description: "Interact with the MCP endpoint"
143
- },
144
- subCommands: { tools, call }
145
- });
132
+ return data.result;
133
+ }
134
+
146
135
  export {
147
- mcp_default as default
136
+ resolveMcpEndpoint,
137
+ mcpRpc
148
138
  };