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 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
- if (!running) return { supported: true, installed: true, running: false, version, spaces: [], pageCount: 0 };
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 { supported: true, installed: true, running: true, version, spaces, pageCount };
133
+ return { ...base, spaces, pageCount };
114
134
  } catch (error) {
115
135
  return {
116
- supported: true,
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 lastRefreshAt = 0;
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(detailed).then((status) => {
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 online ? { text: t("badge.online"), active: true } : { text: t("badge.offline"), active: false };
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() - lastRefreshAt > 3e3) {
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(true);
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
- cachedStatus = status;
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 = binary ? await installedVersion(binary) : void 0;
604
- if (!binary || !version) {
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
- await new Promise((resolve) => setTimeout(resolve, 1800));
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
- action.notifyUpdate();
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
- action.notifyUpdate();
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(true), 5e3);
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
@@ -95,7 +95,8 @@
95
95
  }
96
96
  },
97
97
  "error": {
98
- "notInstalled": "Ego Browser 未安装"
98
+ "notInstalled": "Ego Browser 未安装",
99
+ "launchTimeout": "Ego Lite 已启动但还没就绪,请稍等片刻再试。"
99
100
  },
100
101
  "progress": {
101
102
  "launching": "正在唤醒 Ego 浏览器…|给浏览器泡杯咖啡…|Ego 正在伸懒腰…",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "finch-ego-lite",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Let Finch browse the web in isolated Ego Lite task spaces while you follow every page it opens.",
5
5
  "author": {
6
6
  "name": "PuterJam",