@nexical/cli 0.11.15 → 0.11.17

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/index.js CHANGED
@@ -15,7 +15,7 @@ import { fileURLToPath } from "url";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "@nexical/cli",
18
- version: "0.11.15",
18
+ version: "0.11.17",
19
19
  license: "Apache-2.0",
20
20
  type: "module",
21
21
  bin: {
@@ -44,17 +44,23 @@ var package_default = {
44
44
  ]
45
45
  },
46
46
  dependencies: {
47
+ "@nexical/ai": "^0.1.3",
47
48
  "@nexical/cli-core": "^0.1.16",
48
49
  dotenv: "^17.3.1",
49
50
  "fast-glob": "^3.3.3",
50
51
  glob: "^13.0.5",
51
52
  jiti: "^2.6.1",
53
+ minimist: "^1.2.8",
54
+ nunjucks: "^3.2.4",
55
+ repomix: "^1.11.1",
52
56
  yaml: "^2.8.2"
53
57
  },
54
58
  devDependencies: {
55
59
  "@eslint/js": "^9.39.2",
56
60
  "@types/fs-extra": "^11.0.4",
61
+ "@types/minimist": "^1.2.5",
57
62
  "@types/node": "^25.3.0",
63
+ "@types/nunjucks": "^3.2.6",
58
64
  "@vitest/coverage-v8": "^4.0.18",
59
65
  eslint: "^9.39.2",
60
66
  "eslint-config-prettier": "^10.1.8",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI, findProjectRoot } from '@nexical/cli-core';\nimport { fileURLToPath } from 'node:url';\nimport { discoverCommandDirectories } from './src/utils/discovery.js';\nimport pkg from './package.json';\nimport path from 'node:path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst commandName = 'nexical';\nconst projectRoot = (await findProjectRoot(commandName, process.cwd())) || process.cwd();\nconst coreCommandsDir = path.resolve(__dirname, './src/commands');\nconst additionalCommands = discoverCommandDirectories(projectRoot);\n\n// Filter out duplicate core commands and source versions\nconst filteredAdditional = additionalCommands.filter((dir) => {\n const resolvedDir = path.resolve(dir);\n const resolvedCore = path.resolve(coreCommandsDir);\n\n if (resolvedDir === resolvedCore) return false;\n\n // Check if this is another instance of the core CLI commands (by checking path suffix)\n const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');\n const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');\n const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');\n\n if (\n resolvedDir.endsWith(coreSuffix) ||\n resolvedDir.endsWith(coreSuffixSrc) ||\n resolvedDir.endsWith(coreSuffixRawSrc)\n ) {\n return false;\n }\n\n // Handle mismatch between dist/src and src/\n if (resolvedCore.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {\n const srcVersion = resolvedCore.replace(\n path.join(path.sep, 'dist', 'src', 'commands'),\n path.join(path.sep, 'src', 'commands'),\n );\n if (resolvedDir === srcVersion) return false;\n }\n\n return true;\n});\n\nconst app = new CLI({\n version: pkg.version,\n commandName: commandName,\n searchDirectories: [...new Set([coreCommandsDir, ...filteredAdditional])],\n});\napp.start();\n","{\n \"name\": \"@nexical/cli\",\n \"version\": \"0.11.15\",\n \"license\": \"Apache-2.0\",\n \"type\": \"module\",\n \"bin\": {\n \"nexical\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/index.js\",\n \"test\": \"npm run test:unit && npm run test:integration && npm run test:e2e\",\n \"test:unit\": \"vitest run --config vitest.config.ts --coverage\",\n \"test:integration\": \"vitest run --config vitest.integration.config.ts\",\n \"test:e2e\": \"npm run build && vitest run --config vitest.e2e.config.ts\",\n \"test:watch\": \"vitest\",\n \"format\": \"prettier --write .\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"prepare\": \"husky\"\n },\n \"lint-staged\": {\n \"**/*\": [\n \"prettier --write --ignore-unknown\"\n ],\n \"**/*.{js,jsx,ts,tsx,astro}\": [\n \"eslint --fix\"\n ]\n },\n \"dependencies\": {\n \"@nexical/cli-core\": \"^0.1.16\",\n \"dotenv\": \"^17.3.1\",\n \"fast-glob\": \"^3.3.3\",\n \"glob\": \"^13.0.5\",\n \"jiti\": \"^2.6.1\",\n \"yaml\": \"^2.8.2\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^25.3.0\",\n \"@vitest/coverage-v8\": \"^4.0.18\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.6.0\",\n \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.0.1\",\n \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.3\",\n \"globals\": \"^17.3.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.56.0\",\n \"vitest\": \"^4.0.18\"\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,SAAS,KAAK,uBAAuB;AACrC,SAAS,qBAAqB;;;ACF9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,KAAO;AAAA,IACL,SAAW;AAAA,EACb;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,QAAU;AAAA,IACV,MAAQ;AAAA,IACR,YAAY;AAAA,IACZ,SAAW;AAAA,EACb;AAAA,EACA,eAAe;AAAA,IACb,QAAQ;AAAA,MACN;AAAA,IACF;AAAA,IACA,8BAA8B;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAAA,EACA,cAAgB;AAAA,IACd,qBAAqB;AAAA,IACrB,QAAU;AAAA,IACV,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,uBAAuB;AAAA,IACvB,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,6BAA6B;AAAA,IAC7B,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,SAAW;AAAA,IACX,OAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,qBAAqB;AAAA,IACrB,QAAU;AAAA,EACZ;AACF;;;ADxDA,OAAO,UAAU;AAEjB,IAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE7D,IAAM,cAAc;AACpB,IAAM,cAAe,MAAM,gBAAgB,aAAa,QAAQ,IAAI,CAAC,KAAM,QAAQ,IAAI;AACvF,IAAM,kBAAkB,KAAK,QAAQ,WAAW,gBAAgB;AAChE,IAAM,qBAAqB,2BAA2B,WAAW;AAGjE,IAAM,qBAAqB,mBAAmB,OAAO,CAAC,QAAQ;AAC5D,QAAM,cAAc,KAAK,QAAQ,GAAG;AACpC,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,gBAAgB,aAAc,QAAO;AAGzC,QAAM,aAAa,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AACzE,QAAM,gBAAgB,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AAC5E,QAAM,mBAAmB,KAAK,KAAK,YAAY,OAAO,OAAO,UAAU;AAEvE,MACE,YAAY,SAAS,UAAU,KAC/B,YAAY,SAAS,aAAa,KAClC,YAAY,SAAS,gBAAgB,GACrC;AACA,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,SAAS,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU,CAAC,GAAG;AACzE,UAAM,aAAa,aAAa;AAAA,MAC9B,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU;AAAA,MAC7C,KAAK,KAAK,KAAK,KAAK,OAAO,UAAU;AAAA,IACvC;AACA,QAAI,gBAAgB,WAAY,QAAO;AAAA,EACzC;AAEA,SAAO;AACT,CAAC;AAED,IAAM,MAAM,IAAI,IAAI;AAAA,EAClB,SAAS,gBAAI;AAAA,EACb;AAAA,EACA,mBAAmB,CAAC,GAAG,oBAAI,IAAI,CAAC,iBAAiB,GAAG,kBAAkB,CAAC,CAAC;AAC1E,CAAC;AACD,IAAI,MAAM;","names":[]}
1
+ {"version":3,"sources":["../index.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI, findProjectRoot } from '@nexical/cli-core';\nimport { fileURLToPath } from 'node:url';\nimport { discoverCommandDirectories } from './src/utils/discovery.js';\nimport pkg from './package.json';\nimport path from 'node:path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst commandName = 'nexical';\nconst projectRoot = (await findProjectRoot(commandName, process.cwd())) || process.cwd();\nconst coreCommandsDir = path.resolve(__dirname, './src/commands');\nconst additionalCommands = discoverCommandDirectories(projectRoot);\n\n// Filter out duplicate core commands and source versions\nconst filteredAdditional = additionalCommands.filter((dir) => {\n const resolvedDir = path.resolve(dir);\n const resolvedCore = path.resolve(coreCommandsDir);\n\n if (resolvedDir === resolvedCore) return false;\n\n // Check if this is another instance of the core CLI commands (by checking path suffix)\n const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');\n const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');\n const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');\n\n if (\n resolvedDir.endsWith(coreSuffix) ||\n resolvedDir.endsWith(coreSuffixSrc) ||\n resolvedDir.endsWith(coreSuffixRawSrc)\n ) {\n return false;\n }\n\n // Handle mismatch between dist/src and src/\n if (resolvedCore.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {\n const srcVersion = resolvedCore.replace(\n path.join(path.sep, 'dist', 'src', 'commands'),\n path.join(path.sep, 'src', 'commands'),\n );\n if (resolvedDir === srcVersion) return false;\n }\n\n return true;\n});\n\nconst app = new CLI({\n version: pkg.version,\n commandName: commandName,\n searchDirectories: [...new Set([coreCommandsDir, ...filteredAdditional])],\n});\napp.start();\n","{\n \"name\": \"@nexical/cli\",\n \"version\": \"0.11.17\",\n \"license\": \"Apache-2.0\",\n \"type\": \"module\",\n \"bin\": {\n \"nexical\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/index.js\",\n \"test\": \"npm run test:unit && npm run test:integration && npm run test:e2e\",\n \"test:unit\": \"vitest run --config vitest.config.ts --coverage\",\n \"test:integration\": \"vitest run --config vitest.integration.config.ts\",\n \"test:e2e\": \"npm run build && vitest run --config vitest.e2e.config.ts\",\n \"test:watch\": \"vitest\",\n \"format\": \"prettier --write .\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"prepare\": \"husky\"\n },\n \"lint-staged\": {\n \"**/*\": [\n \"prettier --write --ignore-unknown\"\n ],\n \"**/*.{js,jsx,ts,tsx,astro}\": [\n \"eslint --fix\"\n ]\n },\n \"dependencies\": {\n \"@nexical/ai\": \"^0.1.3\",\n \"@nexical/cli-core\": \"^0.1.16\",\n \"dotenv\": \"^17.3.1\",\n \"fast-glob\": \"^3.3.3\",\n \"glob\": \"^13.0.5\",\n \"jiti\": \"^2.6.1\",\n \"minimist\": \"^1.2.8\",\n \"nunjucks\": \"^3.2.4\",\n \"repomix\": \"^1.11.1\",\n \"yaml\": \"^2.8.2\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/minimist\": \"^1.2.5\",\n \"@types/node\": \"^25.3.0\",\n \"@types/nunjucks\": \"^3.2.6\",\n \"@vitest/coverage-v8\": \"^4.0.18\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.6.0\",\n \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.0.1\",\n \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.3\",\n \"globals\": \"^17.3.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.56.0\",\n \"vitest\": \"^4.0.18\"\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,4 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
- import {
3
- SetupCommand
4
- } from "../../chunk-GUUPSHWC.js";
5
- import {
6
- require_lib
7
- } from "../../chunk-OUGA4CB4.js";
8
2
  import {
9
3
  addAll,
10
4
  clone,
@@ -15,6 +9,12 @@ import {
15
9
  import {
16
10
  resolveGitUrl
17
11
  } from "../../chunk-PJIOCW2A.js";
12
+ import {
13
+ SetupCommand
14
+ } from "../../chunk-GUUPSHWC.js";
15
+ import {
16
+ require_lib
17
+ } from "../../chunk-OUGA4CB4.js";
18
18
  import {
19
19
  __toESM,
20
20
  init_esm_shims
@@ -1,7 +1,4 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
- import {
3
- require_lib
4
- } from "../../../chunk-OUGA4CB4.js";
5
2
  import {
6
3
  addSubmodule,
7
4
  clone,
@@ -10,6 +7,9 @@ import {
10
7
  import {
11
8
  resolveGitUrl
12
9
  } from "../../../chunk-PJIOCW2A.js";
10
+ import {
11
+ require_lib
12
+ } from "../../../chunk-OUGA4CB4.js";
13
13
  import {
14
14
  __toESM,
15
15
  init_esm_shims
@@ -0,0 +1,19 @@
1
+ import { BaseCommand, CommandDefinition } from '@nexical/cli-core';
2
+
3
+ declare class PromptCommand extends BaseCommand {
4
+ static usage: string;
5
+ static description: string;
6
+ static requiresProject: boolean;
7
+ static args: CommandDefinition;
8
+ run(options: {
9
+ promptName: string;
10
+ args?: string[];
11
+ module?: string;
12
+ m?: string;
13
+ interactive?: boolean;
14
+ i?: boolean;
15
+ models?: string;
16
+ }): Promise<void>;
17
+ }
18
+
19
+ export { PromptCommand as default };
@@ -0,0 +1,347 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ require_lib
4
+ } from "../../chunk-OUGA4CB4.js";
5
+ import {
6
+ __toESM,
7
+ init_esm_shims
8
+ } from "../../chunk-OYFWMYPG.js";
9
+
10
+ // src/commands/prompt.ts
11
+ init_esm_shims();
12
+ var import_fs_extra = __toESM(require_lib(), 1);
13
+ import { BaseCommand, logger } from "@nexical/cli-core";
14
+ import path from "path";
15
+ import os from "os";
16
+ import nunjucks from "nunjucks";
17
+ import minimist from "minimist";
18
+ import readline from "readline";
19
+ import YAML from "yaml";
20
+ import { pack } from "repomix";
21
+ import { existsSync, statSync } from "fs";
22
+ import { AiClientFactory } from "@nexical/ai";
23
+ var PromptCommand = class extends BaseCommand {
24
+ static usage = "prompt <prompt-name> [args...]";
25
+ static description = "Run an AI prompt using templates from the prompts directory.";
26
+ static requiresProject = true;
27
+ static args = {
28
+ args: [
29
+ {
30
+ name: "promptName",
31
+ required: true,
32
+ description: "The name of the markdown file in the prompts directory."
33
+ },
34
+ {
35
+ name: "args...",
36
+ required: false,
37
+ description: "Additional arguments for the template and command"
38
+ }
39
+ ],
40
+ options: [
41
+ {
42
+ name: "--module, -m <module>",
43
+ description: "Target a specific module (searches apps/frontend/modules and apps/backend/modules)"
44
+ },
45
+ { name: "--interactive, -i", description: "Run in interactive chat mode" },
46
+ {
47
+ name: "--models <models>",
48
+ description: "Comma-separated list of models to try",
49
+ default: "gemini-3-flash-preview,gemini-3-pro-preview"
50
+ }
51
+ ]
52
+ };
53
+ async run(options) {
54
+ const projectRoot = this.projectRoot;
55
+ const promptName = options.promptName;
56
+ const argv = minimist(options.args || []);
57
+ const isInteractive = options.interactive || options.i || argv.interactive || argv.i;
58
+ const moduleName = options.module || options.m || argv.module || argv.m;
59
+ const modelsArg = options.models || argv.models || "gemini-3-flash-preview,gemini-3-pro-preview";
60
+ const models = modelsArg.split(",").map((m) => m.trim()).filter(Boolean);
61
+ const promptFileName = promptName.endsWith(".md") ? promptName : `${promptName}.md`;
62
+ const PROMPTS_DIRS = [path.join(projectRoot, "prompts")];
63
+ const generatorAgentsPrompts = path.join(projectRoot, "packages/generator/prompts/agents");
64
+ if (await import_fs_extra.default.pathExists(generatorAgentsPrompts)) {
65
+ PROMPTS_DIRS.push(generatorAgentsPrompts);
66
+ }
67
+ let promptFile;
68
+ for (const dir of PROMPTS_DIRS) {
69
+ const candidate = path.join(dir, promptFileName);
70
+ if (await import_fs_extra.default.pathExists(candidate)) {
71
+ promptFile = candidate;
72
+ break;
73
+ }
74
+ }
75
+ if (!promptFile) {
76
+ this.error(
77
+ `Prompt file '${promptFileName}' not found in any of the search directories:
78
+ ` + PROMPTS_DIRS.map((d) => ` - ${d}`).join("\n")
79
+ );
80
+ return;
81
+ }
82
+ const contextVars = { ...argv };
83
+ if (moduleName) {
84
+ const frontendPath = path.join(projectRoot, "apps/frontend/modules", moduleName);
85
+ const backendPath = path.join(projectRoot, "apps/backend/modules", moduleName);
86
+ let moduleRoot;
87
+ let moduleType;
88
+ if (await import_fs_extra.default.pathExists(frontendPath)) {
89
+ moduleRoot = frontendPath;
90
+ moduleType = "frontend";
91
+ } else if (await import_fs_extra.default.pathExists(backendPath)) {
92
+ moduleRoot = backendPath;
93
+ moduleType = "backend";
94
+ }
95
+ if (!moduleRoot) {
96
+ this.error(
97
+ `Module '${moduleName}' not found in apps/frontend/modules or apps/backend/modules.`
98
+ );
99
+ return;
100
+ }
101
+ logger.debug(`[Context] Targeting ${moduleType} module: ${moduleName}`);
102
+ contextVars.module_root = moduleRoot;
103
+ contextVars.module_name = moduleName;
104
+ contextVars.module_type = moduleType;
105
+ contextVars.root_path = moduleRoot + "/";
106
+ } else {
107
+ if (!contextVars.root_path) {
108
+ contextVars.root_path = process.cwd() + "/";
109
+ }
110
+ }
111
+ const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(PROMPTS_DIRS), {
112
+ autoescape: false,
113
+ trimBlocks: true,
114
+ lstripBlocks: true
115
+ });
116
+ const asyncResolvers = /* @__PURE__ */ new Map();
117
+ let resolverId = 0;
118
+ env.addGlobal("context", (targetPath) => {
119
+ const id = `__NEXICAL_ASYNC_CONTEXT_${resolverId++}__`;
120
+ const promise = (async () => {
121
+ try {
122
+ if (!existsSync(targetPath)) {
123
+ logger.debug(`[Context] Path not found: ${targetPath}`);
124
+ return `[Path not found: ${targetPath}]`;
125
+ }
126
+ const stats = statSync(targetPath);
127
+ if (stats.isFile()) {
128
+ logger.debug(`[Context] Reading file directly at: ${targetPath}`);
129
+ const content = await import_fs_extra.default.readFile(targetPath, "utf-8");
130
+ return `<CODEBASE_CONTEXT path="${targetPath}">
131
+ ${content}
132
+ </CODEBASE_CONTEXT>`;
133
+ }
134
+ logger.debug(`[Context] Analyzing codebase at: ${targetPath}`);
135
+ const tempOutputFile = path.join(
136
+ os.tmpdir(),
137
+ `repomix-output-${Date.now()}-${Math.random().toString(36).substring(7)}.xml`
138
+ );
139
+ await pack([targetPath], {
140
+ input: { maxFileSize: 1024 * 1024 * 10 },
141
+ output: {
142
+ filePath: tempOutputFile,
143
+ style: "xml",
144
+ showLineNumbers: false,
145
+ fileSummary: false,
146
+ directoryStructure: false,
147
+ removeComments: false,
148
+ removeEmptyLines: false,
149
+ includeEmptyDirectories: false,
150
+ topFilesLength: 5,
151
+ parsableStyle: false,
152
+ files: true,
153
+ compress: false,
154
+ truncateBase64: true,
155
+ copyToClipboard: false,
156
+ includeDiffs: false,
157
+ includeLogs: false,
158
+ includeLogsCount: 0,
159
+ gitSortByChanges: false,
160
+ includeFullDirectoryStructure: false
161
+ },
162
+ ignore: {
163
+ useGitignore: true,
164
+ useDotIgnore: true,
165
+ useDefaultPatterns: true,
166
+ customPatterns: ["**/node_modules", "**/dist"]
167
+ },
168
+ include: [],
169
+ security: { enableSecurityCheck: false },
170
+ tokenCount: { encoding: "o200k_base" },
171
+ cwd: targetPath
172
+ });
173
+ const output = await import_fs_extra.default.readFile(tempOutputFile, "utf-8");
174
+ try {
175
+ await import_fs_extra.default.unlink(tempOutputFile);
176
+ } catch {
177
+ }
178
+ return `<CODEBASE_CONTEXT path="${targetPath}">
179
+ ${output}
180
+ </CODEBASE_CONTEXT>`;
181
+ } catch (error) {
182
+ logger.error(`[Context] Error generating context for ${targetPath}: ${error}`);
183
+ return `[Error generating context for ${targetPath}]`;
184
+ }
185
+ })();
186
+ asyncResolvers.set(id, promise);
187
+ return id;
188
+ });
189
+ env.addGlobal("read", (relativePath) => {
190
+ const id = `__NEXICAL_ASYNC_READ_${resolverId++}__`;
191
+ const promise = (async () => {
192
+ try {
193
+ const cwdStr = process.cwd();
194
+ if (Array.isArray(relativePath)) {
195
+ const contents = await Promise.all(
196
+ relativePath.map(async (p) => {
197
+ const resolvedPath2 = path.resolve(cwdStr, p);
198
+ if (!existsSync(resolvedPath2)) {
199
+ logger.debug(`[Read] File not found: ${resolvedPath2}`);
200
+ return `[File not found: ${resolvedPath2}]`;
201
+ }
202
+ return await import_fs_extra.default.readFile(resolvedPath2, "utf-8");
203
+ })
204
+ );
205
+ return contents.join("\n\n");
206
+ } else if (typeof relativePath === "string" && relativePath.includes(",")) {
207
+ const contents = await Promise.all(
208
+ relativePath.split(",").map(async (p) => {
209
+ const resolvedPath2 = path.resolve(cwdStr, p.trim());
210
+ if (!existsSync(resolvedPath2)) {
211
+ logger.debug(`[Read] File not found: ${resolvedPath2}`);
212
+ return `[File not found: ${resolvedPath2}]`;
213
+ }
214
+ return await import_fs_extra.default.readFile(resolvedPath2, "utf-8");
215
+ })
216
+ );
217
+ return contents.join("\n\n");
218
+ }
219
+ const resolvedPath = path.resolve(cwdStr, relativePath);
220
+ if (!existsSync(resolvedPath)) {
221
+ logger.debug(`[Read] File not found: ${resolvedPath}`);
222
+ return `[File not found: ${resolvedPath}]`;
223
+ }
224
+ return await import_fs_extra.default.readFile(resolvedPath, "utf-8");
225
+ } catch {
226
+ logger.warn(`[Read] Warning: Could not read file: ${relativePath}`);
227
+ return `[Error reading file ${relativePath}]`;
228
+ }
229
+ })();
230
+ asyncResolvers.set(id, promise);
231
+ return id;
232
+ });
233
+ let templateContent;
234
+ try {
235
+ templateContent = await import_fs_extra.default.readFile(promptFile, "utf-8");
236
+ } catch (error) {
237
+ if (error instanceof Error) {
238
+ this.error(`Error reading prompt file: ${error.message}`);
239
+ } else {
240
+ this.error(`Error reading prompt file: ${String(error)}`);
241
+ }
242
+ return;
243
+ }
244
+ logger.debug(
245
+ `[Render] Rendering template with variables:`,
246
+ JSON.stringify(contextVars, null, 2)
247
+ );
248
+ let renderedPrompt;
249
+ try {
250
+ renderedPrompt = env.renderString(templateContent, {
251
+ ...contextVars
252
+ });
253
+ } catch (e) {
254
+ this.error(`Template render error: ${e}`);
255
+ return;
256
+ }
257
+ for (const [id, promise] of asyncResolvers.entries()) {
258
+ try {
259
+ const resolvedValue = await promise;
260
+ renderedPrompt = renderedPrompt.replace(id, resolvedValue);
261
+ } catch (e) {
262
+ logger.error(`[Render] Failed to resolve async variable ${id}: ${e}`);
263
+ renderedPrompt = renderedPrompt.replace(id, `[Error resolving id]`);
264
+ }
265
+ }
266
+ const tempFile = path.join(os.tmpdir(), ".temp_prompt_active.md");
267
+ await import_fs_extra.default.writeFile(tempFile, renderedPrompt, "utf-8");
268
+ logger.debug(`[Buffer] Wrote active prompt to ${tempFile}`);
269
+ logger.info(`[Agent] Model rotation strategy: [${models.join(", ")}]`);
270
+ const configPath = path.join(projectRoot, "nexical.yaml");
271
+ let aiConfig = {};
272
+ if (await import_fs_extra.default.pathExists(configPath)) {
273
+ try {
274
+ const content = await import_fs_extra.default.readFile(configPath, "utf8");
275
+ const config = YAML.parse(content) || {};
276
+ aiConfig = config.ai || {};
277
+ } catch {
278
+ logger.warn("Failed to parse nexical.yaml AI config, using defaults.");
279
+ }
280
+ }
281
+ const aiClient = AiClientFactory.create(aiConfig);
282
+ let currentPrompt = renderedPrompt;
283
+ let finalCode = 0;
284
+ while (true) {
285
+ let success = false;
286
+ let lastOutput = "";
287
+ for (const model of models) {
288
+ logger.info(`[Agent] Attempting with model: \x1B[36m${model}\x1B[0m...`);
289
+ const result = await aiClient.run(model, currentPrompt);
290
+ if (result.code === 0) {
291
+ success = true;
292
+ lastOutput = result.output;
293
+ break;
294
+ }
295
+ if (result.shouldRetry) {
296
+ logger.info(`[Agent] Switching to next model...`);
297
+ continue;
298
+ } else {
299
+ finalCode = result.code;
300
+ break;
301
+ }
302
+ }
303
+ if (!success) {
304
+ if (finalCode === 0) finalCode = 1;
305
+ this.error(`[Agent] \u274C All attempts failed.`);
306
+ break;
307
+ }
308
+ if (!isInteractive) {
309
+ break;
310
+ }
311
+ currentPrompt += `
312
+ ${lastOutput}`;
313
+ const askLink = () => {
314
+ const rl = readline.createInterface({
315
+ input: process.stdin,
316
+ output: process.stdout
317
+ });
318
+ return new Promise((resolve) => {
319
+ this.info('\n(Type "exit" or "quit" to end the session)');
320
+ rl.question("> ", (ans) => {
321
+ rl.close();
322
+ resolve(ans);
323
+ });
324
+ });
325
+ };
326
+ const answer = await askLink();
327
+ if (["exit", "quit"].includes(answer.trim().toLowerCase())) {
328
+ break;
329
+ }
330
+ currentPrompt += `
331
+ User: ${answer}
332
+ `;
333
+ }
334
+ try {
335
+ await import_fs_extra.default.unlink(tempFile);
336
+ logger.debug(`[Cleanup] Removed active prompt file`);
337
+ } catch {
338
+ }
339
+ if (finalCode !== 0) {
340
+ process.exit(finalCode);
341
+ }
342
+ }
343
+ };
344
+ export {
345
+ PromptCommand as default
346
+ };
347
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/commands/prompt.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport os from 'node:os';\nimport nunjucks from 'nunjucks';\nimport minimist from 'minimist';\nimport readline from 'node:readline';\nimport YAML from 'yaml';\nimport { pack } from 'repomix';\nimport { existsSync, statSync } from 'node:fs';\nimport { AiClientFactory } from '@nexical/ai';\n\nexport default class PromptCommand extends BaseCommand {\n static usage = 'prompt <prompt-name> [args...]';\n static description = 'Run an AI prompt using templates from the prompts directory.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [\n {\n name: 'promptName',\n required: true,\n description: 'The name of the markdown file in the prompts directory.',\n },\n {\n name: 'args...',\n required: false,\n description: 'Additional arguments for the template and command',\n },\n ],\n options: [\n {\n name: '--module, -m <module>',\n description:\n 'Target a specific module (searches apps/frontend/modules and apps/backend/modules)',\n },\n { name: '--interactive, -i', description: 'Run in interactive chat mode' },\n {\n name: '--models <models>',\n description: 'Comma-separated list of models to try',\n default: 'gemini-3-flash-preview,gemini-3-pro-preview',\n },\n ],\n };\n\n async run(options: {\n promptName: string;\n args?: string[];\n module?: string;\n m?: string;\n interactive?: boolean;\n i?: boolean;\n models?: string;\n }) {\n const projectRoot = this.projectRoot as string;\n const promptName = options.promptName;\n\n // Parse additional template flags\n const argv = minimist(options.args || []);\n const isInteractive = options.interactive || options.i || argv.interactive || argv.i;\n const moduleName = options.module || options.m || argv.module || argv.m;\n const modelsArg =\n options.models || argv.models || 'gemini-3-flash-preview,gemini-3-pro-preview';\n const models = modelsArg\n .split(',')\n .map((m: string) => m.trim())\n .filter(Boolean);\n\n // Resolve prompt file from multiple directories\n const promptFileName = promptName.endsWith('.md') ? promptName : `${promptName}.md`;\n const PROMPTS_DIRS = [path.join(projectRoot, 'prompts')];\n const generatorAgentsPrompts = path.join(projectRoot, 'packages/generator/prompts/agents');\n\n if (await fs.pathExists(generatorAgentsPrompts)) {\n PROMPTS_DIRS.push(generatorAgentsPrompts);\n }\n\n let promptFile: string | undefined;\n for (const dir of PROMPTS_DIRS) {\n const candidate = path.join(dir, promptFileName);\n if (await fs.pathExists(candidate)) {\n promptFile = candidate;\n break;\n }\n }\n\n if (!promptFile) {\n this.error(\n `Prompt file '${promptFileName}' not found in any of the search directories:\\n` +\n PROMPTS_DIRS.map((d) => ` - ${d}`).join('\\n'),\n );\n return;\n }\n\n // Module Resolution Logic\n const contextVars = { ...argv };\n if (moduleName) {\n const frontendPath = path.join(projectRoot, 'apps/frontend/modules', moduleName);\n const backendPath = path.join(projectRoot, 'apps/backend/modules', moduleName);\n\n let moduleRoot: string | undefined;\n let moduleType: 'frontend' | 'backend' | undefined;\n\n if (await fs.pathExists(frontendPath)) {\n moduleRoot = frontendPath;\n moduleType = 'frontend';\n } else if (await fs.pathExists(backendPath)) {\n moduleRoot = backendPath;\n moduleType = 'backend';\n }\n\n if (!moduleRoot) {\n this.error(\n `Module '${moduleName}' not found in apps/frontend/modules or apps/backend/modules.`,\n );\n return;\n }\n\n logger.debug(`[Context] Targeting ${moduleType} module: ${moduleName}`);\n contextVars.module_root = moduleRoot;\n contextVars.module_name = moduleName;\n contextVars.module_type = moduleType;\n contextVars.root_path = moduleRoot + '/';\n } else {\n if (!contextVars.root_path) {\n contextVars.root_path = process.cwd() + '/';\n }\n }\n\n // Configure Nunjucks\n const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(PROMPTS_DIRS), {\n autoescape: false,\n trimBlocks: true,\n lstripBlocks: true,\n });\n\n const asyncResolvers = new Map<string, Promise<string>>();\n let resolverId = 0;\n\n // Helper: context(path) -> runs repomix\n env.addGlobal('context', (targetPath: string) => {\n const id = `__NEXICAL_ASYNC_CONTEXT_${resolverId++}__`;\n const promise = (async () => {\n try {\n if (!existsSync(targetPath)) {\n logger.debug(`[Context] Path not found: ${targetPath}`);\n return `[Path not found: ${targetPath}]`;\n }\n\n const stats = statSync(targetPath);\n if (stats.isFile()) {\n logger.debug(`[Context] Reading file directly at: ${targetPath}`);\n const content = await fs.readFile(targetPath, 'utf-8');\n return `<CODEBASE_CONTEXT path=\"${targetPath}\">\\n${content}\\n</CODEBASE_CONTEXT>`;\n }\n\n logger.debug(`[Context] Analyzing codebase at: ${targetPath}`);\n const tempOutputFile = path.join(\n os.tmpdir(),\n `repomix-output-${Date.now()}-${Math.random().toString(36).substring(7)}.xml`,\n );\n\n await pack([targetPath], {\n input: { maxFileSize: 1024 * 1024 * 10 },\n output: {\n filePath: tempOutputFile,\n style: 'xml',\n showLineNumbers: false,\n fileSummary: false,\n directoryStructure: false,\n removeComments: false,\n removeEmptyLines: false,\n includeEmptyDirectories: false,\n topFilesLength: 5,\n parsableStyle: false,\n files: true,\n compress: false,\n truncateBase64: true,\n copyToClipboard: false,\n includeDiffs: false,\n includeLogs: false,\n includeLogsCount: 0,\n gitSortByChanges: false,\n includeFullDirectoryStructure: false,\n },\n ignore: {\n useGitignore: true,\n useDotIgnore: true,\n useDefaultPatterns: true,\n customPatterns: ['**/node_modules', '**/dist'],\n },\n include: [],\n security: { enableSecurityCheck: false },\n tokenCount: { encoding: 'o200k_base' },\n cwd: targetPath,\n } as unknown as Parameters<typeof pack>[1]);\n\n const output = await fs.readFile(tempOutputFile, 'utf-8');\n try {\n await fs.unlink(tempOutputFile);\n } catch {\n /* ignore */\n }\n return `<CODEBASE_CONTEXT path=\"${targetPath}\">\\n${output}\\n</CODEBASE_CONTEXT>`;\n } catch (error) {\n logger.error(`[Context] Error generating context for ${targetPath}: ${error}`);\n return `[Error generating context for ${targetPath}]`;\n }\n })();\n asyncResolvers.set(id, promise);\n return id;\n });\n\n // Helper: read(path) -> reads local file\n env.addGlobal('read', (relativePath: string | string[]) => {\n const id = `__NEXICAL_ASYNC_READ_${resolverId++}__`;\n const promise = (async () => {\n try {\n const cwdStr = process.cwd();\n if (Array.isArray(relativePath)) {\n const contents = await Promise.all(\n relativePath.map(async (p) => {\n const resolvedPath = path.resolve(cwdStr, p);\n if (!existsSync(resolvedPath)) {\n logger.debug(`[Read] File not found: ${resolvedPath}`);\n return `[File not found: ${resolvedPath}]`;\n }\n return await fs.readFile(resolvedPath, 'utf-8');\n }),\n );\n return contents.join('\\n\\n');\n } else if (typeof relativePath === 'string' && relativePath.includes(',')) {\n const contents = await Promise.all(\n relativePath.split(',').map(async (p) => {\n const resolvedPath = path.resolve(cwdStr, p.trim());\n if (!existsSync(resolvedPath)) {\n logger.debug(`[Read] File not found: ${resolvedPath}`);\n return `[File not found: ${resolvedPath}]`;\n }\n return await fs.readFile(resolvedPath, 'utf-8');\n }),\n );\n return contents.join('\\n\\n');\n }\n\n const resolvedPath = path.resolve(cwdStr, relativePath as string);\n if (!existsSync(resolvedPath)) {\n logger.debug(`[Read] File not found: ${resolvedPath}`);\n return `[File not found: ${resolvedPath}]`;\n }\n return await fs.readFile(resolvedPath, 'utf-8');\n } catch {\n logger.warn(`[Read] Warning: Could not read file: ${relativePath}`);\n return `[Error reading file ${relativePath}]`;\n }\n })();\n asyncResolvers.set(id, promise);\n return id;\n });\n\n // Read template content\n let templateContent: string;\n try {\n templateContent = await fs.readFile(promptFile, 'utf-8');\n } catch (error) {\n if (error instanceof Error) {\n this.error(`Error reading prompt file: ${error.message}`);\n } else {\n this.error(`Error reading prompt file: ${String(error)}`);\n }\n return;\n }\n\n // Render template\n logger.debug(\n `[Render] Rendering template with variables:`,\n JSON.stringify(contextVars, null, 2),\n );\n let renderedPrompt: string;\n try {\n renderedPrompt = env.renderString(templateContent, {\n ...contextVars,\n });\n } catch (e) {\n this.error(`Template render error: ${e}`);\n return;\n }\n\n // Resolve placeholders\n for (const [id, promise] of asyncResolvers.entries()) {\n try {\n const resolvedValue = await promise;\n renderedPrompt = renderedPrompt.replace(id, resolvedValue);\n } catch (e) {\n logger.error(`[Render] Failed to resolve async variable ${id}: ${e}`);\n renderedPrompt = renderedPrompt.replace(id, `[Error resolving id]`);\n }\n }\n\n // Buffer to file\n const tempFile = path.join(os.tmpdir(), '.temp_prompt_active.md');\n await fs.writeFile(tempFile, renderedPrompt, 'utf-8');\n logger.debug(`[Buffer] Wrote active prompt to ${tempFile}`);\n\n logger.info(`[Agent] Model rotation strategy: [${models.join(', ')}]`);\n\n // Extract AI configuration from nexical.yaml\n const configPath = path.join(projectRoot, 'nexical.yaml');\n let aiConfig: Record<string, unknown> = {};\n if (await fs.pathExists(configPath)) {\n try {\n const content = await fs.readFile(configPath, 'utf8');\n const config = YAML.parse(content) || {};\n aiConfig = (config.ai as Record<string, unknown>) || {};\n } catch {\n logger.warn('Failed to parse nexical.yaml AI config, using defaults.');\n }\n }\n\n // Create AI client\n const aiClient = AiClientFactory.create(aiConfig);\n\n let currentPrompt = renderedPrompt;\n let finalCode = 0;\n\n while (true) {\n let success = false;\n let lastOutput = '';\n\n for (const model of models) {\n logger.info(`[Agent] Attempting with model: \\x1b[36m${model}\\x1b[0m...`);\n const result = await aiClient.run(model, currentPrompt);\n\n if (result.code === 0) {\n success = true;\n lastOutput = result.output;\n break;\n }\n\n if (result.shouldRetry) {\n logger.info(`[Agent] Switching to next model...`);\n continue;\n } else {\n finalCode = result.code;\n break;\n }\n }\n\n if (!success) {\n if (finalCode === 0) finalCode = 1;\n this.error(`[Agent] \\u274C All attempts failed.`);\n break;\n }\n\n if (!isInteractive) {\n break;\n }\n\n currentPrompt += `\\n${lastOutput}`;\n\n const askLink = () => {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise<string>((resolve) => {\n this.info('\\n(Type \"exit\" or \"quit\" to end the session)');\n rl.question('> ', (ans) => {\n rl.close();\n resolve(ans);\n });\n });\n };\n\n const answer = await askLink();\n\n if (['exit', 'quit'].includes(answer.trim().toLowerCase())) {\n break;\n }\n\n currentPrompt += `\\nUser: ${answer}\\n`;\n }\n\n try {\n await fs.unlink(tempFile);\n logger.debug(`[Cleanup] Removed active prompt file`);\n } catch {\n // ignore cleanup errors\n }\n\n if (finalCode !== 0) {\n process.exit(finalCode);\n }\n }\n}\n"],"mappings":"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","names":["fs","resolvedPath"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexical/cli",
3
- "version": "0.11.15",
3
+ "version": "0.11.17",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,17 +29,23 @@
29
29
  ]
30
30
  },
31
31
  "dependencies": {
32
+ "@nexical/ai": "^0.1.3",
32
33
  "@nexical/cli-core": "^0.1.16",
33
34
  "dotenv": "^17.3.1",
34
35
  "fast-glob": "^3.3.3",
35
36
  "glob": "^13.0.5",
36
37
  "jiti": "^2.6.1",
38
+ "minimist": "^1.2.8",
39
+ "nunjucks": "^3.2.4",
40
+ "repomix": "^1.11.1",
37
41
  "yaml": "^2.8.2"
38
42
  },
39
43
  "devDependencies": {
40
44
  "@eslint/js": "^9.39.2",
41
45
  "@types/fs-extra": "^11.0.4",
46
+ "@types/minimist": "^1.2.5",
42
47
  "@types/node": "^25.3.0",
48
+ "@types/nunjucks": "^3.2.6",
43
49
  "@vitest/coverage-v8": "^4.0.18",
44
50
  "eslint": "^9.39.2",
45
51
  "eslint-config-prettier": "^10.1.8",
@@ -0,0 +1,395 @@
1
+ import { type CommandDefinition, BaseCommand, logger } from '@nexical/cli-core';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import os from 'node:os';
5
+ import nunjucks from 'nunjucks';
6
+ import minimist from 'minimist';
7
+ import readline from 'node:readline';
8
+ import YAML from 'yaml';
9
+ import { pack } from 'repomix';
10
+ import { existsSync, statSync } from 'node:fs';
11
+ import { AiClientFactory } from '@nexical/ai';
12
+
13
+ export default class PromptCommand extends BaseCommand {
14
+ static usage = 'prompt <prompt-name> [args...]';
15
+ static description = 'Run an AI prompt using templates from the prompts directory.';
16
+ static requiresProject = true;
17
+
18
+ static args: CommandDefinition = {
19
+ args: [
20
+ {
21
+ name: 'promptName',
22
+ required: true,
23
+ description: 'The name of the markdown file in the prompts directory.',
24
+ },
25
+ {
26
+ name: 'args...',
27
+ required: false,
28
+ description: 'Additional arguments for the template and command',
29
+ },
30
+ ],
31
+ options: [
32
+ {
33
+ name: '--module, -m <module>',
34
+ description:
35
+ 'Target a specific module (searches apps/frontend/modules and apps/backend/modules)',
36
+ },
37
+ { name: '--interactive, -i', description: 'Run in interactive chat mode' },
38
+ {
39
+ name: '--models <models>',
40
+ description: 'Comma-separated list of models to try',
41
+ default: 'gemini-3-flash-preview,gemini-3-pro-preview',
42
+ },
43
+ ],
44
+ };
45
+
46
+ async run(options: {
47
+ promptName: string;
48
+ args?: string[];
49
+ module?: string;
50
+ m?: string;
51
+ interactive?: boolean;
52
+ i?: boolean;
53
+ models?: string;
54
+ }) {
55
+ const projectRoot = this.projectRoot as string;
56
+ const promptName = options.promptName;
57
+
58
+ // Parse additional template flags
59
+ const argv = minimist(options.args || []);
60
+ const isInteractive = options.interactive || options.i || argv.interactive || argv.i;
61
+ const moduleName = options.module || options.m || argv.module || argv.m;
62
+ const modelsArg =
63
+ options.models || argv.models || 'gemini-3-flash-preview,gemini-3-pro-preview';
64
+ const models = modelsArg
65
+ .split(',')
66
+ .map((m: string) => m.trim())
67
+ .filter(Boolean);
68
+
69
+ // Resolve prompt file from multiple directories
70
+ const promptFileName = promptName.endsWith('.md') ? promptName : `${promptName}.md`;
71
+ const PROMPTS_DIRS = [path.join(projectRoot, 'prompts')];
72
+ const generatorAgentsPrompts = path.join(projectRoot, 'packages/generator/prompts/agents');
73
+
74
+ if (await fs.pathExists(generatorAgentsPrompts)) {
75
+ PROMPTS_DIRS.push(generatorAgentsPrompts);
76
+ }
77
+
78
+ let promptFile: string | undefined;
79
+ for (const dir of PROMPTS_DIRS) {
80
+ const candidate = path.join(dir, promptFileName);
81
+ if (await fs.pathExists(candidate)) {
82
+ promptFile = candidate;
83
+ break;
84
+ }
85
+ }
86
+
87
+ if (!promptFile) {
88
+ this.error(
89
+ `Prompt file '${promptFileName}' not found in any of the search directories:\n` +
90
+ PROMPTS_DIRS.map((d) => ` - ${d}`).join('\n'),
91
+ );
92
+ return;
93
+ }
94
+
95
+ // Module Resolution Logic
96
+ const contextVars = { ...argv };
97
+ if (moduleName) {
98
+ const frontendPath = path.join(projectRoot, 'apps/frontend/modules', moduleName);
99
+ const backendPath = path.join(projectRoot, 'apps/backend/modules', moduleName);
100
+
101
+ let moduleRoot: string | undefined;
102
+ let moduleType: 'frontend' | 'backend' | undefined;
103
+
104
+ if (await fs.pathExists(frontendPath)) {
105
+ moduleRoot = frontendPath;
106
+ moduleType = 'frontend';
107
+ } else if (await fs.pathExists(backendPath)) {
108
+ moduleRoot = backendPath;
109
+ moduleType = 'backend';
110
+ }
111
+
112
+ if (!moduleRoot) {
113
+ this.error(
114
+ `Module '${moduleName}' not found in apps/frontend/modules or apps/backend/modules.`,
115
+ );
116
+ return;
117
+ }
118
+
119
+ logger.debug(`[Context] Targeting ${moduleType} module: ${moduleName}`);
120
+ contextVars.module_root = moduleRoot;
121
+ contextVars.module_name = moduleName;
122
+ contextVars.module_type = moduleType;
123
+ contextVars.root_path = moduleRoot + '/';
124
+ } else {
125
+ if (!contextVars.root_path) {
126
+ contextVars.root_path = process.cwd() + '/';
127
+ }
128
+ }
129
+
130
+ // Configure Nunjucks
131
+ const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(PROMPTS_DIRS), {
132
+ autoescape: false,
133
+ trimBlocks: true,
134
+ lstripBlocks: true,
135
+ });
136
+
137
+ const asyncResolvers = new Map<string, Promise<string>>();
138
+ let resolverId = 0;
139
+
140
+ // Helper: context(path) -> runs repomix
141
+ env.addGlobal('context', (targetPath: string) => {
142
+ const id = `__NEXICAL_ASYNC_CONTEXT_${resolverId++}__`;
143
+ const promise = (async () => {
144
+ try {
145
+ if (!existsSync(targetPath)) {
146
+ logger.debug(`[Context] Path not found: ${targetPath}`);
147
+ return `[Path not found: ${targetPath}]`;
148
+ }
149
+
150
+ const stats = statSync(targetPath);
151
+ if (stats.isFile()) {
152
+ logger.debug(`[Context] Reading file directly at: ${targetPath}`);
153
+ const content = await fs.readFile(targetPath, 'utf-8');
154
+ return `<CODEBASE_CONTEXT path="${targetPath}">\n${content}\n</CODEBASE_CONTEXT>`;
155
+ }
156
+
157
+ logger.debug(`[Context] Analyzing codebase at: ${targetPath}`);
158
+ const tempOutputFile = path.join(
159
+ os.tmpdir(),
160
+ `repomix-output-${Date.now()}-${Math.random().toString(36).substring(7)}.xml`,
161
+ );
162
+
163
+ await pack([targetPath], {
164
+ input: { maxFileSize: 1024 * 1024 * 10 },
165
+ output: {
166
+ filePath: tempOutputFile,
167
+ style: 'xml',
168
+ showLineNumbers: false,
169
+ fileSummary: false,
170
+ directoryStructure: false,
171
+ removeComments: false,
172
+ removeEmptyLines: false,
173
+ includeEmptyDirectories: false,
174
+ topFilesLength: 5,
175
+ parsableStyle: false,
176
+ files: true,
177
+ compress: false,
178
+ truncateBase64: true,
179
+ copyToClipboard: false,
180
+ includeDiffs: false,
181
+ includeLogs: false,
182
+ includeLogsCount: 0,
183
+ gitSortByChanges: false,
184
+ includeFullDirectoryStructure: false,
185
+ },
186
+ ignore: {
187
+ useGitignore: true,
188
+ useDotIgnore: true,
189
+ useDefaultPatterns: true,
190
+ customPatterns: ['**/node_modules', '**/dist'],
191
+ },
192
+ include: [],
193
+ security: { enableSecurityCheck: false },
194
+ tokenCount: { encoding: 'o200k_base' },
195
+ cwd: targetPath,
196
+ } as unknown as Parameters<typeof pack>[1]);
197
+
198
+ const output = await fs.readFile(tempOutputFile, 'utf-8');
199
+ try {
200
+ await fs.unlink(tempOutputFile);
201
+ } catch {
202
+ /* ignore */
203
+ }
204
+ return `<CODEBASE_CONTEXT path="${targetPath}">\n${output}\n</CODEBASE_CONTEXT>`;
205
+ } catch (error) {
206
+ logger.error(`[Context] Error generating context for ${targetPath}: ${error}`);
207
+ return `[Error generating context for ${targetPath}]`;
208
+ }
209
+ })();
210
+ asyncResolvers.set(id, promise);
211
+ return id;
212
+ });
213
+
214
+ // Helper: read(path) -> reads local file
215
+ env.addGlobal('read', (relativePath: string | string[]) => {
216
+ const id = `__NEXICAL_ASYNC_READ_${resolverId++}__`;
217
+ const promise = (async () => {
218
+ try {
219
+ const cwdStr = process.cwd();
220
+ if (Array.isArray(relativePath)) {
221
+ const contents = await Promise.all(
222
+ relativePath.map(async (p) => {
223
+ const resolvedPath = path.resolve(cwdStr, p);
224
+ if (!existsSync(resolvedPath)) {
225
+ logger.debug(`[Read] File not found: ${resolvedPath}`);
226
+ return `[File not found: ${resolvedPath}]`;
227
+ }
228
+ return await fs.readFile(resolvedPath, 'utf-8');
229
+ }),
230
+ );
231
+ return contents.join('\n\n');
232
+ } else if (typeof relativePath === 'string' && relativePath.includes(',')) {
233
+ const contents = await Promise.all(
234
+ relativePath.split(',').map(async (p) => {
235
+ const resolvedPath = path.resolve(cwdStr, p.trim());
236
+ if (!existsSync(resolvedPath)) {
237
+ logger.debug(`[Read] File not found: ${resolvedPath}`);
238
+ return `[File not found: ${resolvedPath}]`;
239
+ }
240
+ return await fs.readFile(resolvedPath, 'utf-8');
241
+ }),
242
+ );
243
+ return contents.join('\n\n');
244
+ }
245
+
246
+ const resolvedPath = path.resolve(cwdStr, relativePath as string);
247
+ if (!existsSync(resolvedPath)) {
248
+ logger.debug(`[Read] File not found: ${resolvedPath}`);
249
+ return `[File not found: ${resolvedPath}]`;
250
+ }
251
+ return await fs.readFile(resolvedPath, 'utf-8');
252
+ } catch {
253
+ logger.warn(`[Read] Warning: Could not read file: ${relativePath}`);
254
+ return `[Error reading file ${relativePath}]`;
255
+ }
256
+ })();
257
+ asyncResolvers.set(id, promise);
258
+ return id;
259
+ });
260
+
261
+ // Read template content
262
+ let templateContent: string;
263
+ try {
264
+ templateContent = await fs.readFile(promptFile, 'utf-8');
265
+ } catch (error) {
266
+ if (error instanceof Error) {
267
+ this.error(`Error reading prompt file: ${error.message}`);
268
+ } else {
269
+ this.error(`Error reading prompt file: ${String(error)}`);
270
+ }
271
+ return;
272
+ }
273
+
274
+ // Render template
275
+ logger.debug(
276
+ `[Render] Rendering template with variables:`,
277
+ JSON.stringify(contextVars, null, 2),
278
+ );
279
+ let renderedPrompt: string;
280
+ try {
281
+ renderedPrompt = env.renderString(templateContent, {
282
+ ...contextVars,
283
+ });
284
+ } catch (e) {
285
+ this.error(`Template render error: ${e}`);
286
+ return;
287
+ }
288
+
289
+ // Resolve placeholders
290
+ for (const [id, promise] of asyncResolvers.entries()) {
291
+ try {
292
+ const resolvedValue = await promise;
293
+ renderedPrompt = renderedPrompt.replace(id, resolvedValue);
294
+ } catch (e) {
295
+ logger.error(`[Render] Failed to resolve async variable ${id}: ${e}`);
296
+ renderedPrompt = renderedPrompt.replace(id, `[Error resolving id]`);
297
+ }
298
+ }
299
+
300
+ // Buffer to file
301
+ const tempFile = path.join(os.tmpdir(), '.temp_prompt_active.md');
302
+ await fs.writeFile(tempFile, renderedPrompt, 'utf-8');
303
+ logger.debug(`[Buffer] Wrote active prompt to ${tempFile}`);
304
+
305
+ logger.info(`[Agent] Model rotation strategy: [${models.join(', ')}]`);
306
+
307
+ // Extract AI configuration from nexical.yaml
308
+ const configPath = path.join(projectRoot, 'nexical.yaml');
309
+ let aiConfig: Record<string, unknown> = {};
310
+ if (await fs.pathExists(configPath)) {
311
+ try {
312
+ const content = await fs.readFile(configPath, 'utf8');
313
+ const config = YAML.parse(content) || {};
314
+ aiConfig = (config.ai as Record<string, unknown>) || {};
315
+ } catch {
316
+ logger.warn('Failed to parse nexical.yaml AI config, using defaults.');
317
+ }
318
+ }
319
+
320
+ // Create AI client
321
+ const aiClient = AiClientFactory.create(aiConfig);
322
+
323
+ let currentPrompt = renderedPrompt;
324
+ let finalCode = 0;
325
+
326
+ while (true) {
327
+ let success = false;
328
+ let lastOutput = '';
329
+
330
+ for (const model of models) {
331
+ logger.info(`[Agent] Attempting with model: \x1b[36m${model}\x1b[0m...`);
332
+ const result = await aiClient.run(model, currentPrompt);
333
+
334
+ if (result.code === 0) {
335
+ success = true;
336
+ lastOutput = result.output;
337
+ break;
338
+ }
339
+
340
+ if (result.shouldRetry) {
341
+ logger.info(`[Agent] Switching to next model...`);
342
+ continue;
343
+ } else {
344
+ finalCode = result.code;
345
+ break;
346
+ }
347
+ }
348
+
349
+ if (!success) {
350
+ if (finalCode === 0) finalCode = 1;
351
+ this.error(`[Agent] \u274C All attempts failed.`);
352
+ break;
353
+ }
354
+
355
+ if (!isInteractive) {
356
+ break;
357
+ }
358
+
359
+ currentPrompt += `\n${lastOutput}`;
360
+
361
+ const askLink = () => {
362
+ const rl = readline.createInterface({
363
+ input: process.stdin,
364
+ output: process.stdout,
365
+ });
366
+ return new Promise<string>((resolve) => {
367
+ this.info('\n(Type "exit" or "quit" to end the session)');
368
+ rl.question('> ', (ans) => {
369
+ rl.close();
370
+ resolve(ans);
371
+ });
372
+ });
373
+ };
374
+
375
+ const answer = await askLink();
376
+
377
+ if (['exit', 'quit'].includes(answer.trim().toLowerCase())) {
378
+ break;
379
+ }
380
+
381
+ currentPrompt += `\nUser: ${answer}\n`;
382
+ }
383
+
384
+ try {
385
+ await fs.unlink(tempFile);
386
+ logger.debug(`[Cleanup] Removed active prompt file`);
387
+ } catch {
388
+ // ignore cleanup errors
389
+ }
390
+
391
+ if (finalCode !== 0) {
392
+ process.exit(finalCode);
393
+ }
394
+ }
395
+ }
package/GEMINI.md DELETED
@@ -1,199 +0,0 @@
1
- # Nexical CLI Core Development Guide
2
-
3
- This guide details how to build command libraries and CLIs using the `@nexical/cli-core` framework within this project.
4
-
5
- ## Overview
6
-
7
- The project is configured to use `@nexical/cli-core` as its CLI framework. usage entails:
8
-
9
- 1. **Entry Point**: `index.ts` initializes the `CLI` instance.
10
- 2. **Command Discovery**: The CLI automatically scans `src/commands` for command files.
11
- 3. **Command Implementation**: Commands are TypeScript classes extending `BaseCommand`.
12
-
13
- ## Directory Structure
14
-
15
- Commands are defined in `src/commands`. The file structure directly maps to the command hierarchy.
16
-
17
- | File Path | Command | Description |
18
- | :---------------------------- | :---------------- | :-------------------------------- |
19
- | `src/commands/init.ts` | `app init` | Root level command |
20
- | `src/commands/user/create.ts` | `app user create` | Subcommand |
21
- | `src/commands/user/index.ts` | `app user` | Parent command handler (optional) |
22
-
23
- > **Note**: The CLI name (`app`) is configured in `index.ts`.
24
-
25
- ## Creating a New Command
26
-
27
- To create a new command, add a `.ts` file in `src/commands`.
28
-
29
- ### 1. Basic Command Template
30
-
31
- Create `src/commands/hello.ts`:
32
-
33
- ```typescript
34
- import { BaseCommand } from '@nexical/cli-core';
35
-
36
- export default class HelloCommand extends BaseCommand {
37
- // Description displayed in help menus
38
- static description = 'Prints a hello message';
39
-
40
- // The main execution method
41
- async run(options: any) {
42
- this.success('Hello World!');
43
- }
44
- }
45
- ```
46
-
47
- **Run it:**
48
-
49
- ```bash
50
- npm run cli hello
51
- ```
52
-
53
- ### 2. Arguments and Options
54
-
55
- Use the static `args` property to define inputs.
56
-
57
- ```typescript
58
- import { BaseCommand } from '@nexical/cli-core';
59
-
60
- export default class GreetCommand extends BaseCommand {
61
- static description = 'Greets a user';
62
-
63
- static args = {
64
- // Positional Arguments
65
- args: [
66
- {
67
- name: 'name',
68
- description: 'Name of the user',
69
- required: true,
70
- },
71
- ],
72
- // Options (Flags)
73
- options: [
74
- {
75
- name: '--loud',
76
- description: 'Print in uppercase',
77
- default: false,
78
- },
79
- {
80
- name: '-c, --count <n>',
81
- description: 'Number of times to greet',
82
- default: 1,
83
- },
84
- ],
85
- };
86
-
87
- async run(options: any) {
88
- // 'name' is mapped from args
89
- // 'loud' and 'count' are mapped from options
90
- const { name, loud, count } = options;
91
-
92
- let message = `Hello, ${name}`;
93
- if (loud) message = message.toUpperCase();
94
-
95
- for (let i = 0; i < count; i++) {
96
- this.info(message);
97
- }
98
- }
99
- }
100
- ```
101
-
102
- **Run it:**
103
-
104
- ```bash
105
- npm run cli greet Adrian --loud --count 3
106
- ```
107
-
108
- ### 3. User Input & Prompts
109
-
110
- `BaseCommand` provides built-in methods for interactivity.
111
-
112
- ```typescript
113
- export default class InteractiveCommand extends BaseCommand {
114
- async run() {
115
- // Simple confirmation or input
116
- const name = await this.prompt('What is your name?');
117
-
118
- this.success(`Nice to meet you, ${name}`);
119
- }
120
- }
121
- ```
122
-
123
- ### 4. Output Helpers
124
-
125
- Use the built-in helper methods for consistent logging:
126
-
127
- - `this.success(msg)`: Green checkmark (✔ Success)
128
- - `this.error(msg)`: Red cross (✖ Error) - **Exits process**
129
- - `this.warn(msg)`: Yellow warning (⚠ Warning)
130
- - `this.info(msg)`: Standard log
131
- - `this.notice(msg)`: Blue notice (📢 Note)
132
- - `this.input(msg)`: Cyan input prompt (? Question)
133
-
134
- ## Subcommands
135
-
136
- To create grouped commands (e.g., `user create`, `user list`), use directories.
137
-
138
- 1. **Create Directory**: `src/commands/user/`
139
- 2. **Add Commands**:
140
- - `src/commands/user/create.ts` -> `app user create`
141
- - `src/commands/user/list.ts` -> `app user list`
142
-
143
- ### Index Commands (Container Commands)
144
-
145
- If you need the parent command `app user` to do something (or just provide a description for the group), create `src/commands/user/index.ts`.
146
-
147
- ```typescript
148
- // src/commands/user/index.ts
149
- import { BaseCommand } from '@nexical/cli-core';
150
-
151
- export default class UserCommand extends BaseCommand {
152
- static description = 'Manage users';
153
-
154
- async run() {
155
- // This runs when user types 'app user' without a subcommand
156
- // Often used to show help
157
- this.cli.getRawCLI().outputHelp();
158
- }
159
- }
160
- ```
161
-
162
- ## Internal API Reference
163
-
164
- ### `BaseCommand`
165
-
166
- All commands inherit from `BaseCommand`.
167
-
168
- **Properties:**
169
-
170
- - `cli`: Access to the main `CLI` instance.
171
- - `projectRoot`: Path to the project root (if detected).
172
- - `config`: Loaded configuration from `{cliName}.yml`.
173
- - `globalOptions`: Global flags passed to the CLI (e.g., `--debug`).
174
-
175
- **Methods:**
176
-
177
- - `init()`: Called before `run()`. Useful for setup.
178
- - `run(options)`: Abstract method. Must be implemented.
179
- - `prompt(message)`: Async, returns string.
180
-
181
- **Static Properties:**
182
-
183
- - `description`: String. Shown in help.
184
- - `args`: Object. Defines arguments and options.
185
- - `args`: Array of `{ name, description, required, default }`.
186
- - `options`: Array of `{ name, description, default }`.
187
- - `requiresProject`: Boolean. If true, command fails if not run inside a project with a config file.
188
-
189
- ## Best Practices
190
-
191
- 1. **Type Safety**: While `options` is `any` in `run()`, validate inputs early.
192
- 2. **Error Handling**: Use `this.error()` for fatal errors to ensure proper exit codes.
193
- 3. **Clean Output**: Use the helper methods (`success`, `info`, etc.) instead of `console.log` for a consistent UI.
194
- 4. **Async**: The `run` method is async. Always `await` asynchronous operations.
195
-
196
- ## Troubleshooting
197
-
198
- - **Command not found**: Ensure the file exports a class leveraging `export default` and extends `BaseCommand`.
199
- - **Changes not reflected**: If using `tsup`, ensure you are building or running in dev mode (`npm run dev`). For `npm run cli` using `ts-node` (via `cli.ts` or similar), changes should be instant.