@hanzo/dev 1.0.0 → 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
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  "use strict";
4
3
  var __create = Object.create;
5
4
  var __defProp = Object.defineProperty;
@@ -922,9 +921,9 @@ var require_command = __commonJS({
922
921
  "node_modules/commander/lib/command.js"(exports2) {
923
922
  var EventEmitter = require("events").EventEmitter;
924
923
  var childProcess = require("child_process");
925
- var path = require("path");
926
- var fs = require("fs");
927
- var process2 = require("process");
924
+ var path2 = require("path");
925
+ var fs2 = require("fs");
926
+ var process3 = require("process");
928
927
  var { Argument: Argument2, humanReadableArgName } = require_argument();
929
928
  var { CommanderError: CommanderError2 } = require_error();
930
929
  var { Help: Help2 } = require_help();
@@ -970,10 +969,10 @@ var require_command = __commonJS({
970
969
  this._showHelpAfterError = false;
971
970
  this._showSuggestionAfterError = true;
972
971
  this._outputConfiguration = {
973
- writeOut: (str) => process2.stdout.write(str),
974
- writeErr: (str) => process2.stderr.write(str),
975
- getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0,
976
- 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,
977
976
  outputError: (str, write) => write(str)
978
977
  };
979
978
  this._hidden = false;
@@ -1330,7 +1329,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1330
1329
  if (this._exitCallback) {
1331
1330
  this._exitCallback(new CommanderError2(exitCode, code, message));
1332
1331
  }
1333
- process2.exit(exitCode);
1332
+ process3.exit(exitCode);
1334
1333
  }
1335
1334
  /**
1336
1335
  * Register callback `fn` for the command.
@@ -1661,8 +1660,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1661
1660
  }
1662
1661
  parseOptions = parseOptions || {};
1663
1662
  if (argv === void 0) {
1664
- argv = process2.argv;
1665
- if (process2.versions && process2.versions.electron) {
1663
+ argv = process3.argv;
1664
+ if (process3.versions && process3.versions.electron) {
1666
1665
  parseOptions.from = "electron";
1667
1666
  }
1668
1667
  }
@@ -1675,7 +1674,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1675
1674
  userArgs = argv.slice(2);
1676
1675
  break;
1677
1676
  case "electron":
1678
- if (process2.defaultApp) {
1677
+ if (process3.defaultApp) {
1679
1678
  this._scriptPath = argv[1];
1680
1679
  userArgs = argv.slice(2);
1681
1680
  } else {
@@ -1746,10 +1745,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1746
1745
  let launchWithNode = false;
1747
1746
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1748
1747
  function findFile(baseDir, baseName) {
1749
- const localBin = path.resolve(baseDir, baseName);
1750
- if (fs.existsSync(localBin)) return localBin;
1751
- if (sourceExt.includes(path.extname(baseName))) return void 0;
1752
- 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}`));
1753
1752
  if (foundExt) return `${localBin}${foundExt}`;
1754
1753
  return void 0;
1755
1754
  }
@@ -1760,41 +1759,41 @@ Expecting one of '${allowedValues.join("', '")}'`);
1760
1759
  if (this._scriptPath) {
1761
1760
  let resolvedScriptPath;
1762
1761
  try {
1763
- resolvedScriptPath = fs.realpathSync(this._scriptPath);
1762
+ resolvedScriptPath = fs2.realpathSync(this._scriptPath);
1764
1763
  } catch (err) {
1765
1764
  resolvedScriptPath = this._scriptPath;
1766
1765
  }
1767
- executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
1766
+ executableDir = path2.resolve(path2.dirname(resolvedScriptPath), executableDir);
1768
1767
  }
1769
1768
  if (executableDir) {
1770
1769
  let localFile = findFile(executableDir, executableFile);
1771
1770
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1772
- const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
1771
+ const legacyName = path2.basename(this._scriptPath, path2.extname(this._scriptPath));
1773
1772
  if (legacyName !== this._name) {
1774
1773
  localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
1775
1774
  }
1776
1775
  }
1777
1776
  executableFile = localFile || executableFile;
1778
1777
  }
1779
- launchWithNode = sourceExt.includes(path.extname(executableFile));
1778
+ launchWithNode = sourceExt.includes(path2.extname(executableFile));
1780
1779
  let proc;
1781
- if (process2.platform !== "win32") {
1780
+ if (process3.platform !== "win32") {
1782
1781
  if (launchWithNode) {
1783
1782
  args.unshift(executableFile);
1784
- args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1785
- 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" });
1786
1785
  } else {
1787
1786
  proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
1788
1787
  }
1789
1788
  } else {
1790
1789
  args.unshift(executableFile);
1791
- args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1792
- proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
1790
+ args = incrementNodeInspectorPort(process3.execArgv).concat(args);
1791
+ proc = childProcess.spawn(process3.execPath, args, { stdio: "inherit" });
1793
1792
  }
1794
1793
  if (!proc.killed) {
1795
1794
  const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
1796
1795
  signals.forEach((signal) => {
1797
- process2.on(signal, () => {
1796
+ process3.on(signal, () => {
1798
1797
  if (proc.killed === false && proc.exitCode === null) {
1799
1798
  proc.kill(signal);
1800
1799
  }
@@ -1803,10 +1802,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1803
1802
  }
1804
1803
  const exitCallback = this._exitCallback;
1805
1804
  if (!exitCallback) {
1806
- proc.on("close", process2.exit.bind(process2));
1805
+ proc.on("close", process3.exit.bind(process3));
1807
1806
  } else {
1808
1807
  proc.on("close", () => {
1809
- exitCallback(new CommanderError2(process2.exitCode || 0, "commander.executeSubCommandAsync", "(close)"));
1808
+ exitCallback(new CommanderError2(process3.exitCode || 0, "commander.executeSubCommandAsync", "(close)"));
1810
1809
  });
1811
1810
  }
1812
1811
  proc.on("error", (err) => {
@@ -1821,7 +1820,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1821
1820
  throw new Error(`'${executableFile}' not executable`);
1822
1821
  }
1823
1822
  if (!exitCallback) {
1824
- process2.exit(1);
1823
+ process3.exit(1);
1825
1824
  } else {
1826
1825
  const wrappedError = new CommanderError2(1, "commander.executeSubCommandAsync", "(error)");
1827
1826
  wrappedError.nestedError = err;
@@ -2287,11 +2286,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2287
2286
  */
2288
2287
  _parseOptionsEnv() {
2289
2288
  this.options.forEach((option) => {
2290
- if (option.envVar && option.envVar in process2.env) {
2289
+ if (option.envVar && option.envVar in process3.env) {
2291
2290
  const optionKey = option.attributeName();
2292
2291
  if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(this.getOptionValueSource(optionKey))) {
2293
2292
  if (option.required || option.optional) {
2294
- this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
2293
+ this.emit(`optionEnv:${option.name()}`, process3.env[option.envVar]);
2295
2294
  } else {
2296
2295
  this.emit(`optionEnv:${option.name()}`);
2297
2296
  }
@@ -2559,7 +2558,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2559
2558
  * @return {Command}
2560
2559
  */
2561
2560
  nameFromFilename(filename) {
2562
- this._name = path.basename(filename, path.extname(filename));
2561
+ this._name = path2.basename(filename, path2.extname(filename));
2563
2562
  return this;
2564
2563
  }
2565
2564
  /**
@@ -2573,9 +2572,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2573
2572
  * @param {string} [path]
2574
2573
  * @return {string|null|Command}
2575
2574
  */
2576
- executableDir(path2) {
2577
- if (path2 === void 0) return this._executableDir;
2578
- this._executableDir = path2;
2575
+ executableDir(path3) {
2576
+ if (path3 === void 0) return this._executableDir;
2577
+ this._executableDir = path3;
2579
2578
  return this;
2580
2579
  }
2581
2580
  /**
@@ -2667,7 +2666,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2667
2666
  */
2668
2667
  help(contextOptions) {
2669
2668
  this.outputHelp(contextOptions);
2670
- let exitCode = process2.exitCode || 0;
2669
+ let exitCode = process3.exitCode || 0;
2671
2670
  if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
2672
2671
  exitCode = 1;
2673
2672
  }
@@ -3060,10 +3059,10 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
3060
3059
  return 3;
3061
3060
  }
3062
3061
  if ("TERM_PROGRAM" in env) {
3063
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
3062
+ const version2 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
3064
3063
  switch (env.TERM_PROGRAM) {
3065
3064
  case "iTerm.app": {
3066
- return version >= 3 ? 3 : 2;
3065
+ return version2 >= 3 ? 3 : 2;
3067
3066
  }
3068
3067
  case "Apple_Terminal": {
3069
3068
  return 2;
@@ -3272,18 +3271,347 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
3272
3271
  var source_default = chalk;
3273
3272
 
3274
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"));
3275
3279
  var program2 = new Command();
3276
- program2.name("@hanzo/dev").description("Hanzo Dev - Meta AI development CLI").version("1.0.0");
3277
- program2.command("claude [query...]").description("Run Claude AI").action((query) => {
3278
- console.log(source_default.blue("\u{1F916} Running Claude AI..."));
3279
- 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
+ });
3280
3532
  });
3281
- program2.command("codex [query...]").description("Run OpenAI Codex").action((query) => {
3282
- console.log(source_default.green("\u{1F916} Running OpenAI Codex..."));
3283
- 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
+ }
3284
3596
  });
3285
- program2.command("gemini [query...]").description("Run Google Gemini").action((query) => {
3286
- console.log(source_default.yellow("\u{1F916} Running Google Gemini..."));
3287
- 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
+ }
3288
3605
  });
3289
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,14 +1,13 @@
1
1
  {
2
2
  "name": "@hanzo/dev",
3
- "version": "1.0.0",
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": {
7
- "dev": "./dist/cli/dev.js",
8
- "hanzo-dev": "./dist/cli/dev.js"
7
+ "dev": "./dist/cli/dev.js"
9
8
  },
10
9
  "scripts": {
11
- "build": "esbuild src/cli/dev.ts --bundle --platform=node --target=node16 --outfile=dist/cli/dev.js --external:vscode --external:inquirer --banner:js='#!/usr/bin/env node' && chmod +x dist/cli/dev.js",
10
+ "build": "esbuild src/cli/dev.ts --bundle --platform=node --target=node16 --outfile=dist/cli/dev.js --external:vscode --external:inquirer && chmod +x dist/cli/dev.js",
12
11
  "dev": "tsc --watch",
13
12
  "test": "jest",
14
13
  "prepublishOnly": "npm run build"
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
+ }