cognova 0.2.14 → 0.2.15

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/cli/index.js CHANGED
@@ -156,7 +156,7 @@ var require_src = __commonJS({
156
156
 
157
157
  // src/commands/init.ts
158
158
  import { execSync as execSync5 } from "child_process";
159
- import { join as join7 } from "path";
159
+ import { join as join9 } from "path";
160
160
  import crypto3 from "crypto";
161
161
 
162
162
  // ../node_modules/.pnpm/@clack+core@1.0.1/node_modules/@clack/core/dist/index.mjs
@@ -1189,7 +1189,7 @@ ${l}
1189
1189
  } }).prompt();
1190
1190
 
1191
1191
  // src/commands/init.ts
1192
- var import_picocolors5 = __toESM(require_picocolors(), 1);
1192
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
1193
1193
 
1194
1194
  // src/lib/prerequisites.ts
1195
1195
  import { execSync } from "child_process";
@@ -1201,6 +1201,9 @@ function checkCommand(cmd) {
1201
1201
  return null;
1202
1202
  }
1203
1203
  }
1204
+ function sleep(ms) {
1205
+ return new Promise((resolve) => setTimeout(resolve, ms));
1206
+ }
1204
1207
  async function checkPrerequisites() {
1205
1208
  R2.step("Checking prerequisites...");
1206
1209
  const nodeOut = checkCommand("node --version");
@@ -1250,9 +1253,48 @@ async function checkPrerequisites() {
1250
1253
  } else {
1251
1254
  R2.success(`Claude Code ${claudeOut}`);
1252
1255
  }
1253
- let hasDocker = !!dockerOut;
1256
+ let dockerInstalled = !!dockerOut;
1257
+ let dockerReady = false;
1254
1258
  if (dockerOut) {
1255
- R2.success(`Docker available ${import_picocolors3.default.dim("(for local PostgreSQL)")}`);
1259
+ const daemonRunning = checkCommand("docker info");
1260
+ if (daemonRunning) {
1261
+ dockerReady = true;
1262
+ R2.success(`Docker available ${import_picocolors3.default.dim("(for local PostgreSQL)")}`);
1263
+ } else {
1264
+ R2.warn("Docker is installed but the daemon is not running");
1265
+ const startDocker = await Re({
1266
+ message: "Start Docker now?",
1267
+ initialValue: true
1268
+ });
1269
+ if (!Ct(startDocker) && startDocker) {
1270
+ const s = bt2();
1271
+ s.start("Starting Docker daemon");
1272
+ try {
1273
+ if (process.platform === "darwin") {
1274
+ execSync("open -a Docker", { stdio: "pipe" });
1275
+ } else if (process.platform === "linux") {
1276
+ execSync("sudo systemctl start docker", { stdio: "inherit" });
1277
+ }
1278
+ for (let i = 0; i < 30; i++) {
1279
+ await sleep(1e3);
1280
+ if (checkCommand("docker info")) {
1281
+ dockerReady = true;
1282
+ s.stop("Docker daemon is running");
1283
+ break;
1284
+ }
1285
+ }
1286
+ if (!dockerReady) {
1287
+ s.stop("Docker daemon did not start in time");
1288
+ R2.warn("You can start it manually and continue with local PostgreSQL, or use remote PostgreSQL");
1289
+ }
1290
+ } catch {
1291
+ s.stop("Failed to start Docker daemon");
1292
+ R2.warn("Start Docker manually: https://docs.docker.com/get-docker/");
1293
+ }
1294
+ } else {
1295
+ R2.info("You can use a remote PostgreSQL instead");
1296
+ }
1297
+ }
1256
1298
  } else {
1257
1299
  const installDocker = await Re({
1258
1300
  message: "Docker not found. Install it? (needed only for local PostgreSQL)",
@@ -1265,23 +1307,26 @@ async function checkPrerequisites() {
1265
1307
  if (process.platform === "darwin") {
1266
1308
  execSync("brew install --cask docker", { stdio: "inherit" });
1267
1309
  s.stop("Docker Desktop installed \u2014 open it from Applications to finish setup");
1310
+ R2.info("Open Docker Desktop from Applications, then re-run this installer");
1268
1311
  } else {
1269
1312
  const cmd = process.platform === "linux" ? "sudo apt-get update -qq && sudo apt-get install -y -qq docker.io docker-compose-plugin" : "apt-get update -qq && apt-get install -y -qq docker.io docker-compose-plugin";
1270
1313
  execSync(cmd, { stdio: "inherit" });
1271
1314
  s.stop("Docker installed");
1272
1315
  }
1273
- hasDocker = !!checkCommand("docker --version");
1316
+ dockerInstalled = !!checkCommand("docker --version");
1317
+ dockerReady = !!checkCommand("docker info");
1274
1318
  } catch {
1275
1319
  s.stop("Docker installation failed");
1276
1320
  R2.warn("Install Docker manually: https://docs.docker.com/get-docker/");
1277
1321
  }
1278
1322
  } else {
1279
- R2.info(`Skipped \u2014 you can use a remote PostgreSQL instead`);
1323
+ R2.info("Skipped \u2014 you can use a remote PostgreSQL instead");
1280
1324
  }
1281
1325
  }
1282
1326
  return {
1283
1327
  ok: true,
1284
- hasDocker,
1328
+ dockerInstalled,
1329
+ dockerReady,
1285
1330
  nodeVersion: nodeOut || "",
1286
1331
  pythonVersion: pythonOut || "",
1287
1332
  pnpmVersion: pnpmOut || "",
@@ -1471,13 +1516,15 @@ async function setupInstallDir(installDir) {
1471
1516
  copyAppSource(packageDir, installDir);
1472
1517
  s.stop("Application files installed");
1473
1518
  }
1474
- function writeMetadata(installDir, vaultPath, version) {
1519
+ function writeMetadata(installDir, vaultPath, version, dbPassword, dbPort) {
1475
1520
  const metadata = {
1476
1521
  version,
1477
1522
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1478
1523
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1479
1524
  installDir,
1480
- vaultPath
1525
+ vaultPath,
1526
+ dbPassword,
1527
+ dbPort
1481
1528
  };
1482
1529
  writeFileSync(join2(installDir, ".cognova"), JSON.stringify(metadata, null, 2));
1483
1530
  const homeMeta = join2(process.env.HOME || "~", ".cognova");
@@ -1517,9 +1564,37 @@ async function setupVault() {
1517
1564
 
1518
1565
  // src/lib/database.ts
1519
1566
  import { execSync as execSync2 } from "child_process";
1567
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1568
+ import { join as join4 } from "path";
1520
1569
  import crypto from "crypto";
1521
1570
  var import_picocolors4 = __toESM(require_picocolors(), 1);
1522
- async function setupDatabase(hasDocker) {
1571
+ function checkPortInUse(port) {
1572
+ try {
1573
+ const containers = execSync2(`docker ps --filter "publish=${port}" --format "{{.Names}}"`, {
1574
+ encoding: "utf-8"
1575
+ }).trim();
1576
+ return containers.length > 0;
1577
+ } catch {
1578
+ try {
1579
+ execSync2(`lsof -i :${port}`, { stdio: "pipe" });
1580
+ return true;
1581
+ } catch {
1582
+ return false;
1583
+ }
1584
+ }
1585
+ }
1586
+ function readMetadata2() {
1587
+ try {
1588
+ const metaPath = join4(process.env.HOME || "~", ".cognova");
1589
+ if (!existsSync4(metaPath))
1590
+ return null;
1591
+ const content = readFileSync2(metaPath, "utf-8");
1592
+ return JSON.parse(content);
1593
+ } catch {
1594
+ return null;
1595
+ }
1596
+ }
1597
+ async function setupDatabase(dockerReady) {
1523
1598
  const dbType = await Je({
1524
1599
  message: "Database setup",
1525
1600
  options: [
@@ -1529,14 +1604,15 @@ async function setupDatabase(hasDocker) {
1529
1604
  });
1530
1605
  if (Ct(dbType)) process.exit(0);
1531
1606
  if (dbType === "local") {
1532
- if (!hasDocker) {
1533
- R2.error("Docker is required for local PostgreSQL.");
1534
- R2.info("Install Docker: https://docs.docker.com/get-docker/");
1607
+ if (!dockerReady) {
1608
+ R2.error("Docker daemon is not running.");
1609
+ R2.info("Start Docker Desktop (macOS) or run: sudo systemctl start docker (Linux)");
1535
1610
  R2.info('Or choose "Remote PostgreSQL" and use Neon: https://neon.tech');
1536
1611
  process.exit(1);
1537
1612
  }
1538
1613
  const password = crypto.randomBytes(16).toString("hex");
1539
1614
  const containerName = "cognova-db";
1615
+ let port = 5432;
1540
1616
  try {
1541
1617
  const existing = execSync2(`docker ps -a --filter name=${containerName} --format "{{.Status}}"`, {
1542
1618
  encoding: "utf-8"
@@ -1551,6 +1627,17 @@ async function setupDatabase(hasDocker) {
1551
1627
  if (reuse) {
1552
1628
  if (!existing.startsWith("Up"))
1553
1629
  execSync2(`docker start ${containerName}`, { stdio: "pipe" });
1630
+ const metadata = readMetadata2();
1631
+ if (metadata?.dbPassword && metadata?.dbPort) {
1632
+ const connectionString3 = `postgres://postgres:${metadata.dbPassword}@localhost:${metadata.dbPort}/cognova`;
1633
+ R2.info(`Using stored credentials (port ${metadata.dbPort})`);
1634
+ return {
1635
+ type: "local",
1636
+ connectionString: connectionString3,
1637
+ password: metadata.dbPassword,
1638
+ port: metadata.dbPort
1639
+ };
1640
+ }
1554
1641
  R2.info("Using existing container. Ensure DATABASE_URL in .env matches its credentials.");
1555
1642
  const connStr = await Ze({
1556
1643
  message: "Connection string for existing container",
@@ -1564,6 +1651,23 @@ async function setupDatabase(hasDocker) {
1564
1651
  }
1565
1652
  } catch {
1566
1653
  }
1654
+ if (checkPortInUse(port)) {
1655
+ R2.warn(`Port ${port} is already in use`);
1656
+ const altPort = await Ze({
1657
+ message: "Choose an alternative port",
1658
+ placeholder: "5433",
1659
+ defaultValue: "5433",
1660
+ validate: (v) => {
1661
+ const num = parseInt(v);
1662
+ if (isNaN(num) || num < 1024 || num > 65535)
1663
+ return "Port must be between 1024 and 65535";
1664
+ if (checkPortInUse(num))
1665
+ return `Port ${num} is also in use`;
1666
+ }
1667
+ });
1668
+ if (Ct(altPort)) process.exit(0);
1669
+ port = parseInt(altPort);
1670
+ }
1567
1671
  const s = bt2();
1568
1672
  s.start("Starting PostgreSQL container");
1569
1673
  try {
@@ -1573,7 +1677,7 @@ async function setupDatabase(hasDocker) {
1573
1677
  `-e POSTGRES_USER=postgres`,
1574
1678
  `-e POSTGRES_PASSWORD=${password}`,
1575
1679
  `-e POSTGRES_DB=cognova`,
1576
- `-p 5432:5432`,
1680
+ `-p ${port}:5432`,
1577
1681
  `--restart unless-stopped`,
1578
1682
  `postgres:16-alpine`
1579
1683
  ].join(" "), { stdio: "pipe" });
@@ -1584,7 +1688,7 @@ async function setupDatabase(hasDocker) {
1584
1688
  ready = true;
1585
1689
  break;
1586
1690
  } catch {
1587
- await sleep(1e3);
1691
+ await sleep2(1e3);
1588
1692
  }
1589
1693
  }
1590
1694
  if (!ready) {
@@ -1598,9 +1702,14 @@ async function setupDatabase(hasDocker) {
1598
1702
  R2.error(`Docker error: ${err}`);
1599
1703
  process.exit(1);
1600
1704
  }
1601
- const connectionString2 = `postgres://postgres:${password}@localhost:5432/cognova`;
1705
+ const connectionString2 = `postgres://postgres:${password}@localhost:${port}/cognova`;
1602
1706
  R2.info(`Connection: ${import_picocolors4.default.dim(connectionString2)}`);
1603
- return { type: "local", connectionString: connectionString2 };
1707
+ return {
1708
+ type: "local",
1709
+ connectionString: connectionString2,
1710
+ password,
1711
+ port
1712
+ };
1604
1713
  }
1605
1714
  const connectionString = await Ze({
1606
1715
  message: "PostgreSQL connection string",
@@ -1613,13 +1722,13 @@ async function setupDatabase(hasDocker) {
1613
1722
  if (Ct(connectionString)) process.exit(0);
1614
1723
  return { type: "remote", connectionString };
1615
1724
  }
1616
- function sleep(ms) {
1725
+ function sleep2(ms) {
1617
1726
  return new Promise((resolve) => setTimeout(resolve, ms));
1618
1727
  }
1619
1728
 
1620
1729
  // src/lib/config.ts
1621
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1622
- import { join as join4 } from "path";
1730
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1731
+ import { join as join5 } from "path";
1623
1732
 
1624
1733
  // src/templates/env.ts
1625
1734
  import crypto2 from "crypto";
@@ -1658,11 +1767,11 @@ function generateEnvFile(config) {
1658
1767
  // src/lib/config.ts
1659
1768
  function writeEnvFile(config) {
1660
1769
  const content = generateEnvFile(config);
1661
- writeFileSync2(join4(config.installDir, ".env"), content);
1770
+ writeFileSync2(join5(config.installDir, ".env"), content);
1662
1771
  }
1663
1772
  function loadEnvFile(installDir) {
1664
1773
  try {
1665
- const content = readFileSync2(join4(installDir, ".env"), "utf-8");
1774
+ const content = readFileSync3(join5(installDir, ".env"), "utf-8");
1666
1775
  const env = {};
1667
1776
  for (const line of content.split("\n")) {
1668
1777
  const trimmed = line.trim();
@@ -1678,8 +1787,8 @@ function loadEnvFile(installDir) {
1678
1787
  }
1679
1788
 
1680
1789
  // src/lib/claude-config.ts
1681
- import { cpSync as cpSync2, mkdirSync as mkdirSync3, existsSync as existsSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
1682
- import { join as join5 } from "path";
1790
+ import { cpSync as cpSync2, mkdirSync as mkdirSync3, existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
1791
+ import { join as join6 } from "path";
1683
1792
 
1684
1793
  // src/templates/claude-md.ts
1685
1794
  function generateClaudeMd(config) {
@@ -1923,27 +2032,27 @@ function generateSettingsJson(config) {
1923
2032
  // src/lib/claude-config.ts
1924
2033
  async function installClaudeConfig(config, options) {
1925
2034
  const claudeDir = getClaudeDir();
1926
- const sourceDir = join5(getPackageDir(), "Claude");
2035
+ const sourceDir = join6(getPackageDir(), "Claude");
1927
2036
  const installAll = !options;
1928
2037
  mkdirSync3(claudeDir, { recursive: true });
1929
2038
  if (installAll || options?.skills) {
1930
- const skillsSrc = join5(sourceDir, "skills");
1931
- if (existsSync4(skillsSrc))
1932
- cpSync2(skillsSrc, join5(claudeDir, "skills"), { recursive: true, force: true });
2039
+ const skillsSrc = join6(sourceDir, "skills");
2040
+ if (existsSync5(skillsSrc))
2041
+ cpSync2(skillsSrc, join6(claudeDir, "skills"), { recursive: true, force: true });
1933
2042
  }
1934
2043
  if (installAll || options?.hooks) {
1935
- const hooksSrc = join5(sourceDir, "hooks");
1936
- if (existsSync4(hooksSrc))
1937
- cpSync2(hooksSrc, join5(claudeDir, "hooks"), { recursive: true, force: true });
2044
+ const hooksSrc = join6(sourceDir, "hooks");
2045
+ if (existsSync5(hooksSrc))
2046
+ cpSync2(hooksSrc, join6(claudeDir, "hooks"), { recursive: true, force: true });
1938
2047
  }
1939
2048
  if (installAll || options?.rules) {
1940
- const rulesSrc = join5(sourceDir, "rules");
1941
- if (existsSync4(rulesSrc))
1942
- cpSync2(rulesSrc, join5(claudeDir, "rules"), { recursive: true, force: true });
2049
+ const rulesSrc = join6(sourceDir, "rules");
2050
+ if (existsSync5(rulesSrc))
2051
+ cpSync2(rulesSrc, join6(claudeDir, "rules"), { recursive: true, force: true });
1943
2052
  }
1944
2053
  if (installAll || options?.claudeMd) {
1945
- const claudeMdPath = join5(claudeDir, "CLAUDE.md");
1946
- if (existsSync4(claudeMdPath) && installAll) {
2054
+ const claudeMdPath = join6(claudeDir, "CLAUDE.md");
2055
+ if (existsSync5(claudeMdPath) && installAll) {
1947
2056
  const overwrite = await Re({
1948
2057
  message: "~/.claude/CLAUDE.md already exists. Overwrite?",
1949
2058
  initialValue: false
@@ -1959,10 +2068,10 @@ async function installClaudeConfig(config, options) {
1959
2068
  }
1960
2069
  }
1961
2070
  if (installAll || options?.settings) {
1962
- const settingsPath = join5(claudeDir, "settings.json");
1963
- if (existsSync4(settingsPath) && installAll) {
2071
+ const settingsPath = join6(claudeDir, "settings.json");
2072
+ if (existsSync5(settingsPath) && installAll) {
1964
2073
  try {
1965
- const existing = JSON.parse(readFileSync3(settingsPath, "utf-8"));
2074
+ const existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
1966
2075
  const generated = JSON.parse(generateSettingsJson(config));
1967
2076
  const merged = mergeSettings(existing, generated);
1968
2077
  writeFileSync3(settingsPath, JSON.stringify(merged, null, 2) + "\n");
@@ -1977,23 +2086,23 @@ async function installClaudeConfig(config, options) {
1977
2086
  }
1978
2087
  function syncClaudeConfig(sourceDir) {
1979
2088
  const claudeDir = getClaudeDir();
1980
- const claudeSrc = join5(sourceDir, "Claude");
1981
- if (!existsSync4(claudeSrc)) return;
2089
+ const claudeSrc = join6(sourceDir, "Claude");
2090
+ if (!existsSync5(claudeSrc)) return;
1982
2091
  mkdirSync3(claudeDir, { recursive: true });
1983
- const skillsSrc = join5(claudeSrc, "skills");
1984
- if (existsSync4(skillsSrc)) {
1985
- mkdirSync3(join5(claudeDir, "skills"), { recursive: true });
1986
- cpSync2(skillsSrc, join5(claudeDir, "skills"), { recursive: true, force: true });
2092
+ const skillsSrc = join6(claudeSrc, "skills");
2093
+ if (existsSync5(skillsSrc)) {
2094
+ mkdirSync3(join6(claudeDir, "skills"), { recursive: true });
2095
+ cpSync2(skillsSrc, join6(claudeDir, "skills"), { recursive: true, force: true });
1987
2096
  }
1988
- const hooksSrc = join5(claudeSrc, "hooks");
1989
- if (existsSync4(hooksSrc)) {
1990
- mkdirSync3(join5(claudeDir, "hooks"), { recursive: true });
1991
- cpSync2(hooksSrc, join5(claudeDir, "hooks"), { recursive: true, force: true });
2097
+ const hooksSrc = join6(claudeSrc, "hooks");
2098
+ if (existsSync5(hooksSrc)) {
2099
+ mkdirSync3(join6(claudeDir, "hooks"), { recursive: true });
2100
+ cpSync2(hooksSrc, join6(claudeDir, "hooks"), { recursive: true, force: true });
1992
2101
  }
1993
- const rulesSrc = join5(claudeSrc, "rules");
1994
- if (existsSync4(rulesSrc)) {
1995
- mkdirSync3(join5(claudeDir, "rules"), { recursive: true });
1996
- cpSync2(rulesSrc, join5(claudeDir, "rules"), { recursive: true, force: true });
2102
+ const rulesSrc = join6(claudeSrc, "rules");
2103
+ if (existsSync5(rulesSrc)) {
2104
+ mkdirSync3(join6(claudeDir, "rules"), { recursive: true });
2105
+ cpSync2(rulesSrc, join6(claudeDir, "rules"), { recursive: true, force: true });
1997
2106
  }
1998
2107
  }
1999
2108
  function mergeSettings(existing, generated) {
@@ -2009,8 +2118,8 @@ function mergeSettings(existing, generated) {
2009
2118
 
2010
2119
  // src/lib/process-manager.ts
2011
2120
  import { execSync as execSync3 } from "child_process";
2012
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
2013
- import { join as join6 } from "path";
2121
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
2122
+ import { join as join7 } from "path";
2014
2123
 
2015
2124
  // src/templates/pm2-ecosystem.ts
2016
2125
  function generatePm2Ecosystem(config) {
@@ -2082,11 +2191,11 @@ async function setupAndStart(config) {
2082
2191
  return;
2083
2192
  }
2084
2193
  }
2085
- const logsDir = join6(config.installDir, "logs");
2086
- if (!existsSync5(logsDir))
2194
+ const logsDir = join7(config.installDir, "logs");
2195
+ if (!existsSync6(logsDir))
2087
2196
  mkdirSync4(logsDir, { recursive: true });
2088
2197
  const ecosystem = generatePm2Ecosystem(config);
2089
- writeFileSync4(join6(config.installDir, "ecosystem.config.cjs"), ecosystem);
2198
+ writeFileSync4(join7(config.installDir, "ecosystem.config.cjs"), ecosystem);
2090
2199
  const s = bt2();
2091
2200
  s.start("Starting Cognova with PM2");
2092
2201
  try {
@@ -2123,103 +2232,278 @@ async function waitForHealth(url, maxWaitSeconds = 30) {
2123
2232
  return false;
2124
2233
  }
2125
2234
 
2235
+ // src/lib/progress.ts
2236
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync } from "fs";
2237
+ import { join as join8 } from "path";
2238
+ var PROGRESS_FILE = join8(process.env.HOME || "~", ".cognova-setup.json");
2239
+ function loadProgress() {
2240
+ try {
2241
+ if (!existsSync7(PROGRESS_FILE))
2242
+ return null;
2243
+ const content = readFileSync5(PROGRESS_FILE, "utf-8");
2244
+ return JSON.parse(content);
2245
+ } catch {
2246
+ return null;
2247
+ }
2248
+ }
2249
+ function saveProgress(progress) {
2250
+ try {
2251
+ const content = JSON.stringify(progress, null, 2);
2252
+ writeFileSync5(PROGRESS_FILE, content, "utf-8");
2253
+ } catch (err) {
2254
+ console.warn(`Failed to save progress: ${err}`);
2255
+ }
2256
+ }
2257
+ function clearProgress() {
2258
+ try {
2259
+ if (existsSync7(PROGRESS_FILE))
2260
+ unlinkSync(PROGRESS_FILE);
2261
+ } catch {
2262
+ }
2263
+ }
2264
+
2265
+ // src/lib/retry.ts
2266
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
2267
+ var SkipError = class extends Error {
2268
+ constructor() {
2269
+ super("Step skipped by user");
2270
+ this.name = "SkipError";
2271
+ }
2272
+ };
2273
+ async function withRetry(label, fn, options = {}) {
2274
+ const { maxRetries = 3, canSkip = true } = options;
2275
+ let attempt = 0;
2276
+ while (true) {
2277
+ try {
2278
+ return await fn();
2279
+ } catch (err) {
2280
+ attempt++;
2281
+ const errorMsg = err instanceof Error ? err.message : String(err);
2282
+ R2.error(`${label} failed: ${errorMsg}`);
2283
+ if (attempt >= maxRetries) {
2284
+ R2.error(`Maximum retry attempts (${maxRetries}) reached`);
2285
+ if (!canSkip) {
2286
+ R2.error("This step cannot be skipped. Setup aborted.");
2287
+ process.exit(1);
2288
+ }
2289
+ }
2290
+ const choices = [
2291
+ { value: "retry", label: "Retry", hint: "Try again" }
2292
+ ];
2293
+ if (canSkip) {
2294
+ choices.push({ value: "skip", label: "Skip", hint: "Continue without this step" });
2295
+ }
2296
+ choices.push({ value: "abort", label: "Abort", hint: "Exit setup" });
2297
+ const action = await Je({
2298
+ message: `${label} failed. What would you like to do?`,
2299
+ options: choices
2300
+ });
2301
+ if (Ct(action) || action === "abort") {
2302
+ R2.warn("Setup aborted by user");
2303
+ process.exit(1);
2304
+ }
2305
+ if (action === "skip") {
2306
+ throw new SkipError();
2307
+ }
2308
+ R2.info(import_picocolors5.default.dim(`Retrying ${label}...`));
2309
+ }
2310
+ }
2311
+ }
2312
+
2126
2313
  // src/commands/init.ts
2127
2314
  async function init() {
2128
- We(import_picocolors5.default.bgCyan(import_picocolors5.default.black(" Cognova Setup ")));
2129
- const prereqs = await checkPrerequisites();
2130
- R2.step(import_picocolors5.default.bold("Agent Personality"));
2131
- const personality = await promptPersonality();
2132
- R2.step(import_picocolors5.default.bold("Installation"));
2133
- const defaultDir = join7(process.env.HOME || "~", "cognova");
2134
- const installDir = await Ze({
2135
- message: "Where should Cognova be installed?",
2136
- placeholder: defaultDir,
2137
- defaultValue: defaultDir
2138
- });
2139
- if (Ct(installDir)) process.exit(0);
2140
- const resolvedInstallDir = installDir.replace("~", process.env.HOME || "");
2141
- await setupInstallDir(resolvedInstallDir);
2142
- R2.step(import_picocolors5.default.bold("Vault"));
2143
- const vault = await setupVault();
2144
- R2.step(import_picocolors5.default.bold("Database"));
2145
- const database = await setupDatabase(prereqs.hasDocker);
2146
- R2.step(import_picocolors5.default.bold("Network Access"));
2147
- const accessMode = await Je({
2148
- message: "How will you access Cognova?",
2149
- options: [
2150
- { value: "localhost", label: "Local only", hint: "http://localhost:3000" },
2151
- { value: "specific", label: "Specific IP or domain", hint: "LAN IP, hostname, or domain" },
2152
- { value: "any", label: "Any connection", hint: "Accepts requests from any origin (0.0.0.0)" }
2153
- ]
2154
- });
2155
- if (Ct(accessMode)) process.exit(0);
2156
- let appUrl = "http://localhost:3000";
2157
- if (accessMode === "specific") {
2158
- const host = await Ze({
2159
- message: "IP address or domain (include port if not 80/443)",
2160
- placeholder: "192.168.1.100:3000"
2315
+ We(import_picocolors6.default.bgCyan(import_picocolors6.default.black(" Cognova Setup ")));
2316
+ const existingProgress = loadProgress();
2317
+ let progress = existingProgress || {
2318
+ completedSteps: [],
2319
+ partialConfig: {},
2320
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2321
+ };
2322
+ if (existingProgress) {
2323
+ R2.warn("Previous setup was interrupted");
2324
+ const resume = await Re({
2325
+ message: "Resume from where you left off?",
2326
+ initialValue: true
2161
2327
  });
2162
- if (Ct(host)) process.exit(0);
2163
- appUrl = host.startsWith("http") ? host : `http://${host}`;
2164
- }
2165
- R2.warn(import_picocolors5.default.yellow(import_picocolors5.default.bold("Security Notice")));
2166
- R2.warn([
2167
- "Cognova gives an AI agent unrestricted access to this machine.",
2168
- "It can read, write, and execute anything via the embedded terminal",
2169
- "and Claude Code CLI.",
2170
- "",
2171
- ` ${import_picocolors5.default.dim("\u2022")} Do not run on a personal machine or alongside sensitive data`,
2172
- ` ${import_picocolors5.default.dim("\u2022")} Use a dedicated VM, container, or isolated environment`,
2173
- ` ${import_picocolors5.default.dim("\u2022")} Put a reverse proxy with TLS in front for remote access`,
2174
- ` ${import_picocolors5.default.dim("\u2022")} Do not store SSH keys, cloud creds, or production secrets here`
2175
- ].join("\n"));
2176
- if (accessMode === "any") {
2177
- R2.warn(import_picocolors5.default.red(
2178
- "Binding to 0.0.0.0 exposes this to your entire network."
2179
- ));
2180
- }
2181
- const proceed = await Re({
2182
- message: "I understand the risks. Continue?",
2183
- initialValue: false
2184
- });
2185
- if (Ct(proceed) || !proceed) process.exit(0);
2186
- R2.step(import_picocolors5.default.bold("Authentication"));
2187
- const adminEmail = await Ze({
2188
- message: "Admin email",
2189
- placeholder: "admin@example.com",
2190
- defaultValue: "admin@example.com"
2191
- });
2192
- if (Ct(adminEmail)) process.exit(0);
2193
- let adminPassword;
2194
- while (true) {
2195
- const pw = await He({
2196
- message: "Admin password",
2197
- validate: (v) => {
2198
- if (!v || v.length < 8) return "Minimum 8 characters";
2328
+ if (Ct(resume)) process.exit(0);
2329
+ if (!resume) {
2330
+ clearProgress();
2331
+ progress = {
2332
+ completedSteps: [],
2333
+ partialConfig: {},
2334
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2335
+ };
2336
+ }
2337
+ }
2338
+ const prereqs = progress.completedSteps.includes("prerequisites") ? { ok: true, dockerInstalled: true, dockerReady: true, nodeVersion: "", pythonVersion: "", pnpmVersion: "", claudeInstalled: true } : await checkPrerequisites();
2339
+ if (!progress.completedSteps.includes("prerequisites")) {
2340
+ progress.completedSteps.push("prerequisites");
2341
+ saveProgress(progress);
2342
+ }
2343
+ R2.step(import_picocolors6.default.bold("Agent Personality"));
2344
+ const personality = progress.partialConfig.personality || await promptPersonality();
2345
+ if (!progress.completedSteps.includes("personality")) {
2346
+ progress.partialConfig.personality = personality;
2347
+ progress.completedSteps.push("personality");
2348
+ saveProgress(progress);
2349
+ }
2350
+ R2.step(import_picocolors6.default.bold("Installation"));
2351
+ const defaultDir = join9(process.env.HOME || "~", "cognova");
2352
+ let resolvedInstallDir;
2353
+ if (progress.partialConfig.installDir) {
2354
+ resolvedInstallDir = progress.partialConfig.installDir;
2355
+ R2.info(`Using install directory: ${resolvedInstallDir}`);
2356
+ } else {
2357
+ const installDir = await Ze({
2358
+ message: "Where should Cognova be installed?",
2359
+ placeholder: defaultDir,
2360
+ defaultValue: defaultDir
2361
+ });
2362
+ if (Ct(installDir)) process.exit(0);
2363
+ resolvedInstallDir = installDir.replace("~", process.env.HOME || "");
2364
+ await setupInstallDir(resolvedInstallDir);
2365
+ progress.partialConfig.installDir = resolvedInstallDir;
2366
+ progress.completedSteps.push("installDir");
2367
+ saveProgress(progress);
2368
+ }
2369
+ R2.step(import_picocolors6.default.bold("Vault"));
2370
+ const vault = progress.partialConfig.vault || await setupVault();
2371
+ if (!progress.completedSteps.includes("vault")) {
2372
+ progress.partialConfig.vault = vault;
2373
+ progress.completedSteps.push("vault");
2374
+ saveProgress(progress);
2375
+ }
2376
+ R2.step(import_picocolors6.default.bold("Database"));
2377
+ let database = progress.partialConfig.database;
2378
+ if (!database) {
2379
+ try {
2380
+ database = await withRetry(
2381
+ "Database setup",
2382
+ () => setupDatabase(prereqs.dockerReady),
2383
+ { canSkip: false }
2384
+ );
2385
+ progress.partialConfig.database = database;
2386
+ progress.completedSteps.push("database");
2387
+ saveProgress(progress);
2388
+ } catch (err) {
2389
+ if (err instanceof SkipError) {
2390
+ R2.error("Database setup is required and cannot be skipped");
2199
2391
  }
2392
+ process.exit(1);
2393
+ }
2394
+ }
2395
+ R2.step(import_picocolors6.default.bold("Network Access"));
2396
+ let accessMode;
2397
+ let appUrl;
2398
+ if (progress.partialConfig.accessMode && progress.partialConfig.appUrl) {
2399
+ accessMode = progress.partialConfig.accessMode;
2400
+ appUrl = progress.partialConfig.appUrl;
2401
+ R2.info(`Using access mode: ${accessMode} (${appUrl})`);
2402
+ } else {
2403
+ accessMode = await Je({
2404
+ message: "How will you access Cognova?",
2405
+ options: [
2406
+ { value: "localhost", label: "Local only", hint: "http://localhost:3000" },
2407
+ { value: "specific", label: "Specific IP or domain", hint: "LAN IP, hostname, or domain" },
2408
+ { value: "any", label: "Any connection", hint: "Accepts requests from any origin (0.0.0.0)" }
2409
+ ]
2200
2410
  });
2201
- if (Ct(pw)) process.exit(0);
2202
- const confirm = await He({
2203
- message: "Confirm password"
2411
+ if (Ct(accessMode)) process.exit(0);
2412
+ appUrl = "http://localhost:3000";
2413
+ if (accessMode === "specific") {
2414
+ const host = await Ze({
2415
+ message: "IP address or domain (include port if not 80/443)",
2416
+ placeholder: "192.168.1.100:3000"
2417
+ });
2418
+ if (Ct(host)) process.exit(0);
2419
+ appUrl = host.startsWith("http") ? host : `http://${host}`;
2420
+ }
2421
+ progress.partialConfig.accessMode = accessMode;
2422
+ progress.partialConfig.appUrl = appUrl;
2423
+ progress.completedSteps.push("network");
2424
+ saveProgress(progress);
2425
+ }
2426
+ if (!progress.completedSteps.includes("security")) {
2427
+ R2.warn(import_picocolors6.default.yellow(import_picocolors6.default.bold("Security Notice")));
2428
+ R2.warn([
2429
+ "Cognova gives an AI agent unrestricted access to this machine.",
2430
+ "It can read, write, and execute anything via the embedded terminal",
2431
+ "and Claude Code CLI.",
2432
+ "",
2433
+ ` ${import_picocolors6.default.dim("\u2022")} Do not run on a personal machine or alongside sensitive data`,
2434
+ ` ${import_picocolors6.default.dim("\u2022")} Use a dedicated VM, container, or isolated environment`,
2435
+ ` ${import_picocolors6.default.dim("\u2022")} Put a reverse proxy with TLS in front for remote access`,
2436
+ ` ${import_picocolors6.default.dim("\u2022")} Do not store SSH keys, cloud creds, or production secrets here`
2437
+ ].join("\n"));
2438
+ if (accessMode === "any") {
2439
+ R2.warn(import_picocolors6.default.red(
2440
+ "Binding to 0.0.0.0 exposes this to your entire network."
2441
+ ));
2442
+ }
2443
+ const proceed = await Re({
2444
+ message: "I understand the risks. Continue?",
2445
+ initialValue: false
2204
2446
  });
2205
- if (Ct(confirm)) process.exit(0);
2206
- if (pw === confirm) {
2207
- adminPassword = pw;
2208
- break;
2447
+ if (Ct(proceed) || !proceed) process.exit(0);
2448
+ progress.completedSteps.push("security");
2449
+ saveProgress(progress);
2450
+ }
2451
+ R2.step(import_picocolors6.default.bold("Authentication"));
2452
+ let adminEmail;
2453
+ let adminPassword;
2454
+ let adminName;
2455
+ if (progress.partialConfig.auth) {
2456
+ adminEmail = progress.partialConfig.auth.adminEmail;
2457
+ adminPassword = progress.partialConfig.auth.adminPassword;
2458
+ adminName = progress.partialConfig.auth.adminName;
2459
+ R2.info(`Using admin: ${adminEmail}`);
2460
+ } else {
2461
+ const emailInput = await Ze({
2462
+ message: "Admin email",
2463
+ placeholder: "admin@example.com",
2464
+ defaultValue: "admin@example.com"
2465
+ });
2466
+ if (Ct(emailInput)) process.exit(0);
2467
+ adminEmail = emailInput;
2468
+ while (true) {
2469
+ const pw = await He({
2470
+ message: "Admin password",
2471
+ validate: (v) => {
2472
+ if (!v || v.length < 8) return "Minimum 8 characters";
2473
+ }
2474
+ });
2475
+ if (Ct(pw)) process.exit(0);
2476
+ const confirm = await He({
2477
+ message: "Confirm password"
2478
+ });
2479
+ if (Ct(confirm)) process.exit(0);
2480
+ if (pw === confirm) {
2481
+ adminPassword = pw;
2482
+ break;
2483
+ }
2484
+ R2.warn("Passwords do not match. Try again.");
2209
2485
  }
2210
- R2.warn("Passwords do not match. Try again.");
2486
+ const nameInput = await Ze({
2487
+ message: "Admin display name",
2488
+ placeholder: personality.userName,
2489
+ defaultValue: personality.userName
2490
+ });
2491
+ if (Ct(nameInput)) process.exit(0);
2492
+ adminName = nameInput;
2493
+ progress.partialConfig.auth = {
2494
+ adminEmail,
2495
+ adminPassword,
2496
+ adminName,
2497
+ authSecret: crypto3.randomBytes(32).toString("base64")
2498
+ };
2499
+ progress.completedSteps.push("auth");
2500
+ saveProgress(progress);
2211
2501
  }
2212
- const adminName = await Ze({
2213
- message: "Admin display name",
2214
- placeholder: personality.userName,
2215
- defaultValue: personality.userName
2216
- });
2217
- if (Ct(adminName)) process.exit(0);
2218
2502
  const config = {
2219
2503
  personality,
2220
2504
  vault,
2221
2505
  database,
2222
- auth: {
2506
+ auth: progress.partialConfig.auth || {
2223
2507
  adminEmail,
2224
2508
  adminPassword,
2225
2509
  adminName,
@@ -2229,7 +2513,7 @@ async function init() {
2229
2513
  accessMode,
2230
2514
  installDir: resolvedInstallDir
2231
2515
  };
2232
- R2.step(import_picocolors5.default.bold("Configuration"));
2516
+ R2.step(import_picocolors6.default.bold("Configuration"));
2233
2517
  const s = bt2();
2234
2518
  s.start("Writing .env");
2235
2519
  writeEnvFile(config);
@@ -2237,24 +2521,86 @@ async function init() {
2237
2521
  s.start("Installing Claude Code configuration");
2238
2522
  await installClaudeConfig(config);
2239
2523
  s.stop("Claude config installed to ~/.claude/");
2240
- writeMetadata(resolvedInstallDir, vault.path, getPackageVersion());
2241
- R2.step(import_picocolors5.default.bold("Setup"));
2242
- s.start("Installing dependencies");
2243
- execSync5("pnpm install", { cwd: resolvedInstallDir, stdio: "pipe" });
2244
- s.stop("Dependencies installed");
2245
- R2.step(import_picocolors5.default.bold("Launch"));
2246
- await setupAndStart(config);
2247
- await waitForHealth("http://localhost:3000");
2248
- R2.step(import_picocolors5.default.bold("Summary"));
2524
+ writeMetadata(
2525
+ resolvedInstallDir,
2526
+ vault.path,
2527
+ getPackageVersion(),
2528
+ database.password,
2529
+ database.port
2530
+ );
2531
+ R2.step(import_picocolors6.default.bold("Setup"));
2532
+ if (!progress.completedSteps.includes("install")) {
2533
+ try {
2534
+ await withRetry(
2535
+ "Dependency installation",
2536
+ async () => {
2537
+ s.start("Installing dependencies");
2538
+ execSync5("pnpm install", { cwd: resolvedInstallDir, stdio: "pipe" });
2539
+ s.stop("Dependencies installed");
2540
+ },
2541
+ { canSkip: false }
2542
+ );
2543
+ progress.completedSteps.push("install");
2544
+ saveProgress(progress);
2545
+ } catch (err) {
2546
+ s.stop("Dependency installation failed");
2547
+ if (err instanceof SkipError) {
2548
+ R2.error("Dependency installation is required and cannot be skipped");
2549
+ }
2550
+ process.exit(1);
2551
+ }
2552
+ } else {
2553
+ R2.info("Dependencies already installed");
2554
+ }
2555
+ R2.step(import_picocolors6.default.bold("Launch"));
2556
+ if (!progress.completedSteps.includes("pm2")) {
2557
+ try {
2558
+ await withRetry(
2559
+ "PM2 setup and start",
2560
+ () => setupAndStart(config),
2561
+ { canSkip: false }
2562
+ );
2563
+ progress.completedSteps.push("pm2");
2564
+ saveProgress(progress);
2565
+ } catch (err) {
2566
+ if (err instanceof SkipError) {
2567
+ R2.error("PM2 setup is required and cannot be skipped");
2568
+ }
2569
+ process.exit(1);
2570
+ }
2571
+ } else {
2572
+ R2.info("PM2 already configured");
2573
+ }
2574
+ if (!progress.completedSteps.includes("health")) {
2575
+ try {
2576
+ await withRetry(
2577
+ "Health check",
2578
+ () => waitForHealth("http://localhost:3000"),
2579
+ { canSkip: true }
2580
+ );
2581
+ progress.completedSteps.push("health");
2582
+ saveProgress(progress);
2583
+ } catch (err) {
2584
+ if (err instanceof SkipError) {
2585
+ R2.warn("Health check skipped - app may not be fully started");
2586
+ R2.info("Check status with: pm2 status");
2587
+ } else {
2588
+ process.exit(1);
2589
+ }
2590
+ }
2591
+ } else {
2592
+ R2.info("App already healthy");
2593
+ }
2594
+ R2.step(import_picocolors6.default.bold("Summary"));
2249
2595
  R2.info([
2250
2596
  "",
2251
- ` ${import_picocolors5.default.cyan("App URL:")} ${config.appUrl}`,
2252
- ` ${import_picocolors5.default.cyan("Admin Login:")} ${config.auth.adminEmail}`,
2253
- ` ${import_picocolors5.default.cyan("Vault:")} ${vault.path}`,
2254
- ` ${import_picocolors5.default.cyan("Install Dir:")} ${resolvedInstallDir}`,
2255
- ` ${import_picocolors5.default.cyan("Agent:")} ${personality.agentName}`,
2597
+ ` ${import_picocolors6.default.cyan("App URL:")} ${config.appUrl}`,
2598
+ ` ${import_picocolors6.default.cyan("Admin Login:")} ${config.auth.adminEmail}`,
2599
+ ` ${import_picocolors6.default.cyan("Vault:")} ${vault.path}`,
2600
+ ` ${import_picocolors6.default.cyan("Install Dir:")} ${resolvedInstallDir}`,
2601
+ ` ${import_picocolors6.default.cyan("Agent:")} ${personality.agentName}`,
2256
2602
  "",
2257
- ` ${import_picocolors5.default.dim("Manage:")}`,
2603
+ ` ${import_picocolors6.default.dim("Manage:")}`,
2258
2604
  ` cognova start Start the app`,
2259
2605
  ` cognova stop Stop the app`,
2260
2606
  ` cognova restart Restart the app`,
@@ -2266,17 +2612,18 @@ async function init() {
2266
2612
  ` pm2 monit Monitor resources`,
2267
2613
  ""
2268
2614
  ].join("\n"));
2269
- Le(`${personality.agentName} is ready. Open ${import_picocolors5.default.underline(config.appUrl)} to get started.`);
2615
+ Le(`${personality.agentName} is ready. Open ${import_picocolors6.default.underline(config.appUrl)} to get started.`);
2616
+ clearProgress();
2270
2617
  }
2271
2618
 
2272
2619
  // src/commands/start.ts
2273
2620
  import { execSync as execSync6 } from "child_process";
2274
- import { existsSync as existsSync6 } from "fs";
2275
- import { join as join8 } from "path";
2621
+ import { existsSync as existsSync8 } from "fs";
2622
+ import { join as join10 } from "path";
2276
2623
  async function start() {
2277
2624
  const installDir = findInstallDir();
2278
- const ecosystem = join8(installDir, "ecosystem.config.cjs");
2279
- if (!existsSync6(ecosystem)) {
2625
+ const ecosystem = join10(installDir, "ecosystem.config.cjs");
2626
+ if (!existsSync8(ecosystem)) {
2280
2627
  console.error("No ecosystem.config.cjs found. Run `cognova init` first.");
2281
2628
  process.exit(1);
2282
2629
  }
@@ -2302,9 +2649,9 @@ async function restart() {
2302
2649
 
2303
2650
  // src/commands/update.ts
2304
2651
  import { execSync as execSync7 } from "child_process";
2305
- import { existsSync as existsSync7, cpSync as cpSync3, rmSync } from "fs";
2306
- import { join as join9 } from "path";
2307
- var import_picocolors6 = __toESM(require_picocolors(), 1);
2652
+ import { existsSync as existsSync9, cpSync as cpSync3, rmSync } from "fs";
2653
+ import { join as join11 } from "path";
2654
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
2308
2655
  var BACKUP_ITEMS = [
2309
2656
  "app",
2310
2657
  "server",
@@ -2318,33 +2665,33 @@ var BACKUP_ITEMS = [
2318
2665
  "pnpm-lock.yaml"
2319
2666
  ];
2320
2667
  function createBackup(installDir) {
2321
- const backupDir = join9(installDir, ".update-backup");
2322
- if (existsSync7(backupDir))
2668
+ const backupDir = join11(installDir, ".update-backup");
2669
+ if (existsSync9(backupDir))
2323
2670
  rmSync(backupDir, { recursive: true });
2324
2671
  for (const item of BACKUP_ITEMS) {
2325
- const src = join9(installDir, item);
2326
- if (!existsSync7(src)) continue;
2327
- const dest = join9(backupDir, item);
2672
+ const src = join11(installDir, item);
2673
+ if (!existsSync9(src)) continue;
2674
+ const dest = join11(backupDir, item);
2328
2675
  cpSync3(src, dest, { recursive: true });
2329
2676
  }
2330
2677
  return backupDir;
2331
2678
  }
2332
2679
  function restoreBackup(installDir, backupDir) {
2333
2680
  for (const item of BACKUP_ITEMS) {
2334
- const src = join9(backupDir, item);
2335
- if (!existsSync7(src)) continue;
2336
- const dest = join9(installDir, item);
2337
- if (existsSync7(dest))
2681
+ const src = join11(backupDir, item);
2682
+ if (!existsSync9(src)) continue;
2683
+ const dest = join11(installDir, item);
2684
+ if (existsSync9(dest))
2338
2685
  rmSync(dest, { recursive: true });
2339
2686
  cpSync3(src, dest, { recursive: true });
2340
2687
  }
2341
2688
  }
2342
2689
  function cleanupBackup(backupDir) {
2343
- if (existsSync7(backupDir))
2690
+ if (existsSync9(backupDir))
2344
2691
  rmSync(backupDir, { recursive: true });
2345
2692
  }
2346
2693
  async function update() {
2347
- We(import_picocolors6.default.bgCyan(import_picocolors6.default.black(" Cognova Update ")));
2694
+ We(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" Cognova Update ")));
2348
2695
  const installDir = findInstallDir();
2349
2696
  const metadata = readMetadata(installDir);
2350
2697
  if (!metadata) {
@@ -2366,7 +2713,7 @@ async function update() {
2366
2713
  Le("Nothing to update.");
2367
2714
  return;
2368
2715
  }
2369
- s.stop(`Update available: ${metadata.version} \u2192 ${import_picocolors6.default.green(latestVersion)}`);
2716
+ s.stop(`Update available: ${metadata.version} \u2192 ${import_picocolors7.default.green(latestVersion)}`);
2370
2717
  s.start("Creating backup");
2371
2718
  const backupDir = createBackup(installDir);
2372
2719
  s.stop("Backup created");
@@ -2395,14 +2742,14 @@ async function update() {
2395
2742
  s.stop("Migrations complete");
2396
2743
  } catch (err) {
2397
2744
  updateFailed = true;
2398
- s.stop(import_picocolors6.default.red("Update failed"));
2745
+ s.stop(import_picocolors7.default.red("Update failed"));
2399
2746
  const execErr = err;
2400
2747
  R2.error(`Error: ${execErr.message || err}`);
2401
2748
  if (execErr.stdout)
2402
- R2.error(import_picocolors6.default.dim(String(execErr.stdout).trim()));
2749
+ R2.error(import_picocolors7.default.dim(String(execErr.stdout).trim()));
2403
2750
  if (execErr.stderr)
2404
- R2.error(import_picocolors6.default.dim(String(execErr.stderr).trim()));
2405
- R2.step(import_picocolors6.default.bold("Rolling back"));
2751
+ R2.error(import_picocolors7.default.dim(String(execErr.stderr).trim()));
2752
+ R2.step(import_picocolors7.default.bold("Rolling back"));
2406
2753
  const rollbackSpinner = bt2();
2407
2754
  rollbackSpinner.start("Restoring previous version");
2408
2755
  try {
@@ -2411,7 +2758,7 @@ async function update() {
2411
2758
  rollbackSpinner.stop("Previous version restored");
2412
2759
  R2.info("Your previous installation has been restored.");
2413
2760
  } catch (rollbackErr) {
2414
- rollbackSpinner.stop(import_picocolors6.default.red("Rollback failed"));
2761
+ rollbackSpinner.stop(import_picocolors7.default.red("Rollback failed"));
2415
2762
  R2.error(`Rollback failed: ${rollbackErr instanceof Error ? rollbackErr.message : rollbackErr}`);
2416
2763
  R2.error(`Manual recovery: backup is at ${backupDir}`);
2417
2764
  Le("Update and rollback both failed. See errors above.");
@@ -2433,17 +2780,17 @@ async function update() {
2433
2780
  } catch {
2434
2781
  s.stop("PM2 restart failed \u2014 start manually with `cognova start`");
2435
2782
  }
2436
- R2.info(`Run ${import_picocolors6.default.cyan("cognova reset")} to regenerate CLAUDE.md or settings.json.`);
2783
+ R2.info(`Run ${import_picocolors7.default.cyan("cognova reset")} to regenerate CLAUDE.md or settings.json.`);
2437
2784
  Le(`Updated to v${latestVersion}`);
2438
2785
  }
2439
2786
 
2440
2787
  // src/commands/doctor.ts
2441
2788
  import { execSync as execSync8 } from "child_process";
2442
- import { existsSync as existsSync8 } from "fs";
2443
- import { join as join10 } from "path";
2444
- var import_picocolors7 = __toESM(require_picocolors(), 1);
2789
+ import { existsSync as existsSync10 } from "fs";
2790
+ import { join as join12 } from "path";
2791
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
2445
2792
  async function doctor() {
2446
- We(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" Cognova Doctor ")));
2793
+ We(import_picocolors8.default.bgCyan(import_picocolors8.default.black(" Cognova Doctor ")));
2447
2794
  const installDir = findInstallDir();
2448
2795
  const claudeDir = getClaudeDir();
2449
2796
  const metadata = readMetadata(installDir);
@@ -2453,35 +2800,55 @@ async function doctor() {
2453
2800
  const checks = [
2454
2801
  {
2455
2802
  name: ".env file",
2456
- check: () => existsSync8(join10(installDir, ".env"))
2803
+ check: () => existsSync10(join12(installDir, ".env")),
2804
+ fixDescription: 'Run "cognova reset" to regenerate'
2457
2805
  },
2458
2806
  {
2459
2807
  name: "node_modules",
2460
- check: () => existsSync8(join10(installDir, "node_modules"))
2808
+ check: () => existsSync10(join12(installDir, "node_modules")),
2809
+ fix: async () => {
2810
+ const s = bt2();
2811
+ s.start("Installing dependencies");
2812
+ execSync8("pnpm install", { cwd: installDir, stdio: "inherit" });
2813
+ s.stop("Dependencies installed");
2814
+ },
2815
+ fixDescription: "Install dependencies"
2461
2816
  },
2462
2817
  {
2463
2818
  name: "Build output",
2464
- check: () => existsSync8(join10(installDir, ".output", "server", "index.mjs"))
2819
+ check: () => existsSync10(join12(installDir, ".output", "server", "index.mjs")),
2820
+ fix: async () => {
2821
+ const s = bt2();
2822
+ s.start("Building application");
2823
+ execSync8("pnpm build", { cwd: installDir, stdio: "inherit" });
2824
+ s.stop("Build complete");
2825
+ },
2826
+ fixDescription: "Build the application"
2465
2827
  },
2466
2828
  {
2467
2829
  name: "~/.claude/CLAUDE.md",
2468
- check: () => existsSync8(join10(claudeDir, "CLAUDE.md"))
2830
+ check: () => existsSync10(join12(claudeDir, "CLAUDE.md")),
2831
+ fixDescription: 'Run "cognova reset --claude" to reinstall'
2469
2832
  },
2470
2833
  {
2471
2834
  name: "~/.claude/skills/",
2472
- check: () => existsSync8(join10(claudeDir, "skills"))
2835
+ check: () => existsSync10(join12(claudeDir, "skills")),
2836
+ fixDescription: 'Run "cognova reset --claude" to reinstall'
2473
2837
  },
2474
2838
  {
2475
2839
  name: "~/.claude/hooks/",
2476
- check: () => existsSync8(join10(claudeDir, "hooks"))
2840
+ check: () => existsSync10(join12(claudeDir, "hooks")),
2841
+ fixDescription: 'Run "cognova reset --claude" to reinstall'
2477
2842
  },
2478
2843
  {
2479
2844
  name: "~/.claude/rules/",
2480
- check: () => existsSync8(join10(claudeDir, "rules"))
2845
+ check: () => existsSync10(join12(claudeDir, "rules")),
2846
+ fixDescription: 'Run "cognova reset --claude" to reinstall'
2481
2847
  },
2482
2848
  {
2483
2849
  name: "~/.claude/settings.json",
2484
- check: () => existsSync8(join10(claudeDir, "settings.json"))
2850
+ check: () => existsSync10(join12(claudeDir, "settings.json")),
2851
+ fixDescription: 'Run "cognova reset --claude" to reinstall'
2485
2852
  },
2486
2853
  {
2487
2854
  name: "Claude Code CLI",
@@ -2492,7 +2859,8 @@ async function doctor() {
2492
2859
  } catch {
2493
2860
  return false;
2494
2861
  }
2495
- }
2862
+ },
2863
+ fixDescription: "Install with: npm install -g @anthropic-ai/claude-code"
2496
2864
  },
2497
2865
  {
2498
2866
  name: "Python 3",
@@ -2503,7 +2871,36 @@ async function doctor() {
2503
2871
  } catch {
2504
2872
  return false;
2505
2873
  }
2506
- }
2874
+ },
2875
+ fixDescription: "Install from https://python.org/"
2876
+ },
2877
+ {
2878
+ name: "Docker daemon",
2879
+ check: () => {
2880
+ try {
2881
+ execSync8("docker info", { stdio: "pipe" });
2882
+ return true;
2883
+ } catch {
2884
+ return false;
2885
+ }
2886
+ },
2887
+ fix: async () => {
2888
+ const s = bt2();
2889
+ s.start("Starting Docker daemon");
2890
+ try {
2891
+ if (process.platform === "darwin") {
2892
+ execSync8("open -a Docker", { stdio: "pipe" });
2893
+ R2.info("Docker Desktop is starting - this may take a moment");
2894
+ } else if (process.platform === "linux") {
2895
+ execSync8("sudo systemctl start docker", { stdio: "inherit" });
2896
+ }
2897
+ s.stop("Docker daemon start command issued");
2898
+ } catch (err) {
2899
+ s.stop("Failed to start Docker daemon");
2900
+ throw err;
2901
+ }
2902
+ },
2903
+ fixDescription: "Start Docker daemon"
2507
2904
  },
2508
2905
  {
2509
2906
  name: "PM2 process",
@@ -2517,7 +2914,19 @@ async function doctor() {
2517
2914
  } catch {
2518
2915
  return false;
2519
2916
  }
2520
- }
2917
+ },
2918
+ fix: async () => {
2919
+ const s = bt2();
2920
+ s.start("Starting PM2 process");
2921
+ try {
2922
+ execSync8("pm2 start ecosystem.config.cjs", { cwd: installDir, stdio: "inherit" });
2923
+ s.stop("PM2 process started");
2924
+ } catch (err) {
2925
+ s.stop("Failed to start PM2");
2926
+ throw err;
2927
+ }
2928
+ },
2929
+ fixDescription: "Start the PM2 process"
2521
2930
  },
2522
2931
  {
2523
2932
  name: "App health endpoint",
@@ -2547,7 +2956,7 @@ async function doctor() {
2547
2956
  name: "Vault directory",
2548
2957
  check: () => {
2549
2958
  if (!metadata) return false;
2550
- return existsSync8(metadata.vaultPath);
2959
+ return existsSync10(metadata.vaultPath);
2551
2960
  }
2552
2961
  },
2553
2962
  {
@@ -2563,32 +2972,63 @@ async function doctor() {
2563
2972
  }
2564
2973
  }
2565
2974
  ];
2566
- let passed = 0;
2567
- let failed = 0;
2568
- for (const { name, check } of checks) {
2975
+ const results = [];
2976
+ for (const check of checks) {
2569
2977
  try {
2570
- const result = check();
2978
+ const result = check.check();
2571
2979
  if (result === true) {
2572
- R2.success(`${import_picocolors7.default.green("PASS")} ${name}`);
2573
- passed++;
2980
+ R2.success(`${import_picocolors8.default.green("PASS")} ${check.name}`);
2981
+ results.push({ check, status: "pass" });
2574
2982
  } else if (result === false) {
2575
- R2.error(`${import_picocolors7.default.red("FAIL")} ${name}`);
2576
- failed++;
2983
+ R2.error(`${import_picocolors8.default.red("FAIL")} ${check.name}`);
2984
+ if (check.fixDescription)
2985
+ R2.info(` \u2192 ${import_picocolors8.default.dim(check.fixDescription)}`);
2986
+ results.push({ check, status: "fail" });
2577
2987
  } else {
2578
- R2.warn(`${import_picocolors7.default.yellow("WARN")} ${name}: ${result}`);
2988
+ R2.warn(`${import_picocolors8.default.yellow("WARN")} ${check.name}: ${result}`);
2989
+ results.push({ check, status: "warn", message: result });
2579
2990
  }
2580
2991
  } catch {
2581
- R2.error(`${import_picocolors7.default.red("FAIL")} ${name}`);
2582
- failed++;
2992
+ R2.error(`${import_picocolors8.default.red("FAIL")} ${check.name}`);
2993
+ if (check.fixDescription)
2994
+ R2.info(` \u2192 ${import_picocolors8.default.dim(check.fixDescription)}`);
2995
+ results.push({ check, status: "fail" });
2996
+ }
2997
+ }
2998
+ const passed = results.filter((r) => r.status === "pass").length;
2999
+ const failed = results.filter((r) => r.status === "fail").length;
3000
+ const fixableFailures = results.filter((r) => r.status === "fail" && r.check.fix);
3001
+ if (fixableFailures.length > 0) {
3002
+ R2.info("");
3003
+ const runFixes = await Re({
3004
+ message: `${fixableFailures.length} issue(s) can be auto-fixed. Run fixes now?`,
3005
+ initialValue: true
3006
+ });
3007
+ if (!Ct(runFixes) && runFixes) {
3008
+ for (const { check } of fixableFailures) {
3009
+ if (!check.fix) continue;
3010
+ R2.step(import_picocolors8.default.bold(`Fixing: ${check.name}`));
3011
+ try {
3012
+ await check.fix();
3013
+ const result = check.check();
3014
+ if (result === true) {
3015
+ R2.success(`${import_picocolors8.default.green("\u2713")} ${check.name} is now fixed`);
3016
+ } else {
3017
+ R2.warn(`${import_picocolors8.default.yellow("!")} ${check.name} still failing after fix attempt`);
3018
+ }
3019
+ } catch (err) {
3020
+ R2.error(`${import_picocolors8.default.red("\u2717")} Failed to fix ${check.name}: ${err}`);
3021
+ }
3022
+ }
2583
3023
  }
2584
3024
  }
2585
3025
  Le(`${passed} passed, ${failed} failed`);
2586
3026
  }
2587
3027
 
2588
3028
  // src/commands/reset.ts
2589
- var import_picocolors8 = __toESM(require_picocolors(), 1);
3029
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
2590
3030
  async function reset() {
2591
- We(import_picocolors8.default.bgCyan(import_picocolors8.default.black(" Cognova Reset ")));
3031
+ We(import_picocolors9.default.bgCyan(import_picocolors9.default.black(" Cognova Reset ")));
2592
3032
  const installDir = findInstallDir();
2593
3033
  const metadata = readMetadata(installDir);
2594
3034
  if (!metadata) {