diff --git a/atmn/src/cli.tsx b/atmn/src/cli.tsx
index 47f289a..2da3b7f 100644
--- a/atmn/src/cli.tsx
+++ b/atmn/src/cli.tsx
@@ -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";
@@ -450,4 +452,13 @@ const originalEmit = process.emitWarning as any;
return originalEmit(warning, ...args);
};
-program.parse();
+async function main() {
+ if (!checkAtmnInstalled() && process.stdout.isTTY) {
+ const { waitUntilExit } = render();
+ await waitUntilExit();
+ }
+
+ program.parse();
+}
+
+main();
diff --git a/atmn/src/lib/precheck.ts b/atmn/src/lib/precheck.ts
new file mode 100644
index 0000000..26ff270
--- /dev/null
+++ b/atmn/src/lib/precheck.ts
@@ -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;
+ }
+}
diff --git a/atmn/src/views/react/install/InstallPrompt.tsx b/atmn/src/views/react/install/InstallPrompt.tsx
new file mode 100644
index 0000000..b772ad2
--- /dev/null
+++ b/atmn/src/views/react/install/InstallPrompt.tsx
@@ -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("choosing");
+ const [error, setError] = useState(null);
+
+ const items: SelectMenuItem[] = [
+ { label: "npm", value: "npm" },
+ { label: "pnpm", value: "pnpm" },
+ { label: "bun", value: "bun" },
+ ];
+
+ const handleSelect = useCallback(
+ (item: SelectMenuItem) => {
+ setState("installing");
+
+ const commands: Record = {
+ 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) {
+ setError(
+ err instanceof Error ? err.message : "Installation failed",
+ );
+ }
+ },
+ [exit],
+ );
+
+ if (error) {
+ return (
+
+
+
+ Install manually: npm install atmn / pnpm add atmn / bun add atmn
+
+
+ );
+ }
+
+ return (
+
+
+
+ ! The{" "}
+
+ atmn
+ {" "}
+ package is not in your dependencies.
+
+
+
+ It's needed so autumn.config.ts can import types and builders.
+
+
+ {state === "choosing" && (
+
+ Install with:
+
+
+
+
+ )}
+
+ {state === "installing" && (
+
+
+
+ )}
+
+ {state === "success" && (
+
+
+
+ )}
+
+ );
+}