@promptai.credit/cli 0.2.0 → 0.4.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/README.md +12 -2
- package/dist/index.js +469 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,13 +47,16 @@ State lives in `~/.promptai/` (`config.json`, `state.json`, `cli.log`).
|
|
|
47
47
|
npm install -g @promptai.credit/cli
|
|
48
48
|
promptai install claude # Claude Code hooks
|
|
49
49
|
promptai install cursor # Cursor Agents Window / classic IDE hooks
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`promptai install` then asks for your email and payout wallet, and docks ads on the **right** by default (like the IDE panel). Enter accepts a detected email.
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
```bash
|
|
52
55
|
promptai status # device, balance, banked ad watches, recent prompts
|
|
53
56
|
promptai watch # open a rewarded ad now (banks a credit for later)
|
|
54
|
-
promptai set wallet 0x... # payout address
|
|
55
57
|
promptai claim # USDC on Base
|
|
56
58
|
promptai set ads off # opt out any time
|
|
59
|
+
promptai set side left # or `default` for a normal browser tab
|
|
57
60
|
```
|
|
58
61
|
|
|
59
62
|
The default server is `https://api.promptai.credit`; point elsewhere with
|
|
@@ -67,6 +70,13 @@ Developing from the repo instead: `pnpm --filter @promptai.credit/cli build`, th
|
|
|
67
70
|
- Credits only accrue for turns funded by a **verified** ad watch, enforced
|
|
68
71
|
server-side (same rules as the Cursor extension: one session funds one prompt,
|
|
69
72
|
capped at $5).
|
|
73
|
+
- Monthly earn caps: $75/device and $150 cumulative per linked email (UTC
|
|
74
|
+
calendar month). Claims require a linked email that an admin has activated.
|
|
75
|
+
- The account email is auto-detected (Claude Code login in `~/.claude.json`,
|
|
76
|
+
the Cursor account in its state DB, then `git config user.email`) and offered
|
|
77
|
+
as the default during `promptai install`. Auto-detected emails link without
|
|
78
|
+
verification; typing a different address (or `promptai set email`) sends a
|
|
79
|
+
6-digit code to prove ownership.
|
|
70
80
|
- Headless environments (CI, ssh without a display) never get browser tabs and
|
|
71
81
|
simply skip crediting.
|
|
72
82
|
- Uninstall with `promptai uninstall claude`; other hooks in your settings are
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
4
|
+
import * as fs7 from "node:fs";
|
|
5
|
+
import * as path6 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/claude.ts
|
|
8
8
|
import * as crypto3 from "node:crypto";
|
|
9
|
-
import * as
|
|
10
|
-
import * as
|
|
11
|
-
import * as
|
|
9
|
+
import * as fs5 from "node:fs";
|
|
10
|
+
import * as os3 from "node:os";
|
|
11
|
+
import * as path4 from "node:path";
|
|
12
12
|
|
|
13
13
|
// src/api.ts
|
|
14
14
|
import * as crypto from "node:crypto";
|
|
@@ -43,6 +43,12 @@ function redeemCredit(serverUrl, params) {
|
|
|
43
43
|
function fetchBalance(serverUrl, deviceId) {
|
|
44
44
|
return request(`${serverUrl}/balance/${deviceId}`);
|
|
45
45
|
}
|
|
46
|
+
function linkEmail(serverUrl, params) {
|
|
47
|
+
return post(serverUrl, "/devices/link", params);
|
|
48
|
+
}
|
|
49
|
+
function verifyEmailLink(serverUrl, params) {
|
|
50
|
+
return post(serverUrl, "/devices/link/verify", params);
|
|
51
|
+
}
|
|
46
52
|
function claim(serverUrl, params) {
|
|
47
53
|
return post(serverUrl, "/claim", {
|
|
48
54
|
...params,
|
|
@@ -51,21 +57,9 @@ function claim(serverUrl, params) {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
// src/browser.ts
|
|
54
|
-
import { spawn } from "node:child_process";
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
58
|
-
}
|
|
59
|
-
function openInBrowser(url) {
|
|
60
|
-
if (!hasDisplay()) return false;
|
|
61
|
-
const [cmd, args2] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
62
|
-
try {
|
|
63
|
-
spawn(cmd, args2, { detached: true, stdio: "ignore" }).unref();
|
|
64
|
-
return true;
|
|
65
|
-
} catch {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
60
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
61
|
+
import * as fs2 from "node:fs";
|
|
62
|
+
import * as path2 from "node:path";
|
|
69
63
|
|
|
70
64
|
// src/config.ts
|
|
71
65
|
import * as crypto2 from "node:crypto";
|
|
@@ -94,8 +88,13 @@ function loadConfig() {
|
|
|
94
88
|
deviceId: "",
|
|
95
89
|
serverUrl: "https://api.promptai.credit",
|
|
96
90
|
wallet: "",
|
|
97
|
-
adsOptIn: true
|
|
91
|
+
adsOptIn: true,
|
|
92
|
+
email: "",
|
|
93
|
+
adSide: "right"
|
|
98
94
|
});
|
|
95
|
+
if (config.adSide !== "right" && config.adSide !== "left" && config.adSide !== "default") {
|
|
96
|
+
config.adSide = "right";
|
|
97
|
+
}
|
|
99
98
|
if (!config.deviceId) {
|
|
100
99
|
config.deviceId = crypto2.randomUUID();
|
|
101
100
|
saveConfig(config);
|
|
@@ -139,6 +138,240 @@ function log(message) {
|
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
|
|
141
|
+
// src/browser.ts
|
|
142
|
+
function hasDisplay() {
|
|
143
|
+
if (process.platform === "darwin" || process.platform === "win32") return true;
|
|
144
|
+
return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
145
|
+
}
|
|
146
|
+
function screenBounds() {
|
|
147
|
+
try {
|
|
148
|
+
if (process.platform === "darwin") {
|
|
149
|
+
const out = execFileSync(
|
|
150
|
+
"osascript",
|
|
151
|
+
["-e", 'tell application "Finder" to get bounds of window of desktop'],
|
|
152
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
153
|
+
).trim();
|
|
154
|
+
const [x1, y1, x2, y2] = out.split(",").map((s) => Number(s.trim()));
|
|
155
|
+
if ([x1, y1, x2, y2].every(Number.isFinite)) {
|
|
156
|
+
return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (process.platform === "linux") {
|
|
160
|
+
const out = execFileSync("xrandr", ["--current"], {
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
timeout: 3e3
|
|
163
|
+
});
|
|
164
|
+
const m = out.match(/(\d+)x(\d+)\s+\d+\.\d+\*/);
|
|
165
|
+
if (m) return { x: 0, y: 0, width: Number(m[1]), height: Number(m[2]) };
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
function windowRect(side) {
|
|
172
|
+
const screen = screenBounds() ?? { x: 0, y: 0, width: 1440, height: 900 };
|
|
173
|
+
const w = 500;
|
|
174
|
+
const h = Math.min(800, Math.max(560, screen.height - 80));
|
|
175
|
+
const y = screen.y + 40;
|
|
176
|
+
const x = side === "right" ? screen.x + screen.width - w - 16 : screen.x + 16;
|
|
177
|
+
return { x: Math.round(x), y: Math.round(y), w, h };
|
|
178
|
+
}
|
|
179
|
+
function chromeBin() {
|
|
180
|
+
const candidates = process.platform === "darwin" ? [
|
|
181
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
182
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
183
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
184
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
185
|
+
] : process.platform === "win32" ? [
|
|
186
|
+
path2.join(process.env.LOCALAPPDATA ?? "", "Google", "Chrome", "Application", "chrome.exe"),
|
|
187
|
+
path2.join(process.env.PROGRAMFILES ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
|
|
188
|
+
path2.join(
|
|
189
|
+
process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)",
|
|
190
|
+
"Google",
|
|
191
|
+
"Chrome",
|
|
192
|
+
"Application",
|
|
193
|
+
"chrome.exe"
|
|
194
|
+
),
|
|
195
|
+
path2.join(process.env.LOCALAPPDATA ?? "", "Microsoft", "Edge", "Application", "msedge.exe")
|
|
196
|
+
] : ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "brave-browser", "microsoft-edge"];
|
|
197
|
+
for (const candidate of candidates) {
|
|
198
|
+
if (!candidate) continue;
|
|
199
|
+
if (candidate.includes("/") || candidate.includes("\\")) {
|
|
200
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const resolved = execFileSync("which", [candidate], {
|
|
205
|
+
encoding: "utf8",
|
|
206
|
+
timeout: 2e3
|
|
207
|
+
}).trim();
|
|
208
|
+
if (resolved) return resolved;
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
function openDefault(url) {
|
|
215
|
+
const [cmd, args2] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
216
|
+
try {
|
|
217
|
+
spawn(cmd, args2, { detached: true, stdio: "ignore" }).unref();
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function openAndPlaceMac(url, rect) {
|
|
224
|
+
if (!openDefault(url)) return false;
|
|
225
|
+
const script = `
|
|
226
|
+
delay 0.9
|
|
227
|
+
tell application "System Events"
|
|
228
|
+
tell (first application process whose frontmost is true)
|
|
229
|
+
try
|
|
230
|
+
set position of front window to {${rect.x}, ${rect.y}}
|
|
231
|
+
set size of front window to {${rect.w}, ${rect.h}}
|
|
232
|
+
end try
|
|
233
|
+
end tell
|
|
234
|
+
end tell
|
|
235
|
+
`;
|
|
236
|
+
try {
|
|
237
|
+
spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
function openInBrowser(url, side = "right") {
|
|
243
|
+
if (!hasDisplay()) return false;
|
|
244
|
+
if (side === "default") return openDefault(url);
|
|
245
|
+
const rect = windowRect(side);
|
|
246
|
+
const chrome = chromeBin();
|
|
247
|
+
if (chrome) {
|
|
248
|
+
const profile = path2.join(promptaiDir(), "ad-window");
|
|
249
|
+
try {
|
|
250
|
+
spawn(
|
|
251
|
+
chrome,
|
|
252
|
+
[
|
|
253
|
+
`--user-data-dir=${profile}`,
|
|
254
|
+
`--app=${url}`,
|
|
255
|
+
`--window-position=${rect.x},${rect.y}`,
|
|
256
|
+
`--window-size=${rect.w},${rect.h}`,
|
|
257
|
+
"--new-window"
|
|
258
|
+
],
|
|
259
|
+
{ detached: true, stdio: "ignore" }
|
|
260
|
+
).unref();
|
|
261
|
+
return true;
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (process.platform === "darwin") return openAndPlaceMac(url, rect);
|
|
266
|
+
return openDefault(url);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/email.ts
|
|
270
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
271
|
+
import * as fs3 from "node:fs";
|
|
272
|
+
import * as os2 from "node:os";
|
|
273
|
+
import * as path3 from "node:path";
|
|
274
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
275
|
+
function clean(value) {
|
|
276
|
+
if (typeof value !== "string") return null;
|
|
277
|
+
const email = value.trim().toLowerCase();
|
|
278
|
+
return EMAIL_RE.test(email) ? email : null;
|
|
279
|
+
}
|
|
280
|
+
function fromClaudeJson() {
|
|
281
|
+
try {
|
|
282
|
+
const raw = fs3.readFileSync(path3.join(os2.homedir(), ".claude.json"), "utf8");
|
|
283
|
+
const data = JSON.parse(raw);
|
|
284
|
+
return clean(data.oauthAccount?.emailAddress);
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function cursorStateDbPath() {
|
|
290
|
+
const home = os2.homedir();
|
|
291
|
+
switch (process.platform) {
|
|
292
|
+
case "darwin":
|
|
293
|
+
return path3.join(
|
|
294
|
+
home,
|
|
295
|
+
"Library",
|
|
296
|
+
"Application Support",
|
|
297
|
+
"Cursor",
|
|
298
|
+
"User",
|
|
299
|
+
"globalStorage",
|
|
300
|
+
"state.vscdb"
|
|
301
|
+
);
|
|
302
|
+
case "win32":
|
|
303
|
+
return path3.join(
|
|
304
|
+
process.env.APPDATA ?? path3.join(home, "AppData", "Roaming"),
|
|
305
|
+
"Cursor",
|
|
306
|
+
"User",
|
|
307
|
+
"globalStorage",
|
|
308
|
+
"state.vscdb"
|
|
309
|
+
);
|
|
310
|
+
default:
|
|
311
|
+
return path3.join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function fromCursorStateDb() {
|
|
315
|
+
const src = cursorStateDbPath();
|
|
316
|
+
if (!fs3.existsSync(src)) return null;
|
|
317
|
+
let dir = null;
|
|
318
|
+
try {
|
|
319
|
+
dir = fs3.mkdtempSync(path3.join(os2.tmpdir(), "promptai-email-"));
|
|
320
|
+
const db = path3.join(dir, "state.vscdb");
|
|
321
|
+
fs3.copyFileSync(src, db);
|
|
322
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
323
|
+
if (fs3.existsSync(src + suffix)) fs3.copyFileSync(src + suffix, db + suffix);
|
|
324
|
+
}
|
|
325
|
+
const stdout = execFileSync2(
|
|
326
|
+
"sqlite3",
|
|
327
|
+
[db, "SELECT value FROM ItemTable WHERE key='cursorAuth/cachedEmail'"],
|
|
328
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
329
|
+
).trim();
|
|
330
|
+
if (stdout.startsWith('"')) {
|
|
331
|
+
try {
|
|
332
|
+
return clean(JSON.parse(stdout));
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return clean(stdout);
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
} finally {
|
|
341
|
+
if (dir) fs3.rmSync(dir, { recursive: true, force: true });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function fromGitConfig() {
|
|
345
|
+
try {
|
|
346
|
+
return clean(
|
|
347
|
+
execFileSync2("git", ["config", "--get", "user.email"], {
|
|
348
|
+
encoding: "utf8",
|
|
349
|
+
timeout: 5e3
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
} catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function detectEmail() {
|
|
357
|
+
const claude = fromClaudeJson();
|
|
358
|
+
if (claude) return { email: claude, source: "claude-account" };
|
|
359
|
+
const cursor = fromCursorStateDb();
|
|
360
|
+
if (cursor) return { email: cursor, source: "cursor-account" };
|
|
361
|
+
const git = fromGitConfig();
|
|
362
|
+
if (git) return { email: git, source: "git-config" };
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
function resolveEmail(config) {
|
|
366
|
+
if (config.email) return config.email;
|
|
367
|
+
const detected = detectEmail();
|
|
368
|
+
if (!detected) return "";
|
|
369
|
+
config.email = detected.email;
|
|
370
|
+
saveConfig(config);
|
|
371
|
+
log(`auto-detected email ${detected.email} (${detected.source})`);
|
|
372
|
+
return detected.email;
|
|
373
|
+
}
|
|
374
|
+
|
|
142
375
|
// src/pricing.ts
|
|
143
376
|
var TABLE = [
|
|
144
377
|
{ match: ["fable"], inputPerMtok: 3, outputPerMtok: 15 },
|
|
@@ -169,9 +402,9 @@ function costUsd(model, inputTokens, outputTokens) {
|
|
|
169
402
|
}
|
|
170
403
|
|
|
171
404
|
// src/transcript.ts
|
|
172
|
-
import * as
|
|
405
|
+
import * as fs4 from "node:fs";
|
|
173
406
|
function readTranscriptUsage(transcriptPath, watermark) {
|
|
174
|
-
const raw =
|
|
407
|
+
const raw = fs4.readFileSync(transcriptPath, "utf8");
|
|
175
408
|
const byMessageId = /* @__PURE__ */ new Map();
|
|
176
409
|
let parsedCount = 0;
|
|
177
410
|
for (const line of raw.split("\n")) {
|
|
@@ -213,7 +446,7 @@ function readTranscriptUsage(transcriptPath, watermark) {
|
|
|
213
446
|
};
|
|
214
447
|
}
|
|
215
448
|
function readGlassTranscriptUsage(transcriptPath, watermark) {
|
|
216
|
-
const raw =
|
|
449
|
+
const raw = fs4.readFileSync(transcriptPath, "utf8");
|
|
217
450
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
218
451
|
let inputChars = 0;
|
|
219
452
|
let outputChars = 0;
|
|
@@ -248,13 +481,13 @@ var MAX_CREDIT_PER_AD_USD = 5;
|
|
|
248
481
|
var AD_MIN_INTERVAL_MS = 9e4;
|
|
249
482
|
var SETTLE_RETRIES = [400, 800, 1500];
|
|
250
483
|
function claudeSettingsPath() {
|
|
251
|
-
return
|
|
484
|
+
return path4.join(os3.homedir(), ".claude", "settings.json");
|
|
252
485
|
}
|
|
253
486
|
function hookCommand() {
|
|
254
|
-
let script =
|
|
487
|
+
let script = path4.resolve(process.argv[1] ?? "");
|
|
255
488
|
if (script.endsWith(".ts")) {
|
|
256
|
-
const dist =
|
|
257
|
-
if (!
|
|
489
|
+
const dist = path4.resolve(path4.dirname(script), "..", "dist", "index.js");
|
|
490
|
+
if (!fs5.existsSync(dist)) {
|
|
258
491
|
throw new Error(
|
|
259
492
|
`Build the CLI first (pnpm --filter @promptai.credit/cli build); hooks cannot run ${script} directly.`
|
|
260
493
|
);
|
|
@@ -265,13 +498,13 @@ function hookCommand() {
|
|
|
265
498
|
}
|
|
266
499
|
function installClaudeHooks() {
|
|
267
500
|
const settingsPath = claudeSettingsPath();
|
|
268
|
-
|
|
501
|
+
fs5.mkdirSync(path4.dirname(settingsPath), { recursive: true });
|
|
269
502
|
let settings = {};
|
|
270
|
-
if (
|
|
503
|
+
if (fs5.existsSync(settingsPath)) {
|
|
271
504
|
try {
|
|
272
|
-
settings = JSON.parse(
|
|
505
|
+
settings = JSON.parse(fs5.readFileSync(settingsPath, "utf8"));
|
|
273
506
|
} catch {
|
|
274
|
-
|
|
507
|
+
fs5.copyFileSync(settingsPath, settingsPath + ".promptai-backup");
|
|
275
508
|
settings = {};
|
|
276
509
|
}
|
|
277
510
|
}
|
|
@@ -291,17 +524,17 @@ function installClaudeHooks() {
|
|
|
291
524
|
changed = true;
|
|
292
525
|
}
|
|
293
526
|
}
|
|
294
|
-
if (changed || !
|
|
295
|
-
|
|
527
|
+
if (changed || !fs5.existsSync(settingsPath)) {
|
|
528
|
+
fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
296
529
|
}
|
|
297
530
|
return { changed, settingsPath };
|
|
298
531
|
}
|
|
299
532
|
function uninstallClaudeHooks() {
|
|
300
533
|
const settingsPath = claudeSettingsPath();
|
|
301
|
-
if (!
|
|
534
|
+
if (!fs5.existsSync(settingsPath)) return { changed: false };
|
|
302
535
|
let settings;
|
|
303
536
|
try {
|
|
304
|
-
settings = JSON.parse(
|
|
537
|
+
settings = JSON.parse(fs5.readFileSync(settingsPath, "utf8"));
|
|
305
538
|
} catch {
|
|
306
539
|
return { changed: false };
|
|
307
540
|
}
|
|
@@ -321,7 +554,7 @@ function uninstallClaudeHooks() {
|
|
|
321
554
|
}
|
|
322
555
|
}
|
|
323
556
|
if (changed) {
|
|
324
|
-
|
|
557
|
+
fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
325
558
|
}
|
|
326
559
|
return { changed };
|
|
327
560
|
}
|
|
@@ -339,7 +572,7 @@ function handleUserPromptSubmit(payload) {
|
|
|
339
572
|
}
|
|
340
573
|
if (config.adsOptIn && Date.now() - state.lastAdOpenedAt >= AD_MIN_INTERVAL_MS) {
|
|
341
574
|
const url = watchUrl(config.serverUrl, config.deviceId, SOURCE);
|
|
342
|
-
if (openInBrowser(url)) {
|
|
575
|
+
if (openInBrowser(url, config.adSide)) {
|
|
343
576
|
state.lastAdOpenedAt = Date.now();
|
|
344
577
|
log(`[claude] opened ad tab for session=${sessionId}`);
|
|
345
578
|
} else if (!hasDisplay()) {
|
|
@@ -376,7 +609,13 @@ async function handleStop(payload) {
|
|
|
376
609
|
const promptId = crypto3.randomUUID();
|
|
377
610
|
let verified = false;
|
|
378
611
|
if (config.adsOptIn && cost > 0) {
|
|
379
|
-
verified = await redeemAgainstAd(
|
|
612
|
+
verified = await redeemAgainstAd(
|
|
613
|
+
config.serverUrl,
|
|
614
|
+
config.deviceId,
|
|
615
|
+
promptId,
|
|
616
|
+
cost,
|
|
617
|
+
resolveEmail(config)
|
|
618
|
+
);
|
|
380
619
|
}
|
|
381
620
|
state.prompts.unshift({
|
|
382
621
|
id: promptId,
|
|
@@ -394,7 +633,7 @@ async function handleStop(payload) {
|
|
|
394
633
|
`[claude] settled session=${sessionId} model=${usage.model} in=${usage.inputTokens} out=${usage.outputTokens} cost=$${cost} verified=${verified}`
|
|
395
634
|
);
|
|
396
635
|
}
|
|
397
|
-
async function redeemAgainstAd(serverUrl, deviceId, promptId, cost) {
|
|
636
|
+
async function redeemAgainstAd(serverUrl, deviceId, promptId, cost, email) {
|
|
398
637
|
try {
|
|
399
638
|
const { sessions } = await listVerifiedSessions(serverUrl, deviceId);
|
|
400
639
|
if (sessions.length === 0) {
|
|
@@ -405,7 +644,8 @@ async function redeemAgainstAd(serverUrl, deviceId, promptId, cost) {
|
|
|
405
644
|
sessionId: sessions[0].sessionId,
|
|
406
645
|
deviceId,
|
|
407
646
|
promptId,
|
|
408
|
-
amountUsd: Math.min(cost, MAX_CREDIT_PER_AD_USD)
|
|
647
|
+
amountUsd: Math.min(cost, MAX_CREDIT_PER_AD_USD),
|
|
648
|
+
email: email || void 0
|
|
409
649
|
});
|
|
410
650
|
return true;
|
|
411
651
|
} catch (err) {
|
|
@@ -416,9 +656,9 @@ async function redeemAgainstAd(serverUrl, deviceId, promptId, cost) {
|
|
|
416
656
|
|
|
417
657
|
// src/cursor.ts
|
|
418
658
|
import * as crypto4 from "node:crypto";
|
|
419
|
-
import * as
|
|
420
|
-
import * as
|
|
421
|
-
import * as
|
|
659
|
+
import * as fs6 from "node:fs";
|
|
660
|
+
import * as os4 from "node:os";
|
|
661
|
+
import * as path5 from "node:path";
|
|
422
662
|
var CURSOR_SOURCE = "cursor-agents";
|
|
423
663
|
var HOOK_MARKER2 = "promptai";
|
|
424
664
|
var MAX_CREDIT_PER_AD_USD2 = 5;
|
|
@@ -428,17 +668,17 @@ function isCursorPayload(payload) {
|
|
|
428
668
|
return typeof payload.cursor_version === "string" || typeof payload.conversation_id === "string";
|
|
429
669
|
}
|
|
430
670
|
function cursorHooksJsonPath() {
|
|
431
|
-
return
|
|
671
|
+
return path5.join(os4.homedir(), ".cursor", "hooks.json");
|
|
432
672
|
}
|
|
433
673
|
function installCursorHooks() {
|
|
434
674
|
const hooksPath = cursorHooksJsonPath();
|
|
435
|
-
|
|
675
|
+
fs6.mkdirSync(path5.dirname(hooksPath), { recursive: true });
|
|
436
676
|
let config = {};
|
|
437
|
-
if (
|
|
677
|
+
if (fs6.existsSync(hooksPath)) {
|
|
438
678
|
try {
|
|
439
|
-
config = JSON.parse(
|
|
679
|
+
config = JSON.parse(fs6.readFileSync(hooksPath, "utf8"));
|
|
440
680
|
} catch {
|
|
441
|
-
|
|
681
|
+
fs6.copyFileSync(hooksPath, hooksPath + ".promptai-backup");
|
|
442
682
|
config = {};
|
|
443
683
|
}
|
|
444
684
|
}
|
|
@@ -460,17 +700,17 @@ function installCursorHooks() {
|
|
|
460
700
|
changed = true;
|
|
461
701
|
}
|
|
462
702
|
}
|
|
463
|
-
if (changed || !
|
|
464
|
-
|
|
703
|
+
if (changed || !fs6.existsSync(hooksPath)) {
|
|
704
|
+
fs6.writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n");
|
|
465
705
|
}
|
|
466
706
|
return { changed, hooksPath };
|
|
467
707
|
}
|
|
468
708
|
function uninstallCursorHooks() {
|
|
469
709
|
const hooksPath = cursorHooksJsonPath();
|
|
470
|
-
if (!
|
|
710
|
+
if (!fs6.existsSync(hooksPath)) return { changed: false };
|
|
471
711
|
let config;
|
|
472
712
|
try {
|
|
473
|
-
config = JSON.parse(
|
|
713
|
+
config = JSON.parse(fs6.readFileSync(hooksPath, "utf8"));
|
|
474
714
|
} catch {
|
|
475
715
|
return { changed: false };
|
|
476
716
|
}
|
|
@@ -487,14 +727,14 @@ function uninstallCursorHooks() {
|
|
|
487
727
|
}
|
|
488
728
|
}
|
|
489
729
|
if (changed) {
|
|
490
|
-
|
|
730
|
+
fs6.writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n");
|
|
491
731
|
}
|
|
492
732
|
return { changed };
|
|
493
733
|
}
|
|
494
734
|
function extensionIsActive() {
|
|
495
|
-
const infoPath =
|
|
735
|
+
const infoPath = path5.join(os4.homedir(), ".cursor", "promptai-listener.json");
|
|
496
736
|
try {
|
|
497
|
-
const info = JSON.parse(
|
|
737
|
+
const info = JSON.parse(fs6.readFileSync(infoPath, "utf8"));
|
|
498
738
|
if (typeof info.pid !== "number") return false;
|
|
499
739
|
process.kill(info.pid, 0);
|
|
500
740
|
return true;
|
|
@@ -503,18 +743,18 @@ function extensionIsActive() {
|
|
|
503
743
|
}
|
|
504
744
|
}
|
|
505
745
|
function acquireOnceLock(key) {
|
|
506
|
-
const dir =
|
|
507
|
-
|
|
746
|
+
const dir = path5.join(promptaiDir(), "locks");
|
|
747
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
508
748
|
try {
|
|
509
749
|
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
510
|
-
for (const name of
|
|
511
|
-
const p =
|
|
512
|
-
if (
|
|
750
|
+
for (const name of fs6.readdirSync(dir)) {
|
|
751
|
+
const p = path5.join(dir, name);
|
|
752
|
+
if (fs6.statSync(p).mtimeMs < cutoff) fs6.rmSync(p, { recursive: true, force: true });
|
|
513
753
|
}
|
|
514
754
|
} catch {
|
|
515
755
|
}
|
|
516
756
|
try {
|
|
517
|
-
|
|
757
|
+
fs6.mkdirSync(path5.join(dir, key.replace(/[^a-zA-Z0-9_-]/g, "_")));
|
|
518
758
|
return true;
|
|
519
759
|
} catch {
|
|
520
760
|
return false;
|
|
@@ -539,7 +779,7 @@ function handleCursorPromptSubmitted(payload) {
|
|
|
539
779
|
const remote = process.env.CURSOR_CODE_REMOTE === "true";
|
|
540
780
|
if (config.adsOptIn && !extensionIsActive() && Date.now() - state.lastAdOpenedAt >= AD_MIN_INTERVAL_MS2) {
|
|
541
781
|
const url = watchUrl(config.serverUrl, config.deviceId, CURSOR_SOURCE);
|
|
542
|
-
if (!remote && openInBrowser(url)) {
|
|
782
|
+
if (!remote && openInBrowser(url, config.adSide)) {
|
|
543
783
|
state.lastAdOpenedAt = Date.now();
|
|
544
784
|
log(`[cursor] opened ad tab for conv=${convId}`);
|
|
545
785
|
} else if (remote || !hasDisplay()) {
|
|
@@ -607,7 +847,13 @@ async function handleCursorStop(payload) {
|
|
|
607
847
|
const promptId = crypto4.randomUUID();
|
|
608
848
|
let verified = false;
|
|
609
849
|
if (config.adsOptIn && cost > 0) {
|
|
610
|
-
verified = await redeemAgainstAd2(
|
|
850
|
+
verified = await redeemAgainstAd2(
|
|
851
|
+
config.serverUrl,
|
|
852
|
+
config.deviceId,
|
|
853
|
+
promptId,
|
|
854
|
+
cost,
|
|
855
|
+
resolveEmail(config)
|
|
856
|
+
);
|
|
611
857
|
}
|
|
612
858
|
state.prompts.unshift({
|
|
613
859
|
id: promptId,
|
|
@@ -626,7 +872,7 @@ async function handleCursorStop(payload) {
|
|
|
626
872
|
`[cursor] settled conv=${convId} model=${model} in=${inputTokens} out=${outputTokens} cost=$${cost}${estimated ? " (estimated)" : ""} verified=${verified}`
|
|
627
873
|
);
|
|
628
874
|
}
|
|
629
|
-
async function redeemAgainstAd2(serverUrl, deviceId, promptId, cost) {
|
|
875
|
+
async function redeemAgainstAd2(serverUrl, deviceId, promptId, cost, email) {
|
|
630
876
|
try {
|
|
631
877
|
const { sessions } = await listVerifiedSessions(serverUrl, deviceId);
|
|
632
878
|
if (sessions.length === 0) {
|
|
@@ -637,7 +883,8 @@ async function redeemAgainstAd2(serverUrl, deviceId, promptId, cost) {
|
|
|
637
883
|
sessionId: sessions[0].sessionId,
|
|
638
884
|
deviceId,
|
|
639
885
|
promptId,
|
|
640
|
-
amountUsd: Math.min(cost, MAX_CREDIT_PER_AD_USD2)
|
|
886
|
+
amountUsd: Math.min(cost, MAX_CREDIT_PER_AD_USD2),
|
|
887
|
+
email: email || void 0
|
|
641
888
|
});
|
|
642
889
|
return true;
|
|
643
890
|
} catch (err) {
|
|
@@ -646,6 +893,112 @@ async function redeemAgainstAd2(serverUrl, deviceId, promptId, cost) {
|
|
|
646
893
|
}
|
|
647
894
|
}
|
|
648
895
|
|
|
896
|
+
// src/setup.ts
|
|
897
|
+
import * as readline from "node:readline";
|
|
898
|
+
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
899
|
+
var WALLET_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
900
|
+
function promptLine(question) {
|
|
901
|
+
return new Promise((resolve2) => {
|
|
902
|
+
process.stdout.write(question);
|
|
903
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
904
|
+
rl.once("line", (line) => {
|
|
905
|
+
rl.close();
|
|
906
|
+
resolve2(line);
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
function printLinkStatus(claimActivated) {
|
|
911
|
+
console.log(
|
|
912
|
+
claimActivated ? "Email linked. Account is activated for USDC claims." : "Email linked. USDC claims stay blocked until an admin activates this account."
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
async function linkEmailInteractive(config, email, source) {
|
|
916
|
+
try {
|
|
917
|
+
const result = await linkEmail(config.serverUrl, {
|
|
918
|
+
deviceId: config.deviceId,
|
|
919
|
+
email,
|
|
920
|
+
source
|
|
921
|
+
});
|
|
922
|
+
if (result.otpRequired) {
|
|
923
|
+
console.log(result.message ?? `Verification code sent to ${email}.`);
|
|
924
|
+
const code = (await promptLine("Enter the 6-digit code: ")).trim();
|
|
925
|
+
if (!/^\d{6}$/.test(code)) {
|
|
926
|
+
console.error("That doesn't look like a 6-digit code. Run: promptai set email " + email);
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
const verified = await verifyEmailLink(config.serverUrl, {
|
|
930
|
+
deviceId: config.deviceId,
|
|
931
|
+
email,
|
|
932
|
+
code
|
|
933
|
+
});
|
|
934
|
+
printLinkStatus(verified.claimActivated);
|
|
935
|
+
} else {
|
|
936
|
+
printLinkStatus(Boolean(result.claimActivated));
|
|
937
|
+
}
|
|
938
|
+
return true;
|
|
939
|
+
} catch (err) {
|
|
940
|
+
console.error(`Email not linked: ${err instanceof Error ? err.message : String(err)}`);
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function parseYes(raw, defaultYes) {
|
|
945
|
+
const v = raw.trim().toLowerCase();
|
|
946
|
+
if (!v) return defaultYes;
|
|
947
|
+
return v === "y" || v === "yes";
|
|
948
|
+
}
|
|
949
|
+
function parseSide(raw) {
|
|
950
|
+
const v = raw.trim().toLowerCase();
|
|
951
|
+
if (v === "left" || v === "l") return "left";
|
|
952
|
+
if (v === "default" || v === "browser" || v === "n" || v === "no") return "default";
|
|
953
|
+
return "right";
|
|
954
|
+
}
|
|
955
|
+
async function onboard(config) {
|
|
956
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
957
|
+
if (!config.adSide) config.adSide = "right";
|
|
958
|
+
saveConfig(config);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
console.log("");
|
|
962
|
+
console.log("Let's roll you in.");
|
|
963
|
+
console.log("");
|
|
964
|
+
const detected = detectEmail();
|
|
965
|
+
const emailHint = config.email || detected?.email || "";
|
|
966
|
+
const emailRaw = (await promptLine(emailHint ? `Email [${emailHint}]: ` : "Email: ")).trim().toLowerCase();
|
|
967
|
+
const email = emailRaw || emailHint;
|
|
968
|
+
if (email && EMAIL_RE2.test(email)) {
|
|
969
|
+
const source = detected?.email === email ? "auto" : "manual";
|
|
970
|
+
config.email = email;
|
|
971
|
+
const linked = await linkEmailInteractive(config, email, source);
|
|
972
|
+
if (!linked) {
|
|
973
|
+
saveConfig(config);
|
|
974
|
+
}
|
|
975
|
+
} else if (email) {
|
|
976
|
+
console.log("That doesn't look like an email. Set it later: promptai set email you@example.com");
|
|
977
|
+
} else {
|
|
978
|
+
console.log("Skipped email. Set it later: promptai set email you@example.com");
|
|
979
|
+
}
|
|
980
|
+
const walletHint = config.wallet || "";
|
|
981
|
+
const walletRaw = (await promptLine(walletHint ? `Wallet [${walletHint}]: ` : "Wallet (0x\u2026, optional): ")).trim();
|
|
982
|
+
const wallet = walletRaw || walletHint;
|
|
983
|
+
if (wallet) {
|
|
984
|
+
if (WALLET_RE.test(wallet)) {
|
|
985
|
+
config.wallet = wallet;
|
|
986
|
+
console.log("Wallet saved.");
|
|
987
|
+
} else {
|
|
988
|
+
console.log("That doesn't look like a 0x address. Set it later: promptai set wallet 0x...");
|
|
989
|
+
}
|
|
990
|
+
} else {
|
|
991
|
+
console.log("Skipped wallet. Set it later: promptai set wallet 0x...");
|
|
992
|
+
}
|
|
993
|
+
const sideRaw = await promptLine("Show ads on the right while your agent works? [Y/n]: ");
|
|
994
|
+
config.adSide = parseYes(sideRaw, true) ? "right" : parseSide(sideRaw);
|
|
995
|
+
console.log(
|
|
996
|
+
config.adSide === "right" ? "Ads will open on the right." : config.adSide === "left" ? "Ads will open on the left." : "Ads will open in your default browser."
|
|
997
|
+
);
|
|
998
|
+
saveConfig(config);
|
|
999
|
+
console.log("");
|
|
1000
|
+
}
|
|
1001
|
+
|
|
649
1002
|
// src/index.ts
|
|
650
1003
|
var HELP = `promptai - ad-subsidized prompt credits for terminal agents
|
|
651
1004
|
|
|
@@ -657,8 +1010,10 @@ Usage:
|
|
|
657
1010
|
promptai watch Open a rewarded ad in the browser now
|
|
658
1011
|
promptai claim [address] Claim your verified balance as USDC (Base)
|
|
659
1012
|
promptai set wallet 0x... Set the payout wallet
|
|
1013
|
+
promptai set email <email> Link your email (required for USDC claims; groups devices)
|
|
660
1014
|
promptai set server <url> Point at a different API server
|
|
661
1015
|
promptai set ads on|off Toggle the rewarded-ads opt-in
|
|
1016
|
+
promptai set side right|left|default Where the ad window docks (default: right)
|
|
662
1017
|
promptai hook (internal) invoked by agent hooks, JSON on stdin
|
|
663
1018
|
`;
|
|
664
1019
|
function readStdin() {
|
|
@@ -677,11 +1032,11 @@ async function cmdHook() {
|
|
|
677
1032
|
return;
|
|
678
1033
|
}
|
|
679
1034
|
const event = String(payload.hook_event_name ?? "");
|
|
680
|
-
if (process.env.PROMPTAI_DUMP === "1" ||
|
|
1035
|
+
if (process.env.PROMPTAI_DUMP === "1" || fs7.existsSync(path6.join(promptaiDir(), "debug"))) {
|
|
681
1036
|
try {
|
|
682
|
-
const dir =
|
|
683
|
-
|
|
684
|
-
|
|
1037
|
+
const dir = path6.join(promptaiDir(), "dump");
|
|
1038
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1039
|
+
fs7.writeFileSync(path6.join(dir, `${Date.now()}-${event || "unknown"}.json`), raw);
|
|
685
1040
|
} catch {
|
|
686
1041
|
}
|
|
687
1042
|
}
|
|
@@ -705,10 +1060,13 @@ async function cmdHook() {
|
|
|
705
1060
|
async function cmdStatus() {
|
|
706
1061
|
const config = loadConfig();
|
|
707
1062
|
const state = loadState();
|
|
1063
|
+
const email = resolveEmail(config);
|
|
708
1064
|
console.log(`device ${config.deviceId}`);
|
|
709
1065
|
console.log(`server ${config.serverUrl}`);
|
|
710
1066
|
console.log(`wallet ${config.wallet || "(not set - promptai set wallet 0x...)"}`);
|
|
1067
|
+
console.log(`email ${email || "(not detected - promptai set email you@example.com)"}`);
|
|
711
1068
|
console.log(`ads ${config.adsOptIn ? "opted in" : "opted out"}`);
|
|
1069
|
+
console.log(`side ${config.adSide}`);
|
|
712
1070
|
console.log(`watch ${watchUrl(config.serverUrl, config.deviceId, "manual")}`);
|
|
713
1071
|
try {
|
|
714
1072
|
const [balance, verified] = await Promise.all([
|
|
@@ -718,6 +1076,12 @@ async function cmdStatus() {
|
|
|
718
1076
|
console.log(
|
|
719
1077
|
`balance $${balance.balanceUsd.toFixed(4)} (earned $${balance.earnedUsd.toFixed(4)}, claimed $${balance.claimedUsd.toFixed(4)})`
|
|
720
1078
|
);
|
|
1079
|
+
console.log(
|
|
1080
|
+
`monthly device $${balance.monthEarnedDeviceUsd.toFixed(4)} / $${balance.deviceCapUsd}` + (balance.email ? ` \xB7 email $${balance.monthEarnedEmailUsd.toFixed(4)} / $${balance.emailCapUsd}` : "")
|
|
1081
|
+
);
|
|
1082
|
+
console.log(
|
|
1083
|
+
`claims ${!balance.email ? "blocked (link an email first)" : balance.claimActivated ? "activated" : "blocked (waiting for admin activation)"}`
|
|
1084
|
+
);
|
|
721
1085
|
console.log(`banked ${verified.sessions.length} verified ad watch(es) ready to fund prompts`);
|
|
722
1086
|
} catch (err) {
|
|
723
1087
|
console.log(`balance unavailable (${String(err)})`);
|
|
@@ -736,7 +1100,7 @@ async function cmdStatus() {
|
|
|
736
1100
|
async function cmdWatch() {
|
|
737
1101
|
const config = loadConfig();
|
|
738
1102
|
const url = watchUrl(config.serverUrl, config.deviceId, SOURCE);
|
|
739
|
-
if (openInBrowser(url)) {
|
|
1103
|
+
if (openInBrowser(url, config.adSide)) {
|
|
740
1104
|
console.log(`Opened ${url}`);
|
|
741
1105
|
console.log("Watch the full ad; the credit banks automatically once the server verifies it.");
|
|
742
1106
|
} else if (!hasDisplay()) {
|
|
@@ -754,6 +1118,13 @@ async function cmdClaim(addressArg) {
|
|
|
754
1118
|
process.exitCode = 1;
|
|
755
1119
|
return;
|
|
756
1120
|
}
|
|
1121
|
+
const email = resolveEmail(config);
|
|
1122
|
+
if (email) {
|
|
1123
|
+
try {
|
|
1124
|
+
await linkEmail(config.serverUrl, { deviceId: config.deviceId, email, source: "auto" });
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
757
1128
|
const balance = await fetchBalance(config.serverUrl, config.deviceId);
|
|
758
1129
|
if (balance.balanceUsd < 1e-6) {
|
|
759
1130
|
console.log("Nothing to claim yet - watch an ad (promptai watch) and run some agent prompts first.");
|
|
@@ -768,7 +1139,7 @@ async function cmdClaim(addressArg) {
|
|
|
768
1139
|
console.log(`Sent: ${result.txHash}`);
|
|
769
1140
|
console.log(result.explorerUrl);
|
|
770
1141
|
}
|
|
771
|
-
function cmdSet(key, value) {
|
|
1142
|
+
async function cmdSet(key, value) {
|
|
772
1143
|
const config = loadConfig();
|
|
773
1144
|
if (key === "wallet" && value && /^0x[0-9a-fA-F]{40}$/.test(value)) {
|
|
774
1145
|
config.wallet = value;
|
|
@@ -776,21 +1147,40 @@ function cmdSet(key, value) {
|
|
|
776
1147
|
config.serverUrl = value.replace(/\/$/, "");
|
|
777
1148
|
} else if (key === "ads" && (value === "on" || value === "off")) {
|
|
778
1149
|
config.adsOptIn = value === "on";
|
|
1150
|
+
} else if (key === "side" && (value === "right" || value === "left" || value === "default")) {
|
|
1151
|
+
config.adSide = value;
|
|
1152
|
+
} else if (key === "email" && value && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value)) {
|
|
1153
|
+
config.email = value.trim().toLowerCase();
|
|
779
1154
|
} else {
|
|
780
|
-
console.error(
|
|
1155
|
+
console.error(
|
|
1156
|
+
"Usage: promptai set wallet 0x... | set server <url> | set ads on|off | set side right|left|default | set email you@example.com"
|
|
1157
|
+
);
|
|
781
1158
|
process.exitCode = 1;
|
|
782
1159
|
return;
|
|
783
1160
|
}
|
|
1161
|
+
if (key === "email") {
|
|
1162
|
+
const email = config.email;
|
|
1163
|
+
const ok = await linkEmailInteractive(config, email, "manual");
|
|
1164
|
+
if (!ok) {
|
|
1165
|
+
process.exitCode = 1;
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
saveConfig(config);
|
|
1169
|
+
console.log("email updated.");
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
784
1172
|
saveConfig(config);
|
|
785
1173
|
console.log(`${key} updated.`);
|
|
786
1174
|
}
|
|
787
|
-
function cmdInstall(agent) {
|
|
1175
|
+
async function cmdInstall(agent) {
|
|
788
1176
|
if (agent === "claude" || agent === "claude-code") {
|
|
789
1177
|
const { changed, settingsPath } = installClaudeHooks();
|
|
790
1178
|
console.log(
|
|
791
1179
|
changed ? `Hooks installed into ${settingsPath}.` : `Hooks already installed in ${settingsPath}.`
|
|
792
1180
|
);
|
|
793
|
-
console.log("Claude Code picks them up on its next session.
|
|
1181
|
+
console.log("Claude Code picks them up on its next session.");
|
|
1182
|
+
await onboard(loadConfig());
|
|
1183
|
+
console.log("Try: promptai watch, then run a prompt.");
|
|
794
1184
|
return;
|
|
795
1185
|
}
|
|
796
1186
|
if (agent === "cursor" || agent === "cursor-agents") {
|
|
@@ -801,6 +1191,8 @@ function cmdInstall(agent) {
|
|
|
801
1191
|
console.log(
|
|
802
1192
|
"Works in the Agents Window and the classic IDE (the CLI stands down when the promptai extension is running). Cursor hot-reloads hooks.json."
|
|
803
1193
|
);
|
|
1194
|
+
await onboard(loadConfig());
|
|
1195
|
+
console.log("Try: promptai watch, then run a prompt.");
|
|
804
1196
|
return;
|
|
805
1197
|
}
|
|
806
1198
|
console.error("Supported agents: claude, cursor (Codex CLI coming next). Usage: promptai install <agent>");
|
|
@@ -827,7 +1219,7 @@ try {
|
|
|
827
1219
|
await cmdHook();
|
|
828
1220
|
break;
|
|
829
1221
|
case "install":
|
|
830
|
-
cmdInstall(args[0]);
|
|
1222
|
+
await cmdInstall(args[0]);
|
|
831
1223
|
break;
|
|
832
1224
|
case "uninstall":
|
|
833
1225
|
cmdUninstall(args[0]);
|
|
@@ -842,7 +1234,7 @@ try {
|
|
|
842
1234
|
await cmdClaim(args[0]);
|
|
843
1235
|
break;
|
|
844
1236
|
case "set":
|
|
845
|
-
cmdSet(args[0], args[1]);
|
|
1237
|
+
await cmdSet(args[0], args[1]);
|
|
846
1238
|
break;
|
|
847
1239
|
default:
|
|
848
1240
|
console.log(HELP);
|