@hanzo/dev 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/dev.js CHANGED
@@ -921,9 +921,9 @@ var require_command = __commonJS({
921
921
  "node_modules/commander/lib/command.js"(exports2) {
922
922
  var EventEmitter = require("events").EventEmitter;
923
923
  var childProcess = require("child_process");
924
- var path = require("path");
925
- var fs = require("fs");
926
- var process2 = require("process");
924
+ var path2 = require("path");
925
+ var fs2 = require("fs");
926
+ var process3 = require("process");
927
927
  var { Argument: Argument2, humanReadableArgName } = require_argument();
928
928
  var { CommanderError: CommanderError2 } = require_error();
929
929
  var { Help: Help2 } = require_help();
@@ -969,10 +969,10 @@ var require_command = __commonJS({
969
969
  this._showHelpAfterError = false;
970
970
  this._showSuggestionAfterError = true;
971
971
  this._outputConfiguration = {
972
- writeOut: (str) => process2.stdout.write(str),
973
- writeErr: (str) => process2.stderr.write(str),
974
- getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0,
975
- getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : void 0,
972
+ writeOut: (str) => process3.stdout.write(str),
973
+ writeErr: (str) => process3.stderr.write(str),
974
+ getOutHelpWidth: () => process3.stdout.isTTY ? process3.stdout.columns : void 0,
975
+ getErrHelpWidth: () => process3.stderr.isTTY ? process3.stderr.columns : void 0,
976
976
  outputError: (str, write) => write(str)
977
977
  };
978
978
  this._hidden = false;
@@ -1329,7 +1329,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1329
1329
  if (this._exitCallback) {
1330
1330
  this._exitCallback(new CommanderError2(exitCode, code, message));
1331
1331
  }
1332
- process2.exit(exitCode);
1332
+ process3.exit(exitCode);
1333
1333
  }
1334
1334
  /**
1335
1335
  * Register callback `fn` for the command.
@@ -1660,8 +1660,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1660
1660
  }
1661
1661
  parseOptions = parseOptions || {};
1662
1662
  if (argv === void 0) {
1663
- argv = process2.argv;
1664
- if (process2.versions && process2.versions.electron) {
1663
+ argv = process3.argv;
1664
+ if (process3.versions && process3.versions.electron) {
1665
1665
  parseOptions.from = "electron";
1666
1666
  }
1667
1667
  }
@@ -1674,7 +1674,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1674
1674
  userArgs = argv.slice(2);
1675
1675
  break;
1676
1676
  case "electron":
1677
- if (process2.defaultApp) {
1677
+ if (process3.defaultApp) {
1678
1678
  this._scriptPath = argv[1];
1679
1679
  userArgs = argv.slice(2);
1680
1680
  } else {
@@ -1745,10 +1745,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1745
1745
  let launchWithNode = false;
1746
1746
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1747
1747
  function findFile(baseDir, baseName) {
1748
- const localBin = path.resolve(baseDir, baseName);
1749
- if (fs.existsSync(localBin)) return localBin;
1750
- if (sourceExt.includes(path.extname(baseName))) return void 0;
1751
- const foundExt = sourceExt.find((ext) => fs.existsSync(`${localBin}${ext}`));
1748
+ const localBin = path2.resolve(baseDir, baseName);
1749
+ if (fs2.existsSync(localBin)) return localBin;
1750
+ if (sourceExt.includes(path2.extname(baseName))) return void 0;
1751
+ const foundExt = sourceExt.find((ext) => fs2.existsSync(`${localBin}${ext}`));
1752
1752
  if (foundExt) return `${localBin}${foundExt}`;
1753
1753
  return void 0;
1754
1754
  }
@@ -1759,41 +1759,41 @@ Expecting one of '${allowedValues.join("', '")}'`);
1759
1759
  if (this._scriptPath) {
1760
1760
  let resolvedScriptPath;
1761
1761
  try {
1762
- resolvedScriptPath = fs.realpathSync(this._scriptPath);
1762
+ resolvedScriptPath = fs2.realpathSync(this._scriptPath);
1763
1763
  } catch (err) {
1764
1764
  resolvedScriptPath = this._scriptPath;
1765
1765
  }
1766
- executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
1766
+ executableDir = path2.resolve(path2.dirname(resolvedScriptPath), executableDir);
1767
1767
  }
1768
1768
  if (executableDir) {
1769
1769
  let localFile = findFile(executableDir, executableFile);
1770
1770
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1771
- const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
1771
+ const legacyName = path2.basename(this._scriptPath, path2.extname(this._scriptPath));
1772
1772
  if (legacyName !== this._name) {
1773
1773
  localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
1774
1774
  }
1775
1775
  }
1776
1776
  executableFile = localFile || executableFile;
1777
1777
  }
1778
- launchWithNode = sourceExt.includes(path.extname(executableFile));
1778
+ launchWithNode = sourceExt.includes(path2.extname(executableFile));
1779
1779
  let proc;
1780
- if (process2.platform !== "win32") {
1780
+ if (process3.platform !== "win32") {
1781
1781
  if (launchWithNode) {
1782
1782
  args.unshift(executableFile);
1783
- args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1784
- proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" });
1783
+ args = incrementNodeInspectorPort(process3.execArgv).concat(args);
1784
+ proc = childProcess.spawn(process3.argv[0], args, { stdio: "inherit" });
1785
1785
  } else {
1786
1786
  proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
1787
1787
  }
1788
1788
  } else {
1789
1789
  args.unshift(executableFile);
1790
- args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1791
- proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
1790
+ args = incrementNodeInspectorPort(process3.execArgv).concat(args);
1791
+ proc = childProcess.spawn(process3.execPath, args, { stdio: "inherit" });
1792
1792
  }
1793
1793
  if (!proc.killed) {
1794
1794
  const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
1795
1795
  signals.forEach((signal) => {
1796
- process2.on(signal, () => {
1796
+ process3.on(signal, () => {
1797
1797
  if (proc.killed === false && proc.exitCode === null) {
1798
1798
  proc.kill(signal);
1799
1799
  }
@@ -1802,10 +1802,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1802
1802
  }
1803
1803
  const exitCallback = this._exitCallback;
1804
1804
  if (!exitCallback) {
1805
- proc.on("close", process2.exit.bind(process2));
1805
+ proc.on("close", process3.exit.bind(process3));
1806
1806
  } else {
1807
1807
  proc.on("close", () => {
1808
- exitCallback(new CommanderError2(process2.exitCode || 0, "commander.executeSubCommandAsync", "(close)"));
1808
+ exitCallback(new CommanderError2(process3.exitCode || 0, "commander.executeSubCommandAsync", "(close)"));
1809
1809
  });
1810
1810
  }
1811
1811
  proc.on("error", (err) => {
@@ -1820,7 +1820,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1820
1820
  throw new Error(`'${executableFile}' not executable`);
1821
1821
  }
1822
1822
  if (!exitCallback) {
1823
- process2.exit(1);
1823
+ process3.exit(1);
1824
1824
  } else {
1825
1825
  const wrappedError = new CommanderError2(1, "commander.executeSubCommandAsync", "(error)");
1826
1826
  wrappedError.nestedError = err;
@@ -2286,11 +2286,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2286
2286
  */
2287
2287
  _parseOptionsEnv() {
2288
2288
  this.options.forEach((option) => {
2289
- if (option.envVar && option.envVar in process2.env) {
2289
+ if (option.envVar && option.envVar in process3.env) {
2290
2290
  const optionKey = option.attributeName();
2291
2291
  if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(this.getOptionValueSource(optionKey))) {
2292
2292
  if (option.required || option.optional) {
2293
- this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
2293
+ this.emit(`optionEnv:${option.name()}`, process3.env[option.envVar]);
2294
2294
  } else {
2295
2295
  this.emit(`optionEnv:${option.name()}`);
2296
2296
  }
@@ -2558,7 +2558,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2558
2558
  * @return {Command}
2559
2559
  */
2560
2560
  nameFromFilename(filename) {
2561
- this._name = path.basename(filename, path.extname(filename));
2561
+ this._name = path2.basename(filename, path2.extname(filename));
2562
2562
  return this;
2563
2563
  }
2564
2564
  /**
@@ -2572,9 +2572,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2572
2572
  * @param {string} [path]
2573
2573
  * @return {string|null|Command}
2574
2574
  */
2575
- executableDir(path2) {
2576
- if (path2 === void 0) return this._executableDir;
2577
- this._executableDir = path2;
2575
+ executableDir(path3) {
2576
+ if (path3 === void 0) return this._executableDir;
2577
+ this._executableDir = path3;
2578
2578
  return this;
2579
2579
  }
2580
2580
  /**
@@ -2666,7 +2666,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2666
2666
  */
2667
2667
  help(contextOptions) {
2668
2668
  this.outputHelp(contextOptions);
2669
- let exitCode = process2.exitCode || 0;
2669
+ let exitCode = process3.exitCode || 0;
2670
2670
  if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
2671
2671
  exitCode = 1;
2672
2672
  }
@@ -3059,10 +3059,10 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
3059
3059
  return 3;
3060
3060
  }
3061
3061
  if ("TERM_PROGRAM" in env) {
3062
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
3062
+ const version2 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
3063
3063
  switch (env.TERM_PROGRAM) {
3064
3064
  case "iTerm.app": {
3065
- return version >= 3 ? 3 : 2;
3065
+ return version2 >= 3 ? 3 : 2;
3066
3066
  }
3067
3067
  case "Apple_Terminal": {
3068
3068
  return 2;
@@ -3271,18 +3271,347 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
3271
3271
  var source_default = chalk;
3272
3272
 
3273
3273
  // src/cli/dev.ts
3274
+ var import_inquirer = __toESM(require("inquirer"));
3275
+ var import_child_process = require("child_process");
3276
+ var fs = __toESM(require("fs"));
3277
+ var path = __toESM(require("path"));
3278
+ var os2 = __toESM(require("os"));
3274
3279
  var program2 = new Command();
3275
- program2.name("@hanzo/dev").description("Hanzo Dev - Meta AI development CLI").version("1.0.0");
3276
- program2.command("claude [query...]").description("Run Claude AI").action((query) => {
3277
- console.log(source_default.blue("\u{1F916} Running Claude AI..."));
3278
- console.log("Query:", query.join(" "));
3280
+ function loadEnvFiles() {
3281
+ const envFiles = [".env", ".env.local", ".env.development", ".env.production"];
3282
+ const cwd = process.cwd();
3283
+ envFiles.forEach((file) => {
3284
+ const filePath = path.join(cwd, file);
3285
+ if (fs.existsSync(filePath)) {
3286
+ const content = fs.readFileSync(filePath, "utf-8");
3287
+ content.split("\n").forEach((line) => {
3288
+ const match = line.match(/^([^=]+)=(.*)$/);
3289
+ if (match) {
3290
+ const key = match[1].trim();
3291
+ const value = match[2].trim();
3292
+ if (!process.env[key]) {
3293
+ process.env[key] = value;
3294
+ }
3295
+ }
3296
+ });
3297
+ }
3298
+ });
3299
+ }
3300
+ loadEnvFiles();
3301
+ var TOOLS = {
3302
+ claude: {
3303
+ name: "Claude (Anthropic)",
3304
+ command: "claude-code",
3305
+ checkCommand: "which claude-code",
3306
+ description: "Claude Code - AI coding assistant",
3307
+ color: source_default.blue,
3308
+ apiKeys: ["ANTHROPIC_API_KEY", "CLAUDE_API_KEY"],
3309
+ priority: 1
3310
+ },
3311
+ aider: {
3312
+ name: "Aider",
3313
+ command: "aider",
3314
+ checkCommand: "which aider",
3315
+ description: "AI pair programming in your terminal",
3316
+ color: source_default.green,
3317
+ apiKeys: ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "CLAUDE_API_KEY"],
3318
+ priority: 2
3319
+ },
3320
+ openhands: {
3321
+ name: "OpenHands",
3322
+ command: "openhands",
3323
+ checkCommand: "which openhands",
3324
+ description: "AI software development agent",
3325
+ color: source_default.magenta,
3326
+ apiKeys: ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "LLM_API_KEY"],
3327
+ priority: 3
3328
+ },
3329
+ gemini: {
3330
+ name: "Gemini (Google)",
3331
+ command: "gemini",
3332
+ checkCommand: "which gemini",
3333
+ description: "Google Gemini AI assistant",
3334
+ color: source_default.yellow,
3335
+ apiKeys: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
3336
+ priority: 4
3337
+ },
3338
+ codex: {
3339
+ name: "OpenAI Codex",
3340
+ command: "codex",
3341
+ checkCommand: "which codex",
3342
+ description: "OpenAI coding assistant",
3343
+ color: source_default.cyan,
3344
+ apiKeys: ["OPENAI_API_KEY"],
3345
+ priority: 5
3346
+ }
3347
+ };
3348
+ function hasApiKey(tool) {
3349
+ const toolConfig = TOOLS[tool];
3350
+ if (!toolConfig || !toolConfig.apiKeys) return false;
3351
+ return toolConfig.apiKeys.some((key) => !!process.env[key]);
3352
+ }
3353
+ async function isToolInstalled(tool) {
3354
+ return new Promise((resolve) => {
3355
+ var _a;
3356
+ const checkCmd = ((_a = TOOLS[tool]) == null ? void 0 : _a.checkCommand) || `which ${tool}`;
3357
+ const check = (0, import_child_process.spawn)("sh", ["-c", checkCmd]);
3358
+ check.on("close", (code) => {
3359
+ resolve(code === 0);
3360
+ });
3361
+ });
3362
+ }
3363
+ async function getAvailableTools() {
3364
+ const available = [];
3365
+ for (const toolKey of Object.keys(TOOLS)) {
3366
+ const isInstalled = await isToolInstalled(toolKey);
3367
+ const hasKey = hasApiKey(toolKey);
3368
+ if (isInstalled || hasKey) {
3369
+ available.push(toolKey);
3370
+ }
3371
+ }
3372
+ return available.sort((a, b) => {
3373
+ const priorityA = TOOLS[a].priority;
3374
+ const priorityB = TOOLS[b].priority;
3375
+ return priorityA - priorityB;
3376
+ });
3377
+ }
3378
+ async function getDefaultTool() {
3379
+ const availableTools = await getAvailableTools();
3380
+ if (availableTools.length === 0) return null;
3381
+ for (const tool of availableTools) {
3382
+ if (await isToolInstalled(tool) && hasApiKey(tool)) {
3383
+ return tool;
3384
+ }
3385
+ }
3386
+ return availableTools[0];
3387
+ }
3388
+ function runTool(tool, args = []) {
3389
+ const toolConfig = TOOLS[tool];
3390
+ if (!toolConfig) {
3391
+ console.error(source_default.red(`Unknown tool: ${tool}`));
3392
+ process.exit(1);
3393
+ }
3394
+ console.log(toolConfig.color(`
3395
+ \u{1F680} Launching ${toolConfig.name}...
3396
+ `));
3397
+ const child = (0, import_child_process.spawn)(toolConfig.command, args, {
3398
+ stdio: "inherit",
3399
+ shell: true,
3400
+ env: process.env
3401
+ // Pass through all environment variables
3402
+ });
3403
+ child.on("error", (error) => {
3404
+ var _a;
3405
+ console.error(source_default.red(`Failed to start ${toolConfig.name}: ${error.message}`));
3406
+ if (!hasApiKey(tool)) {
3407
+ console.log(source_default.yellow(`
3408
+ Make sure you have one of these API keys configured:`));
3409
+ (_a = toolConfig.apiKeys) == null ? void 0 : _a.forEach((key) => {
3410
+ console.log(source_default.gray(` - ${key}`));
3411
+ });
3412
+ }
3413
+ process.exit(1);
3414
+ });
3415
+ child.on("exit", (code) => {
3416
+ if (code !== 0) {
3417
+ console.error(source_default.red(`${toolConfig.name} exited with code ${code}`));
3418
+ }
3419
+ process.exit(code || 0);
3420
+ });
3421
+ }
3422
+ async function interactiveMode() {
3423
+ console.log(source_default.bold.cyan("\n\u{1F916} Hanzo Dev - AI Development Assistant\n"));
3424
+ const envFiles = [".env", ".env.local", ".env.development", ".env.production"];
3425
+ const detectedEnvFiles = envFiles.filter((file) => fs.existsSync(path.join(process.cwd(), file)));
3426
+ if (detectedEnvFiles.length > 0) {
3427
+ console.log(source_default.gray("\u{1F4C4} Detected environment files:"));
3428
+ detectedEnvFiles.forEach((file) => {
3429
+ console.log(source_default.gray(` - ${file}`));
3430
+ });
3431
+ console.log();
3432
+ }
3433
+ const availableTools = await getAvailableTools();
3434
+ const defaultTool = await getDefaultTool();
3435
+ if (availableTools.length === 0) {
3436
+ console.log(source_default.yellow("No AI tools available. Please either:"));
3437
+ console.log(source_default.yellow("\n1. Install a tool:"));
3438
+ console.log(source_default.gray(" npm install -g @hanzo/claude-code"));
3439
+ console.log(source_default.gray(" pip install aider-chat"));
3440
+ console.log(source_default.gray(" pip install openhands"));
3441
+ console.log(source_default.yellow("\n2. Or configure API keys in your .env file:"));
3442
+ console.log(source_default.gray(" ANTHROPIC_API_KEY=sk-ant-..."));
3443
+ console.log(source_default.gray(" OPENAI_API_KEY=sk-..."));
3444
+ console.log(source_default.gray(" GOOGLE_API_KEY=..."));
3445
+ process.exit(1);
3446
+ }
3447
+ console.log(source_default.gray("\u{1F511} Detected API keys:"));
3448
+ const allApiKeys = /* @__PURE__ */ new Set();
3449
+ Object.values(TOOLS).forEach((tool) => {
3450
+ var _a;
3451
+ (_a = tool.apiKeys) == null ? void 0 : _a.forEach((key) => allApiKeys.add(key));
3452
+ });
3453
+ let hasAnyKey = false;
3454
+ allApiKeys.forEach((key) => {
3455
+ if (process.env[key]) {
3456
+ console.log(source_default.green(` \u2713 ${key}`));
3457
+ hasAnyKey = true;
3458
+ }
3459
+ });
3460
+ if (!hasAnyKey) {
3461
+ console.log(source_default.gray(" (none detected)"));
3462
+ }
3463
+ console.log();
3464
+ const choices = await Promise.all(availableTools.map(async (tool) => {
3465
+ const toolConfig = TOOLS[tool];
3466
+ const isInstalled = await isToolInstalled(tool);
3467
+ const hasKey = hasApiKey(tool);
3468
+ let status = "";
3469
+ if (isInstalled && hasKey) {
3470
+ status = source_default.green(" [Installed + API Key]");
3471
+ } else if (isInstalled) {
3472
+ status = source_default.yellow(" [Installed]");
3473
+ } else if (hasKey) {
3474
+ status = source_default.cyan(" [API Key Only]");
3475
+ }
3476
+ return {
3477
+ name: `${toolConfig.name} - ${toolConfig.description}${status}`,
3478
+ value: tool,
3479
+ short: toolConfig.name
3480
+ };
3481
+ }));
3482
+ const { selectedTool } = await import_inquirer.default.prompt([
3483
+ {
3484
+ type: "list",
3485
+ name: "selectedTool",
3486
+ message: "Select an AI tool to launch:",
3487
+ choices,
3488
+ default: defaultTool
3489
+ }
3490
+ ]);
3491
+ const { passDirectory } = await import_inquirer.default.prompt([
3492
+ {
3493
+ type: "confirm",
3494
+ name: "passDirectory",
3495
+ message: `Open in current directory (${process.cwd()})?`,
3496
+ default: true
3497
+ }
3498
+ ]);
3499
+ const args = passDirectory ? ["."] : [];
3500
+ runTool(selectedTool, args);
3501
+ }
3502
+ var packagePath = path.join(__dirname, "../../package.json");
3503
+ var version = "1.2.0";
3504
+ try {
3505
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
3506
+ version = packageJson.version;
3507
+ } catch (error) {
3508
+ }
3509
+ program2.name("dev").description("Hanzo Dev - Meta AI development CLI that manages and runs all AI coding assistants").version(version);
3510
+ Object.entries(TOOLS).forEach(([toolKey, toolConfig]) => {
3511
+ program2.command(`${toolKey} [args...]`).description(`Launch ${toolConfig.name}`).action(async (args) => {
3512
+ var _a;
3513
+ const isInstalled = await isToolInstalled(toolKey);
3514
+ const hasKey = hasApiKey(toolKey);
3515
+ if (!isInstalled && !hasKey) {
3516
+ console.error(source_default.red(`${toolConfig.name} is not available.`));
3517
+ console.log(source_default.yellow(`
3518
+ To use ${toolConfig.name}, you need to either:`));
3519
+ console.log(source_default.yellow("1. Install it:"));
3520
+ console.log(source_default.gray(` Follow installation instructions for ${toolConfig.name}`));
3521
+ console.log(source_default.yellow("\n2. Configure API keys:"));
3522
+ (_a = toolConfig.apiKeys) == null ? void 0 : _a.forEach((key) => {
3523
+ console.log(source_default.gray(` ${key}=your-api-key`));
3524
+ });
3525
+ process.exit(1);
3526
+ }
3527
+ if (!isInstalled) {
3528
+ console.log(source_default.yellow(`Note: ${toolConfig.name} is not installed locally, using API mode.`));
3529
+ }
3530
+ runTool(toolKey, args);
3531
+ });
3279
3532
  });
3280
- program2.command("codex [query...]").description("Run OpenAI Codex").action((query) => {
3281
- console.log(source_default.green("\u{1F916} Running OpenAI Codex..."));
3282
- console.log("Query:", query.join(" "));
3533
+ program2.command("list").alias("ls").description("List all available AI tools and API keys").action(async () => {
3534
+ console.log(source_default.bold.cyan("\n\u{1F4CB} AI Tools Status:\n"));
3535
+ const envFiles = [".env", ".env.local", ".env.development", ".env.production"];
3536
+ const detectedEnvFiles = envFiles.filter((file) => fs.existsSync(path.join(process.cwd(), file)));
3537
+ if (detectedEnvFiles.length > 0) {
3538
+ console.log(source_default.bold("Environment files:"));
3539
+ detectedEnvFiles.forEach((file) => {
3540
+ console.log(source_default.gray(` \u{1F4C4} ${file}`));
3541
+ });
3542
+ console.log();
3543
+ }
3544
+ console.log(source_default.bold("API Keys:"));
3545
+ const allApiKeys = /* @__PURE__ */ new Set();
3546
+ Object.values(TOOLS).forEach((tool) => {
3547
+ var _a;
3548
+ (_a = tool.apiKeys) == null ? void 0 : _a.forEach((key) => allApiKeys.add(key));
3549
+ });
3550
+ let hasAnyKey = false;
3551
+ allApiKeys.forEach((key) => {
3552
+ var _a;
3553
+ if (process.env[key]) {
3554
+ console.log(source_default.green(` \u2713 ${key} = ${(_a = process.env[key]) == null ? void 0 : _a.substring(0, 10)}...`));
3555
+ hasAnyKey = true;
3556
+ }
3557
+ });
3558
+ if (!hasAnyKey) {
3559
+ console.log(source_default.gray(" (no API keys detected)"));
3560
+ }
3561
+ console.log();
3562
+ console.log(source_default.bold("Tools:"));
3563
+ for (const [toolKey, toolConfig] of Object.entries(TOOLS)) {
3564
+ const isInstalled = await isToolInstalled(toolKey);
3565
+ const hasKey = hasApiKey(toolKey);
3566
+ let status = source_default.red("\u2717 Not Available");
3567
+ if (isInstalled && hasKey) {
3568
+ status = source_default.green("\u2713 Ready (Installed + API Key)");
3569
+ } else if (isInstalled) {
3570
+ status = source_default.yellow("\u26A0 Installed (No API Key)");
3571
+ } else if (hasKey) {
3572
+ status = source_default.cyan("\u2601 API Mode (Not Installed)");
3573
+ }
3574
+ console.log(` ${status} ${toolConfig.color(toolConfig.name)}`);
3575
+ console.log(source_default.gray(` ${toolConfig.description}`));
3576
+ if (!hasKey && toolConfig.apiKeys) {
3577
+ console.log(source_default.gray(` Requires: ${toolConfig.apiKeys.join(" or ")}`));
3578
+ }
3579
+ }
3580
+ });
3581
+ program2.command("status").description("Show current working directory and environment").action(() => {
3582
+ console.log(source_default.bold.cyan("\n\u{1F4CA} Hanzo Dev Status\n"));
3583
+ console.log(`Current Directory: ${source_default.green(process.cwd())}`);
3584
+ console.log(`User: ${source_default.green(os2.userInfo().username)}`);
3585
+ console.log(`Node Version: ${source_default.green(process.version)}`);
3586
+ console.log(`Platform: ${source_default.green(os2.platform())}`);
3587
+ const envFiles = [".env", ".env.local", ".env.development", ".env.production"];
3588
+ const detectedEnvFiles = envFiles.filter((file) => fs.existsSync(path.join(process.cwd(), file)));
3589
+ if (detectedEnvFiles.length > 0) {
3590
+ console.log(`
3591
+ Environment Files:`);
3592
+ detectedEnvFiles.forEach((file) => {
3593
+ console.log(source_default.green(` \u2713 ${file}`));
3594
+ });
3595
+ }
3283
3596
  });
3284
- program2.command("gemini [query...]").description("Run Google Gemini").action((query) => {
3285
- console.log(source_default.yellow("\u{1F916} Running Google Gemini..."));
3286
- console.log("Query:", query.join(" "));
3597
+ program2.action(async () => {
3598
+ const defaultTool = await getDefaultTool();
3599
+ if (defaultTool && process.argv.length === 2) {
3600
+ console.log(source_default.gray(`Auto-selecting ${TOOLS[defaultTool].name} based on available API keys...`));
3601
+ runTool(defaultTool, ["."]);
3602
+ } else {
3603
+ interactiveMode();
3604
+ }
3287
3605
  });
3288
3606
  program2.parse();
3607
+ if (process.argv.length === 2) {
3608
+ (async () => {
3609
+ const defaultTool = await getDefaultTool();
3610
+ if (defaultTool) {
3611
+ console.log(source_default.gray(`Auto-selecting ${TOOLS[defaultTool].name} based on available API keys...`));
3612
+ runTool(defaultTool, ["."]);
3613
+ } else {
3614
+ interactiveMode();
3615
+ }
3616
+ })();
3617
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/dev",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Hanzo Dev - Meta AI development CLI that manages and runs all LLMs and CLI tools",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/cli/dev.ts CHANGED
@@ -1,36 +1,429 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { spawn } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
4
9
 
5
10
  const program = new Command();
6
11
 
12
+ // Load environment variables from .env files
13
+ function loadEnvFiles(): void {
14
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
15
+ const cwd = process.cwd();
16
+
17
+ envFiles.forEach(file => {
18
+ const filePath = path.join(cwd, file);
19
+ if (fs.existsSync(filePath)) {
20
+ const content = fs.readFileSync(filePath, 'utf-8');
21
+ content.split('\n').forEach(line => {
22
+ const match = line.match(/^([^=]+)=(.*)$/);
23
+ if (match) {
24
+ const key = match[1].trim();
25
+ const value = match[2].trim();
26
+ // Only set if not already in environment
27
+ if (!process.env[key]) {
28
+ process.env[key] = value;
29
+ }
30
+ }
31
+ });
32
+ }
33
+ });
34
+ }
35
+
36
+ // Load env files on startup
37
+ loadEnvFiles();
38
+
39
+ // Available tools configuration with API key detection
40
+ const TOOLS = {
41
+ claude: {
42
+ name: 'Claude (Anthropic)',
43
+ command: 'claude-code',
44
+ checkCommand: 'which claude-code',
45
+ description: 'Claude Code - AI coding assistant',
46
+ color: chalk.blue,
47
+ apiKeys: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
48
+ priority: 1
49
+ },
50
+ aider: {
51
+ name: 'Aider',
52
+ command: 'aider',
53
+ checkCommand: 'which aider',
54
+ description: 'AI pair programming in your terminal',
55
+ color: chalk.green,
56
+ apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
57
+ priority: 2
58
+ },
59
+ openhands: {
60
+ name: 'OpenHands',
61
+ command: 'openhands',
62
+ checkCommand: 'which openhands',
63
+ description: 'AI software development agent',
64
+ color: chalk.magenta,
65
+ apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'LLM_API_KEY'],
66
+ priority: 3
67
+ },
68
+ gemini: {
69
+ name: 'Gemini (Google)',
70
+ command: 'gemini',
71
+ checkCommand: 'which gemini',
72
+ description: 'Google Gemini AI assistant',
73
+ color: chalk.yellow,
74
+ apiKeys: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
75
+ priority: 4
76
+ },
77
+ codex: {
78
+ name: 'OpenAI Codex',
79
+ command: 'codex',
80
+ checkCommand: 'which codex',
81
+ description: 'OpenAI coding assistant',
82
+ color: chalk.cyan,
83
+ apiKeys: ['OPENAI_API_KEY'],
84
+ priority: 5
85
+ }
86
+ };
87
+
88
+ // Check if a tool has API keys configured
89
+ function hasApiKey(tool: string): boolean {
90
+ const toolConfig = TOOLS[tool as keyof typeof TOOLS];
91
+ if (!toolConfig || !toolConfig.apiKeys) return false;
92
+
93
+ return toolConfig.apiKeys.some(key => !!process.env[key]);
94
+ }
95
+
96
+ // Check if a tool is installed
97
+ async function isToolInstalled(tool: string): Promise<boolean> {
98
+ return new Promise((resolve) => {
99
+ const checkCmd = TOOLS[tool as keyof typeof TOOLS]?.checkCommand || `which ${tool}`;
100
+ const check = spawn('sh', ['-c', checkCmd]);
101
+ check.on('close', (code) => {
102
+ resolve(code === 0);
103
+ });
104
+ });
105
+ }
106
+
107
+ // Get list of available tools (installed OR has API key)
108
+ async function getAvailableTools(): Promise<string[]> {
109
+ const available: string[] = [];
110
+ for (const toolKey of Object.keys(TOOLS)) {
111
+ const isInstalled = await isToolInstalled(toolKey);
112
+ const hasKey = hasApiKey(toolKey);
113
+ if (isInstalled || hasKey) {
114
+ available.push(toolKey);
115
+ }
116
+ }
117
+ // Sort by priority
118
+ return available.sort((a, b) => {
119
+ const priorityA = TOOLS[a as keyof typeof TOOLS].priority;
120
+ const priorityB = TOOLS[b as keyof typeof TOOLS].priority;
121
+ return priorityA - priorityB;
122
+ });
123
+ }
124
+
125
+ // Get default tool based on API keys and installation
126
+ async function getDefaultTool(): Promise<string | null> {
127
+ const availableTools = await getAvailableTools();
128
+ if (availableTools.length === 0) return null;
129
+
130
+ // Prefer tools that have both API key and are installed
131
+ for (const tool of availableTools) {
132
+ if (await isToolInstalled(tool) && hasApiKey(tool)) {
133
+ return tool;
134
+ }
135
+ }
136
+
137
+ // Otherwise return first available
138
+ return availableTools[0];
139
+ }
140
+
141
+ // Run a tool with optional arguments
142
+ function runTool(tool: string, args: string[] = []): void {
143
+ const toolConfig = TOOLS[tool as keyof typeof TOOLS];
144
+ if (!toolConfig) {
145
+ console.error(chalk.red(`Unknown tool: ${tool}`));
146
+ process.exit(1);
147
+ }
148
+
149
+ console.log(toolConfig.color(`\nšŸš€ Launching ${toolConfig.name}...\n`));
150
+
151
+ const child = spawn(toolConfig.command, args, {
152
+ stdio: 'inherit',
153
+ shell: true,
154
+ env: process.env // Pass through all environment variables
155
+ });
156
+
157
+ child.on('error', (error) => {
158
+ console.error(chalk.red(`Failed to start ${toolConfig.name}: ${error.message}`));
159
+ if (!hasApiKey(tool)) {
160
+ console.log(chalk.yellow(`\nMake sure you have one of these API keys configured:`));
161
+ toolConfig.apiKeys?.forEach(key => {
162
+ console.log(chalk.gray(` - ${key}`));
163
+ });
164
+ }
165
+ process.exit(1);
166
+ });
167
+
168
+ child.on('exit', (code) => {
169
+ if (code !== 0) {
170
+ console.error(chalk.red(`${toolConfig.name} exited with code ${code}`));
171
+ }
172
+ process.exit(code || 0);
173
+ });
174
+ }
175
+
176
+ // Interactive mode
177
+ async function interactiveMode(): Promise<void> {
178
+ console.log(chalk.bold.cyan('\nšŸ¤– Hanzo Dev - AI Development Assistant\n'));
179
+
180
+ // Show detected environment files
181
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
182
+ const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
183
+ if (detectedEnvFiles.length > 0) {
184
+ console.log(chalk.gray('šŸ“„ Detected environment files:'));
185
+ detectedEnvFiles.forEach(file => {
186
+ console.log(chalk.gray(` - ${file}`));
187
+ });
188
+ console.log();
189
+ }
190
+
191
+ const availableTools = await getAvailableTools();
192
+ const defaultTool = await getDefaultTool();
193
+
194
+ if (availableTools.length === 0) {
195
+ console.log(chalk.yellow('No AI tools available. Please either:'));
196
+ console.log(chalk.yellow('\n1. Install a tool:'));
197
+ console.log(chalk.gray(' npm install -g @hanzo/claude-code'));
198
+ console.log(chalk.gray(' pip install aider-chat'));
199
+ console.log(chalk.gray(' pip install openhands'));
200
+
201
+ console.log(chalk.yellow('\n2. Or configure API keys in your .env file:'));
202
+ console.log(chalk.gray(' ANTHROPIC_API_KEY=sk-ant-...'));
203
+ console.log(chalk.gray(' OPENAI_API_KEY=sk-...'));
204
+ console.log(chalk.gray(' GOOGLE_API_KEY=...'));
205
+ process.exit(1);
206
+ }
207
+
208
+ // Show available API keys
209
+ console.log(chalk.gray('šŸ”‘ Detected API keys:'));
210
+ const allApiKeys = new Set<string>();
211
+ Object.values(TOOLS).forEach(tool => {
212
+ tool.apiKeys?.forEach(key => allApiKeys.add(key));
213
+ });
214
+
215
+ let hasAnyKey = false;
216
+ allApiKeys.forEach(key => {
217
+ if (process.env[key]) {
218
+ console.log(chalk.green(` āœ“ ${key}`));
219
+ hasAnyKey = true;
220
+ }
221
+ });
222
+
223
+ if (!hasAnyKey) {
224
+ console.log(chalk.gray(' (none detected)'));
225
+ }
226
+ console.log();
227
+
228
+ const choices = await Promise.all(availableTools.map(async tool => {
229
+ const toolConfig = TOOLS[tool as keyof typeof TOOLS];
230
+ const isInstalled = await isToolInstalled(tool);
231
+ const hasKey = hasApiKey(tool);
232
+
233
+ let status = '';
234
+ if (isInstalled && hasKey) {
235
+ status = chalk.green(' [Installed + API Key]');
236
+ } else if (isInstalled) {
237
+ status = chalk.yellow(' [Installed]');
238
+ } else if (hasKey) {
239
+ status = chalk.cyan(' [API Key Only]');
240
+ }
241
+
242
+ return {
243
+ name: `${toolConfig.name} - ${toolConfig.description}${status}`,
244
+ value: tool,
245
+ short: toolConfig.name
246
+ };
247
+ }));
248
+
249
+ const { selectedTool } = await inquirer.prompt([
250
+ {
251
+ type: 'list',
252
+ name: 'selectedTool',
253
+ message: 'Select an AI tool to launch:',
254
+ choices: choices,
255
+ default: defaultTool
256
+ }
257
+ ]);
258
+
259
+ // Check if we should pass the current directory
260
+ const { passDirectory } = await inquirer.prompt([
261
+ {
262
+ type: 'confirm',
263
+ name: 'passDirectory',
264
+ message: `Open in current directory (${process.cwd()})?`,
265
+ default: true
266
+ }
267
+ ]);
268
+
269
+ const args = passDirectory ? ['.'] : [];
270
+ runTool(selectedTool, args);
271
+ }
272
+
273
+ // Setup version from package.json
274
+ const packagePath = path.join(__dirname, '../../package.json');
275
+ let version = '1.2.0';
276
+ try {
277
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
278
+ version = packageJson.version;
279
+ } catch (error) {
280
+ // Use default version if package.json not found
281
+ }
282
+
7
283
  program
8
- .name('@hanzo/dev')
9
- .description('Hanzo Dev - Meta AI development CLI')
10
- .version('1.0.0');
284
+ .name('dev')
285
+ .description('Hanzo Dev - Meta AI development CLI that manages and runs all AI coding assistants')
286
+ .version(version);
287
+
288
+ // Direct tool commands
289
+ Object.entries(TOOLS).forEach(([toolKey, toolConfig]) => {
290
+ program
291
+ .command(`${toolKey} [args...]`)
292
+ .description(`Launch ${toolConfig.name}`)
293
+ .action(async (args) => {
294
+ const isInstalled = await isToolInstalled(toolKey);
295
+ const hasKey = hasApiKey(toolKey);
296
+
297
+ if (!isInstalled && !hasKey) {
298
+ console.error(chalk.red(`${toolConfig.name} is not available.`));
299
+ console.log(chalk.yellow(`\nTo use ${toolConfig.name}, you need to either:`));
300
+ console.log(chalk.yellow('1. Install it:'));
301
+ console.log(chalk.gray(` Follow installation instructions for ${toolConfig.name}`));
302
+ console.log(chalk.yellow('\n2. Configure API keys:'));
303
+ toolConfig.apiKeys?.forEach(key => {
304
+ console.log(chalk.gray(` ${key}=your-api-key`));
305
+ });
306
+ process.exit(1);
307
+ }
308
+
309
+ if (!isInstalled) {
310
+ console.log(chalk.yellow(`Note: ${toolConfig.name} is not installed locally, using API mode.`));
311
+ }
312
+
313
+ runTool(toolKey, args);
314
+ });
315
+ });
11
316
 
317
+ // List installed tools
12
318
  program
13
- .command('claude [query...]')
14
- .description('Run Claude AI')
15
- .action((query) => {
16
- console.log(chalk.blue('šŸ¤– Running Claude AI...'));
17
- console.log('Query:', query.join(' '));
319
+ .command('list')
320
+ .alias('ls')
321
+ .description('List all available AI tools and API keys')
322
+ .action(async () => {
323
+ console.log(chalk.bold.cyan('\nšŸ“‹ AI Tools Status:\n'));
324
+
325
+ // Show environment files
326
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
327
+ const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
328
+ if (detectedEnvFiles.length > 0) {
329
+ console.log(chalk.bold('Environment files:'));
330
+ detectedEnvFiles.forEach(file => {
331
+ console.log(chalk.gray(` šŸ“„ ${file}`));
332
+ });
333
+ console.log();
334
+ }
335
+
336
+ // Show API keys
337
+ console.log(chalk.bold('API Keys:'));
338
+ const allApiKeys = new Set<string>();
339
+ Object.values(TOOLS).forEach(tool => {
340
+ tool.apiKeys?.forEach(key => allApiKeys.add(key));
341
+ });
342
+
343
+ let hasAnyKey = false;
344
+ allApiKeys.forEach(key => {
345
+ if (process.env[key]) {
346
+ console.log(chalk.green(` āœ“ ${key} = ${process.env[key]?.substring(0, 10)}...`));
347
+ hasAnyKey = true;
348
+ }
349
+ });
350
+
351
+ if (!hasAnyKey) {
352
+ console.log(chalk.gray(' (no API keys detected)'));
353
+ }
354
+ console.log();
355
+
356
+ // Show tools
357
+ console.log(chalk.bold('Tools:'));
358
+ for (const [toolKey, toolConfig] of Object.entries(TOOLS)) {
359
+ const isInstalled = await isToolInstalled(toolKey);
360
+ const hasKey = hasApiKey(toolKey);
361
+
362
+ let status = chalk.red('āœ— Not Available');
363
+ if (isInstalled && hasKey) {
364
+ status = chalk.green('āœ“ Ready (Installed + API Key)');
365
+ } else if (isInstalled) {
366
+ status = chalk.yellow('⚠ Installed (No API Key)');
367
+ } else if (hasKey) {
368
+ status = chalk.cyan('☁ API Mode (Not Installed)');
369
+ }
370
+
371
+ console.log(` ${status} ${toolConfig.color(toolConfig.name)}`);
372
+ console.log(chalk.gray(` ${toolConfig.description}`));
373
+ if (!hasKey && toolConfig.apiKeys) {
374
+ console.log(chalk.gray(` Requires: ${toolConfig.apiKeys.join(' or ')}`));
375
+ }
376
+ }
18
377
  });
19
378
 
379
+ // Status command
20
380
  program
21
- .command('codex [query...]')
22
- .description('Run OpenAI Codex')
23
- .action((query) => {
24
- console.log(chalk.green('šŸ¤– Running OpenAI Codex...'));
25
- console.log('Query:', query.join(' '));
381
+ .command('status')
382
+ .description('Show current working directory and environment')
383
+ .action(() => {
384
+ console.log(chalk.bold.cyan('\nšŸ“Š Hanzo Dev Status\n'));
385
+ console.log(`Current Directory: ${chalk.green(process.cwd())}`);
386
+ console.log(`User: ${chalk.green(os.userInfo().username)}`);
387
+ console.log(`Node Version: ${chalk.green(process.version)}`);
388
+ console.log(`Platform: ${chalk.green(os.platform())}`);
389
+
390
+ // Show environment files
391
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
392
+ const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
393
+ if (detectedEnvFiles.length > 0) {
394
+ console.log(`\nEnvironment Files:`);
395
+ detectedEnvFiles.forEach(file => {
396
+ console.log(chalk.green(` āœ“ ${file}`));
397
+ });
398
+ }
26
399
  });
27
400
 
401
+ // Default action - run default tool or interactive mode
28
402
  program
29
- .command('gemini [query...]')
30
- .description('Run Google Gemini')
31
- .action((query) => {
32
- console.log(chalk.yellow('šŸ¤– Running Google Gemini...'));
33
- console.log('Query:', query.join(' '));
403
+ .action(async () => {
404
+ const defaultTool = await getDefaultTool();
405
+ if (defaultTool && process.argv.length === 2) {
406
+ // If we have a default tool and no arguments, run it directly
407
+ console.log(chalk.gray(`Auto-selecting ${TOOLS[defaultTool as keyof typeof TOOLS].name} based on available API keys...`));
408
+ runTool(defaultTool, ['.']);
409
+ } else {
410
+ // Otherwise show interactive mode
411
+ interactiveMode();
412
+ }
34
413
  });
35
414
 
36
- program.parse();
415
+ // Parse arguments
416
+ program.parse();
417
+
418
+ // If no arguments provided, check for default tool
419
+ if (process.argv.length === 2) {
420
+ (async () => {
421
+ const defaultTool = await getDefaultTool();
422
+ if (defaultTool) {
423
+ console.log(chalk.gray(`Auto-selecting ${TOOLS[defaultTool as keyof typeof TOOLS].name} based on available API keys...`));
424
+ runTool(defaultTool, ['.']);
425
+ } else {
426
+ interactiveMode();
427
+ }
428
+ })();
429
+ }