@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 +376 -48
- package/package.json +3 -4
- package/src/cli/dev.ts +412 -19
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
|
|
926
|
-
var
|
|
927
|
-
var
|
|
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) =>
|
|
974
|
-
writeErr: (str) =>
|
|
975
|
-
getOutHelpWidth: () =>
|
|
976
|
-
getErrHelpWidth: () =>
|
|
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
|
-
|
|
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 =
|
|
1665
|
-
if (
|
|
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 (
|
|
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 =
|
|
1750
|
-
if (
|
|
1751
|
-
if (sourceExt.includes(
|
|
1752
|
-
const foundExt = sourceExt.find((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 =
|
|
1762
|
+
resolvedScriptPath = fs2.realpathSync(this._scriptPath);
|
|
1764
1763
|
} catch (err) {
|
|
1765
1764
|
resolvedScriptPath = this._scriptPath;
|
|
1766
1765
|
}
|
|
1767
|
-
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 =
|
|
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(
|
|
1778
|
+
launchWithNode = sourceExt.includes(path2.extname(executableFile));
|
|
1780
1779
|
let proc;
|
|
1781
|
-
if (
|
|
1780
|
+
if (process3.platform !== "win32") {
|
|
1782
1781
|
if (launchWithNode) {
|
|
1783
1782
|
args.unshift(executableFile);
|
|
1784
|
-
args = incrementNodeInspectorPort(
|
|
1785
|
-
proc = childProcess.spawn(
|
|
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(
|
|
1792
|
-
proc = childProcess.spawn(
|
|
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
|
-
|
|
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",
|
|
1805
|
+
proc.on("close", process3.exit.bind(process3));
|
|
1807
1806
|
} else {
|
|
1808
1807
|
proc.on("close", () => {
|
|
1809
|
-
exitCallback(new CommanderError2(
|
|
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
|
-
|
|
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
|
|
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()}`,
|
|
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 =
|
|
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(
|
|
2577
|
-
if (
|
|
2578
|
-
this._executableDir =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
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("
|
|
3282
|
-
console.log(source_default.
|
|
3283
|
-
|
|
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.
|
|
3286
|
-
|
|
3287
|
-
|
|
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.
|
|
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
|
|
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('
|
|
9
|
-
.description('Hanzo Dev - Meta AI development CLI')
|
|
10
|
-
.version(
|
|
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('
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
console.log(
|
|
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('
|
|
22
|
-
.description('
|
|
23
|
-
.action((
|
|
24
|
-
console.log(chalk.
|
|
25
|
-
console.log(
|
|
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
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
+
}
|