agent-stage 0.2.0 → 0.2.7
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/cleanup.d.ts +2 -0
- package/dist/commands/{reset.js → cleanup.js} +3 -2
- package/dist/commands/dev/index.d.ts +2 -0
- package/dist/commands/dev/index.js +11 -0
- package/dist/commands/dev/init.d.ts +2 -0
- package/dist/commands/dev/init.js +231 -0
- package/dist/commands/dev/start.d.ts +2 -0
- package/dist/commands/dev/start.js +145 -0
- package/dist/commands/dev/status.d.ts +2 -0
- package/dist/commands/dev/status.js +55 -0
- package/dist/commands/dev/stop.d.ts +2 -0
- package/dist/commands/dev/stop.js +45 -0
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +46 -30
- package/dist/commands/guide.d.ts +2 -0
- package/dist/commands/guide.js +268 -0
- package/dist/commands/init.js +9 -9
- package/dist/commands/inspect.js +1 -1
- package/dist/commands/ls.js +65 -17
- package/dist/commands/page/add.d.ts +2 -0
- package/dist/commands/page/add.js +262 -0
- package/dist/commands/page/index.d.ts +2 -0
- package/dist/commands/page/index.js +11 -0
- package/dist/commands/page/ls.d.ts +2 -0
- package/dist/commands/page/ls.js +45 -0
- package/dist/commands/page/manifest.d.ts +2 -0
- package/dist/commands/page/manifest.js +82 -0
- package/dist/commands/page/rm.d.ts +2 -0
- package/dist/commands/page/rm.js +89 -0
- package/dist/commands/run/exec.d.ts +2 -0
- package/dist/commands/run/exec.js +89 -0
- package/dist/commands/run/get-state.d.ts +2 -0
- package/dist/commands/run/get-state.js +71 -0
- package/dist/commands/run/index.d.ts +2 -0
- package/dist/commands/run/index.js +13 -0
- package/dist/commands/run/inspect.d.ts +2 -0
- package/dist/commands/run/inspect.js +80 -0
- package/dist/commands/run/set-state.d.ts +2 -0
- package/dist/commands/run/set-state.js +93 -0
- package/dist/commands/run/watch.d.ts +2 -0
- package/dist/commands/run/watch.js +72 -0
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.js +230 -0
- package/dist/commands/watch.js +1 -1
- package/dist/index.js +11 -30
- package/dist/utils/agent-helper.d.ts +30 -0
- package/dist/utils/agent-helper.js +181 -0
- package/dist/utils/cloudflared.d.ts +21 -0
- package/dist/utils/cloudflared.js +90 -0
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.js +7 -10
- package/dist/utils/tunnel.d.ts +21 -0
- package/dist/utils/tunnel.js +116 -0
- package/package.json +14 -10
- package/template/package.json +7 -6
- package/template/src/components/PageRenderer.tsx +108 -0
- package/template/src/components/bridge-state-provider.tsx +87 -0
- package/template/src/lib/bridge.ts +53 -0
- package/template/src/pages/counter/store.json +8 -0
- package/template/src/pages/counter/ui.json +108 -0
- package/template/src/pages/test-page/store.json +8 -0
- package/template/src/routeTree.gen.ts +77 -0
- package/template/src/routes/__root.tsx +1 -1
- package/template/src/routes/counter.tsx +19 -0
- package/dist/commands/add-page.d.ts +0 -2
- package/dist/commands/add-page.js +0 -132
- package/dist/commands/reset.d.ts +0 -2
|
@@ -6,8 +6,9 @@ import { rm, unlink } from 'fs/promises';
|
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
7
|
import { getWorkspaceDir, readRuntimeConfig, WORKSPACE_FILE } from '../utils/paths.js';
|
|
8
8
|
const PROJECT_NAME = 'webapp';
|
|
9
|
-
export const
|
|
10
|
-
.
|
|
9
|
+
export const cleanupCommand = new Command()
|
|
10
|
+
.name('cleanup')
|
|
11
|
+
.description('Clean up Agentstage by removing the project and workspace configuration')
|
|
11
12
|
.option('-y, --yes', 'Skip confirmation', false)
|
|
12
13
|
.action(async (options) => {
|
|
13
14
|
console.log();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { devInitCommand } from './init.js';
|
|
3
|
+
import { devStartCommand } from './start.js';
|
|
4
|
+
import { devStopCommand } from './stop.js';
|
|
5
|
+
import { devStatusCommand } from './status.js';
|
|
6
|
+
export const devCommand = new Command('dev')
|
|
7
|
+
.description('Development commands for Agentstage')
|
|
8
|
+
.addCommand(devInitCommand)
|
|
9
|
+
.addCommand(devStartCommand)
|
|
10
|
+
.addCommand(devStopCommand)
|
|
11
|
+
.addCommand(devStatusCommand);
|
|
@@ -0,0 +1,231 @@
|
|
|
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
|
+
import { checkCloudflared, printInstallInstructions } from '../../utils/cloudflared.js';
|
|
13
|
+
const PROJECT_NAME = 'webapp';
|
|
14
|
+
// Get the template directory path (works in both dev and prod)
|
|
15
|
+
function getTemplateDir() {
|
|
16
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
17
|
+
const currentDir = dirname(currentFilePath);
|
|
18
|
+
// Production: template is next to dist
|
|
19
|
+
const prodPath = join(currentDir, '..', '..', '..', '..', 'template');
|
|
20
|
+
// Development: template is in the source
|
|
21
|
+
const devPath = join(currentDir, '..', '..', '..', '..', '..', 'template');
|
|
22
|
+
if (existsSync(prodPath)) {
|
|
23
|
+
return prodPath;
|
|
24
|
+
}
|
|
25
|
+
if (existsSync(devPath)) {
|
|
26
|
+
return devPath;
|
|
27
|
+
}
|
|
28
|
+
// Fallback: try current working directory relative paths
|
|
29
|
+
const cwdProdPath = join(process.cwd(), 'packages', 'cli', 'template');
|
|
30
|
+
if (existsSync(cwdProdPath)) {
|
|
31
|
+
return cwdProdPath;
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Template directory not found. Please ensure the CLI is properly installed.');
|
|
34
|
+
}
|
|
35
|
+
export const devInitCommand = new Command('init')
|
|
36
|
+
.description('Initialize a new Agentstage project')
|
|
37
|
+
.option('-y, --yes', 'Use default settings (non-interactive)', false)
|
|
38
|
+
.option('--skip-cloudflared-check', 'Skip cloudflared installation check', false)
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
const name = PROJECT_NAME;
|
|
41
|
+
const useDefault = options.yes;
|
|
42
|
+
// 1. Check cloudflared installation
|
|
43
|
+
if (!options.skipCloudflaredCheck) {
|
|
44
|
+
const cloudflaredInfo = await checkCloudflared();
|
|
45
|
+
if (!cloudflaredInfo.installed) {
|
|
46
|
+
printInstallInstructions(cloudflaredInfo);
|
|
47
|
+
const shouldContinue = await p.confirm({
|
|
48
|
+
message: 'Continue with initialization? (You can install cloudflared later)',
|
|
49
|
+
initialValue: true,
|
|
50
|
+
});
|
|
51
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
52
|
+
consola.info('Cancelled');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
consola.success(`Cloudflare Tunnel available: ${c.dim(cloudflaredInfo.version)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 2. 选择工作目录模式
|
|
61
|
+
let locationMode;
|
|
62
|
+
if (useDefault) {
|
|
63
|
+
locationMode = 'default';
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const result = await p.select({
|
|
67
|
+
message: 'Where to store the project?',
|
|
68
|
+
options: [
|
|
69
|
+
{
|
|
70
|
+
value: 'default',
|
|
71
|
+
label: `Default (~/.agentstage/${name})`,
|
|
72
|
+
hint: 'Recommended',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
value: 'current',
|
|
76
|
+
label: 'Current directory (./.agentstage)',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: 'custom',
|
|
80
|
+
label: 'Custom path',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
if (p.isCancel(result)) {
|
|
85
|
+
consola.info('Cancelled');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
locationMode = result;
|
|
89
|
+
}
|
|
90
|
+
// 3. 确定目标目录
|
|
91
|
+
let targetDir;
|
|
92
|
+
switch (locationMode) {
|
|
93
|
+
case 'default':
|
|
94
|
+
targetDir = join(homedir(), '.agentstage', name);
|
|
95
|
+
break;
|
|
96
|
+
case 'current':
|
|
97
|
+
targetDir = join(process.cwd(), '.agentstage');
|
|
98
|
+
break;
|
|
99
|
+
case 'custom':
|
|
100
|
+
const customPath = await p.text({
|
|
101
|
+
message: 'Enter custom path:',
|
|
102
|
+
placeholder: '/path/to/project',
|
|
103
|
+
validate: (value) => {
|
|
104
|
+
if (!value || value.trim() === '') {
|
|
105
|
+
return 'Path is required';
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
if (p.isCancel(customPath)) {
|
|
110
|
+
consola.info('Cancelled');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
targetDir = resolve(customPath);
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
targetDir = join(homedir(), '.agentstage', name);
|
|
117
|
+
}
|
|
118
|
+
// 4. 检查目录
|
|
119
|
+
if (existsSync(targetDir)) {
|
|
120
|
+
const files = await readdirSafe(targetDir);
|
|
121
|
+
if (files.length > 0) {
|
|
122
|
+
// 项目已存在,提示并退出
|
|
123
|
+
console.log();
|
|
124
|
+
consola.info('Project already initialized!');
|
|
125
|
+
console.log(` Location: ${c.cyan(targetDir)}`);
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(` cd ${c.cyan(targetDir)}`);
|
|
128
|
+
console.log(` ${c.cyan('agentstage dev start')}`);
|
|
129
|
+
console.log();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// 5. 保存工作目录配置
|
|
134
|
+
await setWorkspaceDir(targetDir);
|
|
135
|
+
const s = p.spinner();
|
|
136
|
+
try {
|
|
137
|
+
// 6. 复制模板文件
|
|
138
|
+
s.start('Creating project from template...');
|
|
139
|
+
const templateDir = getTemplateDir();
|
|
140
|
+
await mkdir(targetDir, { recursive: true });
|
|
141
|
+
await copyTemplateFiles(templateDir, targetDir);
|
|
142
|
+
s.stop('Project template copied');
|
|
143
|
+
// 7. 更新 package.json 中的 workspace 依赖
|
|
144
|
+
s.start('Configuring project...');
|
|
145
|
+
await configurePackageJson(targetDir);
|
|
146
|
+
s.stop('Project configured');
|
|
147
|
+
// 8. 安装依赖
|
|
148
|
+
s.start('Installing dependencies...');
|
|
149
|
+
await installDependencies(targetDir);
|
|
150
|
+
s.stop('Dependencies installed');
|
|
151
|
+
// 完成
|
|
152
|
+
console.log();
|
|
153
|
+
consola.success('Project created successfully!');
|
|
154
|
+
console.log();
|
|
155
|
+
console.log(` Location: ${c.cyan(targetDir)}`);
|
|
156
|
+
console.log();
|
|
157
|
+
console.log(` cd ${c.cyan(targetDir)}`);
|
|
158
|
+
console.log(` ${c.cyan('agentstage dev start')}`);
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(c.dim('To expose your server to the internet:'));
|
|
161
|
+
console.log(` ${c.cyan('agentstage dev start --tunnel')}`);
|
|
162
|
+
console.log();
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
s.stop('Failed to create project');
|
|
166
|
+
consola.error(error.message);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
async function readdirSafe(dir) {
|
|
171
|
+
try {
|
|
172
|
+
return await readdir(dir);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function copyTemplateFiles(templateDir, targetDir) {
|
|
179
|
+
const entries = await readdir(templateDir, { withFileTypes: true });
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const srcPath = join(templateDir, entry.name);
|
|
182
|
+
const destPath = join(targetDir, entry.name);
|
|
183
|
+
if (entry.isDirectory()) {
|
|
184
|
+
await mkdir(destPath, { recursive: true });
|
|
185
|
+
await copyTemplateFiles(srcPath, destPath);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
await cp(srcPath, destPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function configurePackageJson(targetDir) {
|
|
193
|
+
const packageJsonPath = join(targetDir, 'package.json');
|
|
194
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
|
195
|
+
// Replace workspace:* with actual version or local path
|
|
196
|
+
// In dev mode (monorepo), use file: protocol to reference the local bridge package
|
|
197
|
+
// Check if we're in the monorepo by looking for packages/bridge from the CLI location
|
|
198
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
199
|
+
const localBridgePath = resolve(join(dirname(currentFilePath), '..', '..', '..', '..', '..', 'bridge'));
|
|
200
|
+
const isDev = existsSync(localBridgePath);
|
|
201
|
+
if (isDev) {
|
|
202
|
+
// In dev mode, use file: protocol to reference the local bridge package
|
|
203
|
+
// This works with both npm and pnpm
|
|
204
|
+
packageJson.dependencies['@agentstage/bridge'] = `file:${localBridgePath}`;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// Use npm version for production
|
|
208
|
+
packageJson.dependencies['@agentstage/bridge'] = '^0.1.0';
|
|
209
|
+
}
|
|
210
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
211
|
+
}
|
|
212
|
+
async function installDependencies(targetDir) {
|
|
213
|
+
// Check if we're in the monorepo by looking for packages/bridge from the CLI location
|
|
214
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
215
|
+
const localBridgePath = resolve(join(dirname(currentFilePath), '..', '..', '..', '..', '..', 'bridge'));
|
|
216
|
+
const isDev = existsSync(localBridgePath);
|
|
217
|
+
if (isDev) {
|
|
218
|
+
// In development mode (monorepo), use pnpm
|
|
219
|
+
try {
|
|
220
|
+
await execa('pnpm', ['install'], { cwd: targetDir, stdio: 'pipe' });
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Fallback to npm if pnpm is not available
|
|
224
|
+
await execa('npm', ['install'], { cwd: targetDir, stdio: 'pipe' });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// In production, use npm
|
|
229
|
+
await execa('npm', ['install'], { cwd: targetDir, stdio: 'pipe' });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
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 } from 'fs/promises';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'pathe';
|
|
9
|
+
import { getWorkspaceDir, saveRuntimeConfig, isInitialized, readRuntimeConfig, } from '../../utils/paths.js';
|
|
10
|
+
import { startTunnel, canStartTunnel, printTunnelInfo } from '../../utils/tunnel.js';
|
|
11
|
+
import { checkCloudflared, printInstallInstructions } from '../../utils/cloudflared.js';
|
|
12
|
+
export const devStartCommand = new Command('start')
|
|
13
|
+
.description('Start the Agentstage Runtime (Vite dev server with Bridge)')
|
|
14
|
+
.option('-p, --port <port>', 'Port to run the web server on', '3000')
|
|
15
|
+
.option('-t, --tunnel', 'Expose server to internet via Cloudflare Tunnel', false)
|
|
16
|
+
.option('--open', 'Open browser automatically', false)
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
// 1. 检查是否已初始化
|
|
19
|
+
if (!isInitialized()) {
|
|
20
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const workspaceDir = await getWorkspaceDir();
|
|
24
|
+
const port = parseInt(options.port, 10);
|
|
25
|
+
// 2. 检查是否已运行
|
|
26
|
+
const existingConfig = await readRuntimeConfig();
|
|
27
|
+
if (existingConfig) {
|
|
28
|
+
try {
|
|
29
|
+
process.kill(existingConfig.pid, 0);
|
|
30
|
+
consola.warn(`Runtime is already running (PID: ${existingConfig.pid}, Port: ${existingConfig.port})`);
|
|
31
|
+
console.log(` Web: ${c.cyan(`http://localhost:${existingConfig.port}`)}`);
|
|
32
|
+
if (existingConfig.tunnelUrl) {
|
|
33
|
+
console.log(` Public: ${c.cyan(c.underline(existingConfig.tunnelUrl))}`);
|
|
34
|
+
}
|
|
35
|
+
console.log(` Bridge: ${c.cyan(`ws://localhost:${existingConfig.port}/_bridge`)}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// 进程不存在,继续启动
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 3. 检查是否需要安装依赖
|
|
43
|
+
const nodeModulesPath = join(workspaceDir, 'node_modules');
|
|
44
|
+
if (!existsSync(nodeModulesPath)) {
|
|
45
|
+
const s = p.spinner();
|
|
46
|
+
s.start('Installing dependencies...');
|
|
47
|
+
try {
|
|
48
|
+
await execa('npm', ['install'], { cwd: workspaceDir, stdio: 'pipe' });
|
|
49
|
+
s.stop('Dependencies installed');
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
s.stop('Failed to install dependencies');
|
|
53
|
+
consola.error(error.message);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 4. 检查 cloudflared (如果请求了 tunnel)
|
|
58
|
+
let tunnelUrl;
|
|
59
|
+
if (options.tunnel) {
|
|
60
|
+
const canTunnel = await canStartTunnel();
|
|
61
|
+
if (!canTunnel) {
|
|
62
|
+
const info = await checkCloudflared();
|
|
63
|
+
printInstallInstructions(info);
|
|
64
|
+
consola.error('Cannot start with --tunnel: cloudflared not installed');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const s = p.spinner();
|
|
69
|
+
s.start('Starting Agentstage Runtime...');
|
|
70
|
+
try {
|
|
71
|
+
// 5. 启动 Vite dev server
|
|
72
|
+
const subprocess = execa('npx', ['vite', '--port', String(port), '--host'], {
|
|
73
|
+
cwd: workspaceDir,
|
|
74
|
+
detached: true,
|
|
75
|
+
stdio: 'ignore',
|
|
76
|
+
});
|
|
77
|
+
await mkdir(join(workspaceDir, '.agentstage'), { recursive: true });
|
|
78
|
+
// 6. 启动 tunnel (如果请求了)
|
|
79
|
+
if (options.tunnel) {
|
|
80
|
+
s.message('Starting Cloudflare Tunnel...');
|
|
81
|
+
try {
|
|
82
|
+
const tunnel = await startTunnel(port);
|
|
83
|
+
tunnelUrl = tunnel.url;
|
|
84
|
+
// Save tunnel info for stop command to use
|
|
85
|
+
const tunnelModulePath = join(workspaceDir, '.agentstage', 'tunnel.mjs');
|
|
86
|
+
// Write tunnel info for stop command to use
|
|
87
|
+
await writeFile(tunnelModulePath, `export const tunnelUrl = '${tunnelUrl}';\nexport const tunnelPid = ${subprocess.pid};\n`);
|
|
88
|
+
}
|
|
89
|
+
catch (tunnelError) {
|
|
90
|
+
s.stop('Tunnel failed to start');
|
|
91
|
+
consola.warn(`Tunnel error: ${tunnelError.message}`);
|
|
92
|
+
consola.info('Continuing without tunnel...');
|
|
93
|
+
// Kill the vite process since we're failing
|
|
94
|
+
subprocess.kill();
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// 7. 保存运行时配置
|
|
99
|
+
const config = {
|
|
100
|
+
pid: subprocess.pid,
|
|
101
|
+
port,
|
|
102
|
+
startedAt: new Date().toISOString(),
|
|
103
|
+
tunnelUrl,
|
|
104
|
+
};
|
|
105
|
+
await saveRuntimeConfig(config);
|
|
106
|
+
// 8. 等待一下确保服务启动
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
108
|
+
s.stop('Runtime started successfully');
|
|
109
|
+
console.log();
|
|
110
|
+
consola.success('Agentstage Runtime is running');
|
|
111
|
+
console.log(` Web: ${c.cyan(`http://localhost:${port}`)}`);
|
|
112
|
+
if (tunnelUrl) {
|
|
113
|
+
printTunnelInfo(tunnelUrl);
|
|
114
|
+
}
|
|
115
|
+
console.log(` Bridge: ${c.cyan(`ws://localhost:${port}/_bridge`)}`);
|
|
116
|
+
console.log(` Workspace: ${c.gray(workspaceDir)}`);
|
|
117
|
+
console.log();
|
|
118
|
+
if (options.open) {
|
|
119
|
+
const openUrl = tunnelUrl || `http://localhost:${port}`;
|
|
120
|
+
try {
|
|
121
|
+
await execa('open', [openUrl]);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Ignore open errors
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Keep process alive to maintain tunnel
|
|
128
|
+
if (options.tunnel && tunnelUrl) {
|
|
129
|
+
console.log(c.dim('Press Ctrl+C to stop'));
|
|
130
|
+
process.on('SIGINT', async () => {
|
|
131
|
+
console.log();
|
|
132
|
+
consola.info('Shutting down...');
|
|
133
|
+
subprocess.kill();
|
|
134
|
+
process.exit(0);
|
|
135
|
+
});
|
|
136
|
+
// Keep running
|
|
137
|
+
await new Promise(() => { });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
s.stop('Failed to start runtime');
|
|
142
|
+
consola.error(error.message);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import c from 'picocolors';
|
|
4
|
+
import { readRuntimeConfig, isInitialized, getWorkspaceDir } from '../../utils/paths.js';
|
|
5
|
+
import { checkCloudflared } from '../../utils/cloudflared.js';
|
|
6
|
+
export const devStatusCommand = new Command('status')
|
|
7
|
+
.description('Check the Agentstage Runtime status')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
if (!isInitialized()) {
|
|
10
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const workspaceDir = await getWorkspaceDir();
|
|
14
|
+
const config = await readRuntimeConfig();
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(c.bold('Workspace:'), c.cyan(workspaceDir));
|
|
17
|
+
console.log();
|
|
18
|
+
// Check cloudflared
|
|
19
|
+
const cloudflared = await checkCloudflared();
|
|
20
|
+
console.log(c.bold('Cloudflare Tunnel:'));
|
|
21
|
+
if (cloudflared.installed) {
|
|
22
|
+
console.log(` Status: ${c.green('✓ installed')}`);
|
|
23
|
+
console.log(` Version: ${c.gray(cloudflared.version || 'unknown')}`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log(` Status: ${c.yellow('✗ not installed')}`);
|
|
27
|
+
console.log(` Install: ${c.gray(cloudflared.installCommand)}`);
|
|
28
|
+
}
|
|
29
|
+
console.log();
|
|
30
|
+
// Check runtime
|
|
31
|
+
console.log(c.bold('Runtime:'));
|
|
32
|
+
if (!config) {
|
|
33
|
+
console.log(` Status: ${c.gray('stopped')}`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(config.pid, 0);
|
|
38
|
+
console.log(` Status: ${c.green('running')}`);
|
|
39
|
+
console.log(` PID: ${c.gray(config.pid)}`);
|
|
40
|
+
console.log(` Port: ${c.cyan(config.port)}`);
|
|
41
|
+
console.log(` Local: ${c.cyan(`http://localhost:${config.port}`)}`);
|
|
42
|
+
if (config.tunnelUrl) {
|
|
43
|
+
console.log(` Public: ${c.cyan(c.underline(config.tunnelUrl))}`);
|
|
44
|
+
}
|
|
45
|
+
console.log(` Bridge: ${c.cyan(`ws://localhost:${config.port}/_bridge`)}`);
|
|
46
|
+
console.log(` Started: ${c.gray(new Date(config.startedAt).toLocaleString())}`);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
console.log(` Status: ${c.yellow('stale (process not found)')}`);
|
|
50
|
+
console.log(` Last PID: ${c.gray(config.pid)}`);
|
|
51
|
+
console.log(` Last Port: ${c.gray(config.port)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log();
|
|
55
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import c from 'picocolors';
|
|
4
|
+
import { readRuntimeConfig, removeRuntimeConfig, isInitialized } from '../../utils/paths.js';
|
|
5
|
+
export const devStopCommand = new Command('stop')
|
|
6
|
+
.description('Stop the Agentstage Runtime')
|
|
7
|
+
.action(async () => {
|
|
8
|
+
if (!isInitialized()) {
|
|
9
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const config = await readRuntimeConfig();
|
|
13
|
+
if (!config) {
|
|
14
|
+
consola.warn('Runtime is not running');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Check if process exists
|
|
19
|
+
process.kill(config.pid, 0);
|
|
20
|
+
// Kill the process
|
|
21
|
+
process.kill(config.pid, 'SIGTERM');
|
|
22
|
+
// Wait a bit and check if it's still running
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
24
|
+
try {
|
|
25
|
+
process.kill(config.pid, 0);
|
|
26
|
+
// Still running, force kill
|
|
27
|
+
process.kill(config.pid, 'SIGKILL');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Process already stopped
|
|
31
|
+
}
|
|
32
|
+
await removeRuntimeConfig();
|
|
33
|
+
consola.success('Runtime stopped');
|
|
34
|
+
console.log(` PID: ${c.gray(config.pid)}`);
|
|
35
|
+
console.log(` Port: ${c.gray(config.port)}`);
|
|
36
|
+
if (config.tunnelUrl) {
|
|
37
|
+
console.log(` Tunnel: ${c.gray(config.tunnelUrl)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Process doesn't exist, clean up stale config
|
|
42
|
+
await removeRuntimeConfig();
|
|
43
|
+
consola.info('Runtime was not running (stale config cleaned up)');
|
|
44
|
+
}
|
|
45
|
+
});
|
package/dist/commands/doctor.js
CHANGED
|
@@ -153,7 +153,7 @@ export const doctorCommand = new Command('doctor')
|
|
|
153
153
|
try {
|
|
154
154
|
const workspaceDir = await getWorkspaceDir();
|
|
155
155
|
const nodeModulesPath = join(workspaceDir, 'node_modules');
|
|
156
|
-
const bridgePath = join(nodeModulesPath, '@agentstage
|
|
156
|
+
const bridgePath = join(nodeModulesPath, '@agentstage/bridge');
|
|
157
157
|
if (!existsSync(nodeModulesPath)) {
|
|
158
158
|
results.push({
|
|
159
159
|
name: 'Dependencies',
|
|
@@ -166,7 +166,7 @@ export const doctorCommand = new Command('doctor')
|
|
|
166
166
|
results.push({
|
|
167
167
|
name: 'Dependencies',
|
|
168
168
|
status: 'error',
|
|
169
|
-
message: '
|
|
169
|
+
message: '@agentstage/bridge not installed',
|
|
170
170
|
details: ['Run: npm install @agentstage/bridge'],
|
|
171
171
|
});
|
|
172
172
|
}
|
package/dist/commands/exec.js
CHANGED
|
@@ -1,56 +1,72 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import consola from 'consola';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { join } from 'pathe';
|
|
4
|
+
import { FileStore } from '@agentstage/bridge';
|
|
5
|
+
import { BridgeClient } from '@agentstage/bridge/sdk';
|
|
6
|
+
import { isInitialized, readRuntimeConfig, getPagesDir } from '../utils/paths.js';
|
|
5
7
|
export const execCommand = new Command('exec')
|
|
6
8
|
.description('Execute an action on a page or set state')
|
|
7
9
|
.argument('<page>', 'Page ID')
|
|
8
10
|
.argument('[action]', 'Action name')
|
|
9
11
|
.argument('[payload]', 'Action payload as JSON')
|
|
10
12
|
.option('-s, --state <json>', 'Set state directly')
|
|
13
|
+
.option('--wait [timeoutMs]', 'Wait for browser ACK (optional timeout in ms)')
|
|
11
14
|
.action(async (pageId, action, payload, options) => {
|
|
12
15
|
try {
|
|
13
|
-
// 1. 检查是否已初始化
|
|
14
16
|
if (!isInitialized()) {
|
|
15
17
|
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
16
18
|
process.exit(1);
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
consola.error('Runtime is not running. Please run `agentstage start` first.');
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const client = new BridgeClient(`ws://localhost:${config.port}/_bridge`);
|
|
25
|
-
await client.connect();
|
|
26
|
-
// 查找 page 对应的 store
|
|
27
|
-
const stores = await client.listStores();
|
|
28
|
-
consola.info(`Found ${stores.length} stores:`, stores.map(s => `${s.pageId}:${s.storeKey} (${s.id})`));
|
|
29
|
-
const store = stores.find(s => s.pageId === pageId && s.storeKey === 'main');
|
|
30
|
-
if (!store) {
|
|
31
|
-
consola.error(`Page "${pageId}" not found`);
|
|
32
|
-
consola.info('Available pages:', stores.map(s => s.pageId));
|
|
33
|
-
client.disconnect();
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
20
|
+
const pagesDir = await getPagesDir();
|
|
21
|
+
const fileStore = new FileStore({ pagesDir });
|
|
22
|
+
const storePath = join(pagesDir, pageId, 'store.json');
|
|
36
23
|
if (options.state) {
|
|
37
|
-
// 设置 state
|
|
38
24
|
const state = JSON.parse(options.state);
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
const waitForAck = options.wait !== undefined && options.wait !== false;
|
|
26
|
+
if (waitForAck) {
|
|
27
|
+
const timeoutMs = typeof options.wait === 'string' && Number.isFinite(Number(options.wait))
|
|
28
|
+
? Number(options.wait)
|
|
29
|
+
: 5000;
|
|
30
|
+
const config = await readRuntimeConfig();
|
|
31
|
+
if (!config) {
|
|
32
|
+
consola.error('Bridge runtime is not running. Start it first or remove --wait.');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const client = new BridgeClient(`ws://localhost:${config.port}/_bridge`);
|
|
36
|
+
await client.connect();
|
|
37
|
+
try {
|
|
38
|
+
const result = await client.setStateByKey(pageId, 'main', state, {
|
|
39
|
+
waitForAck: true,
|
|
40
|
+
timeoutMs,
|
|
41
|
+
});
|
|
42
|
+
consola.success(`State updated with ACK (${storePath}, v${result.version})`);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
client.disconnect();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const saved = await fileStore.save(pageId, {
|
|
50
|
+
state,
|
|
51
|
+
version: 0,
|
|
52
|
+
updatedAt: new Date().toISOString(),
|
|
53
|
+
pageId,
|
|
54
|
+
});
|
|
55
|
+
consola.success(`State updated (${storePath}, v${saved.version})`);
|
|
56
|
+
}
|
|
41
57
|
}
|
|
42
58
|
else if (action) {
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
// 保留命令参数兼容性,但文件模式仅支持直接写 state
|
|
60
|
+
if (payload) {
|
|
61
|
+
JSON.parse(payload);
|
|
62
|
+
}
|
|
63
|
+
consola.error('File mode only supports --state. Action dispatch requires a live Bridge connection.');
|
|
64
|
+
process.exit(1);
|
|
47
65
|
}
|
|
48
66
|
else {
|
|
49
67
|
consola.error('Please specify an action or use --state');
|
|
50
|
-
client.disconnect();
|
|
51
68
|
process.exit(1);
|
|
52
69
|
}
|
|
53
|
-
client.disconnect();
|
|
54
70
|
}
|
|
55
71
|
catch (error) {
|
|
56
72
|
consola.error('Failed to execute:', error.message);
|