@blueprintit/shop-os-install 0.4.0 → 0.5.1

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.1" } });
142
143
  } catch (e) {
143
144
  return { ok: false, error: `network: ${e.message}` };
144
145
  }
@@ -172,49 +173,75 @@ function writeJSON(path, obj) {
172
173
  }
173
174
 
174
175
  function ensureMarketplaces(claudeRoot) {
176
+ // Always (re-)register marketplaces and force-delete any stale clone on
177
+ // disk. A clone pinned to an old commit is the #1 reason re-installs keep
178
+ // serving old plugin versions (e.g. obsidian 3.8.0 skills appearing months
179
+ // after the marketplace shipped 3.12.0). Deleting the directory forces
180
+ // Claude Code to re-clone from origin on its next launch.
181
+ //
182
+ // We still report `added` vs already-known to keep the customer-facing
183
+ // install output consistent across runs — the cache-wipe is intentionally
184
+ // invisible.
175
185
  const path = join(claudeRoot, "plugins", "known_marketplaces.json");
176
186
  const known = readJSON(path, {});
177
187
  const added = [];
178
188
  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);
189
+ const installLocation = join(claudeRoot, "plugins", "marketplaces", mp.name);
190
+ if (!known[mp.name]) added.push(mp.name);
191
+ known[mp.name] = {
192
+ source: { source: mp.source.type, repo: mp.source.repo },
193
+ installLocation,
194
+ lastUpdated: new Date().toISOString(),
195
+ };
196
+ if (existsSync(installLocation)) {
197
+ try {
198
+ rmSync(installLocation, { recursive: true, force: true });
199
+ } catch {
200
+ // Best-effort. Locked files on Windows can block removal; in that case
201
+ // Claude Code will keep using the stale clone. Customer can quit Claude
202
+ // Code and re-run to recover.
203
+ }
186
204
  }
187
205
  }
188
- if (added.length) writeJSON(path, known);
206
+ writeJSON(path, known);
189
207
  return { added, total: MARKETPLACES.length };
190
208
  }
191
209
 
192
210
  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).
211
+ // Always reset the Shop OS-required plugin entries to a pending user-scope
212
+ // record. A previously-installed pinned entry (e.g. obsidian 3.8.0 from an
213
+ // earlier beta) would otherwise prevent Claude Code from picking up the
214
+ // marketplace's current version. Pending status forces Claude Code to
215
+ // re-resolve the plugin against the (just-refreshed) marketplace clone on
216
+ // its next launch.
217
+ //
218
+ // This wipes any project-scope variants of these specific plugin ids — that's
219
+ // intentional. Shop OS is meant to be enabled at user scope so the same
220
+ // plugin set works across every vault the operator runs.
221
+ //
222
+ // We still report whether entries were newly created vs pre-existing so the
223
+ // customer-facing install output matches the v0.4.0 conventions — the
224
+ // forced-reset is intentionally invisible.
196
225
  const path = join(claudeRoot, "plugins", "installed_plugins.json");
197
226
  const existing = readJSON(path, { version: 2, plugins: {} });
198
227
  if (!existing.plugins) existing.plugins = {};
199
228
  const installedAt = new Date().toISOString();
200
- let changed = false;
229
+ const queued = [];
201
230
  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
- }
231
+ if (!existing.plugins[id]) queued.push(id);
232
+ existing.plugins[id] = [
233
+ {
234
+ scope: "user",
235
+ installPath: null, // filled in by Claude Code on next marketplace sync
236
+ version: "pending",
237
+ installedAt,
238
+ lastUpdated: installedAt,
239
+ gitCommitSha: "pending-sync",
240
+ },
241
+ ];
215
242
  }
216
- if (changed) writeJSON(path, existing);
217
- return changed;
243
+ writeJSON(path, existing);
244
+ return { queued, total: PLUGINS_TO_ENABLE.length };
218
245
  }
219
246
 
220
247
  function enableForVault(vaultPath) {
@@ -233,6 +260,7 @@ function createVaultClaudeMd(vaultPath, license) {
233
260
  if (existsSync(claudeMd)) return false; // do not overwrite an existing vault
234
261
  const content = `---
235
262
  os-mode: business
263
+ bp-setup-state: pending
236
264
  license-customer: ${license.customer}
237
265
  license-product: ${license.product}
238
266
  installed-at: ${new Date().toISOString()}
@@ -590,12 +618,12 @@ async function main() {
590
618
  }
591
619
 
592
620
  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 {
621
+ const pluginsResult = ensurePluginsInstalled(claudeRoot);
622
+ if (pluginsResult.queued.length === 0) {
598
623
  info("All required plugins already queued");
624
+ } else {
625
+ for (const id of pluginsResult.queued) ok(`Queued plugin: ${id}`);
626
+ info("Claude Code will sync the actual plugin files from the marketplaces on next launch.");
599
627
  }
600
628
 
601
629
  print(dim(` [3/6] ${isExisting ? "Configuring" : "Creating"} vault at ${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.1",
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": {