@blueprintitai/shop-os-install 0.5.15
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/README.md +156 -0
- package/bin/shop-os-install.js +1334 -0
- package/bin/shop-os-update.js +243 -0
- package/package.json +32 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shop OS skills updater.
|
|
4
|
+
*
|
|
5
|
+
* Pulls the latest skills from GitHub without re-running the full installer.
|
|
6
|
+
* Safe to run at any time — does not touch your vault or license.
|
|
7
|
+
*
|
|
8
|
+
* Mac / Linux:
|
|
9
|
+
* npx -y --package=@blueprintitai/shop-os-install shop-os-update
|
|
10
|
+
*
|
|
11
|
+
* Windows (PowerShell):
|
|
12
|
+
* npx -y --package=@blueprintitai/shop-os-install shop-os-update
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join, dirname, delimiter } from "node:path";
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import {
|
|
19
|
+
cpSync,
|
|
20
|
+
existsSync,
|
|
21
|
+
mkdirSync,
|
|
22
|
+
readFileSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from "node:fs";
|
|
26
|
+
import { stdout, stderr, exit } from "node:process";
|
|
27
|
+
|
|
28
|
+
// ---------- output helpers ----------
|
|
29
|
+
|
|
30
|
+
const SUPPORTS_COLOR = stdout.isTTY && !process.env.NO_COLOR;
|
|
31
|
+
const c = (code, s) => (SUPPORTS_COLOR ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
32
|
+
const dim = (s) => c("2", s);
|
|
33
|
+
const bold = (s) => c("1", s);
|
|
34
|
+
const green = (s) => c("32", s);
|
|
35
|
+
const yellow = (s) => c("33", s);
|
|
36
|
+
const red = (s) => c("31", s);
|
|
37
|
+
const cyan = (s) => c("36", s);
|
|
38
|
+
|
|
39
|
+
const print = (msg = "") => stdout.write(msg + "\n");
|
|
40
|
+
const warn = (msg) => stderr.write(yellow("! ") + msg + "\n");
|
|
41
|
+
const fail = (msg) => { stderr.write(red("✗ ") + msg + "\n"); exit(1); };
|
|
42
|
+
const ok = (msg) => print(" " + green("✓") + " " + msg);
|
|
43
|
+
const info = (msg) => print(" " + dim("·") + " " + msg);
|
|
44
|
+
|
|
45
|
+
// ---------- helpers ----------
|
|
46
|
+
|
|
47
|
+
function readJSON(path, fallback) {
|
|
48
|
+
if (!existsSync(path)) return fallback;
|
|
49
|
+
try { return JSON.parse(readFileSync(path, "utf8")); }
|
|
50
|
+
catch { return fallback; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeJSON(path, obj) {
|
|
54
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
55
|
+
writeFileSync(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------- main ----------
|
|
59
|
+
|
|
60
|
+
function banner() {
|
|
61
|
+
[
|
|
62
|
+
"",
|
|
63
|
+
bold(" ╔════════════════════════════════════════════════════════════╗"),
|
|
64
|
+
bold(" ║ ║"),
|
|
65
|
+
bold(" ║ ") + cyan("Shop OS Skills Updater") + bold(" ║"),
|
|
66
|
+
bold(" ║ ") + dim("Pull the latest skills from Blueprint IT") + bold(" ║"),
|
|
67
|
+
bold(" ║ ║"),
|
|
68
|
+
bold(" ╚════════════════════════════════════════════════════════════╝"),
|
|
69
|
+
"",
|
|
70
|
+
].forEach((l) => print(l));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function ensureLocalBinOnPath() {
|
|
74
|
+
// The Claude native installer drops `claude` in ~/.local/bin (all platforms).
|
|
75
|
+
// A fresh shell may not have that on PATH yet, which would make a real install
|
|
76
|
+
// look missing. Prepend it before probing so detection never false-fails.
|
|
77
|
+
const localBin = join(homedir(), ".local", "bin");
|
|
78
|
+
const current = process.env.PATH || "";
|
|
79
|
+
if (existsSync(localBin) && !current.split(delimiter).includes(localBin)) {
|
|
80
|
+
process.env.PATH = localBin + delimiter + current;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function preflight() {
|
|
85
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
86
|
+
if (major < 18) fail(`Node.js 18+ required. You have ${process.version}.`);
|
|
87
|
+
|
|
88
|
+
ensureLocalBinOnPath();
|
|
89
|
+
const probe = spawnSync(
|
|
90
|
+
process.platform === "win32" ? "where" : "which",
|
|
91
|
+
["claude"],
|
|
92
|
+
{ stdio: "ignore", shell: false },
|
|
93
|
+
);
|
|
94
|
+
if (probe.status !== 0) {
|
|
95
|
+
fail("Claude Code not found. Make sure it is installed and on your PATH.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return join(homedir(), ".claude");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function refreshMarketplace(claudeRoot) {
|
|
102
|
+
const installLocation = join(claudeRoot, "plugins", "marketplaces", "blueprint-skills");
|
|
103
|
+
const repoUrl = "https://github.com/blueprintit-ai/blueprint-skills.git";
|
|
104
|
+
|
|
105
|
+
if (existsSync(join(installLocation, ".git"))) {
|
|
106
|
+
const fetch = spawnSync("git", ["fetch", "origin", "main", "--depth=1"], {
|
|
107
|
+
cwd: installLocation,
|
|
108
|
+
stdio: "ignore",
|
|
109
|
+
});
|
|
110
|
+
if (fetch.status === 0) {
|
|
111
|
+
const reset = spawnSync("git", ["reset", "--hard", "FETCH_HEAD"], {
|
|
112
|
+
cwd: installLocation,
|
|
113
|
+
stdio: "ignore",
|
|
114
|
+
});
|
|
115
|
+
if (reset.status === 0) {
|
|
116
|
+
ok("Skills pulled from GitHub");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// fetch/reset failed — wipe and re-clone
|
|
121
|
+
warn("git pull failed, re-cloning marketplace...");
|
|
122
|
+
try { rmSync(installLocation, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
123
|
+
} else {
|
|
124
|
+
info("Marketplace not found locally — cloning fresh...");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
mkdirSync(dirname(installLocation), { recursive: true });
|
|
128
|
+
const clone = spawnSync("git", ["clone", "--depth=1", repoUrl, installLocation], {
|
|
129
|
+
stdio: "ignore",
|
|
130
|
+
});
|
|
131
|
+
if (clone.status !== 0) fail("Could not reach GitHub. Check your internet connection and try again.");
|
|
132
|
+
ok("Skills cloned from GitHub");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function wipePluginCache(claudeRoot) {
|
|
136
|
+
const cacheDir = join(claudeRoot, "plugins", "cache", "blueprint-skills", "obsidian");
|
|
137
|
+
if (existsSync(cacheDir)) {
|
|
138
|
+
try {
|
|
139
|
+
rmSync(cacheDir, { recursive: true, force: true });
|
|
140
|
+
ok("Plugin cache cleared");
|
|
141
|
+
} catch {
|
|
142
|
+
warn("Could not clear plugin cache — Claude Code may load a stale version. Try restarting Claude Code twice.");
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
ok("Plugin cache already clean");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function reinstallPlugin(claudeRoot) {
|
|
150
|
+
const pluginsPath = join(claudeRoot, "plugins", "installed_plugins.json");
|
|
151
|
+
const data = readJSON(pluginsPath, { version: 2, plugins: {} });
|
|
152
|
+
if (!data.plugins) data.plugins = {};
|
|
153
|
+
|
|
154
|
+
const id = "obsidian@blueprint-skills";
|
|
155
|
+
const pluginName = "obsidian";
|
|
156
|
+
const marketplaceName = "blueprint-skills";
|
|
157
|
+
const now = new Date().toISOString();
|
|
158
|
+
const originalInstalledAt = data.plugins[id]?.[0]?.installedAt ?? now;
|
|
159
|
+
|
|
160
|
+
const marketplaceDir = join(claudeRoot, "plugins", "marketplaces", marketplaceName);
|
|
161
|
+
const pluginSourceDir = join(marketplaceDir, "plugins", pluginName);
|
|
162
|
+
|
|
163
|
+
if (existsSync(join(pluginSourceDir, ".claude-plugin"))) {
|
|
164
|
+
let version = "unknown";
|
|
165
|
+
try {
|
|
166
|
+
const pj = JSON.parse(readFileSync(join(pluginSourceDir, ".claude-plugin", "plugin.json"), "utf8"));
|
|
167
|
+
version = pj.version || "unknown";
|
|
168
|
+
} catch { /* keep "unknown" */ }
|
|
169
|
+
|
|
170
|
+
let gitCommitSha = "pending-sync";
|
|
171
|
+
const gitResult = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
172
|
+
cwd: marketplaceDir,
|
|
173
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
174
|
+
encoding: "utf8",
|
|
175
|
+
});
|
|
176
|
+
if (gitResult.status === 0 && gitResult.stdout) {
|
|
177
|
+
gitCommitSha = gitResult.stdout.trim();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const installPath = join(claudeRoot, "plugins", "cache", marketplaceName, pluginName, version);
|
|
181
|
+
try {
|
|
182
|
+
mkdirSync(join(claudeRoot, "plugins", "cache", marketplaceName, pluginName), { recursive: true });
|
|
183
|
+
cpSync(pluginSourceDir, installPath, { recursive: true });
|
|
184
|
+
|
|
185
|
+
// Preserve the existing scope/projectPath so the update doesn't break the
|
|
186
|
+
// vault association Claude Code set up on the original install.
|
|
187
|
+
const existingEntry = data.plugins[id]?.[0];
|
|
188
|
+
const entry = {
|
|
189
|
+
scope: existingEntry?.scope || "user",
|
|
190
|
+
installPath,
|
|
191
|
+
version,
|
|
192
|
+
installedAt: originalInstalledAt,
|
|
193
|
+
lastUpdated: now,
|
|
194
|
+
gitCommitSha,
|
|
195
|
+
};
|
|
196
|
+
if (existingEntry?.projectPath) entry.projectPath = existingEntry.projectPath;
|
|
197
|
+
|
|
198
|
+
data.plugins[id] = [entry];
|
|
199
|
+
writeJSON(pluginsPath, data);
|
|
200
|
+
ok(`Plugin installed (v${version})`);
|
|
201
|
+
return;
|
|
202
|
+
} catch {
|
|
203
|
+
// Fall through to pending stub.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Fallback: preserve existing scope/projectPath while marking for re-sync.
|
|
208
|
+
const existingEntry = data.plugins[id]?.[0];
|
|
209
|
+
const fallbackEntry = {
|
|
210
|
+
scope: existingEntry?.scope || "user",
|
|
211
|
+
installPath: null,
|
|
212
|
+
version: "pending",
|
|
213
|
+
installedAt: originalInstalledAt,
|
|
214
|
+
lastUpdated: now,
|
|
215
|
+
gitCommitSha: "pending-sync",
|
|
216
|
+
};
|
|
217
|
+
if (existingEntry?.projectPath) fallbackEntry.projectPath = existingEntry.projectPath;
|
|
218
|
+
data.plugins[id] = [fallbackEntry];
|
|
219
|
+
writeJSON(pluginsPath, data);
|
|
220
|
+
warn("Could not copy plugin files directly — marked for re-install on next Claude Code launch.");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
banner();
|
|
224
|
+
print(dim(" Updating Shop OS skills. Your vault and license are not affected.\n"));
|
|
225
|
+
|
|
226
|
+
print(dim(" [1/3] Refreshing skill files from GitHub"));
|
|
227
|
+
const claudeRoot = preflight();
|
|
228
|
+
refreshMarketplace(claudeRoot);
|
|
229
|
+
|
|
230
|
+
print("");
|
|
231
|
+
print(dim(" [2/3] Clearing plugin cache"));
|
|
232
|
+
wipePluginCache(claudeRoot);
|
|
233
|
+
|
|
234
|
+
print("");
|
|
235
|
+
print(dim(" [3/3] Installing updated plugin"));
|
|
236
|
+
reinstallPlugin(claudeRoot);
|
|
237
|
+
|
|
238
|
+
print("");
|
|
239
|
+
print(green(" ✓ Update complete."));
|
|
240
|
+
print("");
|
|
241
|
+
print(" " + bold("Restart Claude Code") + " to load the updated skills.");
|
|
242
|
+
print(" Your vault files, license key, and settings are unchanged.");
|
|
243
|
+
print("");
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blueprintitai/shop-os-install",
|
|
3
|
+
"version": "0.5.15",
|
|
4
|
+
"description": "One-command installer for Shop OS \u2014 Blueprint IT's AI Operating System for small businesses.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shop-os-install": "bin/shop-os-install.js",
|
|
8
|
+
"shop-os-update": "bin/shop-os-update.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"shop-os",
|
|
19
|
+
"blueprint-it",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"obsidian",
|
|
22
|
+
"ai-os",
|
|
23
|
+
"cabinet-shop"
|
|
24
|
+
],
|
|
25
|
+
"author": "Blueprint IT <info@blueprintit.ai>",
|
|
26
|
+
"license": "UNLICENSED",
|
|
27
|
+
"homepage": "https://blueprintit.ai/shop-os",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/blueprintit-ai/shop-os-installer.git"
|
|
31
|
+
}
|
|
32
|
+
}
|