@blueprintit/shop-os-install 0.4.0 → 0.5.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.
@@ -25,6 +25,7 @@ import {
25
25
  existsSync,
26
26
  mkdirSync,
27
27
  readFileSync,
28
+ rmSync,
28
29
  writeFileSync,
29
30
  chmodSync,
30
31
  } from "node:fs";
@@ -138,7 +139,7 @@ async function validateLicense(key) {
138
139
  const url = `${LICENSE_SERVER}/validate?key=${encodeURIComponent(key)}`;
139
140
  let resp;
140
141
  try {
141
- resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.3.0" } });
142
+ resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.0" } });
142
143
  } catch (e) {
143
144
  return { ok: false, error: `network: ${e.message}` };
144
145
  }
@@ -172,49 +173,65 @@ function writeJSON(path, obj) {
172
173
  }
173
174
 
174
175
  function ensureMarketplaces(claudeRoot) {
176
+ // Always (re-)register the marketplaces and clear any stale clone on disk.
177
+ // A clone pinned to an old commit is the #1 reason re-installs keep serving
178
+ // old plugin versions (e.g. obsidian 3.8.0 skills appearing months after
179
+ // the marketplace shipped 3.12.0). Clearing the directory forces Claude Code
180
+ // to re-clone from origin on its next launch.
175
181
  const path = join(claudeRoot, "plugins", "known_marketplaces.json");
176
182
  const known = readJSON(path, {});
177
- const added = [];
183
+ const cleared = [];
178
184
  for (const mp of MARKETPLACES) {
179
- if (!known[mp.name]) {
180
- known[mp.name] = {
181
- source: { source: mp.source.type, repo: mp.source.repo },
182
- installLocation: join(claudeRoot, "plugins", "marketplaces", mp.name),
183
- lastUpdated: new Date().toISOString(),
184
- };
185
- added.push(mp.name);
185
+ const installLocation = join(claudeRoot, "plugins", "marketplaces", mp.name);
186
+ known[mp.name] = {
187
+ source: { source: mp.source.type, repo: mp.source.repo },
188
+ installLocation,
189
+ lastUpdated: new Date().toISOString(),
190
+ };
191
+ if (existsSync(installLocation)) {
192
+ try {
193
+ rmSync(installLocation, { recursive: true, force: true });
194
+ cleared.push(mp.name);
195
+ } catch {
196
+ // Best-effort. Locked files on Windows can prevent removal; Claude Code
197
+ // may keep using the stale clone in that case. Customer can manually
198
+ // delete the folder and re-launch to recover.
199
+ }
186
200
  }
187
201
  }
188
- if (added.length) writeJSON(path, known);
189
- return { added, total: MARKETPLACES.length };
202
+ writeJSON(path, known);
203
+ return { cleared, total: MARKETPLACES.length };
190
204
  }
191
205
 
192
206
  function ensurePluginsInstalled(claudeRoot) {
193
- // We record the plugins in installed_plugins.json. Claude Code's marketplace
194
- // refresh on next launch will fetch the actual plugin files into cache/.
195
- // If they're already present, we leave the entry alone (don't downgrade).
207
+ // Always reset the Shop OS-required plugin entries to a pending user-scope
208
+ // record. We can't use a presence check here: a previously-installed pinned
209
+ // entry (e.g. obsidian 3.8.0 from a prior beta) would prevent Claude Code
210
+ // from picking up the marketplace's current version. Forcing pending status
211
+ // makes Claude Code re-resolve the plugin against the (just-refreshed)
212
+ // marketplace clone on its next launch.
213
+ //
214
+ // This wipes any project-scope variants of these specific plugin ids — that's
215
+ // intentional. Shop OS is meant to be enabled at user scope so the same
216
+ // plugin set works across every vault the operator runs.
196
217
  const path = join(claudeRoot, "plugins", "installed_plugins.json");
197
218
  const existing = readJSON(path, { version: 2, plugins: {} });
198
219
  if (!existing.plugins) existing.plugins = {};
199
220
  const installedAt = new Date().toISOString();
200
- let changed = false;
201
221
  for (const id of PLUGINS_TO_ENABLE) {
202
- if (!existing.plugins[id]) {
203
- existing.plugins[id] = [
204
- {
205
- scope: "user",
206
- installPath: null, // filled in by Claude Code on next marketplace sync
207
- version: "pending",
208
- installedAt,
209
- lastUpdated: installedAt,
210
- gitCommitSha: "pending-sync",
211
- },
212
- ];
213
- changed = true;
214
- }
222
+ existing.plugins[id] = [
223
+ {
224
+ scope: "user",
225
+ installPath: null, // filled in by Claude Code on next marketplace sync
226
+ version: "pending",
227
+ installedAt,
228
+ lastUpdated: installedAt,
229
+ gitCommitSha: "pending-sync",
230
+ },
231
+ ];
215
232
  }
216
- if (changed) writeJSON(path, existing);
217
- return changed;
233
+ writeJSON(path, existing);
234
+ return PLUGINS_TO_ENABLE;
218
235
  }
219
236
 
220
237
  function enableForVault(vaultPath) {
@@ -233,6 +250,7 @@ function createVaultClaudeMd(vaultPath, license) {
233
250
  if (existsSync(claudeMd)) return false; // do not overwrite an existing vault
234
251
  const content = `---
235
252
  os-mode: business
253
+ bp-setup-state: pending
236
254
  license-customer: ${license.customer}
237
255
  license-product: ${license.product}
238
256
  installed-at: ${new Date().toISOString()}
@@ -583,20 +601,15 @@ async function main() {
583
601
 
584
602
  print(dim(" [1/6] Registering plugin marketplaces"));
585
603
  const mpResult = ensureMarketplaces(claudeRoot);
586
- if (mpResult.added.length === 0) {
587
- info(`All ${mpResult.total} marketplaces already registered`);
588
- } else {
589
- for (const name of mpResult.added) ok(`Added marketplace: ${name}`);
604
+ for (const mp of MARKETPLACES) ok(`Marketplace ready: ${mp.name}`);
605
+ if (mpResult.cleared.length) {
606
+ info(`Cleared stale clone for ${mpResult.cleared.join(", ")} Claude Code will re-fetch on next launch.`);
590
607
  }
591
608
 
592
- print(dim(" [2/6] Enabling plugins for installation"));
593
- const pluginsChanged = ensurePluginsInstalled(claudeRoot);
594
- if (pluginsChanged) {
595
- for (const id of PLUGINS_TO_ENABLE) ok(`Queued plugin: ${id}`);
596
- info("Claude Code will sync the actual plugin files from the marketplaces on next launch.");
597
- } else {
598
- info("All required plugins already queued");
599
- }
609
+ print(dim(" [2/6] Queueing plugins for sync"));
610
+ const queued = ensurePluginsInstalled(claudeRoot);
611
+ for (const id of queued) ok(`Queued plugin: ${id}`);
612
+ info("Claude Code will sync the latest plugin files from the marketplaces on next launch.");
600
613
 
601
614
  print(dim(` [3/6] ${isExisting ? "Configuring" : "Creating"} vault at ${vaultPath}`));
602
615
  if (!existsSync(vaultPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueprintit/shop-os-install",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "One-command installer for Shop OS — Blueprint IT's AI Operating System for small businesses.",
5
5
  "type": "module",
6
6
  "bin": {