Compare commits

...

1 Commits

Author SHA1 Message Date
Guilhem Lemouel
31acfbd952 feat(cli): enhance app new with Claude Desktop integration and better defaults
- Add .claude/launch.json to generated app scaffold for Claude Code preview support
- Add "Open in Claude Desktop?" prompt that creates a CLI session and opens it
  in Claude Desktop Code mode via the claude://resume deep link
- Improve default CSS template with body background, system fonts, and padding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:09:21 +01:00

View File

@@ -12,6 +12,7 @@ import { resolveWorkspace } from "../../core/context.ts";
import { requireLogin } from "../../core/auth.ts";
import * as wmill from "../../../gen/services.gen.ts";
import path from "node:path";
import { execSync, exec } from "node:child_process";
import {
buildFolderPath,
loadNonDottedPathsSetting,
@@ -99,7 +100,18 @@ import "./index.css";
createApp(App).mount('#root')`;
const indexCss = `.myclass {
const indexCss = `body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #1a1a1a;
}
#root {
padding: 24px;
}
.myclass {
border: 1px solid gray;
padding: 2px;
}`;
@@ -498,6 +510,26 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName};
await mkdir(appDir, { recursive: true });
await mkdir(path.join(appDir, "backend"), { recursive: true });
await mkdir(path.join(appDir, "sql_to_apply"), { recursive: true });
await mkdir(path.join(appDir, ".claude"), { recursive: true });
// Create .claude/launch.json for Claude Code preview
const launchJson = {
version: "0.0.1",
configurations: [
{
name: "windmill",
runtimeExecutable: "bash",
runtimeArgs: ["-c", "wmill app dev --no-open --port ${PORT:-4000}"],
port: 4000,
autoPort: true,
},
],
};
await writeFile(
path.join(appDir, ".claude", "launch.json"),
JSON.stringify(launchJson, null, 2) + "\n",
"utf-8"
);
// Create raw_app.yaml with data configuration
const rawAppConfig: Record<string, unknown> = {
@@ -610,6 +642,8 @@ This folder is for SQL migration files that will be applied to datatables during
log.info("");
log.info(colors.gray("Directory structure:"));
log.info(colors.gray(` ${folderName}/`));
log.info(colors.gray(" ├── .claude/"));
log.info(colors.gray(" │ └── launch.json"));
log.info(colors.gray(" ├── AGENTS.md ← Read this first!"));
log.info(colors.gray(" ├── raw_app.yaml"));
log.info(colors.gray(" ├── DATATABLES.md"));
@@ -662,6 +696,67 @@ This folder is for SQL migration files that will be applied to datatables during
}
log.info("");
log.info(colors.gray(" 4. wmill sync push (to deploy when ready)"));
// Offer to open in Claude Desktop
let hasClaudeDesktop = false;
try {
execSync("ls /Applications/Claude.app", { stdio: "ignore" });
hasClaudeDesktop = true;
} catch {
// Claude Desktop not installed
}
if (hasClaudeDesktop) {
log.info("");
const openInDesktop = await Confirm.prompt({
message: "Open in Claude Desktop?",
default: true,
});
if (openInDesktop) {
try {
const absAppDir = path.resolve(appDir);
const sessionId = crypto.randomUUID();
// Create a persisted CLI session with welcome message (async to allow spinner)
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let i = 0;
const spinner = setInterval(() => {
process.stdout.write(`\r${colors.gray(`${frames[i++ % frames.length]} Creating Claude session...`)}`);
}, 80);
await new Promise<void>((resolve, reject) => {
exec(
`claude --session-id "${sessionId}" -p "Say: Your app is ready, click on preview to test it!"`,
{ cwd: absAppDir },
(error) => (error ? reject(error) : resolve())
);
});
clearInterval(spinner);
process.stdout.write("\r" + " ".repeat(40) + "\r");
// Import the session into Claude Desktop Code mode
const deepLink = `claude://resume?session=${sessionId}&cwd=${encodeURIComponent(absAppDir)}`;
exec(`open ${JSON.stringify(deepLink)}`);
log.info(colors.bold.green("Opened in Claude Desktop!"));
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : String(error);
log.warn(
colors.yellow(
`Could not open in Claude Desktop: ${errorMessage}`
)
);
log.info(
colors.gray(
"You can manually run: cd " + folderName + " && claude"
)
);
}
}
}
}
const command = new Command()