@ztffn/presentation-generator-plugin 1.0.8 → 1.0.9

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.
Files changed (2) hide show
  1. package/bin/index.js +145 -117
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -9,15 +9,16 @@ const readline = require("readline");
9
9
 
10
10
  const NPM_PACKAGE = "@ztffn/presentation-generator-plugin";
11
11
  const PLUGIN_NAME = "presentation-generator";
12
- const INSTALL_DIR = path.join(os.homedir(), ".claude", "plugins", PLUGIN_NAME);
12
+ const GLOBAL_INSTALL_DIR = path.join(os.homedir(), ".claude", "plugins", PLUGIN_NAME);
13
+ const PROJECT_INSTALL_DIR = path.join(process.cwd(), ".claude", "plugins", PLUGIN_NAME);
13
14
  const CURRENT_VERSION = require("../package.json").version;
14
15
 
15
16
  const command = process.argv[2] || "help";
16
17
 
17
18
  // ── Helpers ───────────────────────────────────────────────────────────────────
18
19
 
19
- function getInstalledVersion() {
20
- const pkgPath = path.join(INSTALL_DIR, "package.json");
20
+ function getInstalledVersion(installDir) {
21
+ const pkgPath = path.join(installDir, "package.json");
21
22
  if (!fs.existsSync(pkgPath)) return null;
22
23
  try {
23
24
  return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
@@ -76,7 +77,7 @@ async function getLatestNpmMeta() {
76
77
  }
77
78
  }
78
79
 
79
- async function downloadAndExtract(version, tarballUrl) {
80
+ async function downloadAndExtract(version, tarballUrl, installDir) {
80
81
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-plugin-"));
81
82
  const tarball = path.join(tmpDir, "plugin.tgz");
82
83
 
@@ -86,18 +87,17 @@ async function downloadAndExtract(version, tarballUrl) {
86
87
  console.log("Extracting...");
87
88
  execSync(`tar -xzf "${tarball}" -C "${tmpDir}"`);
88
89
 
89
- // npm tarballs always extract to a "package/" subdirectory
90
90
  const extracted = path.join(tmpDir, "package");
91
91
  if (!fs.existsSync(extracted)) {
92
92
  fs.rmSync(tmpDir, { recursive: true, force: true });
93
93
  throw new Error("Extraction failed: no package/ directory in tarball");
94
94
  }
95
95
 
96
- if (fs.existsSync(INSTALL_DIR)) {
97
- fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
96
+ if (fs.existsSync(installDir)) {
97
+ fs.rmSync(installDir, { recursive: true, force: true });
98
98
  }
99
- fs.mkdirSync(path.dirname(INSTALL_DIR), { recursive: true });
100
- fs.renameSync(extracted, INSTALL_DIR);
99
+ fs.mkdirSync(path.dirname(installDir), { recursive: true });
100
+ fs.renameSync(extracted, installDir);
101
101
  fs.rmSync(tmpDir, { recursive: true, force: true });
102
102
  }
103
103
 
@@ -111,7 +111,10 @@ function promptChoice(title, choices) {
111
111
  });
112
112
  console.log("│");
113
113
 
114
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
114
+ const rl = readline.createInterface({
115
+ input: process.stdin,
116
+ output: process.stdout,
117
+ });
115
118
  rl.question(`└ Enter choice [1-${choices.length}]: `, (answer) => {
116
119
  rl.close();
117
120
  const idx = parseInt(answer.trim(), 10) - 1;
@@ -120,188 +123,212 @@ function promptChoice(title, choices) {
120
123
  });
121
124
  }
122
125
 
123
- async function resolveSettingsPath() {
126
+ async function resolveInstallTarget() {
124
127
  const projectClaudeDir = path.join(process.cwd(), ".claude");
125
- const userSettingsPath = path.join(os.homedir(), ".claude", "settings.json");
126
128
 
127
129
  if (!fs.existsSync(projectClaudeDir)) {
128
- return { settingsPath: userSettingsPath, scope: "user" };
130
+ return {
131
+ installDir: GLOBAL_INSTALL_DIR,
132
+ settingsPath: path.join(os.homedir(), ".claude", "settings.json"),
133
+ scope: "global",
134
+ };
129
135
  }
130
136
 
131
137
  const idx = await promptChoice("Installation scope", [
132
138
  {
133
139
  label: "Global",
134
- hint: "Registered in ~/.claude/settings.json — available in all sessions",
140
+ hint: "~/.claude/plugins/ — available in all your Claude sessions",
135
141
  },
136
142
  {
137
143
  label: "Project",
138
- hint: "Registered in .claude/settings.json — commit to share with your team",
144
+ hint: ".claude/plugins/lives in this directory, commit to share with your team",
139
145
  },
140
146
  ]);
141
147
 
142
148
  if (idx === 1) {
143
149
  return {
150
+ installDir: PROJECT_INSTALL_DIR,
144
151
  settingsPath: path.join(projectClaudeDir, "settings.json"),
145
152
  scope: "project",
146
153
  };
147
154
  }
148
155
 
149
- return { settingsPath: userSettingsPath, scope: "user" };
156
+ return {
157
+ installDir: GLOBAL_INSTALL_DIR,
158
+ settingsPath: path.join(os.homedir(), ".claude", "settings.json"),
159
+ scope: "global",
160
+ };
150
161
  }
151
162
 
152
- async function registerPlugin() {
153
- console.log("\nRegistering plugin with Claude Code...");
154
-
155
- const { settingsPath, scope } = await resolveSettingsPath();
156
-
157
- try {
158
- let settings = {};
159
- if (fs.existsSync(settingsPath)) {
163
+ function writeSettings(settingsPath, installDir) {
164
+ let settings = {};
165
+ if (fs.existsSync(settingsPath)) {
166
+ try {
160
167
  settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
168
+ } catch {
169
+ settings = {};
161
170
  }
171
+ }
162
172
 
163
- if (!Array.isArray(settings.enabledPlugins)) {
164
- settings.enabledPlugins = [];
165
- }
173
+ if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object" || Array.isArray(settings.enabledPlugins)) {
174
+ settings.enabledPlugins = {};
175
+ }
166
176
 
167
- if (!settings.enabledPlugins.includes(INSTALL_DIR)) {
168
- settings.enabledPlugins.push(INSTALL_DIR);
169
- }
177
+ settings.enabledPlugins[PLUGIN_NAME] = installDir;
170
178
 
171
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
172
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
179
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
180
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
181
+ }
173
182
 
174
- if (scope === "project") {
175
- console.log(`Plugin registered in .claude/settings.json ✓`);
176
- } else {
177
- console.log(`Plugin registered in ~/.claude/settings.json ✓`);
183
+ function removeFromSettings(settingsPath) {
184
+ if (!fs.existsSync(settingsPath)) return;
185
+ try {
186
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
187
+ if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") return;
188
+ if (PLUGIN_NAME in settings.enabledPlugins) {
189
+ delete settings.enabledPlugins[PLUGIN_NAME];
190
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
191
+ console.log(`Removed entry from ${settingsPath}`);
178
192
  }
179
193
  } catch {
180
- console.log("\nCould not write settings. Add manually to ~/.claude/settings.json:");
181
- console.log(JSON.stringify({ enabledPlugins: [INSTALL_DIR] }, null, 2));
182
- console.log(`\nOr load for a single session:`);
183
- console.log(` claude --plugin-dir "${INSTALL_DIR}"`);
194
+ // ignore
184
195
  }
185
196
  }
186
197
 
187
198
  // ── Commands ──────────────────────────────────────────────────────────────────
188
199
 
189
200
  async function install() {
190
- const meta = await getLatestNpmMeta();
191
-
192
- if (fs.existsSync(INSTALL_DIR)) {
193
- const installed = getInstalledVersion();
194
-
195
- if (meta && installed && installed !== meta.version) {
196
- console.log(`\nPlugin already installed (v${installed}).`);
197
- console.log(
198
- `v${meta.version} is available — run: npx ${NPM_PACKAGE} update\n`
199
- );
200
- } else {
201
- console.log(
202
- `\nPlugin already installed (v${installed || "unknown"}). Nothing to do.\n`
203
- );
201
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
202
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
203
+
204
+ if (globalExists || projectExists) {
205
+ const meta = await getLatestNpmMeta();
206
+ if (globalExists) {
207
+ const v = getInstalledVersion(GLOBAL_INSTALL_DIR);
208
+ if (meta && v !== meta.version) {
209
+ console.log(`\nGlobal install found (v${v}). v${meta.version} available — run: npx ${NPM_PACKAGE} update\n`);
210
+ } else {
211
+ console.log(`\nGlobal install already up to date (v${v || "unknown"}).\n`);
212
+ }
213
+ }
214
+ if (projectExists) {
215
+ const v = getInstalledVersion(PROJECT_INSTALL_DIR);
216
+ if (meta && v !== meta.version) {
217
+ console.log(`\nProject install found (v${v}). v${meta.version} available — run: npx ${NPM_PACKAGE} update\n`);
218
+ } else {
219
+ console.log(`\nProject install already up to date (v${v || "unknown"}).\n`);
220
+ }
204
221
  }
205
- registerPlugin();
206
222
  return;
207
223
  }
208
224
 
209
- console.log(`\nInstalling presentation-generator plugin...`);
210
-
225
+ const meta = await getLatestNpmMeta();
211
226
  if (!meta) {
212
- console.error(
213
- "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
214
- );
227
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
215
228
  process.exit(1);
216
229
  }
217
230
 
218
- await downloadAndExtract(meta.version, meta.tarball);
219
- console.log(`\nInstalled v${meta.version} to: ${INSTALL_DIR}`);
220
- registerPlugin();
231
+ const { installDir, settingsPath, scope } = await resolveInstallTarget();
232
+
233
+ console.log(`\nInstalling presentation-generator plugin...`);
234
+ await downloadAndExtract(meta.version, meta.tarball, installDir);
235
+
236
+ const label = scope === "project" ? `.claude/plugins/${PLUGIN_NAME}` : installDir;
237
+ console.log(`\nInstalled v${meta.version} to: ${label}`);
238
+
239
+ console.log("\nRegistering plugin with Claude Code...");
240
+ writeSettings(settingsPath, installDir);
241
+
242
+ const settingsLabel = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
243
+ console.log(`Plugin registered in ${settingsLabel} ✓`);
221
244
  }
222
245
 
223
246
  async function update() {
224
- if (!fs.existsSync(INSTALL_DIR)) {
247
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
248
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
249
+
250
+ if (!globalExists && !projectExists) {
225
251
  console.log("\nPlugin not installed. Run install first:");
226
252
  console.log(` npx ${NPM_PACKAGE} install\n`);
227
253
  process.exit(1);
228
254
  }
229
255
 
230
- const before = getInstalledVersion();
231
256
  const meta = await getLatestNpmMeta();
232
-
233
257
  if (!meta) {
234
- console.error(
235
- "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
236
- );
258
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
237
259
  process.exit(1);
238
260
  }
239
261
 
240
- if (before === meta.version) {
241
- console.log(`\nAlready up to date (v${before}) ✓`);
242
- registerPlugin();
243
- return;
244
- }
262
+ for (const [installDir, settingsPath, label] of [
263
+ [GLOBAL_INSTALL_DIR, path.join(os.homedir(), ".claude", "settings.json"), "Global"],
264
+ [PROJECT_INSTALL_DIR, path.join(process.cwd(), ".claude", "settings.json"), "Project"],
265
+ ]) {
266
+ if (!fs.existsSync(installDir)) continue;
267
+
268
+ const before = getInstalledVersion(installDir);
269
+ if (before === meta.version) {
270
+ console.log(`\n${label}: already up to date (v${before}) ✓`);
271
+ continue;
272
+ }
245
273
 
246
- console.log(
247
- `\nUpdating presentation-generator plugin (v${before || "unknown"} → v${meta.version})...`
248
- );
249
- await downloadAndExtract(meta.version, meta.tarball);
250
- console.log(`\nUpdated to v${meta.version} ✓`);
251
- registerPlugin();
274
+ console.log(`\n${label}: updating v${before || "unknown"} → v${meta.version}...`);
275
+ await downloadAndExtract(meta.version, meta.tarball, installDir);
276
+ writeSettings(settingsPath, installDir);
277
+ console.log(`${label}: updated to v${meta.version} ✓`);
278
+ }
252
279
  }
253
280
 
254
281
  async function checkUpdate() {
255
- if (!fs.existsSync(INSTALL_DIR)) {
282
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
283
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
284
+
285
+ if (!globalExists && !projectExists) {
256
286
  console.log("\nPlugin not installed.\n");
257
287
  return;
258
288
  }
259
289
 
260
- const installed = getInstalledVersion();
261
290
  const meta = await getLatestNpmMeta();
262
291
 
263
- if (!meta) {
264
- console.log(
265
- `\nInstalled: v${installed || "unknown"} (could not reach npm registry)\n`
266
- );
267
- return;
268
- }
269
-
270
- if (installed === meta.version) {
271
- console.log(`\nUp to date: v${installed} ✓\n`);
272
- } else {
273
- console.log(`\nUpdate available: v${installed} v${meta.version}`);
274
- console.log(`Run: npx ${NPM_PACKAGE} update\n`);
275
- }
276
- }
277
-
278
- function removeFromSettings(settingsPath) {
279
- if (!fs.existsSync(settingsPath)) return;
280
- try {
281
- const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
282
- if (!Array.isArray(settings.enabledPlugins)) return;
283
- const before = settings.enabledPlugins.length;
284
- settings.enabledPlugins = settings.enabledPlugins.filter((p) => p !== INSTALL_DIR);
285
- if (settings.enabledPlugins.length !== before) {
286
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
287
- console.log(`Removed from ${settingsPath}`);
292
+ for (const [installDir, label] of [
293
+ [GLOBAL_INSTALL_DIR, "Global"],
294
+ [PROJECT_INSTALL_DIR, "Project"],
295
+ ]) {
296
+ if (!fs.existsSync(installDir)) continue;
297
+ const installed = getInstalledVersion(installDir);
298
+
299
+ if (!meta) {
300
+ console.log(`\n${label}: v${installed || "unknown"} (could not reach npm registry)`);
301
+ } else if (installed === meta.version) {
302
+ console.log(`\n${label}: up to date (v${installed}) ✓`);
303
+ } else {
304
+ console.log(`\n${label}: update available v${installed} → v${meta.version}`);
305
+ console.log(` Run: npx ${NPM_PACKAGE} update`);
288
306
  }
289
- } catch {
290
- // ignore
291
307
  }
308
+ console.log();
292
309
  }
293
310
 
294
311
  function uninstall() {
295
- if (!fs.existsSync(INSTALL_DIR)) {
312
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
313
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
314
+
315
+ if (!globalExists && !projectExists) {
296
316
  console.log("\nPlugin not installed.\n");
297
317
  return;
298
318
  }
299
- fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
300
- console.log(`\nRemoved: ${INSTALL_DIR}`);
301
319
 
302
- // Clean up both possible settings locations
303
- removeFromSettings(path.join(os.homedir(), ".claude", "settings.json"));
304
- removeFromSettings(path.join(process.cwd(), ".claude", "settings.json"));
320
+ if (globalExists) {
321
+ fs.rmSync(GLOBAL_INSTALL_DIR, { recursive: true, force: true });
322
+ console.log(`\nRemoved: ${GLOBAL_INSTALL_DIR}`);
323
+ removeFromSettings(path.join(os.homedir(), ".claude", "settings.json"));
324
+ }
325
+
326
+ if (projectExists) {
327
+ fs.rmSync(PROJECT_INSTALL_DIR, { recursive: true, force: true });
328
+ console.log(`\nRemoved: .claude/plugins/${PLUGIN_NAME}`);
329
+ removeFromSettings(path.join(process.cwd(), ".claude", "settings.json"));
330
+ }
331
+
305
332
  console.log();
306
333
  }
307
334
 
@@ -310,15 +337,16 @@ function help() {
310
337
  presentation-generator-plugin v${CURRENT_VERSION}
311
338
 
312
339
  Commands:
313
- install Download plugin from npm and register with Claude Code
340
+ install Download and install the plugin, register with Claude Code
314
341
  update Download and install the latest version from npm
315
342
  check-update Report whether an update is available
316
- uninstall Remove plugin from ~/.claude/plugins/
343
+ uninstall Remove the plugin
317
344
 
318
345
  Usage:
319
346
  npx ${NPM_PACKAGE} install
320
347
  npx ${NPM_PACKAGE} update
321
348
  npx ${NPM_PACKAGE} check-update
349
+ npx ${NPM_PACKAGE} uninstall
322
350
  `);
323
351
  }
324
352
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ztffn/presentation-generator-plugin",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Claude Code plugin for generating graph-based presentations",
5
5
  "bin": {
6
6
  "presentation-generator-plugin": "bin/index.js"