trymeanwhile 1.2.0 → 1.3.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 +87 -51
- package/package.json +1 -1
package/bin/meanwhile.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// result regardless of which one someone happens to run.
|
|
2
|
+
// The `npx trymeanwhile` entry point -- one cross-platform script that
|
|
3
|
+
// detects which tool(s) are actually installed and wires them up. The
|
|
4
|
+
// status line it wires in (statusline.js) runs on Node too, not Python:
|
|
5
|
+
// Node is already guaranteed present since npx can't invoke this script
|
|
6
|
+
// without it, so there's no second runtime to find, verify, or fail on.
|
|
8
7
|
|
|
9
8
|
const fs = require("fs");
|
|
10
9
|
const os = require("os");
|
|
@@ -16,19 +15,24 @@ const SERVER = "https://trymeanwhile.online";
|
|
|
16
15
|
const INSTALL_DIR = path.join(os.homedir(), ".deadtime-client");
|
|
17
16
|
const STATE_DIR = path.join(os.homedir(), ".deadtime");
|
|
18
17
|
|
|
18
|
+
const VERBOSE = process.argv.includes("--verbose");
|
|
19
|
+
|
|
19
20
|
function log(msg) {
|
|
20
21
|
console.log(`meanwhile: ${msg}`);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
// Wiring detail nobody needs to see on a normal run -- which settings.json,
|
|
25
|
+
// which script path, that kind of thing. Real information, just not the
|
|
26
|
+
// first-run experience: it used to be seven identical-looking lines with no
|
|
27
|
+
// hierarchy, so the one line that actually mattered (restart your tool)
|
|
28
|
+
// read the same as housekeeping noise. Still here for anyone debugging why
|
|
29
|
+
// it didn't wire correctly -- just behind --verbose instead of always on.
|
|
30
|
+
function vlog(msg) {
|
|
31
|
+
if (VERBOSE) log(msg);
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
if (commandExists(candidate)) return candidate;
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
34
|
+
function commandExists(cmd) {
|
|
35
|
+
return spawnSync(cmd, ["--version"], { stdio: "ignore" }).status === 0;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function openUrl(url) {
|
|
@@ -39,34 +43,18 @@ function openUrl(url) {
|
|
|
39
43
|
exec(cmd, () => {});
|
|
40
44
|
}
|
|
41
45
|
|
|
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
46
|
async function downloadTo(remoteName, localPath) {
|
|
59
47
|
const res = await fetch(`${SERVER}/${remoteName}`);
|
|
60
48
|
if (!res.ok) throw new Error(`couldn't download ${remoteName} (${res.status})`);
|
|
61
49
|
fs.writeFileSync(localPath, await res.text());
|
|
62
50
|
}
|
|
63
51
|
|
|
64
|
-
function wireSettings(settingsPath,
|
|
52
|
+
function wireSettings(settingsPath, runtime, scriptPath) {
|
|
65
53
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
66
54
|
const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, "utf8") || "{}") : {};
|
|
67
|
-
settings.statusLine = { type: "command", command:
|
|
55
|
+
settings.statusLine = { type: "command", command: `"${runtime}" "${scriptPath}"` };
|
|
68
56
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
69
|
-
|
|
57
|
+
vlog(`wired into ${settingsPath}`);
|
|
70
58
|
}
|
|
71
59
|
|
|
72
60
|
// Wraps any long-running shell command (npm install, docker build, terraform
|
|
@@ -114,13 +102,49 @@ async function wrapCommand(args) {
|
|
|
114
102
|
});
|
|
115
103
|
}
|
|
116
104
|
|
|
105
|
+
// `npx trymeanwhile claim` -- the whole point of this is that nobody
|
|
106
|
+
// should ever need to remember (or read) a raw node/path/--claim
|
|
107
|
+
// incantation just to check what they've earned. Same install ID file,
|
|
108
|
+
// same /earnings endpoint statusline.js already hits, just reachable
|
|
109
|
+
// through the one command name people already typed once.
|
|
110
|
+
async function claimCommand() {
|
|
111
|
+
const idPath = path.join(STATE_DIR, "install_id");
|
|
112
|
+
if (!fs.existsSync(idPath)) {
|
|
113
|
+
log("nothing to claim yet -- you haven't installed. run `npx trymeanwhile` first.");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const installId = fs.readFileSync(idPath, "utf8").trim();
|
|
117
|
+
const claimUrl = `${SERVER}/claim?id=${installId}`;
|
|
118
|
+
|
|
119
|
+
console.log("meanwhile -- your account");
|
|
120
|
+
console.log(` ID: ${installId}`);
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(`${SERVER}/earnings?id=${installId}`);
|
|
123
|
+
if (res.ok) {
|
|
124
|
+
const earnings = await res.json();
|
|
125
|
+
console.log(` earned: $${Number(earnings.user_earnings).toFixed(2)}`);
|
|
126
|
+
console.log(` shown: ${earnings.total_calls} lines (${earnings.sponsor_calls} sponsored)`);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(" earned: (couldn't reach server -- check your connection)");
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
console.log(" earned: (couldn't reach server -- check your connection)");
|
|
132
|
+
}
|
|
133
|
+
console.log();
|
|
134
|
+
console.log(` register a payout email: ${claimUrl}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
117
137
|
async function main() {
|
|
118
138
|
if (process.argv[2] === "wrap") {
|
|
119
139
|
await wrapCommand(process.argv.slice(3));
|
|
120
140
|
return;
|
|
121
141
|
}
|
|
142
|
+
if (process.argv[2] === "claim") {
|
|
143
|
+
await claimCommand();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
122
146
|
|
|
123
|
-
|
|
147
|
+
vlog(`installing to ${INSTALL_DIR}`);
|
|
124
148
|
fs.mkdirSync(INSTALL_DIR, { recursive: true });
|
|
125
149
|
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
126
150
|
|
|
@@ -135,13 +159,14 @@ async function main() {
|
|
|
135
159
|
}
|
|
136
160
|
const installId = fs.readFileSync(idPath, "utf8").trim();
|
|
137
161
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
162
|
+
// Node, not Python: this script is already running under Node (it can't
|
|
163
|
+
// not be -- npx just invoked it), so there is no second runtime to find
|
|
164
|
+
// or verify. process.execPath is the absolute path to the exact node
|
|
165
|
+
// binary running right now, more robust than trusting a bare `node` on
|
|
166
|
+
// PATH (some distros only ship `nodejs`). This used to hard-require
|
|
167
|
+
// python3/python/py on top of Node and fail the whole install for
|
|
168
|
+
// anyone without it -- a real dead stop, not a cosmetic gap.
|
|
169
|
+
const node = process.execPath;
|
|
145
170
|
|
|
146
171
|
// Wire whichever tool(s) are actually on this machine, so the same
|
|
147
172
|
// command works everywhere instead of a different one per tool. Claude
|
|
@@ -149,20 +174,31 @@ async function main() {
|
|
|
149
174
|
// built for -- and Copilot CLI is wired too if it's detected on PATH.
|
|
150
175
|
const wireCopilot = commandExists("copilot");
|
|
151
176
|
|
|
152
|
-
const claudeScript = path.join(INSTALL_DIR, "statusline.
|
|
153
|
-
await downloadTo("statusline.
|
|
154
|
-
wireSettings(path.join(os.homedir(), ".claude", "settings.json"),
|
|
177
|
+
const claudeScript = path.join(INSTALL_DIR, "statusline.js");
|
|
178
|
+
await downloadTo("statusline.js", claudeScript);
|
|
179
|
+
wireSettings(path.join(os.homedir(), ".claude", "settings.json"), node, claudeScript);
|
|
155
180
|
|
|
156
181
|
if (wireCopilot) {
|
|
157
|
-
const copilotScript = path.join(INSTALL_DIR, "copilot_statusline.
|
|
158
|
-
await downloadTo("copilot_statusline.
|
|
159
|
-
wireSettings(path.join(os.homedir(), ".copilot", "settings.json"),
|
|
160
|
-
|
|
182
|
+
const copilotScript = path.join(INSTALL_DIR, "copilot_statusline.js");
|
|
183
|
+
await downloadTo("copilot_statusline.js", copilotScript);
|
|
184
|
+
wireSettings(path.join(os.homedir(), ".copilot", "settings.json"), node, copilotScript);
|
|
185
|
+
vlog("Copilot CLI detected -- wired that too, same install ID, earnings share one balance.");
|
|
161
186
|
}
|
|
162
187
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
188
|
+
// The one-time summary a real person actually reads. This used to be
|
|
189
|
+
// seven identically-styled lines -- the one thing that actually matters
|
|
190
|
+
// (restart your tool) read no differently than housekeeping. Blank
|
|
191
|
+
// lines and indentation do the hierarchy work no --verbose flag can.
|
|
192
|
+
console.log("meanwhile: installed. congratulations, you now get paid to wait.");
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(" restart Claude Code to see it live");
|
|
195
|
+
console.log(" (the one step between you and money -- yes, actually do it)");
|
|
196
|
+
console.log();
|
|
197
|
+
if (wireCopilot) {
|
|
198
|
+
console.log("Copilot CLI's in on this too, if it's here. Same balance either way.");
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
201
|
+
console.log("Check what you've earned: npx trymeanwhile claim");
|
|
166
202
|
|
|
167
203
|
// Open the browser straight to the claim page -- same pattern as
|
|
168
204
|
// `gh auth login` / `vercel login` / `wrangler login`. This is the only
|
|
@@ -171,7 +207,7 @@ async function main() {
|
|
|
171
207
|
// because someone merely copied a command.
|
|
172
208
|
const claimUrl = `${SERVER}/claim.html?id=${installId}`;
|
|
173
209
|
openUrl(claimUrl);
|
|
174
|
-
log(`if a browser tab didn't open: ${claimUrl}`);
|
|
210
|
+
console.log(`(if a browser tab didn't open: ${claimUrl})`);
|
|
175
211
|
}
|
|
176
212
|
|
|
177
213
|
main().catch((err) => {
|
package/package.json
CHANGED