@blueprintit/shop-os-install 0.5.2 → 0.5.4
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.
- package/bin/shop-os-install.js +117 -6
- package/package.json +1 -1
package/bin/shop-os-install.js
CHANGED
|
@@ -119,8 +119,17 @@ function getClaudeRoot() {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function checkClaudeCode() {
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// Detect by binary on PATH, not by the ~/.claude directory: when Claude Code
|
|
123
|
+
// is installed via `npm install -g @anthropic-ai/claude-code` (which the
|
|
124
|
+
// setup scripts now do), the .claude directory isn't created until the user
|
|
125
|
+
// launches `claude` for the first time. A binary check correctly identifies
|
|
126
|
+
// installs from npm, the official .ps1/.sh installer, or the desktop app.
|
|
127
|
+
const probe = spawnSync(
|
|
128
|
+
process.platform === "win32" ? "where" : "which",
|
|
129
|
+
["claude"],
|
|
130
|
+
{ stdio: "ignore", shell: false },
|
|
131
|
+
);
|
|
132
|
+
if (probe.status !== 0) {
|
|
124
133
|
print("");
|
|
125
134
|
print(red("Claude Code is not installed."));
|
|
126
135
|
print("");
|
|
@@ -131,6 +140,12 @@ function checkClaudeCode() {
|
|
|
131
140
|
print("re-run this installer.");
|
|
132
141
|
exit(1);
|
|
133
142
|
}
|
|
143
|
+
// Stage ~/.claude so downstream marketplace and plugin writes succeed even
|
|
144
|
+
// if the user hasn't launched `claude` yet to seed the dir themselves.
|
|
145
|
+
const root = getClaudeRoot();
|
|
146
|
+
if (!existsSync(root)) {
|
|
147
|
+
mkdirSync(root, { recursive: true });
|
|
148
|
+
}
|
|
134
149
|
return root;
|
|
135
150
|
}
|
|
136
151
|
|
|
@@ -140,7 +155,7 @@ async function validateLicense(key) {
|
|
|
140
155
|
const url = `${LICENSE_SERVER}/validate?key=${encodeURIComponent(key)}`;
|
|
141
156
|
let resp;
|
|
142
157
|
try {
|
|
143
|
-
resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.
|
|
158
|
+
resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.3" } });
|
|
144
159
|
} catch (e) {
|
|
145
160
|
return { ok: false, error: `network: ${e.message}` };
|
|
146
161
|
}
|
|
@@ -297,6 +312,45 @@ function ensurePluginsInstalled(claudeRoot) {
|
|
|
297
312
|
return { queued, total: PLUGINS_TO_ENABLE.length };
|
|
298
313
|
}
|
|
299
314
|
|
|
315
|
+
// Vault-scoped permission allowlist. Non-technical Shop OS customers were hitting
|
|
316
|
+
// constant tool-permission prompts during /bp-setup (~50+ Read/Write/Bash dialogs
|
|
317
|
+
// per onboarding run). These patterns pre-approve the tool surface bp-setup and
|
|
318
|
+
// the daily Shop OS flow actually use, scoped to this vault's project settings —
|
|
319
|
+
// not the user's global settings. Risk model: customer's own paid Claude
|
|
320
|
+
// subscription, own machine, own data; we trade some inbound-injection surface
|
|
321
|
+
// for a workable UX. Writes/edits are intentionally bounded to the vault path.
|
|
322
|
+
function buildPermissionAllowList(vaultPath) {
|
|
323
|
+
return [
|
|
324
|
+
// Read-side: bp-setup reads reference templates from the marketplace clone
|
|
325
|
+
// (~/.claude/plugins/...) AND any context files the user drops in. Read,
|
|
326
|
+
// Glob, and Grep are low-risk; allow them broadly.
|
|
327
|
+
"Read",
|
|
328
|
+
"Glob",
|
|
329
|
+
"Grep",
|
|
330
|
+
// Write-side: scope to the vault directory so Claude can't be coaxed into
|
|
331
|
+
// writing outside it.
|
|
332
|
+
`Write(${vaultPath}/**)`,
|
|
333
|
+
`Edit(${vaultPath}/**)`,
|
|
334
|
+
// Bash: bp-setup creates directories, chmods hooks, finds reference files.
|
|
335
|
+
// Restrict to the specific subcommands the skill actually invokes.
|
|
336
|
+
"Bash(mkdir:*)",
|
|
337
|
+
"Bash(chmod:*)",
|
|
338
|
+
"Bash(find:*)",
|
|
339
|
+
"Bash(ls:*)",
|
|
340
|
+
"Bash(cat:*)",
|
|
341
|
+
"Bash(grep:*)",
|
|
342
|
+
"Bash(echo:*)",
|
|
343
|
+
"Bash(test:*)",
|
|
344
|
+
"Bash(touch:*)",
|
|
345
|
+
// Network: bp-setup's Phase B+ fetches links the user pastes (LinkedIn,
|
|
346
|
+
// About pages, brand guidelines, etc.).
|
|
347
|
+
"WebFetch",
|
|
348
|
+
"WebSearch",
|
|
349
|
+
// Plan tool: surfaced by some Superpowers skills, no need to prompt.
|
|
350
|
+
"TodoWrite",
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
|
|
300
354
|
function enableForVault(vaultPath) {
|
|
301
355
|
const settingsPath = join(vaultPath, ".claude", "settings.json");
|
|
302
356
|
const settings = readJSON(settingsPath, {});
|
|
@@ -304,10 +358,57 @@ function enableForVault(vaultPath) {
|
|
|
304
358
|
for (const id of PLUGINS_TO_ENABLE) {
|
|
305
359
|
settings.enabledPlugins[id] = true;
|
|
306
360
|
}
|
|
361
|
+
// Merge (don't clobber) any existing permission allowlist a re-install would
|
|
362
|
+
// have written, then re-add ours. De-dupe by entry string.
|
|
363
|
+
if (!settings.permissions) settings.permissions = {};
|
|
364
|
+
const existing = Array.isArray(settings.permissions.allow)
|
|
365
|
+
? settings.permissions.allow
|
|
366
|
+
: [];
|
|
367
|
+
const ours = buildPermissionAllowList(vaultPath);
|
|
368
|
+
settings.permissions.allow = Array.from(new Set([...existing, ...ours]));
|
|
307
369
|
writeJSON(settingsPath, settings);
|
|
308
370
|
return settingsPath;
|
|
309
371
|
}
|
|
310
372
|
|
|
373
|
+
// Pre-empt Claude Code's first-run wizard (theme picker, terminal-setup prompt,
|
|
374
|
+
// onboarding screens) by writing the "already onboarded" flags BEFORE the
|
|
375
|
+
// setup script launches `claude`. The auth/sign-in flow is independent and
|
|
376
|
+
// still happens — we can't bypass that and don't try. Safe to run repeatedly:
|
|
377
|
+
// merges with any existing config rather than overwriting.
|
|
378
|
+
function seedClaudeCodeDefaults(claudeRoot) {
|
|
379
|
+
const seeded = { onboardingFlags: false, theme: false };
|
|
380
|
+
|
|
381
|
+
// ~/.claude.json holds Claude Code's user-state (numStartups, hasCompletedOnboarding,
|
|
382
|
+
// anonymousId, etc.). Live next to ~/.claude/, not inside it.
|
|
383
|
+
const userStatePath = join(homedir(), ".claude.json");
|
|
384
|
+
const userState = readJSON(userStatePath, {});
|
|
385
|
+
if (!userState.hasCompletedOnboarding) {
|
|
386
|
+
userState.hasCompletedOnboarding = true;
|
|
387
|
+
if (!userState.lastOnboardingVersion) userState.lastOnboardingVersion = "1.0.30";
|
|
388
|
+
seeded.onboardingFlags = true;
|
|
389
|
+
try {
|
|
390
|
+
writeFileSync(userStatePath, JSON.stringify(userState, null, 2) + "\n", "utf8");
|
|
391
|
+
} catch {
|
|
392
|
+
// best-effort; permission errors here aren't worth blocking the install
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ~/.claude/settings.json holds user-scope settings (theme, permissions, etc.)
|
|
397
|
+
const settingsPath = join(claudeRoot, "settings.json");
|
|
398
|
+
const settings = readJSON(settingsPath, {});
|
|
399
|
+
if (!settings.theme) {
|
|
400
|
+
settings.theme = "dark";
|
|
401
|
+
seeded.theme = true;
|
|
402
|
+
try {
|
|
403
|
+
writeJSON(settingsPath, settings);
|
|
404
|
+
} catch {
|
|
405
|
+
// best-effort
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return seeded;
|
|
410
|
+
}
|
|
411
|
+
|
|
311
412
|
function createVaultClaudeMd(vaultPath, license) {
|
|
312
413
|
const claudeMd = join(vaultPath, "CLAUDE.md");
|
|
313
414
|
if (existsSync(claudeMd)) return false; // do not overwrite an existing vault
|
|
@@ -696,15 +797,25 @@ async function main() {
|
|
|
696
797
|
if (rawResult.created) ok("Raw/ inbox + Raw/processed/ created (drop materials in Raw/ to seed the vault)");
|
|
697
798
|
else info("Raw/ inbox already present (left untouched)");
|
|
698
799
|
|
|
699
|
-
print(dim(" [4/
|
|
800
|
+
print(dim(" [4/7] Enabling plugins + permission allowlist for this vault"));
|
|
700
801
|
const settingsPath = enableForVault(vaultPath);
|
|
701
802
|
ok(`Wrote ${settingsPath.replace(homedir(), "~")}`);
|
|
803
|
+
info("Pre-approved Read/Write/Edit/Bash patterns so /bp-setup runs without permission prompts");
|
|
804
|
+
|
|
805
|
+
print(dim(" [5/7] Pre-seeding Claude Code defaults"));
|
|
806
|
+
const seeded = seedClaudeCodeDefaults(claudeRoot);
|
|
807
|
+
if (seeded.onboardingFlags || seeded.theme) {
|
|
808
|
+
if (seeded.onboardingFlags) ok("Marked Claude Code onboarding complete (skips theme/terminal wizard on first launch)");
|
|
809
|
+
if (seeded.theme) ok("Set Claude Code theme to dark");
|
|
810
|
+
} else {
|
|
811
|
+
info("Claude Code already configured — left existing settings untouched");
|
|
812
|
+
}
|
|
702
813
|
|
|
703
|
-
print(dim(" [
|
|
814
|
+
print(dim(" [6/7] Saving license"));
|
|
704
815
|
const licensePath = saveLicenseFile(license);
|
|
705
816
|
ok(`License saved to ${licensePath.replace(homedir(), "~")} (chmod 600)`);
|
|
706
817
|
|
|
707
|
-
print(dim(" [
|
|
818
|
+
print(dim(" [7/7] Installing Shop OS Chat launcher"));
|
|
708
819
|
const launcherPath = writeChatLauncher(vaultPath);
|
|
709
820
|
ok(`Wrote ${launcherPath.replace(homedir(), "~")}`);
|
|
710
821
|
|