@nexical/cli 0.11.17 → 0.11.19
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/chunk-54HY52LH.js +38 -0
- package/dist/chunk-54HY52LH.js.map +1 -0
- package/dist/index.js +6 -23
- package/dist/index.js.map +1 -1
- package/dist/src/commands/prompt.js +10 -240
- package/dist/src/commands/prompt.js.map +1 -1
- package/dist/src/utils/filter.d.ts +9 -0
- package/dist/src/utils/filter.js +9 -0
- package/dist/src/utils/filter.js.map +1 -0
- package/index.ts +2 -31
- package/package.json +2 -4
- package/src/commands/prompt.ts +10 -273
- package/src/utils/filter.ts +47 -0
- package/test/integration/commands/prompt.integration.test.ts +110 -0
- package/test/unit/commands/prompt.test.ts +257 -0
- package/test/unit/utils/filter.test.ts +40 -0
- package/vitest.config.ts +1 -1
package/src/commands/prompt.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { type CommandDefinition, BaseCommand, logger } from '@nexical/cli-core';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import nunjucks from 'nunjucks';
|
|
6
4
|
import minimist from 'minimist';
|
|
7
|
-
import readline from 'node:readline';
|
|
8
5
|
import YAML from 'yaml';
|
|
9
|
-
import {
|
|
10
|
-
import { existsSync, statSync } from 'node:fs';
|
|
11
|
-
import { AiClientFactory } from '@nexical/ai';
|
|
6
|
+
import { PromptRunner } from '@nexical/ai';
|
|
12
7
|
|
|
13
8
|
export default class PromptCommand extends BaseCommand {
|
|
14
9
|
static usage = 'prompt <prompt-name> [args...]';
|
|
@@ -57,7 +52,7 @@ export default class PromptCommand extends BaseCommand {
|
|
|
57
52
|
|
|
58
53
|
// Parse additional template flags
|
|
59
54
|
const argv = minimist(options.args || []);
|
|
60
|
-
const isInteractive = options.interactive || options.i || argv.interactive || argv.i;
|
|
55
|
+
const isInteractive = !!(options.interactive || options.i || argv.interactive || argv.i);
|
|
61
56
|
const moduleName = options.module || options.m || argv.module || argv.m;
|
|
62
57
|
const modelsArg =
|
|
63
58
|
options.models || argv.models || 'gemini-3-flash-preview,gemini-3-pro-preview';
|
|
@@ -66,8 +61,6 @@ export default class PromptCommand extends BaseCommand {
|
|
|
66
61
|
.map((m: string) => m.trim())
|
|
67
62
|
.filter(Boolean);
|
|
68
63
|
|
|
69
|
-
// Resolve prompt file from multiple directories
|
|
70
|
-
const promptFileName = promptName.endsWith('.md') ? promptName : `${promptName}.md`;
|
|
71
64
|
const PROMPTS_DIRS = [path.join(projectRoot, 'prompts')];
|
|
72
65
|
const generatorAgentsPrompts = path.join(projectRoot, 'packages/generator/prompts/agents');
|
|
73
66
|
|
|
@@ -75,23 +68,6 @@ export default class PromptCommand extends BaseCommand {
|
|
|
75
68
|
PROMPTS_DIRS.push(generatorAgentsPrompts);
|
|
76
69
|
}
|
|
77
70
|
|
|
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
71
|
// Module Resolution Logic
|
|
96
72
|
const contextVars = { ...argv };
|
|
97
73
|
if (moduleName) {
|
|
@@ -127,183 +103,6 @@ export default class PromptCommand extends BaseCommand {
|
|
|
127
103
|
}
|
|
128
104
|
}
|
|
129
105
|
|
|
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
106
|
// Extract AI configuration from nexical.yaml
|
|
308
107
|
const configPath = path.join(projectRoot, 'nexical.yaml');
|
|
309
108
|
let aiConfig: Record<string, unknown> = {};
|
|
@@ -317,76 +116,14 @@ export default class PromptCommand extends BaseCommand {
|
|
|
317
116
|
}
|
|
318
117
|
}
|
|
319
118
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
}
|
|
119
|
+
const finalCode = await PromptRunner.run({
|
|
120
|
+
promptName,
|
|
121
|
+
promptDirs: PROMPTS_DIRS,
|
|
122
|
+
args: contextVars,
|
|
123
|
+
aiConfig,
|
|
124
|
+
models,
|
|
125
|
+
interactive: isInteractive as boolean,
|
|
126
|
+
});
|
|
390
127
|
|
|
391
128
|
if (finalCode !== 0) {
|
|
392
129
|
process.exit(finalCode);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters out duplicate core commands and source versions from additional command directories.
|
|
5
|
+
* @param additionalCommands List of discovered command directories.
|
|
6
|
+
* @param coreCommandsDir The primary core commands directory.
|
|
7
|
+
* @returns Filtered list of additional command directories.
|
|
8
|
+
*/
|
|
9
|
+
export function filterCommandDirectories(
|
|
10
|
+
additionalCommands: string[],
|
|
11
|
+
coreCommandsDir: string,
|
|
12
|
+
): string[] {
|
|
13
|
+
return additionalCommands.filter((dir) => {
|
|
14
|
+
const resolvedDir = path.resolve(dir);
|
|
15
|
+
const resolvedCore = path.resolve(coreCommandsDir);
|
|
16
|
+
|
|
17
|
+
if (resolvedDir === resolvedCore) return false;
|
|
18
|
+
|
|
19
|
+
// Check if this is another instance of the core CLI commands (by checking path suffix)
|
|
20
|
+
const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');
|
|
21
|
+
const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');
|
|
22
|
+
const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
resolvedDir.endsWith(coreSuffix) ||
|
|
26
|
+
resolvedDir.endsWith(coreSuffixSrc) ||
|
|
27
|
+
resolvedDir.endsWith(coreSuffixRawSrc)
|
|
28
|
+
) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle mismatch between dist/src and src/
|
|
33
|
+
const distSuffix = path.join('dist', 'src', 'commands');
|
|
34
|
+
const srcSuffix = path.join('src', 'commands');
|
|
35
|
+
if (resolvedCore.endsWith(distSuffix)) {
|
|
36
|
+
const baseDir = resolvedCore.substring(0, resolvedCore.length - distSuffix.length);
|
|
37
|
+
const srcVersion = path.join(baseDir, srcSuffix);
|
|
38
|
+
const normalizedDir = path.normalize(resolvedDir);
|
|
39
|
+
const normalizedSrc = path.normalize(srcVersion);
|
|
40
|
+
if (normalizedDir === normalizedSrc) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest';
|
|
2
|
+
import PromptCommand from '../../../src/commands/prompt.js';
|
|
3
|
+
import { createTempDir, createMockRepo, cleanupTestRoot } from '../../utils/integration-helpers.js';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { CLI } from '@nexical/cli-core';
|
|
7
|
+
import { PromptRunner } from '@nexical/ai';
|
|
8
|
+
|
|
9
|
+
vi.mock('@nexical/ai', () => ({
|
|
10
|
+
PromptRunner: {
|
|
11
|
+
run: vi.fn().mockResolvedValue(0),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe('Prompt Command Integration', () => {
|
|
16
|
+
let projectDir: string;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
const temp = await createTempDir('prompt-integration-');
|
|
20
|
+
projectDir = await createMockRepo(temp, {
|
|
21
|
+
'package.json': '{"name": "prompt-project", "version": "1.0.0"}',
|
|
22
|
+
'nexical.yaml': 'ai:\n provider: vertex',
|
|
23
|
+
'prompts/test-prompt.md': 'Testing {{ name }}',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
await cleanupTestRoot();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should resolve project config and call PromptRunner', async () => {
|
|
32
|
+
const originalCwd = process.cwd();
|
|
33
|
+
try {
|
|
34
|
+
process.chdir(projectDir);
|
|
35
|
+
const cli = new CLI({ commandName: 'nexical' });
|
|
36
|
+
const command = new PromptCommand(cli);
|
|
37
|
+
|
|
38
|
+
// We need to set projectRoot manually because BaseCommand detection
|
|
39
|
+
// might fail if not properly initialized in test env
|
|
40
|
+
(command as unknown as { projectRoot: string }).projectRoot = projectDir;
|
|
41
|
+
|
|
42
|
+
await command.run({ promptName: 'test-prompt' });
|
|
43
|
+
|
|
44
|
+
expect(PromptRunner.run).toHaveBeenCalledWith(
|
|
45
|
+
expect.objectContaining({
|
|
46
|
+
promptName: 'test-prompt',
|
|
47
|
+
promptDirs: [path.join(projectDir, 'prompts')],
|
|
48
|
+
aiConfig: { provider: 'vertex' },
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
} finally {
|
|
52
|
+
process.chdir(originalCwd);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should resolve module context in integration', async () => {
|
|
57
|
+
const originalCwd = process.cwd();
|
|
58
|
+
try {
|
|
59
|
+
process.chdir(projectDir);
|
|
60
|
+
|
|
61
|
+
const moduleDir = path.join(projectDir, 'apps/frontend/modules/my-mod');
|
|
62
|
+
await fs.ensureDir(moduleDir);
|
|
63
|
+
|
|
64
|
+
const cli = new CLI({ commandName: 'nexical' });
|
|
65
|
+
const command = new PromptCommand(cli);
|
|
66
|
+
(command as unknown as { projectRoot: string }).projectRoot = projectDir;
|
|
67
|
+
|
|
68
|
+
await command.run({ promptName: 'test-prompt', module: 'my-mod' });
|
|
69
|
+
|
|
70
|
+
expect(PromptRunner.run).toHaveBeenCalledWith(
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
args: expect.objectContaining({
|
|
73
|
+
module_name: 'my-mod',
|
|
74
|
+
module_type: 'frontend',
|
|
75
|
+
module_root: moduleDir,
|
|
76
|
+
}),
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
} finally {
|
|
80
|
+
process.chdir(originalCwd);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should include generator agents prompts if they exist in integration', async () => {
|
|
85
|
+
const originalCwd = process.cwd();
|
|
86
|
+
try {
|
|
87
|
+
process.chdir(projectDir);
|
|
88
|
+
|
|
89
|
+
const generatorPromptsDir = path.join(projectDir, 'packages/generator/prompts/agents');
|
|
90
|
+
await fs.ensureDir(generatorPromptsDir);
|
|
91
|
+
|
|
92
|
+
const cli = new CLI({ commandName: 'nexical' });
|
|
93
|
+
const command = new PromptCommand(cli);
|
|
94
|
+
(command as unknown as { projectRoot: string }).projectRoot = projectDir;
|
|
95
|
+
|
|
96
|
+
await command.run({ promptName: 'test-prompt' });
|
|
97
|
+
|
|
98
|
+
expect(PromptRunner.run).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
promptDirs: expect.arrayContaining([
|
|
101
|
+
path.join(projectDir, 'prompts'),
|
|
102
|
+
generatorPromptsDir,
|
|
103
|
+
]),
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
} finally {
|
|
107
|
+
process.chdir(originalCwd);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|