@promptai.credit/cli 0.4.0 → 0.4.2
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 +29 -8
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/README.md +33 -0
- package/claude-plugin/hooks/ads.tsx +557 -0
- package/claude-plugin/hooks/hooks.json +4 -0
- package/claude-plugin/hooks/logo.ts +167 -0
- package/dist/index.js +246 -85
- package/package.json +4 -3
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 fs8 from "node:fs";
|
|
5
|
+
import * as path7 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 fs6 from "node:fs";
|
|
10
|
+
import * as os4 from "node:os";
|
|
11
|
+
import * as path5 from "node:path";
|
|
12
12
|
|
|
13
13
|
// src/api.ts
|
|
14
14
|
import * as crypto from "node:crypto";
|
|
@@ -246,15 +246,20 @@ function openInBrowser(url, side = "right") {
|
|
|
246
246
|
const chrome = chromeBin();
|
|
247
247
|
if (chrome) {
|
|
248
248
|
const profile = path2.join(promptaiDir(), "ad-window");
|
|
249
|
+
const appUrl = `${url}${url.includes("?") ? "&" : "?"}_=${Date.now()}`;
|
|
249
250
|
try {
|
|
250
251
|
spawn(
|
|
251
252
|
chrome,
|
|
252
253
|
[
|
|
253
254
|
`--user-data-dir=${profile}`,
|
|
254
|
-
`--app=${
|
|
255
|
+
`--app=${appUrl}`,
|
|
255
256
|
`--window-position=${rect.x},${rect.y}`,
|
|
256
257
|
`--window-size=${rect.w},${rect.h}`,
|
|
257
|
-
"--new-window"
|
|
258
|
+
"--new-window",
|
|
259
|
+
"--no-first-run",
|
|
260
|
+
"--no-default-browser-check",
|
|
261
|
+
"--disable-session-crashed-bubble",
|
|
262
|
+
"--hide-crash-restore-bubble"
|
|
258
263
|
],
|
|
259
264
|
{ detached: true, stdio: "ignore" }
|
|
260
265
|
).unref();
|
|
@@ -372,6 +377,113 @@ function resolveEmail(config) {
|
|
|
372
377
|
return detected.email;
|
|
373
378
|
}
|
|
374
379
|
|
|
380
|
+
// src/plugin.ts
|
|
381
|
+
import * as fs4 from "node:fs";
|
|
382
|
+
import * as os3 from "node:os";
|
|
383
|
+
import * as path4 from "node:path";
|
|
384
|
+
import { fileURLToPath } from "node:url";
|
|
385
|
+
var PLUGIN_NAME = "promptai";
|
|
386
|
+
var HEARTBEAT_FILE = "native-plugin.json";
|
|
387
|
+
var HEARTBEAT_FRESH_MS = 2 * 60 * 60 * 1e3;
|
|
388
|
+
function claudePluginInstallDir() {
|
|
389
|
+
return path4.join(os3.homedir(), ".claude", "skills", PLUGIN_NAME);
|
|
390
|
+
}
|
|
391
|
+
function heartbeatPath() {
|
|
392
|
+
return path4.join(promptaiDir(), HEARTBEAT_FILE);
|
|
393
|
+
}
|
|
394
|
+
function pluginSourceDir() {
|
|
395
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
396
|
+
const candidates = [
|
|
397
|
+
// npm package / after build: claude-plugin/ next to dist/
|
|
398
|
+
path4.resolve(here, "..", "claude-plugin"),
|
|
399
|
+
// monorepo: product/cli/dist|src → product/claude-plugin
|
|
400
|
+
path4.resolve(here, "..", "..", "claude-plugin")
|
|
401
|
+
];
|
|
402
|
+
for (const dir of candidates) {
|
|
403
|
+
if (fs4.existsSync(path4.join(dir, ".claude-plugin", "plugin.json"))) {
|
|
404
|
+
return dir;
|
|
405
|
+
}
|
|
406
|
+
if (fs4.existsSync(path4.join(dir, "hooks", "ads.tsx"))) {
|
|
407
|
+
return dir;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
function copyDirSync(src, dest) {
|
|
413
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
414
|
+
for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
|
|
415
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
416
|
+
const from = path4.join(src, entry.name);
|
|
417
|
+
const to = path4.join(dest, entry.name);
|
|
418
|
+
if (entry.isDirectory()) {
|
|
419
|
+
copyDirSync(from, to);
|
|
420
|
+
} else if (entry.isFile()) {
|
|
421
|
+
fs4.copyFileSync(from, to);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function isClaudePluginInstalled() {
|
|
426
|
+
const dir = claudePluginInstallDir();
|
|
427
|
+
return fs4.existsSync(path4.join(dir, ".claude-plugin", "plugin.json")) || fs4.existsSync(path4.join(dir, "hooks", "ads.tsx"));
|
|
428
|
+
}
|
|
429
|
+
function isNativePluginActive(now = Date.now()) {
|
|
430
|
+
try {
|
|
431
|
+
const raw = fs4.readFileSync(heartbeatPath(), "utf8");
|
|
432
|
+
const data = JSON.parse(raw);
|
|
433
|
+
if (data.active !== true) return false;
|
|
434
|
+
const at = Number(data.at ?? 0);
|
|
435
|
+
const freshMs = Number(data.freshMs ?? HEARTBEAT_FRESH_MS);
|
|
436
|
+
if (!Number.isFinite(at) || at <= 0) return false;
|
|
437
|
+
return now - at < freshMs;
|
|
438
|
+
} catch {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function clearNativeHeartbeat() {
|
|
443
|
+
try {
|
|
444
|
+
fs4.unlinkSync(heartbeatPath());
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function installClaudePlugin() {
|
|
449
|
+
const source = pluginSourceDir();
|
|
450
|
+
const installPath = claudePluginInstallDir();
|
|
451
|
+
if (!source) {
|
|
452
|
+
return {
|
|
453
|
+
changed: false,
|
|
454
|
+
installPath,
|
|
455
|
+
sourcePath: null,
|
|
456
|
+
error: "Claude Mods plugin sources not found next to the CLI. Reinstall @promptai.credit/cli."
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
fs4.mkdirSync(path4.dirname(installPath), { recursive: true });
|
|
460
|
+
fs4.rmSync(installPath, { recursive: true, force: true });
|
|
461
|
+
copyDirSync(source, installPath);
|
|
462
|
+
fs4.writeFileSync(
|
|
463
|
+
heartbeatPath(),
|
|
464
|
+
JSON.stringify(
|
|
465
|
+
{
|
|
466
|
+
active: false,
|
|
467
|
+
installed: true,
|
|
468
|
+
installedAt: Date.now(),
|
|
469
|
+
installPath,
|
|
470
|
+
source,
|
|
471
|
+
note: "Becomes active when Claude Code loads the plugin (Mods / function hooks)."
|
|
472
|
+
},
|
|
473
|
+
null,
|
|
474
|
+
2
|
|
475
|
+
) + "\n"
|
|
476
|
+
);
|
|
477
|
+
return { changed: true, installPath, sourcePath: source };
|
|
478
|
+
}
|
|
479
|
+
function uninstallClaudePlugin() {
|
|
480
|
+
const installPath = claudePluginInstallDir();
|
|
481
|
+
const existed = fs4.existsSync(installPath);
|
|
482
|
+
fs4.rmSync(installPath, { recursive: true, force: true });
|
|
483
|
+
clearNativeHeartbeat();
|
|
484
|
+
return { changed: existed, installPath };
|
|
485
|
+
}
|
|
486
|
+
|
|
375
487
|
// src/pricing.ts
|
|
376
488
|
var TABLE = [
|
|
377
489
|
{ match: ["fable"], inputPerMtok: 3, outputPerMtok: 15 },
|
|
@@ -402,9 +514,9 @@ function costUsd(model, inputTokens, outputTokens) {
|
|
|
402
514
|
}
|
|
403
515
|
|
|
404
516
|
// src/transcript.ts
|
|
405
|
-
import * as
|
|
517
|
+
import * as fs5 from "node:fs";
|
|
406
518
|
function readTranscriptUsage(transcriptPath, watermark) {
|
|
407
|
-
const raw =
|
|
519
|
+
const raw = fs5.readFileSync(transcriptPath, "utf8");
|
|
408
520
|
const byMessageId = /* @__PURE__ */ new Map();
|
|
409
521
|
let parsedCount = 0;
|
|
410
522
|
for (const line of raw.split("\n")) {
|
|
@@ -446,7 +558,7 @@ function readTranscriptUsage(transcriptPath, watermark) {
|
|
|
446
558
|
};
|
|
447
559
|
}
|
|
448
560
|
function readGlassTranscriptUsage(transcriptPath, watermark) {
|
|
449
|
-
const raw =
|
|
561
|
+
const raw = fs5.readFileSync(transcriptPath, "utf8");
|
|
450
562
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
451
563
|
let inputChars = 0;
|
|
452
564
|
let outputChars = 0;
|
|
@@ -481,13 +593,13 @@ var MAX_CREDIT_PER_AD_USD = 5;
|
|
|
481
593
|
var AD_MIN_INTERVAL_MS = 9e4;
|
|
482
594
|
var SETTLE_RETRIES = [400, 800, 1500];
|
|
483
595
|
function claudeSettingsPath() {
|
|
484
|
-
return
|
|
596
|
+
return path5.join(os4.homedir(), ".claude", "settings.json");
|
|
485
597
|
}
|
|
486
598
|
function hookCommand() {
|
|
487
|
-
let script =
|
|
599
|
+
let script = path5.resolve(process.argv[1] ?? "");
|
|
488
600
|
if (script.endsWith(".ts")) {
|
|
489
|
-
const dist =
|
|
490
|
-
if (!
|
|
601
|
+
const dist = path5.resolve(path5.dirname(script), "..", "dist", "index.js");
|
|
602
|
+
if (!fs6.existsSync(dist)) {
|
|
491
603
|
throw new Error(
|
|
492
604
|
`Build the CLI first (pnpm --filter @promptai.credit/cli build); hooks cannot run ${script} directly.`
|
|
493
605
|
);
|
|
@@ -496,21 +608,34 @@ function hookCommand() {
|
|
|
496
608
|
}
|
|
497
609
|
return `"${process.execPath}" "${script}" hook`;
|
|
498
610
|
}
|
|
611
|
+
var MODS_FLAG = "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS";
|
|
612
|
+
var MODS_FLAG_MARKER = "mods-flag-added";
|
|
613
|
+
function modsFlagMarkerPath() {
|
|
614
|
+
return path5.join(promptaiDir(), MODS_FLAG_MARKER);
|
|
615
|
+
}
|
|
499
616
|
function installClaudeHooks() {
|
|
500
617
|
const settingsPath = claudeSettingsPath();
|
|
501
|
-
|
|
618
|
+
fs6.mkdirSync(path5.dirname(settingsPath), { recursive: true });
|
|
502
619
|
let settings = {};
|
|
503
|
-
if (
|
|
620
|
+
if (fs6.existsSync(settingsPath)) {
|
|
504
621
|
try {
|
|
505
|
-
settings = JSON.parse(
|
|
622
|
+
settings = JSON.parse(fs6.readFileSync(settingsPath, "utf8"));
|
|
506
623
|
} catch {
|
|
507
|
-
|
|
624
|
+
fs6.copyFileSync(settingsPath, settingsPath + ".promptai-backup");
|
|
508
625
|
settings = {};
|
|
509
626
|
}
|
|
510
627
|
}
|
|
511
628
|
settings.hooks = settings.hooks ?? {};
|
|
512
629
|
const command2 = hookCommand();
|
|
513
630
|
let changed = false;
|
|
631
|
+
settings.env = settings.env && typeof settings.env === "object" ? settings.env : {};
|
|
632
|
+
if (settings.env[MODS_FLAG] === void 0) {
|
|
633
|
+
settings.env[MODS_FLAG] = "1";
|
|
634
|
+
fs6.mkdirSync(promptaiDir(), { recursive: true });
|
|
635
|
+
fs6.writeFileSync(modsFlagMarkerPath(), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
636
|
+
`);
|
|
637
|
+
changed = true;
|
|
638
|
+
}
|
|
514
639
|
for (const event of ["UserPromptSubmit", "Stop"]) {
|
|
515
640
|
const groups = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
516
641
|
let ours = groups.flatMap((g) => g.hooks ?? []).find((h) => typeof h.command === "string" && h.command.includes(HOOK_MARKER));
|
|
@@ -524,23 +649,30 @@ function installClaudeHooks() {
|
|
|
524
649
|
changed = true;
|
|
525
650
|
}
|
|
526
651
|
}
|
|
527
|
-
if (changed || !
|
|
528
|
-
|
|
652
|
+
if (changed || !fs6.existsSync(settingsPath)) {
|
|
653
|
+
fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
529
654
|
}
|
|
530
655
|
return { changed, settingsPath };
|
|
531
656
|
}
|
|
532
657
|
function uninstallClaudeHooks() {
|
|
533
658
|
const settingsPath = claudeSettingsPath();
|
|
534
|
-
if (!
|
|
659
|
+
if (!fs6.existsSync(settingsPath)) return { changed: false };
|
|
535
660
|
let settings;
|
|
536
661
|
try {
|
|
537
|
-
settings = JSON.parse(
|
|
662
|
+
settings = JSON.parse(fs6.readFileSync(settingsPath, "utf8"));
|
|
538
663
|
} catch {
|
|
539
664
|
return { changed: false };
|
|
540
665
|
}
|
|
541
|
-
if (!settings.hooks) return { changed: false };
|
|
542
666
|
let changed = false;
|
|
543
|
-
|
|
667
|
+
if (fs6.existsSync(modsFlagMarkerPath())) {
|
|
668
|
+
if (settings.env?.[MODS_FLAG] === "1") {
|
|
669
|
+
delete settings.env[MODS_FLAG];
|
|
670
|
+
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
671
|
+
changed = true;
|
|
672
|
+
}
|
|
673
|
+
fs6.rmSync(modsFlagMarkerPath(), { force: true });
|
|
674
|
+
}
|
|
675
|
+
for (const [event, groups] of Object.entries(settings.hooks ?? {})) {
|
|
544
676
|
if (!Array.isArray(groups)) continue;
|
|
545
677
|
const kept = groups.map((g) => ({
|
|
546
678
|
...g,
|
|
@@ -554,7 +686,7 @@ function uninstallClaudeHooks() {
|
|
|
554
686
|
}
|
|
555
687
|
}
|
|
556
688
|
if (changed) {
|
|
557
|
-
|
|
689
|
+
fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
558
690
|
}
|
|
559
691
|
return { changed };
|
|
560
692
|
}
|
|
@@ -571,12 +703,16 @@ function handleUserPromptSubmit(payload) {
|
|
|
571
703
|
}
|
|
572
704
|
}
|
|
573
705
|
if (config.adsOptIn && Date.now() - state.lastAdOpenedAt >= AD_MIN_INTERVAL_MS) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
706
|
+
if (isNativePluginActive()) {
|
|
707
|
+
log(`[claude] native Mods plugin active, skipped browser ad tab session=${sessionId}`);
|
|
708
|
+
} else {
|
|
709
|
+
const url = watchUrl(config.serverUrl, config.deviceId, SOURCE);
|
|
710
|
+
if (openInBrowser(url, config.adSide)) {
|
|
711
|
+
state.lastAdOpenedAt = Date.now();
|
|
712
|
+
log(`[claude] opened ad tab for session=${sessionId}`);
|
|
713
|
+
} else if (!hasDisplay()) {
|
|
714
|
+
log(`[claude] headless environment, skipped ad tab`);
|
|
715
|
+
}
|
|
580
716
|
}
|
|
581
717
|
}
|
|
582
718
|
saveState(state);
|
|
@@ -656,9 +792,9 @@ async function redeemAgainstAd(serverUrl, deviceId, promptId, cost, email) {
|
|
|
656
792
|
|
|
657
793
|
// src/cursor.ts
|
|
658
794
|
import * as crypto4 from "node:crypto";
|
|
659
|
-
import * as
|
|
660
|
-
import * as
|
|
661
|
-
import * as
|
|
795
|
+
import * as fs7 from "node:fs";
|
|
796
|
+
import * as os5 from "node:os";
|
|
797
|
+
import * as path6 from "node:path";
|
|
662
798
|
var CURSOR_SOURCE = "cursor-agents";
|
|
663
799
|
var HOOK_MARKER2 = "promptai";
|
|
664
800
|
var MAX_CREDIT_PER_AD_USD2 = 5;
|
|
@@ -668,17 +804,17 @@ function isCursorPayload(payload) {
|
|
|
668
804
|
return typeof payload.cursor_version === "string" || typeof payload.conversation_id === "string";
|
|
669
805
|
}
|
|
670
806
|
function cursorHooksJsonPath() {
|
|
671
|
-
return
|
|
807
|
+
return path6.join(os5.homedir(), ".cursor", "hooks.json");
|
|
672
808
|
}
|
|
673
809
|
function installCursorHooks() {
|
|
674
810
|
const hooksPath = cursorHooksJsonPath();
|
|
675
|
-
|
|
811
|
+
fs7.mkdirSync(path6.dirname(hooksPath), { recursive: true });
|
|
676
812
|
let config = {};
|
|
677
|
-
if (
|
|
813
|
+
if (fs7.existsSync(hooksPath)) {
|
|
678
814
|
try {
|
|
679
|
-
config = JSON.parse(
|
|
815
|
+
config = JSON.parse(fs7.readFileSync(hooksPath, "utf8"));
|
|
680
816
|
} catch {
|
|
681
|
-
|
|
817
|
+
fs7.copyFileSync(hooksPath, hooksPath + ".promptai-backup");
|
|
682
818
|
config = {};
|
|
683
819
|
}
|
|
684
820
|
}
|
|
@@ -700,17 +836,17 @@ function installCursorHooks() {
|
|
|
700
836
|
changed = true;
|
|
701
837
|
}
|
|
702
838
|
}
|
|
703
|
-
if (changed || !
|
|
704
|
-
|
|
839
|
+
if (changed || !fs7.existsSync(hooksPath)) {
|
|
840
|
+
fs7.writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n");
|
|
705
841
|
}
|
|
706
842
|
return { changed, hooksPath };
|
|
707
843
|
}
|
|
708
844
|
function uninstallCursorHooks() {
|
|
709
845
|
const hooksPath = cursorHooksJsonPath();
|
|
710
|
-
if (!
|
|
846
|
+
if (!fs7.existsSync(hooksPath)) return { changed: false };
|
|
711
847
|
let config;
|
|
712
848
|
try {
|
|
713
|
-
config = JSON.parse(
|
|
849
|
+
config = JSON.parse(fs7.readFileSync(hooksPath, "utf8"));
|
|
714
850
|
} catch {
|
|
715
851
|
return { changed: false };
|
|
716
852
|
}
|
|
@@ -727,14 +863,14 @@ function uninstallCursorHooks() {
|
|
|
727
863
|
}
|
|
728
864
|
}
|
|
729
865
|
if (changed) {
|
|
730
|
-
|
|
866
|
+
fs7.writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n");
|
|
731
867
|
}
|
|
732
868
|
return { changed };
|
|
733
869
|
}
|
|
734
870
|
function extensionIsActive() {
|
|
735
|
-
const infoPath =
|
|
871
|
+
const infoPath = path6.join(os5.homedir(), ".cursor", "promptai-listener.json");
|
|
736
872
|
try {
|
|
737
|
-
const info = JSON.parse(
|
|
873
|
+
const info = JSON.parse(fs7.readFileSync(infoPath, "utf8"));
|
|
738
874
|
if (typeof info.pid !== "number") return false;
|
|
739
875
|
process.kill(info.pid, 0);
|
|
740
876
|
return true;
|
|
@@ -743,18 +879,18 @@ function extensionIsActive() {
|
|
|
743
879
|
}
|
|
744
880
|
}
|
|
745
881
|
function acquireOnceLock(key) {
|
|
746
|
-
const dir =
|
|
747
|
-
|
|
882
|
+
const dir = path6.join(promptaiDir(), "locks");
|
|
883
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
748
884
|
try {
|
|
749
885
|
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
750
|
-
for (const name of
|
|
751
|
-
const p =
|
|
752
|
-
if (
|
|
886
|
+
for (const name of fs7.readdirSync(dir)) {
|
|
887
|
+
const p = path6.join(dir, name);
|
|
888
|
+
if (fs7.statSync(p).mtimeMs < cutoff) fs7.rmSync(p, { recursive: true, force: true });
|
|
753
889
|
}
|
|
754
890
|
} catch {
|
|
755
891
|
}
|
|
756
892
|
try {
|
|
757
|
-
|
|
893
|
+
fs7.mkdirSync(path6.join(dir, key.replace(/[^a-zA-Z0-9_-]/g, "_")));
|
|
758
894
|
return true;
|
|
759
895
|
} catch {
|
|
760
896
|
return false;
|
|
@@ -898,12 +1034,12 @@ import * as readline from "node:readline";
|
|
|
898
1034
|
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
899
1035
|
var WALLET_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
900
1036
|
function promptLine(question) {
|
|
901
|
-
return new Promise((
|
|
1037
|
+
return new Promise((resolve3) => {
|
|
902
1038
|
process.stdout.write(question);
|
|
903
1039
|
const rl = readline.createInterface({ input: process.stdin });
|
|
904
1040
|
rl.once("line", (line) => {
|
|
905
1041
|
rl.close();
|
|
906
|
-
|
|
1042
|
+
resolve3(line);
|
|
907
1043
|
});
|
|
908
1044
|
});
|
|
909
1045
|
}
|
|
@@ -941,17 +1077,6 @@ async function linkEmailInteractive(config, email, source) {
|
|
|
941
1077
|
return false;
|
|
942
1078
|
}
|
|
943
1079
|
}
|
|
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
1080
|
async function onboard(config) {
|
|
956
1081
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
957
1082
|
if (!config.adSide) config.adSide = "right";
|
|
@@ -990,11 +1115,7 @@ async function onboard(config) {
|
|
|
990
1115
|
} else {
|
|
991
1116
|
console.log("Skipped wallet. Set it later: promptai set wallet 0x...");
|
|
992
1117
|
}
|
|
993
|
-
|
|
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
|
-
);
|
|
1118
|
+
config.adSide = config.adSide || "right";
|
|
998
1119
|
saveConfig(config);
|
|
999
1120
|
console.log("");
|
|
1000
1121
|
}
|
|
@@ -1003,24 +1124,24 @@ async function onboard(config) {
|
|
|
1003
1124
|
var HELP = `promptai - ad-subsidized prompt credits for terminal agents
|
|
1004
1125
|
|
|
1005
1126
|
Usage:
|
|
1006
|
-
promptai install claude Wire hooks
|
|
1127
|
+
promptai install claude Wire hooks + Claude Mods native ad plugin
|
|
1007
1128
|
promptai install cursor Wire hooks into ~/.cursor/hooks.json (Agents Window)
|
|
1008
|
-
promptai uninstall <agent> Remove our hooks (other hooks untouched)
|
|
1129
|
+
promptai uninstall <agent> Remove our hooks/plugin (other hooks untouched)
|
|
1009
1130
|
promptai status Device, balance, banked ad credits, recent prompts
|
|
1010
1131
|
promptai watch Open a rewarded ad in the browser now
|
|
1011
1132
|
promptai claim [address] Claim your verified balance as USDC (Base)
|
|
1012
1133
|
promptai set wallet 0x... Set the payout wallet
|
|
1013
1134
|
promptai set email <email> Link your email (required for USDC claims; groups devices)
|
|
1014
1135
|
promptai set server <url> Point at a different API server
|
|
1015
|
-
promptai
|
|
1136
|
+
promptai on | off Toggle rewarded ads
|
|
1016
1137
|
promptai set side right|left|default Where the ad window docks (default: right)
|
|
1017
1138
|
promptai hook (internal) invoked by agent hooks, JSON on stdin
|
|
1018
1139
|
`;
|
|
1019
1140
|
function readStdin() {
|
|
1020
|
-
return new Promise((
|
|
1141
|
+
return new Promise((resolve3) => {
|
|
1021
1142
|
const chunks = [];
|
|
1022
1143
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
1023
|
-
process.stdin.on("end", () =>
|
|
1144
|
+
process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
1024
1145
|
});
|
|
1025
1146
|
}
|
|
1026
1147
|
async function cmdHook() {
|
|
@@ -1032,11 +1153,11 @@ async function cmdHook() {
|
|
|
1032
1153
|
return;
|
|
1033
1154
|
}
|
|
1034
1155
|
const event = String(payload.hook_event_name ?? "");
|
|
1035
|
-
if (process.env.PROMPTAI_DUMP === "1" ||
|
|
1156
|
+
if (process.env.PROMPTAI_DUMP === "1" || fs8.existsSync(path7.join(promptaiDir(), "debug"))) {
|
|
1036
1157
|
try {
|
|
1037
|
-
const dir =
|
|
1038
|
-
|
|
1039
|
-
|
|
1158
|
+
const dir = path7.join(promptaiDir(), "dump");
|
|
1159
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1160
|
+
fs8.writeFileSync(path7.join(dir, `${Date.now()}-${event || "unknown"}.json`), raw);
|
|
1040
1161
|
} catch {
|
|
1041
1162
|
}
|
|
1042
1163
|
}
|
|
@@ -1067,6 +1188,11 @@ async function cmdStatus() {
|
|
|
1067
1188
|
console.log(`email ${email || "(not detected - promptai set email you@example.com)"}`);
|
|
1068
1189
|
console.log(`ads ${config.adsOptIn ? "opted in" : "opted out"}`);
|
|
1069
1190
|
console.log(`side ${config.adSide}`);
|
|
1191
|
+
const nativeInstalled = isClaudePluginInstalled();
|
|
1192
|
+
const nativeActive = isNativePluginActive();
|
|
1193
|
+
console.log(
|
|
1194
|
+
`native ${nativeActive ? "active (AbovePrompt)" : nativeInstalled ? "installed (waiting for Mods session)" : "not installed"}`
|
|
1195
|
+
);
|
|
1070
1196
|
console.log(`watch ${watchUrl(config.serverUrl, config.deviceId, "manual")}`);
|
|
1071
1197
|
try {
|
|
1072
1198
|
const [balance, verified] = await Promise.all([
|
|
@@ -1145,15 +1271,13 @@ async function cmdSet(key, value) {
|
|
|
1145
1271
|
config.wallet = value;
|
|
1146
1272
|
} else if (key === "server" && value && /^https?:\/\//.test(value)) {
|
|
1147
1273
|
config.serverUrl = value.replace(/\/$/, "");
|
|
1148
|
-
} else if (key === "ads" && (value === "on" || value === "off")) {
|
|
1149
|
-
config.adsOptIn = value === "on";
|
|
1150
1274
|
} else if (key === "side" && (value === "right" || value === "left" || value === "default")) {
|
|
1151
1275
|
config.adSide = value;
|
|
1152
1276
|
} else if (key === "email" && value && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value)) {
|
|
1153
1277
|
config.email = value.trim().toLowerCase();
|
|
1154
1278
|
} else {
|
|
1155
1279
|
console.error(
|
|
1156
|
-
"Usage: promptai set wallet 0x... | set server <url> | set
|
|
1280
|
+
"Usage: promptai set wallet 0x... | set server <url> | set side right|left|default | set email you@example.com"
|
|
1157
1281
|
);
|
|
1158
1282
|
process.exitCode = 1;
|
|
1159
1283
|
return;
|
|
@@ -1172,15 +1296,42 @@ async function cmdSet(key, value) {
|
|
|
1172
1296
|
saveConfig(config);
|
|
1173
1297
|
console.log(`${key} updated.`);
|
|
1174
1298
|
}
|
|
1299
|
+
function cmdAds(on) {
|
|
1300
|
+
const config = loadConfig();
|
|
1301
|
+
config.adsOptIn = on;
|
|
1302
|
+
saveConfig(config);
|
|
1303
|
+
console.log(on ? "Ads on." : "Ads off.");
|
|
1304
|
+
}
|
|
1305
|
+
function printInstallNext() {
|
|
1306
|
+
console.log("You can toggle ads with promptai on / promptai off.");
|
|
1307
|
+
console.log("Try: promptai watch, then run a prompt.");
|
|
1308
|
+
}
|
|
1309
|
+
function printClaudeModsHint(installPath) {
|
|
1310
|
+
console.log("");
|
|
1311
|
+
console.log("Native AbovePrompt ads use Claude Mods (function hooks, early access).");
|
|
1312
|
+
console.log("Enabled via env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 in your Claude settings;");
|
|
1313
|
+
console.log("this also turns on any other function-hook plugins you have.");
|
|
1314
|
+
console.log(`Plugin path: ${installPath}`);
|
|
1315
|
+
console.log("Where Mods are unavailable, ads still open in the browser (/watch).");
|
|
1316
|
+
}
|
|
1175
1317
|
async function cmdInstall(agent) {
|
|
1176
1318
|
if (agent === "claude" || agent === "claude-code") {
|
|
1177
1319
|
const { changed, settingsPath } = installClaudeHooks();
|
|
1178
1320
|
console.log(
|
|
1179
1321
|
changed ? `Hooks installed into ${settingsPath}.` : `Hooks already installed in ${settingsPath}.`
|
|
1180
1322
|
);
|
|
1181
|
-
|
|
1323
|
+
const plugin = installClaudePlugin();
|
|
1324
|
+
if (plugin.error) {
|
|
1325
|
+
console.log(`Mods plugin: ${plugin.error}`);
|
|
1326
|
+
} else {
|
|
1327
|
+
console.log(
|
|
1328
|
+
plugin.changed ? `Mods plugin installed into ${plugin.installPath}.` : `Mods plugin already at ${plugin.installPath}.`
|
|
1329
|
+
);
|
|
1330
|
+
printClaudeModsHint(plugin.installPath);
|
|
1331
|
+
}
|
|
1332
|
+
console.log("Claude Code picks hooks up on its next session.");
|
|
1182
1333
|
await onboard(loadConfig());
|
|
1183
|
-
|
|
1334
|
+
printInstallNext();
|
|
1184
1335
|
return;
|
|
1185
1336
|
}
|
|
1186
1337
|
if (agent === "cursor" || agent === "cursor-agents") {
|
|
@@ -1192,7 +1343,7 @@ async function cmdInstall(agent) {
|
|
|
1192
1343
|
"Works in the Agents Window and the classic IDE (the CLI stands down when the promptai extension is running). Cursor hot-reloads hooks.json."
|
|
1193
1344
|
);
|
|
1194
1345
|
await onboard(loadConfig());
|
|
1195
|
-
|
|
1346
|
+
printInstallNext();
|
|
1196
1347
|
return;
|
|
1197
1348
|
}
|
|
1198
1349
|
console.error("Supported agents: claude, cursor (Codex CLI coming next). Usage: promptai install <agent>");
|
|
@@ -1202,6 +1353,10 @@ function cmdUninstall(agent) {
|
|
|
1202
1353
|
if (agent === "claude" || agent === "claude-code") {
|
|
1203
1354
|
const { changed } = uninstallClaudeHooks();
|
|
1204
1355
|
console.log(changed ? "Hooks removed." : "No promptai hooks found.");
|
|
1356
|
+
const plugin = uninstallClaudePlugin();
|
|
1357
|
+
console.log(
|
|
1358
|
+
plugin.changed ? `Mods plugin removed from ${plugin.installPath}.` : "No Mods plugin install found."
|
|
1359
|
+
);
|
|
1205
1360
|
return;
|
|
1206
1361
|
}
|
|
1207
1362
|
if (agent === "cursor" || agent === "cursor-agents") {
|
|
@@ -1236,6 +1391,12 @@ try {
|
|
|
1236
1391
|
case "set":
|
|
1237
1392
|
await cmdSet(args[0], args[1]);
|
|
1238
1393
|
break;
|
|
1394
|
+
case "on":
|
|
1395
|
+
cmdAds(true);
|
|
1396
|
+
break;
|
|
1397
|
+
case "off":
|
|
1398
|
+
cmdAds(false);
|
|
1399
|
+
break;
|
|
1239
1400
|
default:
|
|
1240
1401
|
console.log(HELP);
|
|
1241
1402
|
if (command && command !== "help" && command !== "--help") process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptai.credit/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
-
"README.md"
|
|
25
|
+
"README.md",
|
|
26
|
+
"claude-plugin"
|
|
26
27
|
],
|
|
27
28
|
"scripts": {
|
|
28
29
|
"dev": "tsx src/index.ts",
|
|
29
|
-
"build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\"",
|
|
30
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\" && node scripts/copy-plugin.mjs",
|
|
30
31
|
"prepublishOnly": "pnpm build",
|
|
31
32
|
"typecheck": "tsc --noEmit"
|
|
32
33
|
},
|