awel 0.1.2 → 0.2.0
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/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 +3 -3
- package/dist/cli/providers/types.d.ts +2 -0
- package/dist/cli/providers/vercel.js +44 -1
- 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/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,4 +1,4 @@
|
|
|
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
4
|
function printSetupInstructions() {
|
|
@@ -37,8 +37,8 @@ export async function ensureProvider(projectCwd) {
|
|
|
37
37
|
awel.log(` \u25CF ${p.label}`);
|
|
38
38
|
}
|
|
39
39
|
awel.log('');
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
writeAwelConfig(projectCwd, { ...config, onboarded: true });
|
|
41
|
+
return;
|
|
42
42
|
}
|
|
43
43
|
if (isFirstRun && available.length === 0) {
|
|
44
44
|
// First run with NO providers — show welcome + instructions, exit
|
|
@@ -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, '/');
|
|
@@ -184,9 +226,10 @@ export function createVercelProvider(modelId, providerType) {
|
|
|
184
226
|
try {
|
|
185
227
|
// Self-contained providers handle their own system prompt and tools internally.
|
|
186
228
|
// All other providers get our system prompt + tools.
|
|
229
|
+
const basePrompt = config.creationMode ? CREATION_SYSTEM_PROMPT : SYSTEM_PROMPT;
|
|
187
230
|
const systemPrompt = isSelfContained
|
|
188
231
|
? undefined
|
|
189
|
-
: `${
|
|
232
|
+
: `${basePrompt}\n\nThe user's project directory is: ${config.projectCwd}`;
|
|
190
233
|
const streamTextArgs = {
|
|
191
234
|
model,
|
|
192
235
|
...(systemPrompt && { system: systemPrompt }),
|
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>;
|
package/dist/cli/proxy.js
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
/**
|
|
7
|
+
* Reads the dashboard index.html and injects the creation mode flag.
|
|
8
|
+
* Returns the modified HTML or null if the file doesn't exist.
|
|
9
|
+
*/
|
|
10
|
+
function getCreationModeHtml() {
|
|
11
|
+
const indexPath = join(__dirname, '../dashboard/index.html');
|
|
12
|
+
if (!existsSync(indexPath))
|
|
13
|
+
return null;
|
|
14
|
+
let html = readFileSync(indexPath, 'utf-8');
|
|
15
|
+
const creationScript = `<script>window.__AWEL_CREATION_MODE__=true</script>`;
|
|
16
|
+
if (html.includes('</head>')) {
|
|
17
|
+
html = html.replace('</head>', `${creationScript}</head>`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
html = creationScript + html;
|
|
21
|
+
}
|
|
22
|
+
return html;
|
|
23
|
+
}
|
|
1
24
|
/**
|
|
2
25
|
* Creates proxy middleware that forwards requests to the target app
|
|
3
26
|
* and injects the Awel host script into HTML responses.
|
|
4
27
|
*/
|
|
5
|
-
export function createProxyMiddleware(targetPort, projectCwd) {
|
|
28
|
+
export function createProxyMiddleware(targetPort, projectCwd, isFresh) {
|
|
6
29
|
return async (c, _next) => {
|
|
7
30
|
const url = new URL(c.req.url);
|
|
31
|
+
// In creation mode, serve the dashboard at all HTML navigation requests.
|
|
32
|
+
// Non-HTML requests (JS, CSS, HMR) still proxy through to the dev server.
|
|
33
|
+
if (isFresh?.()) {
|
|
34
|
+
const accept = c.req.header('accept') || '';
|
|
35
|
+
const isNavigation = accept.includes('text/html');
|
|
36
|
+
if (isNavigation) {
|
|
37
|
+
const html = getCreationModeHtml();
|
|
38
|
+
if (html) {
|
|
39
|
+
return new Response(html, {
|
|
40
|
+
status: 200,
|
|
41
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
8
46
|
const targetUrl = `http://localhost:${targetPort}${url.pathname}${url.search}`;
|
|
9
47
|
try {
|
|
10
48
|
// Clone headers and remove Accept-Encoding to get uncompressed response
|
|
@@ -18,7 +56,7 @@ export function createProxyMiddleware(targetPort, projectCwd) {
|
|
|
18
56
|
: undefined,
|
|
19
57
|
});
|
|
20
58
|
const contentType = response.headers.get('content-type') || '';
|
|
21
|
-
// If it's HTML, inject
|
|
59
|
+
// If it's HTML, inject the Awel host script
|
|
22
60
|
if (contentType.includes('text/html')) {
|
|
23
61
|
let html = await response.text();
|
|
24
62
|
// Inject project CWD (for source-map resolution) and the host script
|
|
@@ -26,12 +64,8 @@ export function createProxyMiddleware(targetPort, projectCwd) {
|
|
|
26
64
|
? `<script>window.__AWEL_PROJECT_CWD__=${JSON.stringify(projectCwd)}</script>`
|
|
27
65
|
: '';
|
|
28
66
|
// Constrain Next.js error overlay stacking context below Awel's UI.
|
|
29
|
-
// Covers both legacy and modern Next.js error overlay custom elements.
|
|
30
67
|
const awelOverlayStyle = `<style id="awel-overlay-fix">nextjs-portal, nextjs-portal-root, next-error-overlay, nextjs-dev-tools { position: relative !important; z-index: 999997 !important; }</style>`;
|
|
31
68
|
const scriptTag = `${awelOverlayStyle}${cwdScript}<script src="/_awel/host.js"></script>`;
|
|
32
|
-
// Inject into <head> so the script loads early — even on error pages
|
|
33
|
-
// where the body might be minimal or replaced by Next.js error recovery.
|
|
34
|
-
// Fall back to </body> then </html> if <head> isn't found.
|
|
35
69
|
if (html.includes('</head>')) {
|
|
36
70
|
html = html.replace('</head>', `${scriptTag}</head>`);
|
|
37
71
|
}
|
|
@@ -56,7 +90,6 @@ export function createProxyMiddleware(targetPort, projectCwd) {
|
|
|
56
90
|
// For non-HTML responses, pass through as-is but clean up encoding headers
|
|
57
91
|
const body = await response.arrayBuffer();
|
|
58
92
|
const responseHeaders = new Headers(response.headers);
|
|
59
|
-
// Remove content-encoding since we've already decoded the response
|
|
60
93
|
responseHeaders.delete('content-encoding');
|
|
61
94
|
responseHeaders.delete('content-length');
|
|
62
95
|
return new Response(body, {
|
package/dist/cli/server.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ interface ServerOptions {
|
|
|
2
2
|
awelPort: number;
|
|
3
3
|
targetPort: number;
|
|
4
4
|
projectCwd: string;
|
|
5
|
+
fresh?: boolean;
|
|
5
6
|
}
|
|
6
|
-
export declare function startServer({ awelPort, targetPort, projectCwd }: ServerOptions): Promise<void>;
|
|
7
|
+
export declare function startServer({ awelPort, targetPort, projectCwd, fresh }: ServerOptions): Promise<void>;
|
|
7
8
|
export {};
|
package/dist/cli/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createInspectorRoute } from './inspector.js';
|
|
|
8
8
|
import { createCommentPopupRoute } from './comment-popup.js';
|
|
9
9
|
import { trackProxySocket } from './devserver.js';
|
|
10
10
|
import { getMimeType } from './config.js';
|
|
11
|
+
import { markProjectReady } from './awel-config.js';
|
|
11
12
|
import { awel } from './logger.js';
|
|
12
13
|
import { fileURLToPath } from 'url';
|
|
13
14
|
import { dirname, join } from 'path';
|
|
@@ -37,8 +38,9 @@ function serveDashboardIndex() {
|
|
|
37
38
|
return null;
|
|
38
39
|
return readFileSync(indexPath, 'utf-8');
|
|
39
40
|
}
|
|
40
|
-
export async function startServer({ awelPort, targetPort, projectCwd }) {
|
|
41
|
+
export async function startServer({ awelPort, targetPort, projectCwd, fresh }) {
|
|
41
42
|
const app = new Hono();
|
|
43
|
+
let isFresh = fresh ?? false;
|
|
42
44
|
// Create a proxy for WebSocket connections
|
|
43
45
|
const wsProxy = httpProxy.createProxyServer({
|
|
44
46
|
target: `http://localhost:${targetPort}`,
|
|
@@ -52,13 +54,22 @@ export async function startServer({ awelPort, targetPort, projectCwd }) {
|
|
|
52
54
|
trackProxySocket(proxySocket);
|
|
53
55
|
});
|
|
54
56
|
// Mount agent API routes
|
|
55
|
-
app.route('/', createAgentRoute(projectCwd, targetPort));
|
|
57
|
+
app.route('/', createAgentRoute(projectCwd, targetPort, () => isFresh));
|
|
56
58
|
// Mount undo API routes
|
|
57
59
|
app.route('/', createUndoRoute(projectCwd));
|
|
58
60
|
// Mount inspector relay routes
|
|
59
61
|
app.route('/', createInspectorRoute(projectCwd));
|
|
60
62
|
// Serve the comment popup page (loaded in an iframe by the host script)
|
|
61
63
|
app.route('/', createCommentPopupRoute());
|
|
64
|
+
// ─── Project Status Endpoints ─────────────────────────────
|
|
65
|
+
app.get('/api/project/status', (c) => {
|
|
66
|
+
return c.json({ fresh: isFresh });
|
|
67
|
+
});
|
|
68
|
+
app.post('/api/project/mark-ready', (c) => {
|
|
69
|
+
markProjectReady(projectCwd);
|
|
70
|
+
isFresh = false;
|
|
71
|
+
return c.json({ success: true });
|
|
72
|
+
});
|
|
62
73
|
// Serve the host script
|
|
63
74
|
app.get('/_awel/host.js', async (c) => {
|
|
64
75
|
const hostDistPath = join(__dirname, '../host/host.js');
|
|
@@ -89,7 +100,7 @@ export async function startServer({ awelPort, targetPort, projectCwd }) {
|
|
|
89
100
|
return c.text('Not found', 404);
|
|
90
101
|
});
|
|
91
102
|
// Proxy all other requests to the target app
|
|
92
|
-
app.all('*', createProxyMiddleware(targetPort, projectCwd));
|
|
103
|
+
app.all('*', createProxyMiddleware(targetPort, projectCwd, () => isFresh));
|
|
93
104
|
// Create the HTTP server with Hono
|
|
94
105
|
const server = serve({
|
|
95
106
|
fetch: app.fetch,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Project Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This is a blank Next.js project created with Awel. It uses the App Router, TypeScript, and Tailwind CSS.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
- `src/app/page.tsx` — Home page
|
|
10
|
+
- `src/app/layout.tsx` — Root layout with metadata and fonts
|
|
11
|
+
- `src/app/globals.css` — Global styles and Tailwind imports
|
|
12
|
+
|
|
13
|
+
## Conventions
|
|
14
|
+
|
|
15
|
+
- Use server components by default; add `'use client'` only when needed
|
|
16
|
+
- Style with Tailwind CSS utility classes
|
|
17
|
+
- Keep components in `src/components/`
|
|
18
|
+
- Use TypeScript for all files
|
|
19
|
+
|
|
20
|
+
## Example Prompts
|
|
21
|
+
|
|
22
|
+
- "Add a navigation bar with links to Home and About pages"
|
|
23
|
+
- "Create an About page at /about with a brief bio section"
|
|
24
|
+
- "Add a dark mode toggle using Tailwind's dark: variant"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function Home() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="flex min-h-screen flex-col items-center justify-center p-8">
|
|
4
|
+
<h1 className="text-4xl font-bold tracking-tight">Welcome to your new project</h1>
|
|
5
|
+
<p className="mt-4 text-lg text-gray-600 dark:text-gray-400">
|
|
6
|
+
Open the Awel overlay to start building with AI.
|
|
7
|
+
</p>
|
|
8
|
+
</main>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Project Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A blog application built with Next.js, TypeScript, and Tailwind CSS. Features a post listing page and dynamic post pages.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
- `src/app/page.tsx` — Blog listing page showing all posts
|
|
10
|
+
- `src/app/blog/[slug]/page.tsx` — Dynamic blog post page
|
|
11
|
+
- `src/lib/posts.ts` — Post data and utility functions
|
|
12
|
+
|
|
13
|
+
## Conventions
|
|
14
|
+
|
|
15
|
+
- Use server components by default; add `'use client'` only when needed
|
|
16
|
+
- Style with Tailwind CSS utility classes
|
|
17
|
+
- Keep components in `src/components/`
|
|
18
|
+
- Use TypeScript for all files
|
|
19
|
+
- Posts are stored as data in `src/lib/posts.ts` (replace with a CMS or MDX later)
|
|
20
|
+
|
|
21
|
+
## Example Prompts
|
|
22
|
+
|
|
23
|
+
- "Add a new blog post about getting started with Next.js"
|
|
24
|
+
- "Add tags to posts and create a filter by tag"
|
|
25
|
+
- "Add a search bar to filter posts by title"
|
|
26
|
+
- "Create an RSS feed at /feed.xml"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { notFound } from 'next/navigation';
|
|
3
|
+
import { getPostBySlug, getAllPosts } from '@/lib/posts';
|
|
4
|
+
|
|
5
|
+
export function generateStaticParams() {
|
|
6
|
+
return getAllPosts().map((post) => ({ slug: post.slug }));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
|
|
10
|
+
const { slug } = await params;
|
|
11
|
+
const post = getPostBySlug(slug);
|
|
12
|
+
|
|
13
|
+
if (!post) {
|
|
14
|
+
notFound();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<main className="mx-auto max-w-3xl px-6 py-16">
|
|
19
|
+
<Link
|
|
20
|
+
href="/"
|
|
21
|
+
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
22
|
+
>
|
|
23
|
+
← Back to blog
|
|
24
|
+
</Link>
|
|
25
|
+
<article className="mt-8">
|
|
26
|
+
<time className="text-sm text-gray-500 dark:text-gray-400">
|
|
27
|
+
{post.date}
|
|
28
|
+
</time>
|
|
29
|
+
<h1 className="mt-2 text-4xl font-bold tracking-tight">
|
|
30
|
+
{post.title}
|
|
31
|
+
</h1>
|
|
32
|
+
<div className="prose prose-gray mt-8 dark:prose-invert">
|
|
33
|
+
<p>{post.content}</p>
|
|
34
|
+
</div>
|
|
35
|
+
</article>
|
|
36
|
+
</main>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { getAllPosts } from '@/lib/posts';
|
|
3
|
+
|
|
4
|
+
export default function BlogHome() {
|
|
5
|
+
const posts = getAllPosts();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<main className="mx-auto max-w-3xl px-6 py-16">
|
|
9
|
+
<h1 className="text-4xl font-bold tracking-tight">Blog</h1>
|
|
10
|
+
<p className="mt-4 text-gray-600 dark:text-gray-400">
|
|
11
|
+
Thoughts, ideas, and tutorials.
|
|
12
|
+
</p>
|
|
13
|
+
<div className="mt-12 space-y-10">
|
|
14
|
+
{posts.map((post) => (
|
|
15
|
+
<article key={post.slug}>
|
|
16
|
+
<time className="text-sm text-gray-500 dark:text-gray-400">
|
|
17
|
+
{post.date}
|
|
18
|
+
</time>
|
|
19
|
+
<h2 className="mt-1 text-xl font-semibold">
|
|
20
|
+
<Link
|
|
21
|
+
href={`/blog/${post.slug}`}
|
|
22
|
+
className="hover:underline"
|
|
23
|
+
>
|
|
24
|
+
{post.title}
|
|
25
|
+
</Link>
|
|
26
|
+
</h2>
|
|
27
|
+
<p className="mt-2 text-gray-600 dark:text-gray-400">
|
|
28
|
+
{post.excerpt}
|
|
29
|
+
</p>
|
|
30
|
+
</article>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
</main>
|
|
34
|
+
);
|
|
35
|
+
}
|