slackhive 0.1.44 → 0.1.45

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/cli/dist/index.js CHANGED
@@ -6,9 +6,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
9
12
  var __commonJS = (cb, mod) => function __require() {
10
13
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
14
  };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
12
19
  var __copyProps = (to, from, except, desc) => {
13
20
  if (from && typeof from === "object" || typeof from === "function") {
14
21
  for (let key of __getOwnPropNames(from))
@@ -25,6 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
32
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
33
  mod
27
34
  ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
36
 
29
37
  // node_modules/commander/lib/error.js
30
38
  var require_error = __commonJS({
@@ -15199,12 +15207,365 @@ var require_prompts3 = __commonJS({
15199
15207
  }
15200
15208
  });
15201
15209
 
15210
+ // src/commands/manage.ts
15211
+ var manage_exports = {};
15212
+ __export(manage_exports, {
15213
+ logs: () => logs,
15214
+ start: () => start,
15215
+ status: () => status,
15216
+ stop: () => stop,
15217
+ update: () => update
15218
+ });
15219
+ function getConfigPath() {
15220
+ return (0, import_path.join)(getSlackhiveDir(), "config.json");
15221
+ }
15222
+ function saveProjectDir(dir) {
15223
+ const configPath = getConfigPath();
15224
+ (0, import_fs.mkdirSync)((0, import_path.join)(configPath, ".."), { recursive: true });
15225
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify({ projectDir: dir }));
15226
+ }
15227
+ function findProjectDir() {
15228
+ const isSlackHiveDir = (d) => (0, import_fs.existsSync)((0, import_path.join)(d, "package.json")) && (0, import_fs.existsSync)((0, import_path.join)(d, "apps", "runner"));
15229
+ if (isSlackHiveDir(process.cwd())) {
15230
+ const dir = process.cwd();
15231
+ saveProjectDir(dir);
15232
+ return dir;
15233
+ }
15234
+ const sub = (0, import_path.join)(process.cwd(), "slackhive");
15235
+ if (isSlackHiveDir(sub)) {
15236
+ saveProjectDir(sub);
15237
+ return sub;
15238
+ }
15239
+ try {
15240
+ const config = JSON.parse((0, import_fs.readFileSync)(getConfigPath(), "utf-8"));
15241
+ if (config.projectDir && (0, import_fs.existsSync)((0, import_path.join)(config.projectDir, "apps", "runner"))) {
15242
+ return config.projectDir;
15243
+ }
15244
+ } catch {
15245
+ }
15246
+ console.log(import_chalk.default.red(" Could not find SlackHive project."));
15247
+ console.log(import_chalk.default.gray(" Run this command from the SlackHive directory, or run `slackhive init` first."));
15248
+ process.exit(1);
15249
+ }
15250
+ function getSlackhiveDir() {
15251
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
15252
+ return (0, import_path.join)(home, ".slackhive");
15253
+ }
15254
+ function getPidFile() {
15255
+ return (0, import_path.join)(getSlackhiveDir(), "slackhive.pid");
15256
+ }
15257
+ function writePid(pid, webPort = 3001, internalPort = 3002) {
15258
+ const dir = getSlackhiveDir();
15259
+ (0, import_fs.mkdirSync)(dir, { recursive: true });
15260
+ (0, import_fs.writeFileSync)(getPidFile(), JSON.stringify({ pid, webPort, internalPort }));
15261
+ }
15262
+ function readPidInfo() {
15263
+ try {
15264
+ const raw = (0, import_fs.readFileSync)(getPidFile(), "utf-8").trim();
15265
+ let info;
15266
+ if (raw.startsWith("{")) {
15267
+ info = JSON.parse(raw);
15268
+ } else {
15269
+ info = { pid: parseInt(raw, 10), webPort: 3001, internalPort: 3002 };
15270
+ }
15271
+ process.kill(info.pid, 0);
15272
+ return info;
15273
+ } catch {
15274
+ try {
15275
+ (0, import_fs.unlinkSync)(getPidFile());
15276
+ } catch {
15277
+ }
15278
+ return null;
15279
+ }
15280
+ }
15281
+ function nativeStart(dir) {
15282
+ const existing = readPidInfo();
15283
+ if (existing) {
15284
+ const stopSpinner = (0, import_ora.default)("Stopping existing instance...").start();
15285
+ try {
15286
+ process.kill(-existing.pid, "SIGTERM");
15287
+ } catch {
15288
+ }
15289
+ try {
15290
+ process.kill(existing.pid, "SIGTERM");
15291
+ } catch {
15292
+ }
15293
+ for (const port of [String(existing.webPort), String(existing.internalPort)]) {
15294
+ try {
15295
+ (0, import_child_process.execSync)(`lsof -ti:${port} | xargs kill -9`, { stdio: "ignore" });
15296
+ } catch {
15297
+ }
15298
+ }
15299
+ try {
15300
+ (0, import_fs.unlinkSync)(getPidFile());
15301
+ } catch {
15302
+ }
15303
+ stopSpinner.succeed("Stopped existing instance");
15304
+ }
15305
+ const spinner = (0, import_ora.default)("Starting SlackHive...").start();
15306
+ try {
15307
+ const standaloneTs = (0, import_path.join)(dir, "apps", "runner", "src", "standalone.ts");
15308
+ const tsxBin = (0, import_path.join)(dir, "node_modules", ".bin", "tsx");
15309
+ if (!(0, import_fs.existsSync)(standaloneTs)) {
15310
+ spinner.fail("standalone.ts not found \u2014 is this a valid SlackHive repo?");
15311
+ return;
15312
+ }
15313
+ if (!(0, import_fs.existsSync)(tsxBin)) {
15314
+ spinner.text = "Installing dependencies...";
15315
+ (0, import_child_process.execSync)("npm install", { cwd: dir, stdio: "ignore", timeout: 3e5 });
15316
+ }
15317
+ const env = {
15318
+ PATH: process.env.PATH ?? "",
15319
+ HOME: process.env.HOME ?? "",
15320
+ USER: process.env.USER ?? "",
15321
+ SHELL: process.env.SHELL ?? "",
15322
+ TERM: process.env.TERM ?? "",
15323
+ DATABASE_TYPE: "sqlite",
15324
+ NODE_ENV: "production"
15325
+ };
15326
+ const envFile = (0, import_path.join)(dir, ".env");
15327
+ if ((0, import_fs.existsSync)(envFile)) {
15328
+ const envContent = (0, import_fs.readFileSync)(envFile, "utf-8");
15329
+ for (const line of envContent.split("\n")) {
15330
+ const trimmed = line.trim();
15331
+ if (!trimmed || trimmed.startsWith("#")) continue;
15332
+ const eqIdx = trimmed.indexOf("=");
15333
+ if (eqIdx > 0) {
15334
+ const key = trimmed.slice(0, eqIdx);
15335
+ const val = trimmed.slice(eqIdx + 1);
15336
+ if (key === "CLAUDE_CODE_OAUTH_TOKEN" || key === "CLAUDE_CODE_OAUTH_REFRESH_TOKEN") continue;
15337
+ env[key] = val;
15338
+ }
15339
+ }
15340
+ }
15341
+ env.DATABASE_TYPE = "sqlite";
15342
+ const isPortFree = (port) => {
15343
+ try {
15344
+ (0, import_child_process.execSync)(`lsof -ti:${port}`, { stdio: ["pipe", "pipe", "pipe"] });
15345
+ return false;
15346
+ } catch {
15347
+ return true;
15348
+ }
15349
+ };
15350
+ let webPort = 3001;
15351
+ while (!isPortFree(webPort) && webPort < 3100) webPort++;
15352
+ if (!isPortFree(webPort)) {
15353
+ spinner.fail("No free port in range 3001\u20133099 for web UI");
15354
+ return;
15355
+ }
15356
+ let internalPort = 3002;
15357
+ while ((!isPortFree(internalPort) || internalPort === webPort) && internalPort < 3100) internalPort++;
15358
+ if (!isPortFree(internalPort) || internalPort === webPort) {
15359
+ spinner.fail("No free port in range 3002\u20133099 for internal runner");
15360
+ return;
15361
+ }
15362
+ env.PORT = String(webPort);
15363
+ env.RUNNER_INTERNAL_PORT = String(internalPort);
15364
+ if (webPort !== 3001) console.log(import_chalk.default.yellow(` Port 3001 in use, using ${webPort}`));
15365
+ spinner.text = "Starting...";
15366
+ const logDir = (0, import_path.join)(process.env.HOME ?? "/tmp", ".slackhive", "logs");
15367
+ (0, import_fs.mkdirSync)(logDir, { recursive: true });
15368
+ const out = (0, import_fs.openSync)((0, import_path.join)(logDir, "native-stdout.log"), "a");
15369
+ const err = (0, import_fs.openSync)((0, import_path.join)(logDir, "native-stderr.log"), "a");
15370
+ const child = (0, import_child_process.spawn)(tsxBin, [standaloneTs], {
15371
+ cwd: dir,
15372
+ env,
15373
+ detached: true,
15374
+ stdio: ["ignore", out, err]
15375
+ });
15376
+ child.unref();
15377
+ writePid(child.pid, webPort, internalPort);
15378
+ spinner.succeed(`SlackHive started (PID ${child.pid})`);
15379
+ console.log(import_chalk.default.gray(` Web UI: http://localhost:${webPort}`));
15380
+ console.log(import_chalk.default.gray(` Data: ${getSlackhiveDir()}/data.db`));
15381
+ console.log(import_chalk.default.gray(` Logs: ${getSlackhiveDir()}/logs/runner.log`));
15382
+ } catch (err) {
15383
+ spinner.fail(`Failed to start: ${err.message}`);
15384
+ }
15385
+ }
15386
+ function findOrphanRunnerPids(excludePid) {
15387
+ try {
15388
+ const out = (0, import_child_process.execSync)("ps -e -o pid,command", { encoding: "utf-8" });
15389
+ const pids = [];
15390
+ for (const line of out.split("\n")) {
15391
+ const m = line.trim().match(/^(\d+)\s+(.*)$/);
15392
+ if (!m) continue;
15393
+ const pid = Number(m[1]);
15394
+ const cmd = m[2];
15395
+ if (pid === process.pid) continue;
15396
+ if (excludePid !== null && pid === excludePid) continue;
15397
+ if (cmd.includes("apps/runner/dist/standalone.js") || /tsx.*apps\/runner\/src\/index\.ts/.test(cmd) || /tsx watch src\/index\.ts/.test(cmd)) {
15398
+ pids.push(pid);
15399
+ }
15400
+ }
15401
+ return pids;
15402
+ } catch {
15403
+ return [];
15404
+ }
15405
+ }
15406
+ function killOrphans(excludePid) {
15407
+ const pids = findOrphanRunnerPids(excludePid);
15408
+ if (pids.length === 0) return 0;
15409
+ for (const pid of pids) {
15410
+ try {
15411
+ process.kill(pid, "SIGTERM");
15412
+ } catch {
15413
+ }
15414
+ }
15415
+ const deadline = Date.now() + 2e3;
15416
+ while (Date.now() < deadline) {
15417
+ const alive = pids.filter((pid) => {
15418
+ try {
15419
+ process.kill(pid, 0);
15420
+ return true;
15421
+ } catch {
15422
+ return false;
15423
+ }
15424
+ });
15425
+ if (alive.length === 0) return pids.length;
15426
+ try {
15427
+ (0, import_child_process.execSync)("sleep 0.2");
15428
+ } catch {
15429
+ }
15430
+ }
15431
+ for (const pid of pids) {
15432
+ try {
15433
+ process.kill(pid, "SIGKILL");
15434
+ } catch {
15435
+ }
15436
+ }
15437
+ return pids.length;
15438
+ }
15439
+ function nativeStop() {
15440
+ const info = readPidInfo();
15441
+ const spinner = (0, import_ora.default)("Stopping SlackHive...").start();
15442
+ try {
15443
+ if (info) {
15444
+ try {
15445
+ process.kill(-info.pid, "SIGTERM");
15446
+ } catch {
15447
+ }
15448
+ try {
15449
+ process.kill(info.pid, "SIGTERM");
15450
+ } catch {
15451
+ }
15452
+ for (const port of [String(info.webPort), String(info.internalPort)]) {
15453
+ try {
15454
+ (0, import_child_process.execSync)(`lsof -ti:${port} | xargs kill -9`, { stdio: "ignore" });
15455
+ } catch {
15456
+ }
15457
+ }
15458
+ try {
15459
+ (0, import_fs.unlinkSync)(getPidFile());
15460
+ } catch {
15461
+ }
15462
+ }
15463
+ const killed = killOrphans(info?.pid ?? null);
15464
+ try {
15465
+ (0, import_fs.unlinkSync)((0, import_path.join)(getSlackhiveDir(), "runner.lock"));
15466
+ } catch {
15467
+ }
15468
+ if (!info && killed === 0) {
15469
+ spinner.info("SlackHive was not running");
15470
+ return;
15471
+ }
15472
+ const extra = killed > 0 ? ` (killed ${killed} orphaned runner process${killed === 1 ? "" : "es"})` : "";
15473
+ spinner.succeed(`SlackHive stopped${extra}`);
15474
+ } catch {
15475
+ spinner.fail("Failed to stop SlackHive");
15476
+ }
15477
+ }
15478
+ function nativeStatus() {
15479
+ const info = readPidInfo();
15480
+ console.log("");
15481
+ console.log(import_chalk.default.bold(" SlackHive Status"));
15482
+ console.log("");
15483
+ if (info) {
15484
+ console.log(import_chalk.default.green(` Status: Running (PID ${info.pid})`));
15485
+ console.log(import_chalk.default.gray(` Web UI: http://localhost:${info.webPort}`));
15486
+ console.log(import_chalk.default.gray(` Database: ${getSlackhiveDir()}/data.db`));
15487
+ console.log(import_chalk.default.gray(` Logs: ${getSlackhiveDir()}/logs/runner.log`));
15488
+ } else {
15489
+ console.log(import_chalk.default.red(" Status: Stopped"));
15490
+ console.log(import_chalk.default.gray(" Run `slackhive start` to start"));
15491
+ }
15492
+ console.log("");
15493
+ }
15494
+ function nativeLogs(follow) {
15495
+ const logDir = (0, import_path.join)(getSlackhiveDir(), "logs");
15496
+ const files = ["runner.log", "native-stdout.log", "native-stderr.log"].map((f) => (0, import_path.join)(logDir, f)).filter((f) => (0, import_fs.existsSync)(f));
15497
+ if (files.length === 0) {
15498
+ console.log(import_chalk.default.yellow(" No log files found. Is SlackHive running?"));
15499
+ return;
15500
+ }
15501
+ const args = follow ? ["-f", ...files] : ["-n", "200", ...files];
15502
+ (0, import_child_process.spawn)("tail", args, { stdio: "inherit" });
15503
+ }
15504
+ function nativeUpdate(dir) {
15505
+ const info = readPidInfo();
15506
+ if (info) {
15507
+ const stopSpinner = (0, import_ora.default)("Stopping SlackHive...").start();
15508
+ try {
15509
+ process.kill(info.pid, "SIGTERM");
15510
+ } catch {
15511
+ }
15512
+ try {
15513
+ (0, import_fs.unlinkSync)(getPidFile());
15514
+ } catch {
15515
+ }
15516
+ stopSpinner.succeed("Stopped");
15517
+ }
15518
+ const pullSpinner = (0, import_ora.default)("Pulling latest changes...").start();
15519
+ try {
15520
+ (0, import_child_process.execSync)("git pull", { cwd: dir, stdio: "ignore" });
15521
+ pullSpinner.succeed("Code updated");
15522
+ } catch {
15523
+ pullSpinner.fail("Failed to pull");
15524
+ return;
15525
+ }
15526
+ const buildSpinner = (0, import_ora.default)("Rebuilding...").start();
15527
+ try {
15528
+ (0, import_child_process.execSync)("npm install && npm run build", { cwd: dir, stdio: "ignore", timeout: 12e4 });
15529
+ buildSpinner.succeed("Rebuilt");
15530
+ } catch {
15531
+ buildSpinner.fail("Build failed");
15532
+ return;
15533
+ }
15534
+ nativeStart(dir);
15535
+ }
15536
+ async function start() {
15537
+ nativeStart(findProjectDir());
15538
+ }
15539
+ async function stop() {
15540
+ nativeStop();
15541
+ }
15542
+ async function status() {
15543
+ nativeStatus();
15544
+ }
15545
+ async function logs(opts) {
15546
+ nativeLogs(opts.follow !== false);
15547
+ }
15548
+ async function update() {
15549
+ nativeUpdate(findProjectDir());
15550
+ }
15551
+ var import_child_process, import_fs, import_path, import_chalk, import_ora;
15552
+ var init_manage = __esm({
15553
+ "src/commands/manage.ts"() {
15554
+ "use strict";
15555
+ import_child_process = require("child_process");
15556
+ import_fs = require("fs");
15557
+ import_path = require("path");
15558
+ import_chalk = __toESM(require_source());
15559
+ import_ora = __toESM(require_ora());
15560
+ }
15561
+ });
15562
+
15202
15563
  // package.json
15203
15564
  var require_package = __commonJS({
15204
15565
  "package.json"(exports2, module2) {
15205
15566
  module2.exports = {
15206
15567
  name: "slackhive",
15207
- version: "0.1.44",
15568
+ version: "0.1.45",
15208
15569
  description: "CLI to install and manage SlackHive \u2014 AI agent teams on Slack",
15209
15570
  bin: {
15210
15571
  slackhive: "./dist/index.js"
@@ -15268,11 +15629,11 @@ var {
15268
15629
  } = import_index.default;
15269
15630
 
15270
15631
  // src/commands/init.ts
15271
- var import_child_process = require("child_process");
15272
- var import_fs = require("fs");
15273
- var import_path = require("path");
15274
- var import_chalk = __toESM(require_source());
15275
- var import_ora = __toESM(require_ora());
15632
+ var import_child_process2 = require("child_process");
15633
+ var import_fs2 = require("fs");
15634
+ var import_path2 = require("path");
15635
+ var import_chalk2 = __toESM(require_source());
15636
+ var import_ora2 = __toESM(require_ora());
15276
15637
  var import_prompts = __toESM(require_prompts3());
15277
15638
  var REPO_URL = "https://github.com/pelago-labs/slackhive.git";
15278
15639
  function parseOAuthFromJson(json) {
@@ -15288,7 +15649,7 @@ function parseOAuthFromJson(json) {
15288
15649
  }
15289
15650
  function extractOAuthCredentials() {
15290
15651
  try {
15291
- const creds = (0, import_child_process.execSync)('security find-generic-password -s "Claude Code-credentials" -w', {
15652
+ const creds = (0, import_child_process2.execSync)('security find-generic-password -s "Claude Code-credentials" -w', {
15292
15653
  encoding: "utf-8",
15293
15654
  stdio: ["pipe", "pipe", "ignore"]
15294
15655
  }).trim();
@@ -15297,7 +15658,7 @@ function extractOAuthCredentials() {
15297
15658
  } catch {
15298
15659
  }
15299
15660
  try {
15300
- const creds = (0, import_child_process.execSync)('secret-tool lookup service "Claude Code-credentials"', {
15661
+ const creds = (0, import_child_process2.execSync)('secret-tool lookup service "Claude Code-credentials"', {
15301
15662
  encoding: "utf-8",
15302
15663
  stdio: ["pipe", "pipe", "ignore"]
15303
15664
  }).trim();
@@ -15306,9 +15667,9 @@ function extractOAuthCredentials() {
15306
15667
  } catch {
15307
15668
  }
15308
15669
  try {
15309
- const credPath = (0, import_path.join)(process.env.HOME || "~", ".claude", ".credentials.json");
15310
- if ((0, import_fs.existsSync)(credPath)) {
15311
- const creds = (0, import_fs.readFileSync)(credPath, "utf-8").trim();
15670
+ const credPath = (0, import_path2.join)(process.env.HOME || "~", ".claude", ".credentials.json");
15671
+ if ((0, import_fs2.existsSync)(credPath)) {
15672
+ const creds = (0, import_fs2.readFileSync)(credPath, "utf-8").trim();
15312
15673
  const result = parseOAuthFromJson(creds);
15313
15674
  if (result) return result;
15314
15675
  }
@@ -15316,44 +15677,54 @@ function extractOAuthCredentials() {
15316
15677
  }
15317
15678
  return null;
15318
15679
  }
15680
+ function syncCredentialsFile(creds) {
15681
+ const credPath = (0, import_path2.join)(process.env.HOME || "~", ".claude", ".credentials.json");
15682
+ const payload = JSON.stringify({
15683
+ claudeAiOauth: {
15684
+ accessToken: creds.accessToken,
15685
+ refreshToken: creds.refreshToken
15686
+ }
15687
+ }, null, 2);
15688
+ (0, import_fs2.writeFileSync)(credPath, payload, { mode: 384 });
15689
+ }
15319
15690
  async function init(opts) {
15320
- const dir = (0, import_path.resolve)(opts.dir);
15321
- const O = import_chalk.default.hex("#D97757").bold;
15322
- const W = import_chalk.default.hex("#EBE6E0").bold;
15691
+ const dir = (0, import_path2.resolve)(opts.dir);
15692
+ const O = import_chalk2.default.hex("#D97757").bold;
15693
+ const W = import_chalk2.default.hex("#EBE6E0").bold;
15323
15694
  console.log("");
15324
15695
  console.log(" " + W("\u2502 \u2502"));
15325
15696
  console.log(" " + W("\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u253C\u2500\u2500\u2500"));
15326
15697
  console.log(" " + O(">") + W(" \u2500\u2500\u253C\u2500\u2500") + O("\u2588") + W("\u253C\u2500\u2500"));
15327
15698
  console.log(" " + W("\u2502 \u2502"));
15328
15699
  console.log("");
15329
- console.log(import_chalk.default.bold(" SlackHive") + import_chalk.default.gray(" \u2014 AI agent teams on Slack"));
15700
+ console.log(import_chalk2.default.bold(" SlackHive") + import_chalk2.default.gray(" \u2014 AI agent teams on Slack"));
15330
15701
  console.log("");
15331
- console.log(import_chalk.default.bold.hex("#D97757")(" [1/4]") + import_chalk.default.bold(" Checking prerequisites"));
15702
+ console.log(import_chalk2.default.bold.hex("#D97757")(" [1/4]") + import_chalk2.default.bold(" Checking prerequisites"));
15332
15703
  console.log("");
15704
+ const needsClone = !(0, import_fs2.existsSync)(dir);
15333
15705
  const checks = [
15334
- { name: "Docker daemon", cmd: "docker info", errMsg: "Docker is not running. Please start Docker Desktop and try again." },
15335
- { name: "Docker Compose", cmd: "docker compose version", errMsg: "Docker Compose not found. Please install Docker Desktop." },
15336
- { name: "Git", cmd: "git --version", errMsg: "Git not found. Please install Git first." }
15706
+ { name: "Node.js", cmd: "node --version", errMsg: "Node.js not found. Please install Node.js 20+ first." },
15707
+ ...needsClone ? [{ name: "Git", cmd: "git --version", errMsg: "Git not found. Install Git or download SlackHive manually." }] : []
15337
15708
  ];
15338
15709
  for (const check of checks) {
15339
- const spinner = (0, import_ora.default)(` Checking ${check.name}...`).start();
15710
+ const spinner = (0, import_ora2.default)(` Checking ${check.name}...`).start();
15340
15711
  try {
15341
- (0, import_child_process.execSync)(check.cmd, { stdio: "ignore" });
15342
- spinner.succeed(import_chalk.default.green(`${check.name} ready`));
15712
+ (0, import_child_process2.execSync)(check.cmd, { stdio: "ignore" });
15713
+ spinner.succeed(import_chalk2.default.green(`${check.name} ready`));
15343
15714
  } catch {
15344
- spinner.fail(import_chalk.default.red(`${check.name}: ${check.errMsg}`));
15715
+ spinner.fail(import_chalk2.default.red(`${check.name}: ${check.errMsg}`));
15345
15716
  process.exit(1);
15346
15717
  }
15347
15718
  }
15348
15719
  console.log("");
15349
- console.log(import_chalk.default.bold.hex("#D97757")(" [2/4]") + import_chalk.default.bold(" Getting SlackHive"));
15720
+ console.log(import_chalk2.default.bold.hex("#D97757")(" [2/4]") + import_chalk2.default.bold(" Getting SlackHive"));
15350
15721
  console.log("");
15351
- if ((0, import_fs.existsSync)(dir)) {
15352
- console.log(import_chalk.default.yellow(` note: Directory ${opts.dir} already exists \u2014 using existing`));
15722
+ if ((0, import_fs2.existsSync)(dir)) {
15723
+ console.log(import_chalk2.default.yellow(` note: Directory ${opts.dir} already exists \u2014 using existing`));
15353
15724
  } else {
15354
- const spinner = (0, import_ora.default)(" Cloning repository...").start();
15725
+ const spinner = (0, import_ora2.default)(" Cloning repository...").start();
15355
15726
  try {
15356
- (0, import_child_process.execSync)(`git clone --depth 1 ${REPO_URL} "${dir}"`, { stdio: "ignore" });
15727
+ (0, import_child_process2.execSync)(`git clone --depth 1 ${REPO_URL} "${dir}"`, { stdio: "ignore" });
15357
15728
  spinner.succeed("Repository cloned");
15358
15729
  } catch {
15359
15730
  spinner.fail("Failed to clone repository");
@@ -15361,10 +15732,10 @@ async function init(opts) {
15361
15732
  }
15362
15733
  }
15363
15734
  console.log("");
15364
- const envPath = (0, import_path.join)(dir, ".env");
15365
- const freshEnv = !(0, import_fs.existsSync)(envPath);
15735
+ const envPath = (0, import_path2.join)(dir, ".env");
15736
+ const freshEnv = !(0, import_fs2.existsSync)(envPath);
15366
15737
  if (freshEnv) {
15367
- console.log(import_chalk.default.bold.hex("#D97757")(" [3/4]") + import_chalk.default.bold(" Configure environment"));
15738
+ console.log(import_chalk2.default.bold.hex("#D97757")(" [3/4]") + import_chalk2.default.bold(" Configure environment"));
15368
15739
  console.log("");
15369
15740
  const authMode = await (0, import_prompts.default)({
15370
15741
  type: "select",
@@ -15376,7 +15747,7 @@ async function init(opts) {
15376
15747
  ]
15377
15748
  });
15378
15749
  if (!authMode.mode) {
15379
- console.log(import_chalk.default.red("\n Setup cancelled."));
15750
+ console.log(import_chalk2.default.red("\n Setup cancelled."));
15380
15751
  process.exit(1);
15381
15752
  }
15382
15753
  const questions = [];
@@ -15389,42 +15760,42 @@ async function init(opts) {
15389
15760
  validate: (v) => v.startsWith("sk-") ? true : "Must start with sk-"
15390
15761
  });
15391
15762
  } else {
15392
- const claudeDir = (0, import_path.join)(process.env.HOME || "~", ".claude");
15393
- if (!(0, import_fs.existsSync)(claudeDir)) {
15394
- console.log(import_chalk.default.yellow("\n warning: ~/.claude not found. Run `claude login` first, then re-run `slackhive init`."));
15763
+ const claudeDir = (0, import_path2.join)(process.env.HOME || "~", ".claude");
15764
+ if (!(0, import_fs2.existsSync)(claudeDir)) {
15765
+ console.log(import_chalk2.default.yellow("\n warning: ~/.claude not found. Run `claude login` first, then re-run `slackhive init`."));
15395
15766
  process.exit(1);
15396
15767
  }
15397
- console.log(import_chalk.default.green(" \u2713") + " Found ~/.claude credentials");
15398
- const spinner = (0, import_ora.default)(" Extracting OAuth credentials...").start();
15768
+ console.log(import_chalk2.default.green(" \u2713") + " Found ~/.claude credentials");
15769
+ const spinner = (0, import_ora2.default)(" Extracting OAuth credentials...").start();
15399
15770
  oauthCreds = extractOAuthCredentials();
15400
15771
  if (oauthCreds) {
15401
15772
  spinner.succeed("OAuth credentials extracted");
15773
+ syncCredentialsFile(oauthCreds);
15402
15774
  } else {
15403
15775
  spinner.warn("Could not auto-extract credentials from keychain");
15404
- console.log(import_chalk.default.gray(" On Linux/headless servers, paste your OAuth token manually."));
15405
- console.log(import_chalk.default.gray(" Get it from a machine where you ran `claude login`:"));
15406
- console.log(import_chalk.default.gray(' security find-generic-password -s "Claude Code-credentials" -w'));
15776
+ console.log(import_chalk2.default.gray(" On Linux/headless servers, paste your OAuth token manually."));
15777
+ console.log(import_chalk2.default.gray(" Get it from a machine where you ran `claude login`:"));
15778
+ console.log(import_chalk2.default.gray(' security find-generic-password -s "Claude Code-credentials" -w'));
15407
15779
  console.log("");
15408
15780
  const tokenResponse = await (0, import_prompts.default)([
15409
15781
  { type: "password", name: "accessToken", message: "OAuth access token (sk-ant-oat01-...)", validate: (v) => v.startsWith("sk-ant-oat") ? true : "Must start with sk-ant-oat" },
15410
15782
  { type: "password", name: "refreshToken", message: "OAuth refresh token (sk-ant-ort01-...)", validate: (v) => v.startsWith("sk-ant-ort") ? true : "Must start with sk-ant-ort" }
15411
15783
  ]);
15412
15784
  if (!tokenResponse.accessToken || !tokenResponse.refreshToken) {
15413
- console.log(import_chalk.default.red("\n Setup cancelled. Use API Key mode instead on headless servers."));
15785
+ console.log(import_chalk2.default.red("\n Setup cancelled. Use API Key mode instead on headless servers."));
15414
15786
  process.exit(1);
15415
15787
  }
15416
15788
  oauthCreds = { accessToken: tokenResponse.accessToken, refreshToken: tokenResponse.refreshToken };
15789
+ syncCredentialsFile(oauthCreds);
15417
15790
  }
15418
15791
  }
15419
15792
  questions.push(
15420
15793
  { type: "text", name: "adminUsername", message: "Admin username", initial: "admin" },
15421
- { type: "password", name: "adminPassword", message: "Admin password", validate: (v) => v.length >= 6 ? true : "At least 6 characters" },
15422
- { type: "text", name: "postgresPassword", message: "Postgres password", initial: randomSecret().slice(0, 16) },
15423
- { type: "text", name: "redisPassword", message: "Redis password", initial: randomSecret().slice(0, 16) }
15794
+ { type: "password", name: "adminPassword", message: "Admin password", validate: (v) => v.length >= 6 ? true : "At least 6 characters" }
15424
15795
  );
15425
15796
  const response = await (0, import_prompts.default)(questions);
15426
15797
  if (!response.adminPassword) {
15427
- console.log(import_chalk.default.red("\n Setup cancelled."));
15798
+ console.log(import_chalk2.default.red("\n Setup cancelled."));
15428
15799
  process.exit(1);
15429
15800
  }
15430
15801
  let envContent = "# Generated by slackhive init\n\n";
@@ -15432,22 +15803,13 @@ async function init(opts) {
15432
15803
  envContent += `ANTHROPIC_API_KEY=${response.anthropicKey}
15433
15804
  `;
15434
15805
  } else {
15435
- envContent += `# Claude Code subscription \u2014 OAuth credentials from keychain
15436
- `;
15437
- envContent += `CLAUDE_CODE_OAUTH_TOKEN=${oauthCreds.accessToken}
15438
- `;
15439
- envContent += `CLAUDE_CODE_OAUTH_REFRESH_TOKEN=${oauthCreds.refreshToken}
15806
+ envContent += `# Claude Code subscription \u2014 auth handled by system (claude login)
15440
15807
  `;
15441
15808
  }
15442
15809
  envContent += `
15443
- POSTGRES_DB=slackhive
15810
+ # SQLite, no Docker required
15444
15811
  `;
15445
- envContent += `POSTGRES_USER=slackhive
15446
- `;
15447
- envContent += `POSTGRES_PASSWORD=${response.postgresPassword}
15448
- `;
15449
- envContent += `
15450
- REDIS_PASSWORD=${response.redisPassword}
15812
+ envContent += `DATABASE_TYPE=sqlite
15451
15813
  `;
15452
15814
  envContent += `
15453
15815
  ADMIN_USERNAME=${response.adminUsername}
@@ -15461,322 +15823,149 @@ ADMIN_USERNAME=${response.adminUsername}
15461
15823
  envContent += `
15462
15824
  NODE_ENV=production
15463
15825
  `;
15464
- (0, import_fs.writeFileSync)(envPath, envContent);
15826
+ (0, import_fs2.writeFileSync)(envPath, envContent);
15465
15827
  console.log("");
15466
- console.log(import_chalk.default.green(" \u2713") + " .env file created");
15828
+ console.log(import_chalk2.default.green(" \u2713") + " .env file created");
15467
15829
  console.log("");
15468
15830
  } else {
15469
- console.log(import_chalk.default.bold.hex("#D97757")(" [3/4]") + import_chalk.default.bold(" Configure environment"));
15831
+ console.log(import_chalk2.default.bold.hex("#D97757")(" [3/4]") + import_chalk2.default.bold(" Configure environment"));
15470
15832
  console.log("");
15471
- const envContents = (0, import_fs.existsSync)(envPath) ? require("fs").readFileSync(envPath, "utf-8") : "";
15833
+ let envContents = (0, import_fs2.existsSync)(envPath) ? (0, import_fs2.readFileSync)(envPath, "utf-8") : "";
15472
15834
  const missingKeys = [];
15473
- if (!envContents.includes("REDIS_PASSWORD=")) missingKeys.push("REDIS_PASSWORD");
15474
15835
  if (!envContents.includes("AUTH_SECRET=")) missingKeys.push("AUTH_SECRET");
15475
15836
  if (!envContents.includes("ENV_SECRET_KEY=")) missingKeys.push("ENV_SECRET_KEY");
15476
15837
  if (missingKeys.length > 0) {
15477
- console.log(import_chalk.default.yellow(` warning: .env is missing: ${missingKeys.join(", ")} \u2014 patching...`));
15838
+ console.log(import_chalk2.default.yellow(` warning: .env is missing: ${missingKeys.join(", ")} \u2014 patching...`));
15478
15839
  let patch = "";
15479
- if (!envContents.includes("REDIS_PASSWORD=")) patch += `
15480
- REDIS_PASSWORD=${randomSecret().slice(0, 16)}
15481
- `;
15482
15840
  if (!envContents.includes("AUTH_SECRET=")) patch += `AUTH_SECRET=${randomSecret()}
15483
15841
  `;
15484
15842
  if (!envContents.includes("ENV_SECRET_KEY=")) patch += `ENV_SECRET_KEY=${randomSecret()}
15485
15843
  `;
15486
15844
  require("fs").appendFileSync(envPath, patch);
15487
- console.log(import_chalk.default.green(" \u2713") + " .env patched");
15845
+ envContents = (0, import_fs2.readFileSync)(envPath, "utf-8");
15846
+ console.log(import_chalk2.default.green(" \u2713") + " .env patched");
15847
+ }
15848
+ if (envContents.includes("CLAUDE_CODE_OAUTH_TOKEN=")) {
15849
+ const spinner = (0, import_ora2.default)(" Syncing OAuth credentials...").start();
15850
+ const freshCreds = extractOAuthCredentials();
15851
+ if (freshCreds) {
15852
+ envContents = envContents.replace(/CLAUDE_CODE_OAUTH_TOKEN=.*/, `CLAUDE_CODE_OAUTH_TOKEN=${freshCreds.accessToken}`).replace(/CLAUDE_CODE_OAUTH_REFRESH_TOKEN=.*/, `CLAUDE_CODE_OAUTH_REFRESH_TOKEN=${freshCreds.refreshToken}`);
15853
+ (0, import_fs2.writeFileSync)(envPath, envContents);
15854
+ syncCredentialsFile(freshCreds);
15855
+ spinner.succeed("OAuth credentials synced");
15856
+ } else {
15857
+ spinner.warn("Could not extract credentials \u2014 run `claude login` if agents fail to authenticate");
15858
+ }
15488
15859
  } else {
15489
- console.log(import_chalk.default.yellow(" note: .env already exists \u2014 skipping configuration"));
15860
+ console.log(import_chalk2.default.yellow(" note: .env already exists \u2014 skipping configuration"));
15490
15861
  }
15491
15862
  console.log("");
15492
15863
  }
15493
15864
  let webReady = true;
15494
15865
  if (!opts.skipStart) {
15495
- console.log(import_chalk.default.bold.hex("#D97757")(" [4/4]") + import_chalk.default.bold(" Building & starting services"));
15496
- console.log(import_chalk.default.gray(" This takes 3\u20135 minutes on first run while Docker builds images."));
15866
+ console.log(import_chalk2.default.bold.hex("#D97757")(" [4/4]") + import_chalk2.default.bold(" Installing & starting"));
15867
+ console.log(import_chalk2.default.gray(" Installing dependencies and building TypeScript..."));
15497
15868
  console.log("");
15869
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(dir, ".slackhive-native"), "native");
15870
+ const installSpinner = (0, import_ora2.default)(" Installing npm dependencies...").start();
15498
15871
  try {
15499
- const dfOut = (0, import_child_process.execSync)('docker system df --format "{{.Size}}"', { encoding: "utf-8" });
15500
- void dfOut;
15501
- } catch {
15502
- console.log(import_chalk.default.yellow(" note: Could not check Docker disk usage \u2014 continuing anyway"));
15872
+ (0, import_child_process2.execSync)("npm install", { cwd: dir, stdio: "ignore", timeout: 18e4 });
15873
+ installSpinner.succeed("Dependencies installed");
15874
+ } catch (err) {
15875
+ installSpinner.fail("npm install failed");
15876
+ console.log(import_chalk2.default.red(` ${err.message}`));
15877
+ webReady = false;
15503
15878
  }
15504
- try {
15505
- const df = (0, import_child_process.execSync)("df -k . | tail -1", { encoding: "utf-8" }).trim();
15506
- const available = parseInt(df.split(/\s+/)[3]);
15507
- if (!isNaN(available) && available < 3 * 1024 * 1024) {
15508
- console.log(import_chalk.default.yellow(` warning: less than 3GB disk space available. Build may fail.`));
15509
- console.log("");
15879
+ if (webReady) {
15880
+ const buildSpinner = (0, import_ora2.default)(" Building TypeScript...").start();
15881
+ try {
15882
+ (0, import_child_process2.execSync)("npm run build -w packages/shared -w cli && npx tsc --project apps/runner/tsconfig.json --skipLibCheck", { cwd: dir, stdio: "ignore", timeout: 12e4 });
15883
+ buildSpinner.succeed("Build complete");
15884
+ } catch (err) {
15885
+ buildSpinner.fail("Build failed");
15886
+ console.log(import_chalk2.default.red(` ${err.message}`));
15887
+ webReady = false;
15510
15888
  }
15511
- } catch {
15512
15889
  }
15513
- if (freshEnv) {
15890
+ if (webReady) {
15891
+ const nextSpinner = (0, import_ora2.default)(" Building Next.js web app...").start();
15514
15892
  try {
15515
- (0, import_child_process.execSync)("docker compose down -v", { cwd: dir, stdio: "ignore" });
15516
- } catch {
15893
+ const envPath2 = (0, import_path2.join)(dir, ".env");
15894
+ const envVars = { ...process.env };
15895
+ if ((0, import_fs2.existsSync)(envPath2)) {
15896
+ for (const line of (0, import_fs2.readFileSync)(envPath2, "utf-8").split("\n")) {
15897
+ const t = line.trim();
15898
+ if (!t || t.startsWith("#")) continue;
15899
+ const eq = t.indexOf("=");
15900
+ if (eq > 0) envVars[t.slice(0, eq)] = t.slice(eq + 1);
15901
+ }
15902
+ }
15903
+ (0, import_child_process2.execSync)("npx next build", {
15904
+ cwd: (0, import_path2.join)(dir, "apps", "web"),
15905
+ stdio: "ignore",
15906
+ timeout: 3e5,
15907
+ env: envVars
15908
+ });
15909
+ nextSpinner.succeed("Web app built");
15910
+ } catch (err) {
15911
+ nextSpinner.fail("Next.js build failed");
15912
+ console.log(import_chalk2.default.red(` ${err.message}`));
15913
+ webReady = false;
15517
15914
  }
15518
15915
  }
15519
- const buildOk = await runDockerBuild(dir, opts.dir);
15520
- if (buildOk) {
15916
+ if (webReady) {
15917
+ const startSpinner = (0, import_ora2.default)(" Starting SlackHive...").start();
15521
15918
  try {
15522
- (0, import_child_process.execSync)("docker compose up -d", { cwd: dir, stdio: "ignore" });
15919
+ const manage = (init_manage(), __toCommonJS(manage_exports));
15920
+ process.chdir(dir);
15921
+ startSpinner.succeed("SlackHive started");
15523
15922
  } catch {
15524
- }
15525
- const webSpinner = (0, import_ora.default)(" Waiting for web UI to be ready...").start();
15526
- let ready = false;
15527
- for (let i = 0; i < 60; i++) {
15528
- try {
15529
- (0, import_child_process.execSync)("curl -sf http://localhost:3001/login", { stdio: "ignore" });
15530
- ready = true;
15531
- break;
15532
- } catch {
15533
- await sleep(3e3);
15534
- }
15535
- }
15536
- if (ready) {
15537
- webSpinner.succeed("Web UI is ready");
15538
- } else {
15923
+ startSpinner.warn("Auto-start skipped \u2014 run `slackhive start` manually");
15539
15924
  webReady = false;
15540
- webSpinner.stopAndPersist({ symbol: " " });
15541
15925
  }
15542
- } else {
15543
- webReady = false;
15544
15926
  }
15545
15927
  }
15546
15928
  console.log("");
15547
15929
  if (webReady) {
15548
- console.log(" " + import_chalk.default.bgHex("#D97757").black.bold(" SlackHive is ready! "));
15930
+ console.log(" " + import_chalk2.default.bgHex("#D97757").black.bold(" SlackHive is ready! "));
15549
15931
  console.log("");
15550
- console.log(` ${import_chalk.default.bold("Open:")} ${import_chalk.default.cyan("http://localhost:3001")}`);
15932
+ console.log(` ${import_chalk2.default.bold("Open:")} ${import_chalk2.default.cyan(`http://localhost:${process.env.PORT ?? "3001"}`)}`);
15551
15933
  } else {
15552
- console.log(" " + import_chalk.default.bold("Setup complete!"));
15934
+ console.log(" " + import_chalk2.default.bold("Setup complete!"));
15553
15935
  console.log("");
15554
- console.log(import_chalk.default.gray(" Services are still starting. Once ready:"));
15555
- console.log(` ${import_chalk.default.bold("Run:")} ${import_chalk.default.cyan("slackhive start")}`);
15556
- console.log(` ${import_chalk.default.bold("Open:")} ${import_chalk.default.cyan("http://localhost:3001")}`);
15557
- }
15558
- console.log(` ${import_chalk.default.bold("Dir:")} ${import_chalk.default.gray(dir)}`);
15936
+ console.log(import_chalk2.default.gray(" Services are still starting. Once ready:"));
15937
+ console.log(` ${import_chalk2.default.bold("Run:")} ${import_chalk2.default.cyan("slackhive start")}`);
15938
+ console.log(` ${import_chalk2.default.bold("Open:")} ${import_chalk2.default.cyan(`http://localhost:${process.env.PORT ?? "3001"}`)}`);
15939
+ }
15940
+ console.log(` ${import_chalk2.default.bold("Dir:")} ${import_chalk2.default.gray(dir)}`);
15941
+ console.log(` ${import_chalk2.default.bold("Mode:")} ${import_chalk2.default.gray("Native (SQLite)")}`);
15942
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
15943
+ console.log(` ${import_chalk2.default.bold("Database:")} ${import_chalk2.default.gray((0, import_path2.join)(home, ".slackhive", "data.db"))}`);
15944
+ console.log(` ${import_chalk2.default.bold("Logs:")} ${import_chalk2.default.gray((0, import_path2.join)(home, ".slackhive", "logs", "runner.log"))}`);
15559
15945
  console.log("");
15560
- console.log(import_chalk.default.gray(" Useful commands:"));
15561
- console.log(import_chalk.default.gray(" slackhive start \u2014 Start services"));
15562
- console.log(import_chalk.default.gray(" slackhive stop \u2014 Stop services"));
15563
- console.log(import_chalk.default.gray(" slackhive status \u2014 Show container status"));
15564
- console.log(import_chalk.default.gray(" slackhive logs \u2014 Tail runner logs"));
15565
- console.log(import_chalk.default.gray(" slackhive update \u2014 Pull latest & rebuild"));
15946
+ console.log(import_chalk2.default.gray(" Useful commands:"));
15947
+ console.log(import_chalk2.default.gray(" slackhive start \u2014 Start services"));
15948
+ console.log(import_chalk2.default.gray(" slackhive stop \u2014 Stop services"));
15949
+ console.log(import_chalk2.default.gray(" slackhive status \u2014 Show container status"));
15950
+ console.log(import_chalk2.default.gray(" slackhive logs \u2014 Tail runner logs"));
15951
+ console.log(import_chalk2.default.gray(" slackhive update \u2014 Pull latest & rebuild"));
15566
15952
  console.log("");
15567
15953
  }
15568
- function runDockerBuild(cwd, displayDir) {
15569
- return new Promise((resolve2) => {
15570
- const proc = (0, import_child_process.spawn)("docker", ["compose", "--progress", "plain", "up", "-d", "--build"], {
15571
- cwd,
15572
- env: { ...process.env }
15573
- });
15574
- const startTime = Date.now();
15575
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
15576
- let frameIdx = 0;
15577
- const phases = [
15578
- { name: "Installing system packages", weight: 10, pattern: /apk add|fetch.*APKINDEX/i },
15579
- { name: "Installing npm dependencies", weight: 30, pattern: /npm ci|npm install|added \d+ packages/i },
15580
- { name: "Compiling TypeScript", weight: 10, pattern: /tsc|--skipLibCheck/i },
15581
- { name: "Building web app", weight: 30, pattern: /next build|next\.config/i },
15582
- { name: "Creating containers", weight: 10, pattern: /exporting to image|naming to|exporting layers/i },
15583
- { name: "Starting services", weight: 10, pattern: /Container .*(Starting|Started|Healthy|Created)/i }
15584
- ];
15585
- let currentPhase = 0;
15586
- let phaseStartTime = Date.now();
15587
- function getProgress() {
15588
- let pct = 0;
15589
- for (let i = 0; i < currentPhase; i++) pct += phases[i].weight;
15590
- if (currentPhase < phases.length) {
15591
- const elapsed = (Date.now() - phaseStartTime) / 1e3;
15592
- const estimatedDuration = currentPhase === 1 ? 90 : currentPhase === 3 ? 100 : 30;
15593
- const partial = Math.min(0.9, elapsed / estimatedDuration);
15594
- pct += phases[currentPhase].weight * partial;
15595
- }
15596
- return Math.min(99, Math.round(pct));
15597
- }
15598
- function renderBar() {
15599
- const pct = getProgress();
15600
- const cols = process.stdout.columns || 80;
15601
- const barWidth = Math.min(20, Math.max(10, cols - 55));
15602
- const filled = Math.round(pct / 100 * barWidth);
15603
- const empty = barWidth - filled;
15604
- const bar = import_chalk.default.hex("#D97757")("\u2588".repeat(filled)) + import_chalk.default.gray("\u2591".repeat(empty));
15605
- const elapsed = Math.floor((Date.now() - startTime) / 1e3);
15606
- const phaseName = currentPhase < phases.length ? phases[currentPhase].name : "Finishing";
15607
- const frame = frames[frameIdx++ % frames.length];
15608
- const pctStr = String(pct).padStart(2);
15609
- return ` ${import_chalk.default.hex("#D97757")(frame)} ${bar} ${import_chalk.default.bold(pctStr + "%")} ${phaseName} ${import_chalk.default.gray("(" + elapsed + "s)")}`;
15610
- }
15611
- const spinnerInterval = setInterval(() => {
15612
- process.stdout.write(`\r\x1B[K${renderBar()}`);
15613
- }, 80);
15614
- let buf = "";
15615
- const errorLines = [];
15616
- const onData = (chunk) => {
15617
- buf += chunk.toString().replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
15618
- const lines = buf.split("\n");
15619
- buf = lines.pop() ?? "";
15620
- for (const raw of lines) {
15621
- const line = raw.trim();
15622
- if (!line) continue;
15623
- for (let i = currentPhase + 1; i < phases.length; i++) {
15624
- if (phases[i].pattern.test(line)) {
15625
- const elapsed = Math.floor((Date.now() - phaseStartTime) / 1e3);
15626
- process.stdout.write("\r\x1B[K");
15627
- console.log(" " + import_chalk.default.green("\u2713") + " " + phases[currentPhase].name + import_chalk.default.gray(` (${elapsed}s)`));
15628
- for (let j = currentPhase + 1; j < i; j++) {
15629
- console.log(" " + import_chalk.default.green("\u2713") + " " + phases[j].name + import_chalk.default.gray(" (cached)"));
15630
- }
15631
- currentPhase = i;
15632
- phaseStartTime = Date.now();
15633
- break;
15634
- }
15635
- }
15636
- if (/error/i.test(line)) errorLines.push(line);
15637
- }
15638
- };
15639
- proc.stdout.on("data", onData);
15640
- proc.stderr.on("data", onData);
15641
- proc.on("close", (code) => {
15642
- clearInterval(spinnerInterval);
15643
- process.stdout.write("\r\x1B[K");
15644
- if (code === 0) {
15645
- const elapsed = Math.floor((Date.now() - phaseStartTime) / 1e3);
15646
- if (currentPhase < phases.length) {
15647
- console.log(" " + import_chalk.default.green("\u2713") + " " + phases[currentPhase].name + import_chalk.default.gray(` (${elapsed}s)`));
15648
- for (let j = currentPhase + 1; j < phases.length; j++) {
15649
- console.log(" " + import_chalk.default.green("\u2713") + " " + phases[j].name);
15650
- }
15651
- }
15652
- console.log("");
15653
- console.log(" " + import_chalk.default.green("\u2713") + import_chalk.default.bold(" All services started"));
15654
- resolve2(true);
15655
- return;
15656
- }
15657
- console.log(" " + import_chalk.default.red("\u2717") + " Failed to start services");
15658
- console.log("");
15659
- const allErrors = errorLines.join("\n").toLowerCase();
15660
- if (allErrors.includes("no space left") || allErrors.includes("disk full")) {
15661
- console.log(import_chalk.default.yellow(" Cause: Docker is out of disk space."));
15662
- console.log(import_chalk.default.gray(" Fix: docker system prune -a"));
15663
- } else if (allErrors.includes("port is already allocated") || allErrors.includes("address already in use")) {
15664
- const portMatch = /bind for .+:(\d+)/.exec(allErrors);
15665
- const port = portMatch ? portMatch[1] : "a required port";
15666
- console.log(import_chalk.default.yellow(` Cause: Port ${port} is already in use.`));
15667
- console.log(import_chalk.default.gray(` Fix: stop the process on port ${port} and retry`));
15668
- } else if (allErrors.includes("permission denied") || allErrors.includes("unauthorized")) {
15669
- console.log(import_chalk.default.yellow(" Cause: Docker permission denied \u2014 is Docker Desktop running?"));
15670
- } else if (allErrors.includes("memory") || allErrors.includes("oom")) {
15671
- console.log(import_chalk.default.yellow(" Cause: Docker ran out of memory."));
15672
- console.log(import_chalk.default.gray(" Fix: increase Docker Desktop memory to 4GB+ in Settings \u2192 Resources"));
15673
- } else if (allErrors.includes("network") || allErrors.includes("timeout") || allErrors.includes("pull") || allErrors.includes("tls") || allErrors.includes("certificate")) {
15674
- console.log(import_chalk.default.yellow(" Cause: Network/TLS error \u2014 try restarting Docker Desktop."));
15675
- } else if (errorLines.length > 0) {
15676
- console.log(import_chalk.default.gray(" Error details:"));
15677
- errorLines.slice(-5).forEach((l) => console.log(import_chalk.default.red(" " + l)));
15678
- }
15679
- console.log("");
15680
- console.log(import_chalk.default.gray(` To retry: cd ${displayDir} && docker compose up -d --build`));
15681
- resolve2(false);
15682
- });
15683
- });
15684
- }
15685
15954
  function randomSecret() {
15686
15955
  const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
15687
15956
  let result = "";
15688
15957
  for (let i = 0; i < 32; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
15689
15958
  return result;
15690
15959
  }
15691
- function sleep(ms) {
15692
- return new Promise((r) => setTimeout(r, ms));
15693
- }
15694
-
15695
- // src/commands/manage.ts
15696
- var import_child_process2 = require("child_process");
15697
- var import_fs2 = require("fs");
15698
- var import_path2 = require("path");
15699
- var import_chalk2 = __toESM(require_source());
15700
- var import_ora2 = __toESM(require_ora());
15701
- function findProjectDir() {
15702
- if ((0, import_fs2.existsSync)((0, import_path2.join)(process.cwd(), "docker-compose.yml"))) {
15703
- return process.cwd();
15704
- }
15705
- const sub = (0, import_path2.join)(process.cwd(), "slackhive");
15706
- if ((0, import_fs2.existsSync)((0, import_path2.join)(sub, "docker-compose.yml"))) {
15707
- return sub;
15708
- }
15709
- console.log(import_chalk2.default.red(" Could not find SlackHive project."));
15710
- console.log(import_chalk2.default.gray(" Run this command from the SlackHive directory, or run `slackhive init` first."));
15711
- process.exit(1);
15712
- }
15713
- async function start() {
15714
- const dir = findProjectDir();
15715
- const spinner = (0, import_ora2.default)("Starting SlackHive services...").start();
15716
- try {
15717
- (0, import_child_process2.execSync)("docker compose up -d", { cwd: dir, stdio: "ignore" });
15718
- spinner.succeed("All services started");
15719
- console.log(import_chalk2.default.gray(" Web UI: http://localhost:3001"));
15720
- } catch {
15721
- spinner.fail("Failed to start services");
15722
- }
15723
- }
15724
- async function stop() {
15725
- const dir = findProjectDir();
15726
- const spinner = (0, import_ora2.default)("Stopping SlackHive services...").start();
15727
- try {
15728
- (0, import_child_process2.execSync)("docker compose stop", { cwd: dir, stdio: "ignore" });
15729
- spinner.succeed("All services stopped");
15730
- } catch {
15731
- spinner.fail("Failed to stop services");
15732
- }
15733
- }
15734
- async function status() {
15735
- const dir = findProjectDir();
15736
- try {
15737
- const output = (0, import_child_process2.execSync)("docker compose ps", { cwd: dir, encoding: "utf-8" });
15738
- console.log("");
15739
- console.log(import_chalk2.default.bold(" SlackHive Status"));
15740
- console.log("");
15741
- console.log(output);
15742
- } catch {
15743
- console.log(import_chalk2.default.red(" Failed to get status"));
15744
- }
15745
- }
15746
- async function logs(opts) {
15747
- const dir = findProjectDir();
15748
- const args = ["compose", "logs", "runner"];
15749
- if (opts.follow !== false) args.push("-f");
15750
- const proc = (0, import_child_process2.spawn)("docker", args, { cwd: dir, stdio: "inherit" });
15751
- proc.on("error", () => console.log(import_chalk2.default.red(" Failed to tail logs")));
15752
- }
15753
- async function update() {
15754
- const dir = findProjectDir();
15755
- const pullSpinner = (0, import_ora2.default)("Pulling latest changes...").start();
15756
- try {
15757
- (0, import_child_process2.execSync)("git pull", { cwd: dir, stdio: "ignore" });
15758
- pullSpinner.succeed("Code updated");
15759
- } catch {
15760
- pullSpinner.fail("Failed to pull \u2014 do you have uncommitted changes?");
15761
- return;
15762
- }
15763
- const buildSpinner = (0, import_ora2.default)("Rebuilding services (this may take a minute)...").start();
15764
- try {
15765
- (0, import_child_process2.execSync)("docker compose up -d --build", { cwd: dir, stdio: "ignore", timeout: 6e5 });
15766
- buildSpinner.succeed("Services rebuilt and restarted");
15767
- console.log(import_chalk2.default.gray(" Web UI: http://localhost:3001"));
15768
- } catch {
15769
- buildSpinner.fail("Failed to rebuild");
15770
- }
15771
- }
15772
15960
 
15773
15961
  // src/index.ts
15962
+ init_manage();
15774
15963
  var { version } = require_package();
15775
15964
  var program2 = new Command();
15776
15965
  program2.name("slackhive").description("CLI to install and manage SlackHive \u2014 AI agent teams on Slack").version(version);
15777
15966
  program2.command("init").description("Clone SlackHive repo, configure environment, and start services").option("-d, --dir <path>", "Directory to install into", "slackhive").option("--skip-start", "Skip starting services after init").action(init);
15778
15967
  program2.command("start").description("Start all SlackHive services").action(start);
15779
- program2.command("stop").description("Stop all SlackHive services").action(stop);
15968
+ program2.command("stop").description("Stop all SlackHive services (also kills orphaned runner processes)").action(stop);
15780
15969
  program2.command("status").description("Show running SlackHive containers").action(status);
15781
15970
  program2.command("logs").description("Tail runner service logs").option("-f, --follow", "Follow log output", true).action(logs);
15782
15971
  program2.command("update").description("Pull latest changes and rebuild").action(update);