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 +18 -17
- package/dist/browser-auth-5NM6CIN7.js +470 -0
- package/dist/{mcp-M2P7KK4C.js → chunk-6USNZB7E.js} +64 -74
- package/dist/{chunk-7LUE7OJK.js → chunk-RV6X4P62.js} +1 -1
- package/dist/{chunk-NG33L2S7.js → chunk-ZDMROF24.js} +2 -2
- package/dist/{connector-WVYEJLPU.js → connector-26KT2II4.js} +2 -2
- package/dist/{context-R36C4KTS.js → context-ERFLLCQK.js} +1 -1
- package/dist/{db-APU4VTB6.js → db-ETXQBGO2.js} +3 -3
- package/dist/{dev-EVH2ITQ7.js → dev-5HZQGSMK.js} +3 -3
- package/dist/{doctor-XONDHUND.js → doctor-QZEC2GKR.js} +2 -2
- package/dist/{embeddings-AH3BLVFN.js → embeddings-425LPWPY.js} +3 -3
- package/dist/{feed-QKU5LWE6.js → feed-SV333DHN.js} +3 -3
- package/dist/{feed-sync-LOT5ZNDF.js → feed-sync-UIXPYEJO.js} +62 -36
- package/dist/mcp-3EIPOE4A.js +10 -0
- package/dist/{openclaw-NN7EIOSB.js → openclaw-Q6VSZHKD.js} +180 -45
- package/dist/run-RZZFXJNG.js +65 -0
- package/dist/{server-5GYVVR2T.js → server-S66FQJBP.js} +3 -3
- package/dist/{version-6NLY47MV.js → version-4EIVIAAI.js} +1 -1
- package/dist/{worker-ETZD3I3B.js → worker-WPTOEUR2.js} +2 -2
- package/package.json +4 -4
- /package/dist/{chunk-LPKFXYXP.js → chunk-ANTW3P24.js} +0 -0
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
parseGlobalFlags
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZDMROF24.js";
|
|
5
5
|
import {
|
|
6
6
|
CliError
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
23
|
-
context: () => import("./context-
|
|
24
|
-
login: () => import("./openclaw-
|
|
25
|
-
token: () => import("./openclaw-
|
|
26
|
-
health: () => import("./openclaw-
|
|
27
|
-
configure: () => import("./openclaw-
|
|
28
|
-
db: () => import("./db-
|
|
29
|
-
dev: () => import("./dev-
|
|
30
|
-
doctor: () => import("./doctor-
|
|
31
|
-
server: () => import("./server-
|
|
32
|
-
worker: () => import("./worker-
|
|
33
|
-
embeddings: () => import("./embeddings-
|
|
34
|
-
connector: () => import("./connector-
|
|
35
|
-
feed: () => import("./feed-
|
|
36
|
-
|
|
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
|
-
|
|
3
|
+
deriveMcpUrl,
|
|
7
4
|
getUsableToken
|
|
8
|
-
} from "./chunk-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
136
|
+
resolveMcpEndpoint,
|
|
137
|
+
mcpRpc
|
|
148
138
|
};
|