@ztffn/presentation-generator-plugin 1.0.0 → 1.0.4

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.
@@ -20,7 +20,9 @@ jobs:
20
20
  node-version: "20"
21
21
  registry-url: "https://registry.npmjs.org"
22
22
 
23
- - run: npm publish --provenance --access public
23
+ - run: npm publish --access public
24
+ env:
25
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24
26
 
25
27
  release-zip:
26
28
  name: Create GitHub Release with zip
package/README.md CHANGED
@@ -20,16 +20,14 @@ Requires Claude Code 1.0.33 or later.
20
20
  claude --plugin-dir /path/to/presentation-generator-plugin
21
21
  ```
22
22
 
23
- To always have it available in a project, add it to `.claude/settings.json`:
23
+ To always have it available in a project, register it with Claude Code:
24
24
 
25
- ```json
26
- {
27
- "plugins": [
28
- { "path": "/path/to/presentation-generator-plugin" }
29
- ]
30
- }
25
+ ```bash
26
+ claude plugin install /path/to/presentation-generator-plugin --scope project
31
27
  ```
32
28
 
29
+ This writes the correct entry to `.claude/settings.json`. Commit that file to share the plugin with your team.
30
+
33
31
  To update: download the new zip from Releases, replace the old folder.
34
32
 
35
33
  ### Option B — install via npx (requires Node.js 18+ and GitHub auth)
package/bin/index.js CHANGED
@@ -1,182 +1,230 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync, spawnSync } = require("child_process");
3
+ const https = require("https");
4
+ const { execSync } = require("child_process");
4
5
  const fs = require("fs");
5
6
  const path = require("path");
6
7
  const os = require("os");
7
8
 
8
- const REPO_URL =
9
- "https://github.com/ztffn/presentation-generator-plugin.git";
9
+ const NPM_PACKAGE = "@ztffn/presentation-generator-plugin";
10
10
  const PLUGIN_NAME = "presentation-generator";
11
11
  const INSTALL_DIR = path.join(os.homedir(), ".claude", "plugins", PLUGIN_NAME);
12
12
  const CURRENT_VERSION = require("../package.json").version;
13
13
 
14
14
  const command = process.argv[2] || "help";
15
15
 
16
- function run(cmd, opts = {}) {
17
- return execSync(cmd, { stdio: "inherit", ...opts });
18
- }
16
+ // ── Helpers ───────────────────────────────────────────────────────────────────
19
17
 
20
- function runCapture(cmd) {
18
+ function getInstalledVersion() {
19
+ const pkgPath = path.join(INSTALL_DIR, "package.json");
20
+ if (!fs.existsSync(pkgPath)) return null;
21
21
  try {
22
- return execSync(cmd, { encoding: "utf8", stderr: "pipe" }).trim();
22
+ return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
23
23
  } catch {
24
24
  return null;
25
25
  }
26
26
  }
27
27
 
28
- function checkGitAuth() {
29
- const result = spawnSync("gh", ["auth", "status"], { encoding: "utf8" });
30
- if (result.status !== 0) {
31
- console.error("\nGitHub authentication required.");
32
- console.error("Run: gh auth login\n");
33
- process.exit(1);
34
- }
28
+ function fetchJson(url) {
29
+ return new Promise((resolve, reject) => {
30
+ https
31
+ .get(url, { headers: { Accept: "application/json" } }, (res) => {
32
+ let data = "";
33
+ res.on("data", (chunk) => (data += chunk));
34
+ res.on("end", () => {
35
+ try {
36
+ resolve(JSON.parse(data));
37
+ } catch (e) {
38
+ reject(e);
39
+ }
40
+ });
41
+ })
42
+ .on("error", reject);
43
+ });
35
44
  }
36
45
 
37
- function getInstalledVersion() {
38
- const pkgPath = path.join(INSTALL_DIR, "package.json");
39
- if (!fs.existsSync(pkgPath)) return null;
46
+ function downloadFile(url, dest) {
47
+ return new Promise((resolve, reject) => {
48
+ const follow = (u) => {
49
+ https
50
+ .get(u, (res) => {
51
+ if (res.statusCode === 301 || res.statusCode === 302) {
52
+ follow(res.headers.location);
53
+ return;
54
+ }
55
+ const file = fs.createWriteStream(dest);
56
+ res.pipe(file);
57
+ file.on("finish", () => file.close(resolve));
58
+ file.on("error", reject);
59
+ })
60
+ .on("error", reject);
61
+ };
62
+ follow(url);
63
+ });
64
+ }
65
+
66
+ async function getLatestNpmVersion() {
40
67
  try {
41
- return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
68
+ const data = await fetchJson(
69
+ `https://registry.npmjs.org/${NPM_PACKAGE}/latest`
70
+ );
71
+ return data.version || null;
42
72
  } catch {
43
73
  return null;
44
74
  }
45
75
  }
46
76
 
47
- function getLatestRemoteVersion() {
48
- const result = runCapture(
49
- `git ls-remote --tags ${REPO_URL} | grep -o 'v[0-9]*\\.[0-9]*\\.[0-9]*' | sort -V | tail -1`
50
- );
51
- return result ? result.replace(/^v/, "") : null;
52
- }
77
+ async function downloadAndExtract(version) {
78
+ const pkgSlug = NPM_PACKAGE.replace("@", "").replace("/", "-");
79
+ const tarballUrl = `https://registry.npmjs.org/${NPM_PACKAGE}/-/${pkgSlug}-${version}.tgz`;
80
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-plugin-"));
81
+ const tarball = path.join(tmpDir, "plugin.tgz");
53
82
 
54
- function printSettingsHelp() {
55
- const settingsPath = path.join(process.cwd(), ".claude", "settings.json");
56
- const settingsExists = fs.existsSync(settingsPath);
83
+ console.log(`Downloading v${version} from npm...`);
84
+ await downloadFile(tarballUrl, tarball);
57
85
 
58
- console.log("\nAdd the plugin to your project:");
59
- console.log(
60
- ` File: ${path.join(process.cwd(), ".claude", "settings.json")}`
61
- );
86
+ console.log("Extracting...");
87
+ execSync(`tar -xzf "${tarball}" -C "${tmpDir}"`);
62
88
 
63
- if (settingsExists) {
64
- let settings;
65
- try {
66
- settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
67
- } catch {
68
- settings = {};
69
- }
70
- const plugins = settings.plugins || [];
71
- const alreadyAdded = plugins.some((p) => p.path === INSTALL_DIR);
72
- if (alreadyAdded) {
73
- console.log(" Already configured in settings.json ✓");
74
- return;
75
- }
89
+ // npm tarballs always extract to a "package/" subdirectory
90
+ const extracted = path.join(tmpDir, "package");
91
+ if (!fs.existsSync(extracted)) {
92
+ fs.rmSync(tmpDir, { recursive: true, force: true });
93
+ throw new Error("Extraction failed: no package/ directory in tarball");
76
94
  }
77
95
 
78
- console.log(
79
- ` Add: { "plugins": [{ "path": "${INSTALL_DIR}" }] }\n`
80
- );
81
-
82
- // Offer to auto-configure
83
- try {
84
- const readline = require("readline").createInterface({
85
- input: process.stdin,
86
- output: process.stdout,
87
- });
88
- readline.question(
89
- "Auto-add to .claude/settings.json? [Y/n] ",
90
- (answer) => {
91
- readline.close();
92
- if (answer.toLowerCase() !== "n") {
93
- addToSettings(settingsPath);
94
- }
95
- }
96
- );
97
- } catch {
98
- // non-interactive context
96
+ if (fs.existsSync(INSTALL_DIR)) {
97
+ fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
99
98
  }
99
+ fs.mkdirSync(path.dirname(INSTALL_DIR), { recursive: true });
100
+ fs.renameSync(extracted, INSTALL_DIR);
101
+ fs.rmSync(tmpDir, { recursive: true, force: true });
100
102
  }
101
103
 
102
- function addToSettings(settingsPath) {
103
- const dir = path.dirname(settingsPath);
104
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
104
+ function registerPlugin() {
105
+ console.log("\nRegistering plugin with Claude Code...");
105
106
 
106
- let settings = {};
107
- if (fs.existsSync(settingsPath)) {
108
- try {
107
+ // Prefer project-level settings if .claude/ exists in cwd, else user-level
108
+ const projectDir = path.join(process.cwd(), ".claude");
109
+ const settingsPath = fs.existsSync(projectDir)
110
+ ? path.join(projectDir, "settings.json")
111
+ : path.join(os.homedir(), ".claude", "settings.json");
112
+
113
+ try {
114
+ let settings = {};
115
+ if (fs.existsSync(settingsPath)) {
109
116
  settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
110
- } catch {}
111
- }
117
+ }
112
118
 
113
- settings.plugins = settings.plugins || [];
114
- const alreadyAdded = settings.plugins.some((p) => p.path === INSTALL_DIR);
115
- if (!alreadyAdded) {
116
- settings.plugins.push({ path: INSTALL_DIR });
119
+ if (!Array.isArray(settings.enabledPlugins)) {
120
+ settings.enabledPlugins = [];
121
+ }
122
+
123
+ if (!settings.enabledPlugins.includes(INSTALL_DIR)) {
124
+ settings.enabledPlugins.push(INSTALL_DIR);
125
+ }
126
+
127
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
117
128
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
118
- console.log(" settings.json updated ✓");
129
+
130
+ const label = settingsPath.startsWith(process.cwd())
131
+ ? ".claude/settings.json"
132
+ : settingsPath;
133
+ console.log(`Plugin registered in ${label} ✓`);
134
+
135
+ if (settingsPath.startsWith(process.cwd())) {
136
+ console.log("Commit that file to share the plugin with your team.");
137
+ }
138
+ } catch {
139
+ console.log(
140
+ "\nCould not write settings. Add manually to .claude/settings.json:"
141
+ );
142
+ console.log(JSON.stringify({ enabledPlugins: [INSTALL_DIR] }, null, 2));
143
+ console.log(`\nOr load for a single session:`);
144
+ console.log(` claude --plugin-dir "${INSTALL_DIR}"`);
119
145
  }
120
146
  }
121
147
 
122
148
  // ── Commands ──────────────────────────────────────────────────────────────────
123
149
 
124
- function install() {
125
- checkGitAuth();
126
-
150
+ async function install() {
127
151
  if (fs.existsSync(INSTALL_DIR)) {
128
152
  const installed = getInstalledVersion();
129
- const latest = getLatestRemoteVersion();
153
+ const latest = await getLatestNpmVersion();
130
154
 
131
155
  if (latest && installed && installed !== latest) {
132
156
  console.log(`\nPlugin already installed (v${installed}).`);
133
- console.log(`v${latest} is available — run: npx @huma/presentation-generator-plugin update\n`);
157
+ console.log(
158
+ `v${latest} is available — run: npx ${NPM_PACKAGE} update\n`
159
+ );
134
160
  } else {
135
- console.log(`\nPlugin already installed (v${installed || "unknown"}). Nothing to do.\n`);
161
+ console.log(
162
+ `\nPlugin already installed (v${installed || "unknown"}). Nothing to do.\n`
163
+ );
136
164
  }
137
- printSettingsHelp();
165
+ registerPlugin();
138
166
  return;
139
167
  }
140
168
 
141
169
  console.log(`\nInstalling presentation-generator plugin...`);
142
- fs.mkdirSync(path.dirname(INSTALL_DIR), { recursive: true });
143
- run(`git clone ${REPO_URL} "${INSTALL_DIR}"`);
144
- console.log(`\nInstalled to: ${INSTALL_DIR}`);
145
- printSettingsHelp();
146
- }
147
170
 
148
- function update() {
149
- checkGitAuth();
171
+ const version = await getLatestNpmVersion();
172
+ if (!version) {
173
+ console.error(
174
+ "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
175
+ );
176
+ process.exit(1);
177
+ }
178
+
179
+ await downloadAndExtract(version);
180
+ console.log(`\nInstalled v${version} to: ${INSTALL_DIR}`);
181
+ registerPlugin();
182
+ }
150
183
 
184
+ async function update() {
151
185
  if (!fs.existsSync(INSTALL_DIR)) {
152
186
  console.log("\nPlugin not installed. Run install first:");
153
- console.log(" npx @ztffn/presentation-generator-plugin install\n");
187
+ console.log(` npx ${NPM_PACKAGE} install\n`);
154
188
  process.exit(1);
155
189
  }
156
190
 
157
191
  const before = getInstalledVersion();
158
- console.log(`\nUpdating presentation-generator plugin (current: v${before || "unknown"})...`);
159
- run(`git -C "${INSTALL_DIR}" pull`);
192
+ const latest = await getLatestNpmVersion();
160
193
 
161
- const after = getInstalledVersion();
162
- if (before !== after) {
163
- console.log(`\nUpdated v${before} v${after} ✓\n`);
164
- } else {
165
- console.log(`\nAlready up to date (v${after}) ✓\n`);
194
+ if (!latest) {
195
+ console.error(
196
+ "\nCould not fetch latest version from npm registry. Check your internet connection.\n"
197
+ );
198
+ process.exit(1);
166
199
  }
200
+
201
+ if (before === latest) {
202
+ console.log(`\nAlready up to date (v${before}) ✓`);
203
+ registerPlugin();
204
+ return;
205
+ }
206
+
207
+ console.log(
208
+ `\nUpdating presentation-generator plugin (v${before || "unknown"} → v${latest})...`
209
+ );
210
+ await downloadAndExtract(latest);
211
+ console.log(`\nUpdated to v${latest} ✓`);
212
+ registerPlugin();
167
213
  }
168
214
 
169
- function checkUpdate() {
215
+ async function checkUpdate() {
170
216
  if (!fs.existsSync(INSTALL_DIR)) {
171
- console.log("\nPlugin not installed.");
217
+ console.log("\nPlugin not installed.\n");
172
218
  return;
173
219
  }
174
220
 
175
221
  const installed = getInstalledVersion();
176
- const latest = getLatestRemoteVersion();
222
+ const latest = await getLatestNpmVersion();
177
223
 
178
224
  if (!latest) {
179
- console.log(`\nInstalled: v${installed || "unknown"} (could not reach remote)\n`);
225
+ console.log(
226
+ `\nInstalled: v${installed || "unknown"} (could not reach npm registry)\n`
227
+ );
180
228
  return;
181
229
  }
182
230
 
@@ -184,7 +232,7 @@ function checkUpdate() {
184
232
  console.log(`\nUp to date: v${installed} ✓\n`);
185
233
  } else {
186
234
  console.log(`\nUpdate available: v${installed} → v${latest}`);
187
- console.log(`Run: npx @ztffn/presentation-generator-plugin update\n`);
235
+ console.log(`Run: npx ${NPM_PACKAGE} update\n`);
188
236
  }
189
237
  }
190
238
 
@@ -194,8 +242,7 @@ function uninstall() {
194
242
  return;
195
243
  }
196
244
  fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
197
- console.log(`\nRemoved: ${INSTALL_DIR}`);
198
- console.log("You may also want to remove the plugin entry from .claude/settings.json\n");
245
+ console.log(`\nRemoved: ${INSTALL_DIR}\n`);
199
246
  }
200
247
 
201
248
  function help() {
@@ -203,21 +250,28 @@ function help() {
203
250
  presentation-generator-plugin v${CURRENT_VERSION}
204
251
 
205
252
  Commands:
206
- install Clone plugin to ~/.claude/plugins/ and configure project
207
- update Pull latest changes from GitHub
253
+ install Download plugin from npm and register with Claude Code
254
+ update Download and install the latest version from npm
208
255
  check-update Report whether an update is available
209
256
  uninstall Remove plugin from ~/.claude/plugins/
210
257
 
211
258
  Usage:
212
- npx @ztffn/presentation-generator-plugin install
213
- npx @ztffn/presentation-generator-plugin update
214
- npx @ztffn/presentation-generator-plugin check-update
215
-
216
- Requires: gh auth login (GitHub authentication)
259
+ npx ${NPM_PACKAGE} install
260
+ npx ${NPM_PACKAGE} update
261
+ npx ${NPM_PACKAGE} check-update
217
262
  `);
218
263
  }
219
264
 
220
- const commands = { install, update, "check-update": checkUpdate, uninstall, help };
265
+ // ── Dispatch ──────────────────────────────────────────────────────────────────
266
+
267
+ const commands = {
268
+ install,
269
+ update,
270
+ "check-update": checkUpdate,
271
+ uninstall,
272
+ help,
273
+ };
274
+
221
275
  const fn = commands[command];
222
276
 
223
277
  if (!fn) {
@@ -226,4 +280,7 @@ if (!fn) {
226
280
  process.exit(1);
227
281
  }
228
282
 
229
- fn();
283
+ Promise.resolve(fn()).catch((err) => {
284
+ console.error("\nError:", err.message, "\n");
285
+ process.exit(1);
286
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ztffn/presentation-generator-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "Claude Code plugin for generating graph-based presentations",
5
5
  "bin": {
6
6
  "presentation-generator-plugin": "bin/index.js"