Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion atmn/src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import { pull as newPull } from "./commands/pull/pull.js"; // New pull implement
import { FRONTEND_URL } from "./constants.js";
import { fetchOrganizationMe } from "./lib/api/endpoints/index.js";
import { isProd, setCliContext } from "./lib/env/cliContext.js";
import { checkAtmnInstalled } from "./lib/precheck.js";
import { readFromEnv } from "./lib/utils.js";
// Import Ink views
import { QueryProvider } from "./views/react/components/providers/QueryProvider.js";
import { InitFlow } from "./views/react/init/InitFlow.js";
import { InstallPrompt } from "./views/react/install/InstallPrompt.js";
import { PullView } from "./views/react/pull/Pull.js";
import { APP_VERSION } from "./lib/version.js";

Expand Down Expand Up @@ -450,4 +452,13 @@ const originalEmit = process.emitWarning as any;
return originalEmit(warning, ...args);
};

program.parse();
async function main() {
if (!checkAtmnInstalled() && process.stdout.isTTY) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The interactive precheck runs before program.parse(), so CLI flags like --headless haven't been processed yet. This breaks CI/agent workflows where --headless is expected to suppress interactivity. Consider parsing a minimal flag check first, or checking for --headless in process.argv before the precheck.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atmn/src/cli.tsx, line 456:

<comment>The interactive precheck runs before `program.parse()`, so CLI flags like `--headless` haven't been processed yet. This breaks CI/agent workflows where `--headless` is expected to suppress interactivity. Consider parsing a minimal flag check first, or checking for `--headless` in `process.argv` before the precheck.</comment>

<file context>
@@ -450,4 +452,13 @@ const originalEmit = process.emitWarning as any;
 
-program.parse();
+async function main() {
+	if (!checkAtmnInstalled() && process.stdout.isTTY) {
+		const { waitUntilExit } = render(<InstallPrompt />);
+		await waitUntilExit();
</file context>
Fix with Cubic

const { waitUntilExit } = render(<InstallPrompt />);
await waitUntilExit();
}

program.parse();
}
Comment on lines +455 to +462
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Precheck runs before flags

main() runs the interactive install precheck before program.parse(), so global flags like --headless (which you set in the preAction hook) are not applied yet. This means atmn --headless ... will still see process.stdout.isTTY as true and can trigger the Ink InstallPrompt, breaking CI/agent usage where --headless is expected to suppress interactivity.

Prompt To Fix With AI
This is a comment left during a code review.
Path: atmn/src/cli.tsx
Line: 455:462

Comment:
**Precheck runs before flags**

`main()` runs the interactive install precheck before `program.parse()`, so global flags like `--headless` (which you set in the `preAction` hook) are not applied yet. This means `atmn --headless ...` will still see `process.stdout.isTTY` as true and can trigger the Ink `InstallPrompt`, breaking CI/agent usage where `--headless` is expected to suppress interactivity.

How can I resolve this? If you propose a fix, please make it concise.


main();
23 changes: 23 additions & 0 deletions atmn/src/lib/precheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from "fs";
import path from "path";

/**
* Check if `atmn` is listed in the project's package.json
* (dependencies or devDependencies). This ensures autumn.config.ts
* can import from "atmn" without type errors.
*/
export function checkAtmnInstalled(): boolean {
try {
const packageJsonPath = path.join(process.cwd(), "package.json");
if (!fs.existsSync(packageJsonPath)) {
return false;
}

const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
return !!(
packageJson.dependencies?.atmn || packageJson.devDependencies?.atmn
);
} catch {
return false;
}
}
95 changes: 95 additions & 0 deletions atmn/src/views/react/install/InstallPrompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { execSync } from "child_process";
import { Box, Text, useApp } from "ink";
import { useCallback, useState } from "react";
import {
SelectMenu,
type SelectMenuItem,
StatusLine,
} from "../components/index.js";

type PackageManager = "npm" | "pnpm" | "bun";
type InstallState = "choosing" | "installing" | "success" | "skipped";

export function InstallPrompt() {
const { exit } = useApp();
const [state, setState] = useState<InstallState>("choosing");
const [error, setError] = useState<string | null>(null);

const items: SelectMenuItem<PackageManager>[] = [
{ label: "npm", value: "npm" },
{ label: "pnpm", value: "pnpm" },
{ label: "bun", value: "bun" },
];

const handleSelect = useCallback(
(item: SelectMenuItem<PackageManager>) => {
setState("installing");

const commands: Record<PackageManager, string> = {
npm: "npm install atmn",
pnpm: "pnpm add atmn",
bun: "bun add atmn",
};

try {
execSync(commands[item.value], { stdio: "inherit" });
setState("success");
setTimeout(() => exit(), 500);
} catch (err) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The error handler sets state but never calls exit(), causing the CLI to hang indefinitely when installation fails. The waitUntilExit() in cli.tsx will never resolve because the Ink app remains running. Add exit() after setting the error to allow the process to terminate gracefully.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atmn/src/views/react/install/InstallPrompt.tsx, line 38:

<comment>The error handler sets state but never calls `exit()`, causing the CLI to hang indefinitely when installation fails. The `waitUntilExit()` in cli.tsx will never resolve because the Ink app remains running. Add `exit()` after setting the error to allow the process to terminate gracefully.</comment>

<file context>
@@ -0,0 +1,95 @@
+				execSync(commands[item.value], { stdio: "inherit" });
+				setState("success");
+				setTimeout(() => exit(), 500);
+			} catch (err) {
+				setError(
+					err instanceof Error ? err.message : "Installation failed",
</file context>
Fix with Cubic

setError(
err instanceof Error ? err.message : "Installation failed",
);
}
Comment on lines +34 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI can hang on failure

When execSync(...) throws, you set error but never call exit(), so the Ink renderer in cli.tsx will wait indefinitely on waitUntilExit() with no way to continue unless the user manually terminates the process. The error UI renders, but the process doesn’t exit or return control to program.parse().

Prompt To Fix With AI
This is a comment left during a code review.
Path: atmn/src/views/react/install/InstallPrompt.tsx
Line: 34:42

Comment:
**CLI can hang on failure**

When `execSync(...)` throws, you set `error` but never call `exit()`, so the Ink renderer in `cli.tsx` will wait indefinitely on `waitUntilExit()` with no way to continue unless the user manually terminates the process. The error UI renders, but the process doesn’t exit or return control to `program.parse()`.

How can I resolve this? If you propose a fix, please make it concise.

},
[exit],
);

if (error) {
return (
<Box flexDirection="column" paddingLeft={1}>
<StatusLine status="error" message={`Failed to install atmn: ${error}`} />
<Text dimColor>
Install manually: npm install atmn / pnpm add atmn / bun add atmn
</Text>
</Box>
);
}

return (
<Box flexDirection="column" paddingLeft={1}>
<Box marginBottom={1}>
<Text>
<Text color="yellow">!</Text> The{" "}
<Text color="magenta" bold>
atmn
</Text>{" "}
package is not in your dependencies.
</Text>
</Box>
<Text dimColor>
It's needed so autumn.config.ts can import types and builders.
</Text>

{state === "choosing" && (
<Box flexDirection="column" marginTop={1}>
<Text>Install with:</Text>
<Box marginTop={1}>
<SelectMenu items={items} onSelect={handleSelect} />
</Box>
</Box>
)}

{state === "installing" && (
<Box marginTop={1}>
<StatusLine status="loading" message="Installing atmn..." />
</Box>
)}

{state === "success" && (
<Box marginTop={1}>
<StatusLine status="success" message="atmn installed" />
</Box>
)}
</Box>
);
}