slackhive 0.1.6 → 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.
Files changed (2) hide show
  1. package/dist/commands/init.js +115 -85
  2. package/package.json +1 -1
@@ -33,55 +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 info' },
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
- console.log(chalk_1.default.green(' ✓') + ` ${check.name} found`);
48
+ spinner.succeed(chalk_1.default.green(`${check.name} ready`));
46
49
  }
47
50
  catch {
48
- if (check.name === 'Docker') {
49
- console.log(chalk_1.default.red(' ✗ Docker daemon is not running. Please start Docker Desktop and try again.'));
50
- }
51
- else {
52
- console.log(chalk_1.default.red(` ✗ ${check.name} not found. Please install it first.`));
53
- }
51
+ spinner.fail(chalk_1.default.red(`${check.name}: ${check.errMsg}`));
54
52
  process.exit(1);
55
53
  }
56
54
  }
57
55
  console.log('');
58
- // ── 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('');
59
59
  if ((0, fs_1.existsSync)(dir)) {
60
- console.log(chalk_1.default.yellow(` Directory ${opts.dir} already exists. Using existing.`));
60
+ console.log(chalk_1.default.yellow(` Directory ${opts.dir} already exists using existing`));
61
61
  }
62
62
  else {
63
- const spinner = (0, ora_1.default)('Cloning SlackHive repository...').start();
63
+ const spinner = (0, ora_1.default)(' Cloning repository...').start();
64
64
  try {
65
65
  (0, child_process_1.execSync)(`git clone ${REPO_URL} "${dir}"`, { stdio: 'ignore' });
66
66
  spinner.succeed('Repository cloned');
67
67
  }
68
- catch (e) {
68
+ catch {
69
69
  spinner.fail('Failed to clone repository');
70
70
  process.exit(1);
71
71
  }
72
72
  }
73
- // ── Configure .env ─────────────────────────────────────────────────────────
73
+ console.log('');
74
+ // ── Step 3: Configure .env ────────────────────────────────────────────────
74
75
  const envPath = (0, path_1.join)(dir, '.env');
75
- const envExamplePath = (0, path_1.join)(dir, '.env.example');
76
- if (!(0, fs_1.existsSync)(envPath) && (0, fs_1.existsSync)(envExamplePath)) {
76
+ if (!(0, fs_1.existsSync)(envPath)) {
77
+ console.log(chalk_1.default.bold.hex('#D97757')(' [3/4]') + chalk_1.default.bold(' Configure environment'));
77
78
  console.log('');
78
- console.log(chalk_1.default.bold(' Configure environment:'));
79
- console.log('');
80
- // Auth mode selection
81
79
  const authMode = await (0, prompts_1.default)({
82
80
  type: 'select',
83
81
  name: 'mode',
84
- message: 'How do you want to authenticate with Claude?',
82
+ message: 'Claude authentication',
85
83
  choices: [
86
84
  { title: 'API Key — pay-per-use via Anthropic API', value: 'apikey' },
87
85
  { title: 'Subscription — run `claude login` first', value: 'subscription' },
@@ -101,14 +99,12 @@ async function init(opts) {
101
99
  });
102
100
  }
103
101
  else {
104
- // Check if ~/.claude exists
105
102
  const claudeDir = (0, path_1.join)(process.env.HOME || '~', '.claude');
106
103
  if (!(0, fs_1.existsSync)(claudeDir)) {
107
104
  console.log(chalk_1.default.yellow('\n ⚠ ~/.claude not found. Run `claude login` first, then re-run `slackhive init`.'));
108
105
  process.exit(1);
109
106
  }
110
107
  console.log(chalk_1.default.green(' ✓') + ' Found ~/.claude credentials');
111
- // Find claude binary
112
108
  let claudeBinDefault = '/usr/local/bin/claude';
113
109
  try {
114
110
  const found = (0, child_process_1.execSync)('which claude', { encoding: 'utf-8' }).trim();
@@ -123,37 +119,12 @@ async function init(opts) {
123
119
  initial: claudeBinDefault,
124
120
  });
125
121
  }
126
- questions.push({
127
- type: 'text',
128
- name: 'adminUsername',
129
- message: 'Admin username',
130
- initial: 'admin',
131
- }, {
132
- type: 'password',
133
- name: 'adminPassword',
134
- message: 'Admin password',
135
- validate: (v) => v.length >= 6 ? true : 'At least 6 characters',
136
- }, {
137
- type: 'text',
138
- name: 'postgresPassword',
139
- message: 'Postgres password',
140
- initial: randomSecret().slice(0, 16),
141
- }, {
142
- type: 'text',
143
- name: 'redisPassword',
144
- message: 'Redis password',
145
- initial: randomSecret().slice(0, 16),
146
- });
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) });
147
123
  const response = await (0, prompts_1.default)(questions);
148
- if (authMode.mode === 'apikey' && !response.anthropicKey) {
149
- console.log(chalk_1.default.red('\n Setup cancelled.'));
150
- process.exit(1);
151
- }
152
124
  if (!response.adminPassword) {
153
125
  console.log(chalk_1.default.red('\n Setup cancelled.'));
154
126
  process.exit(1);
155
127
  }
156
- // Build .env from scratch so there are no placeholder values
157
128
  let envContent = '# Generated by slackhive init\n\n';
158
129
  if (authMode.mode === 'apikey') {
159
130
  envContent += `ANTHROPIC_API_KEY=${response.anthropicKey}\n`;
@@ -171,68 +142,127 @@ async function init(opts) {
171
142
  envContent += `AUTH_SECRET=${randomSecret()}\n`;
172
143
  envContent += `\nNODE_ENV=production\n`;
173
144
  (0, fs_1.writeFileSync)(envPath, envContent);
174
- console.log(chalk_1.default.green('\n ✓') + ' .env file created');
145
+ console.log('');
146
+ console.log(chalk_1.default.green(' ✓') + ' .env file created');
147
+ console.log('');
175
148
  }
176
- else if ((0, fs_1.existsSync)(envPath)) {
177
- console.log(chalk_1.default.yellow(' .env already exists, skipping configuration'));
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('');
178
154
  }
179
- // ── Start services ─────────────────────────────────────────────────────────
155
+ // ── Step 4: Build & start ─────────────────────────────────────────────────
180
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.'));
181
159
  console.log('');
182
- const spinner = (0, ora_1.default)('Starting SlackHive services (this may take a few minutes on first run)...').start();
183
- try {
184
- (0, child_process_1.execSync)('docker compose up -d --build', { cwd: dir, stdio: 'ignore', timeout: 600000 });
185
- spinner.succeed('All services started');
186
- }
187
- catch {
188
- spinner.fail('Failed to start services');
189
- console.log(chalk_1.default.gray(' Try running manually: cd ' + opts.dir + ' && docker compose up -d --build'));
190
- process.exit(1);
191
- }
192
- // Wait for web to be ready
193
- 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();
194
163
  let ready = false;
195
- for (let i = 0; i < 30; i++) {
164
+ for (let i = 0; i < 40; i++) {
196
165
  try {
197
- (0, child_process_1.execSync)('curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/login | grep -q 200', { stdio: 'ignore' });
166
+ (0, child_process_1.execSync)('curl -sf http://localhost:3001/login', { stdio: 'ignore' });
198
167
  ready = true;
199
168
  break;
200
169
  }
201
170
  catch {
202
- await sleep(2000);
171
+ await sleep(3000);
203
172
  }
204
173
  }
205
174
  if (ready) {
206
- webSpinner.succeed('Web UI ready');
175
+ webSpinner.succeed('Web UI is ready');
207
176
  }
208
177
  else {
209
- webSpinner.warn('Web UI may still be starting — check http://localhost:3001');
178
+ webSpinner.warn('Web UI may still be starting up');
210
179
  }
211
180
  }
212
- // ── Done ───────────────────────────────────────────────────────────────────
181
+ // ── Done ──────────────────────────────────────────────────────────────────
213
182
  console.log('');
214
- console.log(chalk_1.default.hex('#D97757').bold(' 🐝 SlackHive is ready!'));
183
+ console.log(' ' + chalk_1.default.bgHex('#D97757').black.bold(' SlackHive is ready! '));
215
184
  console.log('');
216
- console.log(` ${chalk_1.default.bold('Web UI:')} http://localhost:3001`);
217
- console.log(` ${chalk_1.default.bold('Login:')} http://localhost:3001/login`);
218
- 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)}`);
219
187
  console.log('');
220
- console.log(chalk_1.default.gray(' Commands:'));
221
- console.log(chalk_1.default.gray(' slackhive start Start services'));
222
- console.log(chalk_1.default.gray(' slackhive stop Stop services'));
223
- console.log(chalk_1.default.gray(' slackhive status Show container status'));
224
- console.log(chalk_1.default.gray(' slackhive logs Tail runner logs'));
225
- 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'));
226
194
  console.log('');
227
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
+ }
228
259
  function randomSecret() {
229
260
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
230
261
  let result = '';
231
- for (let i = 0; i < 32; i++) {
262
+ for (let i = 0; i < 32; i++)
232
263
  result += chars.charAt(Math.floor(Math.random() * chars.length));
233
- }
234
264
  return result;
235
265
  }
236
266
  function sleep(ms) {
237
- return new Promise(resolve => setTimeout(resolve, ms));
267
+ return new Promise(r => setTimeout(r, ms));
238
268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slackhive",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "CLI to install and manage SlackHive — AI agent teams on Slack",
5
5
  "bin": {
6
6
  "slackhive": "./dist/index.js"