finch-ego-lite 0.1.4 → 0.1.5
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/index.js +91 -32
- package/i18n/en-US.json +2 -1
- package/i18n/zh-CN.json +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { promisify } from "node:util";
|
|
|
7
7
|
var execFileAsync = promisify(execFile);
|
|
8
8
|
var WEBSITE = "https://lite.ego.app/";
|
|
9
9
|
var STATUS_MARKER = "__EGO_STATUS__";
|
|
10
|
+
var OFFLINE_CONFIRMATIONS = 2;
|
|
10
11
|
var commandQueue = Promise.resolve();
|
|
11
12
|
function egoBinary() {
|
|
12
13
|
const local = join(homedir(), ".local", "bin", "ego-browser");
|
|
@@ -56,6 +57,26 @@ async function installedVersion(binary) {
|
|
|
56
57
|
return void 0;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
var VERSION_TTL = 5 * 6e4;
|
|
61
|
+
var cachedVersion;
|
|
62
|
+
async function probeVersion(binary) {
|
|
63
|
+
if (cachedVersion && Date.now() - cachedVersion.at < VERSION_TTL) return cachedVersion.value;
|
|
64
|
+
const value = await installedVersion(binary);
|
|
65
|
+
if (value) cachedVersion = { value, at: Date.now() };
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
async function probeInstall(binary, running) {
|
|
69
|
+
const version = running && cachedVersion?.value || await probeVersion(binary);
|
|
70
|
+
return { installed: running || Boolean(version), version };
|
|
71
|
+
}
|
|
72
|
+
async function waitForRunning(deadlineMs = 12e3) {
|
|
73
|
+
const until = Date.now() + deadlineMs;
|
|
74
|
+
for (; ; ) {
|
|
75
|
+
if (await processRunning()) return true;
|
|
76
|
+
if (Date.now() >= until) return false;
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
59
80
|
function statusScript(detailed) {
|
|
60
81
|
return `
|
|
61
82
|
const spaces = await listTaskSpaces();
|
|
@@ -98,11 +119,10 @@ async function getStatus(detailed = false) {
|
|
|
98
119
|
return { supported: false, installed: false, running: false, spaces: [], pageCount: 0 };
|
|
99
120
|
}
|
|
100
121
|
const binary = egoBinary();
|
|
101
|
-
if (!binary) return { supported: true, installed: false, running: false, spaces: [], pageCount: 0 };
|
|
102
|
-
const version = await installedVersion(binary);
|
|
103
|
-
if (!version) return { supported: true, installed: false, running: false, spaces: [], pageCount: 0 };
|
|
104
122
|
const running = await processRunning();
|
|
105
|
-
|
|
123
|
+
const { installed, version } = await probeInstall(binary, running);
|
|
124
|
+
const base = { supported: true, installed, running, version, spaces: [], pageCount: 0 };
|
|
125
|
+
if (!running || !version) return base;
|
|
106
126
|
try {
|
|
107
127
|
const output = await runCommand(binary, ["nodejs", "-e", statusScript(detailed)], 1e4);
|
|
108
128
|
const spaces = parseSpaces(output);
|
|
@@ -110,19 +130,30 @@ async function getStatus(detailed = false) {
|
|
|
110
130
|
if (space.tabs) return sum + space.tabs.length;
|
|
111
131
|
return sum + (space.recentTabTitles?.length ?? 0);
|
|
112
132
|
}, 0);
|
|
113
|
-
return {
|
|
133
|
+
return { ...base, spaces, pageCount };
|
|
114
134
|
} catch (error) {
|
|
115
135
|
return {
|
|
116
|
-
|
|
117
|
-
installed: true,
|
|
118
|
-
running: true,
|
|
119
|
-
version,
|
|
120
|
-
spaces: [],
|
|
121
|
-
pageCount: 0,
|
|
136
|
+
...base,
|
|
122
137
|
error: error instanceof Error ? error.message : String(error)
|
|
123
138
|
};
|
|
124
139
|
}
|
|
125
140
|
}
|
|
141
|
+
async function probeStatus(previous) {
|
|
142
|
+
if (process.platform !== "darwin") {
|
|
143
|
+
return { supported: false, installed: false, running: false, spaces: [], pageCount: 0 };
|
|
144
|
+
}
|
|
145
|
+
const running = await processRunning();
|
|
146
|
+
const { installed, version } = await probeInstall(egoBinary(), running);
|
|
147
|
+
return {
|
|
148
|
+
supported: true,
|
|
149
|
+
installed,
|
|
150
|
+
running,
|
|
151
|
+
version: version ?? previous?.version,
|
|
152
|
+
// A cached space list is meaningless once the process is gone.
|
|
153
|
+
spaces: running ? previous?.spaces ?? [] : [],
|
|
154
|
+
pageCount: running ? previous?.pageCount ?? 0 : 0
|
|
155
|
+
};
|
|
156
|
+
}
|
|
126
157
|
async function launchEgo() {
|
|
127
158
|
if (process.platform !== "darwin") throw new Error("Ego Lite currently supports macOS only.");
|
|
128
159
|
await execFileAsync("open", ["-a", "ego lite"], { timeout: 5e3 });
|
|
@@ -299,19 +330,45 @@ function activate(ctx) {
|
|
|
299
330
|
}
|
|
300
331
|
}));
|
|
301
332
|
let cachedStatus;
|
|
302
|
-
let
|
|
333
|
+
let lastDetailAt = 0;
|
|
334
|
+
let offlineMisses = 0;
|
|
335
|
+
let recheckTimer;
|
|
303
336
|
let refreshInFlight;
|
|
304
337
|
let pendingDetailedRefresh = false;
|
|
338
|
+
const isOnline = (status) => status?.installed === true && status.running === true;
|
|
339
|
+
const publishStatus = (next, detailed) => {
|
|
340
|
+
if (isOnline(cachedStatus) && !isOnline(next) && offlineMisses + 1 < OFFLINE_CONFIRMATIONS) {
|
|
341
|
+
offlineMisses += 1;
|
|
342
|
+
if (!recheckTimer) {
|
|
343
|
+
recheckTimer = setTimeout(() => {
|
|
344
|
+
recheckTimer = void 0;
|
|
345
|
+
void refreshStatus(false);
|
|
346
|
+
}, 600);
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const previousOnline = isOnline(cachedStatus);
|
|
351
|
+
const same = JSON.stringify(cachedStatus ?? null) === JSON.stringify(next);
|
|
352
|
+
offlineMisses = 0;
|
|
353
|
+
cachedStatus = next;
|
|
354
|
+
if (detailed) lastDetailAt = Date.now();
|
|
355
|
+
if (previousOnline !== isOnline(next)) {
|
|
356
|
+
ctx.logger.info("Ego online state changed", {
|
|
357
|
+
online: isOnline(next),
|
|
358
|
+
running: next.running,
|
|
359
|
+
installed: next.installed,
|
|
360
|
+
version: next.version,
|
|
361
|
+
error: next.error
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if (!same) action.notifyUpdate();
|
|
365
|
+
};
|
|
305
366
|
const refreshStatus = (detailed = false) => {
|
|
306
367
|
if (refreshInFlight) {
|
|
307
368
|
if (detailed) pendingDetailedRefresh = true;
|
|
308
369
|
return refreshInFlight;
|
|
309
370
|
}
|
|
310
|
-
refreshInFlight = getStatus(
|
|
311
|
-
cachedStatus = status;
|
|
312
|
-
lastRefreshAt = Date.now();
|
|
313
|
-
action.notifyUpdate();
|
|
314
|
-
}).catch((error) => ctx.logger.warn("Ego background status refresh failed", error)).finally(() => {
|
|
371
|
+
refreshInFlight = (detailed ? getStatus(true) : probeStatus(cachedStatus)).then((status) => publishStatus(status, detailed)).catch((error) => ctx.logger.warn("Ego background status refresh failed", error)).finally(() => {
|
|
315
372
|
refreshInFlight = void 0;
|
|
316
373
|
if (pendingDetailedRefresh) {
|
|
317
374
|
pendingDetailedRefresh = false;
|
|
@@ -322,12 +379,11 @@ function activate(ctx) {
|
|
|
322
379
|
};
|
|
323
380
|
const action = ctx.composerActions.register("ego-browser", {
|
|
324
381
|
async getBadge() {
|
|
325
|
-
const online = cachedStatus?.installed === true && cachedStatus.running === true && !cachedStatus.error;
|
|
326
382
|
if (cachedStatus?.supported === false) return { text: t("badge.unsupported"), active: false };
|
|
327
|
-
return
|
|
383
|
+
return isOnline(cachedStatus) ? { text: t("badge.online"), active: true } : { text: t("badge.offline"), active: false };
|
|
328
384
|
},
|
|
329
385
|
async getMenu() {
|
|
330
|
-
if (cachedStatus === void 0 || Date.now() -
|
|
386
|
+
if (cachedStatus === void 0 || Date.now() - lastDetailAt > 3e3) {
|
|
331
387
|
await refreshStatus(true);
|
|
332
388
|
}
|
|
333
389
|
if (!cachedStatus) {
|
|
@@ -394,6 +450,9 @@ function activate(ctx) {
|
|
|
394
450
|
id: "__summary__",
|
|
395
451
|
label: t("menu.summary.spaces", { count: status.spaces.length }),
|
|
396
452
|
description: t("menu.summary.pages", { count: status.pageCount }),
|
|
453
|
+
// When the space read fails while Ego is up, say so here instead of
|
|
454
|
+
// showing an empty list with no explanation.
|
|
455
|
+
hoverText: status.error ? t("status.error", { error: status.error }) : void 0,
|
|
397
456
|
iconName: "ext:ego-browser-icons/compass",
|
|
398
457
|
disabled: true
|
|
399
458
|
}, {
|
|
@@ -472,7 +531,7 @@ ${domainOf(tab.url, t("site.unknown"))}`,
|
|
|
472
531
|
await ctx.ui.showToast({ title: t("toast.launched"), variant: "success", position: "TC" });
|
|
473
532
|
cachedStatus = void 0;
|
|
474
533
|
action.notifyUpdate();
|
|
475
|
-
refreshStatus(true);
|
|
534
|
+
void waitForRunning().then(() => refreshStatus(true));
|
|
476
535
|
return;
|
|
477
536
|
}
|
|
478
537
|
if (itemId.startsWith("resume:")) {
|
|
@@ -497,13 +556,14 @@ ${domainOf(tab.url, t("site.unknown"))}`,
|
|
|
497
556
|
const script = `const result = await completeTaskSpace(${JSON.stringify(spaceId)}, { keep: false }); cliLog(JSON.stringify(result));`;
|
|
498
557
|
await runCommand(binary, ["nodejs", "-e", script], 15e3);
|
|
499
558
|
action.notifyUpdate();
|
|
500
|
-
refreshStatus(
|
|
559
|
+
refreshStatus(false);
|
|
501
560
|
await ctx.ui.showToast({ title: t("toast.closed"), variant: "success", position: "TC" });
|
|
502
561
|
return;
|
|
503
562
|
}
|
|
504
563
|
if (itemId.startsWith("tab:")) {
|
|
505
564
|
const [spaceId, targetId] = unkey(itemId.slice("tab:".length));
|
|
506
565
|
await launchEgo();
|
|
566
|
+
if (!await waitForRunning()) throw new Error(t("error.launchTimeout"));
|
|
507
567
|
const binary = egoBinary();
|
|
508
568
|
const script = `await switchTaskSpace(${JSON.stringify(spaceId)}); await switchTab(${JSON.stringify(targetId)}); cliLog('ok');`;
|
|
509
569
|
await runCommand(binary, ["nodejs", "-e", script], 1e4);
|
|
@@ -566,8 +626,7 @@ Reuse task spaces, respect user ownership, verify meaningful actions, and comple
|
|
|
566
626
|
const actionName = request.action ?? "";
|
|
567
627
|
if (actionName === "status") {
|
|
568
628
|
const status = await getStatus(true);
|
|
569
|
-
|
|
570
|
-
action.notifyUpdate();
|
|
629
|
+
publishStatus(status, true);
|
|
571
630
|
return { content: [{ type: "text", text: statusText(status, t) }], isError: !status.supported || Boolean(status.error) };
|
|
572
631
|
}
|
|
573
632
|
if (!ACTION_STAGES[actionName]) {
|
|
@@ -600,8 +659,8 @@ Reuse task spaces, respect user ownership, verify meaningful actions, and comple
|
|
|
600
659
|
return { content: [{ type: "text", text: t("status.unsupported", { platform: process.platform }) }], isError: true };
|
|
601
660
|
}
|
|
602
661
|
const binary = egoBinary();
|
|
603
|
-
const version =
|
|
604
|
-
if (!
|
|
662
|
+
const version = await probeVersion(binary);
|
|
663
|
+
if (!version && !await processRunning()) {
|
|
605
664
|
return {
|
|
606
665
|
content: [{ type: "text", text: `Ego Lite is not installed. Install it from ${WEBSITE}, finish onboarding, then retry.` }],
|
|
607
666
|
isError: true
|
|
@@ -620,7 +679,9 @@ Reuse task spaces, respect user ownership, verify meaningful actions, and comple
|
|
|
620
679
|
if (!await processRunning()) {
|
|
621
680
|
exec.progress.report({ stage: "launching", message: pickProgress(t, "launching") });
|
|
622
681
|
await launchEgo();
|
|
623
|
-
|
|
682
|
+
if (!await waitForRunning()) {
|
|
683
|
+
return { content: [{ type: "text", text: t("error.launchTimeout") }], isError: true };
|
|
684
|
+
}
|
|
624
685
|
} else {
|
|
625
686
|
await focusEgo();
|
|
626
687
|
}
|
|
@@ -629,8 +690,7 @@ Reuse task spaces, respect user ownership, verify meaningful actions, and comple
|
|
|
629
690
|
exec.progress.report({ stage: ACTION_STAGES[actionName], message: pickProgress(t, ACTION_STAGES[actionName] ?? "browsing") });
|
|
630
691
|
try {
|
|
631
692
|
const output = await runCommand(binary, ["nodejs", "-e", script], timeout);
|
|
632
|
-
|
|
633
|
-
refreshStatus(true);
|
|
693
|
+
refreshStatus(false);
|
|
634
694
|
if (actionName === "screenshot") {
|
|
635
695
|
const path = output.split("__EGO_SHOT__").pop()?.split("\n")[0]?.trim();
|
|
636
696
|
if (!path || !existsSync(path)) {
|
|
@@ -646,15 +706,14 @@ Reuse task spaces, respect user ownership, verify meaningful actions, and comple
|
|
|
646
706
|
}
|
|
647
707
|
return { content: [{ type: "text", text: output || "ok" }] };
|
|
648
708
|
} catch (error) {
|
|
649
|
-
|
|
650
|
-
refreshStatus(true);
|
|
709
|
+
refreshStatus(false);
|
|
651
710
|
const message = error instanceof Error ? error.message : String(error);
|
|
652
711
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
653
712
|
}
|
|
654
713
|
}
|
|
655
714
|
}));
|
|
656
715
|
refreshStatus(true);
|
|
657
|
-
const timer = setInterval(() => refreshStatus(
|
|
716
|
+
const timer = setInterval(() => refreshStatus(false), 5e3);
|
|
658
717
|
ctx.subscriptions.push({ dispose: () => clearInterval(timer) });
|
|
659
718
|
ctx.logger.info("ego-browser mini tool activated", { cached: Boolean(cachedStatus) });
|
|
660
719
|
}
|
package/i18n/en-US.json
CHANGED
|
@@ -95,7 +95,8 @@
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
"error": {
|
|
98
|
-
"notInstalled": "Ego Browser is not installed"
|
|
98
|
+
"notInstalled": "Ego Browser is not installed",
|
|
99
|
+
"launchTimeout": "Ego Lite was launched but is not ready yet. Wait a moment and try again."
|
|
99
100
|
},
|
|
100
101
|
"progress": {
|
|
101
102
|
"launching": "Waking up Ego Browser…|Pouring a coffee for the browser…|Ego is stretching…",
|
package/i18n/zh-CN.json
CHANGED