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.
- package/bin/meanwhile.js +60 -39
- 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
|
|
3
|
-
//
|
|
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
|
|
6
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 (
|
|
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} "${
|
|
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