@hasna/computer 0.1.1 → 0.1.3
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/dashboard/dist/assets/index-6UXnbsOB.js +40 -0
- package/dashboard/dist/assets/index-CwAxlYtY.css +1 -0
- package/dashboard/dist/index.html +13 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/cli/index.js +442 -15
- package/dist/db/agents.d.ts +26 -0
- package/dist/db/agents.d.ts.map +1 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/drivers/mac/accessibility.d.ts +33 -0
- package/dist/drivers/mac/accessibility.d.ts.map +1 -0
- package/dist/drivers/mac/index.d.ts +7 -1
- package/dist/drivers/mac/index.d.ts.map +1 -1
- package/dist/drivers/mac/screenshot.d.ts +1 -1
- package/dist/drivers/mac/screenshot.d.ts.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -36
- package/dist/lib/integrations.d.ts +30 -0
- package/dist/lib/integrations.d.ts.map +1 -0
- package/dist/lib/terminal-image.d.ts +29 -0
- package/dist/lib/terminal-image.d.ts.map +1 -0
- package/dist/mcp/index.js +347 -36
- package/dist/server/index.js +182 -36
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/helpers/accessibility +0 -0
- package/helpers/accessibility.swift +161 -0
- package/package.json +3 -1
package/dist/cli/index.js
CHANGED
|
@@ -2211,10 +2211,15 @@ import { mkdir } from "fs/promises";
|
|
|
2211
2211
|
import { tmpdir } from "os";
|
|
2212
2212
|
import { join } from "path";
|
|
2213
2213
|
import { readFile, unlink } from "fs/promises";
|
|
2214
|
-
async function captureScreenshot() {
|
|
2214
|
+
async function captureScreenshot(displayNumber) {
|
|
2215
2215
|
const timestamp = Date.now();
|
|
2216
2216
|
const tmpPath = join(tmpdir(), `computer-screenshot-${timestamp}.png`);
|
|
2217
|
-
const
|
|
2217
|
+
const args = ["screencapture", "-x", "-C", "-t", "png"];
|
|
2218
|
+
if (displayNumber) {
|
|
2219
|
+
args.push(`-D${displayNumber}`);
|
|
2220
|
+
}
|
|
2221
|
+
args.push(tmpPath);
|
|
2222
|
+
const proc = Bun.spawn(args, {
|
|
2218
2223
|
stdout: "pipe",
|
|
2219
2224
|
stderr: "pipe"
|
|
2220
2225
|
});
|
|
@@ -2437,19 +2442,23 @@ function getScrollHelperPath() {
|
|
|
2437
2442
|
|
|
2438
2443
|
// src/drivers/mac/index.ts
|
|
2439
2444
|
class MacDriver {
|
|
2445
|
+
displayNumber;
|
|
2446
|
+
constructor(opts) {
|
|
2447
|
+
this.displayNumber = opts?.displayNumber;
|
|
2448
|
+
}
|
|
2440
2449
|
async getScreenSize() {
|
|
2441
2450
|
return getScreenSize();
|
|
2442
2451
|
}
|
|
2443
2452
|
async screenshot() {
|
|
2444
|
-
return captureScreenshot();
|
|
2453
|
+
return captureScreenshot(this.displayNumber);
|
|
2445
2454
|
}
|
|
2446
2455
|
async execute(action) {
|
|
2447
2456
|
return executeAction(action);
|
|
2448
2457
|
}
|
|
2449
2458
|
async dispose() {}
|
|
2450
2459
|
}
|
|
2451
|
-
function createMacDriver() {
|
|
2452
|
-
return new MacDriver;
|
|
2460
|
+
function createMacDriver(opts) {
|
|
2461
|
+
return new MacDriver(opts);
|
|
2453
2462
|
}
|
|
2454
2463
|
|
|
2455
2464
|
// node_modules/@anthropic-ai/sdk/version.mjs
|
|
@@ -21386,6 +21395,7 @@ function getDb() {
|
|
|
21386
21395
|
total_tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
21387
21396
|
total_tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
21388
21397
|
total_duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
21398
|
+
tags TEXT,
|
|
21389
21399
|
error TEXT,
|
|
21390
21400
|
created_at TEXT NOT NULL,
|
|
21391
21401
|
completed_at TEXT
|
|
@@ -21441,9 +21451,9 @@ function getDb() {
|
|
|
21441
21451
|
async function createSession(session) {
|
|
21442
21452
|
const d = getDb();
|
|
21443
21453
|
d.prepare(`
|
|
21444
|
-
INSERT INTO sessions (id, task, provider, model, status, steps, total_tokens_in, total_tokens_out, total_duration_ms, error, created_at, completed_at)
|
|
21445
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21446
|
-
`).run(session.id, session.task, session.provider, session.model, session.status, session.steps, session.total_tokens_in, session.total_tokens_out, session.total_duration_ms, session.error ?? null, session.created_at, session.completed_at ?? null);
|
|
21454
|
+
INSERT INTO sessions (id, task, provider, model, status, steps, total_tokens_in, total_tokens_out, total_duration_ms, tags, error, created_at, completed_at)
|
|
21455
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21456
|
+
`).run(session.id, session.task, session.provider, session.model, session.status, session.steps, session.total_tokens_in, session.total_tokens_out, session.total_duration_ms, session.tags?.length ? JSON.stringify(session.tags) : null, session.error ?? null, session.created_at, session.completed_at ?? null);
|
|
21447
21457
|
}
|
|
21448
21458
|
async function updateSession(session) {
|
|
21449
21459
|
const d = getDb();
|
|
@@ -21460,14 +21470,29 @@ async function logAction(params) {
|
|
|
21460
21470
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21461
21471
|
`).run(params.session_id, params.step, params.action.type, JSON.stringify(params.action), params.reasoning, params.screenshot_path ?? null, params.success ? 1 : 0, params.error ?? null, params.duration_ms, params.tokens_in ?? null, params.tokens_out ?? null);
|
|
21462
21472
|
}
|
|
21473
|
+
function getSession(id) {
|
|
21474
|
+
const d = getDb();
|
|
21475
|
+
const row = d.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
21476
|
+
if (!row)
|
|
21477
|
+
return null;
|
|
21478
|
+
return rowToSession(row);
|
|
21479
|
+
}
|
|
21463
21480
|
function listSessions(opts) {
|
|
21464
21481
|
const d = getDb();
|
|
21465
21482
|
let sql = "SELECT * FROM sessions";
|
|
21466
21483
|
const params = [];
|
|
21484
|
+
const conditions = [];
|
|
21467
21485
|
if (opts?.status) {
|
|
21468
|
-
|
|
21486
|
+
conditions.push("status = ?");
|
|
21469
21487
|
params.push(opts.status);
|
|
21470
21488
|
}
|
|
21489
|
+
if (opts?.tag) {
|
|
21490
|
+
conditions.push("tags LIKE ?");
|
|
21491
|
+
params.push(`%"${opts.tag}"%`);
|
|
21492
|
+
}
|
|
21493
|
+
if (conditions.length > 0) {
|
|
21494
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
21495
|
+
}
|
|
21471
21496
|
sql += " ORDER BY created_at DESC";
|
|
21472
21497
|
if (opts?.limit) {
|
|
21473
21498
|
sql += " LIMIT ?";
|
|
@@ -21544,12 +21569,104 @@ function rowToSession(row) {
|
|
|
21544
21569
|
total_tokens_in: row.total_tokens_in,
|
|
21545
21570
|
total_tokens_out: row.total_tokens_out,
|
|
21546
21571
|
total_duration_ms: row.total_duration_ms,
|
|
21572
|
+
tags: row.tags ? JSON.parse(row.tags) : undefined,
|
|
21547
21573
|
error: row.error,
|
|
21548
21574
|
created_at: row.created_at,
|
|
21549
21575
|
completed_at: row.completed_at
|
|
21550
21576
|
};
|
|
21551
21577
|
}
|
|
21552
21578
|
|
|
21579
|
+
// src/lib/integrations.ts
|
|
21580
|
+
async function saveToRecordings(session, logs) {
|
|
21581
|
+
try {
|
|
21582
|
+
const { saveRecording } = await import("@hasna/recordings");
|
|
21583
|
+
await saveRecording({
|
|
21584
|
+
title: `Computer Use: ${session.task.slice(0, 100)}`,
|
|
21585
|
+
type: "computer-use",
|
|
21586
|
+
source: "computer",
|
|
21587
|
+
duration_ms: session.total_duration_ms,
|
|
21588
|
+
metadata: {
|
|
21589
|
+
session_id: session.id,
|
|
21590
|
+
provider: session.provider,
|
|
21591
|
+
model: session.model,
|
|
21592
|
+
steps: session.steps,
|
|
21593
|
+
tokens_in: session.total_tokens_in,
|
|
21594
|
+
tokens_out: session.total_tokens_out,
|
|
21595
|
+
status: session.status,
|
|
21596
|
+
tags: session.tags
|
|
21597
|
+
},
|
|
21598
|
+
transcript: logs.map((l) => ({
|
|
21599
|
+
step: l.step,
|
|
21600
|
+
action: l.action.type,
|
|
21601
|
+
reasoning: l.reasoning?.slice(0, 200),
|
|
21602
|
+
success: l.success,
|
|
21603
|
+
timestamp: l.created_at
|
|
21604
|
+
}))
|
|
21605
|
+
});
|
|
21606
|
+
return true;
|
|
21607
|
+
} catch {
|
|
21608
|
+
return false;
|
|
21609
|
+
}
|
|
21610
|
+
}
|
|
21611
|
+
async function registerWithSessions(session) {
|
|
21612
|
+
try {
|
|
21613
|
+
const mod = await import("@hasna/sessions");
|
|
21614
|
+
const registerSession = mod.registerSession ?? mod.createSession ?? mod.saveSession;
|
|
21615
|
+
if (typeof registerSession !== "function")
|
|
21616
|
+
return false;
|
|
21617
|
+
await registerSession({
|
|
21618
|
+
id: session.id,
|
|
21619
|
+
type: "computer-use",
|
|
21620
|
+
source: "computer",
|
|
21621
|
+
status: session.status,
|
|
21622
|
+
metadata: {
|
|
21623
|
+
task: session.task,
|
|
21624
|
+
provider: session.provider,
|
|
21625
|
+
model: session.model,
|
|
21626
|
+
steps: session.steps
|
|
21627
|
+
},
|
|
21628
|
+
started_at: session.created_at,
|
|
21629
|
+
ended_at: session.completed_at
|
|
21630
|
+
});
|
|
21631
|
+
return true;
|
|
21632
|
+
} catch {
|
|
21633
|
+
return false;
|
|
21634
|
+
}
|
|
21635
|
+
}
|
|
21636
|
+
async function pushToLogs(session, logs) {
|
|
21637
|
+
try {
|
|
21638
|
+
const mod = await import("@hasna/logs");
|
|
21639
|
+
const pushBatch = mod.logPushBatch ?? mod.pushBatch;
|
|
21640
|
+
if (typeof pushBatch !== "function")
|
|
21641
|
+
return false;
|
|
21642
|
+
await pushBatch(logs.map((l) => ({
|
|
21643
|
+
level: l.success ? "info" : "error",
|
|
21644
|
+
source: "computer",
|
|
21645
|
+
message: `[${l.action.type}] ${l.reasoning?.slice(0, 100) ?? ""}`,
|
|
21646
|
+
metadata: {
|
|
21647
|
+
session_id: session.id,
|
|
21648
|
+
step: l.step,
|
|
21649
|
+
action_type: l.action.type,
|
|
21650
|
+
success: l.success,
|
|
21651
|
+
error: l.error,
|
|
21652
|
+
duration_ms: l.duration_ms
|
|
21653
|
+
},
|
|
21654
|
+
timestamp: l.created_at
|
|
21655
|
+
})));
|
|
21656
|
+
return true;
|
|
21657
|
+
} catch {
|
|
21658
|
+
return false;
|
|
21659
|
+
}
|
|
21660
|
+
}
|
|
21661
|
+
async function runPostSessionIntegrations(session, logs) {
|
|
21662
|
+
const [recordings, sessions, logsPushed] = await Promise.all([
|
|
21663
|
+
saveToRecordings(session, logs),
|
|
21664
|
+
registerWithSessions(session),
|
|
21665
|
+
pushToLogs(session, logs)
|
|
21666
|
+
]);
|
|
21667
|
+
return { recordings, sessions, logs: logsPushed };
|
|
21668
|
+
}
|
|
21669
|
+
|
|
21553
21670
|
// src/agent/loop.ts
|
|
21554
21671
|
var DEFAULT_MAX_STEPS = 50;
|
|
21555
21672
|
async function runTask(options) {
|
|
@@ -21563,10 +21680,12 @@ async function runTask(options) {
|
|
|
21563
21680
|
systemPrompt,
|
|
21564
21681
|
screenshotMaxWidth,
|
|
21565
21682
|
dryRun = false,
|
|
21683
|
+
tags,
|
|
21684
|
+
displayNumber,
|
|
21566
21685
|
onStep,
|
|
21567
21686
|
onDone
|
|
21568
21687
|
} = options;
|
|
21569
|
-
const driver = createMacDriver();
|
|
21688
|
+
const driver = createMacDriver({ displayNumber });
|
|
21570
21689
|
const provider = createProvider(providerName, { model });
|
|
21571
21690
|
const config = loadConfig();
|
|
21572
21691
|
const safetyConfig = config.safety;
|
|
@@ -21577,6 +21696,7 @@ async function runTask(options) {
|
|
|
21577
21696
|
provider: providerName,
|
|
21578
21697
|
model: model ?? (providerName === "anthropic" ? "claude-sonnet-4-5-20250514" : "computer-use-preview"),
|
|
21579
21698
|
status: "running",
|
|
21699
|
+
tags,
|
|
21580
21700
|
steps: 0,
|
|
21581
21701
|
total_tokens_in: 0,
|
|
21582
21702
|
total_tokens_out: 0,
|
|
@@ -21629,6 +21749,8 @@ async function runTask(options) {
|
|
|
21629
21749
|
});
|
|
21630
21750
|
await updateSession(session);
|
|
21631
21751
|
onStep?.(step, response, { success: true, duration_ms: 0 });
|
|
21752
|
+
const logs = getActionLogs(sessionId);
|
|
21753
|
+
await runPostSessionIntegrations(session, logs).catch(() => {});
|
|
21632
21754
|
onDone?.(session);
|
|
21633
21755
|
await driver.dispose();
|
|
21634
21756
|
return session;
|
|
@@ -21694,6 +21816,8 @@ async function runTask(options) {
|
|
|
21694
21816
|
session.completed_at = new Date().toISOString();
|
|
21695
21817
|
session.error = `Reached max steps (${maxSteps})`;
|
|
21696
21818
|
await updateSession(session);
|
|
21819
|
+
const endLogs = getActionLogs(sessionId);
|
|
21820
|
+
await runPostSessionIntegrations(session, endLogs).catch(() => {});
|
|
21697
21821
|
onDone?.(session);
|
|
21698
21822
|
await driver.dispose();
|
|
21699
21823
|
return session;
|
|
@@ -21703,6 +21827,8 @@ async function runTask(options) {
|
|
|
21703
21827
|
session.total_duration_ms = Date.now() - startTime;
|
|
21704
21828
|
session.completed_at = new Date().toISOString();
|
|
21705
21829
|
await updateSession(session);
|
|
21830
|
+
const errLogs = getActionLogs(sessionId);
|
|
21831
|
+
await runPostSessionIntegrations(session, errLogs).catch(() => {});
|
|
21706
21832
|
onDone?.(session);
|
|
21707
21833
|
await driver.dispose();
|
|
21708
21834
|
return session;
|
|
@@ -21775,10 +21901,65 @@ function findPricing(model) {
|
|
|
21775
21901
|
return { input: 3, output: 12 };
|
|
21776
21902
|
}
|
|
21777
21903
|
|
|
21904
|
+
// src/lib/terminal-image.ts
|
|
21905
|
+
function detectProtocol() {
|
|
21906
|
+
const term = process.env.TERM_PROGRAM ?? "";
|
|
21907
|
+
const termInfo = process.env.TERM ?? "";
|
|
21908
|
+
if (term === "iTerm.app" || term === "WezTerm") {
|
|
21909
|
+
return "iterm2";
|
|
21910
|
+
}
|
|
21911
|
+
if (termInfo.includes("kitty") || process.env.KITTY_PID) {
|
|
21912
|
+
return "kitty";
|
|
21913
|
+
}
|
|
21914
|
+
return "none";
|
|
21915
|
+
}
|
|
21916
|
+
function renderInlineImage(base64, opts) {
|
|
21917
|
+
const protocol = detectProtocol();
|
|
21918
|
+
const width = opts?.width ?? 40;
|
|
21919
|
+
const height = opts?.height ?? 15;
|
|
21920
|
+
switch (protocol) {
|
|
21921
|
+
case "iterm2":
|
|
21922
|
+
return renderIterm2(base64, width, height, opts?.preserveAspectRatio ?? true);
|
|
21923
|
+
case "kitty":
|
|
21924
|
+
return renderKitty(base64, width, height);
|
|
21925
|
+
case "none":
|
|
21926
|
+
return "";
|
|
21927
|
+
}
|
|
21928
|
+
}
|
|
21929
|
+
function supportsInlineImages() {
|
|
21930
|
+
return detectProtocol() !== "none";
|
|
21931
|
+
}
|
|
21932
|
+
function renderIterm2(base64, width, height, preserveAspectRatio) {
|
|
21933
|
+
const params = [
|
|
21934
|
+
`width=${width}`,
|
|
21935
|
+
`height=${height}`,
|
|
21936
|
+
`preserveAspectRatio=${preserveAspectRatio ? 1 : 0}`,
|
|
21937
|
+
"inline=1"
|
|
21938
|
+
].join(";");
|
|
21939
|
+
return `\x1B]1337;File=${params}:${base64}\x07
|
|
21940
|
+
`;
|
|
21941
|
+
}
|
|
21942
|
+
function renderKitty(base64, width, height) {
|
|
21943
|
+
const chunkSize = 4096;
|
|
21944
|
+
const chunks = [];
|
|
21945
|
+
for (let i = 0;i < base64.length; i += chunkSize) {
|
|
21946
|
+
const chunk = base64.slice(i, i + chunkSize);
|
|
21947
|
+
const isLast = i + chunkSize >= base64.length;
|
|
21948
|
+
const more = isLast ? 0 : 1;
|
|
21949
|
+
if (i === 0) {
|
|
21950
|
+
chunks.push(`\x1B_Gf=100,a=T,m=${more},c=${width},r=${height};${chunk}\x1B\\`);
|
|
21951
|
+
} else {
|
|
21952
|
+
chunks.push(`\x1B_Gm=${more};${chunk}\x1B\\`);
|
|
21953
|
+
}
|
|
21954
|
+
}
|
|
21955
|
+
return chunks.join("") + `
|
|
21956
|
+
`;
|
|
21957
|
+
}
|
|
21958
|
+
|
|
21778
21959
|
// src/cli/index.ts
|
|
21779
21960
|
var program2 = new Command;
|
|
21780
21961
|
program2.name("computer").description("Open-source computer use for AI agents \u2014 control your Mac with AI").version("0.1.0");
|
|
21781
|
-
program2.command("run").description("Run a computer use task").argument("<task>", "Natural language description of the task").option("-p, --provider <provider>", "AI provider (anthropic|openai)", "anthropic").option("-m, --model <model>", "Model to use").option("-s, --max-steps <n>", "Maximum number of steps", "50").option("--save-screenshots", "Save screenshots to disk", false).option("--screenshots-dir <dir>", "Directory to save screenshots").option("--system-prompt <prompt>", "Custom system prompt").option("--max-width <px>", "Max screenshot width for AI model (default: 1280)", "1280").option("--dry-run", "Plan actions without executing them", false).action(async (task, opts) => {
|
|
21962
|
+
program2.command("run").description("Run a computer use task").argument("<task>", "Natural language description of the task").option("-p, --provider <provider>", "AI provider (anthropic|openai)", "anthropic").option("-m, --model <model>", "Model to use").option("-s, --max-steps <n>", "Maximum number of steps", "50").option("--save-screenshots", "Save screenshots to disk", false).option("--screenshots-dir <dir>", "Directory to save screenshots").option("--system-prompt <prompt>", "Custom system prompt").option("--max-width <px>", "Max screenshot width for AI model (default: 1280)", "1280").option("--dry-run", "Plan actions without executing them", false).option("--no-preview", "Disable inline screenshot preview in terminal").option("--tag <tags...>", "Tag this session (can specify multiple)").option("--display <n>", "Display number to capture (1=main, 2=secondary)").action(async (task, opts) => {
|
|
21782
21963
|
const cfg = loadConfig();
|
|
21783
21964
|
const provider = opts.provider ?? cfg.provider;
|
|
21784
21965
|
const maxSteps = parseInt(opts.maxSteps) || cfg.maxSteps;
|
|
@@ -21797,6 +21978,8 @@ program2.command("run").description("Run a computer use task").argument("<task>"
|
|
|
21797
21978
|
systemPrompt: opts.systemPrompt,
|
|
21798
21979
|
screenshotMaxWidth: maxWidth,
|
|
21799
21980
|
dryRun: opts.dryRun,
|
|
21981
|
+
tags: opts.tag,
|
|
21982
|
+
displayNumber: opts.display ? parseInt(opts.display) : undefined,
|
|
21800
21983
|
onStep: (step, response, result) => {
|
|
21801
21984
|
const status = result.success ? chalk.green("OK") : chalk.red("FAIL");
|
|
21802
21985
|
const actionDesc = response.action ? `${response.action.type}${response.action.type === "click" ? ` (${response.action.point.x},${response.action.point.y})` : ""}` : "done";
|
|
@@ -21806,6 +21989,11 @@ program2.command("run").description("Run a computer use task").argument("<task>"
|
|
|
21806
21989
|
const short = response.reasoning.slice(0, 120).replace(/\n/g, " ");
|
|
21807
21990
|
console.log(chalk.dim(` ${short}${response.reasoning.length > 120 ? "..." : ""}`));
|
|
21808
21991
|
}
|
|
21992
|
+
if (opts.preview !== false && result.screenshot && supportsInlineImages()) {
|
|
21993
|
+
const img = renderInlineImage(result.screenshot.base64, { width: 40, height: 12 });
|
|
21994
|
+
if (img)
|
|
21995
|
+
process.stdout.write(img);
|
|
21996
|
+
}
|
|
21809
21997
|
},
|
|
21810
21998
|
onDone: (session2) => {
|
|
21811
21999
|
console.log();
|
|
@@ -21834,10 +22022,11 @@ program2.command("screenshot").description("Take a screenshot of the current scr
|
|
|
21834
22022
|
console.log(chalk.dim(`Base64 length: ${ss.base64.length} chars`));
|
|
21835
22023
|
}
|
|
21836
22024
|
});
|
|
21837
|
-
program2.command("sessions").description("List computer use sessions").option("-n, --limit <n>", "Number of sessions to show", "20").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
22025
|
+
program2.command("sessions").description("List computer use sessions").option("-n, --limit <n>", "Number of sessions to show", "20").option("--status <status>", "Filter by status").option("--tag <tag>", "Filter by tag").action(async (opts) => {
|
|
21838
22026
|
const sessions = listSessions({
|
|
21839
22027
|
limit: parseInt(opts.limit),
|
|
21840
|
-
status: opts.status
|
|
22028
|
+
status: opts.status,
|
|
22029
|
+
tag: opts.tag
|
|
21841
22030
|
});
|
|
21842
22031
|
if (sessions.length === 0) {
|
|
21843
22032
|
console.log(chalk.dim("No sessions found."));
|
|
@@ -21845,7 +22034,8 @@ program2.command("sessions").description("List computer use sessions").option("-
|
|
|
21845
22034
|
}
|
|
21846
22035
|
for (const s of sessions) {
|
|
21847
22036
|
const statusColor = s.status === "completed" ? chalk.green : s.status === "failed" ? chalk.red : s.status === "running" ? chalk.yellow : chalk.dim;
|
|
21848
|
-
|
|
22037
|
+
const tagStr = s.tags?.length ? chalk.magenta(` [${s.tags.join(", ")}]`) : "";
|
|
22038
|
+
console.log(`${chalk.dim(s.id.slice(0, 8))} ${statusColor(s.status.padEnd(10))} ${chalk.cyan(s.provider.padEnd(10))} ${s.steps} steps${tagStr} ${chalk.dim(s.created_at)}`);
|
|
21849
22039
|
console.log(chalk.dim(` ${s.task.slice(0, 100)}`));
|
|
21850
22040
|
}
|
|
21851
22041
|
});
|
|
@@ -21899,6 +22089,80 @@ program2.command("stats").description("Show usage statistics").action(async () =
|
|
|
21899
22089
|
console.log(` Steps: ${stats.total_steps}`);
|
|
21900
22090
|
console.log(` Tokens: ${stats.total_tokens.toLocaleString()}`);
|
|
21901
22091
|
});
|
|
22092
|
+
program2.command("watch").description("Live-stream what the agent sees (polls running session)").argument("[id]", "Session ID to watch (default: latest running)").option("-i, --interval <ms>", "Poll interval in milliseconds", "500").option("--no-preview", "Disable inline screenshot preview").action(async (id, opts) => {
|
|
22093
|
+
const interval = parseInt(opts.interval);
|
|
22094
|
+
let sessionId = id;
|
|
22095
|
+
if (!sessionId) {
|
|
22096
|
+
const running = listSessions({ status: "running", limit: 1 });
|
|
22097
|
+
if (running.length === 0) {
|
|
22098
|
+
console.log(chalk.yellow("No running sessions found. Start one with `computer run <task>`."));
|
|
22099
|
+
process.exit(0);
|
|
22100
|
+
}
|
|
22101
|
+
sessionId = running[0].id;
|
|
22102
|
+
} else {
|
|
22103
|
+
const all = listSessions({ limit: 100 });
|
|
22104
|
+
const match = all.find((s) => s.id.startsWith(sessionId));
|
|
22105
|
+
if (match)
|
|
22106
|
+
sessionId = match.id;
|
|
22107
|
+
}
|
|
22108
|
+
console.log(chalk.bold.cyan("computer watch") + ` \u2014 session ${chalk.dim(sessionId.slice(0, 8))}`);
|
|
22109
|
+
console.log(chalk.dim(`Polling every ${interval}ms. Press Ctrl+C to stop.
|
|
22110
|
+
`));
|
|
22111
|
+
let lastStep = -1;
|
|
22112
|
+
const poll = async () => {
|
|
22113
|
+
const session = getSession(sessionId);
|
|
22114
|
+
if (!session) {
|
|
22115
|
+
console.log(chalk.red("Session not found."));
|
|
22116
|
+
process.exit(1);
|
|
22117
|
+
}
|
|
22118
|
+
const logs = getActionLogs(sessionId);
|
|
22119
|
+
const newLogs = logs.filter((l) => l.step > lastStep);
|
|
22120
|
+
for (const log of newLogs) {
|
|
22121
|
+
const status = log.success ? chalk.green("OK") : chalk.red("FAIL");
|
|
22122
|
+
console.log(chalk.dim(`[${String(log.step + 1).padStart(3)}]`) + ` ${status} ${chalk.yellow(log.action.type)}` + chalk.dim(` (${log.duration_ms}ms)`));
|
|
22123
|
+
if (log.reasoning) {
|
|
22124
|
+
const short = log.reasoning.slice(0, 120).replace(/\n/g, " ");
|
|
22125
|
+
console.log(chalk.dim(` ${short}${log.reasoning.length > 120 ? "..." : ""}`));
|
|
22126
|
+
}
|
|
22127
|
+
if (log.error) {
|
|
22128
|
+
console.log(chalk.red(` Error: ${log.error}`));
|
|
22129
|
+
}
|
|
22130
|
+
if (opts.preview !== false && log.screenshot_path && supportsInlineImages()) {
|
|
22131
|
+
try {
|
|
22132
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
22133
|
+
const imgData = readFileSync3(log.screenshot_path);
|
|
22134
|
+
const b64 = imgData.toString("base64");
|
|
22135
|
+
const img = renderInlineImage(b64, { width: 40, height: 12 });
|
|
22136
|
+
if (img)
|
|
22137
|
+
process.stdout.write(img);
|
|
22138
|
+
} catch {}
|
|
22139
|
+
}
|
|
22140
|
+
lastStep = log.step;
|
|
22141
|
+
}
|
|
22142
|
+
if (session.status !== "running") {
|
|
22143
|
+
console.log();
|
|
22144
|
+
const totalCost = formatCost(calculateCost(session.model, session.total_tokens_in, session.total_tokens_out));
|
|
22145
|
+
if (session.status === "completed" && !session.error) {
|
|
22146
|
+
console.log(chalk.green.bold("Session completed."));
|
|
22147
|
+
} else if (session.status === "completed") {
|
|
22148
|
+
console.log(chalk.yellow.bold(`Session finished: ${session.error}`));
|
|
22149
|
+
} else {
|
|
22150
|
+
console.log(chalk.red.bold(`Session ${session.status}: ${session.error}`));
|
|
22151
|
+
}
|
|
22152
|
+
console.log(chalk.dim(`Steps: ${session.steps} | Tokens: ${(session.total_tokens_in + session.total_tokens_out).toLocaleString()} | Cost: ${totalCost} | Duration: ${(session.total_duration_ms / 1000).toFixed(1)}s`));
|
|
22153
|
+
process.exit(0);
|
|
22154
|
+
}
|
|
22155
|
+
};
|
|
22156
|
+
await poll();
|
|
22157
|
+
const timer = setInterval(poll, interval);
|
|
22158
|
+
process.on("SIGINT", () => {
|
|
22159
|
+
clearInterval(timer);
|
|
22160
|
+
console.log(chalk.dim(`
|
|
22161
|
+
Stopped watching.`));
|
|
22162
|
+
process.exit(0);
|
|
22163
|
+
});
|
|
22164
|
+
await new Promise(() => {});
|
|
22165
|
+
});
|
|
21902
22166
|
program2.command("search").description("Search sessions by task text").argument("<query>", "Search query").option("-n, --limit <n>", "Max results", "20").action(async (query, opts) => {
|
|
21903
22167
|
const sessions = searchSessions(query, parseInt(opts.limit));
|
|
21904
22168
|
if (sessions.length === 0) {
|
|
@@ -21907,7 +22171,8 @@ program2.command("search").description("Search sessions by task text").argument(
|
|
|
21907
22171
|
}
|
|
21908
22172
|
for (const s of sessions) {
|
|
21909
22173
|
const statusColor = s.status === "completed" ? chalk.green : s.status === "failed" ? chalk.red : chalk.dim;
|
|
21910
|
-
|
|
22174
|
+
const tagStr = s.tags?.length ? chalk.magenta(` [${s.tags.join(", ")}]`) : "";
|
|
22175
|
+
console.log(`${chalk.dim(s.id.slice(0, 8))} ${statusColor(s.status.padEnd(10))} ${chalk.cyan(s.provider.padEnd(10))} ${s.steps} steps${tagStr} ${chalk.dim(s.created_at)}`);
|
|
21911
22176
|
console.log(chalk.dim(` ${s.task.slice(0, 100)}`));
|
|
21912
22177
|
}
|
|
21913
22178
|
});
|
|
@@ -21949,5 +22214,167 @@ configCmd.command("reset").description("Reset config to defaults").action(async
|
|
|
21949
22214
|
saveConfig2(DEFAULT_CONFIG2);
|
|
21950
22215
|
console.log(chalk.green("Config reset to defaults."));
|
|
21951
22216
|
});
|
|
22217
|
+
program2.command("replay").description("Replay a session \u2014 show actions and screenshots in sequence").argument("<id>", "Session ID (or prefix)").option("--speed <x>", "Replay speed multiplier (default: 2)", "2").option("--no-preview", "Disable inline screenshot preview").action(async (id, opts) => {
|
|
22218
|
+
const sessions = listSessions({ limit: 1000 });
|
|
22219
|
+
const session = sessions.find((s) => s.id.startsWith(id));
|
|
22220
|
+
if (!session) {
|
|
22221
|
+
console.log(chalk.red(`Session not found: ${id}`));
|
|
22222
|
+
process.exit(1);
|
|
22223
|
+
}
|
|
22224
|
+
const logs = getActionLogs(session.id);
|
|
22225
|
+
if (logs.length === 0) {
|
|
22226
|
+
console.log(chalk.dim("No action logs for this session."));
|
|
22227
|
+
process.exit(0);
|
|
22228
|
+
}
|
|
22229
|
+
const speed = parseFloat(opts.speed);
|
|
22230
|
+
const totalCost = formatCost(calculateCost(session.model, session.total_tokens_in, session.total_tokens_out));
|
|
22231
|
+
console.log(chalk.bold.cyan("computer replay") + ` \u2014 ${chalk.dim(session.id.slice(0, 8))}`);
|
|
22232
|
+
console.log(chalk.dim(`Task: ${session.task}`));
|
|
22233
|
+
console.log(chalk.dim(`Provider: ${session.provider} | ${logs.length} steps | Speed: ${speed}x
|
|
22234
|
+
`));
|
|
22235
|
+
for (let i = 0;i < logs.length; i++) {
|
|
22236
|
+
const log = logs[i];
|
|
22237
|
+
const status = log.success ? chalk.green("OK") : chalk.red("FAIL");
|
|
22238
|
+
console.log(chalk.dim(`[${String(log.step + 1).padStart(3)}]`) + ` ${status} ${chalk.yellow(log.action.type)}` + chalk.dim(` (${log.duration_ms}ms)`));
|
|
22239
|
+
if (log.reasoning) {
|
|
22240
|
+
const short = log.reasoning.slice(0, 120).replace(/\n/g, " ");
|
|
22241
|
+
console.log(chalk.dim(` ${short}${log.reasoning.length > 120 ? "..." : ""}`));
|
|
22242
|
+
}
|
|
22243
|
+
if (log.error) {
|
|
22244
|
+
console.log(chalk.red(` Error: ${log.error}`));
|
|
22245
|
+
}
|
|
22246
|
+
if (opts.preview !== false && log.screenshot_path && supportsInlineImages()) {
|
|
22247
|
+
try {
|
|
22248
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
22249
|
+
const imgData = readFileSync3(log.screenshot_path);
|
|
22250
|
+
const b64 = imgData.toString("base64");
|
|
22251
|
+
const img = renderInlineImage(b64, { width: 40, height: 12 });
|
|
22252
|
+
if (img)
|
|
22253
|
+
process.stdout.write(img);
|
|
22254
|
+
} catch {}
|
|
22255
|
+
}
|
|
22256
|
+
if (i < logs.length - 1) {
|
|
22257
|
+
const nextLog = logs[i + 1];
|
|
22258
|
+
const delay = Math.max(100, Math.round(log.duration_ms / speed));
|
|
22259
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
22260
|
+
}
|
|
22261
|
+
}
|
|
22262
|
+
console.log();
|
|
22263
|
+
console.log(chalk.dim(`Replay complete. ${logs.length} steps | Cost: ${totalCost} | Duration: ${(session.total_duration_ms / 1000).toFixed(1)}s`));
|
|
22264
|
+
});
|
|
22265
|
+
program2.command("completions").description("Generate shell completions").argument("<shell>", "Shell type (zsh or bash)").action(async (shell) => {
|
|
22266
|
+
if (shell === "zsh") {
|
|
22267
|
+
console.log(generateZshCompletions());
|
|
22268
|
+
} else if (shell === "bash") {
|
|
22269
|
+
console.log(generateBashCompletions());
|
|
22270
|
+
} else {
|
|
22271
|
+
console.error(chalk.red(`Unknown shell: ${shell}. Use "zsh" or "bash".`));
|
|
22272
|
+
process.exit(1);
|
|
22273
|
+
}
|
|
22274
|
+
});
|
|
21952
22275
|
registerCloudCommands(program2, "computer");
|
|
21953
22276
|
program2.parse();
|
|
22277
|
+
function generateZshCompletions() {
|
|
22278
|
+
return `#compdef computer
|
|
22279
|
+
# Zsh completions for @hasna/computer
|
|
22280
|
+
# Install: computer completions zsh > ~/.zsh/completions/_computer
|
|
22281
|
+
|
|
22282
|
+
_computer() {
|
|
22283
|
+
local -a commands
|
|
22284
|
+
commands=(
|
|
22285
|
+
'run:Run a computer use task'
|
|
22286
|
+
'screenshot:Take a screenshot'
|
|
22287
|
+
'sessions:List sessions'
|
|
22288
|
+
'session:Show session details'
|
|
22289
|
+
'delete:Delete a session'
|
|
22290
|
+
'stats:Show usage statistics'
|
|
22291
|
+
'watch:Live-stream agent activity'
|
|
22292
|
+
'search:Search sessions'
|
|
22293
|
+
'config:View or modify configuration'
|
|
22294
|
+
'completions:Generate shell completions'
|
|
22295
|
+
'cloud:Cloud sync and feedback'
|
|
22296
|
+
)
|
|
22297
|
+
|
|
22298
|
+
_arguments -C \\
|
|
22299
|
+
'1:command:->command' \\
|
|
22300
|
+
'*::arg:->args'
|
|
22301
|
+
|
|
22302
|
+
case "$state" in
|
|
22303
|
+
command)
|
|
22304
|
+
_describe 'command' commands
|
|
22305
|
+
;;
|
|
22306
|
+
args)
|
|
22307
|
+
case "$words[1]" in
|
|
22308
|
+
run)
|
|
22309
|
+
_arguments \\
|
|
22310
|
+
'-p[AI provider]:provider:(anthropic openai)' \\
|
|
22311
|
+
'-m[Model to use]:model:' \\
|
|
22312
|
+
'-s[Max steps]:steps:' \\
|
|
22313
|
+
'--save-screenshots[Save screenshots]' \\
|
|
22314
|
+
'--dry-run[Plan without executing]' \\
|
|
22315
|
+
'--tag[Tag session]:tag:' \\
|
|
22316
|
+
'--max-width[Max screenshot width]:width:' \\
|
|
22317
|
+
'--no-preview[Disable inline preview]' \\
|
|
22318
|
+
'1:task:'
|
|
22319
|
+
;;
|
|
22320
|
+
sessions)
|
|
22321
|
+
_arguments \\
|
|
22322
|
+
'-n[Limit]:limit:' \\
|
|
22323
|
+
'--status[Filter by status]:status:(running completed failed cancelled)' \\
|
|
22324
|
+
'--tag[Filter by tag]:tag:'
|
|
22325
|
+
;;
|
|
22326
|
+
config)
|
|
22327
|
+
local -a config_cmds
|
|
22328
|
+
config_cmds=(show get set path edit reset)
|
|
22329
|
+
_describe 'config command' config_cmds
|
|
22330
|
+
;;
|
|
22331
|
+
completions)
|
|
22332
|
+
_arguments '1:shell:(zsh bash)'
|
|
22333
|
+
;;
|
|
22334
|
+
esac
|
|
22335
|
+
;;
|
|
22336
|
+
esac
|
|
22337
|
+
}
|
|
22338
|
+
|
|
22339
|
+
_computer "$@"`;
|
|
22340
|
+
}
|
|
22341
|
+
function generateBashCompletions() {
|
|
22342
|
+
return `# Bash completions for @hasna/computer
|
|
22343
|
+
# Install: computer completions bash >> ~/.bashrc
|
|
22344
|
+
|
|
22345
|
+
_computer_completions() {
|
|
22346
|
+
local cur prev commands
|
|
22347
|
+
COMPREPLY=()
|
|
22348
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
22349
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
22350
|
+
commands="run screenshot sessions session delete stats watch search config completions cloud"
|
|
22351
|
+
|
|
22352
|
+
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
22353
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
22354
|
+
return 0
|
|
22355
|
+
fi
|
|
22356
|
+
|
|
22357
|
+
case "\${COMP_WORDS[1]}" in
|
|
22358
|
+
run)
|
|
22359
|
+
COMPREPLY=( $(compgen -W "-p --provider -m --model -s --max-steps --save-screenshots --dry-run --tag --max-width --no-preview" -- "$cur") )
|
|
22360
|
+
;;
|
|
22361
|
+
sessions)
|
|
22362
|
+
COMPREPLY=( $(compgen -W "-n --limit --status --tag" -- "$cur") )
|
|
22363
|
+
;;
|
|
22364
|
+
config)
|
|
22365
|
+
COMPREPLY=( $(compgen -W "show get set path edit reset" -- "$cur") )
|
|
22366
|
+
;;
|
|
22367
|
+
completions)
|
|
22368
|
+
COMPREPLY=( $(compgen -W "zsh bash" -- "$cur") )
|
|
22369
|
+
;;
|
|
22370
|
+
--provider|-p)
|
|
22371
|
+
COMPREPLY=( $(compgen -W "anthropic openai" -- "$cur") )
|
|
22372
|
+
;;
|
|
22373
|
+
--status)
|
|
22374
|
+
COMPREPLY=( $(compgen -W "running completed failed cancelled" -- "$cur") )
|
|
22375
|
+
;;
|
|
22376
|
+
esac
|
|
22377
|
+
}
|
|
22378
|
+
|
|
22379
|
+
complete -F _computer_completions computer`;
|
|
22380
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface Agent {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
capabilities?: string[];
|
|
6
|
+
focus?: string;
|
|
7
|
+
last_heartbeat: string;
|
|
8
|
+
created_at: string;
|
|
9
|
+
}
|
|
10
|
+
/** Ensure agents table exists */
|
|
11
|
+
export declare function ensureAgentsTable(): void;
|
|
12
|
+
/** Register or update an agent */
|
|
13
|
+
export declare function registerAgent(agent: {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
capabilities?: string[];
|
|
17
|
+
}): Agent;
|
|
18
|
+
/** Send a heartbeat for an agent */
|
|
19
|
+
export declare function heartbeat(agentId: string): boolean;
|
|
20
|
+
/** Set agent focus */
|
|
21
|
+
export declare function setFocus(agentId: string, focus: string): boolean;
|
|
22
|
+
/** Get an agent by ID */
|
|
23
|
+
export declare function getAgent(id: string): Agent | null;
|
|
24
|
+
/** List all agents */
|
|
25
|
+
export declare function listAgents(): Agent[];
|
|
26
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iCAAiC;AACjC,wBAAgB,iBAAiB,IAAI,IAAI,CAaxC;AAED,kCAAkC;AAClC,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,GAAG,KAAK,CA0BR;AAED,oCAAoC;AACpC,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQlD;AAED,sBAAsB;AACtB,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKhE;AAED,yBAAyB;AACzB,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAMjD;AAED,sBAAsB;AACtB,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAKpC"}
|
package/dist/db/index.d.ts
CHANGED
package/dist/db/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAMzF,0DAA0D;AAC1D,wBAAgB,KAAK,IAAI,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAMzF,0DAA0D;AAC1D,wBAAgB,KAAK,IAAI,SAAS,CA6EjC;AAED,2BAA2B;AAC3B,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBnE;AAED,uBAAuB;AACvB,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBnE;AAED,qCAAqC;AACrC,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhB;AAED,0BAA0B;AAC1B,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAKrD;AAED,oBAAoB;AACpB,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE;IAClC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,EAAE,CA8BZ;AAED,0CAA0C;AAC1C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,OAAO,EAAE,CAU3E;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB,SAAS,EAAE,CAuBb;AAED,oCAAoC;AACpC,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAoB5D;AAED,oCAAoC;AACpC,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAKjD;AAED,wBAAwB;AACxB,wBAAgB,QAAQ,IAAI;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAmBA"}
|