@must-b/must-b 1.72.7 → 1.72.8

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.
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Skill Singularity Compiler — Phase 42
4
+ *
5
+ * Reads all functional skill files from must-b-skills/ (commands, agents,
6
+ * hooks, SKILL.md) and compiles them into a single embedded JSON bundle at
7
+ * src/core/embedded-skills.json.
8
+ *
9
+ * Explicitly excluded:
10
+ * - demo.gif, LICENSE.md, README.md, CHANGELOG.md, SECURITY.md
11
+ * - .github/, .vscode/, .devcontainer/, examples/, scripts/, Script/
12
+ * - Any binary / non-UTF8 file
13
+ */
14
+
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const ROOT = path.resolve(__dirname, '..');
21
+ const SKILLS_BASE = path.join(ROOT, 'must-b-skills');
22
+ const OUT_FILE = path.join(ROOT, 'src', 'core', 'embedded-skills.json');
23
+
24
+ // ── Blocklists ──────────────────────────────────────────────────────────────
25
+
26
+ const SKIP_DIRS = new Set([
27
+ '.github', '.vscode', '.devcontainer',
28
+ 'examples', 'scripts', 'Script', 'node_modules',
29
+ 'references', 'hooks-handlers', // docs-only
30
+ ]);
31
+
32
+ const SKIP_FILES = new Set([
33
+ 'README.md', 'CHANGELOG.md', 'SECURITY.md', 'LICENSE.md',
34
+ 'LICENSE', 'LICENSE.txt', 'demo.gif',
35
+ ]);
36
+
37
+ const ALLOWED_EXTENSIONS = new Set(['.md', '.json', '.yaml', '.yml', '.txt']);
38
+
39
+ // ── Helpers ─────────────────────────────────────────────────────────────────
40
+
41
+ function slugify(name) {
42
+ return name
43
+ .replace(/\.md$/i, '')
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9]+/g, '-')
46
+ .replace(/^-|-$/g, '');
47
+ }
48
+
49
+ const PLUGIN_EMOJIS = {
50
+ 'agent-sdk-dev': '🤖',
51
+ 'claude-opus-4-5-migration': '⬆️',
52
+ 'code-review': '🔍',
53
+ 'commit-commands': '📦',
54
+ 'explanatory-output-style': '📝',
55
+ 'feature-dev': '🚀',
56
+ 'frontend-design': '🎨',
57
+ 'hookify': '🪝',
58
+ 'learning-output-style': '📚',
59
+ 'finance-core': '📈',
60
+ 'os-tools': '🖥️',
61
+ 'plugin-dev': '🔌',
62
+ 'pr-review-toolkit': '🔎',
63
+ 'ralph-wiggum': '🎭',
64
+ 'security-guidance': '🔒',
65
+ 'global': '🌐',
66
+ };
67
+
68
+ function parseFrontmatter(content) {
69
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
70
+ if (!match) return { name: '', description: '', argumentHint: '', allowedTools: [], body: content };
71
+
72
+ const fm = match[1];
73
+ const body = match[2] ?? '';
74
+
75
+ let name = '';
76
+ const np = fm.match(/^name:\s*(.+)$/m);
77
+ if (np) name = np[1].trim().replace(/^["']|["']$/g, '');
78
+
79
+ let description = '';
80
+ const dq = fm.match(/^description:\s*"((?:[^"\\]|\\.)*)"\s*$/m);
81
+ if (dq) {
82
+ description = dq[1].replace(/\\"/g, '"');
83
+ } else {
84
+ const dp = fm.match(/^description:\s*(.+)$/m);
85
+ if (dp) description = dp[1].trim().replace(/^["']|["']$/g, '');
86
+ }
87
+
88
+ let argumentHint = '';
89
+ const ah = fm.match(/^argument-hint:\s*(.+)$/m);
90
+ if (ah) argumentHint = ah[1].trim().replace(/^["'\[]|["'\]]$/g, '');
91
+
92
+ let allowedTools = [];
93
+ const atLine = fm.match(/^allowed-tools:\s*([\s\S]*?)(?=\n\S|$)/m);
94
+ if (atLine) {
95
+ const raw = atLine[1].trim();
96
+ if (raw.startsWith('[')) {
97
+ try {
98
+ const parsed = JSON.parse(raw);
99
+ if (Array.isArray(parsed)) allowedTools = parsed.map(String);
100
+ } catch {
101
+ allowedTools = raw.replace(/^\[|\]$/g, '').split(',')
102
+ .map(s => s.trim().replace(/^"|"$/g, '')).filter(Boolean);
103
+ }
104
+ } else {
105
+ allowedTools = raw.split(',').map(s => s.trim()).filter(Boolean);
106
+ }
107
+ }
108
+
109
+ return { name, description, argumentHint, allowedTools, body };
110
+ }
111
+
112
+ function buildEntry(filePath, fileName, pluginName, idPrefix, extraTags) {
113
+ let content;
114
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch { return null; }
115
+
116
+ const { name: fmName, description, argumentHint, allowedTools, body } = parseFrontmatter(content);
117
+ const fileSlug = slugify(fileName);
118
+ const id = `${idPrefix}-${fileSlug}`;
119
+ const emoji = PLUGIN_EMOJIS[pluginName] ?? '🔧';
120
+ const displayName = fmName || fileSlug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
121
+
122
+ return {
123
+ id,
124
+ name: displayName,
125
+ description: description || displayName,
126
+ emoji,
127
+ tags: ['imported', 'claude-code', pluginName, ...extraTags],
128
+ pluginName,
129
+ commandFile: fileName,
130
+ allowedTools,
131
+ argumentHint,
132
+ promptBody: body,
133
+ hasScripts: false,
134
+ requires: {},
135
+ };
136
+ }
137
+
138
+ // ── Loaders ──────────────────────────────────────────────────────────────────
139
+
140
+ function loadDir(dir, pluginName, idPrefix, extraTags) {
141
+ if (!fs.existsSync(dir)) return [];
142
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
143
+ const skills = [];
144
+ for (const entry of entries) {
145
+ if (!entry.isFile()) continue;
146
+ if (SKIP_FILES.has(entry.name)) continue;
147
+ if (!ALLOWED_EXTENSIONS.has(path.extname(entry.name))) continue;
148
+ if (!entry.name.endsWith('.md')) continue;
149
+ const skill = buildEntry(path.join(dir, entry.name), entry.name, pluginName, idPrefix, extraTags);
150
+ if (skill) skills.push(skill);
151
+ }
152
+ return skills;
153
+ }
154
+
155
+ function loadSkillsRecursive(skillsDir, pluginName) {
156
+ if (!fs.existsSync(skillsDir)) return [];
157
+ const results = [];
158
+
159
+ function walk(dir) {
160
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
161
+ for (const entry of entries) {
162
+ if (entry.isDirectory()) {
163
+ if (!SKIP_DIRS.has(entry.name)) walk(path.join(dir, entry.name));
164
+ } else if (entry.isFile() && entry.name === 'SKILL.md') {
165
+ const rel = path.relative(skillsDir, path.join(dir, entry.name));
166
+ const relSlug = rel.replace(/[\\/]/g, '-').replace(/\.md$/i, '').toLowerCase().replace(/[^a-z0-9-]+/g, '-');
167
+ const skill = buildEntry(
168
+ path.join(dir, entry.name),
169
+ entry.name,
170
+ pluginName,
171
+ `cc-${slugify(pluginName)}-skill-${relSlug}`,
172
+ ['skill'],
173
+ );
174
+ if (skill) {
175
+ skill.id = `cc-${slugify(pluginName)}-skill-${relSlug}`;
176
+ results.push(skill);
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ walk(skillsDir);
183
+ return results;
184
+ }
185
+
186
+ function loadHooks(hooksDir, pluginName) {
187
+ if (!fs.existsSync(hooksDir)) return [];
188
+ const entries = fs.readdirSync(hooksDir, { withFileTypes: true });
189
+ const skills = [];
190
+ for (const entry of entries) {
191
+ if (!entry.isFile()) continue;
192
+ if (SKIP_FILES.has(entry.name)) continue;
193
+ if (!ALLOWED_EXTENSIONS.has(path.extname(entry.name))) continue;
194
+ const skill = buildEntry(
195
+ path.join(hooksDir, entry.name),
196
+ entry.name,
197
+ pluginName,
198
+ `cc-${slugify(pluginName)}-hook`,
199
+ ['hook'],
200
+ );
201
+ if (skill) skills.push(skill);
202
+ }
203
+ return skills;
204
+ }
205
+
206
+ // ── Main scan ────────────────────────────────────────────────────────────────
207
+
208
+ function scan() {
209
+ if (!fs.existsSync(SKILLS_BASE)) {
210
+ console.error(`[bundle-skills] ERROR: must-b-skills/ not found at ${SKILLS_BASE}`);
211
+ process.exit(1);
212
+ }
213
+
214
+ const all = [];
215
+
216
+ // Global .claude/commands/
217
+ const globalCmds = path.join(SKILLS_BASE, '.claude', 'commands');
218
+ all.push(...loadDir(globalCmds, 'global', 'cc-global', ['command', 'global']));
219
+
220
+ // Per-plugin
221
+ const pluginsRoot = path.join(SKILLS_BASE, 'plugins');
222
+ if (fs.existsSync(pluginsRoot)) {
223
+ for (const entry of fs.readdirSync(pluginsRoot, { withFileTypes: true })) {
224
+ if (!entry.isDirectory()) continue;
225
+ if (SKIP_DIRS.has(entry.name)) continue;
226
+ const pluginDir = path.join(pluginsRoot, entry.name);
227
+ const pluginName = entry.name;
228
+ const pSlug = slugify(pluginName);
229
+
230
+ all.push(...loadDir(path.join(pluginDir, 'commands'), pluginName, `cc-${pSlug}`, ['command']));
231
+ all.push(...loadDir(path.join(pluginDir, 'agents'), pluginName, `cc-${pSlug}-agent`, ['agent']));
232
+ all.push(...loadSkillsRecursive(path.join(pluginDir, 'skills'), pluginName));
233
+ all.push(...loadHooks(path.join(pluginDir, 'hooks'), pluginName));
234
+ }
235
+ }
236
+
237
+ // Deduplicate by id
238
+ const seen = new Set();
239
+ const deduped = all.filter(s => {
240
+ if (seen.has(s.id)) return false;
241
+ seen.add(s.id);
242
+ return true;
243
+ });
244
+ deduped.sort((a, b) => a.id.localeCompare(b.id));
245
+ return deduped;
246
+ }
247
+
248
+ // ── Write output ─────────────────────────────────────────────────────────────
249
+
250
+ const skills = scan();
251
+ const bundle = {
252
+ version: '1.71.4',
253
+ generatedAt: new Date().toISOString(),
254
+ count: skills.length,
255
+ skills,
256
+ };
257
+
258
+ fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
259
+ fs.writeFileSync(OUT_FILE, JSON.stringify(bundle, null, 2), 'utf8');
260
+
261
+ console.log(`[bundle-skills] ✓ Compiled ${skills.length} skills → ${path.relative(ROOT, OUT_FILE)}`);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Recreates node_modules/must-b/ after every npm install.
3
+ * The must-b package shim maps ./plugin-sdk/* to the internal TypeScript
4
+ * source tree so that channel extensions can import from "must-b/plugin-sdk/*"
5
+ * without a separate published npm package.
6
+ *
7
+ * Global installs (npm install -g) run postinstall with elevated file-system
8
+ * restrictions — writing to node_modules may be denied with EACCES or EPERM.
9
+ * The shim is best-effort: a permission failure is logged as a warning and the
10
+ * install continues cleanly. Channel extensions will not load without the shim,
11
+ * but all core Must-b features remain fully operational.
12
+ */
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
18
+ const shimDir = path.join(root, 'node_modules', 'must-b');
19
+
20
+ const shimPkg = JSON.stringify({
21
+ name: 'must-b',
22
+ version: '1.0.0',
23
+ exports: {
24
+ './plugin-sdk/*': '../../src/core/source/plugin-sdk/*.ts',
25
+ },
26
+ }, null, 2);
27
+
28
+ try {
29
+ fs.mkdirSync(shimDir, { recursive: true });
30
+ fs.writeFileSync(path.join(shimDir, 'package.json'), shimPkg);
31
+ console.log('[postinstall] must-b plugin-sdk shim written →', shimDir);
32
+ } catch (err) {
33
+ const code = err && err.code;
34
+ if (code === 'EACCES' || code === 'EPERM' || code === 'EROFS') {
35
+ console.warn(
36
+ '[postinstall] WARNING: Could not write must-b shim to node_modules ' +
37
+ `(${code} — likely a global npm install with strict permissions).\n` +
38
+ ' Channel extensions may not load. All core Must-b features are unaffected.\n' +
39
+ ' To enable extensions, run: npm install --prefix <your-project-dir>'
40
+ );
41
+ } else {
42
+ // Unexpected error — surface it but still don't crash the install
43
+ console.warn('[postinstall] WARNING: must-b shim creation failed:', err && err.message || err);
44
+ }
45
+ }
@@ -0,0 +1,113 @@
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Must-b Installer for Windows (PowerShell)
5
+ .DESCRIPTION
6
+ Downloads and installs Must-b — the Autonomous AI Platform.
7
+ Usage: iex (irm https://must-b.com/install.ps1)
8
+ — or —
9
+ pwsh scripts/install.ps1
10
+ #>
11
+
12
+ $ErrorActionPreference = "Stop"
13
+
14
+ $REPO = "https://github.com/aytac43-0/Must-b"
15
+ $INSTALL_DIR = Join-Path $env:USERPROFILE ".must-b"
16
+ $NODE_MIN = 20
17
+
18
+ # ── UI helpers ───────────────────────────────────────────────────────────
19
+ function Header($msg) { Write-Host "`n $msg" -ForegroundColor DarkYellow }
20
+ function Ok($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
21
+ function Fail($msg) { Write-Host " [ERROR] $msg" -ForegroundColor Red; exit 1 }
22
+
23
+ Write-Host @"
24
+
25
+ __ __ _ _
26
+ | \/ |_ _ ___| |_ | |__
27
+ | |\/| | | | / __| __| __ | '_ \
28
+ | | | | |_| \__ \ |_ / \ | |_) |
29
+ |_| |_|\__,_|___/\__| \__/ |_.__/
30
+ Autonomous AI Platform
31
+
32
+ "@ -ForegroundColor DarkYellow
33
+
34
+ # ── Prerequisites ─────────────────────────────────────────────────────────
35
+ Header "Checking prerequisites..."
36
+
37
+ # Node.js
38
+ $nodePath = Get-Command node -ErrorAction SilentlyContinue
39
+ if (-not $nodePath) {
40
+ Fail "Node.js not found. Install Node.js $NODE_MIN+ from https://nodejs.org and re-run."
41
+ }
42
+ $nodeVersion = (node -e "process.stdout.write(process.version.slice(1))").Trim()
43
+ $nodeMajor = [int]($nodeVersion.Split(".")[0])
44
+ if ($nodeMajor -lt $NODE_MIN) {
45
+ Fail "Node.js $NODE_MIN+ required (found v$nodeVersion). Please upgrade."
46
+ }
47
+ Ok "Node.js v$nodeVersion"
48
+
49
+ # Git
50
+ $gitPath = Get-Command git -ErrorAction SilentlyContinue
51
+ if (-not $gitPath) {
52
+ Fail "git not found. Install Git from https://git-scm.com and re-run."
53
+ }
54
+ Ok "git $(git --version)"
55
+
56
+ # npm
57
+ $npmPath = Get-Command npm -ErrorAction SilentlyContinue
58
+ if (-not $npmPath) { Fail "npm not found." }
59
+ Ok "npm $(npm --version)"
60
+
61
+ # ── Clone / update ────────────────────────────────────────────────────────
62
+ Header "Installing Must-b to $INSTALL_DIR..."
63
+
64
+ if (Test-Path (Join-Path $INSTALL_DIR ".git")) {
65
+ Write-Host " Existing installation found — pulling latest..."
66
+ git -C $INSTALL_DIR pull --ff-only
67
+ } else {
68
+ git clone --depth 1 $REPO $INSTALL_DIR
69
+ }
70
+ Ok "Repository ready"
71
+
72
+ # ── Install dependencies ──────────────────────────────────────────────────
73
+ Header "Installing backend dependencies..."
74
+ Set-Location $INSTALL_DIR
75
+ npm install --prefer-offline --omit=dev
76
+ Ok "Backend dependencies installed"
77
+
78
+ Header "Installing frontend dependencies..."
79
+ Set-Location (Join-Path $INSTALL_DIR "public\must-b-ui")
80
+ npm install --prefer-offline --omit=dev
81
+ Ok "Frontend dependencies installed"
82
+
83
+ # ── Create launcher (must-b.cmd) ──────────────────────────────────────────
84
+ Header "Creating launcher..."
85
+ $binDir = Join-Path $env:USERPROFILE "AppData\Local\Programs\must-b\bin"
86
+ $launcher = Join-Path $binDir "must-b.cmd"
87
+ New-Item -ItemType Directory -Force -Path $binDir | Out-Null
88
+
89
+ @"
90
+ @echo off
91
+ cd /d "$INSTALL_DIR"
92
+ node --experimental-specifier-resolution=node dist/index.js %*
93
+ "@ | Set-Content $launcher -Encoding ASCII
94
+
95
+ # Add to user PATH if not already present
96
+ $userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
97
+ if ($userPath -notlike "*$binDir*") {
98
+ [Environment]::SetEnvironmentVariable("PATH", "$userPath;$binDir", "User")
99
+ Write-Host " Added $binDir to user PATH"
100
+ }
101
+ Ok "Launcher created at $launcher"
102
+
103
+ # ── Done ─────────────────────────────────────────────────────────────────
104
+ Set-Location $INSTALL_DIR
105
+ Write-Host ""
106
+ Write-Host " Must-b installed successfully!" -ForegroundColor Green
107
+ Write-Host ""
108
+ Write-Host " Start the gateway: must-b"
109
+ Write-Host " Open in browser: http://localhost:4309"
110
+ Write-Host " Run setup wizard: must-b onboard"
111
+ Write-Host ""
112
+ Write-Host " Tip: restart your terminal for the PATH change to take effect."
113
+ Write-Host ""
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # ────────────────────────────────────────────────────────────────────────────
3
+ # Must-b Installer — Linux / macOS
4
+ # Usage: curl -fsSL https://must-b.com/install.sh | bash
5
+ # — or —
6
+ # bash scripts/install.sh
7
+ # ────────────────────────────────────────────────────────────────────────────
8
+ set -euo pipefail
9
+
10
+ REPO="https://github.com/aytac43-0/Must-b"
11
+ INSTALL_DIR="$HOME/.must-b"
12
+ NODE_MIN_MAJOR=20
13
+ BOLD="\033[1m"
14
+ ORANGE="\033[38;5;208m"
15
+ GREEN="\033[0;32m"
16
+ RED="\033[0;31m"
17
+ RESET="\033[0m"
18
+
19
+ header() { echo -e "\n${ORANGE}${BOLD}▸ $*${RESET}"; }
20
+ ok() { echo -e " ${GREEN}✓${RESET} $*"; }
21
+ fail() { echo -e " ${RED}✗ $*${RESET}"; exit 1; }
22
+
23
+ echo -e "\n${ORANGE}${BOLD}"
24
+ cat <<'BANNER'
25
+ __ __ _ _
26
+ | \/ |_ _ ___| |_ | |__
27
+ | |\/| | | | / __| __| __ | '_ \
28
+ | | | | |_| \__ \ |_ / \ | |_) |
29
+ |_| |_|\__,_|___/\__| \__/ |_.__/
30
+ Autonomous AI Platform
31
+ BANNER
32
+ echo -e "${RESET}"
33
+
34
+ # ── Prerequisites ─────────────────────────────────────────────────────────
35
+ header "Checking prerequisites…"
36
+
37
+ # Node.js
38
+ if ! command -v node &>/dev/null; then
39
+ fail "Node.js not found. Install Node.js ${NODE_MIN_MAJOR}+ from https://nodejs.org and re-run."
40
+ fi
41
+ NODE_MAJOR=$(node -e "process.stdout.write(process.version.slice(1).split('.')[0])")
42
+ if [ "$NODE_MAJOR" -lt "$NODE_MIN_MAJOR" ]; then
43
+ fail "Node.js ${NODE_MIN_MAJOR}+ required (found v${NODE_MAJOR}). Please upgrade."
44
+ fi
45
+ ok "Node.js $(node --version)"
46
+
47
+ # Git
48
+ if ! command -v git &>/dev/null; then
49
+ fail "git not found. Please install git and re-run."
50
+ fi
51
+ ok "git $(git --version | awk '{print $3}')"
52
+
53
+ # npm
54
+ if ! command -v npm &>/dev/null; then
55
+ fail "npm not found."
56
+ fi
57
+ ok "npm $(npm --version)"
58
+
59
+ # ── Clone / update ────────────────────────────────────────────────────────
60
+ header "Installing Must-b to ${INSTALL_DIR}…"
61
+
62
+ if [ -d "$INSTALL_DIR/.git" ]; then
63
+ echo " Existing installation found — pulling latest…"
64
+ git -C "$INSTALL_DIR" pull --ff-only
65
+ else
66
+ git clone --depth 1 "$REPO" "$INSTALL_DIR"
67
+ fi
68
+ ok "Repository ready"
69
+
70
+ # ── Install dependencies ──────────────────────────────────────────────────
71
+ header "Installing backend dependencies…"
72
+ cd "$INSTALL_DIR"
73
+ npm install --prefer-offline --omit=dev
74
+ ok "Backend dependencies installed"
75
+
76
+ header "Installing frontend dependencies…"
77
+ cd "$INSTALL_DIR/public/must-b-ui"
78
+ npm install --prefer-offline --omit=dev
79
+ ok "Frontend dependencies installed"
80
+
81
+ # ── Create launcher ───────────────────────────────────────────────────────
82
+ header "Creating launcher…"
83
+ LAUNCHER="$HOME/.local/bin/must-b"
84
+ mkdir -p "$(dirname "$LAUNCHER")"
85
+
86
+ cat > "$LAUNCHER" <<EOF
87
+ #!/usr/bin/env bash
88
+ cd "$INSTALL_DIR"
89
+ exec node --experimental-specifier-resolution=node dist/index.js "\$@"
90
+ EOF
91
+ chmod +x "$LAUNCHER"
92
+
93
+ # Add ~/.local/bin to PATH hint
94
+ SHELL_RC=""
95
+ case "$SHELL" in
96
+ */zsh) SHELL_RC="$HOME/.zshrc" ;;
97
+ */bash) SHELL_RC="$HOME/.bashrc" ;;
98
+ esac
99
+ if [ -n "$SHELL_RC" ] && ! grep -q '.local/bin' "$SHELL_RC" 2>/dev/null; then
100
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_RC"
101
+ echo " Added ~/.local/bin to PATH in ${SHELL_RC}"
102
+ fi
103
+ ok "Launcher created at ${LAUNCHER}"
104
+
105
+ # ── Done ─────────────────────────────────────────────────────────────────
106
+ echo -e "\n${GREEN}${BOLD}Must-b installed successfully!${RESET}\n"
107
+ echo " Start the gateway: must-b"
108
+ echo " Open in browser: http://localhost:4309"
109
+ echo " Run setup wizard: must-b onboard"
110
+ echo ""
111
+ echo " Tip: restart your terminal (or run: source ${SHELL_RC:-~/.bashrc})"
112
+ echo ""
@@ -0,0 +1,92 @@
1
+ /**
2
+ * paparazzi.mjs — Must-b UI Screenshot Utility
3
+ *
4
+ * Usage: node scripts/paparazzi.mjs
5
+ *
6
+ * Requires Must-b to be running on localhost:4309.
7
+ * Saves screenshots to screenshots/<route_name>.png (git + npm ignored).
8
+ */
9
+
10
+ import { chromium } from 'playwright';
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const ROOT = path.resolve(__dirname, '..');
17
+ const OUT_DIR = path.join(ROOT, 'screenshots');
18
+ const BASE_URL = 'http://localhost:4309';
19
+
20
+ const ROUTES = [
21
+ { name: 'browser', path: '/app/browser' },
22
+ { name: 'automations', path: '/app/automations' },
23
+ { name: 'skills', path: '/app/skills' },
24
+ { name: 'plugins', path: '/app/plugins' },
25
+ { name: 'files', path: '/app/files' },
26
+ { name: 'memory', path: '/app/memory' },
27
+ { name: 'settings', path: '/app/settings' },
28
+ { name: 'keys', path: '/app/settings' }, // settings hosts API keys
29
+ ];
30
+
31
+ // ── Wait for server ──────────────────────────────────────────────────────────
32
+ async function waitForServer(timeoutMs = 20_000) {
33
+ const start = Date.now();
34
+ process.stdout.write(` Waiting for ${BASE_URL} `);
35
+ while (Date.now() - start < timeoutMs) {
36
+ try {
37
+ const r = await fetch(`${BASE_URL}/api/setup/status`, { signal: AbortSignal.timeout(1200) });
38
+ if (r.status < 500) { process.stdout.write(' ready!\n'); return; }
39
+ } catch { /* not up yet */ }
40
+ await new Promise(r => setTimeout(r, 400));
41
+ process.stdout.write('.');
42
+ }
43
+ process.stdout.write('\n');
44
+ throw new Error(`Server not responding after ${timeoutMs}ms — start Must-b first.`);
45
+ }
46
+
47
+ // ── Main ─────────────────────────────────────────────────────────────────────
48
+ async function main() {
49
+ console.log('\n Must-b Paparazzi\n ─────────────────────────');
50
+
51
+ await waitForServer();
52
+ fs.mkdirSync(OUT_DIR, { recursive: true });
53
+
54
+ const browser = await chromium.launch({ headless: true });
55
+ const context = await browser.newContext({
56
+ viewport: { width: 1440, height: 900 },
57
+ colorScheme: 'dark',
58
+ deviceScaleFactor: 1,
59
+ });
60
+ const page = await context.newPage();
61
+
62
+ // Dismiss any JS alerts automatically
63
+ page.on('dialog', d => d.dismiss().catch(() => {}));
64
+
65
+ let ok = 0, fail = 0;
66
+
67
+ for (const route of ROUTES) {
68
+ const url = BASE_URL + route.path;
69
+ const outFile = path.join(OUT_DIR, `${route.name}.png`);
70
+ process.stdout.write(` → ${(route.path).padEnd(24)}`);
71
+ try {
72
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 25_000 });
73
+ // Extra settle for animations and deferred fetches
74
+ await page.waitForTimeout(1800);
75
+ await page.screenshot({ path: outFile, fullPage: false });
76
+ process.stdout.write(` ✓ screenshots/${route.name}.png\n`);
77
+ ok++;
78
+ } catch (e) {
79
+ process.stdout.write(` ✗ ${String(e.message).split('\n')[0]}\n`);
80
+ fail++;
81
+ }
82
+ }
83
+
84
+ await browser.close();
85
+
86
+ console.log(`\n ─────────────────────────`);
87
+ console.log(` Done — ${ok} captured, ${fail} failed.`);
88
+ if (ok > 0) console.log(` Folder: ${OUT_DIR}`);
89
+ console.log();
90
+ }
91
+
92
+ main().catch(err => { console.error('\n Error:', err.message); process.exit(1); });