promptup-plugin 0.1.1 → 0.1.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.
Files changed (2) hide show
  1. package/bin/install.cjs +133 -42
  2. package/package.json +1 -1
package/bin/install.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
+ const { execSync } = require('child_process');
6
7
 
7
8
  // Colors
8
9
  const cyan = '\x1b[36m';
@@ -40,28 +41,106 @@ if (hasUninstall) {
40
41
  console.log(`${yellow}Uninstalling PromptUp...${reset}\n`);
41
42
 
42
43
  // Remove skills
43
- const skillsDir = path.join(CLAUDE_DIR, 'skills');
44
44
  for (const skill of ['eval', 'pr-report', 'status']) {
45
- const dest = path.join(skillsDir, skill);
45
+ const dest = path.join(CLAUDE_DIR, 'skills', skill);
46
46
  if (fs.existsSync(dest)) {
47
47
  fs.rmSync(dest, { recursive: true });
48
48
  console.log(` ${red}✗${reset} Removed skill: ${skill}`);
49
49
  }
50
50
  }
51
51
 
52
- // Remove MCP from global settings
53
- const settingsLocal = path.join(CLAUDE_DIR, 'settings.local.json');
54
- if (fs.existsSync(settingsLocal)) {
52
+ // Remove hooks from settings.json
53
+ const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
54
+ if (fs.existsSync(settingsPath)) {
55
55
  try {
56
- const settings = JSON.parse(fs.readFileSync(settingsLocal, 'utf-8'));
57
- if (settings.mcpServers?.promptup) {
58
- delete settings.mcpServers.promptup;
59
- fs.writeFileSync(settingsLocal, JSON.stringify(settings, null, 2) + '\n');
60
- console.log(` ${red}✗${reset} Removed MCP server from settings.local.json`);
56
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
57
+ let changed = false;
58
+
59
+ for (const event of ['SessionStart', 'UserPromptSubmit']) {
60
+ if (settings.hooks?.[event]) {
61
+ settings.hooks[event] = settings.hooks[event].filter(
62
+ (h) => !h.hooks?.some((hk) => hk.command?.includes('.promptup')),
63
+ );
64
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
65
+ changed = true;
66
+ }
67
+ }
68
+
69
+ if (settings.statusLine?.command?.includes('.promptup')) {
70
+ delete settings.statusLine;
71
+ changed = true;
72
+ }
73
+
74
+ if (changed) {
75
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
76
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
77
+ console.log(` ${red}✗${reset} Removed hooks and statusline from settings.json`);
78
+ }
79
+ } catch {}
80
+ }
81
+
82
+ // Also clean settings.local.json (from older installs)
83
+ const settingsLocalPath = path.join(CLAUDE_DIR, 'settings.local.json');
84
+ if (fs.existsSync(settingsLocalPath)) {
85
+ try {
86
+ const settings = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf-8'));
87
+ let changed = false;
88
+
89
+ for (const event of ['SessionStart', 'UserPromptSubmit']) {
90
+ if (settings.hooks?.[event]) {
91
+ settings.hooks[event] = settings.hooks[event].filter(
92
+ (h) => !h.hooks?.some((hk) => hk.command?.includes('.promptup')),
93
+ );
94
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
95
+ changed = true;
96
+ }
97
+ }
98
+
99
+ if (settings.statusLine?.command?.includes('.promptup')) {
100
+ delete settings.statusLine;
101
+ changed = true;
102
+ }
103
+
104
+ if (changed) {
105
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
106
+ const remaining = Object.keys(settings).length;
107
+ if (remaining === 0) {
108
+ fs.unlinkSync(settingsLocalPath);
109
+ } else {
110
+ fs.writeFileSync(settingsLocalPath, JSON.stringify(settings, null, 2) + '\n');
111
+ }
112
+ console.log(` ${red}✗${reset} Cleaned settings.local.json`);
61
113
  }
62
114
  } catch {}
63
115
  }
64
116
 
117
+ // Remove MCP from .mcp.json files
118
+ for (const mcpPath of [
119
+ path.join(CLAUDE_DIR, '.mcp.json'),
120
+ path.join(process.cwd(), '.mcp.json'),
121
+ ]) {
122
+ if (fs.existsSync(mcpPath)) {
123
+ try {
124
+ const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
125
+ if (mcp.mcpServers?.promptup) {
126
+ delete mcp.mcpServers.promptup;
127
+ if (Object.keys(mcp.mcpServers).length === 0) {
128
+ fs.unlinkSync(mcpPath);
129
+ } else {
130
+ fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
131
+ }
132
+ console.log(` ${red}✗${reset} Removed MCP from ${mcpPath}`);
133
+ }
134
+ } catch {}
135
+ }
136
+ }
137
+
138
+ // Remove plugin dir
139
+ if (fs.existsSync(PLUGIN_DIR)) {
140
+ fs.rmSync(PLUGIN_DIR, { recursive: true });
141
+ console.log(` ${red}✗${reset} Removed plugin at ${PLUGIN_DIR}`);
142
+ }
143
+
65
144
  console.log(`\n${green}PromptUp uninstalled.${reset}`);
66
145
  console.log(`${dim}Data preserved at ${DATA_DIR} — delete manually if desired.${reset}\n`);
67
146
  process.exit(0);
@@ -74,7 +153,6 @@ if (hasLocal) scope = 'local';
74
153
  if (hasGlobal) scope = 'global';
75
154
 
76
155
  if (!hasLocal && !hasGlobal) {
77
- // Default to global
78
156
  scope = 'global';
79
157
  console.log(`${dim}Installing globally (use --local for project-only)${reset}\n`);
80
158
  }
@@ -87,7 +165,6 @@ const packageRoot = path.resolve(__dirname, '..');
87
165
 
88
166
  console.log(`${bold}Setting up PromptUp...${reset}\n`);
89
167
 
90
- // Ensure dirs exist
91
168
  fs.mkdirSync(PLUGIN_DIR, { recursive: true });
92
169
  fs.mkdirSync(DATA_DIR, { recursive: true });
93
170
 
@@ -97,7 +174,6 @@ console.log(` ${green}✓${reset} Installed plugin runtime`);
97
174
 
98
175
  // Copy hooks/
99
176
  copyDirSync(path.join(packageRoot, 'hooks'), path.join(PLUGIN_DIR, 'hooks'));
100
- // Make hooks executable
101
177
  for (const f of fs.readdirSync(path.join(PLUGIN_DIR, 'hooks'))) {
102
178
  if (f.endsWith('.sh')) {
103
179
  fs.chmodSync(path.join(PLUGIN_DIR, 'hooks', f), 0o755);
@@ -119,13 +195,28 @@ if (fs.existsSync(path.join(packageRoot, 'statusline.sh'))) {
119
195
  console.log(` ${green}✓${reset} Installed statusline`);
120
196
  }
121
197
 
122
- // Copy package.json for version tracking
198
+ // Copy package.json for version tracking + dependency install
123
199
  fs.copyFileSync(
124
200
  path.join(packageRoot, 'package.json'),
125
201
  path.join(PLUGIN_DIR, 'package.json'),
126
202
  );
127
203
 
128
- // ─── Step 2: Install skills to ~/.claude/skills/ ────────────────────────────
204
+ // ─── Step 2: Install dependencies ───────────────────────────────────────────
205
+
206
+ console.log(` ${dim}Installing dependencies (better-sqlite3, MCP SDK)...${reset}`);
207
+ try {
208
+ execSync('npm install --production --no-audit --no-fund', {
209
+ cwd: PLUGIN_DIR,
210
+ stdio: 'pipe',
211
+ timeout: 120000,
212
+ });
213
+ console.log(` ${green}✓${reset} Dependencies installed`);
214
+ } catch (err) {
215
+ console.log(` ${red}✗${reset} Dependency install failed: ${err.message}`);
216
+ console.log(` ${yellow}Try manually: cd ${PLUGIN_DIR} && npm install --production${reset}`);
217
+ }
218
+
219
+ // ─── Step 3: Install skills to ~/.claude/skills/ ────────────────────────────
129
220
 
130
221
  const skillsDir = path.join(CLAUDE_DIR, 'skills');
131
222
  fs.mkdirSync(skillsDir, { recursive: true });
@@ -139,40 +230,38 @@ for (const skill of ['eval', 'pr-report', 'status']) {
139
230
  }
140
231
  }
141
232
 
142
- // ─── Step 3: Configure MCP server ───────────────────────────────────────────
233
+ // ─── Step 4: Configure MCP server ───────────────────────────────────────────
143
234
 
144
235
  const mcpEntry = {
145
236
  command: 'node',
146
237
  args: [path.join(PLUGIN_DIR, 'dist', 'index.js')],
147
238
  };
148
239
 
149
- if (scope === 'global') {
150
- // Add to ~/.claude/.mcp.json (global MCP config)
151
- const mcpPath = path.join(CLAUDE_DIR, '.mcp.json');
152
- const mcp = fs.existsSync(mcpPath)
153
- ? JSON.parse(fs.readFileSync(mcpPath, 'utf-8'))
154
- : {};
155
-
156
- if (!mcp.mcpServers) mcp.mcpServers = {};
157
- mcp.mcpServers.promptup = mcpEntry;
158
- fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
159
- console.log(` ${green}✓${reset} MCP server → ~/.claude/.mcp.json (global)`);
160
- } else {
161
- // Add to .mcp.json in current directory
162
- const mcpPath = path.join(process.cwd(), '.mcp.json');
163
- const mcp = fs.existsSync(mcpPath)
164
- ? JSON.parse(fs.readFileSync(mcpPath, 'utf-8'))
165
- : {};
166
-
167
- if (!mcp.mcpServers) mcp.mcpServers = {};
168
- mcp.mcpServers.promptup = mcpEntry;
169
- fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
170
- console.log(` ${green}✓${reset} MCP server → .mcp.json (local)`);
240
+ // MCP always goes to project .mcp.json (Claude Code reads MCP from here)
241
+ const mcpPath = path.join(process.cwd(), '.mcp.json');
242
+ const mcp = fs.existsSync(mcpPath)
243
+ ? JSON.parse(fs.readFileSync(mcpPath, 'utf-8'))
244
+ : {};
245
+
246
+ if (!mcp.mcpServers) mcp.mcpServers = {};
247
+ mcp.mcpServers.promptup = mcpEntry;
248
+ fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
249
+ console.log(` ${green}✓${reset} MCP server .mcp.json`);
250
+
251
+ // Also try to register globally via claude CLI (silent fail if not available)
252
+ try {
253
+ execSync(
254
+ `claude mcp add promptup -s user -- node ${path.join(PLUGIN_DIR, 'dist', 'index.js')}`,
255
+ { stdio: 'pipe', timeout: 10000 },
256
+ );
257
+ console.log(` ${green}✓${reset} MCP server → claude global config`);
258
+ } catch {
259
+ // claude CLI not available or failed — that's fine, .mcp.json is enough
171
260
  }
172
261
 
173
- // ─── Step 4: Configure hooks ────────────────────────────────────────────────
262
+ // ─── Step 5: Configure hooks (in settings.json like GSD does) ───────────────
174
263
 
175
- const settingsPath = path.join(CLAUDE_DIR, 'settings.local.json');
264
+ const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
176
265
  const settings = fs.existsSync(settingsPath)
177
266
  ? JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
178
267
  : {};
@@ -217,7 +306,7 @@ if (!hasAutoEval) {
217
306
  console.log(` ${green}✓${reset} Hook: UserPromptSubmit → auto-eval`);
218
307
  }
219
308
 
220
- // Statusline
309
+ // Statusline (respect existing — prompt if already set, like GSD)
221
310
  if (!settings.statusLine) {
222
311
  settings.statusLine = {
223
312
  type: 'command',
@@ -225,11 +314,13 @@ if (!settings.statusLine) {
225
314
  padding: 2,
226
315
  };
227
316
  console.log(` ${green}✓${reset} Statusline: pupmeter`);
317
+ } else if (!settings.statusLine.command?.includes('.promptup')) {
318
+ console.log(` ${yellow}⚠${reset} Statusline already configured — skipped (existing: ${settings.statusLine.command?.slice(0, 40)}...)`);
228
319
  }
229
320
 
230
321
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
231
322
 
232
- // ─── Step 5: Create default config ─────────────────────────────────────────
323
+ // ─── Step 6: Create default config ─────────────────────────────────────────
233
324
 
234
325
  const configPath = path.join(DATA_DIR, 'config.json');
235
326
  if (!fs.existsSync(configPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptup-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "AI coding skill evaluator for Claude Code — 11-dimension scoring, decision intelligence, PR reports",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",