awel 0.1.2 → 0.2.1
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/README.md +22 -5
- package/README.zh-CN.md +22 -5
- package/dist/cli/agent.d.ts +2 -1
- package/dist/cli/agent.js +3 -2
- package/dist/cli/awel-config.d.ts +4 -0
- package/dist/cli/awel-config.js +8 -0
- package/dist/cli/create.d.ts +1 -0
- package/dist/cli/create.js +136 -0
- package/dist/cli/index.js +58 -4
- package/dist/cli/onboarding.js +49 -24
- package/dist/cli/providers/types.d.ts +2 -0
- package/dist/cli/providers/vercel.js +51 -2
- package/dist/cli/proxy.d.ts +1 -1
- package/dist/cli/proxy.js +40 -7
- package/dist/cli/server.d.ts +2 -1
- package/dist/cli/server.js +14 -3
- package/dist/cli/templates/blank/AGENT.md +24 -0
- package/dist/cli/templates/blank/src/app/page.tsx +10 -0
- package/dist/cli/templates/blog/AGENT.md +26 -0
- package/dist/cli/templates/blog/src/app/blog/[slug]/page.tsx +38 -0
- package/dist/cli/templates/blog/src/app/page.tsx +35 -0
- package/dist/cli/templates/blog/src/lib/posts.ts +42 -0
- package/dist/cli/templates/dashboard/AGENT.md +26 -0
- package/dist/cli/templates/dashboard/src/app/layout.tsx +28 -0
- package/dist/cli/templates/dashboard/src/app/page.tsx +30 -0
- package/dist/cli/templates/dashboard/src/components/Sidebar.tsx +31 -0
- package/dist/cli/templates/dashboard/src/components/StatsCard.tsx +24 -0
- package/dist/cli/templates/landing/AGENT.md +26 -0
- package/dist/cli/templates/landing/src/app/page.tsx +13 -0
- package/dist/cli/templates/landing/src/components/CTA.tsx +23 -0
- package/dist/cli/templates/landing/src/components/Features.tsx +46 -0
- package/dist/cli/templates/landing/src/components/Hero.tsx +26 -0
- package/dist/cli/templates/portfolio/AGENT.md +24 -0
- package/dist/cli/templates/portfolio/src/app/page.tsx +50 -0
- package/dist/cli/templates/portfolio/src/components/ProjectCard.tsx +30 -0
- package/dist/cli/templates/saas/AGENT.md +28 -0
- package/dist/cli/templates/saas/src/app/dashboard/page.tsx +10 -0
- package/dist/cli/templates/saas/src/app/page.tsx +37 -0
- package/dist/cli/templates/saas/src/app/pricing/page.tsx +53 -0
- package/dist/cli/templates/saas/src/components/Navbar.tsx +25 -0
- package/dist/cli/templates/saas/src/components/PricingCard.tsx +46 -0
- package/dist/cli/templates/templates/blank/AGENT.md +24 -0
- package/dist/cli/templates/templates/blank/src/app/page.tsx +10 -0
- package/dist/cli/templates/templates/blog/AGENT.md +26 -0
- package/dist/cli/templates/templates/blog/src/app/blog/[slug]/page.tsx +38 -0
- package/dist/cli/templates/templates/blog/src/app/page.tsx +35 -0
- package/dist/cli/templates/templates/blog/src/lib/posts.ts +42 -0
- package/dist/cli/templates/templates/dashboard/AGENT.md +26 -0
- package/dist/cli/templates/templates/dashboard/src/app/layout.tsx +28 -0
- package/dist/cli/templates/templates/dashboard/src/app/page.tsx +30 -0
- package/dist/cli/templates/templates/dashboard/src/components/Sidebar.tsx +31 -0
- package/dist/cli/templates/templates/dashboard/src/components/StatsCard.tsx +24 -0
- package/dist/cli/templates/templates/landing/AGENT.md +26 -0
- package/dist/cli/templates/templates/landing/src/app/page.tsx +13 -0
- package/dist/cli/templates/templates/landing/src/components/CTA.tsx +23 -0
- package/dist/cli/templates/templates/landing/src/components/Features.tsx +46 -0
- package/dist/cli/templates/templates/landing/src/components/Hero.tsx +26 -0
- package/dist/cli/templates/templates/portfolio/AGENT.md +24 -0
- package/dist/cli/templates/templates/portfolio/src/app/page.tsx +50 -0
- package/dist/cli/templates/templates/portfolio/src/components/ProjectCard.tsx +30 -0
- package/dist/cli/templates/templates/saas/AGENT.md +28 -0
- package/dist/cli/templates/templates/saas/src/app/dashboard/page.tsx +10 -0
- package/dist/cli/templates/templates/saas/src/app/page.tsx +37 -0
- package/dist/cli/templates/templates/saas/src/app/pricing/page.tsx +53 -0
- package/dist/cli/templates/templates/saas/src/components/Navbar.tsx +25 -0
- package/dist/cli/templates/templates/saas/src/components/PricingCard.tsx +46 -0
- package/dist/dashboard/assets/index-3djfaQxV.js +323 -0
- package/dist/dashboard/assets/index-BQNq3kAP.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/package.json +2 -1
- package/dist/dashboard/assets/index-B374_cjZ.css +0 -1
- package/dist/dashboard/assets/index-BJ6wUfxa.js +0 -318
package/README.md
CHANGED
|
@@ -8,16 +8,24 @@ AI-powered development overlay for Next.js. Awel runs a proxy in front of your d
|
|
|
8
8
|
|
|
9
9
|
## Quick Start
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
# Skip if you're already in a Next.js app
|
|
13
|
-
npx create-next-app@latest my-app && cd my-app
|
|
11
|
+
### Create a new project
|
|
14
12
|
|
|
13
|
+
```bash
|
|
15
14
|
# Set up at least one AI provider (pick one):
|
|
16
15
|
export ANTHROPIC_API_KEY="sk-ant-..." # Anthropic API
|
|
17
16
|
export OPENAI_API_KEY="sk-..." # OpenAI
|
|
18
17
|
export GOOGLE_GENERATIVE_AI_API_KEY="..." # Google AI
|
|
19
18
|
# Or install the Claude CLI: https://docs.anthropic.com/en/docs/claude-code
|
|
20
19
|
|
|
20
|
+
npx awel create
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This scaffolds a new Next.js project and marks it for creation mode. Follow the instructions to `cd` into the project and run `npx awel dev`. You'll see a full-page creation UI where you describe what you want to build — the AI agent generates the entire app for you, then transitions to the normal Awel overlay.
|
|
24
|
+
|
|
25
|
+
### Use with an existing project
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd my-existing-next-app
|
|
21
29
|
npx awel dev
|
|
22
30
|
```
|
|
23
31
|
|
|
@@ -25,10 +33,11 @@ Awel needs at least one configured provider to function. See [Supported Models](
|
|
|
25
33
|
|
|
26
34
|
This starts Awel on port 3001 and proxies your Next.js dev server on port 3000. Open `http://localhost:3001` to see your app with the Awel overlay.
|
|
27
35
|
|
|
28
|
-
###
|
|
36
|
+
### Commands
|
|
29
37
|
|
|
30
38
|
```
|
|
31
|
-
awel
|
|
39
|
+
awel create Create a new Next.js project with Awel
|
|
40
|
+
awel dev [options] Start the development server with Awel overlay
|
|
32
41
|
|
|
33
42
|
-p, --port <port> Target app port (default: 3000)
|
|
34
43
|
-v, --verbose Print LLM stream events to stderr
|
|
@@ -65,6 +74,13 @@ Awel uses the [Vercel AI SDK](https://sdk.vercel.ai) and supports multiple provi
|
|
|
65
74
|
|
|
66
75
|
Switch models at any time from the dropdown in the dashboard header.
|
|
67
76
|
|
|
77
|
+
### Additional Environment Variables
|
|
78
|
+
|
|
79
|
+
| Variable | Description |
|
|
80
|
+
|----------|-------------|
|
|
81
|
+
| `OPENAI_BASE_URL` | Custom base URL for the OpenAI provider (e.g. a proxy or compatible API). Defaults to `https://api.openai.com/v1`. |
|
|
82
|
+
| `AWEL_MAX_OUTPUT_TOKENS` | Maximum number of tokens the model can generate per response. Applies to all providers. |
|
|
83
|
+
|
|
68
84
|
## Agent Tools
|
|
69
85
|
|
|
70
86
|
The AI agent has access to:
|
|
@@ -94,6 +110,7 @@ The AI agent has access to:
|
|
|
94
110
|
- **Diff review** — review a summary of all file changes before accepting
|
|
95
111
|
- **Dark mode** — follows your system preference
|
|
96
112
|
- **i18n** — English and Chinese
|
|
113
|
+
- **Creation mode** — `awel create` scaffolds a new project and launches a full-page AI chat where you describe your app and the agent builds it from scratch
|
|
97
114
|
|
|
98
115
|
## Development
|
|
99
116
|
|
package/README.zh-CN.md
CHANGED
|
@@ -8,16 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
## 快速开始
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
# 如果你已经在一个 Next.js 项目中,可以跳过这一步
|
|
13
|
-
npx create-next-app@latest my-app && cd my-app
|
|
11
|
+
### 创建新项目
|
|
14
12
|
|
|
13
|
+
```bash
|
|
15
14
|
# 至少配置一个 AI 服务商(任选其一):
|
|
16
15
|
export ANTHROPIC_API_KEY="sk-ant-..." # Anthropic API
|
|
17
16
|
export OPENAI_API_KEY="sk-..." # OpenAI
|
|
18
17
|
export GOOGLE_GENERATIVE_AI_API_KEY="..." # Google AI
|
|
19
18
|
# 或安装 Claude CLI:https://docs.anthropic.com/en/docs/claude-code
|
|
20
19
|
|
|
20
|
+
npx awel create
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
此命令会创建一个新的 Next.js 项目并标记为创建模式。按照提示 `cd` 进入项目目录并运行 `npx awel dev`。你会看到一个全屏创建界面,描述你想构建的应用——AI 智能体会为你生成整个应用,完成后自动切换到正常的 Awel 浮层模式。
|
|
24
|
+
|
|
25
|
+
### 在已有项目中使用
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd my-existing-next-app
|
|
21
29
|
npx awel dev
|
|
22
30
|
```
|
|
23
31
|
|
|
@@ -25,10 +33,11 @@ Awel 需要至少一个已配置的服务商才能运行。完整列表见[支
|
|
|
25
33
|
|
|
26
34
|
Awel 会在端口 3001 启动,并代理运行在端口 3000 的 Next.js 开发服务器。打开 `http://localhost:3001` 即可看到带有 Awel 浮层的应用。
|
|
27
35
|
|
|
28
|
-
###
|
|
36
|
+
### 命令
|
|
29
37
|
|
|
30
38
|
```
|
|
31
|
-
awel
|
|
39
|
+
awel create 创建一个带有 Awel 的新 Next.js 项目
|
|
40
|
+
awel dev [options] 启动带有 Awel 浮层的开发服务器
|
|
32
41
|
|
|
33
42
|
-p, --port <port> 目标应用端口(默认:3000)
|
|
34
43
|
-v, --verbose 将 LLM 流式事件输出到 stderr
|
|
@@ -65,6 +74,13 @@ Awel 使用 [Vercel AI SDK](https://sdk.vercel.ai),支持多个服务商。设
|
|
|
65
74
|
|
|
66
75
|
可随时在面板顶部的下拉菜单中切换模型。
|
|
67
76
|
|
|
77
|
+
### 额外环境变量
|
|
78
|
+
|
|
79
|
+
| 变量 | 说明 |
|
|
80
|
+
|------|------|
|
|
81
|
+
| `OPENAI_BASE_URL` | OpenAI 服务商的自定义 Base URL(例如代理或兼容 API)。默认为 `https://api.openai.com/v1`。 |
|
|
82
|
+
| `AWEL_MAX_OUTPUT_TOKENS` | 模型单次响应的最大生成 token 数。对所有服务商生效。 |
|
|
83
|
+
|
|
68
84
|
## 智能体工具
|
|
69
85
|
|
|
70
86
|
AI 智能体可使用以下工具:
|
|
@@ -94,6 +110,7 @@ AI 智能体可使用以下工具:
|
|
|
94
110
|
- **Diff 审查** — 在接受变更前查看所有文件修改的摘要
|
|
95
111
|
- **深色模式** — 跟随系统偏好
|
|
96
112
|
- **国际化** — 支持英文和中文
|
|
113
|
+
- **创建模式** — `awel create` 创建新项目并启动全屏 AI 对话界面,描述你的应用,智能体从零开始为你构建
|
|
97
114
|
|
|
98
115
|
## 开发
|
|
99
116
|
|
package/dist/cli/agent.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ import { Hono } from 'hono';
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates the agent API routes with SSE streaming.
|
|
4
4
|
* @param projectCwd - The user's project directory (where they ran `awel dev`)
|
|
5
|
+
* @param isFresh - Getter for whether the project is in creation mode
|
|
5
6
|
*/
|
|
6
|
-
export declare function createAgentRoute(projectCwd: string, targetPort: number): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
7
|
+
export declare function createAgentRoute(projectCwd: string, targetPort: number, isFresh?: () => boolean): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
package/dist/cli/agent.js
CHANGED
|
@@ -95,8 +95,9 @@ function setSSEHeaders(c) {
|
|
|
95
95
|
/**
|
|
96
96
|
* Creates the agent API routes with SSE streaming.
|
|
97
97
|
* @param projectCwd - The user's project directory (where they ran `awel dev`)
|
|
98
|
+
* @param isFresh - Getter for whether the project is in creation mode
|
|
98
99
|
*/
|
|
99
|
-
export function createAgentRoute(projectCwd, targetPort) {
|
|
100
|
+
export function createAgentRoute(projectCwd, targetPort, isFresh) {
|
|
100
101
|
const agent = new Hono();
|
|
101
102
|
// ─── Model Catalog ───────────────────────────────────────
|
|
102
103
|
agent.get('/api/models', (c) => {
|
|
@@ -158,7 +159,7 @@ export function createAgentRoute(projectCwd, targetPort) {
|
|
|
158
159
|
// Appending eagerly would leave orphan user messages in the session when
|
|
159
160
|
// the stream is aborted or paused for user input, causing consecutive
|
|
160
161
|
// user messages that trigger API 400 errors (especially with Anthropic).
|
|
161
|
-
provider.streamResponse(adapter, messages, { projectCwd, targetPort, signal })
|
|
162
|
+
provider.streamResponse(adapter, messages, { projectCwd, targetPort, signal, creationMode: isFresh?.() })
|
|
162
163
|
.then((responseMessages) => {
|
|
163
164
|
if (responseMessages.length > 0) {
|
|
164
165
|
appendUserMessage(userContent);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface AwelConfig {
|
|
2
2
|
babelPlugin?: boolean;
|
|
3
3
|
onboarded?: boolean;
|
|
4
|
+
fresh?: boolean;
|
|
5
|
+
createdAt?: string;
|
|
4
6
|
}
|
|
5
7
|
export declare function readAwelConfig(projectCwd: string): AwelConfig;
|
|
6
8
|
export declare function writeAwelConfig(projectCwd: string, config: AwelConfig): void;
|
|
9
|
+
export declare function isProjectFresh(projectCwd: string): boolean;
|
|
10
|
+
export declare function markProjectReady(projectCwd: string): void;
|
package/dist/cli/awel-config.js
CHANGED
|
@@ -18,3 +18,11 @@ export function writeAwelConfig(projectCwd, config) {
|
|
|
18
18
|
}
|
|
19
19
|
writeFileSync(join(dir, 'config.json'), JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
20
20
|
}
|
|
21
|
+
export function isProjectFresh(projectCwd) {
|
|
22
|
+
return readAwelConfig(projectCwd).fresh === true;
|
|
23
|
+
}
|
|
24
|
+
export function markProjectReady(projectCwd) {
|
|
25
|
+
const config = readAwelConfig(projectCwd);
|
|
26
|
+
config.fresh = false;
|
|
27
|
+
writeAwelConfig(projectCwd, config);
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCreate(projectNameArg?: string): Promise<void>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { existsSync, cpSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, dirname, resolve } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import * as p from '@clack/prompts';
|
|
6
|
+
import { writeAwelConfig } from './awel-config.js';
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const TEMPLATES = [
|
|
9
|
+
{ value: 'blank', label: 'Blank', hint: 'Empty Next.js app with AGENT.md' },
|
|
10
|
+
{ value: 'landing', label: 'Landing Page', hint: 'Hero, features, and CTA sections' },
|
|
11
|
+
{ value: 'dashboard', label: 'Dashboard', hint: 'Sidebar layout with stats cards' },
|
|
12
|
+
{ value: 'blog', label: 'Blog', hint: 'Blog listing with dynamic post pages' },
|
|
13
|
+
{ value: 'portfolio', label: 'Portfolio', hint: 'Project showcase with cards' },
|
|
14
|
+
{ value: 'saas', label: 'SaaS', hint: 'Landing, pricing, and dashboard pages' },
|
|
15
|
+
];
|
|
16
|
+
function detectPackageManager() {
|
|
17
|
+
const agent = process.env.npm_config_user_agent;
|
|
18
|
+
if (agent) {
|
|
19
|
+
if (agent.startsWith('pnpm'))
|
|
20
|
+
return 'pnpm';
|
|
21
|
+
if (agent.startsWith('yarn'))
|
|
22
|
+
return 'yarn';
|
|
23
|
+
if (agent.startsWith('bun'))
|
|
24
|
+
return 'bun';
|
|
25
|
+
}
|
|
26
|
+
return 'npm';
|
|
27
|
+
}
|
|
28
|
+
function getTemplatesDir() {
|
|
29
|
+
return join(__dirname, 'templates');
|
|
30
|
+
}
|
|
31
|
+
export async function runCreate(projectNameArg) {
|
|
32
|
+
p.intro('Create a new Awel project');
|
|
33
|
+
// 1. Project name
|
|
34
|
+
let projectName;
|
|
35
|
+
if (projectNameArg) {
|
|
36
|
+
projectName = projectNameArg;
|
|
37
|
+
p.log.info(`Project name: ${projectName}`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const result = await p.text({
|
|
41
|
+
message: 'What is your project name?',
|
|
42
|
+
placeholder: 'my-app',
|
|
43
|
+
validate(value) {
|
|
44
|
+
if (!value || !value.trim())
|
|
45
|
+
return 'Project name is required';
|
|
46
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value.trim()))
|
|
47
|
+
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
if (p.isCancel(result)) {
|
|
51
|
+
p.cancel('Operation cancelled.');
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
projectName = result;
|
|
55
|
+
}
|
|
56
|
+
const projectDir = resolve(process.cwd(), projectName);
|
|
57
|
+
// Check if directory already exists
|
|
58
|
+
if (existsSync(projectDir)) {
|
|
59
|
+
p.cancel(`Directory "${projectName}" already exists.`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// 2. Template selection
|
|
63
|
+
const template = await p.select({
|
|
64
|
+
message: 'Which template would you like to use?',
|
|
65
|
+
options: TEMPLATES,
|
|
66
|
+
});
|
|
67
|
+
if (p.isCancel(template)) {
|
|
68
|
+
p.cancel('Operation cancelled.');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
// 3. Package manager
|
|
72
|
+
const detected = detectPackageManager();
|
|
73
|
+
const packageManager = await p.select({
|
|
74
|
+
message: 'Which package manager do you want to use?',
|
|
75
|
+
options: [
|
|
76
|
+
{ value: 'npm', label: 'npm', hint: detected === 'npm' ? 'detected' : undefined },
|
|
77
|
+
{ value: 'pnpm', label: 'pnpm', hint: detected === 'pnpm' ? 'detected' : undefined },
|
|
78
|
+
{ value: 'yarn', label: 'yarn', hint: detected === 'yarn' ? 'detected' : undefined },
|
|
79
|
+
{ value: 'bun', label: 'bun', hint: detected === 'bun' ? 'detected' : undefined },
|
|
80
|
+
],
|
|
81
|
+
initialValue: detected,
|
|
82
|
+
});
|
|
83
|
+
if (p.isCancel(packageManager)) {
|
|
84
|
+
p.cancel('Operation cancelled.');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
const pm = packageManager;
|
|
88
|
+
const templateName = template;
|
|
89
|
+
// 4. Scaffold with create-next-app
|
|
90
|
+
const s = p.spinner();
|
|
91
|
+
s.start('Creating Next.js app...');
|
|
92
|
+
const useFlag = pm === 'npm' ? '--use-npm' : pm === 'pnpm' ? '--use-pnpm' : pm === 'yarn' ? '--use-yarn' : '--use-bun';
|
|
93
|
+
const cnaArgs = [
|
|
94
|
+
'create-next-app@latest',
|
|
95
|
+
projectDir,
|
|
96
|
+
'--ts',
|
|
97
|
+
'--tailwind',
|
|
98
|
+
'--eslint',
|
|
99
|
+
'--app',
|
|
100
|
+
'--src-dir',
|
|
101
|
+
'--import-alias', '@/*',
|
|
102
|
+
useFlag,
|
|
103
|
+
];
|
|
104
|
+
try {
|
|
105
|
+
execSync(`npx ${cnaArgs.map(a => `"${a}"`).join(' ')}`, {
|
|
106
|
+
stdio: 'inherit',
|
|
107
|
+
timeout: 120000,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
s.stop('Failed to create Next.js app.');
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
p.log.error(message);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
s.stop('Next.js app created.');
|
|
117
|
+
// 5. Copy template overlay files
|
|
118
|
+
s.start('Applying template...');
|
|
119
|
+
const templateDir = join(getTemplatesDir(), templateName);
|
|
120
|
+
if (existsSync(templateDir) && readdirSync(templateDir).length > 0) {
|
|
121
|
+
cpSync(templateDir, projectDir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
s.stop('Template applied.');
|
|
124
|
+
// 6. Write Awel config
|
|
125
|
+
writeAwelConfig(projectDir, {
|
|
126
|
+
template: templateName,
|
|
127
|
+
packageManager: pm,
|
|
128
|
+
createdAt: new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
// 7. Done
|
|
131
|
+
p.log.success(`Project "${projectName}" created with the "${templateName}" template.`);
|
|
132
|
+
p.log.info('Next steps:');
|
|
133
|
+
p.log.step(` cd ${projectName}`);
|
|
134
|
+
p.log.step(' npx awel dev');
|
|
135
|
+
p.outro('Happy building!');
|
|
136
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { ensureBabelPlugin } from './babel-setup.js';
|
|
|
6
6
|
import { ensureProvider } from './onboarding.js';
|
|
7
7
|
import { awel } from './logger.js';
|
|
8
8
|
import { spawnDevServer } from './subprocess.js';
|
|
9
|
+
import { writeAwelConfig, isProjectFresh } from './awel-config.js';
|
|
10
|
+
import { resolve } from 'path';
|
|
9
11
|
program
|
|
10
12
|
.name('awel')
|
|
11
13
|
.description('AI-powered development overlay for Next.js')
|
|
@@ -19,18 +21,70 @@ program
|
|
|
19
21
|
const targetPort = parseInt(options.port, 10);
|
|
20
22
|
if (options.verbose)
|
|
21
23
|
setVerbose(true);
|
|
24
|
+
const fresh = isProjectFresh(process.cwd());
|
|
22
25
|
await ensureProvider(process.cwd());
|
|
23
|
-
|
|
26
|
+
if (!fresh)
|
|
27
|
+
await ensureBabelPlugin(process.cwd());
|
|
24
28
|
awel.log('🌟 Starting Awel...');
|
|
29
|
+
if (fresh)
|
|
30
|
+
awel.log(' Mode: Creation (new project)');
|
|
25
31
|
awel.log(` Target app port: ${targetPort}`);
|
|
26
32
|
awel.log(` Awel control server: http://localhost:${AWEL_PORT}`);
|
|
27
33
|
awel.log('');
|
|
28
34
|
// Start the Awel control server (proxy + dashboard)
|
|
29
|
-
await startServer({ awelPort: AWEL_PORT, targetPort, projectCwd: process.cwd() });
|
|
35
|
+
await startServer({ awelPort: AWEL_PORT, targetPort, projectCwd: process.cwd(), fresh });
|
|
30
36
|
// Start the user's Next.js app via subprocess manager (handles auto-restart)
|
|
31
37
|
await spawnDevServer({ port: targetPort, cwd: process.cwd() });
|
|
32
38
|
awel.log('');
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
if (fresh) {
|
|
40
|
+
awel.log(`✨ Awel is ready! Open http://localhost:${AWEL_PORT}`);
|
|
41
|
+
awel.log(' Describe what you want to build and Awel will create it for you.');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
awel.log(`✨ Awel is ready! Open http://localhost:${AWEL_PORT}`);
|
|
45
|
+
awel.log(' Look for the floating button in the bottom-right corner.');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
program
|
|
49
|
+
.command('create')
|
|
50
|
+
.description('Create a new Next.js project with Awel')
|
|
51
|
+
.action(async () => {
|
|
52
|
+
const p = await import('@clack/prompts');
|
|
53
|
+
const { execa } = await import('execa');
|
|
54
|
+
p.intro('Create a new project with Awel');
|
|
55
|
+
const name = await p.text({
|
|
56
|
+
message: 'Project name',
|
|
57
|
+
placeholder: 'my-app',
|
|
58
|
+
validate: (value) => {
|
|
59
|
+
if (!value)
|
|
60
|
+
return 'Project name is required';
|
|
61
|
+
if (!/^[a-z0-9._-]+$/i.test(value))
|
|
62
|
+
return 'Project name must only contain letters, numbers, dashes, dots, and underscores';
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (p.isCancel(name)) {
|
|
66
|
+
p.cancel('Cancelled');
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
const s = p.spinner();
|
|
70
|
+
s.start('Creating Next.js project...');
|
|
71
|
+
try {
|
|
72
|
+
await execa('npx', [
|
|
73
|
+
'create-next-app@latest', name,
|
|
74
|
+
'--yes',
|
|
75
|
+
'--typescript', '--tailwind', '--eslint',
|
|
76
|
+
'--app', '--use-npm',
|
|
77
|
+
], { cwd: process.cwd(), stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
s.stop('Failed to create project');
|
|
81
|
+
const stderr = err.stderr || err.message || String(err);
|
|
82
|
+
p.log.error(stderr);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
s.stop('Project created');
|
|
86
|
+
const projectDir = resolve(process.cwd(), name);
|
|
87
|
+
writeAwelConfig(projectDir, { fresh: true, createdAt: new Date().toISOString() });
|
|
88
|
+
p.outro(`Done! Now run:\n\n cd ${name}\n npx awel dev`);
|
|
35
89
|
});
|
|
36
90
|
program.parse();
|
package/dist/cli/onboarding.js
CHANGED
|
@@ -1,25 +1,50 @@
|
|
|
1
|
-
import { readAwelConfig } from './awel-config.js';
|
|
1
|
+
import { readAwelConfig, writeAwelConfig } from './awel-config.js';
|
|
2
2
|
import { getAvailableProviders, PROVIDER_ENV_KEYS, PROVIDER_LABELS } from './providers/registry.js';
|
|
3
3
|
import { awel } from './logger.js';
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
async function promptProviderSetup() {
|
|
5
|
+
const p = await import('@clack/prompts');
|
|
6
|
+
p.log.warn('No LLM providers are configured.');
|
|
7
|
+
p.log.message('Awel needs at least one AI provider to function.\n');
|
|
8
|
+
const provider = await p.select({
|
|
9
|
+
message: 'Which provider would you like to set up?',
|
|
10
|
+
options: [
|
|
11
|
+
{ value: 'claude-code', label: 'Claude Code', hint: 'Uses Claude CLI binary' },
|
|
12
|
+
{ value: 'anthropic', label: 'Anthropic API', hint: 'ANTHROPIC_API_KEY' },
|
|
13
|
+
{ value: 'openai', label: 'OpenAI', hint: 'OPENAI_API_KEY' },
|
|
14
|
+
{ value: 'google-ai', label: 'Google AI', hint: 'GOOGLE_GENERATIVE_AI_API_KEY' },
|
|
15
|
+
{ value: 'vercel-gateway', label: 'Vercel AI Gateway', hint: 'AI_GATEWAY_API_KEY' },
|
|
16
|
+
{ value: 'qwen', label: 'Qwen', hint: 'DASHSCOPE_API_KEY' },
|
|
17
|
+
{ value: 'minimax', label: 'MiniMax', hint: 'MINIMAX_API_KEY' },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
if (p.isCancel(provider)) {
|
|
21
|
+
p.cancel('Cancelled');
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
if (provider === 'claude-code') {
|
|
25
|
+
p.note('Install the Claude CLI:\n\n' +
|
|
26
|
+
' npm install -g @anthropic-ai/claude-code\n\n' +
|
|
27
|
+
'Then authenticate:\n\n' +
|
|
28
|
+
' claude login', 'Claude Code Setup');
|
|
29
|
+
}
|
|
30
|
+
else if (provider === 'openai') {
|
|
31
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
32
|
+
p.note(`Export your API key:\n\n` +
|
|
33
|
+
` export ${envKey}="your-api-key"\n\n` +
|
|
34
|
+
`Or add it to your .env file:\n\n` +
|
|
35
|
+
` ${envKey}=your-api-key\n\n` +
|
|
36
|
+
`To use a custom base URL (e.g. a proxy or compatible API):\n\n` +
|
|
37
|
+
` export OPENAI_BASE_URL="https://your-proxy.com/v1"`, 'OpenAI Setup');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
12
41
|
const label = PROVIDER_LABELS[provider] ?? provider;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
42
|
+
p.note(`Export your API key:\n\n` +
|
|
43
|
+
` export ${envKey}="your-api-key"\n\n` +
|
|
44
|
+
`Or add it to your .env file:\n\n` +
|
|
45
|
+
` ${envKey}=your-api-key`, `${label} Setup`);
|
|
16
46
|
}
|
|
17
|
-
|
|
18
|
-
const label = PROVIDER_LABELS['claude-code'] ?? 'Claude Code';
|
|
19
|
-
awel.log(` ${label}`);
|
|
20
|
-
awel.log(' Install the Claude CLI: https://docs.anthropic.com/en/docs/claude-code');
|
|
21
|
-
awel.log('');
|
|
22
|
-
awel.log('Then run `awel dev` again.');
|
|
47
|
+
p.log.message('Then run `awel dev` again.');
|
|
23
48
|
}
|
|
24
49
|
export async function ensureProvider(projectCwd) {
|
|
25
50
|
const config = readAwelConfig(projectCwd);
|
|
@@ -37,21 +62,21 @@ export async function ensureProvider(projectCwd) {
|
|
|
37
62
|
awel.log(` \u25CF ${p.label}`);
|
|
38
63
|
}
|
|
39
64
|
awel.log('');
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
writeAwelConfig(projectCwd, { ...config, onboarded: true });
|
|
66
|
+
return;
|
|
42
67
|
}
|
|
43
68
|
if (isFirstRun && available.length === 0) {
|
|
44
|
-
// First run with NO providers — show welcome +
|
|
69
|
+
// First run with NO providers — show welcome + interactive setup, exit
|
|
45
70
|
awel.log('');
|
|
46
71
|
awel.log('Welcome to Awel!');
|
|
47
72
|
awel.log('AI-powered development overlay for Next.js');
|
|
48
73
|
awel.log('');
|
|
49
|
-
|
|
74
|
+
await promptProviderSetup();
|
|
50
75
|
process.exit(1);
|
|
51
76
|
}
|
|
52
77
|
if (!isFirstRun && available.length === 0) {
|
|
53
|
-
// Subsequent run with NO providers —
|
|
54
|
-
|
|
78
|
+
// Subsequent run with NO providers — interactive setup, exit
|
|
79
|
+
await promptProviderSetup();
|
|
55
80
|
process.exit(1);
|
|
56
81
|
}
|
|
57
82
|
// Subsequent run with providers available — silent pass-through
|
|
@@ -6,6 +6,8 @@ export interface ProviderConfig {
|
|
|
6
6
|
targetPort: number;
|
|
7
7
|
/** When aborted, the provider should stop the LLM stream and exit early. */
|
|
8
8
|
signal?: AbortSignal;
|
|
9
|
+
/** When true, the agent uses a creation-mode system prompt for building new apps. */
|
|
10
|
+
creationMode?: boolean;
|
|
9
11
|
}
|
|
10
12
|
export interface StreamProvider {
|
|
11
13
|
streamResponse(stream: SSEStreamingApi, messages: ModelMessage[], config: ProviderConfig): Promise<ResponseMessage[]>;
|
|
@@ -67,6 +67,48 @@ Inspector Context:
|
|
|
67
67
|
|
|
68
68
|
Language:
|
|
69
69
|
- IMPORTANT: Always respond in the same language the user writes in. If the user writes in Chinese, respond in Chinese. If the user writes in English, respond in English. Match the user's language throughout the conversation.`;
|
|
70
|
+
const CREATION_SYSTEM_PROMPT = `You are Awel in creation mode. The user has just created a fresh Next.js project and wants to build something new.
|
|
71
|
+
|
|
72
|
+
You have access to the same tools as in normal mode:
|
|
73
|
+
- Read: Read file contents
|
|
74
|
+
- Write: Create or overwrite files (creates parent directories automatically)
|
|
75
|
+
- Edit: Find-and-replace edits in files
|
|
76
|
+
- Bash: Execute shell commands
|
|
77
|
+
- Glob: Find files by glob pattern
|
|
78
|
+
- Ls: List directory contents
|
|
79
|
+
- AskUser: Ask the user clarifying questions with selectable options
|
|
80
|
+
- Grep: Search file contents for a regex pattern
|
|
81
|
+
- MultiEdit: Apply multiple find-and-replace edits to a single file in one call
|
|
82
|
+
- WebSearch: Search the web for real-time information
|
|
83
|
+
- WebFetch: Fetch content from a URL
|
|
84
|
+
- CodeSearch: Search the web for code examples
|
|
85
|
+
- TodoRead: Read the current task list
|
|
86
|
+
- TodoWrite: Create or update the task list
|
|
87
|
+
- RestartDevServer: Restart the dev server
|
|
88
|
+
|
|
89
|
+
Your workflow:
|
|
90
|
+
|
|
91
|
+
1. UNDERSTAND: If the user's request is vague or could go multiple directions, use AskUser to ask 1-3 clarifying questions about the app type, key features, and design preferences. Keep questions focused and provide concrete options.
|
|
92
|
+
|
|
93
|
+
2. GENERATE: Create a complete, working Next.js app using:
|
|
94
|
+
- App Router with TypeScript
|
|
95
|
+
- Tailwind CSS for all styling
|
|
96
|
+
- Clean component structure in the app/ directory
|
|
97
|
+
- Modern, responsive design with good typography and spacing
|
|
98
|
+
- Proper error handling and loading states
|
|
99
|
+
- No placeholder code or TODOs — everything should work
|
|
100
|
+
|
|
101
|
+
3. VERIFY: After generating all files, check that the dev server is running without errors. If there are build errors, fix them.
|
|
102
|
+
|
|
103
|
+
Clarifying Questions:
|
|
104
|
+
- When a user's request is ambiguous or has multiple valid approaches, use the AskUser tool to ask clarifying questions before proceeding.
|
|
105
|
+
- Present 1-4 questions, each with 2-4 concrete options. Use multiSelect when choices are not mutually exclusive.
|
|
106
|
+
- Keep header labels short (max 12 chars). Put the recommended option first with "(Recommended)" in the label.
|
|
107
|
+
- CRITICAL: All fields in the AskUser tool must be plain text. Do NOT use markdown formatting.
|
|
108
|
+
- After calling AskUser, STOP and wait for the user's answers before continuing.
|
|
109
|
+
|
|
110
|
+
Language:
|
|
111
|
+
- IMPORTANT: Always respond in the same language the user writes in. If the user writes in Chinese, respond in Chinese. If the user writes in English, respond in English.`;
|
|
70
112
|
/** Detects files Claude Code uses for plan output (.claude/plans/*.md, plan.md) */
|
|
71
113
|
function isPlanFile(filePath) {
|
|
72
114
|
const normalized = filePath.replace(/\\/g, '/');
|
|
@@ -107,7 +149,9 @@ function createModel(modelId, providerType, cwd) {
|
|
|
107
149
|
return anthropic(modelId);
|
|
108
150
|
}
|
|
109
151
|
else if (providerType === 'openai') {
|
|
110
|
-
const openai = createOpenAI({
|
|
152
|
+
const openai = createOpenAI({
|
|
153
|
+
baseURL: process.env.OPENAI_BASE_URL,
|
|
154
|
+
});
|
|
111
155
|
return openai(modelId);
|
|
112
156
|
}
|
|
113
157
|
else if (providerType === 'google-ai') {
|
|
@@ -184,15 +228,20 @@ export function createVercelProvider(modelId, providerType) {
|
|
|
184
228
|
try {
|
|
185
229
|
// Self-contained providers handle their own system prompt and tools internally.
|
|
186
230
|
// All other providers get our system prompt + tools.
|
|
231
|
+
const basePrompt = config.creationMode ? CREATION_SYSTEM_PROMPT : SYSTEM_PROMPT;
|
|
187
232
|
const systemPrompt = isSelfContained
|
|
188
233
|
? undefined
|
|
189
|
-
: `${
|
|
234
|
+
: `${basePrompt}\n\nThe user's project directory is: ${config.projectCwd}`;
|
|
235
|
+
const maxOutputTokens = process.env.AWEL_MAX_OUTPUT_TOKENS
|
|
236
|
+
? parseInt(process.env.AWEL_MAX_OUTPUT_TOKENS, 10)
|
|
237
|
+
: undefined;
|
|
190
238
|
const streamTextArgs = {
|
|
191
239
|
model,
|
|
192
240
|
...(systemPrompt && { system: systemPrompt }),
|
|
193
241
|
messages,
|
|
194
242
|
tools,
|
|
195
243
|
...(!isSelfContained && { stopWhen: stepCountIs(25) }),
|
|
244
|
+
...(maxOutputTokens && { maxOutputTokens }),
|
|
196
245
|
abortSignal: abortController.signal,
|
|
197
246
|
};
|
|
198
247
|
logEvent('stream:start', `model=${modelId} provider=${providerType} messages=${messages.length}`);
|
package/dist/cli/proxy.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Creates proxy middleware that forwards requests to the target app
|
|
3
3
|
* and injects the Awel host script into HTML responses.
|
|
4
4
|
*/
|
|
5
|
-
export declare function createProxyMiddleware(targetPort: number, projectCwd?: string): (c: any, _next: () => Promise<void>) => Promise<any>;
|
|
5
|
+
export declare function createProxyMiddleware(targetPort: number, projectCwd?: string, isFresh?: () => boolean): (c: any, _next: () => Promise<void>) => Promise<any>;
|