@blueprintit/shop-os-install 0.5.2 → 0.5.3

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.
@@ -140,7 +140,7 @@ async function validateLicense(key) {
140
140
  const url = `${LICENSE_SERVER}/validate?key=${encodeURIComponent(key)}`;
141
141
  let resp;
142
142
  try {
143
- resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.2" } });
143
+ resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.3" } });
144
144
  } catch (e) {
145
145
  return { ok: false, error: `network: ${e.message}` };
146
146
  }
@@ -297,6 +297,45 @@ function ensurePluginsInstalled(claudeRoot) {
297
297
  return { queued, total: PLUGINS_TO_ENABLE.length };
298
298
  }
299
299
 
300
+ // Vault-scoped permission allowlist. Non-technical Shop OS customers were hitting
301
+ // constant tool-permission prompts during /bp-setup (~50+ Read/Write/Bash dialogs
302
+ // per onboarding run). These patterns pre-approve the tool surface bp-setup and
303
+ // the daily Shop OS flow actually use, scoped to this vault's project settings —
304
+ // not the user's global settings. Risk model: customer's own paid Claude
305
+ // subscription, own machine, own data; we trade some inbound-injection surface
306
+ // for a workable UX. Writes/edits are intentionally bounded to the vault path.
307
+ function buildPermissionAllowList(vaultPath) {
308
+ return [
309
+ // Read-side: bp-setup reads reference templates from the marketplace clone
310
+ // (~/.claude/plugins/...) AND any context files the user drops in. Read,
311
+ // Glob, and Grep are low-risk; allow them broadly.
312
+ "Read",
313
+ "Glob",
314
+ "Grep",
315
+ // Write-side: scope to the vault directory so Claude can't be coaxed into
316
+ // writing outside it.
317
+ `Write(${vaultPath}/**)`,
318
+ `Edit(${vaultPath}/**)`,
319
+ // Bash: bp-setup creates directories, chmods hooks, finds reference files.
320
+ // Restrict to the specific subcommands the skill actually invokes.
321
+ "Bash(mkdir:*)",
322
+ "Bash(chmod:*)",
323
+ "Bash(find:*)",
324
+ "Bash(ls:*)",
325
+ "Bash(cat:*)",
326
+ "Bash(grep:*)",
327
+ "Bash(echo:*)",
328
+ "Bash(test:*)",
329
+ "Bash(touch:*)",
330
+ // Network: bp-setup's Phase B+ fetches links the user pastes (LinkedIn,
331
+ // About pages, brand guidelines, etc.).
332
+ "WebFetch",
333
+ "WebSearch",
334
+ // Plan tool: surfaced by some Superpowers skills, no need to prompt.
335
+ "TodoWrite",
336
+ ];
337
+ }
338
+
300
339
  function enableForVault(vaultPath) {
301
340
  const settingsPath = join(vaultPath, ".claude", "settings.json");
302
341
  const settings = readJSON(settingsPath, {});
@@ -304,10 +343,57 @@ function enableForVault(vaultPath) {
304
343
  for (const id of PLUGINS_TO_ENABLE) {
305
344
  settings.enabledPlugins[id] = true;
306
345
  }
346
+ // Merge (don't clobber) any existing permission allowlist a re-install would
347
+ // have written, then re-add ours. De-dupe by entry string.
348
+ if (!settings.permissions) settings.permissions = {};
349
+ const existing = Array.isArray(settings.permissions.allow)
350
+ ? settings.permissions.allow
351
+ : [];
352
+ const ours = buildPermissionAllowList(vaultPath);
353
+ settings.permissions.allow = Array.from(new Set([...existing, ...ours]));
307
354
  writeJSON(settingsPath, settings);
308
355
  return settingsPath;
309
356
  }
310
357
 
358
+ // Pre-empt Claude Code's first-run wizard (theme picker, terminal-setup prompt,
359
+ // onboarding screens) by writing the "already onboarded" flags BEFORE the
360
+ // setup script launches `claude`. The auth/sign-in flow is independent and
361
+ // still happens — we can't bypass that and don't try. Safe to run repeatedly:
362
+ // merges with any existing config rather than overwriting.
363
+ function seedClaudeCodeDefaults(claudeRoot) {
364
+ const seeded = { onboardingFlags: false, theme: false };
365
+
366
+ // ~/.claude.json holds Claude Code's user-state (numStartups, hasCompletedOnboarding,
367
+ // anonymousId, etc.). Live next to ~/.claude/, not inside it.
368
+ const userStatePath = join(homedir(), ".claude.json");
369
+ const userState = readJSON(userStatePath, {});
370
+ if (!userState.hasCompletedOnboarding) {
371
+ userState.hasCompletedOnboarding = true;
372
+ if (!userState.lastOnboardingVersion) userState.lastOnboardingVersion = "1.0.30";
373
+ seeded.onboardingFlags = true;
374
+ try {
375
+ writeFileSync(userStatePath, JSON.stringify(userState, null, 2) + "\n", "utf8");
376
+ } catch {
377
+ // best-effort; permission errors here aren't worth blocking the install
378
+ }
379
+ }
380
+
381
+ // ~/.claude/settings.json holds user-scope settings (theme, permissions, etc.)
382
+ const settingsPath = join(claudeRoot, "settings.json");
383
+ const settings = readJSON(settingsPath, {});
384
+ if (!settings.theme) {
385
+ settings.theme = "dark";
386
+ seeded.theme = true;
387
+ try {
388
+ writeJSON(settingsPath, settings);
389
+ } catch {
390
+ // best-effort
391
+ }
392
+ }
393
+
394
+ return seeded;
395
+ }
396
+
311
397
  function createVaultClaudeMd(vaultPath, license) {
312
398
  const claudeMd = join(vaultPath, "CLAUDE.md");
313
399
  if (existsSync(claudeMd)) return false; // do not overwrite an existing vault
@@ -696,15 +782,25 @@ async function main() {
696
782
  if (rawResult.created) ok("Raw/ inbox + Raw/processed/ created (drop materials in Raw/ to seed the vault)");
697
783
  else info("Raw/ inbox already present (left untouched)");
698
784
 
699
- print(dim(" [4/6] Enabling plugins for this vault"));
785
+ print(dim(" [4/7] Enabling plugins + permission allowlist for this vault"));
700
786
  const settingsPath = enableForVault(vaultPath);
701
787
  ok(`Wrote ${settingsPath.replace(homedir(), "~")}`);
788
+ info("Pre-approved Read/Write/Edit/Bash patterns so /bp-setup runs without permission prompts");
789
+
790
+ print(dim(" [5/7] Pre-seeding Claude Code defaults"));
791
+ const seeded = seedClaudeCodeDefaults(claudeRoot);
792
+ if (seeded.onboardingFlags || seeded.theme) {
793
+ if (seeded.onboardingFlags) ok("Marked Claude Code onboarding complete (skips theme/terminal wizard on first launch)");
794
+ if (seeded.theme) ok("Set Claude Code theme to dark");
795
+ } else {
796
+ info("Claude Code already configured — left existing settings untouched");
797
+ }
702
798
 
703
- print(dim(" [5/6] Saving license"));
799
+ print(dim(" [6/7] Saving license"));
704
800
  const licensePath = saveLicenseFile(license);
705
801
  ok(`License saved to ${licensePath.replace(homedir(), "~")} (chmod 600)`);
706
802
 
707
- print(dim(" [6/6] Installing Shop OS Chat launcher"));
803
+ print(dim(" [7/7] Installing Shop OS Chat launcher"));
708
804
  const launcherPath = writeChatLauncher(vaultPath);
709
805
  ok(`Wrote ${launcherPath.replace(homedir(), "~")}`);
710
806
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueprintit/shop-os-install",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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": {