@ztffn/presentation-generator-plugin 1.0.8 → 1.1.0

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 +148 -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,215 @@ 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) {
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 (!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
+ if (!settings.enabledPlugins.includes(PLUGIN_NAME)) {
178
+ settings.enabledPlugins.push(PLUGIN_NAME);
179
+ }
170
180
 
171
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
172
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
181
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
182
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
183
+ }
173
184
 
174
- if (scope === "project") {
175
- console.log(`Plugin registered in .claude/settings.json ✓`);
176
- } else {
177
- console.log(`Plugin registered in ~/.claude/settings.json ✓`);
185
+ function removeFromSettings(settingsPath) {
186
+ if (!fs.existsSync(settingsPath)) return;
187
+ try {
188
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
189
+ if (!Array.isArray(settings.enabledPlugins)) return;
190
+ const filtered = settings.enabledPlugins.filter((p) => p !== PLUGIN_NAME);
191
+ if (filtered.length !== settings.enabledPlugins.length) {
192
+ settings.enabledPlugins = filtered;
193
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
194
+ console.log(`Removed entry from ${settingsPath}`);
178
195
  }
179
196
  } 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}"`);
197
+ // ignore
184
198
  }
185
199
  }
186
200
 
187
201
  // ── Commands ──────────────────────────────────────────────────────────────────
188
202
 
189
203
  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
- );
204
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
205
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
206
+
207
+ if (globalExists || projectExists) {
208
+ const meta = await getLatestNpmMeta();
209
+ if (globalExists) {
210
+ const v = getInstalledVersion(GLOBAL_INSTALL_DIR);
211
+ if (meta && v !== meta.version) {
212
+ console.log(`\nGlobal install found (v${v}). v${meta.version} available — run: npx ${NPM_PACKAGE} update\n`);
213
+ } else {
214
+ console.log(`\nGlobal install already up to date (v${v || "unknown"}).\n`);
215
+ }
216
+ }
217
+ if (projectExists) {
218
+ const v = getInstalledVersion(PROJECT_INSTALL_DIR);
219
+ if (meta && v !== meta.version) {
220
+ console.log(`\nProject install found (v${v}). v${meta.version} available — run: npx ${NPM_PACKAGE} update\n`);
221
+ } else {
222
+ console.log(`\nProject install already up to date (v${v || "unknown"}).\n`);
223
+ }
204
224
  }
205
- registerPlugin();
206
225
  return;
207
226
  }
208
227
 
209
- console.log(`\nInstalling presentation-generator plugin...`);
210
-
228
+ const meta = await getLatestNpmMeta();
211
229
  if (!meta) {
212
- console.error(
213
- "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
214
- );
230
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
215
231
  process.exit(1);
216
232
  }
217
233
 
218
- await downloadAndExtract(meta.version, meta.tarball);
219
- console.log(`\nInstalled v${meta.version} to: ${INSTALL_DIR}`);
220
- registerPlugin();
234
+ const { installDir, settingsPath, scope } = await resolveInstallTarget();
235
+
236
+ console.log(`\nInstalling presentation-generator plugin...`);
237
+ await downloadAndExtract(meta.version, meta.tarball, installDir);
238
+
239
+ const label = scope === "project" ? `.claude/plugins/${PLUGIN_NAME}` : installDir;
240
+ console.log(`\nInstalled v${meta.version} to: ${label}`);
241
+
242
+ console.log("\nRegistering plugin with Claude Code...");
243
+ writeSettings(settingsPath);
244
+
245
+ const settingsLabel = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
246
+ console.log(`Plugin registered in ${settingsLabel} ✓`);
221
247
  }
222
248
 
223
249
  async function update() {
224
- if (!fs.existsSync(INSTALL_DIR)) {
250
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
251
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
252
+
253
+ if (!globalExists && !projectExists) {
225
254
  console.log("\nPlugin not installed. Run install first:");
226
255
  console.log(` npx ${NPM_PACKAGE} install\n`);
227
256
  process.exit(1);
228
257
  }
229
258
 
230
- const before = getInstalledVersion();
231
259
  const meta = await getLatestNpmMeta();
232
-
233
260
  if (!meta) {
234
- console.error(
235
- "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
236
- );
261
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
237
262
  process.exit(1);
238
263
  }
239
264
 
240
- if (before === meta.version) {
241
- console.log(`\nAlready up to date (v${before}) ✓`);
242
- registerPlugin();
243
- return;
244
- }
265
+ for (const [installDir, settingsPath, label] of [
266
+ [GLOBAL_INSTALL_DIR, path.join(os.homedir(), ".claude", "settings.json"), "Global"],
267
+ [PROJECT_INSTALL_DIR, path.join(process.cwd(), ".claude", "settings.json"), "Project"],
268
+ ]) {
269
+ if (!fs.existsSync(installDir)) continue;
270
+
271
+ const before = getInstalledVersion(installDir);
272
+ if (before === meta.version) {
273
+ console.log(`\n${label}: already up to date (v${before}) ✓`);
274
+ continue;
275
+ }
245
276
 
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();
277
+ console.log(`\n${label}: updating v${before || "unknown"} → v${meta.version}...`);
278
+ await downloadAndExtract(meta.version, meta.tarball, installDir);
279
+ writeSettings(settingsPath);
280
+ console.log(`${label}: updated to v${meta.version} ✓`);
281
+ }
252
282
  }
253
283
 
254
284
  async function checkUpdate() {
255
- if (!fs.existsSync(INSTALL_DIR)) {
285
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
286
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
287
+
288
+ if (!globalExists && !projectExists) {
256
289
  console.log("\nPlugin not installed.\n");
257
290
  return;
258
291
  }
259
292
 
260
- const installed = getInstalledVersion();
261
293
  const meta = await getLatestNpmMeta();
262
294
 
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}`);
295
+ for (const [installDir, label] of [
296
+ [GLOBAL_INSTALL_DIR, "Global"],
297
+ [PROJECT_INSTALL_DIR, "Project"],
298
+ ]) {
299
+ if (!fs.existsSync(installDir)) continue;
300
+ const installed = getInstalledVersion(installDir);
301
+
302
+ if (!meta) {
303
+ console.log(`\n${label}: v${installed || "unknown"} (could not reach npm registry)`);
304
+ } else if (installed === meta.version) {
305
+ console.log(`\n${label}: up to date (v${installed}) ✓`);
306
+ } else {
307
+ console.log(`\n${label}: update available v${installed} → v${meta.version}`);
308
+ console.log(` Run: npx ${NPM_PACKAGE} update`);
288
309
  }
289
- } catch {
290
- // ignore
291
310
  }
311
+ console.log();
292
312
  }
293
313
 
294
314
  function uninstall() {
295
- if (!fs.existsSync(INSTALL_DIR)) {
315
+ const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
316
+ const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
317
+
318
+ if (!globalExists && !projectExists) {
296
319
  console.log("\nPlugin not installed.\n");
297
320
  return;
298
321
  }
299
- fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
300
- console.log(`\nRemoved: ${INSTALL_DIR}`);
301
322
 
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"));
323
+ if (globalExists) {
324
+ fs.rmSync(GLOBAL_INSTALL_DIR, { recursive: true, force: true });
325
+ console.log(`\nRemoved: ${GLOBAL_INSTALL_DIR}`);
326
+ removeFromSettings(path.join(os.homedir(), ".claude", "settings.json"));
327
+ }
328
+
329
+ if (projectExists) {
330
+ fs.rmSync(PROJECT_INSTALL_DIR, { recursive: true, force: true });
331
+ console.log(`\nRemoved: .claude/plugins/${PLUGIN_NAME}`);
332
+ removeFromSettings(path.join(process.cwd(), ".claude", "settings.json"));
333
+ }
334
+
305
335
  console.log();
306
336
  }
307
337
 
@@ -310,15 +340,16 @@ function help() {
310
340
  presentation-generator-plugin v${CURRENT_VERSION}
311
341
 
312
342
  Commands:
313
- install Download plugin from npm and register with Claude Code
343
+ install Download and install the plugin, register with Claude Code
314
344
  update Download and install the latest version from npm
315
345
  check-update Report whether an update is available
316
- uninstall Remove plugin from ~/.claude/plugins/
346
+ uninstall Remove the plugin
317
347
 
318
348
  Usage:
319
349
  npx ${NPM_PACKAGE} install
320
350
  npx ${NPM_PACKAGE} update
321
351
  npx ${NPM_PACKAGE} check-update
352
+ npx ${NPM_PACKAGE} uninstall
322
353
  `);
323
354
  }
324
355
 
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.1.0",
4
4
  "description": "Claude Code plugin for generating graph-based presentations",
5
5
  "bin": {
6
6
  "presentation-generator-plugin": "bin/index.js"