@ztffn/presentation-generator-plugin 1.4.0 → 1.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/bin/index.js +110 -19
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -5,7 +5,6 @@ const { execSync } = require("child_process");
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const os = require("os");
|
|
8
|
-
const readline = require("readline");
|
|
9
8
|
|
|
10
9
|
const NPM_PACKAGE = "@ztffn/presentation-generator-plugin";
|
|
11
10
|
const PLUGIN_NAME = "presentation-generator";
|
|
@@ -104,23 +103,78 @@ async function downloadAndExtract(version, tarballUrl, installDir) {
|
|
|
104
103
|
|
|
105
104
|
function promptChoice(title, choices) {
|
|
106
105
|
return new Promise((resolve) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
let selected = 0;
|
|
107
|
+
|
|
108
|
+
// Lines rendered per call to draw() — used to reposition cursor on redraw
|
|
109
|
+
const lineCount = () => {
|
|
110
|
+
let n = 2; // blank line + title line
|
|
111
|
+
choices.forEach((c) => { n += c.hint ? 2 : 1; });
|
|
112
|
+
n += 1; // trailing │
|
|
113
|
+
n += 1; // └ line (no trailing newline — cursor stays here)
|
|
114
|
+
return n;
|
|
115
|
+
};
|
|
114
116
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
const C = {
|
|
118
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
119
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
120
|
+
up: (n) => `\x1B[${n}A`,
|
|
121
|
+
clear: () => "\x1B[0J",
|
|
122
|
+
cursorHide: "\x1B[?25l",
|
|
123
|
+
cursorShow: "\x1B[?25h",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const draw = (first = false) => {
|
|
127
|
+
if (!first) {
|
|
128
|
+
// \r resets to col 0 before moving up, so C.clear() wipes from the true start of the line
|
|
129
|
+
process.stdout.write("\r" + C.up(lineCount() - 1) + C.clear());
|
|
130
|
+
}
|
|
131
|
+
process.stdout.write(`\n◆ ${title}\n`);
|
|
132
|
+
choices.forEach((c, i) => {
|
|
133
|
+
const active = i === selected;
|
|
134
|
+
const bullet = active ? C.cyan("●") : "○";
|
|
135
|
+
const label = active ? C.cyan(c.label) : c.label;
|
|
136
|
+
process.stdout.write(`│ ${bullet} ${label}\n`);
|
|
137
|
+
if (c.hint) process.stdout.write(`│ ${C.dim(c.hint)}\n`);
|
|
138
|
+
});
|
|
139
|
+
process.stdout.write("│\n");
|
|
140
|
+
process.stdout.write(C.dim("└ ↑↓ to move · Enter to confirm"));
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
process.stdout.write(C.cursorHide);
|
|
144
|
+
draw(true);
|
|
145
|
+
|
|
146
|
+
process.stdin.setRawMode(true);
|
|
147
|
+
process.stdin.resume();
|
|
148
|
+
process.stdin.setEncoding("utf8");
|
|
149
|
+
|
|
150
|
+
const onKey = (key) => {
|
|
151
|
+
if (key === "\x1B[A") { // ↑
|
|
152
|
+
selected = (selected - 1 + choices.length) % choices.length;
|
|
153
|
+
draw();
|
|
154
|
+
} else if (key === "\x1B[B") { // ↓
|
|
155
|
+
selected = (selected + 1) % choices.length;
|
|
156
|
+
draw();
|
|
157
|
+
} else if (key === "\r" || key === " ") { // Enter or Space
|
|
158
|
+
process.stdin.setRawMode(false);
|
|
159
|
+
process.stdin.pause();
|
|
160
|
+
process.stdin.removeListener("data", onKey);
|
|
161
|
+
process.stdout.write(C.cursorShow);
|
|
162
|
+
|
|
163
|
+
// Redraw with confirmed selection, then leave a clean line
|
|
164
|
+
process.stdout.write(C.up(lineCount() - 1) + C.clear());
|
|
165
|
+
process.stdout.write(`\n◆ ${title}\n`);
|
|
166
|
+
process.stdout.write(`│ ${C.cyan("●")} ${C.cyan(choices[selected].label)}\n`);
|
|
167
|
+
process.stdout.write("└\n\n");
|
|
168
|
+
|
|
169
|
+
resolve(selected);
|
|
170
|
+
} else if (key === "\x03") { // Ctrl-C
|
|
171
|
+
process.stdin.setRawMode(false);
|
|
172
|
+
process.stdout.write(C.cursorShow + "\n");
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
process.stdin.on("data", onKey);
|
|
124
178
|
});
|
|
125
179
|
}
|
|
126
180
|
|
|
@@ -190,16 +244,52 @@ function cleanOldCacheVersions(keepVersion) {
|
|
|
190
244
|
}
|
|
191
245
|
}
|
|
192
246
|
|
|
247
|
+
function purgeInstalledPluginsJson() {
|
|
248
|
+
// Directly remove all entries for this plugin from Claude's global registry
|
|
249
|
+
// so that a subsequent `claude plugin install` always writes a fresh entry.
|
|
250
|
+
const registryPath = path.join(
|
|
251
|
+
os.homedir(), ".claude", "plugins", "installed_plugins.json"
|
|
252
|
+
);
|
|
253
|
+
if (!fs.existsSync(registryPath)) return;
|
|
254
|
+
try {
|
|
255
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
|
|
256
|
+
const key = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
257
|
+
if (registry.plugins && key in registry.plugins) {
|
|
258
|
+
delete registry.plugins[key];
|
|
259
|
+
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n");
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
// registry unreadable/corrupt — leave it alone
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
193
266
|
function registerWithClaude(installDir, scope) {
|
|
194
267
|
const pluginsDir = path.dirname(installDir);
|
|
195
268
|
ensureMarketplaceJson(pluginsDir);
|
|
196
269
|
|
|
270
|
+
// Remove any stale registration (e.g. from a previous install at a different path)
|
|
271
|
+
// before adding the current one. Errors are ignored — it may not exist yet.
|
|
197
272
|
try {
|
|
198
|
-
execSync(`claude plugin marketplace
|
|
273
|
+
execSync(`claude plugin marketplace remove "${MARKETPLACE_NAME}"`, { stdio: "pipe" });
|
|
199
274
|
} catch {
|
|
200
|
-
//
|
|
275
|
+
// didn't exist or not removable — ignore
|
|
201
276
|
}
|
|
202
277
|
|
|
278
|
+
// Uninstall any stale plugin entries from both scopes before re-installing.
|
|
279
|
+
// This prevents Claude from reusing a cached entry pointing at an old version path.
|
|
280
|
+
try {
|
|
281
|
+
execSync(`claude plugin uninstall ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project`, { stdio: "pipe" });
|
|
282
|
+
} catch {}
|
|
283
|
+
try {
|
|
284
|
+
execSync(`claude plugin uninstall ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope user`, { stdio: "pipe" });
|
|
285
|
+
} catch {}
|
|
286
|
+
|
|
287
|
+
// Belt-and-suspenders: also purge the registry JSON directly in case
|
|
288
|
+
// `claude plugin uninstall` doesn't clean installed_plugins.json reliably.
|
|
289
|
+
purgeInstalledPluginsJson();
|
|
290
|
+
|
|
291
|
+
execSync(`claude plugin marketplace add "${pluginsDir}"`, { stdio: "pipe" });
|
|
292
|
+
|
|
203
293
|
const scopeFlag = scope === "project" ? "--scope project" : "--scope user";
|
|
204
294
|
execSync(`claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} ${scopeFlag}`, { stdio: "pipe" });
|
|
205
295
|
}
|
|
@@ -263,6 +353,7 @@ async function install() {
|
|
|
263
353
|
|
|
264
354
|
console.log("\nRegistering plugin with Claude Code...");
|
|
265
355
|
registerWithClaude(installDir, scope);
|
|
356
|
+
cleanOldCacheVersions(meta.version);
|
|
266
357
|
|
|
267
358
|
const settingsLabel = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
|
|
268
359
|
console.log(`Plugin registered in ${settingsLabel} ✓`);
|