promptgraph-mcp 2.1.5 → 2.1.7

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 (3) hide show
  1. package/index.js +38 -1
  2. package/package.json +1 -1
  3. package/tui.js +63 -35
package/index.js CHANGED
@@ -182,6 +182,8 @@ if (args[0] === 'marketplace') {
182
182
  process.exit(1);
183
183
  }
184
184
  const { browseMarketplace, browseBundles, installSkill, installBundle } = await import('./marketplace.js');
185
+ const { loadConfig: _lcMkt } = await import('./config.js');
186
+ const { getDb: _getDbMkt } = await import('./db.js');
185
187
  const { spinner: spin2 } = await import('./cli.js');
186
188
  const sp = spin2('Fetching marketplace...');
187
189
  sp.start();
@@ -190,6 +192,37 @@ if (args[0] === 'marketplace') {
190
192
 
191
193
  if (skills?.error) { error(skills.error); process.exit(1); }
192
194
 
195
+ // Build installed set: bundle IDs from config sources + skill IDs from DB
196
+ const installedSet = new Set();
197
+ try {
198
+ const cfg = _lcMkt();
199
+ for (const s of cfg.sources) {
200
+ if (s.source.startsWith('github:')) installedSet.add(s.source.replace('github:', ''));
201
+ if (s.source === 'marketplace') installedSet.add('marketplace');
202
+ }
203
+ const db = _getDbMkt();
204
+ for (const row of db.prepare('SELECT id FROM skills').all()) installedSet.add(row.id);
205
+ // Also add bundle IDs by matching repo names
206
+ for (const b of (Array.isArray(bundles) ? bundles : [])) {
207
+ if (b.repo_url) {
208
+ const repoName = b.repo_url.split('/').join('-');
209
+ if ([...installedSet].some(s => s.toLowerCase().includes(b.id.split('-').pop()))) {
210
+ installedSet.add(b.id);
211
+ }
212
+ }
213
+ }
214
+ // match github sources to bundle ids
215
+ for (const s of cfg.sources) {
216
+ if (!s.source.startsWith('github:')) continue;
217
+ const srcName = s.source.replace('github:', '').toLowerCase();
218
+ for (const b of (Array.isArray(bundles) ? bundles : [])) {
219
+ if (srcName.toLowerCase().includes(b.id.toLowerCase()) || b.id.toLowerCase().includes(srcName.split('-')[0])) {
220
+ installedSet.add(b.id);
221
+ }
222
+ }
223
+ }
224
+ } catch {}
225
+
193
226
  const { runTUI } = await import('./tui.js');
194
227
  await runTUI(
195
228
  Array.isArray(skills) ? skills : [],
@@ -198,11 +231,15 @@ if (args[0] === 'marketplace') {
198
231
  if (item.type === 'bundle') {
199
232
  const r = await installBundle(item.id);
200
233
  if (r?.error) throw new Error(r.error);
234
+ installedSet.add(item.id);
201
235
  } else {
202
236
  const r = await installSkill(item.code || item.id);
203
237
  if (r?.error) throw new Error(r.error);
238
+ installedSet.add(item.id);
239
+ if (item.code) installedSet.add(item.code);
204
240
  }
205
- }
241
+ },
242
+ installedSet
206
243
  );
207
244
  process.exit(0);
208
245
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/tui.js CHANGED
@@ -75,65 +75,89 @@ function filterItems(items, query, tab) {
75
75
 
76
76
  function render(state) {
77
77
  const { cols, rows } = termSize();
78
- const HEADER_ROWS = 4;
78
+ const HEADER_ROWS = 5;
79
79
  const FOOTER_ROWS = 3;
80
80
  const LIST_ROWS = rows - HEADER_ROWS - FOOTER_ROWS;
81
+ const NAME_W = Math.max(20, Math.floor(cols * 0.28));
82
+ const DESC_W = cols - NAME_W - 28;
81
83
 
82
84
  const { items, cursor, scroll, query, searching, tab, status } = state;
85
+ const skills = items.filter(i => i.type === 'skill').length;
86
+ const bundles = items.filter(i => i.type === 'bundle').length;
83
87
 
84
88
  write(HOME);
85
89
 
86
90
  // ── header ─────────────────────────────────────────────────────────────────
87
- const title = purple.bold(' ◆ PromptGraph Marketplace ');
88
- const tabs = ['all', 'skills', 'bundles'].map(t =>
89
- t === tab ? cyan.bold(`[${t}]`) : dim(`[${t}]`)
90
- ).join(' ');
91
- write(truncate(title + ' '.repeat(4) + tabs, cols) + CLEAR_EOL + '\n');
92
-
93
- // search bar
94
- const searchLabel = searching ? green('/ ') : dim('/ ');
95
- const searchVal = searching ? white(query) + (Math.floor(Date.now()/500)%2 ? '' : ' ') : dim(query || 'type / to search');
96
- write(' ' + searchLabel + truncate(searchVal, cols - 4) + CLEAR_EOL + '\n');
97
-
98
- const countLabel = dim(` ${items.length} items`);
99
- const hint = dim(status ? (status.ok ? green(' ' + status.msg) : red('' + status.msg)) : '');
100
- write(countLabel + hint + CLEAR_EOL + '\n');
91
+ // Row 1: title bar
92
+ const titleText = ' PromptGraph Marketplace';
93
+ const tabParts = ['all', 'skills', 'bundles'].map(t =>
94
+ t === tab
95
+ ? `\x1b[48;2;124;58;237m\x1b[97m ${t.toUpperCase()} \x1b[0m`
96
+ : dim(` ${t} `)
97
+ ).join('');
98
+ const titleLine = purple.bold(titleText) + ' ' + tabParts;
99
+ write(titleLine + CLEAR_EOL + '\n');
100
+
101
+ // Row 2: counts
102
+ const countLine = dim(' ') +
103
+ (tab !== 'bundles' ? chalk.white(`${skills} skills`) + dim(' ') : '') +
104
+ (tab !== 'skills' ? chalk.blue(`${bundles} bundles`) : '') +
105
+ (query ? dim(' · filter: ') + cyan(query) : '');
106
+ write(countLine + CLEAR_EOL + '\n');
107
+
108
+ // Row 3: search bar
109
+ const searchLabel = searching ? green(' / ') : dim(' / ');
110
+ const cursor_blink = Math.floor(Date.now() / 500) % 2 ? '▌' : ' ';
111
+ const searchVal = searching
112
+ ? white(query || '') + cursor_blink
113
+ : dim(query ? query : 'type / to search, Tab to switch view');
114
+ write(searchLabel + searchVal + CLEAR_EOL + '\n');
115
+
116
+ // Row 4: status / separator
117
+ if (status) {
118
+ const msg = status.ok ? green(' ✓ ' + status.msg) : red(' ✗ ' + status.msg);
119
+ write(msg + CLEAR_EOL + '\n');
120
+ } else {
121
+ write(dim('─'.repeat(cols)) + CLEAR_EOL + '\n');
122
+ }
101
123
  write(dim('─'.repeat(cols)) + CLEAR_EOL + '\n');
102
124
 
103
125
  // ── list ───────────────────────────────────────────────────────────────────
104
126
  let lastCat = null;
105
- let lineIdx = 0;
106
127
  let rendered = 0;
107
128
 
108
129
  for (let i = scroll; i < items.length && rendered < LIST_ROWS; i++) {
109
130
  const item = items[i];
110
131
  const selected = i === cursor;
111
- const bg = selected ? '\x1b[48;2;60;40;120m' : '';
112
- const reset = selected ? '\x1b[0m' : '';
132
+ const bg = selected ? '\x1b[48;2;55;35;110m' : '';
133
+ const reset = '\x1b[0m';
113
134
 
114
- // category header
135
+ // category header (only when ungrouped / mixed)
115
136
  if (item.category !== lastCat) {
116
137
  if (rendered >= LIST_ROWS) break;
117
138
  const icon = CAT_ICON[item.category] || '📦';
118
- write(bg + ' ' + purple(icon + ' ' + item.category) + reset + CLEAR_EOL + '\n');
139
+ write((selected ? bg : '') + ' ' + purple.bold(icon + ' ' + item.category) + reset + CLEAR_EOL + '\n');
119
140
  lastCat = item.category;
120
141
  rendered++;
142
+ if (rendered >= LIST_ROWS) break;
121
143
  }
122
144
 
123
- if (rendered >= LIST_ROWS) break;
124
-
125
145
  // item row
126
- const sel = selected ? cyan('') : ' ';
127
- const type = item.type === 'bundle' ? blue('⊞') : dim('·');
128
- const name = selected ? white.bold(item.name) : white(item.name);
129
- const stars = item.stars > 0 ? yellow('' + item.stars) : dim('★0');
130
- const extra = item.type === 'bundle'
131
- ? (item.skillCount ? blue(item.skillCount + ' skills') : blue('GitHub'))
132
- : (item.code ? dim(item.code) : '');
133
- const desc = dim(truncate(item.description, cols - 42));
134
-
135
- const left = ` ${sel} ${type} ${truncate(item.name, 28).padEnd(28)} ${stars} ${extra.padEnd(12)}`;
136
- write(bg + truncate(left, cols - desc.length - 2) + ' ' + desc + reset + CLEAR_EOL + '\n');
146
+ const isInstalled = item.type === 'bundle'
147
+ ? installedSet.has(item.id) || installedSet.has(item.id.toLowerCase())
148
+ : installedSet.has(item.id) || installedSet.has(item.code);
149
+ const arrow = selected ? cyan('') : ' ';
150
+ const type = item.type === 'bundle' ? blue('⊞') : dim('·');
151
+ const badge = isInstalled ? green('') : ' ';
152
+ const nameStr = truncate(item.name, NAME_W);
153
+ const namePad = nameStr.padEnd(NAME_W);
154
+ const nameCol = selected ? white.bold(namePad) : white(namePad);
155
+ const extra = item.type === 'bundle'
156
+ ? (item.skillCount ? blue((item.skillCount + ' sk').padEnd(8)) : blue('GitHub '))
157
+ : dim((item.code || '').padEnd(8));
158
+ const desc = dim(truncate(item.description, Math.max(10, DESC_W)));
159
+
160
+ write(bg + ` ${arrow} ${type} ${badge} ${nameCol} ${extra} ${desc}` + reset + CLEAR_EOL + '\n');
137
161
  rendered++;
138
162
  }
139
163
 
@@ -147,8 +171,12 @@ function render(state) {
147
171
  write(dim('─'.repeat(cols)) + CLEAR_EOL + '\n');
148
172
  const sel = items[cursor];
149
173
  if (sel && !searching) {
174
+ const isInst = sel.type === 'bundle'
175
+ ? installedSet.has(sel.id) || installedSet.has(sel.id.toLowerCase())
176
+ : installedSet.has(sel.id) || installedSet.has(sel.code);
150
177
  const installCmd = sel.type === 'bundle' ? `bundle install ${sel.id}` : `install ${sel.code || sel.id}`;
151
- write(dim(` Enter`) + ' install ' + dim('Tab') + ' switch ' + dim('/') + ' search ' + dim('q') + ' quit' + CLEAR_EOL + '\n');
178
+ const instLabel = isInst ? green(' installed') + dim(' ') : dim(' Enter') + chalk.white(' install') + dim(' ');
179
+ write(instLabel + dim('Tab') + ' switch ' + dim('/') + ' search ' + dim('q') + ' quit' + CLEAR_EOL + '\n');
152
180
  write(dim(` → pg ${installCmd}`) + CLEAR_EOL + '\n');
153
181
  } else if (searching) {
154
182
  write(dim(' Type to filter ') + cyan('Enter') + dim(' confirm ') + cyan('Esc') + dim(' cancel') + CLEAR_EOL + '\n');
@@ -176,7 +204,7 @@ function clampScroll(state) {
176
204
 
177
205
  // ── main ─────────────────────────────────────────────────────────────────────
178
206
 
179
- export async function runTUI(allSkills, allBundles, installFn) {
207
+ export async function runTUI(allSkills, allBundles, installFn, installedSet = new Set()) {
180
208
  const allItems = buildItems(allSkills, allBundles);
181
209
 
182
210
  const state = {