smash-os-install 0.1.0

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.
Files changed (3) hide show
  1. package/README.md +47 -0
  2. package/index.js +206 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # smash-os-install
2
+
3
+ Install the [SmashOS](https://github.com/MrShifu01/SmashOS) Claude Code harness into any repository.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. A running SmashOS instance (self-hosted or deployed)
8
+ 2. Your repo registered in the SmashOS dashboard
9
+ 3. An API key generated from SmashOS → Settings → API Keys
10
+
11
+ ## Usage
12
+
13
+ Run inside your repo root:
14
+
15
+ ```bash
16
+ npx smash-os-install
17
+ ```
18
+
19
+ The installer will prompt for:
20
+ - **SmashOS URL** — where your SmashOS is deployed (e.g. `https://your-smash-os.vercel.app`)
21
+ - **Repo ID** — from the SmashOS dashboard URL: `/dashboard/repos/<ID>`
22
+ - **API Key** — from SmashOS → Settings → API Keys
23
+
24
+ ## What gets installed
25
+
26
+ ```
27
+ CLAUDE.md ← harness instructions, loaded every Claude session
28
+ .env.smash-os ← your credentials (gitignored automatically)
29
+ .claude/settings.json ← SessionStart + Stop hooks
30
+ .claude/hooks/smash-os-boot.mjs ← fetches live state from SmashOS on session start
31
+ .claude/hooks/smash-os-sync.mjs ← posts session summary to SmashOS on session end
32
+ .claude/skills/smash-os-status.md ← /smash-os:status command
33
+ .claude/skills/smash-os-run.md ← /smash-os:run command
34
+ .claude/skills/smash-os-memory.md ← /smash-os:memory command
35
+ .claude/skills/smash-os-audit.md ← /smash-os:audit command
36
+ .claude/skills/smash-os-role.md ← /smash-os:role command
37
+ .claude/skills/smash-os-sync.md ← /smash-os:sync command
38
+ ```
39
+
40
+ If `.claude/settings.json` already exists, the hooks are merged — your existing config is preserved.
41
+
42
+ ## After installation
43
+
44
+ 1. Open a Claude Code session in this directory
45
+ 2. The harness activates automatically on session start
46
+ 3. Use `/smash-os:run feature` to trigger a pipeline
47
+ 4. View results in the SmashOS dashboard
package/index.js ADDED
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * smash-os-install — Install the SmashOS Claude Code harness into any repo.
4
+ *
5
+ * Usage (inside your repo root):
6
+ * npx smash-os-install
7
+ *
8
+ * What it does:
9
+ * 1. Prompts for your SmashOS URL, Repo ID, and API key
10
+ * 2. Validates credentials against the SmashOS API
11
+ * 3. Fetches the generated harness files for your repo
12
+ * 4. Writes CLAUDE.md, .claude/hooks/, .claude/skills/, .env.smash-os
13
+ * 5. Merges hooks into existing .claude/settings.json (if present)
14
+ * 6. Adds .env.smash-os to .gitignore
15
+ */
16
+
17
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
18
+ import { join, dirname } from 'path';
19
+ import prompts from 'prompts';
20
+ import chalk from 'chalk';
21
+
22
+ const cwd = process.cwd();
23
+
24
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
25
+
26
+ function writeFile(relPath, content) {
27
+ const abs = join(cwd, relPath);
28
+ mkdirSync(dirname(abs), { recursive: true });
29
+ writeFileSync(abs, content, 'utf8');
30
+ }
31
+
32
+ function mergeSettingsJson(newSettingsContent) {
33
+ const settingsPath = join(cwd, '.claude', 'settings.json');
34
+
35
+ if (!existsSync(settingsPath)) {
36
+ writeFile('.claude/settings.json', newSettingsContent);
37
+ return false; // not merged, freshly created
38
+ }
39
+
40
+ let existing;
41
+ try {
42
+ existing = JSON.parse(readFileSync(settingsPath, 'utf8'));
43
+ } catch {
44
+ // Unreadable — overwrite
45
+ writeFile('.claude/settings.json', newSettingsContent);
46
+ return false;
47
+ }
48
+
49
+ const incoming = JSON.parse(newSettingsContent);
50
+ if (!existing.hooks) existing.hooks = {};
51
+
52
+ for (const [event, hookList] of Object.entries(incoming.hooks ?? {})) {
53
+ if (!existing.hooks[event]) existing.hooks[event] = [];
54
+ for (const hookGroup of hookList) {
55
+ for (const hook of hookGroup.hooks ?? []) {
56
+ const alreadyPresent = existing.hooks[event].some((g) =>
57
+ g.hooks?.some((h) => h.command === hook.command)
58
+ );
59
+ if (!alreadyPresent) {
60
+ existing.hooks[event].push({ hooks: [hook] });
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ writeFile('.claude/settings.json', JSON.stringify(existing, null, 2));
67
+ return true; // merged into existing
68
+ }
69
+
70
+ function ensureGitignore(entry) {
71
+ const gitignorePath = join(cwd, '.gitignore');
72
+ if (existsSync(gitignorePath)) {
73
+ const content = readFileSync(gitignorePath, 'utf8');
74
+ if (content.includes(entry)) return;
75
+ writeFileSync(gitignorePath, content.trimEnd() + '\n' + entry + '\n', 'utf8');
76
+ } else {
77
+ writeFileSync(gitignorePath, entry + '\n', 'utf8');
78
+ }
79
+ }
80
+
81
+ // ─── Main ─────────────────────────────────────────────────────────────────────
82
+
83
+ console.log('');
84
+ console.log(chalk.bold(' SmashOS Harness Installer'));
85
+ console.log(chalk.dim(' Installs the Claude Code harness into your repo'));
86
+ console.log('');
87
+
88
+ const answers = await prompts(
89
+ [
90
+ {
91
+ type: 'text',
92
+ name: 'apiUrl',
93
+ message: 'SmashOS URL (where your SmashOS is deployed)',
94
+ initial: 'http://localhost:5173',
95
+ validate: (v) => (v.startsWith('http') ? true : 'Must be a valid URL'),
96
+ },
97
+ {
98
+ type: 'text',
99
+ name: 'repoId',
100
+ message: 'Repo ID (from the SmashOS dashboard URL: /dashboard/repos/<ID>)',
101
+ validate: (v) => (v.trim().length > 0 ? true : 'Required'),
102
+ },
103
+ {
104
+ type: 'password',
105
+ name: 'apiKey',
106
+ message: 'API Key (from SmashOS Settings → API Keys)',
107
+ validate: (v) => (v.trim().length > 0 ? true : 'Required'),
108
+ },
109
+ ],
110
+ {
111
+ onCancel: () => {
112
+ console.log(chalk.yellow('\n Cancelled.'));
113
+ process.exit(0);
114
+ },
115
+ }
116
+ );
117
+
118
+ const { apiUrl, repoId, apiKey } = answers;
119
+ const cleanUrl = apiUrl.replace(/\/$/, '');
120
+
121
+ // ─── Validate & fetch ─────────────────────────────────────────────────────────
122
+
123
+ process.stdout.write('\n ' + chalk.dim('Connecting to SmashOS…'));
124
+
125
+ let data;
126
+ try {
127
+ const resp = await fetch(`${cleanUrl}/api/repos/${repoId}/layer2-files`, {
128
+ headers: { Authorization: `Bearer ${apiKey}` },
129
+ });
130
+
131
+ if (resp.status === 401) {
132
+ console.log(chalk.red(' ✗'));
133
+ console.error(chalk.red('\n Invalid API key or Repo ID. Check SmashOS → Settings → API Keys.\n'));
134
+ process.exit(1);
135
+ }
136
+ if (resp.status === 404) {
137
+ console.log(chalk.red(' ✗'));
138
+ console.error(chalk.red('\n Repo not found. Check the Repo ID in your SmashOS dashboard URL.\n'));
139
+ process.exit(1);
140
+ }
141
+ if (!resp.ok) {
142
+ console.log(chalk.red(' ✗'));
143
+ console.error(chalk.red(`\n SmashOS returned ${resp.status}. Is the URL correct?\n`));
144
+ process.exit(1);
145
+ }
146
+
147
+ data = await resp.json();
148
+ } catch (err) {
149
+ console.log(chalk.red(' ✗'));
150
+ console.error(chalk.red(`\n Could not reach SmashOS at ${cleanUrl}\n ${err.message}\n`));
151
+ process.exit(1);
152
+ }
153
+
154
+ console.log(chalk.green(' ✓'));
155
+ console.log(' ' + chalk.dim(`Connected to SmashOS — repo: ${chalk.white(data.repo.name)}`));
156
+ console.log('');
157
+
158
+ // ─── Write files ──────────────────────────────────────────────────────────────
159
+
160
+ const { files } = data;
161
+ let written = 0;
162
+ let merged = false;
163
+
164
+ for (const [relPath, content] of Object.entries(files)) {
165
+ if (relPath === '.claude/settings.json') {
166
+ merged = mergeSettingsJson(content);
167
+ console.log(
168
+ ' ' + chalk.green('✓') + ' ' +
169
+ chalk.white('.claude/settings.json') +
170
+ chalk.dim(merged ? ' (merged with existing)' : ' (created)')
171
+ );
172
+ } else {
173
+ writeFile(relPath, content);
174
+ console.log(' ' + chalk.green('✓') + ' ' + chalk.white(relPath));
175
+ }
176
+ written++;
177
+ }
178
+
179
+ // ─── Write .env.smash-os (pre-filled) ────────────────────────────────────────
180
+
181
+ const envContent = [
182
+ '# SmashOS harness credentials — do not commit this file',
183
+ `SMASH_OS_API_URL=${cleanUrl}`,
184
+ `SMASH_OS_REPO_ID=${repoId}`,
185
+ `SMASH_OS_API_KEY=${apiKey}`,
186
+ ].join('\n') + '\n';
187
+
188
+ writeFile('.env.smash-os', envContent);
189
+ console.log(' ' + chalk.green('✓') + ' ' + chalk.white('.env.smash-os') + chalk.dim(' (pre-filled)'));
190
+
191
+ // ─── Gitignore ────────────────────────────────────────────────────────────────
192
+
193
+ ensureGitignore('.env.smash-os');
194
+ console.log(' ' + chalk.green('✓') + ' ' + chalk.dim('.env.smash-os added to .gitignore'));
195
+
196
+ // ─── Done ─────────────────────────────────────────────────────────────────────
197
+
198
+ console.log('');
199
+ console.log(chalk.bold.green(' SmashOS harness installed!') + chalk.dim(` (${written} files)`));
200
+ console.log('');
201
+ console.log(chalk.dim(' Next steps:'));
202
+ console.log(chalk.dim(' 1. Open a Claude Code session in this directory'));
203
+ console.log(chalk.dim(' 2. The harness activates automatically on session start'));
204
+ console.log(chalk.dim(` 3. Use ${chalk.white('/smash-os:run')} to trigger a pipeline`));
205
+ console.log(chalk.dim(` 4. View results at: ${chalk.white(cleanUrl + '/dashboard/repos/' + repoId)}`));
206
+ console.log('');
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "smash-os-install",
3
+ "version": "0.1.0",
4
+ "description": "Install the SmashOS Claude Code harness into any repository",
5
+ "type": "module",
6
+ "bin": {
7
+ "smash-os-install": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "README.md"
12
+ ],
13
+ "keywords": [
14
+ "smash-os",
15
+ "claude-code",
16
+ "ai-workflow",
17
+ "harness",
18
+ "install"
19
+ ],
20
+ "license": "MIT",
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "dependencies": {
25
+ "chalk": "^5.4.1",
26
+ "prompts": "^2.4.2"
27
+ }
28
+ }