slackhive 0.1.5 → 0.1.7
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/dist/commands/init.js +115 -80
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -33,50 +33,53 @@ async function init(opts) {
|
|
|
33
33
|
console.log('');
|
|
34
34
|
console.log(chalk_1.default.bold(' SlackHive') + chalk_1.default.gray(' — AI agent teams on Slack'));
|
|
35
35
|
console.log('');
|
|
36
|
-
// ── Check prerequisites
|
|
36
|
+
// ── Step 1: Check prerequisites ───────────────────────────────────────────
|
|
37
|
+
console.log(chalk_1.default.bold.hex('#D97757')(' [1/4]') + chalk_1.default.bold(' Checking prerequisites'));
|
|
38
|
+
console.log('');
|
|
37
39
|
const checks = [
|
|
38
|
-
{ name: 'Docker', cmd: 'docker
|
|
39
|
-
{ name: 'Docker Compose', cmd: 'docker compose version' },
|
|
40
|
-
{ name: 'Git', cmd: 'git --version' },
|
|
40
|
+
{ name: 'Docker daemon', cmd: 'docker info', errMsg: 'Docker is not running. Please start Docker Desktop and try again.' },
|
|
41
|
+
{ name: 'Docker Compose', cmd: 'docker compose version', errMsg: 'Docker Compose not found. Please install Docker Desktop.' },
|
|
42
|
+
{ name: 'Git', cmd: 'git --version', errMsg: 'Git not found. Please install Git first.' },
|
|
41
43
|
];
|
|
42
44
|
for (const check of checks) {
|
|
45
|
+
const spinner = (0, ora_1.default)(` Checking ${check.name}...`).start();
|
|
43
46
|
try {
|
|
44
47
|
(0, child_process_1.execSync)(check.cmd, { stdio: 'ignore' });
|
|
45
|
-
|
|
48
|
+
spinner.succeed(chalk_1.default.green(`${check.name} ready`));
|
|
46
49
|
}
|
|
47
50
|
catch {
|
|
48
|
-
|
|
51
|
+
spinner.fail(chalk_1.default.red(`${check.name}: ${check.errMsg}`));
|
|
49
52
|
process.exit(1);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
console.log('');
|
|
53
|
-
// ── Clone
|
|
56
|
+
// ── Step 2: Clone ─────────────────────────────────────────────────────────
|
|
57
|
+
console.log(chalk_1.default.bold.hex('#D97757')(' [2/4]') + chalk_1.default.bold(' Getting SlackHive'));
|
|
58
|
+
console.log('');
|
|
54
59
|
if ((0, fs_1.existsSync)(dir)) {
|
|
55
|
-
console.log(chalk_1.default.yellow(` Directory ${opts.dir} already exists
|
|
60
|
+
console.log(chalk_1.default.yellow(` ⚡ Directory ${opts.dir} already exists — using existing`));
|
|
56
61
|
}
|
|
57
62
|
else {
|
|
58
|
-
const spinner = (0, ora_1.default)('Cloning
|
|
63
|
+
const spinner = (0, ora_1.default)(' Cloning repository...').start();
|
|
59
64
|
try {
|
|
60
65
|
(0, child_process_1.execSync)(`git clone ${REPO_URL} "${dir}"`, { stdio: 'ignore' });
|
|
61
66
|
spinner.succeed('Repository cloned');
|
|
62
67
|
}
|
|
63
|
-
catch
|
|
68
|
+
catch {
|
|
64
69
|
spinner.fail('Failed to clone repository');
|
|
65
70
|
process.exit(1);
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
|
-
|
|
73
|
+
console.log('');
|
|
74
|
+
// ── Step 3: Configure .env ────────────────────────────────────────────────
|
|
69
75
|
const envPath = (0, path_1.join)(dir, '.env');
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
if (!(0, fs_1.existsSync)(envPath)) {
|
|
77
|
+
console.log(chalk_1.default.bold.hex('#D97757')(' [3/4]') + chalk_1.default.bold(' Configure environment'));
|
|
72
78
|
console.log('');
|
|
73
|
-
console.log(chalk_1.default.bold(' Configure environment:'));
|
|
74
|
-
console.log('');
|
|
75
|
-
// Auth mode selection
|
|
76
79
|
const authMode = await (0, prompts_1.default)({
|
|
77
80
|
type: 'select',
|
|
78
81
|
name: 'mode',
|
|
79
|
-
message: '
|
|
82
|
+
message: 'Claude authentication',
|
|
80
83
|
choices: [
|
|
81
84
|
{ title: 'API Key — pay-per-use via Anthropic API', value: 'apikey' },
|
|
82
85
|
{ title: 'Subscription — run `claude login` first', value: 'subscription' },
|
|
@@ -96,14 +99,12 @@ async function init(opts) {
|
|
|
96
99
|
});
|
|
97
100
|
}
|
|
98
101
|
else {
|
|
99
|
-
// Check if ~/.claude exists
|
|
100
102
|
const claudeDir = (0, path_1.join)(process.env.HOME || '~', '.claude');
|
|
101
103
|
if (!(0, fs_1.existsSync)(claudeDir)) {
|
|
102
104
|
console.log(chalk_1.default.yellow('\n ⚠ ~/.claude not found. Run `claude login` first, then re-run `slackhive init`.'));
|
|
103
105
|
process.exit(1);
|
|
104
106
|
}
|
|
105
107
|
console.log(chalk_1.default.green(' ✓') + ' Found ~/.claude credentials');
|
|
106
|
-
// Find claude binary
|
|
107
108
|
let claudeBinDefault = '/usr/local/bin/claude';
|
|
108
109
|
try {
|
|
109
110
|
const found = (0, child_process_1.execSync)('which claude', { encoding: 'utf-8' }).trim();
|
|
@@ -118,37 +119,12 @@ async function init(opts) {
|
|
|
118
119
|
initial: claudeBinDefault,
|
|
119
120
|
});
|
|
120
121
|
}
|
|
121
|
-
questions.push({
|
|
122
|
-
type: 'text',
|
|
123
|
-
name: 'adminUsername',
|
|
124
|
-
message: 'Admin username',
|
|
125
|
-
initial: 'admin',
|
|
126
|
-
}, {
|
|
127
|
-
type: 'password',
|
|
128
|
-
name: 'adminPassword',
|
|
129
|
-
message: 'Admin password',
|
|
130
|
-
validate: (v) => v.length >= 6 ? true : 'At least 6 characters',
|
|
131
|
-
}, {
|
|
132
|
-
type: 'text',
|
|
133
|
-
name: 'postgresPassword',
|
|
134
|
-
message: 'Postgres password',
|
|
135
|
-
initial: randomSecret().slice(0, 16),
|
|
136
|
-
}, {
|
|
137
|
-
type: 'text',
|
|
138
|
-
name: 'redisPassword',
|
|
139
|
-
message: 'Redis password',
|
|
140
|
-
initial: randomSecret().slice(0, 16),
|
|
141
|
-
});
|
|
122
|
+
questions.push({ type: 'text', name: 'adminUsername', message: 'Admin username', initial: 'admin' }, { type: 'password', name: 'adminPassword', message: 'Admin password', validate: (v) => v.length >= 6 ? true : 'At least 6 characters' }, { type: 'text', name: 'postgresPassword', message: 'Postgres password', initial: randomSecret().slice(0, 16) }, { type: 'text', name: 'redisPassword', message: 'Redis password', initial: randomSecret().slice(0, 16) });
|
|
142
123
|
const response = await (0, prompts_1.default)(questions);
|
|
143
|
-
if (authMode.mode === 'apikey' && !response.anthropicKey) {
|
|
144
|
-
console.log(chalk_1.default.red('\n Setup cancelled.'));
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
124
|
if (!response.adminPassword) {
|
|
148
125
|
console.log(chalk_1.default.red('\n Setup cancelled.'));
|
|
149
126
|
process.exit(1);
|
|
150
127
|
}
|
|
151
|
-
// Build .env from scratch so there are no placeholder values
|
|
152
128
|
let envContent = '# Generated by slackhive init\n\n';
|
|
153
129
|
if (authMode.mode === 'apikey') {
|
|
154
130
|
envContent += `ANTHROPIC_API_KEY=${response.anthropicKey}\n`;
|
|
@@ -166,68 +142,127 @@ async function init(opts) {
|
|
|
166
142
|
envContent += `AUTH_SECRET=${randomSecret()}\n`;
|
|
167
143
|
envContent += `\nNODE_ENV=production\n`;
|
|
168
144
|
(0, fs_1.writeFileSync)(envPath, envContent);
|
|
169
|
-
console.log(
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log(chalk_1.default.green(' ✓') + ' .env file created');
|
|
147
|
+
console.log('');
|
|
170
148
|
}
|
|
171
|
-
else
|
|
172
|
-
console.log(chalk_1.default.
|
|
149
|
+
else {
|
|
150
|
+
console.log(chalk_1.default.bold.hex('#D97757')(' [3/4]') + chalk_1.default.bold(' Configure environment'));
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(chalk_1.default.yellow(' ⚡ .env already exists — skipping configuration'));
|
|
153
|
+
console.log('');
|
|
173
154
|
}
|
|
174
|
-
// ──
|
|
155
|
+
// ── Step 4: Build & start ─────────────────────────────────────────────────
|
|
175
156
|
if (!opts.skipStart) {
|
|
157
|
+
console.log(chalk_1.default.bold.hex('#D97757')(' [4/4]') + chalk_1.default.bold(' Building & starting services'));
|
|
158
|
+
console.log(chalk_1.default.gray(' This takes 3–5 minutes on first run while Docker builds images.'));
|
|
176
159
|
console.log('');
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
spinner.succeed('All services started');
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
spinner.fail('Failed to start services');
|
|
184
|
-
console.log(chalk_1.default.gray(' Try running manually: cd ' + opts.dir + ' && docker compose up -d --build'));
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}
|
|
187
|
-
// Wait for web to be ready
|
|
188
|
-
const webSpinner = (0, ora_1.default)('Waiting for web UI...').start();
|
|
160
|
+
await runDockerBuild(dir, opts.dir);
|
|
161
|
+
// Wait for web UI
|
|
162
|
+
const webSpinner = (0, ora_1.default)(' Waiting for web UI to be ready...').start();
|
|
189
163
|
let ready = false;
|
|
190
|
-
for (let i = 0; i <
|
|
164
|
+
for (let i = 0; i < 40; i++) {
|
|
191
165
|
try {
|
|
192
|
-
(0, child_process_1.execSync)('curl -
|
|
166
|
+
(0, child_process_1.execSync)('curl -sf http://localhost:3001/login', { stdio: 'ignore' });
|
|
193
167
|
ready = true;
|
|
194
168
|
break;
|
|
195
169
|
}
|
|
196
170
|
catch {
|
|
197
|
-
await sleep(
|
|
171
|
+
await sleep(3000);
|
|
198
172
|
}
|
|
199
173
|
}
|
|
200
174
|
if (ready) {
|
|
201
|
-
webSpinner.succeed('Web UI ready');
|
|
175
|
+
webSpinner.succeed('Web UI is ready');
|
|
202
176
|
}
|
|
203
177
|
else {
|
|
204
|
-
webSpinner.warn('Web UI may still be starting
|
|
178
|
+
webSpinner.warn('Web UI may still be starting up');
|
|
205
179
|
}
|
|
206
180
|
}
|
|
207
|
-
// ── Done
|
|
181
|
+
// ── Done ──────────────────────────────────────────────────────────────────
|
|
208
182
|
console.log('');
|
|
209
|
-
console.log(chalk_1.default.
|
|
183
|
+
console.log(' ' + chalk_1.default.bgHex('#D97757').black.bold(' SlackHive is ready! '));
|
|
210
184
|
console.log('');
|
|
211
|
-
console.log(` ${chalk_1.default.bold('
|
|
212
|
-
console.log(` ${chalk_1.default.bold('
|
|
213
|
-
console.log(` ${chalk_1.default.bold('Project dir:')} ${dir}`);
|
|
185
|
+
console.log(` ${chalk_1.default.bold('→ Open:')} ${chalk_1.default.cyan('http://localhost:3001')}`);
|
|
186
|
+
console.log(` ${chalk_1.default.bold('→ Dir:')} ${chalk_1.default.gray(dir)}`);
|
|
214
187
|
console.log('');
|
|
215
|
-
console.log(chalk_1.default.gray('
|
|
216
|
-
console.log(chalk_1.default.gray(' slackhive start Start services'));
|
|
217
|
-
console.log(chalk_1.default.gray(' slackhive stop Stop services'));
|
|
218
|
-
console.log(chalk_1.default.gray(' slackhive status Show container status'));
|
|
219
|
-
console.log(chalk_1.default.gray(' slackhive logs Tail runner logs'));
|
|
220
|
-
console.log(chalk_1.default.gray(' slackhive update Pull latest & rebuild'));
|
|
188
|
+
console.log(chalk_1.default.gray(' Useful commands:'));
|
|
189
|
+
console.log(chalk_1.default.gray(' slackhive start — Start services'));
|
|
190
|
+
console.log(chalk_1.default.gray(' slackhive stop — Stop services'));
|
|
191
|
+
console.log(chalk_1.default.gray(' slackhive status — Show container status'));
|
|
192
|
+
console.log(chalk_1.default.gray(' slackhive logs — Tail runner logs'));
|
|
193
|
+
console.log(chalk_1.default.gray(' slackhive update — Pull latest & rebuild'));
|
|
221
194
|
console.log('');
|
|
222
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Runs `docker compose up -d --build` with live streaming progress output.
|
|
198
|
+
* Shows each build step as it happens instead of a silent spinner.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} cwd - The project directory.
|
|
201
|
+
* @param {string} displayDir - Display name for error message.
|
|
202
|
+
* @returns {Promise<void>}
|
|
203
|
+
*/
|
|
204
|
+
function runDockerBuild(cwd, displayDir) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const proc = (0, child_process_1.spawn)('docker', ['compose', 'up', '-d', '--build'], {
|
|
207
|
+
cwd,
|
|
208
|
+
env: { ...process.env },
|
|
209
|
+
});
|
|
210
|
+
const stepPattern = /^#\d+ \[([^\]]+)\] (.+)/;
|
|
211
|
+
const donePattern = /^\s*(✔|Container .+ (Started|Running|Healthy)|Image .+ Built)/i;
|
|
212
|
+
let lastStep = '';
|
|
213
|
+
const processLine = (line) => {
|
|
214
|
+
const trimmed = line.trim();
|
|
215
|
+
if (!trimmed || trimmed.startsWith('#') && trimmed.includes('CACHED'))
|
|
216
|
+
return;
|
|
217
|
+
const stepMatch = stepPattern.exec(trimmed);
|
|
218
|
+
if (stepMatch) {
|
|
219
|
+
const label = ` ${chalk_1.default.gray('▸')} ${chalk_1.default.dim(stepMatch[1])} ${stepMatch[2]}`;
|
|
220
|
+
if (label !== lastStep) {
|
|
221
|
+
process.stdout.write('\r\x1b[K' + label.slice(0, process.stdout.columns - 2));
|
|
222
|
+
lastStep = label;
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (donePattern.test(trimmed)) {
|
|
227
|
+
process.stdout.write('\r\x1b[K');
|
|
228
|
+
console.log(' ' + chalk_1.default.green('✓') + ' ' + trimmed.replace(/^✔\s*/, '').replace(/Container /, ''));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
let stdoutBuf = '';
|
|
232
|
+
let stderrBuf = '';
|
|
233
|
+
proc.stdout.on('data', (chunk) => {
|
|
234
|
+
stdoutBuf += chunk.toString();
|
|
235
|
+
const lines = stdoutBuf.split('\n');
|
|
236
|
+
stdoutBuf = lines.pop() ?? '';
|
|
237
|
+
lines.forEach(processLine);
|
|
238
|
+
});
|
|
239
|
+
proc.stderr.on('data', (chunk) => {
|
|
240
|
+
stderrBuf += chunk.toString();
|
|
241
|
+
const lines = stderrBuf.split('\n');
|
|
242
|
+
stderrBuf = lines.pop() ?? '';
|
|
243
|
+
lines.forEach(processLine);
|
|
244
|
+
});
|
|
245
|
+
proc.on('close', (code) => {
|
|
246
|
+
process.stdout.write('\r\x1b[K');
|
|
247
|
+
if (code === 0) {
|
|
248
|
+
console.log(' ' + chalk_1.default.green('✓') + ' All services started');
|
|
249
|
+
resolve();
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log(' ' + chalk_1.default.red('✗') + ' Failed to start services');
|
|
253
|
+
console.log(chalk_1.default.gray(` Try manually: cd ${displayDir} && docker compose up -d --build`));
|
|
254
|
+
reject(new Error(`docker compose exited with code ${code}`));
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
223
259
|
function randomSecret() {
|
|
224
260
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
225
261
|
let result = '';
|
|
226
|
-
for (let i = 0; i < 32; i++)
|
|
262
|
+
for (let i = 0; i < 32; i++)
|
|
227
263
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
228
|
-
}
|
|
229
264
|
return result;
|
|
230
265
|
}
|
|
231
266
|
function sleep(ms) {
|
|
232
|
-
return new Promise(
|
|
267
|
+
return new Promise(r => setTimeout(r, ms));
|
|
233
268
|
}
|