opencode-anthropic-fix 0.0.14

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.
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Installation script for opencode-anthropic-auth.
5
+ *
6
+ * Installs two things:
7
+ * 1. Plugin → ~/.config/opencode/plugin/opencode-anthropic-auth-plugin.js
8
+ * 2. CLI → ~/.local/bin/opencode-anthropic-auth
9
+ *
10
+ * Usage:
11
+ * node scripts/install.mjs link Symlink both (development)
12
+ * node scripts/install.mjs copy Copy both (stable deployment)
13
+ * node scripts/install.mjs uninstall Remove both
14
+ */
15
+
16
+ import { existsSync, lstatSync, readlinkSync } from "node:fs";
17
+ import { mkdir, symlink, unlink, rm, copyFile, chmod } from "node:fs/promises";
18
+ import { join, resolve, dirname } from "node:path";
19
+ import { homedir } from "node:os";
20
+
21
+ import { fileURLToPath } from "node:url";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Paths
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const PROJECT_ROOT = resolve(dirname(__filename), "..");
29
+ const PLUGIN_NAME = "opencode-anthropic-auth";
30
+ const PLUGIN_ENTRY = "opencode-anthropic-auth-plugin.js";
31
+ const CLI_BIN_NAME = "opencode-anthropic-auth";
32
+
33
+ /**
34
+ * Get the OpenCode plugin directory, respecting XDG_CONFIG_HOME.
35
+ * @returns {string}
36
+ */
37
+ function getPluginDir() {
38
+ const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
39
+ return join(configHome, "opencode", "plugin");
40
+ }
41
+
42
+ /**
43
+ * Get the user bin directory (~/.local/bin).
44
+ * @returns {string}
45
+ */
46
+ function getBinDir() {
47
+ return join(homedir(), ".local", "bin");
48
+ }
49
+
50
+ const DIST_DIR = join(PROJECT_ROOT, "dist");
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Helpers
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const bold = (t) => `\x1b[1m${t}\x1b[0m`;
57
+ const green = (t) => `\x1b[32m${t}\x1b[0m`;
58
+ const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
59
+ const red = (t) => `\x1b[31m${t}\x1b[0m`;
60
+ const dim = (t) => `\x1b[2m${t}\x1b[0m`;
61
+
62
+ function printEnvReminder() {
63
+ console.log(yellow("\nNote: if using OpenCode v1.1.52 or earlier, set OPENCODE_DISABLE_DEFAULT_PLUGINS=1"));
64
+ console.log(dim("This is not needed on newer versions where user plugins take priority."));
65
+ }
66
+
67
+ function shortPath(p) {
68
+ const home = homedir();
69
+ if (p.startsWith(home)) return "~" + p.slice(home.length);
70
+ return p;
71
+ }
72
+
73
+ /**
74
+ * Check what currently exists at a path.
75
+ * @param {string} path
76
+ * @returns {{ exists: boolean, isSymlink: boolean, target: string | null, isDir: boolean }}
77
+ */
78
+ function checkExisting(path) {
79
+ if (!existsSync(path)) {
80
+ return { exists: false, isSymlink: false, target: null, isDir: false };
81
+ }
82
+ const stat = lstatSync(path);
83
+ const isSymlink = stat.isSymbolicLink();
84
+ let target = null;
85
+ if (isSymlink) {
86
+ try {
87
+ target = readlinkSync(path);
88
+ } catch {
89
+ // ignore
90
+ }
91
+ }
92
+ return { exists: true, isSymlink, target, isDir: stat.isDirectory() };
93
+ }
94
+
95
+ /**
96
+ * Create or replace a symlink.
97
+ * @param {string} target - What the symlink points to
98
+ * @param {string} linkPath - Where the symlink lives
99
+ * @param {string} label - Human-readable name for logging
100
+ * @returns {Promise<boolean>} true if created, false if already correct
101
+ */
102
+ async function ensureSymlink(target, linkPath, label) {
103
+ await mkdir(dirname(linkPath), { recursive: true });
104
+
105
+ const existing = checkExisting(linkPath);
106
+
107
+ if (existing.exists) {
108
+ if (existing.isSymlink && existing.target === target) {
109
+ console.log(green(`${label}: already linked.`));
110
+ console.log(dim(` ${shortPath(linkPath)} -> ${shortPath(target)}`));
111
+ return false;
112
+ }
113
+
114
+ if (existing.isSymlink) {
115
+ console.log(yellow(`${label}: replacing symlink (was -> ${existing.target})`));
116
+ } else if (existing.isDir) {
117
+ console.log(yellow(`${label}: replacing directory`));
118
+ await rm(linkPath, { recursive: true, force: true });
119
+ } else {
120
+ console.log(yellow(`${label}: replacing existing file`));
121
+ }
122
+ if (!existing.isDir) await unlink(linkPath);
123
+ }
124
+
125
+ await symlink(target, linkPath);
126
+ console.log(green(`${label}: linked.`));
127
+ console.log(dim(` ${shortPath(linkPath)} -> ${shortPath(target)}`));
128
+ return true;
129
+ }
130
+
131
+ /**
132
+ * Remove a path (symlink, file, or directory).
133
+ * @param {string} path
134
+ * @param {string} label
135
+ * @returns {Promise<boolean>} true if something was removed
136
+ */
137
+ async function removePath(path, label) {
138
+ // Use lstatSync to detect broken symlinks (existsSync returns false for them)
139
+ let stat;
140
+ try {
141
+ stat = lstatSync(path);
142
+ } catch {
143
+ return false;
144
+ }
145
+
146
+ if (stat.isDirectory()) {
147
+ await rm(path, { recursive: true, force: true });
148
+ console.log(green(`${label}: removed directory ${shortPath(path)}/`));
149
+ } else {
150
+ await unlink(path);
151
+ const kind = stat.isSymbolicLink() ? "symlink" : "file";
152
+ console.log(green(`${label}: removed ${kind} ${shortPath(path)}`));
153
+ }
154
+ return true;
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Commands
159
+ // ---------------------------------------------------------------------------
160
+
161
+ /**
162
+ * Symlink both plugin and CLI for development.
163
+ */
164
+ async function cmdLink() {
165
+ console.log(bold("Linking opencode-anthropic-auth...\n"));
166
+
167
+ // Plugin: symlink entry point
168
+ const pluginDir = getPluginDir();
169
+ const pluginEntry = join(pluginDir, PLUGIN_ENTRY);
170
+ const pluginTarget = join(PROJECT_ROOT, "index.mjs");
171
+
172
+ // Clean up old-named entry if present
173
+ const oldEntry = join(pluginDir, "opencode-anthropic-auth.js");
174
+ if (existsSync(oldEntry) && oldEntry !== pluginEntry) {
175
+ await unlink(oldEntry);
176
+ console.log(dim("Plugin: removed old opencode-anthropic-auth.js"));
177
+ }
178
+
179
+ await ensureSymlink(pluginTarget, pluginEntry, "Plugin");
180
+
181
+ // CLI: symlink to cli.mjs
182
+ const binDir = getBinDir();
183
+ const cliBin = join(binDir, CLI_BIN_NAME);
184
+ const cliTarget = join(PROJECT_ROOT, "cli.mjs");
185
+ await ensureSymlink(cliTarget, cliBin, "CLI");
186
+
187
+ console.log(dim("\nEdits to source files take effect immediately."));
188
+
189
+ // Check if ~/.local/bin is on PATH
190
+ const pathDirs = (process.env.PATH || "").split(":");
191
+ if (!pathDirs.includes(binDir)) {
192
+ console.log(yellow(`\nNote: ${shortPath(binDir)} is not on your PATH.`));
193
+ console.log(dim(`Add to your shell profile: export PATH="${shortPath(binDir)}:$PATH"`));
194
+ }
195
+
196
+ printEnvReminder();
197
+ }
198
+
199
+ /**
200
+ * Copy bundled plugin and CLI for stable deployment.
201
+ * Requires `npm run build` first (install:copy runs it automatically).
202
+ */
203
+ async function cmdCopy() {
204
+ console.log(bold("Copying opencode-anthropic-auth...\n"));
205
+
206
+ const pluginSrc = join(DIST_DIR, "opencode-anthropic-auth-plugin.js");
207
+ const cliSrc = join(DIST_DIR, "opencode-anthropic-auth-cli.mjs");
208
+
209
+ if (!existsSync(pluginSrc) || !existsSync(cliSrc)) {
210
+ console.error(red("dist/ not found. Run `npm run build` first."));
211
+ process.exit(1);
212
+ }
213
+
214
+ // --- Plugin: single file copy ---
215
+ const pluginDir = getPluginDir();
216
+ const pluginDest = join(pluginDir, PLUGIN_ENTRY);
217
+
218
+ await mkdir(pluginDir, { recursive: true });
219
+
220
+ // Clean up old multi-file copy directory if present
221
+ const oldCopyDir = join(pluginDir, PLUGIN_NAME);
222
+ if (existsSync(oldCopyDir)) {
223
+ await rm(oldCopyDir, { recursive: true, force: true });
224
+ console.log(dim("Plugin: removed old copy directory."));
225
+ }
226
+
227
+ if (existsSync(pluginDest)) await unlink(pluginDest);
228
+ await copyFile(pluginSrc, pluginDest);
229
+
230
+ console.log(green("Plugin: copied."));
231
+ console.log(dim(` ${shortPath(pluginDest)}`));
232
+
233
+ // --- CLI: single file copy ---
234
+ const binDir = getBinDir();
235
+ const cliBin = join(binDir, CLI_BIN_NAME);
236
+
237
+ await mkdir(binDir, { recursive: true });
238
+ if (existsSync(cliBin)) await unlink(cliBin);
239
+ await copyFile(cliSrc, cliBin);
240
+ await chmod(cliBin, 0o755);
241
+
242
+ console.log(green("CLI: copied."));
243
+ console.log(dim(` ${shortPath(cliBin)}`));
244
+
245
+ console.log(dim("\nThis is a snapshot. Re-run to update."));
246
+
247
+ // Check if ~/.local/bin is on PATH
248
+ const pathDirs = (process.env.PATH || "").split(":");
249
+ if (!pathDirs.includes(binDir)) {
250
+ console.log(yellow(`\nNote: ${shortPath(binDir)} is not on your PATH.`));
251
+ console.log(dim(`Add to your shell profile: export PATH="${shortPath(binDir)}:$PATH"`));
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Remove both plugin and CLI.
257
+ */
258
+ async function cmdUninstall() {
259
+ console.log(bold("Uninstalling opencode-anthropic-auth...\n"));
260
+
261
+ let removed = false;
262
+
263
+ // Plugin entry point (current and old name)
264
+ const pluginDir = getPluginDir();
265
+ const pluginEntry = join(pluginDir, PLUGIN_ENTRY);
266
+ if (await removePath(pluginEntry, "Plugin")) removed = true;
267
+ const oldEntry = join(pluginDir, "opencode-anthropic-auth.js");
268
+ if (await removePath(oldEntry, "Plugin (old name)")) removed = true;
269
+
270
+ // Plugin copy directory
271
+ const copyDir = join(pluginDir, PLUGIN_NAME);
272
+ if (await removePath(copyDir, "Plugin")) removed = true;
273
+
274
+ // CLI binary
275
+ const binDir = getBinDir();
276
+ const cliBin = join(binDir, CLI_BIN_NAME);
277
+ if (await removePath(cliBin, "CLI")) removed = true;
278
+
279
+ // Also clean up any old npm link global install
280
+ const npmGlobal = join("/opt/homebrew/bin", "anthropic-auth");
281
+ if (await removePath(npmGlobal, "CLI (old npm link)")) removed = true;
282
+
283
+ if (!removed) {
284
+ console.log(dim("Nothing to remove. Not installed."));
285
+ } else {
286
+ console.log(dim("\nUninstalled."));
287
+ }
288
+ }
289
+
290
+ // ---------------------------------------------------------------------------
291
+ // Main
292
+ // ---------------------------------------------------------------------------
293
+
294
+ const command = process.argv[2];
295
+
296
+ switch (command) {
297
+ case "link":
298
+ await cmdLink();
299
+ break;
300
+ case "copy":
301
+ await cmdCopy();
302
+ break;
303
+ case "uninstall":
304
+ await cmdUninstall();
305
+ break;
306
+ default:
307
+ console.log(`${bold("Installer for opencode-anthropic-auth")}
308
+
309
+ ${dim("Installs:")}
310
+ Plugin ${dim("->")} ~/.config/opencode/plugin/${PLUGIN_ENTRY}
311
+ CLI ${dim("->")} ~/.local/bin/${CLI_BIN_NAME}
312
+
313
+ ${dim("Usage:")}
314
+ node scripts/install.mjs ${bold("link")} Symlink both (development)
315
+ node scripts/install.mjs ${bold("copy")} Copy both (stable deployment)
316
+ node scripts/install.mjs ${bold("uninstall")} Remove both
317
+ `);
318
+ process.exit(command ? 1 : 0);
319
+ }