trymeanwhile 1.0.0 → 1.1.0

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.
Files changed (2) hide show
  1. package/bin/meanwhile.js +60 -39
  2. package/package.json +1 -1
package/bin/meanwhile.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- // Node reimplementation of install.sh/install.ps1 -- one script instead of
3
- // two, since Node itself is already cross-platform. Behavior is kept in
2
+ // Node reimplementation of install.sh/install.ps1/install_copilot.sh -- one
3
+ // script instead of three, since Node itself is already cross-platform and
4
+ // can detect which tool(s) are actually installed. Behavior is kept in
4
5
  // lockstep with those scripts (same install dir, same settings.json shape,
5
- // same non-fatal certifi step) so all three installers produce an
6
- // identical result regardless of which one someone happens to run.
6
+ // same non-fatal certifi step) so every installer produces an identical
7
+ // result regardless of which one someone happens to run.
7
8
 
8
9
  const fs = require("fs");
9
10
  const os = require("os");
@@ -19,10 +20,13 @@ function log(msg) {
19
20
  console.log(`meanwhile: ${msg}`);
20
21
  }
21
22
 
23
+ function commandExists(cmd) {
24
+ return spawnSync(cmd, ["--version"], { stdio: "ignore" }).status === 0;
25
+ }
26
+
22
27
  function findPython() {
23
28
  for (const candidate of ["python3", "python", "py"]) {
24
- const result = spawnSync(candidate, ["--version"], { stdio: "ignore" });
25
- if (result.status === 0) return candidate;
29
+ if (commandExists(candidate)) return candidate;
26
30
  }
27
31
  return null;
28
32
  }
@@ -35,6 +39,36 @@ function openUrl(url) {
35
39
  exec(cmd, () => {});
36
40
  }
37
41
 
42
+ async function ensureCertifi(python) {
43
+ const hasCertifi = spawnSync(python, ["-c", "import certifi"], { stdio: "ignore" }).status === 0;
44
+ if (hasCertifi) return;
45
+ // certifi is a nice-to-have, not a hard requirement -- both status line
46
+ // scripts fall back to the system's own certificate store if it's
47
+ // missing. Never let a failed pip install here take down the install.
48
+ log("installing certifi (helps with HTTPS, not required)...");
49
+ const pipAttempts = [
50
+ ["-m", "pip", "install", "--quiet", "certifi"],
51
+ ["-m", "pip", "install", "--quiet", "--user", "certifi"],
52
+ ["-m", "pip", "install", "--quiet", "--break-system-packages", "certifi"],
53
+ ];
54
+ const installed = pipAttempts.some((args) => spawnSync(python, args, { stdio: "ignore" }).status === 0);
55
+ if (!installed) log("couldn't install certifi, continuing without it (falls back automatically)");
56
+ }
57
+
58
+ async function downloadTo(remoteName, localPath) {
59
+ const res = await fetch(`${SERVER}/${remoteName}`);
60
+ if (!res.ok) throw new Error(`couldn't download ${remoteName} (${res.status})`);
61
+ fs.writeFileSync(localPath, await res.text());
62
+ }
63
+
64
+ function wireSettings(settingsPath, python, scriptPath) {
65
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
66
+ const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, "utf8") || "{}") : {};
67
+ settings.statusLine = { type: "command", command: `${python} "${scriptPath}"` };
68
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
69
+ log(`wired into ${settingsPath}`);
70
+ }
71
+
38
72
  async function main() {
39
73
  log(`installing to ${INSTALL_DIR}`);
40
74
  fs.mkdirSync(INSTALL_DIR, { recursive: true });
@@ -43,55 +77,42 @@ async function main() {
43
77
  // Real install ID, generated on the device the moment an install
44
78
  // actually happens -- not guessed ahead of time by the website. Only
45
79
  // if one doesn't already exist, so re-running this never clobbers an
46
- // existing install's history with a fresh random ID.
80
+ // existing install's history with a fresh random ID. Shared across
81
+ // both integrations below, same as the shell installers.
47
82
  const idPath = path.join(STATE_DIR, "install_id");
48
83
  if (!fs.existsSync(idPath)) {
49
84
  fs.writeFileSync(idPath, crypto.randomUUID(), { mode: 0o600 });
50
85
  }
51
86
  const installId = fs.readFileSync(idPath, "utf8").trim();
52
87
 
53
- const res = await fetch(`${SERVER}/statusline.py`);
54
- if (!res.ok) {
55
- console.error(`meanwhile: couldn't download statusline.py (${res.status}) -- try again in a moment.`);
56
- process.exit(1);
57
- }
58
- fs.writeFileSync(path.join(INSTALL_DIR, "statusline.py"), await res.text());
59
-
60
88
  const python = findPython();
61
89
  if (!python) {
62
90
  log("couldn't find Python on your PATH.");
63
91
  log("install it from https://python.org, then run `npx trymeanwhile` again.");
64
92
  process.exit(1);
65
93
  }
94
+ await ensureCertifi(python);
66
95
 
67
- // certifi is a nice-to-have, not a hard requirement -- statusline.py
68
- // falls back to the system's own certificate store if it's missing.
69
- // Never let a failed pip install here take down the rest of the setup.
70
- const hasCertifi = spawnSync(python, ["-c", "import certifi"], { stdio: "ignore" }).status === 0;
71
- if (!hasCertifi) {
72
- log("installing certifi (helps with HTTPS, not required)...");
73
- const pipAttempts = [
74
- ["-m", "pip", "install", "--quiet", "certifi"],
75
- ["-m", "pip", "install", "--quiet", "--user", "certifi"],
76
- ["-m", "pip", "install", "--quiet", "--break-system-packages", "certifi"],
77
- ];
78
- const installed = pipAttempts.some(
79
- (args) => spawnSync(python, args, { stdio: "ignore" }).status === 0
80
- );
81
- if (!installed) log("couldn't install certifi, continuing without it (statusline.py falls back automatically)");
82
- }
96
+ // Wire whichever tool(s) are actually on this machine, so the same
97
+ // command works everywhere instead of a different one per tool. Claude
98
+ // Code is always wired -- it's the primary target this package was
99
+ // built for -- and Copilot CLI is wired too if it's detected on PATH.
100
+ const wireCopilot = commandExists("copilot");
83
101
 
84
- const scriptPath = path.join(INSTALL_DIR, "statusline.py");
85
- const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
86
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
87
- const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, "utf8") || "{}") : {};
88
- settings.statusLine = { type: "command", command: `${python} "${scriptPath}"` };
89
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
90
- log(`wired into ${settingsPath}`);
102
+ const claudeScript = path.join(INSTALL_DIR, "statusline.py");
103
+ await downloadTo("statusline.py", claudeScript);
104
+ wireSettings(path.join(os.homedir(), ".claude", "settings.json"), python, claudeScript);
105
+
106
+ if (wireCopilot) {
107
+ const copilotScript = path.join(INSTALL_DIR, "copilot_statusline.py");
108
+ await downloadTo("copilot_statusline.py", copilotScript);
109
+ wireSettings(path.join(os.homedir(), ".copilot", "settings.json"), python, copilotScript);
110
+ log("Copilot CLI detected -- wired that too, same install ID, earnings share one balance.");
111
+ }
91
112
 
92
- log("installed. Restart Claude Code (close and reopen your terminal) to see it live.");
113
+ log("installed. Restart Claude Code (and Copilot CLI, if wired) to see it live.");
93
114
  log("to check earnings or register a payout email later, run:");
94
- log(` ${python} "${scriptPath}" --claim`);
115
+ log(` ${python} "${claudeScript}" --claim`);
95
116
 
96
117
  // Open the browser straight to the claim page -- same pattern as
97
118
  // `gh auth login` / `vercel login` / `wrangler login`. This is the only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trymeanwhile",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Get paid while your AI coding agent thinks. Wires a disclosed status line into Claude Code / Copilot CLI.",
5
5
  "bin": {
6
6
  "meanwhile": "bin/meanwhile.js"