@ztffn/presentation-generator-plugin 1.0.7 → 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 -121
  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,192 +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
- console.log(
177
- "Each teammate still needs to run: npx @ztffn/presentation-generator-plugin install"
178
- );
179
- } else {
180
- console.log(`Plugin registered in ~/.claude/settings.json ✓`);
181
- console.log("Available in all your Claude Code sessions.");
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}`);
182
192
  }
183
193
  } catch {
184
- console.log("\nCould not write settings. Add manually to ~/.claude/settings.json:");
185
- console.log(JSON.stringify({ enabledPlugins: [INSTALL_DIR] }, null, 2));
186
- console.log(`\nOr load for a single session:`);
187
- console.log(` claude --plugin-dir "${INSTALL_DIR}"`);
194
+ // ignore
188
195
  }
189
196
  }
190
197
 
191
198
  // ── Commands ──────────────────────────────────────────────────────────────────
192
199
 
193
200
  async function install() {
194
- const meta = await getLatestNpmMeta();
195
-
196
- if (fs.existsSync(INSTALL_DIR)) {
197
- const installed = getInstalledVersion();
198
-
199
- if (meta && installed && installed !== meta.version) {
200
- console.log(`\nPlugin already installed (v${installed}).`);
201
- console.log(
202
- `v${meta.version} is available — run: npx ${NPM_PACKAGE} update\n`
203
- );
204
- } else {
205
- console.log(
206
- `\nPlugin already installed (v${installed || "unknown"}). Nothing to do.\n`
207
- );
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
+ }
208
221
  }
209
- registerPlugin();
210
222
  return;
211
223
  }
212
224
 
213
- console.log(`\nInstalling presentation-generator plugin...`);
214
-
225
+ const meta = await getLatestNpmMeta();
215
226
  if (!meta) {
216
- console.error(
217
- "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
218
- );
227
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
219
228
  process.exit(1);
220
229
  }
221
230
 
222
- await downloadAndExtract(meta.version, meta.tarball);
223
- console.log(`\nInstalled v${meta.version} to: ${INSTALL_DIR}`);
224
- 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} ✓`);
225
244
  }
226
245
 
227
246
  async function update() {
228
- 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) {
229
251
  console.log("\nPlugin not installed. Run install first:");
230
252
  console.log(` npx ${NPM_PACKAGE} install\n`);
231
253
  process.exit(1);
232
254
  }
233
255
 
234
- const before = getInstalledVersion();
235
256
  const meta = await getLatestNpmMeta();
236
-
237
257
  if (!meta) {
238
- console.error(
239
- "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
240
- );
258
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
241
259
  process.exit(1);
242
260
  }
243
261
 
244
- if (before === meta.version) {
245
- console.log(`\nAlready up to date (v${before}) ✓`);
246
- registerPlugin();
247
- return;
248
- }
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
+ }
249
273
 
250
- console.log(
251
- `\nUpdating presentation-generator plugin (v${before || "unknown"} → v${meta.version})...`
252
- );
253
- await downloadAndExtract(meta.version, meta.tarball);
254
- console.log(`\nUpdated to v${meta.version} ✓`);
255
- 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
+ }
256
279
  }
257
280
 
258
281
  async function checkUpdate() {
259
- 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) {
260
286
  console.log("\nPlugin not installed.\n");
261
287
  return;
262
288
  }
263
289
 
264
- const installed = getInstalledVersion();
265
290
  const meta = await getLatestNpmMeta();
266
291
 
267
- if (!meta) {
268
- console.log(
269
- `\nInstalled: v${installed || "unknown"} (could not reach npm registry)\n`
270
- );
271
- return;
272
- }
273
-
274
- if (installed === meta.version) {
275
- console.log(`\nUp to date: v${installed} ✓\n`);
276
- } else {
277
- console.log(`\nUpdate available: v${installed} v${meta.version}`);
278
- console.log(`Run: npx ${NPM_PACKAGE} update\n`);
279
- }
280
- }
281
-
282
- function removeFromSettings(settingsPath) {
283
- if (!fs.existsSync(settingsPath)) return;
284
- try {
285
- const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
286
- if (!Array.isArray(settings.enabledPlugins)) return;
287
- const before = settings.enabledPlugins.length;
288
- settings.enabledPlugins = settings.enabledPlugins.filter((p) => p !== INSTALL_DIR);
289
- if (settings.enabledPlugins.length !== before) {
290
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
291
- 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`);
292
306
  }
293
- } catch {
294
- // ignore
295
307
  }
308
+ console.log();
296
309
  }
297
310
 
298
311
  function uninstall() {
299
- 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) {
300
316
  console.log("\nPlugin not installed.\n");
301
317
  return;
302
318
  }
303
- fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
304
- console.log(`\nRemoved: ${INSTALL_DIR}`);
305
319
 
306
- // Clean up both possible settings locations
307
- removeFromSettings(path.join(os.homedir(), ".claude", "settings.json"));
308
- 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
+
309
332
  console.log();
310
333
  }
311
334
 
@@ -314,15 +337,16 @@ function help() {
314
337
  presentation-generator-plugin v${CURRENT_VERSION}
315
338
 
316
339
  Commands:
317
- install Download plugin from npm and register with Claude Code
340
+ install Download and install the plugin, register with Claude Code
318
341
  update Download and install the latest version from npm
319
342
  check-update Report whether an update is available
320
- uninstall Remove plugin from ~/.claude/plugins/
343
+ uninstall Remove the plugin
321
344
 
322
345
  Usage:
323
346
  npx ${NPM_PACKAGE} install
324
347
  npx ${NPM_PACKAGE} update
325
348
  npx ${NPM_PACKAGE} check-update
349
+ npx ${NPM_PACKAGE} uninstall
326
350
  `);
327
351
  }
328
352
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ztffn/presentation-generator-plugin",
3
- "version": "1.0.7",
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"