@canaryai/cli 0.2.8 → 0.2.12
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/README.md +77 -92
- package/dist/chunk-CEW4BDXD.js +186 -0
- package/dist/chunk-CEW4BDXD.js.map +1 -0
- package/dist/chunk-ERSNYLMZ.js +229 -0
- package/dist/chunk-ERSNYLMZ.js.map +1 -0
- package/dist/{chunk-FK3EZADZ.js → chunk-MSMC6UXW.js} +2021 -873
- package/dist/chunk-MSMC6UXW.js.map +1 -0
- package/dist/{chunk-K2OB72B6.js → chunk-Q7WFBG5C.js} +2 -2
- package/dist/{debug-workflow-55G4Y6YT.js → debug-workflow-53ULOFJC.js} +57 -36
- package/dist/debug-workflow-53ULOFJC.js.map +1 -0
- package/dist/{docs-RPFT7ZJB.js → docs-BEE3LOCO.js} +2 -2
- package/dist/{feature-flag-2FDSKOVX.js → feature-flag-CYTDV4ZB.js} +3 -2
- package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-CYTDV4ZB.js.map} +1 -1
- package/dist/index.js +72 -137
- package/dist/index.js.map +1 -1
- package/dist/init-M6I3MG3D.js +146 -0
- package/dist/init-M6I3MG3D.js.map +1 -0
- package/dist/{issues-6ZDNDSD6.js → issues-NLM72HLU.js} +3 -2
- package/dist/{issues-6ZDNDSD6.js.map → issues-NLM72HLU.js.map} +1 -1
- package/dist/{knobs-MZRTYS3P.js → knobs-O35GAU5M.js} +3 -2
- package/dist/{knobs-MZRTYS3P.js.map → knobs-O35GAU5M.js.map} +1 -1
- package/dist/list-4K4EIGAT.js +57 -0
- package/dist/list-4K4EIGAT.js.map +1 -0
- package/dist/local-NHXXPHZ3.js +63 -0
- package/dist/local-NHXXPHZ3.js.map +1 -0
- package/dist/{local-browser-X7J27IGS.js → local-browser-VAZORCO3.js} +3 -3
- package/dist/login-ZLP64YQP.js +130 -0
- package/dist/login-ZLP64YQP.js.map +1 -0
- package/dist/mcp-ZF5G5DCB.js +377 -0
- package/dist/mcp-ZF5G5DCB.js.map +1 -0
- package/dist/{record-4OX7HXWQ.js → record-V6QKFFH3.js} +133 -72
- package/dist/record-V6QKFFH3.js.map +1 -0
- package/dist/{release-L4IXOHDF.js → release-7TI7EIGD.js} +8 -4
- package/dist/release-7TI7EIGD.js.map +1 -0
- package/dist/session-UGNJXRUW.js +819 -0
- package/dist/session-UGNJXRUW.js.map +1 -0
- package/dist/skill-ORWAPBDW.js +424 -0
- package/dist/skill-ORWAPBDW.js.map +1 -0
- package/dist/{src-I4EXB5OD.js → src-4VIDSK4A.js} +18 -2
- package/dist/start-E532F3BU.js +112 -0
- package/dist/start-E532F3BU.js.map +1 -0
- package/dist/workflow-HXIUXRFI.js +613 -0
- package/dist/workflow-HXIUXRFI.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-6WWHXWCS.js +0 -65
- package/dist/chunk-6WWHXWCS.js.map +0 -1
- package/dist/chunk-DXIAHB72.js +0 -340
- package/dist/chunk-DXIAHB72.js.map +0 -1
- package/dist/chunk-FK3EZADZ.js.map +0 -1
- package/dist/debug-workflow-55G4Y6YT.js.map +0 -1
- package/dist/mcp-4JVLADZL.js +0 -688
- package/dist/mcp-4JVLADZL.js.map +0 -1
- package/dist/record-4OX7HXWQ.js.map +0 -1
- package/dist/release-L4IXOHDF.js.map +0 -1
- /package/dist/{chunk-K2OB72B6.js.map → chunk-Q7WFBG5C.js.map} +0 -0
- /package/dist/{docs-RPFT7ZJB.js.map → docs-BEE3LOCO.js.map} +0 -0
- /package/dist/{local-browser-X7J27IGS.js.map → local-browser-VAZORCO3.js.map} +0 -0
- /package/dist/{src-I4EXB5OD.js.map → src-4VIDSK4A.js.map} +0 -0
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
downloadStorageState,
|
|
4
|
+
selectCredential
|
|
5
|
+
} from "./chunk-ERSNYLMZ.js";
|
|
6
|
+
import {
|
|
7
|
+
callTool,
|
|
8
|
+
createSession,
|
|
9
|
+
deleteAllSessions,
|
|
10
|
+
deleteSession,
|
|
11
|
+
getSession,
|
|
12
|
+
listSessions,
|
|
13
|
+
resolveTargetSession,
|
|
14
|
+
swapSessionContext
|
|
15
|
+
} from "./chunk-CEW4BDXD.js";
|
|
16
|
+
import {
|
|
17
|
+
getArgValue,
|
|
18
|
+
hasFlag,
|
|
19
|
+
resolveConfig
|
|
20
|
+
} from "./chunk-PWWQGYFG.js";
|
|
21
|
+
import {
|
|
22
|
+
BrowserToolExecutor,
|
|
23
|
+
PlaywrightClient,
|
|
24
|
+
dispatchBrowserTool
|
|
25
|
+
} from "./chunk-MSMC6UXW.js";
|
|
26
|
+
import "./chunk-XAA5VQ5N.js";
|
|
27
|
+
import {
|
|
28
|
+
consoleLogger
|
|
29
|
+
} from "./chunk-P5Z2Y5VV.js";
|
|
30
|
+
import "./chunk-VKVL7WBN.js";
|
|
31
|
+
|
|
32
|
+
// src/session/index.ts
|
|
33
|
+
import process2 from "process";
|
|
34
|
+
|
|
35
|
+
// src/session/credentials.ts
|
|
36
|
+
async function resolveCredential(argv, credentialArg) {
|
|
37
|
+
const { apiUrl, token } = await resolveConfig(argv);
|
|
38
|
+
const credential = await selectCredential(apiUrl, token, credentialArg);
|
|
39
|
+
if (!credential) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
console.log(`Fetching credential "${credential.name}"...`);
|
|
43
|
+
let storageStatePath;
|
|
44
|
+
if (credential.storageStateS3Key) {
|
|
45
|
+
storageStatePath = await downloadStorageState({
|
|
46
|
+
apiUrl,
|
|
47
|
+
token,
|
|
48
|
+
propertyId: credential.propertyId,
|
|
49
|
+
credentialId: credential.id,
|
|
50
|
+
prefix: "session-ss"
|
|
51
|
+
});
|
|
52
|
+
if (storageStatePath) {
|
|
53
|
+
console.log("Downloaded storage state.");
|
|
54
|
+
} else {
|
|
55
|
+
console.warn("Warning: Could not download storage state. Starting without auth.");
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log("Credential has no storage state. Starting without auth.");
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
storageStatePath,
|
|
62
|
+
credentialName: credential.name
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/session/daemon.ts
|
|
67
|
+
import { createServer } from "http";
|
|
68
|
+
import fs from "fs/promises";
|
|
69
|
+
import path from "path";
|
|
70
|
+
import os from "os";
|
|
71
|
+
var PIDFILE_DIR = path.join(os.homedir(), ".config", "canary-cli");
|
|
72
|
+
var PIDFILE_PATH = path.join(PIDFILE_DIR, "daemon.json");
|
|
73
|
+
var IDLE_TIMEOUT_MS = 6e4;
|
|
74
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
75
|
+
var nextId = 1;
|
|
76
|
+
var idleTimer = null;
|
|
77
|
+
function resetIdleTimer() {
|
|
78
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
79
|
+
if (sessions.size === 0) {
|
|
80
|
+
idleTimer = setTimeout(async () => {
|
|
81
|
+
if (sessions.size === 0) {
|
|
82
|
+
await removePidfile();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
}, IDLE_TIMEOUT_MS);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function generateId() {
|
|
89
|
+
return `s${nextId++}`;
|
|
90
|
+
}
|
|
91
|
+
async function getSessionUrl(session) {
|
|
92
|
+
try {
|
|
93
|
+
return await session.client.getCurrentUrl();
|
|
94
|
+
} catch {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function toSessionInfo(s) {
|
|
99
|
+
return {
|
|
100
|
+
id: s.id,
|
|
101
|
+
name: s.name,
|
|
102
|
+
mode: s.mode,
|
|
103
|
+
credentialName: s.credentialName,
|
|
104
|
+
url: await getSessionUrl(s),
|
|
105
|
+
startedAt: s.startedAt
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function readBody(req) {
|
|
109
|
+
const chunks = [];
|
|
110
|
+
for await (const chunk of req) {
|
|
111
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
112
|
+
}
|
|
113
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
114
|
+
}
|
|
115
|
+
function parseJson(body) {
|
|
116
|
+
if (!body.trim()) return {};
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(body);
|
|
119
|
+
} catch {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function json(res, status, data) {
|
|
124
|
+
const body = JSON.stringify(data);
|
|
125
|
+
res.writeHead(status, {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"Content-Length": Buffer.byteLength(body)
|
|
128
|
+
});
|
|
129
|
+
res.end(body);
|
|
130
|
+
}
|
|
131
|
+
function parseRoute(url) {
|
|
132
|
+
const [pathname] = url.split("?");
|
|
133
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
134
|
+
return { path: segments, query: {} };
|
|
135
|
+
}
|
|
136
|
+
async function handleHealth(res) {
|
|
137
|
+
json(res, 200, { ok: true, sessions: sessions.size });
|
|
138
|
+
}
|
|
139
|
+
async function handleCreateSession(body, res) {
|
|
140
|
+
const params = parseJson(body);
|
|
141
|
+
const id = generateId();
|
|
142
|
+
const name = params.name ?? id;
|
|
143
|
+
const mode = params.headless ? "headless" : "headed";
|
|
144
|
+
try {
|
|
145
|
+
const client = new PlaywrightClient({ logger: consoleLogger });
|
|
146
|
+
await client.connect({
|
|
147
|
+
browserMode: mode,
|
|
148
|
+
storageStatePath: params.storageStatePath
|
|
149
|
+
});
|
|
150
|
+
const executor = new BrowserToolExecutor(client, {
|
|
151
|
+
autoSnapshotAfterAction: true,
|
|
152
|
+
includeScreenshotWithSnapshot: true,
|
|
153
|
+
clickValidation: true,
|
|
154
|
+
logger: consoleLogger
|
|
155
|
+
});
|
|
156
|
+
const session = {
|
|
157
|
+
id,
|
|
158
|
+
name,
|
|
159
|
+
mode,
|
|
160
|
+
credentialName: params.credentialName,
|
|
161
|
+
client,
|
|
162
|
+
executor,
|
|
163
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
164
|
+
};
|
|
165
|
+
sessions.set(id, session);
|
|
166
|
+
if (idleTimer) {
|
|
167
|
+
clearTimeout(idleTimer);
|
|
168
|
+
idleTimer = null;
|
|
169
|
+
}
|
|
170
|
+
const result = {
|
|
171
|
+
ok: true,
|
|
172
|
+
data: await toSessionInfo(session)
|
|
173
|
+
};
|
|
174
|
+
json(res, 201, result);
|
|
175
|
+
if (params.url) {
|
|
176
|
+
client.navigate(params.url).catch((err) => {
|
|
177
|
+
consoleLogger.warn(`Initial navigation to ${params.url} failed: ${err}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
const result = {
|
|
182
|
+
ok: false,
|
|
183
|
+
error: err instanceof Error ? err.message : String(err)
|
|
184
|
+
};
|
|
185
|
+
json(res, 500, result);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function handleListSessions(res) {
|
|
189
|
+
const list = await Promise.all(Array.from(sessions.values()).map(toSessionInfo));
|
|
190
|
+
json(res, 200, { ok: true, data: list });
|
|
191
|
+
}
|
|
192
|
+
async function handleGetSession(sessionId, res) {
|
|
193
|
+
const session = sessions.get(sessionId);
|
|
194
|
+
if (!session) {
|
|
195
|
+
json(res, 404, { ok: false, error: `Session "${sessionId}" not found` });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
json(res, 200, { ok: true, data: await toSessionInfo(session) });
|
|
199
|
+
}
|
|
200
|
+
async function handleDeleteSession(sessionId, res) {
|
|
201
|
+
const session = sessions.get(sessionId);
|
|
202
|
+
if (!session) {
|
|
203
|
+
json(res, 404, { ok: false, error: `Session "${sessionId}" not found` });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
await session.client.disconnect();
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
sessions.delete(sessionId);
|
|
211
|
+
json(res, 200, { ok: true });
|
|
212
|
+
resetIdleTimer();
|
|
213
|
+
}
|
|
214
|
+
async function handleDeleteAllSessions(res) {
|
|
215
|
+
for (const session of sessions.values()) {
|
|
216
|
+
try {
|
|
217
|
+
await session.client.disconnect();
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
sessions.clear();
|
|
222
|
+
json(res, 200, { ok: true });
|
|
223
|
+
resetIdleTimer();
|
|
224
|
+
}
|
|
225
|
+
async function handleSwapContext(sessionId, body, res) {
|
|
226
|
+
const session = sessions.get(sessionId);
|
|
227
|
+
if (!session) {
|
|
228
|
+
json(res, 404, { ok: false, error: `Session "${sessionId}" not found` });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const params = parseJson(body);
|
|
232
|
+
try {
|
|
233
|
+
await session.client.swapContext({
|
|
234
|
+
storageStatePath: params.storageStatePath
|
|
235
|
+
});
|
|
236
|
+
if (params.credentialName) {
|
|
237
|
+
session.credentialName = params.credentialName;
|
|
238
|
+
}
|
|
239
|
+
json(res, 200, { ok: true, data: await toSessionInfo(session) });
|
|
240
|
+
} catch (err) {
|
|
241
|
+
json(res, 500, {
|
|
242
|
+
ok: false,
|
|
243
|
+
error: err instanceof Error ? err.message : String(err)
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function handleGetStorageState(sessionId, res) {
|
|
248
|
+
const session = sessions.get(sessionId);
|
|
249
|
+
if (!session) {
|
|
250
|
+
json(res, 404, { ok: false, error: `Session "${sessionId}" not found` });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const storageState = await session.client.getStorageState();
|
|
255
|
+
json(res, 200, { ok: true, data: storageState });
|
|
256
|
+
} catch (err) {
|
|
257
|
+
json(res, 500, {
|
|
258
|
+
ok: false,
|
|
259
|
+
error: err instanceof Error ? err.message : String(err)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function handleToolCall(sessionId, toolName, body, res) {
|
|
264
|
+
const session = sessions.get(sessionId);
|
|
265
|
+
if (!session) {
|
|
266
|
+
json(res, 404, { ok: false, error: `Session "${sessionId}" not found` });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const args = parseJson(body);
|
|
270
|
+
const fullToolName = toolName.startsWith("browser_") ? toolName : `browser_${toolName}`;
|
|
271
|
+
try {
|
|
272
|
+
const result = await dispatchBrowserTool(session.executor, fullToolName, args);
|
|
273
|
+
if (typeof result === "string") {
|
|
274
|
+
json(res, 200, { ok: true, text: result });
|
|
275
|
+
} else {
|
|
276
|
+
json(res, 200, {
|
|
277
|
+
ok: true,
|
|
278
|
+
text: result.text,
|
|
279
|
+
images: result.images
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
json(res, 500, {
|
|
284
|
+
ok: false,
|
|
285
|
+
error: err instanceof Error ? err.message : String(err)
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function handleRequest(req, res) {
|
|
290
|
+
const method = req.method ?? "GET";
|
|
291
|
+
const { path: segments } = parseRoute(req.url ?? "/");
|
|
292
|
+
const body = method === "POST" || method === "DELETE" ? await readBody(req) : "";
|
|
293
|
+
try {
|
|
294
|
+
if (method === "GET" && segments[0] === "health") {
|
|
295
|
+
await handleHealth(res);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (method === "POST" && segments[0] === "sessions" && segments.length === 1) {
|
|
299
|
+
await handleCreateSession(body, res);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (method === "GET" && segments[0] === "sessions" && segments.length === 1) {
|
|
303
|
+
await handleListSessions(res);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (method === "GET" && segments[0] === "sessions" && segments.length === 2) {
|
|
307
|
+
await handleGetSession(segments[1], res);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (method === "DELETE" && segments[0] === "sessions" && segments.length === 1) {
|
|
311
|
+
await handleDeleteAllSessions(res);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (method === "DELETE" && segments[0] === "sessions" && segments.length === 2) {
|
|
315
|
+
await handleDeleteSession(segments[1], res);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (method === "POST" && segments[0] === "sessions" && segments[2] === "swap-context" && segments.length === 3) {
|
|
319
|
+
await handleSwapContext(segments[1], body, res);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (method === "GET" && segments[0] === "sessions" && segments[2] === "storage-state" && segments.length === 3) {
|
|
323
|
+
await handleGetStorageState(segments[1], res);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (method === "POST" && segments[0] === "sessions" && segments[2] === "tools" && segments.length === 4) {
|
|
327
|
+
await handleToolCall(segments[1], segments[3], body, res);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
json(res, 404, { ok: false, error: "Not found" });
|
|
331
|
+
} catch (err) {
|
|
332
|
+
json(res, 500, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function writePidfile(port) {
|
|
336
|
+
await fs.mkdir(PIDFILE_DIR, { recursive: true, mode: 448 });
|
|
337
|
+
const state = {
|
|
338
|
+
pid: process.pid,
|
|
339
|
+
port,
|
|
340
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
341
|
+
};
|
|
342
|
+
await fs.writeFile(PIDFILE_PATH, JSON.stringify(state, null, 2), { mode: 384 });
|
|
343
|
+
}
|
|
344
|
+
async function removePidfile() {
|
|
345
|
+
try {
|
|
346
|
+
await fs.unlink(PIDFILE_PATH);
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function cleanup() {
|
|
351
|
+
for (const session of sessions.values()) {
|
|
352
|
+
try {
|
|
353
|
+
await session.client.disconnect();
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
sessions.clear();
|
|
358
|
+
await removePidfile();
|
|
359
|
+
}
|
|
360
|
+
async function startDaemon() {
|
|
361
|
+
const server = createServer(handleRequest);
|
|
362
|
+
server.keepAliveTimeout = 0;
|
|
363
|
+
return new Promise((resolve, reject) => {
|
|
364
|
+
server.listen(0, "127.0.0.1", async () => {
|
|
365
|
+
const addr = server.address();
|
|
366
|
+
if (!addr || typeof addr === "string") {
|
|
367
|
+
reject(new Error("Failed to get server address"));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const port = addr.port;
|
|
371
|
+
await writePidfile(port);
|
|
372
|
+
resetIdleTimer();
|
|
373
|
+
process.on("SIGINT", async () => {
|
|
374
|
+
await cleanup();
|
|
375
|
+
process.exit(0);
|
|
376
|
+
});
|
|
377
|
+
process.on("SIGTERM", async () => {
|
|
378
|
+
await cleanup();
|
|
379
|
+
process.exit(0);
|
|
380
|
+
});
|
|
381
|
+
process.on("exit", () => {
|
|
382
|
+
removePidfile().catch(() => {
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
resolve({ port });
|
|
386
|
+
});
|
|
387
|
+
server.on("error", reject);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async function runDaemon() {
|
|
391
|
+
const { port } = await startDaemon();
|
|
392
|
+
process.stdout.write(`DAEMON_READY:${port}
|
|
393
|
+
`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/session/index.ts
|
|
397
|
+
function formatUptime(startedAt) {
|
|
398
|
+
const seconds = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1e3);
|
|
399
|
+
if (seconds < 60) return `${seconds}s`;
|
|
400
|
+
const minutes = Math.floor(seconds / 60);
|
|
401
|
+
const secs = seconds % 60;
|
|
402
|
+
if (minutes < 60) return `${minutes}m ${secs}s`;
|
|
403
|
+
const hours = Math.floor(minutes / 60);
|
|
404
|
+
const mins = minutes % 60;
|
|
405
|
+
return `${hours}h ${mins}m`;
|
|
406
|
+
}
|
|
407
|
+
function printSessionTable(sessions2) {
|
|
408
|
+
if (sessions2.length === 0) {
|
|
409
|
+
console.log("No active sessions.");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const header = ["ID", "NAME", "MODE", "CREDENTIAL", "URL", "UPTIME"];
|
|
413
|
+
const rows = sessions2.map((s) => [
|
|
414
|
+
s.id,
|
|
415
|
+
s.name,
|
|
416
|
+
s.mode,
|
|
417
|
+
s.credentialName ?? "(none)",
|
|
418
|
+
s.url ?? "(none)",
|
|
419
|
+
formatUptime(s.startedAt)
|
|
420
|
+
]);
|
|
421
|
+
const widths = header.map(
|
|
422
|
+
(h, i) => Math.max(h.length, ...rows.map((r) => r[i].length))
|
|
423
|
+
);
|
|
424
|
+
const pad = (str, width) => str.padEnd(width);
|
|
425
|
+
console.log(header.map((h, i) => pad(h, widths[i])).join(" "));
|
|
426
|
+
for (const row of rows) {
|
|
427
|
+
console.log(row.map((cell, i) => pad(cell, widths[i])).join(" "));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function printToolResult(result, jsonMode) {
|
|
431
|
+
if (jsonMode) {
|
|
432
|
+
console.log(JSON.stringify(result, null, 2));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (!result.ok) {
|
|
436
|
+
console.error(`Error: ${result.error}`);
|
|
437
|
+
process2.exitCode = 1;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (result.text) {
|
|
441
|
+
console.log(result.text);
|
|
442
|
+
}
|
|
443
|
+
if (result.images?.length) {
|
|
444
|
+
console.log(`[${result.images.length} image(s) captured]`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function handleStart(argv) {
|
|
448
|
+
const headless = hasFlag(argv, "--headless");
|
|
449
|
+
const name = getArgValue(argv, "--name");
|
|
450
|
+
const url = getArgValue(argv, "--url");
|
|
451
|
+
const storageStatePath = getArgValue(argv, "--storage-state");
|
|
452
|
+
const jsonMode = hasFlag(argv, "--json");
|
|
453
|
+
const credentialIdx = argv.indexOf("--credential");
|
|
454
|
+
let resolvedCred = null;
|
|
455
|
+
if (credentialIdx !== -1) {
|
|
456
|
+
const nextArg = argv[credentialIdx + 1];
|
|
457
|
+
const credentialArg = nextArg && !nextArg.startsWith("--") ? nextArg : void 0;
|
|
458
|
+
resolvedCred = await resolveCredential(argv, credentialArg);
|
|
459
|
+
if (!resolvedCred) {
|
|
460
|
+
process2.exitCode = 1;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const result = await createSession({
|
|
465
|
+
headless,
|
|
466
|
+
name,
|
|
467
|
+
url,
|
|
468
|
+
storageStatePath: resolvedCred?.storageStatePath ?? storageStatePath,
|
|
469
|
+
credentialName: resolvedCred?.credentialName
|
|
470
|
+
});
|
|
471
|
+
if (jsonMode) {
|
|
472
|
+
console.log(JSON.stringify(result, null, 2));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (!result.ok) {
|
|
476
|
+
console.error(`Error: ${result.error}`);
|
|
477
|
+
process2.exitCode = 1;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const session = result.data;
|
|
481
|
+
console.log(`Session "${session.id}" started (${session.mode})`);
|
|
482
|
+
if (session.credentialName) {
|
|
483
|
+
console.log(` Credential: ${session.credentialName}`);
|
|
484
|
+
}
|
|
485
|
+
const displayUrl = url ?? session.url;
|
|
486
|
+
if (displayUrl) {
|
|
487
|
+
console.log(` URL: ${displayUrl}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function handleList(argv) {
|
|
491
|
+
const jsonMode = hasFlag(argv, "--json");
|
|
492
|
+
const result = await listSessions();
|
|
493
|
+
if (jsonMode) {
|
|
494
|
+
console.log(JSON.stringify(result, null, 2));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (!result.ok) {
|
|
498
|
+
console.error(`Error: ${result.error}`);
|
|
499
|
+
process2.exitCode = 1;
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
printSessionTable(result.data ?? []);
|
|
503
|
+
}
|
|
504
|
+
async function handleStatus(argv) {
|
|
505
|
+
const sessionId = getArgValue(argv, "--session") || (argv[0] && !argv[0].startsWith("--") ? argv[0] : void 0);
|
|
506
|
+
const jsonMode = hasFlag(argv, "--json");
|
|
507
|
+
try {
|
|
508
|
+
const target = await resolveTargetSession(sessionId);
|
|
509
|
+
const result = await getSession(target.id);
|
|
510
|
+
if (jsonMode) {
|
|
511
|
+
console.log(JSON.stringify(result, null, 2));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (!result.ok || !result.data) {
|
|
515
|
+
console.error(`Error: ${result.error}`);
|
|
516
|
+
process2.exitCode = 1;
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const s = result.data;
|
|
520
|
+
console.log(`Session: ${s.id} (${s.name})`);
|
|
521
|
+
console.log(` Mode: ${s.mode}`);
|
|
522
|
+
console.log(` Credential: ${s.credentialName ?? "(none)"}`);
|
|
523
|
+
console.log(` URL: ${s.url ?? "(none)"}`);
|
|
524
|
+
console.log(` Uptime: ${formatUptime(s.startedAt)}`);
|
|
525
|
+
console.log(` Started: ${s.startedAt}`);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
528
|
+
process2.exitCode = 1;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async function handleStop(argv) {
|
|
532
|
+
const jsonMode = hasFlag(argv, "--json");
|
|
533
|
+
if (hasFlag(argv, "--all")) {
|
|
534
|
+
const result = await deleteAllSessions();
|
|
535
|
+
if (jsonMode) {
|
|
536
|
+
console.log(JSON.stringify(result, null, 2));
|
|
537
|
+
} else if (result.ok) {
|
|
538
|
+
console.log("All sessions stopped.");
|
|
539
|
+
} else {
|
|
540
|
+
console.error(`Error: ${result.error}`);
|
|
541
|
+
process2.exitCode = 1;
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const sessionId = getArgValue(argv, "--session") || (argv[0] && !argv[0].startsWith("--") ? argv[0] : void 0);
|
|
546
|
+
try {
|
|
547
|
+
const target = await resolveTargetSession(sessionId);
|
|
548
|
+
const result = await deleteSession(target.id);
|
|
549
|
+
if (jsonMode) {
|
|
550
|
+
console.log(JSON.stringify(result, null, 2));
|
|
551
|
+
} else if (result.ok) {
|
|
552
|
+
console.log(`Session "${target.id}" stopped.`);
|
|
553
|
+
} else {
|
|
554
|
+
console.error(`Error: ${result.error}`);
|
|
555
|
+
process2.exitCode = 1;
|
|
556
|
+
}
|
|
557
|
+
} catch (err) {
|
|
558
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
559
|
+
process2.exitCode = 1;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function handleLoadCredential(argv) {
|
|
563
|
+
const jsonMode = hasFlag(argv, "--json");
|
|
564
|
+
const sessionId = getArgValue(argv, "--session") || (argv[0] && !argv[0].startsWith("--") ? argv[0] : void 0);
|
|
565
|
+
let target;
|
|
566
|
+
try {
|
|
567
|
+
target = await resolveTargetSession(sessionId);
|
|
568
|
+
} catch (err) {
|
|
569
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
570
|
+
process2.exitCode = 1;
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const credentialIdx = argv.indexOf("--credential");
|
|
574
|
+
const nextArg = credentialIdx !== -1 ? argv[credentialIdx + 1] : void 0;
|
|
575
|
+
const credentialArg = nextArg && !nextArg.startsWith("--") ? nextArg : void 0;
|
|
576
|
+
const storageStatePath = getArgValue(argv, "--storage-state");
|
|
577
|
+
let resolvedStorageStatePath;
|
|
578
|
+
let credentialName;
|
|
579
|
+
if (storageStatePath) {
|
|
580
|
+
resolvedStorageStatePath = storageStatePath;
|
|
581
|
+
credentialName = "(local storage state)";
|
|
582
|
+
} else {
|
|
583
|
+
const resolvedCred = await resolveCredential(argv, credentialArg);
|
|
584
|
+
if (!resolvedCred) {
|
|
585
|
+
process2.exitCode = 1;
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
resolvedStorageStatePath = resolvedCred.storageStatePath;
|
|
589
|
+
credentialName = resolvedCred.credentialName;
|
|
590
|
+
}
|
|
591
|
+
const result = await swapSessionContext(target.id, {
|
|
592
|
+
storageStatePath: resolvedStorageStatePath,
|
|
593
|
+
credentialName
|
|
594
|
+
});
|
|
595
|
+
if (jsonMode) {
|
|
596
|
+
console.log(JSON.stringify(result, null, 2));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (!result.ok) {
|
|
600
|
+
console.error(`Error: ${result.error}`);
|
|
601
|
+
process2.exitCode = 1;
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const session = result.data;
|
|
605
|
+
console.log(`Credential loaded into session "${session.id}".`);
|
|
606
|
+
if (session.credentialName) {
|
|
607
|
+
console.log(` Credential: ${session.credentialName}`);
|
|
608
|
+
}
|
|
609
|
+
if (session.url) {
|
|
610
|
+
console.log(` URL: ${session.url}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function handleToolCommand(toolName, argv) {
|
|
614
|
+
const sessionIdOrName = getArgValue(argv, "--session");
|
|
615
|
+
const jsonMode = hasFlag(argv, "--json");
|
|
616
|
+
try {
|
|
617
|
+
const target = await resolveTargetSession(sessionIdOrName);
|
|
618
|
+
const args = buildToolArgs(toolName, argv);
|
|
619
|
+
const result = await callTool(target.id, toolName, args);
|
|
620
|
+
printToolResult(result, jsonMode);
|
|
621
|
+
} catch (err) {
|
|
622
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
623
|
+
process2.exitCode = 1;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function buildToolArgs(toolName, argv) {
|
|
627
|
+
const args = {};
|
|
628
|
+
switch (toolName) {
|
|
629
|
+
case "navigate":
|
|
630
|
+
args.url = getArgValue(argv, "--url");
|
|
631
|
+
break;
|
|
632
|
+
case "snapshot":
|
|
633
|
+
args.search = getArgValue(argv, "--search");
|
|
634
|
+
args.mode = getArgValue(argv, "--mode") ?? "tree";
|
|
635
|
+
break;
|
|
636
|
+
case "screenshot":
|
|
637
|
+
args.fullPage = hasFlag(argv, "--full-page");
|
|
638
|
+
args.label = getArgValue(argv, "--label");
|
|
639
|
+
args.returnImage = true;
|
|
640
|
+
break;
|
|
641
|
+
case "evaluate":
|
|
642
|
+
args.function = getArgValue(argv, "--js");
|
|
643
|
+
break;
|
|
644
|
+
case "click": {
|
|
645
|
+
const ref = getArgValue(argv, "--ref");
|
|
646
|
+
const x = getArgValue(argv, "--x");
|
|
647
|
+
const y = getArgValue(argv, "--y");
|
|
648
|
+
if (ref) args.ref = ref;
|
|
649
|
+
if (x) args.x = parseInt(x, 10);
|
|
650
|
+
if (y) args.y = parseInt(y, 10);
|
|
651
|
+
args.element = getArgValue(argv, "--element") ?? "";
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
case "type":
|
|
655
|
+
args.ref = getArgValue(argv, "--ref");
|
|
656
|
+
args.text = getArgValue(argv, "--text");
|
|
657
|
+
args.element = getArgValue(argv, "--element") ?? "";
|
|
658
|
+
args.submit = hasFlag(argv, "--submit");
|
|
659
|
+
break;
|
|
660
|
+
case "fill_form": {
|
|
661
|
+
const fieldsStr = getArgValue(argv, "--fields");
|
|
662
|
+
if (fieldsStr) {
|
|
663
|
+
try {
|
|
664
|
+
args.fields = JSON.parse(fieldsStr);
|
|
665
|
+
} catch {
|
|
666
|
+
console.error("Error: --fields must be valid JSON");
|
|
667
|
+
process2.exitCode = 1;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
case "select_option":
|
|
673
|
+
args.ref = getArgValue(argv, "--ref");
|
|
674
|
+
args.value = getArgValue(argv, "--value");
|
|
675
|
+
args.element = getArgValue(argv, "--element") ?? "";
|
|
676
|
+
break;
|
|
677
|
+
case "press_key":
|
|
678
|
+
args.key = getArgValue(argv, "--key");
|
|
679
|
+
break;
|
|
680
|
+
case "scroll":
|
|
681
|
+
args.direction = getArgValue(argv, "--direction") ?? "down";
|
|
682
|
+
{
|
|
683
|
+
const amount = getArgValue(argv, "--amount");
|
|
684
|
+
if (amount) args.amount = parseInt(amount, 10);
|
|
685
|
+
}
|
|
686
|
+
break;
|
|
687
|
+
case "hover":
|
|
688
|
+
args.ref = getArgValue(argv, "--ref");
|
|
689
|
+
args.element = getArgValue(argv, "--element") ?? "";
|
|
690
|
+
break;
|
|
691
|
+
case "tabs": {
|
|
692
|
+
args.action = getArgValue(argv, "--action") ?? "list";
|
|
693
|
+
const index = getArgValue(argv, "--index");
|
|
694
|
+
if (index) args.index = parseInt(index, 10);
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case "wait_for": {
|
|
698
|
+
args.text = getArgValue(argv, "--text");
|
|
699
|
+
const timeout = getArgValue(argv, "--timeout");
|
|
700
|
+
if (timeout) args.timeout = parseInt(timeout, 10);
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
case "console_messages":
|
|
704
|
+
args.onlyErrors = hasFlag(argv, "--only-errors");
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
return args;
|
|
708
|
+
}
|
|
709
|
+
var TOOL_ALIASES = {
|
|
710
|
+
navigate: "navigate",
|
|
711
|
+
back: "navigate_back",
|
|
712
|
+
snapshot: "snapshot",
|
|
713
|
+
screenshot: "screenshot",
|
|
714
|
+
evaluate: "evaluate",
|
|
715
|
+
click: "click",
|
|
716
|
+
type: "type",
|
|
717
|
+
fill: "fill_form",
|
|
718
|
+
select: "select_option",
|
|
719
|
+
press: "press_key",
|
|
720
|
+
scroll: "scroll",
|
|
721
|
+
hover: "hover",
|
|
722
|
+
drag: "drag",
|
|
723
|
+
tabs: "tabs",
|
|
724
|
+
wait: "wait_for",
|
|
725
|
+
console: "console_messages",
|
|
726
|
+
network: "network_requests"
|
|
727
|
+
};
|
|
728
|
+
function printHelp() {
|
|
729
|
+
console.log(
|
|
730
|
+
[
|
|
731
|
+
"Usage: canary session <subcommand> [options]",
|
|
732
|
+
"",
|
|
733
|
+
"Session lifecycle:",
|
|
734
|
+
" start [options] Start a new browser session",
|
|
735
|
+
" list List active sessions",
|
|
736
|
+
" status [<id>] Show session details",
|
|
737
|
+
" stop [<id>] Stop a session (or --all)",
|
|
738
|
+
" load-credential Load a credential into a running session",
|
|
739
|
+
"",
|
|
740
|
+
"Browser actions:",
|
|
741
|
+
" navigate --url <url> Navigate to URL",
|
|
742
|
+
" back Navigate back",
|
|
743
|
+
" snapshot [--search <text>] [--mode tree|screenshot|both]",
|
|
744
|
+
" screenshot [--full-page] [--label <text>]",
|
|
745
|
+
" evaluate --js <expression> Evaluate JavaScript",
|
|
746
|
+
" click --ref <ref> [--element <desc>]",
|
|
747
|
+
" click --x <n> --y <n> [--element <desc>]",
|
|
748
|
+
" type --ref <ref> --text <text> [--submit]",
|
|
749
|
+
` fill --fields '[{"ref":"e1","value":"test"}]'`,
|
|
750
|
+
" select --ref <ref> --value <val> [--element <desc>]",
|
|
751
|
+
" press --key <key> Press keyboard key",
|
|
752
|
+
" scroll --direction <dir> [--amount <n>]",
|
|
753
|
+
" hover --ref <ref> [--element <desc>]",
|
|
754
|
+
" tabs [--action list|new|close|select] [--index <n>]",
|
|
755
|
+
" wait --text <text> [--timeout <ms>]",
|
|
756
|
+
" console [--only-errors] Show console messages",
|
|
757
|
+
" network Show network requests",
|
|
758
|
+
"",
|
|
759
|
+
"Start options:",
|
|
760
|
+
" --credential [<name|id>] Use a credential (interactive if no value)",
|
|
761
|
+
" --storage-state <path> Use a local storage state file",
|
|
762
|
+
" --headless Run browser without visible UI",
|
|
763
|
+
" --url <url> Navigate to URL after launch",
|
|
764
|
+
" --name <label> Name the session",
|
|
765
|
+
"",
|
|
766
|
+
"Load-credential options:",
|
|
767
|
+
" --credential [<name|id>] Use a credential (interactive if no value)",
|
|
768
|
+
" --storage-state <path> Use a local storage state file",
|
|
769
|
+
"",
|
|
770
|
+
"Common options:",
|
|
771
|
+
" --session <id|name> Target a specific session (when multiple)",
|
|
772
|
+
" --json Output raw JSON",
|
|
773
|
+
" --env <env> Environment for credential resolution",
|
|
774
|
+
" -h, --help Show this help"
|
|
775
|
+
].join("\n")
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
async function runSession(argv) {
|
|
779
|
+
const [subcommand, ...rest] = argv;
|
|
780
|
+
if (!subcommand || subcommand === "help" || hasFlag(argv, "-h", "--help")) {
|
|
781
|
+
printHelp();
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (subcommand === "daemon") {
|
|
785
|
+
await runDaemon();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
switch (subcommand) {
|
|
789
|
+
case "start":
|
|
790
|
+
await handleStart(rest);
|
|
791
|
+
break;
|
|
792
|
+
case "list":
|
|
793
|
+
await handleList(rest);
|
|
794
|
+
break;
|
|
795
|
+
case "status":
|
|
796
|
+
await handleStatus(rest);
|
|
797
|
+
break;
|
|
798
|
+
case "stop":
|
|
799
|
+
await handleStop(rest);
|
|
800
|
+
break;
|
|
801
|
+
case "load-credential":
|
|
802
|
+
await handleLoadCredential(rest);
|
|
803
|
+
break;
|
|
804
|
+
default: {
|
|
805
|
+
const toolName = TOOL_ALIASES[subcommand];
|
|
806
|
+
if (toolName) {
|
|
807
|
+
await handleToolCommand(toolName, rest);
|
|
808
|
+
} else {
|
|
809
|
+
console.error(`Unknown session subcommand: ${subcommand}`);
|
|
810
|
+
printHelp();
|
|
811
|
+
process2.exitCode = 1;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
export {
|
|
817
|
+
runSession
|
|
818
|
+
};
|
|
819
|
+
//# sourceMappingURL=session-UGNJXRUW.js.map
|