Skip to content

Commit 715ee06

Browse files
committed
fix(core): emit only NDJSON from nx init when running under an AI agent
`nx init` mixes NDJSON events with `Fetching nx...`, `CREATE`/`UPDATE` file lines, `output.log` titles, and a trailing `---USER_NEXT_STEPS---` block — agents must parse stdout line-by-line with brittle heuristics. Under any agent detected by `isAiAgent()`, stdout is pure NDJSON. Sets `NX_AI_AGENT_INIT=1` early in `initHandler` so it survives the tmp-install re-entry and the `nx g <plugin>:init` subprocess. `output.*` methods, `printChanges()` (dot-nx + generate), and the `Fetching ${pkg}` log all gate on it. `userNextSteps` already lives in the `complete` event, so the trailing human block is dropped. Human TTY behavior unchanged. Fixes NXC-4368
1 parent 0fb8515 commit 715ee06

5 files changed

Lines changed: 32 additions & 4 deletions

File tree

‎packages/nx/src/command-line/generate/generate.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ export async function generate(args: { [k: string]: any }) {
416416

417417
const changes = host.listChanges();
418418

419-
if (!opts.quiet) {
419+
if (!opts.quiet && process.env.NX_AI_AGENT_INIT !== 'true') {
420420
printChanges(changes);
421421
}
422422
if (!opts.dryRun) {

‎packages/nx/src/command-line/init/implementation/dot-nx/add-nx-scripts.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ export function generateDotNxSetup(version?: string) {
6161
mode: FsConstants.S_IXUSR | FsConstants.S_IRUSR | FsConstants.S_IWUSR,
6262
});
6363
const changes = host.listChanges();
64-
printChanges(changes);
64+
if (process.env.NX_AI_AGENT_INIT !== 'true') {
65+
printChanges(changes);
66+
}
6567
flushChanges(host.root, changes);
6668
// Ensure that the dot-nx installation is available.
6769
// This is needed when using a global nx with dot-nx, otherwise running any nx command using global command will fail due to missing modules.

‎packages/nx/src/command-line/init/init-v2.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export async function initHandler(
6767
options: InitArgs,
6868
inner = false
6969
): Promise<void> {
70+
// Set early so all subsequent in-process and subprocess writes (e.g. the
71+
// tmp-install fetch log, nx generate output from plugin init generators)
72+
// honor NDJSON-only mode.
73+
if (isAiAgent()) {
74+
process.env.NX_AI_AGENT_INIT = 'true';
75+
}
76+
7077
// Use environment variable to force local execution
7178
if (process.env.NX_USE_LOCAL === 'true' || inner) {
7279
return await initHandlerImpl(options);
@@ -307,7 +314,9 @@ async function runInit(
307314
logProgress('detecting', 'Detected non-JavaScript project');
308315
}
309316
generateDotNxSetup(version);
310-
console.log('');
317+
if (!aiMode) {
318+
console.log('');
319+
}
311320
} else {
312321
if (aiMode) {
313322
logProgress('detecting', 'Detected NPM project');

‎packages/nx/src/utils/output.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ class CLIOutput {
5656
cliName = 'NX';
5757
formatCommand = (taskId: string) => `${pc.dim('nx run')} ${taskId}`;
5858

59+
/**
60+
* When running `nx init` under an AI agent we emit only NDJSON on stdout.
61+
* All human-formatted output via this class becomes a no-op so the stdout
62+
* stream is parseable line-by-line. Callers that need to surface failures
63+
* to agents must do so through the structured error event emitted by the
64+
* init command (see `writeAiOutput(buildErrorResult(...))`); silent
65+
* `output.error` here is intentional — agents check exit code plus the
66+
* final NDJSON event, not human-formatted text.
67+
*/
68+
private get isSilenced(): boolean {
69+
return process.env.NX_AI_AGENT_INIT === 'true';
70+
}
71+
5972
/**
6073
* Longer dash character which forms more of a continuous line when place side to side
6174
* with itself, unlike the standard dash character
@@ -86,10 +99,12 @@ class CLIOutput {
8699
dim = pc.dim;
87100

88101
private writeToStdOut(str: string) {
102+
if (this.isSilenced) return;
89103
process.stdout.write(str);
90104
}
91105

92106
overwriteLine(lineText: string = '') {
107+
if (this.isSilenced) return;
93108
// Ensure we always start writing from column 0.
94109
readline.cursorTo(process.stdout, 0);
95110
// this replaces the existing text up to the new line length

‎packages/nx/src/utils/package-json.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ function preparePackageInstallation(pkg: string, requiredVersion: string) {
382382
cleanup: () => {},
383383
};
384384

385-
console.log(`Fetching ${pkg}...`);
385+
if (process.env.NX_AI_AGENT_INIT !== 'true') {
386+
console.log(`Fetching ${pkg}...`);
387+
}
386388
const packageManager = detectPackageManager();
387389
const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
388390
generatePackageManagerFiles(tempDir, packageManager);

0 commit comments

Comments
 (0)