clisponsor 1.0.11 → 1.0.16
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 +1 -1
- package/bin/clisponsor.mjs +625 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLIsponsor Hook
|
|
2
2
|
|
|
3
|
-
Hook package codebase for the `clisponsor` installer and Codex, Claude Code, and
|
|
3
|
+
Hook package codebase for the `clisponsor` installer and Codex, Claude Code, Gemini, Antigravity, OpenCode, Pi, GitHub Copilot CLI, and Qwen Code hook templates.
|
|
4
4
|
|
|
5
5
|
This codebase owns local installation, device login, diagnostics, and hook adapters. It must not contain public website, dashboard app, API account, or ad-serving server code.
|
|
6
6
|
|
package/bin/clisponsor.mjs
CHANGED
|
@@ -16,6 +16,30 @@ const DEFAULT_BACKEND_BASE_URL = process.env.CLISPONSOR_BACKEND_BASE_URL || "htt
|
|
|
16
16
|
const HOOK_VERSION = "1.0.0";
|
|
17
17
|
const NETWORK_TIMEOUT_MS = 3000;
|
|
18
18
|
const ANTIGRAVITY_EVENTS = ["PreInvocation"];
|
|
19
|
+
const OPENCODE_NON_TUI_COMMANDS = new Set([
|
|
20
|
+
"completion",
|
|
21
|
+
"acp",
|
|
22
|
+
"mcp",
|
|
23
|
+
"run",
|
|
24
|
+
"debug",
|
|
25
|
+
"providers",
|
|
26
|
+
"auth",
|
|
27
|
+
"agent",
|
|
28
|
+
"upgrade",
|
|
29
|
+
"uninstall",
|
|
30
|
+
"serve",
|
|
31
|
+
"web",
|
|
32
|
+
"models",
|
|
33
|
+
"stats",
|
|
34
|
+
"export",
|
|
35
|
+
"import",
|
|
36
|
+
"github",
|
|
37
|
+
"pr",
|
|
38
|
+
"session",
|
|
39
|
+
"plugin",
|
|
40
|
+
"plug",
|
|
41
|
+
"db",
|
|
42
|
+
]);
|
|
19
43
|
|
|
20
44
|
function argValue(name) {
|
|
21
45
|
const prefix = `${name}=`;
|
|
@@ -130,6 +154,85 @@ function chmodExecutable(file) {
|
|
|
130
154
|
} catch {}
|
|
131
155
|
}
|
|
132
156
|
|
|
157
|
+
function xdgConfigHome() {
|
|
158
|
+
return process.env.XDG_CONFIG_HOME || path.join(HOME, ".config");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function openCodeConfigDir() {
|
|
162
|
+
return process.env.OPENCODE_CONFIG_DIR || path.join(xdgConfigHome(), "opencode");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function copilotHome() {
|
|
166
|
+
return process.env.COPILOT_HOME || path.join(HOME, ".copilot");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function qwenHome() {
|
|
170
|
+
return process.env.QWEN_HOME || path.join(HOME, ".qwen");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function colorEnabled() {
|
|
174
|
+
return Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function color(value, code) {
|
|
178
|
+
if (!colorEnabled()) return value;
|
|
179
|
+
return `\u001b[${code}m${value}\u001b[0m`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function green(value) {
|
|
183
|
+
return color(value, "32");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function red(value) {
|
|
187
|
+
return color(value, "31");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function cyan(value) {
|
|
191
|
+
return color(value, "36");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function dim(value) {
|
|
195
|
+
return color(value, "2");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function bold(value) {
|
|
199
|
+
return color(value, "1");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function targetSuffix(spec) {
|
|
203
|
+
return dim(`(${spec.command})`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function installLine(symbol, spec, message, colorFn = (value) => value) {
|
|
207
|
+
console.log(`${colorFn(symbol)} ${spec.label} ${targetSuffix(spec)} - ${message}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function startInstallSpinner(spec) {
|
|
211
|
+
if (!process.stdout.isTTY) return () => {};
|
|
212
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
213
|
+
let index = 0;
|
|
214
|
+
const render = () => {
|
|
215
|
+
process.stdout.write(`\r${cyan(frames[index % frames.length])} ${spec.label} ${targetSuffix(spec)} - installing...`);
|
|
216
|
+
index += 1;
|
|
217
|
+
};
|
|
218
|
+
render();
|
|
219
|
+
const timer = setInterval(render, 80);
|
|
220
|
+
return () => {
|
|
221
|
+
clearInterval(timer);
|
|
222
|
+
process.stdout.write("\r\u001b[2K");
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function withoutInstallLogs(action) {
|
|
227
|
+
const originalLog = console.log;
|
|
228
|
+
console.log = () => {};
|
|
229
|
+
try {
|
|
230
|
+
return action();
|
|
231
|
+
} finally {
|
|
232
|
+
console.log = originalLog;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
133
236
|
function commandExists(command) {
|
|
134
237
|
const paths = String(process.env.PATH || "").split(path.delimiter).filter(Boolean);
|
|
135
238
|
const extensions = process.platform === "win32" ? String(process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";") : [""];
|
|
@@ -162,6 +265,10 @@ function isClisponsorCommand(value) {
|
|
|
162
265
|
(value.includes("clisponsor_claude_hook.mjs") ||
|
|
163
266
|
value.includes("clisponsor_gemini_hook.mjs") ||
|
|
164
267
|
value.includes("clisponsor_antigravity_hook.mjs") ||
|
|
268
|
+
value.includes("clisponsor_opencode_plugin") ||
|
|
269
|
+
value.includes("clisponsor_pi_extension") ||
|
|
270
|
+
value.includes("clisponsor_copilot_hook.mjs") ||
|
|
271
|
+
value.includes("clisponsor_qwen_hook.mjs") ||
|
|
165
272
|
value.includes(`${path.sep}.clisponsor${path.sep}`) ||
|
|
166
273
|
value.includes("/.clisponsor/") ||
|
|
167
274
|
value.includes("\\.clisponsor\\"))
|
|
@@ -235,6 +342,28 @@ function addGeminiCommandHook(settings, eventName, matcher, command) {
|
|
|
235
342
|
return true;
|
|
236
343
|
}
|
|
237
344
|
|
|
345
|
+
function addQwenCommandHook(settings, eventName, command) {
|
|
346
|
+
if (!isPlainObject(settings.hooks)) settings.hooks = {};
|
|
347
|
+
const current = Array.isArray(settings.hooks[eventName]) ? settings.hooks[eventName] : [];
|
|
348
|
+
const exists = current.some((entry) => isClisponsorHookEntry(entry));
|
|
349
|
+
if (exists) return false;
|
|
350
|
+
|
|
351
|
+
settings.hooks[eventName] = [
|
|
352
|
+
...current,
|
|
353
|
+
{
|
|
354
|
+
hooks: [
|
|
355
|
+
{
|
|
356
|
+
type: "command",
|
|
357
|
+
command,
|
|
358
|
+
name: "clisponsor",
|
|
359
|
+
timeout: 5000,
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
238
367
|
function removeClaudeCommandHooks(settings) {
|
|
239
368
|
if (!isPlainObject(settings.hooks)) return false;
|
|
240
369
|
let changed = false;
|
|
@@ -481,6 +610,270 @@ try {
|
|
|
481
610
|
`;
|
|
482
611
|
}
|
|
483
612
|
|
|
613
|
+
function openCodePluginSource() {
|
|
614
|
+
return `import fs from "node:fs";
|
|
615
|
+
import crypto from "node:crypto";
|
|
616
|
+
|
|
617
|
+
const CONFIG_PATH = ${JSON.stringify(CONFIG_PATH)};
|
|
618
|
+
const HOOK_VERSION = ${JSON.stringify(HOOK_VERSION)};
|
|
619
|
+
const NON_TUI_COMMANDS = new Set(${JSON.stringify([...OPENCODE_NON_TUI_COMMANDS])});
|
|
620
|
+
|
|
621
|
+
function readConfig() {
|
|
622
|
+
try {
|
|
623
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
624
|
+
} catch {
|
|
625
|
+
return {};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function likelyTuiInvocation() {
|
|
630
|
+
const args = process.argv.slice(2).filter((arg) => !arg.startsWith("-"));
|
|
631
|
+
if (args.length === 0) return true;
|
|
632
|
+
return !NON_TUI_COMMANDS.has(args[0]);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function sponsoredLine(line) {
|
|
636
|
+
return "[Sponsored] " + line;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export const CLIsponsorOpenCodePlugin = async ({ client }) => {
|
|
640
|
+
if (!likelyTuiInvocation()) return {};
|
|
641
|
+
let startSessionRequested = false;
|
|
642
|
+
|
|
643
|
+
async function serve(event, placement, metadata = {}) {
|
|
644
|
+
if (placement === "StartSession") {
|
|
645
|
+
if (startSessionRequested) return;
|
|
646
|
+
startSessionRequested = true;
|
|
647
|
+
}
|
|
648
|
+
const cfg = readConfig();
|
|
649
|
+
const serveBaseUrl = cfg.serveBaseUrl || cfg.apiBaseUrl;
|
|
650
|
+
if (!serveBaseUrl || !cfg.userId || !cfg.deviceCode || !cfg.deviceSecret) return;
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
const res = await fetch(serveBaseUrl + "/v1/ads/serve", {
|
|
654
|
+
method: "POST",
|
|
655
|
+
headers: {
|
|
656
|
+
"content-type": "application/json",
|
|
657
|
+
"authorization": "Bearer " + cfg.deviceSecret,
|
|
658
|
+
"x-clisponsor-hook-version": HOOK_VERSION,
|
|
659
|
+
},
|
|
660
|
+
body: JSON.stringify({
|
|
661
|
+
user_id: cfg.userId,
|
|
662
|
+
device_code: cfg.deviceCode,
|
|
663
|
+
client: "OpenCode",
|
|
664
|
+
hook_event: event,
|
|
665
|
+
placement,
|
|
666
|
+
idempotency_key: crypto.randomUUID(),
|
|
667
|
+
metadata: {
|
|
668
|
+
hookVersion: HOOK_VERSION,
|
|
669
|
+
openCode: metadata,
|
|
670
|
+
},
|
|
671
|
+
}),
|
|
672
|
+
});
|
|
673
|
+
if (!res.ok) return;
|
|
674
|
+
const ad = await res.json();
|
|
675
|
+
if (!ad.display_line) return;
|
|
676
|
+
await client.tui.showToast({
|
|
677
|
+
body: {
|
|
678
|
+
title: "CLIsponsor " + placement,
|
|
679
|
+
message: sponsoredLine(ad.display_line),
|
|
680
|
+
variant: "info",
|
|
681
|
+
duration: 10000,
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
} catch {}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const startupTimer = setTimeout(() => {
|
|
688
|
+
void serve("plugin.start", "StartSession");
|
|
689
|
+
}, 2500);
|
|
690
|
+
startupTimer.unref?.();
|
|
691
|
+
|
|
692
|
+
return {
|
|
693
|
+
event: async ({ event }) => {
|
|
694
|
+
if (event.type === "session.created") {
|
|
695
|
+
await serve("session.created", "StartSession", {
|
|
696
|
+
sessionID: event.properties?.sessionID,
|
|
697
|
+
});
|
|
698
|
+
} else if (event.type === "session.idle") {
|
|
699
|
+
await serve("session.idle", "EndTurn", {
|
|
700
|
+
sessionID: event.properties?.sessionID,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
"chat.message": async (input) => {
|
|
705
|
+
await serve("chat.message", "StartTurn", {
|
|
706
|
+
sessionID: input.sessionID,
|
|
707
|
+
agent: input.agent,
|
|
708
|
+
});
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
};
|
|
712
|
+
`;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function piExtensionSource() {
|
|
716
|
+
return `import fs from "node:fs";
|
|
717
|
+
import crypto from "node:crypto";
|
|
718
|
+
|
|
719
|
+
const CONFIG_PATH = ${JSON.stringify(CONFIG_PATH)};
|
|
720
|
+
const HOOK_VERSION = ${JSON.stringify(HOOK_VERSION)};
|
|
721
|
+
|
|
722
|
+
function readConfig() {
|
|
723
|
+
try {
|
|
724
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
725
|
+
} catch {
|
|
726
|
+
return {};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function sponsoredLine(line) {
|
|
731
|
+
return "[Sponsored] " + line;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function canShow(ctx) {
|
|
735
|
+
return Boolean(ctx?.hasUI && ctx?.ui?.notify);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
export default function CLIsponsorPiExtension(pi) {
|
|
739
|
+
async function serve(event, placement, ctx, metadata = {}) {
|
|
740
|
+
if (!canShow(ctx)) return;
|
|
741
|
+
const cfg = readConfig();
|
|
742
|
+
const serveBaseUrl = cfg.serveBaseUrl || cfg.apiBaseUrl;
|
|
743
|
+
if (!serveBaseUrl || !cfg.userId || !cfg.deviceCode || !cfg.deviceSecret) return;
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
const res = await fetch(serveBaseUrl + "/v1/ads/serve", {
|
|
747
|
+
method: "POST",
|
|
748
|
+
headers: {
|
|
749
|
+
"content-type": "application/json",
|
|
750
|
+
"authorization": "Bearer " + cfg.deviceSecret,
|
|
751
|
+
"x-clisponsor-hook-version": HOOK_VERSION,
|
|
752
|
+
},
|
|
753
|
+
body: JSON.stringify({
|
|
754
|
+
user_id: cfg.userId,
|
|
755
|
+
device_code: cfg.deviceCode,
|
|
756
|
+
client: "Pi",
|
|
757
|
+
hook_event: event,
|
|
758
|
+
placement,
|
|
759
|
+
idempotency_key: crypto.randomUUID(),
|
|
760
|
+
metadata: {
|
|
761
|
+
hookVersion: HOOK_VERSION,
|
|
762
|
+
pi: metadata,
|
|
763
|
+
},
|
|
764
|
+
}),
|
|
765
|
+
});
|
|
766
|
+
if (!res.ok) return;
|
|
767
|
+
const ad = await res.json();
|
|
768
|
+
if (!ad.display_line) return;
|
|
769
|
+
ctx.ui.notify("CLIsponsor " + placement + "\\n" + sponsoredLine(ad.display_line), "info");
|
|
770
|
+
} catch {}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
pi.on("session_start", async (event, ctx) => {
|
|
774
|
+
await serve("session_start", "StartSession", ctx, {
|
|
775
|
+
reason: event?.reason,
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
780
|
+
await serve("agent_start", "StartTurn", ctx);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
784
|
+
await serve("agent_end", "EndTurn", ctx);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
`;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function copilotHookSource() {
|
|
791
|
+
return `#!/usr/bin/env node
|
|
792
|
+
import fs from "node:fs";
|
|
793
|
+
import crypto from "node:crypto";
|
|
794
|
+
|
|
795
|
+
const event = process.argv[2] || "userPromptSubmitted";
|
|
796
|
+
const CONFIG_PATH = ${JSON.stringify(CONFIG_PATH)};
|
|
797
|
+
const HOOK_VERSION = ${JSON.stringify(HOOK_VERSION)};
|
|
798
|
+
const placements = {
|
|
799
|
+
sessionStart: "StartSession",
|
|
800
|
+
userPromptSubmitted: "StartTurn",
|
|
801
|
+
agentStop: "EndTurn",
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
function sponsoredLine(line) {
|
|
805
|
+
return "[Sponsored] " + line;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function readStdin() {
|
|
809
|
+
return new Promise((resolve) => {
|
|
810
|
+
let data = "";
|
|
811
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
812
|
+
process.stdin.on("end", () => resolve(data));
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function readConfig() {
|
|
817
|
+
try {
|
|
818
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
819
|
+
} catch {
|
|
820
|
+
return {};
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function progress(message) {
|
|
825
|
+
console.log(JSON.stringify({ type: "progress", message }));
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const inputRaw = await readStdin();
|
|
829
|
+
let input = {};
|
|
830
|
+
try {
|
|
831
|
+
input = inputRaw.trim() ? JSON.parse(inputRaw) : {};
|
|
832
|
+
} catch {}
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
const placement = placements[event];
|
|
836
|
+
if (!placement) process.exit(0);
|
|
837
|
+
const cfg = readConfig();
|
|
838
|
+
const serveBaseUrl = cfg.serveBaseUrl || cfg.apiBaseUrl;
|
|
839
|
+
if (!serveBaseUrl || !cfg.userId || !cfg.deviceCode || !cfg.deviceSecret) process.exit(0);
|
|
840
|
+
|
|
841
|
+
const res = await fetch(serveBaseUrl + "/v1/ads/serve", {
|
|
842
|
+
method: "POST",
|
|
843
|
+
headers: {
|
|
844
|
+
"content-type": "application/json",
|
|
845
|
+
"authorization": "Bearer " + cfg.deviceSecret,
|
|
846
|
+
"x-clisponsor-hook-version": HOOK_VERSION,
|
|
847
|
+
},
|
|
848
|
+
body: JSON.stringify({
|
|
849
|
+
user_id: cfg.userId,
|
|
850
|
+
device_code: cfg.deviceCode,
|
|
851
|
+
client: "GitHubCopilot",
|
|
852
|
+
hook_event: event,
|
|
853
|
+
placement,
|
|
854
|
+
idempotency_key: crypto.randomUUID(),
|
|
855
|
+
metadata: {
|
|
856
|
+
hookVersion: HOOK_VERSION,
|
|
857
|
+
copilot: {
|
|
858
|
+
sessionId: input.sessionId || input.session_id,
|
|
859
|
+
source: input.source,
|
|
860
|
+
reason: input.reason,
|
|
861
|
+
stopReason: input.stopReason || input.stop_reason,
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
}),
|
|
865
|
+
});
|
|
866
|
+
if (!res.ok) process.exit(0);
|
|
867
|
+
const ad = await res.json();
|
|
868
|
+
if (!ad.display_line) process.exit(0);
|
|
869
|
+
progress("CLIsponsor " + placement + ": " + sponsoredLine(ad.display_line));
|
|
870
|
+
console.log(JSON.stringify({}));
|
|
871
|
+
} catch {
|
|
872
|
+
process.exit(0);
|
|
873
|
+
}
|
|
874
|
+
`;
|
|
875
|
+
}
|
|
876
|
+
|
|
484
877
|
function installGemini() {
|
|
485
878
|
if (!commandExists("gemini")) {
|
|
486
879
|
console.log("Gemini CLI not found. To enable CLIsponsor for Gemini, install Gemini CLI and rerun: npx clisponsor@latest install");
|
|
@@ -521,24 +914,180 @@ function installAntigravity() {
|
|
|
521
914
|
console.log("Antigravity CLI hook installed.");
|
|
522
915
|
}
|
|
523
916
|
|
|
917
|
+
function installOpenCode() {
|
|
918
|
+
if (!commandExists("opencode")) {
|
|
919
|
+
console.log("OpenCode CLI not found. To enable CLIsponsor for OpenCode, install OpenCode CLI and rerun: npx clisponsor@latest install");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const opencodeDir = path.join(CONFIG_DIR, "opencode");
|
|
924
|
+
fs.mkdirSync(opencodeDir, { recursive: true });
|
|
925
|
+
const stagedPlugin = path.join(opencodeDir, "clisponsor_opencode_plugin.js");
|
|
926
|
+
fs.writeFileSync(stagedPlugin, openCodePluginSource(), { mode: 0o644 });
|
|
927
|
+
|
|
928
|
+
const pluginsDir = path.join(openCodeConfigDir(), "plugins");
|
|
929
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
930
|
+
const installedPlugin = path.join(pluginsDir, "clisponsor_opencode_plugin.js");
|
|
931
|
+
fs.copyFileSync(stagedPlugin, installedPlugin);
|
|
932
|
+
console.log(`Updated ${installedPlugin}`);
|
|
933
|
+
console.log("OpenCode CLI plugin installed.");
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function installPi() {
|
|
937
|
+
if (!commandExists("pi")) {
|
|
938
|
+
console.log("Pi CLI not found. To enable CLIsponsor for Pi, install Pi Coding Agent and rerun: npx clisponsor@latest install");
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const piDir = path.join(CONFIG_DIR, "pi");
|
|
943
|
+
fs.mkdirSync(piDir, { recursive: true });
|
|
944
|
+
const stagedExtension = path.join(piDir, "clisponsor_pi_extension.ts");
|
|
945
|
+
fs.writeFileSync(stagedExtension, piExtensionSource(), { mode: 0o644 });
|
|
946
|
+
|
|
947
|
+
const extensionsDir = path.join(HOME, ".pi", "agent", "extensions");
|
|
948
|
+
fs.mkdirSync(extensionsDir, { recursive: true });
|
|
949
|
+
const installedExtension = path.join(extensionsDir, "clisponsor_pi_extension.ts");
|
|
950
|
+
fs.copyFileSync(stagedExtension, installedExtension);
|
|
951
|
+
console.log(`Updated ${installedExtension}`);
|
|
952
|
+
console.log("Pi CLI extension installed.");
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function installCopilot() {
|
|
956
|
+
if (!commandExists("copilot")) {
|
|
957
|
+
console.log("GitHub Copilot CLI not found. To enable CLIsponsor for GitHub Copilot CLI, install Copilot CLI and rerun: npx clisponsor@latest install");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const copilotDir = path.join(CONFIG_DIR, "copilot");
|
|
962
|
+
fs.mkdirSync(copilotDir, { recursive: true });
|
|
963
|
+
const hookPath = path.join(copilotDir, "clisponsor_copilot_hook.mjs");
|
|
964
|
+
fs.writeFileSync(hookPath, copilotHookSource(), { mode: 0o755 });
|
|
965
|
+
|
|
966
|
+
const hooksPath = path.join(copilotHome(), "hooks", "clisponsor.json");
|
|
967
|
+
writeJson(hooksPath, {
|
|
968
|
+
version: 1,
|
|
969
|
+
hooks: {
|
|
970
|
+
sessionStart: [
|
|
971
|
+
{
|
|
972
|
+
type: "command",
|
|
973
|
+
command: `node ${JSON.stringify(hookPath)} sessionStart`,
|
|
974
|
+
timeoutSec: 5,
|
|
975
|
+
},
|
|
976
|
+
],
|
|
977
|
+
userPromptSubmitted: [
|
|
978
|
+
{
|
|
979
|
+
type: "command",
|
|
980
|
+
command: `node ${JSON.stringify(hookPath)} userPromptSubmitted`,
|
|
981
|
+
timeoutSec: 5,
|
|
982
|
+
},
|
|
983
|
+
],
|
|
984
|
+
agentStop: [
|
|
985
|
+
{
|
|
986
|
+
type: "command",
|
|
987
|
+
command: `node ${JSON.stringify(hookPath)} agentStop`,
|
|
988
|
+
timeoutSec: 5,
|
|
989
|
+
},
|
|
990
|
+
],
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
console.log(`Updated ${hooksPath}`);
|
|
994
|
+
console.log("GitHub Copilot CLI hook installed.");
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function installQwen() {
|
|
998
|
+
if (!commandExists("qwen")) {
|
|
999
|
+
console.log("Qwen Code CLI not found. To enable CLIsponsor for Qwen Code, install Qwen Code and rerun: npx clisponsor@latest install");
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const qwenDir = path.join(CONFIG_DIR, "qwen");
|
|
1004
|
+
fs.mkdirSync(qwenDir, { recursive: true });
|
|
1005
|
+
const hookPath = path.join(qwenDir, "clisponsor_qwen_hook.mjs");
|
|
1006
|
+
fs.writeFileSync(hookPath, agentHookSource("QwenCode"), { mode: 0o755 });
|
|
1007
|
+
|
|
1008
|
+
const settingsPath = path.join(qwenHome(), "settings.json");
|
|
1009
|
+
const settings = readEditableJson(settingsPath, {});
|
|
1010
|
+
addQwenCommandHook(settings, "SessionStart", `node ${JSON.stringify(hookPath)} SessionStart`);
|
|
1011
|
+
addQwenCommandHook(settings, "UserPromptSubmit", `node ${JSON.stringify(hookPath)} UserPromptSubmit`);
|
|
1012
|
+
addQwenCommandHook(settings, "Stop", `node ${JSON.stringify(hookPath)} Stop`);
|
|
1013
|
+
writeJson(settingsPath, settings);
|
|
1014
|
+
console.log(`Updated ${settingsPath}`);
|
|
1015
|
+
console.log("Qwen Code CLI hook installed.");
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function installTargets() {
|
|
1019
|
+
return [
|
|
1020
|
+
{ target: "codex", label: "Codex CLI", command: "codex", install: installCodex },
|
|
1021
|
+
{ target: "claude", label: "Claude Code CLI", command: "claude", install: installClaude },
|
|
1022
|
+
{ target: "gemini", label: "Gemini CLI", command: "gemini", install: installGemini },
|
|
1023
|
+
{ target: "antigravity", aliases: ["agy"], label: "Antigravity CLI", command: "agy", install: installAntigravity },
|
|
1024
|
+
{ target: "opencode", label: "OpenCode CLI", command: "opencode", install: installOpenCode },
|
|
1025
|
+
{ target: "pi", label: "Pi Coding Agent", command: "pi", install: installPi },
|
|
1026
|
+
{ target: "copilot", label: "GitHub Copilot CLI", command: "copilot", install: installCopilot },
|
|
1027
|
+
{ target: "qwen", label: "Qwen Code", command: "qwen", install: installQwen },
|
|
1028
|
+
];
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function targetNames() {
|
|
1032
|
+
return installTargets().flatMap((spec) => [spec.target, ...(spec.aliases || [])]);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function findInstallTarget(target) {
|
|
1036
|
+
return installTargets().find((spec) => spec.target === target || (spec.aliases || []).includes(target));
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function renderInstallHeader(target) {
|
|
1040
|
+
const scope = target === "all" ? "supported CLIs" : findInstallTarget(target)?.label || target;
|
|
1041
|
+
console.log(bold("CLIsponsor installer"));
|
|
1042
|
+
console.log(dim(`Scanning ${scope} on this machine...`));
|
|
1043
|
+
console.log("");
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function renderInstallSummary(results) {
|
|
1047
|
+
const installed = results.filter((result) => result.status === "installed").length;
|
|
1048
|
+
const missing = results.filter((result) => result.status === "missing").length;
|
|
1049
|
+
const failed = results.filter((result) => result.status === "failed").length;
|
|
1050
|
+
console.log("");
|
|
1051
|
+
const summary = `${installed} installed, ${missing} not found${failed ? `, ${failed} failed` : ""}.`;
|
|
1052
|
+
console.log(failed ? red(summary) : green(summary));
|
|
1053
|
+
if (missing) {
|
|
1054
|
+
console.log(dim("Install missing CLIs and rerun: npx clisponsor@latest install"));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function installTarget(spec) {
|
|
1059
|
+
if (!commandExists(spec.command)) {
|
|
1060
|
+
installLine("✕", spec, "not found", red);
|
|
1061
|
+
return { target: spec.target, status: "missing" };
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const stopSpinner = startInstallSpinner(spec);
|
|
1065
|
+
try {
|
|
1066
|
+
withoutInstallLogs(spec.install);
|
|
1067
|
+
stopSpinner();
|
|
1068
|
+
installLine("✓", spec, "installed", green);
|
|
1069
|
+
return { target: spec.target, status: "installed" };
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
stopSpinner();
|
|
1072
|
+
installLine("!", spec, `failed: ${error.message}`, red);
|
|
1073
|
+
return { target: spec.target, status: "failed", error };
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
524
1077
|
function installAll() {
|
|
525
|
-
|
|
526
|
-
installClaude();
|
|
527
|
-
installGemini();
|
|
528
|
-
installAntigravity();
|
|
1078
|
+
return installTargets().map((spec) => installTarget(spec));
|
|
529
1079
|
}
|
|
530
1080
|
|
|
531
1081
|
function install() {
|
|
532
1082
|
const target = process.argv[3] && !process.argv[3].startsWith("--") ? process.argv[3] : "all";
|
|
533
|
-
if (!["all",
|
|
534
|
-
console.error("Unknown install target. Use: codex, claude, gemini, antigravity, or all.");
|
|
1083
|
+
if (!["all", ...targetNames()].includes(target)) {
|
|
1084
|
+
console.error("Unknown install target. Use: codex, claude, gemini, antigravity, opencode, pi, copilot, qwen, or all.");
|
|
535
1085
|
process.exit(1);
|
|
536
1086
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
else installAntigravity();
|
|
1087
|
+
renderInstallHeader(target);
|
|
1088
|
+
const results = target === "all" ? installAll() : [installTarget(findInstallTarget(target))];
|
|
1089
|
+
renderInstallSummary(results);
|
|
1090
|
+
if (results.some((result) => result.status === "failed")) process.exit(1);
|
|
542
1091
|
}
|
|
543
1092
|
|
|
544
1093
|
function uninstallCodex() {
|
|
@@ -591,16 +1140,69 @@ function uninstallAntigravity() {
|
|
|
591
1140
|
console.log("Removed Antigravity hook script.");
|
|
592
1141
|
}
|
|
593
1142
|
|
|
1143
|
+
function uninstallOpenCode() {
|
|
1144
|
+
const installedPlugin = path.join(openCodeConfigDir(), "plugins", "clisponsor_opencode_plugin.js");
|
|
1145
|
+
if (fs.existsSync(installedPlugin)) {
|
|
1146
|
+
fs.rmSync(installedPlugin, { force: true });
|
|
1147
|
+
console.log(`Removed ${installedPlugin}`);
|
|
1148
|
+
} else {
|
|
1149
|
+
console.log("No CLIsponsor OpenCode plugin found.");
|
|
1150
|
+
}
|
|
1151
|
+
fs.rmSync(path.join(CONFIG_DIR, "opencode"), { recursive: true, force: true });
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function uninstallPi() {
|
|
1155
|
+
const installedExtension = path.join(HOME, ".pi", "agent", "extensions", "clisponsor_pi_extension.ts");
|
|
1156
|
+
if (fs.existsSync(installedExtension)) {
|
|
1157
|
+
fs.rmSync(installedExtension, { force: true });
|
|
1158
|
+
console.log(`Removed ${installedExtension}`);
|
|
1159
|
+
} else {
|
|
1160
|
+
console.log("No CLIsponsor Pi extension found.");
|
|
1161
|
+
}
|
|
1162
|
+
fs.rmSync(path.join(CONFIG_DIR, "pi"), { recursive: true, force: true });
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function uninstallCopilot() {
|
|
1166
|
+
const hooksPath = path.join(copilotHome(), "hooks", "clisponsor.json");
|
|
1167
|
+
if (fs.existsSync(hooksPath)) {
|
|
1168
|
+
fs.rmSync(hooksPath, { force: true });
|
|
1169
|
+
console.log(`Removed ${hooksPath}`);
|
|
1170
|
+
} else {
|
|
1171
|
+
console.log("No CLIsponsor GitHub Copilot CLI hook found.");
|
|
1172
|
+
}
|
|
1173
|
+
fs.rmSync(path.join(CONFIG_DIR, "copilot"), { recursive: true, force: true });
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function uninstallQwen() {
|
|
1177
|
+
const settingsPath = path.join(qwenHome(), "settings.json");
|
|
1178
|
+
const settings = readEditableJson(settingsPath, {});
|
|
1179
|
+
if (removeClaudeCommandHooks(settings)) {
|
|
1180
|
+
writeJson(settingsPath, settings);
|
|
1181
|
+
console.log(`Removed CLIsponsor hooks from ${settingsPath}`);
|
|
1182
|
+
} else {
|
|
1183
|
+
console.log("No CLIsponsor Qwen Code hooks found.");
|
|
1184
|
+
}
|
|
1185
|
+
fs.rmSync(path.join(CONFIG_DIR, "qwen", "clisponsor_qwen_hook.mjs"), { force: true });
|
|
1186
|
+
try {
|
|
1187
|
+
fs.rmdirSync(path.join(CONFIG_DIR, "qwen"));
|
|
1188
|
+
} catch {}
|
|
1189
|
+
console.log("Removed Qwen Code hook script.");
|
|
1190
|
+
}
|
|
1191
|
+
|
|
594
1192
|
function uninstall() {
|
|
595
1193
|
const target = process.argv[3] && !process.argv[3].startsWith("--") ? process.argv[3] : "all";
|
|
596
|
-
if (!["all", "codex", "claude", "gemini", "antigravity", "agy"].includes(target)) {
|
|
597
|
-
console.error("Unknown uninstall target. Use: codex, claude, gemini, antigravity, or all.");
|
|
1194
|
+
if (!["all", "codex", "claude", "gemini", "antigravity", "agy", "opencode", "pi", "copilot", "qwen"].includes(target)) {
|
|
1195
|
+
console.error("Unknown uninstall target. Use: codex, claude, gemini, antigravity, opencode, pi, copilot, qwen, or all.");
|
|
598
1196
|
process.exit(1);
|
|
599
1197
|
}
|
|
600
1198
|
if (target === "all" || target === "codex") uninstallCodex();
|
|
601
1199
|
if (target === "all" || target === "claude") uninstallClaude();
|
|
602
1200
|
if (target === "all" || target === "gemini") uninstallGemini();
|
|
603
1201
|
if (target === "all" || target === "antigravity" || target === "agy") uninstallAntigravity();
|
|
1202
|
+
if (target === "all" || target === "opencode") uninstallOpenCode();
|
|
1203
|
+
if (target === "all" || target === "pi") uninstallPi();
|
|
1204
|
+
if (target === "all" || target === "copilot") uninstallCopilot();
|
|
1205
|
+
if (target === "all" || target === "qwen") uninstallQwen();
|
|
604
1206
|
if (hasFlag("--config")) {
|
|
605
1207
|
fs.rmSync(CONFIG_PATH, { force: true });
|
|
606
1208
|
console.log(`Removed ${CONFIG_PATH}`);
|
|
@@ -660,6 +1262,10 @@ async function doctor() {
|
|
|
660
1262
|
claudeHookScript: fs.existsSync(path.join(CONFIG_DIR, "claude", "clisponsor_claude_hook.mjs")),
|
|
661
1263
|
geminiHookScript: fs.existsSync(path.join(CONFIG_DIR, "gemini", "clisponsor_gemini_hook.mjs")),
|
|
662
1264
|
antigravityHookScript: fs.existsSync(path.join(CONFIG_DIR, "antigravity", "clisponsor_antigravity_hook.mjs")),
|
|
1265
|
+
opencodePlugin: fs.existsSync(path.join(openCodeConfigDir(), "plugins", "clisponsor_opencode_plugin.js")),
|
|
1266
|
+
piExtension: fs.existsSync(path.join(HOME, ".pi", "agent", "extensions", "clisponsor_pi_extension.ts")),
|
|
1267
|
+
copilotHook: fs.existsSync(path.join(copilotHome(), "hooks", "clisponsor.json")),
|
|
1268
|
+
qwenHookScript: fs.existsSync(path.join(CONFIG_DIR, "qwen", "clisponsor_qwen_hook.mjs")),
|
|
663
1269
|
},
|
|
664
1270
|
network: {},
|
|
665
1271
|
};
|
|
@@ -687,6 +1293,10 @@ async function doctor() {
|
|
|
687
1293
|
console.log(`Claude hook script: ${diagnostics.installed.claudeHookScript ? "yes" : "no"}`);
|
|
688
1294
|
console.log(`Gemini hook script: ${diagnostics.installed.geminiHookScript ? "yes" : "no"}`);
|
|
689
1295
|
console.log(`Antigravity hook script: ${diagnostics.installed.antigravityHookScript ? "yes" : "no"}`);
|
|
1296
|
+
console.log(`OpenCode plugin: ${diagnostics.installed.opencodePlugin ? "yes" : "no"}`);
|
|
1297
|
+
console.log(`Pi extension: ${diagnostics.installed.piExtension ? "yes" : "no"}`);
|
|
1298
|
+
console.log(`GitHub Copilot CLI hook: ${diagnostics.installed.copilotHook ? "yes" : "no"}`);
|
|
1299
|
+
console.log(`Qwen Code hook script: ${diagnostics.installed.qwenHookScript ? "yes" : "no"}`);
|
|
690
1300
|
if (skipNetwork) {
|
|
691
1301
|
console.log("Network: skipped");
|
|
692
1302
|
} else {
|
|
@@ -697,9 +1307,9 @@ async function doctor() {
|
|
|
697
1307
|
|
|
698
1308
|
function help() {
|
|
699
1309
|
console.log(`clisponsor commands:
|
|
700
|
-
clisponsor install [all|codex|claude|gemini|antigravity]
|
|
1310
|
+
clisponsor install [all|codex|claude|gemini|antigravity|opencode|pi|copilot|qwen]
|
|
701
1311
|
clisponsor login <email> [--label=<device-label>]
|
|
702
|
-
clisponsor uninstall [all|codex|claude|gemini|antigravity] [--config]
|
|
1312
|
+
clisponsor uninstall [all|codex|claude|gemini|antigravity|opencode|pi|copilot|qwen] [--config]
|
|
703
1313
|
clisponsor status
|
|
704
1314
|
clisponsor doctor [--json] [--skip-network]
|
|
705
1315
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clisponsor",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "CLIsponsor installer for Codex, Claude Code, and
|
|
3
|
+
"version": "1.0.16",
|
|
4
|
+
"description": "CLIsponsor installer for Codex, Claude Code, Gemini, Antigravity, OpenCode, Pi, GitHub Copilot CLI, and Qwen Code sponsored CLI placements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20"
|