agent-stage 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/commands/add-page.d.ts +2 -0
- package/dist/commands/add-page.js +132 -0
- package/dist/commands/components.d.ts +2 -0
- package/dist/commands/components.js +136 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +247 -0
- package/dist/commands/exec.d.ts +2 -0
- package/dist/commands/exec.js +59 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +208 -0
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -0
- package/dist/commands/ls.d.ts +2 -0
- package/dist/commands/ls.js +84 -0
- package/dist/commands/reset.d.ts +2 -0
- package/dist/commands/reset.js +92 -0
- package/dist/commands/restart.d.ts +2 -0
- package/dist/commands/restart.js +90 -0
- package/dist/commands/rm-page.d.ts +2 -0
- package/dist/commands/rm-page.js +32 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +82 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +52 -0
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.js +58 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +48 -0
- package/dist/utils/paths.d.ts +45 -0
- package/dist/utils/paths.js +138 -0
- package/package.json +32 -0
- package/template/components.json +17 -0
- package/template/index.html +13 -0
- package/template/package.json +40 -0
- package/template/postcss.config.js +6 -0
- package/template/src/components/ui/button.tsx +55 -0
- package/template/src/components/ui/card.tsx +78 -0
- package/template/src/components/ui/input.tsx +24 -0
- package/template/src/index.css +59 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/main.tsx +23 -0
- package/template/src/routes/__root.tsx +11 -0
- package/template/src/routes/index.tsx +46 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tailwind.config.js +53 -0
- package/template/tsconfig.json +25 -0
- package/template/tsconfig.node.json +11 -0
- package/template/vite.config.ts +22 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import consola from 'consola';
|
|
4
|
+
import c from 'picocolors';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { mkdir, writeFile, readdir, readFile, cp } from 'fs/promises';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { resolve, join, dirname } from 'pathe';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { setWorkspaceDir } from '../utils/paths.js';
|
|
12
|
+
const PROJECT_NAME = 'webapp';
|
|
13
|
+
// Get the template directory path (works in both dev and prod)
|
|
14
|
+
function getTemplateDir() {
|
|
15
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
16
|
+
const currentDir = dirname(currentFilePath);
|
|
17
|
+
// Production: template is next to dist
|
|
18
|
+
const prodPath = join(currentDir, '..', '..', 'template');
|
|
19
|
+
// Development: template is in the source
|
|
20
|
+
const devPath = join(currentDir, '..', '..', '..', 'template');
|
|
21
|
+
if (existsSync(prodPath)) {
|
|
22
|
+
return prodPath;
|
|
23
|
+
}
|
|
24
|
+
if (existsSync(devPath)) {
|
|
25
|
+
return devPath;
|
|
26
|
+
}
|
|
27
|
+
// Fallback: try current working directory relative paths
|
|
28
|
+
const cwdProdPath = join(process.cwd(), 'packages', 'cli', 'template');
|
|
29
|
+
if (existsSync(cwdProdPath)) {
|
|
30
|
+
return cwdProdPath;
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Template directory not found. Please ensure the CLI is properly installed.');
|
|
33
|
+
}
|
|
34
|
+
export const initCommand = new Command('init')
|
|
35
|
+
.description('Initialize a new Agentstage project')
|
|
36
|
+
.option('-y, --yes', 'Use default settings (non-interactive)', false)
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
const name = PROJECT_NAME;
|
|
39
|
+
const useDefault = options.yes;
|
|
40
|
+
// 1. 选择工作目录模式
|
|
41
|
+
let locationMode;
|
|
42
|
+
if (useDefault) {
|
|
43
|
+
locationMode = 'default';
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const result = await p.select({
|
|
47
|
+
message: 'Where to store the project?',
|
|
48
|
+
options: [
|
|
49
|
+
{
|
|
50
|
+
value: 'default',
|
|
51
|
+
label: `Default (~/.agentstage/${name})`,
|
|
52
|
+
hint: 'Recommended'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
value: 'current',
|
|
56
|
+
label: 'Current directory (./.agentstage)'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: 'custom',
|
|
60
|
+
label: 'Custom path'
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
if (p.isCancel(result)) {
|
|
65
|
+
consola.info('Cancelled');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
locationMode = result;
|
|
69
|
+
}
|
|
70
|
+
// 2. 确定目标目录
|
|
71
|
+
let targetDir;
|
|
72
|
+
switch (locationMode) {
|
|
73
|
+
case 'default':
|
|
74
|
+
targetDir = join(homedir(), '.agentstage', name);
|
|
75
|
+
break;
|
|
76
|
+
case 'current':
|
|
77
|
+
targetDir = join(process.cwd(), '.agentstage');
|
|
78
|
+
break;
|
|
79
|
+
case 'custom':
|
|
80
|
+
const customPath = await p.text({
|
|
81
|
+
message: 'Enter custom path:',
|
|
82
|
+
placeholder: '/path/to/project',
|
|
83
|
+
validate: (value) => {
|
|
84
|
+
if (!value || value.trim() === '') {
|
|
85
|
+
return 'Path is required';
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
if (p.isCancel(customPath)) {
|
|
90
|
+
consola.info('Cancelled');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
targetDir = resolve(customPath);
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
targetDir = join(homedir(), '.agentstage', name);
|
|
97
|
+
}
|
|
98
|
+
// 3. 检查目录
|
|
99
|
+
if (existsSync(targetDir)) {
|
|
100
|
+
const files = await readdirSafe(targetDir);
|
|
101
|
+
if (files.length > 0) {
|
|
102
|
+
// 项目已存在,提示并退出
|
|
103
|
+
console.log();
|
|
104
|
+
consola.info('Project already initialized!');
|
|
105
|
+
console.log(` Location: ${c.cyan(targetDir)}`);
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(` cd ${c.cyan(targetDir)}`);
|
|
108
|
+
console.log(` ${c.cyan('agentstage start')}`);
|
|
109
|
+
console.log();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 4. 保存工作目录配置
|
|
114
|
+
await setWorkspaceDir(targetDir);
|
|
115
|
+
const s = p.spinner();
|
|
116
|
+
try {
|
|
117
|
+
// 5. 复制模板文件
|
|
118
|
+
s.start('Creating project from template...');
|
|
119
|
+
const templateDir = getTemplateDir();
|
|
120
|
+
await mkdir(targetDir, { recursive: true });
|
|
121
|
+
await copyTemplateFiles(templateDir, targetDir);
|
|
122
|
+
s.stop('Project template copied');
|
|
123
|
+
// 6. 更新 package.json 中的 workspace 依赖
|
|
124
|
+
s.start('Configuring project...');
|
|
125
|
+
await configurePackageJson(targetDir);
|
|
126
|
+
s.stop('Project configured');
|
|
127
|
+
// 7. 安装依赖
|
|
128
|
+
s.start('Installing dependencies...');
|
|
129
|
+
await installDependencies(targetDir);
|
|
130
|
+
s.stop('Dependencies installed');
|
|
131
|
+
// 完成
|
|
132
|
+
console.log();
|
|
133
|
+
consola.success('Project created successfully!');
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(` Location: ${c.cyan(targetDir)}`);
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(` cd ${c.cyan(targetDir)}`);
|
|
138
|
+
console.log(` ${c.cyan('agentstage start')}`);
|
|
139
|
+
console.log();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
s.stop('Failed to create project');
|
|
143
|
+
consola.error(error.message);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
async function readdirSafe(dir) {
|
|
148
|
+
try {
|
|
149
|
+
return await readdir(dir);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function copyTemplateFiles(templateDir, targetDir) {
|
|
156
|
+
const entries = await readdir(templateDir, { withFileTypes: true });
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const srcPath = join(templateDir, entry.name);
|
|
159
|
+
const destPath = join(targetDir, entry.name);
|
|
160
|
+
if (entry.isDirectory()) {
|
|
161
|
+
await mkdir(destPath, { recursive: true });
|
|
162
|
+
await copyTemplateFiles(srcPath, destPath);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await cp(srcPath, destPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function configurePackageJson(targetDir) {
|
|
170
|
+
const packageJsonPath = join(targetDir, 'package.json');
|
|
171
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
|
172
|
+
// Replace workspace:* with actual version or local path
|
|
173
|
+
// In dev mode (monorepo), use file: protocol to reference the local bridge package
|
|
174
|
+
// Check if we're in the monorepo by looking for packages/bridge from the CLI location
|
|
175
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
176
|
+
const localBridgePath = resolve(join(dirname(currentFilePath), '..', '..', '..', 'bridge'));
|
|
177
|
+
const isDev = existsSync(localBridgePath);
|
|
178
|
+
if (isDev) {
|
|
179
|
+
// In dev mode, use file: protocol to reference the local bridge package
|
|
180
|
+
// This works with both npm and pnpm
|
|
181
|
+
packageJson.dependencies['agent-stage-bridge'] = `file:${localBridgePath}`;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Use npm version for production
|
|
185
|
+
packageJson.dependencies['agent-stage-bridge'] = '^0.1.0';
|
|
186
|
+
}
|
|
187
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
188
|
+
}
|
|
189
|
+
async function installDependencies(targetDir) {
|
|
190
|
+
// Check if we're in the monorepo by looking for packages/bridge from the CLI location
|
|
191
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
192
|
+
const localBridgePath = resolve(join(dirname(currentFilePath), '..', '..', '..', 'bridge'));
|
|
193
|
+
const isDev = existsSync(localBridgePath);
|
|
194
|
+
if (isDev) {
|
|
195
|
+
// In development mode (monorepo), use pnpm
|
|
196
|
+
try {
|
|
197
|
+
await execa('pnpm', ['install'], { cwd: targetDir, stdio: 'pipe' });
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Fallback to npm if pnpm is not available
|
|
201
|
+
await execa('npm', ['install'], { cwd: targetDir, stdio: 'pipe' });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// In production, use npm
|
|
206
|
+
await execa('npm', ['install'], { cwd: targetDir, stdio: 'pipe' });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import c from 'picocolors';
|
|
4
|
+
import { BridgeClient } from 'agent-stage-bridge/sdk';
|
|
5
|
+
import { readRuntimeConfig, isInitialized } from '../utils/paths.js';
|
|
6
|
+
export const inspectCommand = new Command('inspect')
|
|
7
|
+
.description('Inspect a page\'s schema, actions, and current state')
|
|
8
|
+
.argument('<page>', 'Page ID')
|
|
9
|
+
.action(async (pageId) => {
|
|
10
|
+
try {
|
|
11
|
+
// 1. 检查是否已初始化
|
|
12
|
+
if (!isInitialized()) {
|
|
13
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
// 2. 检查是否已启动
|
|
17
|
+
const config = await readRuntimeConfig();
|
|
18
|
+
if (!config) {
|
|
19
|
+
consola.error('Runtime is not running. Please run `agentstage start` first.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const client = new BridgeClient(`ws://localhost:${config.port}/_bridge`);
|
|
23
|
+
await client.connect();
|
|
24
|
+
// 查找 page 对应的 store
|
|
25
|
+
const stores = await client.listStores();
|
|
26
|
+
const pageStores = stores.filter(s => s.pageId === pageId);
|
|
27
|
+
if (pageStores.length === 0) {
|
|
28
|
+
consola.error(`Page "${pageId}" not found or not connected`);
|
|
29
|
+
client.disconnect();
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
for (const storeInfo of pageStores) {
|
|
33
|
+
const description = await client.describe?.(storeInfo.id);
|
|
34
|
+
const state = await client.getState?.(storeInfo.id);
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(c.bold(`${pageId}/${storeInfo.storeKey}`));
|
|
37
|
+
console.log(c.gray('─'.repeat(40)));
|
|
38
|
+
if (description) {
|
|
39
|
+
console.log(c.bold('Schema:'));
|
|
40
|
+
console.log(JSON.stringify(description.schema, null, 2));
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(c.bold('Actions:'));
|
|
43
|
+
if (description.actions) {
|
|
44
|
+
for (const [name, action] of Object.entries(description.actions)) {
|
|
45
|
+
console.log(` ${c.cyan(name)} - ${action.description || ''}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log();
|
|
49
|
+
}
|
|
50
|
+
if (state) {
|
|
51
|
+
console.log(c.bold('Current State:'));
|
|
52
|
+
console.log(JSON.stringify(state.state, null, 2));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
client.disconnect();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
consola.error('Failed to inspect page:', error.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import c from 'picocolors';
|
|
4
|
+
import { readdir } from 'fs/promises';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { join } from 'pathe';
|
|
7
|
+
import { getWorkspaceDir, readRuntimeConfig, isInitialized } from '../utils/paths.js';
|
|
8
|
+
import { BridgeClient } from 'agent-stage-bridge/sdk';
|
|
9
|
+
export const lsCommand = new Command('ls')
|
|
10
|
+
.description('List all pages and their store status')
|
|
11
|
+
.action(async () => {
|
|
12
|
+
try {
|
|
13
|
+
// 1. 检查是否已初始化
|
|
14
|
+
if (!isInitialized()) {
|
|
15
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const workspaceDir = await getWorkspaceDir();
|
|
19
|
+
const routesDir = join(workspaceDir, 'src', 'routes');
|
|
20
|
+
let pages = [];
|
|
21
|
+
if (existsSync(routesDir)) {
|
|
22
|
+
const entries = await readdir(routesDir, { withFileTypes: true });
|
|
23
|
+
pages = entries
|
|
24
|
+
.filter(e => e.isFile() && e.name.endsWith('.tsx') && !e.name.startsWith('_'))
|
|
25
|
+
.map(e => e.name.replace('.tsx', ''))
|
|
26
|
+
.filter(name => name !== 'index'); // index is the home page
|
|
27
|
+
}
|
|
28
|
+
// 2. 检查是否已启动
|
|
29
|
+
const config = await readRuntimeConfig();
|
|
30
|
+
console.log();
|
|
31
|
+
console.log(c.bold('Pages:'));
|
|
32
|
+
console.log();
|
|
33
|
+
let stores = [];
|
|
34
|
+
if (config) {
|
|
35
|
+
try {
|
|
36
|
+
const client = new BridgeClient(`ws://localhost:${config.port}/_bridge`);
|
|
37
|
+
await client.connect();
|
|
38
|
+
stores = await client.listStores();
|
|
39
|
+
client.disconnect();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Bridge 未运行
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Show home page
|
|
46
|
+
const homeStores = stores.filter(s => s.pageId === 'index');
|
|
47
|
+
if (homeStores.length > 0) {
|
|
48
|
+
console.log(` ${c.green('●')} ${c.bold('/ (home)')}`);
|
|
49
|
+
for (const store of homeStores) {
|
|
50
|
+
console.log(` └─ ${store.storeKey} v${store.version}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log(` ${c.gray('○')} / (home)`);
|
|
55
|
+
}
|
|
56
|
+
// Show other pages
|
|
57
|
+
if (pages.length === 0) {
|
|
58
|
+
console.log(c.gray(' No additional pages. Create one with:'));
|
|
59
|
+
console.log(c.gray(' agentstage add-page <name>'));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
for (const page of pages) {
|
|
63
|
+
const pageStores = stores.filter(s => s.pageId === page);
|
|
64
|
+
if (pageStores.length > 0) {
|
|
65
|
+
console.log(` ${c.green('●')} ${c.bold('/' + page)}`);
|
|
66
|
+
for (const store of pageStores) {
|
|
67
|
+
console.log(` └─ ${store.storeKey} v${store.version}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(` ${c.gray('○')} /${page}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
console.log();
|
|
76
|
+
if (stores.length === 0) {
|
|
77
|
+
console.log(c.gray('Runtime is not running. Run `agentstage start` to see live status.'));
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
consola.error('Failed to list pages:', error.message);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import consola from 'consola';
|
|
4
|
+
import c from 'picocolors';
|
|
5
|
+
import { rm, unlink } from 'fs/promises';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { getWorkspaceDir, readRuntimeConfig, WORKSPACE_FILE } from '../utils/paths.js';
|
|
8
|
+
const PROJECT_NAME = 'webapp';
|
|
9
|
+
export const resetCommand = new Command('reset')
|
|
10
|
+
.description('Reset Agentstage by removing the project and workspace configuration')
|
|
11
|
+
.option('-y, --yes', 'Skip confirmation', false)
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
console.log();
|
|
14
|
+
console.log(c.bold('Agentstage Reset'));
|
|
15
|
+
console.log(c.gray('─'.repeat(50)));
|
|
16
|
+
console.log();
|
|
17
|
+
try {
|
|
18
|
+
// 1. 检查 workspace 是否存在
|
|
19
|
+
let workspaceDir;
|
|
20
|
+
try {
|
|
21
|
+
workspaceDir = await getWorkspaceDir();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
consola.info('No workspace found. Nothing to reset.');
|
|
25
|
+
console.log();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// 2. 显示要删除的内容
|
|
29
|
+
console.log(c.yellow('The following will be deleted:'));
|
|
30
|
+
console.log(` • Project: ${c.cyan(workspaceDir)}`);
|
|
31
|
+
console.log(` • Config: ${c.cyan(WORKSPACE_FILE)}`);
|
|
32
|
+
console.log();
|
|
33
|
+
// 3. 检查 runtime 是否在运行
|
|
34
|
+
const config = await readRuntimeConfig();
|
|
35
|
+
if (config) {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(config.pid, 0);
|
|
38
|
+
consola.warn('Runtime appears to be running. Please stop it first:');
|
|
39
|
+
console.log(` ${c.cyan('agentstage stop')}`);
|
|
40
|
+
console.log();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// 进程不在运行,继续
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// 4. 确认
|
|
48
|
+
if (!options.yes) {
|
|
49
|
+
const confirmed = await p.confirm({
|
|
50
|
+
message: 'Are you sure you want to delete everything?',
|
|
51
|
+
initialValue: false,
|
|
52
|
+
});
|
|
53
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
54
|
+
consola.info('Cancelled');
|
|
55
|
+
console.log();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 5. 删除项目目录
|
|
60
|
+
const s = p.spinner();
|
|
61
|
+
s.start('Deleting project...');
|
|
62
|
+
if (existsSync(workspaceDir)) {
|
|
63
|
+
// 使用 rimraf 风格删除(多次尝试)
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
try {
|
|
66
|
+
await rm(workspaceDir, { recursive: true, force: true, maxRetries: 3 });
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (i === 2)
|
|
71
|
+
throw err;
|
|
72
|
+
await new Promise(r => setTimeout(r, 100));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 6. 删除配置文件
|
|
77
|
+
if (existsSync(WORKSPACE_FILE)) {
|
|
78
|
+
await unlink(WORKSPACE_FILE);
|
|
79
|
+
}
|
|
80
|
+
s.stop('Project deleted');
|
|
81
|
+
console.log();
|
|
82
|
+
consola.success('Reset complete!');
|
|
83
|
+
console.log();
|
|
84
|
+
console.log('You can now run:');
|
|
85
|
+
console.log(` ${c.cyan('agentstage init')} to create a new project`);
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
consola.error('Failed to reset:', error.message);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import consola from 'consola';
|
|
4
|
+
import c from 'picocolors';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { mkdir } from 'fs/promises';
|
|
7
|
+
import { join } from 'pathe';
|
|
8
|
+
import { getWorkspaceDir, saveRuntimeConfig, readRuntimeConfig, isInitialized } from '../utils/paths.js';
|
|
9
|
+
async function killProcess(pid) {
|
|
10
|
+
try {
|
|
11
|
+
process.kill(pid, 'SIGTERM');
|
|
12
|
+
let attempts = 0;
|
|
13
|
+
while (attempts < 10) {
|
|
14
|
+
await new Promise(r => setTimeout(r, 500));
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
attempts++;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (attempts >= 10) {
|
|
24
|
+
process.kill(pid, 'SIGKILL');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.code !== 'ESRCH')
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const restartCommand = new Command('restart')
|
|
33
|
+
.description('Restart the Agentstage Runtime (stop and start)')
|
|
34
|
+
.option('-p, --port <port>', 'Port to run the web server on', '3000')
|
|
35
|
+
.action(async (options) => {
|
|
36
|
+
// 1. 检查是否已初始化
|
|
37
|
+
if (!isInitialized()) {
|
|
38
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const workspaceDir = await getWorkspaceDir();
|
|
42
|
+
const port = parseInt(options.port, 10);
|
|
43
|
+
const s = p.spinner();
|
|
44
|
+
// 2. 停止现有服务
|
|
45
|
+
const existingConfig = await readRuntimeConfig();
|
|
46
|
+
if (existingConfig) {
|
|
47
|
+
s.start('Stopping current runtime...');
|
|
48
|
+
try {
|
|
49
|
+
await killProcess(existingConfig.pid);
|
|
50
|
+
s.stop('Runtime stopped');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
s.stop('Failed to stop runtime');
|
|
54
|
+
consola.warn('Continuing with start...');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 3. 启动新服务
|
|
58
|
+
s.start('Starting Agentstage Runtime...');
|
|
59
|
+
try {
|
|
60
|
+
// 启动 TanStack Start 项目(包含 Bridge Gateway)
|
|
61
|
+
const subprocess = execa('npm', ['run', 'dev'], {
|
|
62
|
+
cwd: workspaceDir,
|
|
63
|
+
detached: true,
|
|
64
|
+
stdio: 'ignore',
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
PORT: String(port),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
await mkdir(join(workspaceDir, '.agentstage'), { recursive: true });
|
|
71
|
+
// 保存运行时配置
|
|
72
|
+
await saveRuntimeConfig({
|
|
73
|
+
pid: subprocess.pid,
|
|
74
|
+
port,
|
|
75
|
+
startedAt: new Date().toISOString(),
|
|
76
|
+
});
|
|
77
|
+
s.stop('Runtime restarted successfully');
|
|
78
|
+
console.log();
|
|
79
|
+
consola.success('Agentstage Runtime is running');
|
|
80
|
+
console.log(` Web: ${c.cyan(`http://localhost:${port}`)}`);
|
|
81
|
+
console.log(` Bridge: ${c.cyan(`ws://localhost:${port}/_bridge`)}`);
|
|
82
|
+
console.log();
|
|
83
|
+
console.log(` Workspace: ${c.gray(workspaceDir)}`);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
s.stop('Failed to start runtime');
|
|
87
|
+
consola.error(error.message);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { unlink } from 'fs/promises';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { getWorkspaceDir, isInitialized } from '../utils/paths.js';
|
|
7
|
+
export const rmPageCommand = new Command('rm-page')
|
|
8
|
+
.description('Remove a page from the routes directory')
|
|
9
|
+
.argument('<name>', 'Page name')
|
|
10
|
+
.action(async (name) => {
|
|
11
|
+
// 检查是否已初始化
|
|
12
|
+
if (!isInitialized()) {
|
|
13
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const workspaceDir = await getWorkspaceDir();
|
|
18
|
+
const routesDir = join(workspaceDir, 'src', 'routes');
|
|
19
|
+
const pageFile = join(routesDir, `${name}.tsx`);
|
|
20
|
+
if (!existsSync(pageFile)) {
|
|
21
|
+
consola.error(`Page "${name}" not found at src/routes/${name}.tsx`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
await unlink(pageFile);
|
|
25
|
+
consola.success(`Page "${name}" deleted`);
|
|
26
|
+
console.log(` Note: TanStack Router will automatically remove the route`);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
consola.error('Failed to delete page:', error.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import consola from 'consola';
|
|
4
|
+
import c from 'picocolors';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { mkdir } from 'fs/promises';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'pathe';
|
|
9
|
+
import { getWorkspaceDir, saveRuntimeConfig, isInitialized, readRuntimeConfig } from '../utils/paths.js';
|
|
10
|
+
export const startCommand = new Command('start')
|
|
11
|
+
.description('Start the Agentstage Runtime (Vite dev server with Bridge)')
|
|
12
|
+
.option('-p, --port <port>', 'Port to run the web server on', '3000')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
// 1. 检查是否已初始化
|
|
15
|
+
if (!isInitialized()) {
|
|
16
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const workspaceDir = await getWorkspaceDir();
|
|
20
|
+
const port = parseInt(options.port, 10);
|
|
21
|
+
// 2. 检查是否已运行
|
|
22
|
+
const existingConfig = await readRuntimeConfig();
|
|
23
|
+
if (existingConfig) {
|
|
24
|
+
try {
|
|
25
|
+
process.kill(existingConfig.pid, 0);
|
|
26
|
+
consola.warn(`Runtime is already running (PID: ${existingConfig.pid}, Port: ${existingConfig.port})`);
|
|
27
|
+
console.log(` Web: ${c.cyan(`http://localhost:${existingConfig.port}`)}`);
|
|
28
|
+
console.log(` Bridge: ${c.cyan(`ws://localhost:${existingConfig.port}/_bridge`)}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// 进程不存在,继续启动
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// 3. 检查是否需要安装依赖
|
|
36
|
+
const nodeModulesPath = join(workspaceDir, 'node_modules');
|
|
37
|
+
if (!existsSync(nodeModulesPath)) {
|
|
38
|
+
const s = p.spinner();
|
|
39
|
+
s.start('Installing dependencies...');
|
|
40
|
+
try {
|
|
41
|
+
await execa('npm', ['install'], { cwd: workspaceDir, stdio: 'pipe' });
|
|
42
|
+
s.stop('Dependencies installed');
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
s.stop('Failed to install dependencies');
|
|
46
|
+
consola.error(error.message);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const s = p.spinner();
|
|
51
|
+
s.start('Starting Agentstage Runtime...');
|
|
52
|
+
try {
|
|
53
|
+
// 4. 启动 Vite dev server
|
|
54
|
+
// WebSocket 会通过 bridgePlugin() 自动挂载到 Vite server
|
|
55
|
+
const subprocess = execa('npx', ['vite', '--port', String(port), '--host'], {
|
|
56
|
+
cwd: workspaceDir,
|
|
57
|
+
detached: true,
|
|
58
|
+
stdio: 'ignore',
|
|
59
|
+
});
|
|
60
|
+
await mkdir(join(workspaceDir, '.agentstage'), { recursive: true });
|
|
61
|
+
// 5. 保存运行时配置
|
|
62
|
+
await saveRuntimeConfig({
|
|
63
|
+
pid: subprocess.pid,
|
|
64
|
+
port,
|
|
65
|
+
startedAt: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
// 6. 等待一下确保服务启动
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
69
|
+
s.stop('Runtime started successfully');
|
|
70
|
+
console.log();
|
|
71
|
+
consola.success('Agentstage Runtime is running');
|
|
72
|
+
console.log(` Web: ${c.cyan(`http://localhost:${port}`)}`);
|
|
73
|
+
console.log(` Bridge: ${c.cyan(`ws://localhost:${port}/_bridge`)}`);
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(` Workspace: ${c.gray(workspaceDir)}`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
s.stop('Failed to start runtime');
|
|
79
|
+
consola.error(error.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|