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.
- package/README.md +47 -0
- package/index.js +206 -0
- 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
|
+
}
|