@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 +376 -47
- package/package.json +1 -1
- package/src/cli/dev.ts +412 -19
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
|
|
925
|
-
var
|
|
926
|
-
var
|
|
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) =>
|
|
973
|
-
writeErr: (str) =>
|
|
974
|
-
getOutHelpWidth: () =>
|
|
975
|
-
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,
|
|
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
|
-
|
|
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 =
|
|
1664
|
-
if (
|
|
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 (
|
|
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 =
|
|
1749
|
-
if (
|
|
1750
|
-
if (sourceExt.includes(
|
|
1751
|
-
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}`));
|
|
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 =
|
|
1762
|
+
resolvedScriptPath = fs2.realpathSync(this._scriptPath);
|
|
1763
1763
|
} catch (err) {
|
|
1764
1764
|
resolvedScriptPath = this._scriptPath;
|
|
1765
1765
|
}
|
|
1766
|
-
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 =
|
|
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(
|
|
1778
|
+
launchWithNode = sourceExt.includes(path2.extname(executableFile));
|
|
1779
1779
|
let proc;
|
|
1780
|
-
if (
|
|
1780
|
+
if (process3.platform !== "win32") {
|
|
1781
1781
|
if (launchWithNode) {
|
|
1782
1782
|
args.unshift(executableFile);
|
|
1783
|
-
args = incrementNodeInspectorPort(
|
|
1784
|
-
proc = childProcess.spawn(
|
|
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(
|
|
1791
|
-
proc = childProcess.spawn(
|
|
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
|
-
|
|
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",
|
|
1805
|
+
proc.on("close", process3.exit.bind(process3));
|
|
1806
1806
|
} else {
|
|
1807
1807
|
proc.on("close", () => {
|
|
1808
|
-
exitCallback(new CommanderError2(
|
|
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
|
-
|
|
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
|
|
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()}`,
|
|
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 =
|
|
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(
|
|
2576
|
-
if (
|
|
2577
|
-
this._executableDir =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
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("
|
|
3281
|
-
console.log(source_default.
|
|
3282
|
-
|
|
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.
|
|
3285
|
-
|
|
3286
|
-
|
|
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
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
|
+
}
|