assistme 0.1.8 → 0.1.10

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
@@ -394,7 +394,7 @@ import {
394
394
  import { WebSocket } from "ws";
395
395
  import { execSync, spawn } from "child_process";
396
396
  import { platform } from "os";
397
- import { existsSync } from "fs";
397
+ import { existsSync, unlinkSync } from "fs";
398
398
  var BrowserController = class {
399
399
  ws = null;
400
400
  debugPort;
@@ -860,7 +860,7 @@ function isChromeRunning() {
860
860
  return out2.includes("chrome.exe");
861
861
  }
862
862
  if (platform() === "darwin") {
863
- const out2 = execSync('pgrep -f "Google Chrome.app/Contents/MacOS/Google Chrome$"', {
863
+ const out2 = execSync('pgrep -f "Google Chrome.app/Contents/MacOS/Google Chrome"', {
864
864
  encoding: "utf-8",
865
865
  stdio: ["pipe", "pipe", "pipe"]
866
866
  });
@@ -884,7 +884,7 @@ async function killChromeGracefully() {
884
884
  stdio: ["pipe", "pipe", "pipe"]
885
885
  });
886
886
  } else if (os === "linux") {
887
- execSync("pkill -TERM -f chrome", {
887
+ execSync("pkill -TERM -f '(chrome|chromium)'", {
888
888
  timeout: 5e3,
889
889
  stdio: ["pipe", "pipe", "pipe"]
890
890
  });
@@ -898,44 +898,78 @@ async function killChromeGracefully() {
898
898
  }
899
899
  const start = Date.now();
900
900
  while (Date.now() - start < 8e3) {
901
- if (!isChromeRunning()) return;
901
+ if (!isChromeRunning()) {
902
+ log.debug(`Chrome exited after ${Date.now() - start}ms`);
903
+ return;
904
+ }
902
905
  await new Promise((r) => setTimeout(r, 500));
903
906
  }
907
+ log.debug("Chrome still running after graceful quit, force-killing...");
904
908
  try {
905
909
  if (os === "win32") {
906
910
  execSync("taskkill /F /IM chrome.exe", {
907
911
  stdio: ["pipe", "pipe", "pipe"]
908
912
  });
909
913
  } else {
910
- execSync("pkill -9 -f chrome", {
914
+ execSync("pkill -9 -f '(chrome|chromium)'", {
911
915
  stdio: ["pipe", "pipe", "pipe"]
912
916
  });
913
917
  }
914
918
  } catch {
915
919
  }
916
920
  await new Promise((r) => setTimeout(r, 1e3));
921
+ if (os !== "win32") {
922
+ const home = process.env.HOME;
923
+ if (home) {
924
+ const lockPaths = os === "darwin" ? [
925
+ `${home}/Library/Application Support/Google/Chrome/SingletonLock`,
926
+ `${home}/Library/Application Support/Google/Chrome/SingletonSocket`,
927
+ `${home}/Library/Application Support/Google/Chrome/SingletonCookie`
928
+ ] : [
929
+ `${home}/.config/google-chrome/SingletonLock`,
930
+ `${home}/.config/google-chrome/SingletonSocket`,
931
+ `${home}/.config/google-chrome/SingletonCookie`,
932
+ `${home}/.config/chromium/SingletonLock`,
933
+ `${home}/.config/chromium/SingletonSocket`,
934
+ `${home}/.config/chromium/SingletonCookie`
935
+ ];
936
+ for (const lockPath of lockPaths) {
937
+ try {
938
+ if (existsSync(lockPath)) {
939
+ unlinkSync(lockPath);
940
+ log.debug(`Removed stale lock: ${lockPath}`);
941
+ }
942
+ } catch {
943
+ }
944
+ }
945
+ }
946
+ }
917
947
  }
918
948
  function spawnChrome(chromePath, port) {
919
- const os = platform();
920
949
  const cdpFlag = `--remote-debugging-port=${port}`;
921
- let child;
922
- if (os === "darwin") {
923
- const appName = chromePath.includes("Chromium") ? "Chromium" : chromePath.includes("Canary") ? "Google Chrome Canary" : "Google Chrome";
924
- log.debug(`Spawning Chrome via: open -a "${appName}" --args ${cdpFlag} --restore-last-session`);
925
- child = spawn("open", ["-a", appName, "--args", cdpFlag, "--restore-last-session"], {
926
- detached: true,
927
- stdio: "ignore"
928
- });
929
- } else {
930
- log.debug(`Spawning Chrome via: ${chromePath} ${cdpFlag} --restore-last-session`);
931
- child = spawn(chromePath, [cdpFlag, "--restore-last-session"], {
932
- detached: true,
933
- stdio: "ignore"
934
- });
935
- }
950
+ log.debug(`Spawning Chrome: ${chromePath} ${cdpFlag} --restore-last-session`);
951
+ const child = spawn(chromePath, [cdpFlag, "--restore-last-session"], {
952
+ detached: true,
953
+ stdio: ["ignore", "pipe", "pipe"]
954
+ });
955
+ let stderr = "";
956
+ child.stderr?.on("data", (chunk) => {
957
+ stderr += chunk.toString();
958
+ });
936
959
  child.on("error", (err) => {
937
960
  log.error(`Chrome spawn error: ${err.message}`);
938
961
  });
962
+ child.on("exit", (code, signal) => {
963
+ if (code !== null && code !== 0) {
964
+ log.debug(`Chrome exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`);
965
+ if (stderr) {
966
+ const lines = stderr.split("\n").filter(Boolean).slice(0, 5);
967
+ for (const line of lines) {
968
+ log.debug(` chrome stderr: ${line}`);
969
+ }
970
+ }
971
+ }
972
+ });
939
973
  child.unref();
940
974
  return child;
941
975
  }
@@ -989,34 +1023,57 @@ async function ensureBrowserAvailable(port = 9222) {
989
1023
  if (running) {
990
1024
  log.debug("Killing Chrome gracefully for restart with CDP...");
991
1025
  await killChromeGracefully();
992
- spawnChrome(chromePath, port);
1026
+ await new Promise((r) => setTimeout(r, 2e3));
1027
+ const child2 = spawnChrome(chromePath, port);
993
1028
  if (await waitForCDP(browser)) {
994
1029
  return { success: true, action: "restarted", chromePath };
995
1030
  }
1031
+ if (child2.exitCode !== null) {
1032
+ log.debug(
1033
+ `Chrome process already exited (code ${child2.exitCode}) \u2014 may have crashed or profile is locked`
1034
+ );
1035
+ return {
1036
+ success: false,
1037
+ action: "launch_failed",
1038
+ chromePath,
1039
+ detail: `Chrome exited immediately (code ${child2.exitCode}). This often means the profile is locked by another Chrome instance. Try closing all Chrome windows first, then run assistme again.`
1040
+ };
1041
+ }
996
1042
  log.debug("First CDP wait timed out after restart, retrying...");
997
1043
  if (await waitForCDP(browser, 15e3)) {
998
1044
  return { success: true, action: "restarted", chromePath };
999
1045
  }
1046
+ const stillRunning2 = isChromeRunning();
1000
1047
  return {
1001
1048
  success: false,
1002
1049
  action: "launch_failed",
1003
1050
  chromePath,
1004
- detail: "Chrome was restarted but CDP did not become reachable within timeout."
1051
+ detail: stillRunning2 ? "Chrome is running but CDP port is not responding. Chrome may have started without the --remote-debugging-port flag. Try: 1) Quit Chrome completely, 2) Run assistme again." : "Chrome was restarted but exited unexpectedly. Check if Chrome can start normally from the command line."
1005
1052
  };
1006
1053
  }
1007
- spawnChrome(chromePath, port);
1054
+ const child = spawnChrome(chromePath, port);
1008
1055
  if (await waitForCDP(browser)) {
1009
1056
  return { success: true, action: "launched", chromePath };
1010
1057
  }
1058
+ if (child.exitCode !== null) {
1059
+ log.debug(`Chrome process already exited (code ${child.exitCode})`);
1060
+ return {
1061
+ success: false,
1062
+ action: "launch_failed",
1063
+ chromePath,
1064
+ detail: `Chrome exited immediately (code ${child.exitCode}). Try launching Chrome manually to see any error dialogs.`
1065
+ };
1066
+ }
1011
1067
  log.debug("First CDP wait timed out after launch, retrying...");
1012
1068
  if (await waitForCDP(browser, 15e3)) {
1013
1069
  return { success: true, action: "launched", chromePath };
1014
1070
  }
1071
+ const stillRunning = isChromeRunning();
1015
1072
  return {
1016
1073
  success: false,
1017
1074
  action: "launch_failed",
1018
1075
  chromePath,
1019
- detail: "Chrome was launched but CDP did not become reachable within timeout."
1076
+ detail: stillRunning ? "Chrome is running but CDP port is not responding. Try quitting Chrome completely and running assistme again." : "Chrome exited unexpectedly after launch."
1020
1077
  };
1021
1078
  }
1022
1079
  var browserInstance = null;
@@ -1172,7 +1229,7 @@ import {
1172
1229
  readFileSync,
1173
1230
  writeFileSync,
1174
1231
  statSync,
1175
- unlinkSync,
1232
+ unlinkSync as unlinkSync2,
1176
1233
  rmSync
1177
1234
  } from "fs";
1178
1235
  import { join, basename, dirname } from "path";
@@ -1592,9 +1649,9 @@ ${content}
1592
1649
  await execUnzip(`unzip -o "${zipPath}" -d "${skillDir}"`, {
1593
1650
  timeout: 15e3
1594
1651
  });
1595
- unlinkSync(zipPath);
1652
+ unlinkSync2(zipPath);
1596
1653
  } catch {
1597
- unlinkSync(zipPath);
1654
+ unlinkSync2(zipPath);
1598
1655
  const fileResp = await fetch(
1599
1656
  `${CLAWHUB_API}/skills/${encodeURIComponent(name)}/file?path=SKILL.md&tag=latest`
1600
1657
  );
@@ -1665,7 +1722,7 @@ ${content}
1665
1722
  if (dir !== SKILLS_DIR) {
1666
1723
  rmSync(dir, { recursive: true, force: true });
1667
1724
  } else {
1668
- unlinkSync(skill.filePath);
1725
+ unlinkSync2(skill.filePath);
1669
1726
  }
1670
1727
  this.skills.delete(name);
1671
1728
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "AssistMe CLI Agent - AI-powered assistant that controls your real browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,7 @@
17
17
  import { WebSocket } from "ws";
18
18
  import { execSync, spawn, type ChildProcess } from "node:child_process";
19
19
  import { platform } from "node:os";
20
- import { existsSync } from "node:fs";
20
+ import { existsSync, unlinkSync } from "node:fs";
21
21
  import { log } from "../utils/logger.js";
22
22
 
23
23
  interface CDPTab {
@@ -607,8 +607,9 @@ export function isChromeRunning(): boolean {
607
607
  return out.includes("chrome.exe");
608
608
  }
609
609
  if (platform() === "darwin") {
610
- // Match the actual macOS process name "Google Chrome" (not helper processes)
611
- const out = execSync('pgrep -f "Google Chrome.app/Contents/MacOS/Google Chrome$"', {
610
+ // Match the main Chrome process (not helper/renderer sub-processes).
611
+ // No trailing $ the process command line includes flags after the binary.
612
+ const out = execSync('pgrep -f "Google Chrome.app/Contents/MacOS/Google Chrome"', {
612
613
  encoding: "utf-8",
613
614
  stdio: ["pipe", "pipe", "pipe"],
614
615
  });
@@ -638,7 +639,7 @@ async function killChromeGracefully(): Promise<void> {
638
639
  stdio: ["pipe", "pipe", "pipe"],
639
640
  });
640
641
  } else if (os === "linux") {
641
- execSync("pkill -TERM -f chrome", {
642
+ execSync("pkill -TERM -f '(chrome|chromium)'", {
642
643
  timeout: 5000,
643
644
  stdio: ["pipe", "pipe", "pipe"],
644
645
  });
@@ -655,10 +656,15 @@ async function killChromeGracefully(): Promise<void> {
655
656
  // Wait for Chrome to fully exit (up to 8s)
656
657
  const start = Date.now();
657
658
  while (Date.now() - start < 8000) {
658
- if (!isChromeRunning()) return;
659
+ if (!isChromeRunning()) {
660
+ log.debug(`Chrome exited after ${Date.now() - start}ms`);
661
+ return;
662
+ }
659
663
  await new Promise((r) => setTimeout(r, 500));
660
664
  }
661
665
 
666
+ log.debug("Chrome still running after graceful quit, force-killing...");
667
+
662
668
  // Force kill if still alive
663
669
  try {
664
670
  if (os === "win32") {
@@ -666,14 +672,49 @@ async function killChromeGracefully(): Promise<void> {
666
672
  stdio: ["pipe", "pipe", "pipe"],
667
673
  });
668
674
  } else {
669
- execSync("pkill -9 -f chrome", {
675
+ execSync("pkill -9 -f '(chrome|chromium)'", {
670
676
  stdio: ["pipe", "pipe", "pipe"],
671
677
  });
672
678
  }
673
679
  } catch {
674
680
  /* already dead */
675
681
  }
682
+
683
+ // Wait for processes to fully terminate after SIGKILL
676
684
  await new Promise((r) => setTimeout(r, 1000));
685
+
686
+ // On macOS/Linux, remove the Chrome profile SingletonLock that may linger
687
+ // after a force-kill, preventing the next Chrome instance from starting.
688
+ if (os !== "win32") {
689
+ const home = process.env.HOME;
690
+ if (home) {
691
+ const lockPaths =
692
+ os === "darwin"
693
+ ? [
694
+ `${home}/Library/Application Support/Google/Chrome/SingletonLock`,
695
+ `${home}/Library/Application Support/Google/Chrome/SingletonSocket`,
696
+ `${home}/Library/Application Support/Google/Chrome/SingletonCookie`,
697
+ ]
698
+ : [
699
+ `${home}/.config/google-chrome/SingletonLock`,
700
+ `${home}/.config/google-chrome/SingletonSocket`,
701
+ `${home}/.config/google-chrome/SingletonCookie`,
702
+ `${home}/.config/chromium/SingletonLock`,
703
+ `${home}/.config/chromium/SingletonSocket`,
704
+ `${home}/.config/chromium/SingletonCookie`,
705
+ ];
706
+ for (const lockPath of lockPaths) {
707
+ try {
708
+ if (existsSync(lockPath)) {
709
+ unlinkSync(lockPath);
710
+ log.debug(`Removed stale lock: ${lockPath}`);
711
+ }
712
+ } catch {
713
+ /* best effort */
714
+ }
715
+ }
716
+ }
717
+ }
677
718
  }
678
719
 
679
720
  /**
@@ -681,35 +722,40 @@ async function killChromeGracefully(): Promise<void> {
681
722
  * Returns the child process so callers can detect early failures.
682
723
  */
683
724
  function spawnChrome(chromePath: string, port: number): ChildProcess {
684
- const os = platform();
685
725
  const cdpFlag = `--remote-debugging-port=${port}`;
686
726
 
687
- let child: ChildProcess;
727
+ // Always invoke the Chrome binary directly rather than `open -a`.
728
+ // On macOS, `open -a` silently ignores --args when Chrome is already
729
+ // running, which would cause CDP to never be enabled.
730
+ log.debug(`Spawning Chrome: ${chromePath} ${cdpFlag} --restore-last-session`);
731
+ const child = spawn(chromePath, [cdpFlag, "--restore-last-session"], {
732
+ detached: true,
733
+ stdio: ["ignore", "pipe", "pipe"],
734
+ });
688
735
 
689
- if (os === "darwin") {
690
- // Determine app name from binary path
691
- const appName = chromePath.includes("Chromium")
692
- ? "Chromium"
693
- : chromePath.includes("Canary")
694
- ? "Google Chrome Canary"
695
- : "Google Chrome";
696
- log.debug(`Spawning Chrome via: open -a "${appName}" --args ${cdpFlag} --restore-last-session`);
697
- child = spawn("open", ["-a", appName, "--args", cdpFlag, "--restore-last-session"], {
698
- detached: true,
699
- stdio: "ignore",
700
- });
701
- } else {
702
- log.debug(`Spawning Chrome via: ${chromePath} ${cdpFlag} --restore-last-session`);
703
- child = spawn(chromePath, [cdpFlag, "--restore-last-session"], {
704
- detached: true,
705
- stdio: "ignore",
706
- });
707
- }
736
+ // Capture stderr for diagnostics — Chrome prints errors here
737
+ let stderr = "";
738
+ child.stderr?.on("data", (chunk: Buffer) => {
739
+ stderr += chunk.toString();
740
+ });
708
741
 
709
742
  child.on("error", (err) => {
710
743
  log.error(`Chrome spawn error: ${err.message}`);
711
744
  });
712
745
 
746
+ child.on("exit", (code, signal) => {
747
+ if (code !== null && code !== 0) {
748
+ log.debug(`Chrome exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`);
749
+ if (stderr) {
750
+ // Log first few lines of stderr for diagnostics
751
+ const lines = stderr.split("\n").filter(Boolean).slice(0, 5);
752
+ for (const line of lines) {
753
+ log.debug(` chrome stderr: ${line}`);
754
+ }
755
+ }
756
+ }
757
+ });
758
+
713
759
  child.unref();
714
760
  return child;
715
761
  }
@@ -811,44 +857,77 @@ export async function ensureBrowserAvailable(port = 9222): Promise<AutoLaunchRes
811
857
  if (running) {
812
858
  log.debug("Killing Chrome gracefully for restart with CDP...");
813
859
  await killChromeGracefully();
814
- spawnChrome(chromePath, port);
860
+
861
+ // Extra wait for profile lock release after kill
862
+ await new Promise((r) => setTimeout(r, 2000));
863
+
864
+ const child = spawnChrome(chromePath, port);
815
865
 
816
866
  if (await waitForCDP(browser)) {
817
867
  return { success: true, action: "restarted", chromePath };
818
868
  }
819
869
 
870
+ // Check if Chrome is still alive before retrying
871
+ if (child.exitCode !== null) {
872
+ log.debug(
873
+ `Chrome process already exited (code ${child.exitCode}) — may have crashed or profile is locked`
874
+ );
875
+ return {
876
+ success: false,
877
+ action: "launch_failed",
878
+ chromePath,
879
+ detail: `Chrome exited immediately (code ${child.exitCode}). This often means the profile is locked by another Chrome instance. Try closing all Chrome windows first, then run assistme again.`,
880
+ };
881
+ }
882
+
820
883
  // Retry once — Chrome can be slow to start (extensions, session restore)
821
884
  log.debug("First CDP wait timed out after restart, retrying...");
822
885
  if (await waitForCDP(browser, 15000)) {
823
886
  return { success: true, action: "restarted", chromePath };
824
887
  }
825
888
 
889
+ const stillRunning = isChromeRunning();
826
890
  return {
827
891
  success: false,
828
892
  action: "launch_failed",
829
893
  chromePath,
830
- detail: "Chrome was restarted but CDP did not become reachable within timeout.",
894
+ detail: stillRunning
895
+ ? "Chrome is running but CDP port is not responding. Chrome may have started without the --remote-debugging-port flag. Try: 1) Quit Chrome completely, 2) Run assistme again."
896
+ : "Chrome was restarted but exited unexpectedly. Check if Chrome can start normally from the command line.",
831
897
  };
832
898
  }
833
899
 
834
900
  // Case 4: Chrome not running → launch
835
- spawnChrome(chromePath, port);
901
+ const child = spawnChrome(chromePath, port);
836
902
 
837
903
  if (await waitForCDP(browser)) {
838
904
  return { success: true, action: "launched", chromePath };
839
905
  }
840
906
 
907
+ if (child.exitCode !== null) {
908
+ log.debug(`Chrome process already exited (code ${child.exitCode})`);
909
+ return {
910
+ success: false,
911
+ action: "launch_failed",
912
+ chromePath,
913
+ detail: `Chrome exited immediately (code ${child.exitCode}). Try launching Chrome manually to see any error dialogs.`,
914
+ };
915
+ }
916
+
841
917
  // Retry once
842
918
  log.debug("First CDP wait timed out after launch, retrying...");
843
919
  if (await waitForCDP(browser, 15000)) {
844
920
  return { success: true, action: "launched", chromePath };
845
921
  }
846
922
 
923
+ const stillRunning = isChromeRunning();
847
924
  return {
848
925
  success: false,
849
926
  action: "launch_failed",
850
927
  chromePath,
851
- detail: "Chrome was launched but CDP did not become reachable within timeout.",
928
+ detail: stillRunning
929
+ ? "Chrome is running but CDP port is not responding. Try quitting Chrome completely and running assistme again."
930
+ : "Chrome exited unexpectedly after launch.",
852
931
  };
853
932
  }
854
933