agent-stage 0.2.13 → 0.2.15

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/index.js CHANGED
@@ -12,17 +12,23 @@ import { pageCommand } from './commands/page/index.js';
12
12
  import { runCommand } from './commands/run/index.js';
13
13
  import { guideCommand } from './commands/guide.js';
14
14
  import { cleanupCommand } from './commands/cleanup.js';
15
+ import { componentsCommand } from './commands/components.js';
16
+ import { doctorCommand } from './commands/doctor.js';
17
+ import { verifyCommand } from './commands/verify.js';
15
18
  const program = new Command();
16
19
  program
17
20
  .name('agentstage')
18
21
  .description('Agent UI Stage CLI - Create interactive UI for AI agents')
19
22
  .version(pkg.version);
20
- // New command structure
21
- program.addCommand(devCommand);
22
- program.addCommand(pageCommand);
23
- program.addCommand(runCommand);
24
- program.addCommand(guideCommand);
25
- program.addCommand(cleanupCommand);
23
+ // Register commands
24
+ program.addCommand(devCommand); // dev init/start/stop/status
25
+ program.addCommand(pageCommand); // page add/rm/ls/manifest
26
+ program.addCommand(runCommand); // run get-state/set-state/exec/inspect/watch
27
+ program.addCommand(guideCommand); // guide
28
+ program.addCommand(cleanupCommand); // cleanup
29
+ program.addCommand(componentsCommand); // components
30
+ program.addCommand(doctorCommand); // doctor
31
+ program.addCommand(verifyCommand); // verify
26
32
  // Error handling
27
33
  program.exitOverride();
28
34
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-stage",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "files": [
5
5
  "dist",
6
6
  "template"
@@ -16,8 +16,8 @@
16
16
  "execa": "^9.3.1",
17
17
  "pathe": "^1.1.2",
18
18
  "picocolors": "^1.1.1",
19
- "@agentstage/bridge": "0.1.2",
20
- "@agentstage/render": "0.2.2"
19
+ "@agentstage/render": "0.2.2",
20
+ "@agentstage/bridge": "0.1.2"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.13.5",
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const execCommand: Command;
@@ -1,75 +0,0 @@
1
- import { Command } from 'commander';
2
- import consola from 'consola';
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';
7
- export const execCommand = new Command('exec')
8
- .description('Execute an action on a page or set state')
9
- .argument('<page>', 'Page ID')
10
- .argument('[action]', 'Action name')
11
- .argument('[payload]', 'Action payload as JSON')
12
- .option('-s, --state <json>', 'Set state directly')
13
- .option('--wait [timeoutMs]', 'Wait for browser ACK (optional timeout in ms)')
14
- .action(async (pageId, action, payload, options) => {
15
- try {
16
- if (!isInitialized()) {
17
- consola.error('Project not initialized. Please run `agentstage init` first.');
18
- process.exit(1);
19
- }
20
- const pagesDir = await getPagesDir();
21
- const fileStore = new FileStore({ pagesDir });
22
- const storePath = join(pagesDir, pageId, 'store.json');
23
- if (options.state) {
24
- const state = JSON.parse(options.state);
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
- }
57
- }
58
- else if (action) {
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);
65
- }
66
- else {
67
- consola.error('Please specify an action or use --state');
68
- process.exit(1);
69
- }
70
- }
71
- catch (error) {
72
- consola.error('Failed to execute:', error.message);
73
- process.exit(1);
74
- }
75
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const initCommand: Command;
@@ -1,208 +0,0 @@
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
- // Check if we're in the monorepo by looking for packages from the CLI location
173
- const currentFilePath = fileURLToPath(import.meta.url);
174
- const localBridgePath = resolve(join(dirname(currentFilePath), '..', '..', '..', 'bridge'));
175
- const localRenderPath = resolve(join(dirname(currentFilePath), '..', '..', '..', 'render'));
176
- const isDev = existsSync(localBridgePath) && existsSync(localRenderPath);
177
- if (isDev) {
178
- // In dev mode, use file: protocol to reference the local packages
179
- packageJson.dependencies['@agentstage/bridge'] = `file:${localBridgePath}`;
180
- packageJson.dependencies['@agentstage/render'] = `file:${localRenderPath}`;
181
- }
182
- else {
183
- // Use npm version for production
184
- packageJson.dependencies['@agentstage/bridge'] = '^0.1.0';
185
- packageJson.dependencies['@agentstage/render'] = '^0.2.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
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
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const inspectCommand: Command;
@@ -1,62 +0,0 @@
1
- import { Command } from 'commander';
2
- import consola from 'consola';
3
- import c from 'picocolors';
4
- import { BridgeClient } from '@agentstage/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
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const lsCommand: Command;
@@ -1,132 +0,0 @@
1
- import { Command } from 'commander';
2
- import consola from 'consola';
3
- import c from 'picocolors';
4
- import { readdir, readFile } from 'fs/promises';
5
- import { existsSync } from 'fs';
6
- import { homedir } from 'os';
7
- import { join } from 'pathe';
8
- import { getWorkspaceDir, readRuntimeConfig, isInitialized } from '../utils/paths.js';
9
- import { BridgeClient } from '@agentstage/bridge/sdk';
10
- async function readLocalStores(localPagesDir) {
11
- if (!existsSync(localPagesDir)) {
12
- return [];
13
- }
14
- const entries = await readdir(localPagesDir, { withFileTypes: true });
15
- const stores = [];
16
- for (const entry of entries) {
17
- if (!entry.isDirectory()) {
18
- continue;
19
- }
20
- const pageId = entry.name;
21
- const storePath = join(localPagesDir, pageId, 'store.json');
22
- if (!existsSync(storePath)) {
23
- continue;
24
- }
25
- try {
26
- const content = await readFile(storePath, 'utf8');
27
- const parsed = JSON.parse(content);
28
- stores.push({
29
- pageId: typeof parsed.pageId === 'string' ? parsed.pageId : pageId,
30
- storeKey: 'main',
31
- version: typeof parsed.version === 'number' ? parsed.version : 0,
32
- });
33
- }
34
- catch {
35
- // Ignore invalid local files in offline mode
36
- }
37
- }
38
- return stores;
39
- }
40
- export const lsCommand = new Command('ls')
41
- .description('List all pages and their store status')
42
- .option('--offline', 'Skip Bridge WebSocket and list local store files only')
43
- .action(async (options) => {
44
- try {
45
- if (!isInitialized()) {
46
- consola.error('Project not initialized. Please run `agentstage init` first.');
47
- process.exit(1);
48
- }
49
- const workspaceDir = await getWorkspaceDir();
50
- const routesDir = join(workspaceDir, 'src', 'routes');
51
- let pages = [];
52
- if (existsSync(routesDir)) {
53
- const entries = await readdir(routesDir, { withFileTypes: true });
54
- pages = entries
55
- .filter(e => e.isFile() && e.name.endsWith('.tsx') && !e.name.startsWith('_'))
56
- .map(e => e.name.replace('.tsx', ''))
57
- .filter(name => name !== 'index'); // index is the home page
58
- }
59
- console.log();
60
- console.log(c.bold('Pages:'));
61
- console.log();
62
- let stores = [];
63
- const localPagesDir = join(homedir(), '.agentstage', 'webapp', 'pages');
64
- if (options.offline) {
65
- stores = await readLocalStores(localPagesDir);
66
- }
67
- else {
68
- const config = await readRuntimeConfig();
69
- if (config) {
70
- try {
71
- const client = new BridgeClient(`ws://localhost:${config.port}/_bridge`);
72
- await client.connect();
73
- stores = await client.listStores();
74
- client.disconnect();
75
- }
76
- catch {
77
- // Bridge 未运行
78
- }
79
- }
80
- }
81
- const pageSet = new Set(pages);
82
- for (const store of stores) {
83
- if (store.pageId !== 'index') {
84
- pageSet.add(store.pageId);
85
- }
86
- }
87
- const listedPages = Array.from(pageSet);
88
- // Show home page
89
- const homeStores = stores.filter(s => s.pageId === 'index');
90
- if (homeStores.length > 0) {
91
- console.log(` ${c.green('●')} ${c.bold('/ (home)')}`);
92
- for (const store of homeStores) {
93
- console.log(` └─ ${store.storeKey} v${store.version}`);
94
- }
95
- }
96
- else {
97
- console.log(` ${c.gray('○')} / (home)`);
98
- }
99
- // Show other pages
100
- if (listedPages.length === 0) {
101
- console.log(c.gray(' No additional pages. Create one with:'));
102
- console.log(c.gray(' agentstage add-page <name>'));
103
- }
104
- else {
105
- for (const page of listedPages) {
106
- const pageStores = stores.filter(s => s.pageId === page);
107
- if (pageStores.length > 0) {
108
- console.log(` ${c.green('●')} ${c.bold('/' + page)}`);
109
- for (const store of pageStores) {
110
- console.log(` └─ ${store.storeKey} v${store.version}`);
111
- }
112
- }
113
- else {
114
- console.log(` ${c.gray('○')} /${page}`);
115
- }
116
- }
117
- }
118
- console.log();
119
- if (stores.length === 0) {
120
- if (options.offline) {
121
- console.log(c.gray(`No local store files found in ${localPagesDir}`));
122
- }
123
- else {
124
- console.log(c.gray('Runtime is not running. Run `agentstage start` to see live status.'));
125
- }
126
- console.log();
127
- }
128
- }
129
- catch (error) {
130
- consola.error('Failed to list pages:', error.message);
131
- }
132
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const restartCommand: Command;
@@ -1,90 +0,0 @@
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
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const rmPageCommand: Command;
@@ -1,32 +0,0 @@
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
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const startCommand: Command;
@@ -1,82 +0,0 @@
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
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const statusCommand: Command;
@@ -1,52 +0,0 @@
1
- import { Command } from 'commander';
2
- import consola from 'consola';
3
- import c from 'picocolors';
4
- import { getWorkspaceDir, readRuntimeConfig, isInitialized } from '../utils/paths.js';
5
- function isRunning(pid) {
6
- try {
7
- process.kill(pid, 0);
8
- return true;
9
- }
10
- catch {
11
- return false;
12
- }
13
- }
14
- export const statusCommand = new Command('status')
15
- .description('Show the Agentstage Runtime status')
16
- .action(async () => {
17
- // 1. 检查是否已初始化
18
- if (!isInitialized()) {
19
- consola.error('Project not initialized. Please run `agentstage init` first.');
20
- process.exit(1);
21
- }
22
- const workspaceDir = await getWorkspaceDir();
23
- console.log();
24
- console.log(c.bold('Agentstage Runtime'));
25
- console.log(c.gray('─'.repeat(40)));
26
- console.log(`Workspace: ${c.cyan(workspaceDir)}`);
27
- // 2. 读取运行时配置
28
- const config = await readRuntimeConfig();
29
- if (!config) {
30
- console.log(`Status: ${c.red('●')} Stopped`);
31
- console.log();
32
- console.log(c.gray('Run `agentstage start` to start the runtime.'));
33
- console.log();
34
- return;
35
- }
36
- // 3. 检查进程是否存活
37
- const running = isRunning(config.pid);
38
- if (running) {
39
- console.log(`Status: ${c.green('●')} Running`);
40
- console.log(`PID: ${config.pid}`);
41
- console.log(`Port: ${config.port}`);
42
- console.log(`Started: ${new Date(config.startedAt).toLocaleString()}`);
43
- console.log(`Web: ${c.cyan(`http://localhost:${config.port}`)}`);
44
- console.log(`Bridge: ${c.cyan(`ws://localhost:${config.port}/_bridge`)}`);
45
- }
46
- else {
47
- console.log(`Status: ${c.yellow('●')} Dead (PID file exists but process not found)`);
48
- console.log();
49
- console.log(c.gray('Run `agentstage start` to restart the runtime.'));
50
- }
51
- console.log();
52
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const stopCommand: Command;
@@ -1,58 +0,0 @@
1
- import { Command } from 'commander';
2
- import consola from 'consola';
3
- import { unlink } from 'fs/promises';
4
- import { existsSync } from 'fs';
5
- import { getPidFile, readRuntimeConfig, removeRuntimeConfig, isInitialized } from '../utils/paths.js';
6
- async function killProcess(pid) {
7
- try {
8
- process.kill(pid, 'SIGTERM');
9
- let attempts = 0;
10
- while (attempts < 10) {
11
- await new Promise(r => setTimeout(r, 500));
12
- try {
13
- process.kill(pid, 0);
14
- attempts++;
15
- }
16
- catch {
17
- break;
18
- }
19
- }
20
- if (attempts >= 10) {
21
- process.kill(pid, 'SIGKILL');
22
- }
23
- }
24
- catch (error) {
25
- if (error.code !== 'ESRCH')
26
- throw error;
27
- }
28
- }
29
- export const stopCommand = new Command('stop')
30
- .description('Stop the Agentstage Runtime')
31
- .action(async () => {
32
- // 1. 检查是否已初始化
33
- if (!isInitialized()) {
34
- consola.error('Project not initialized. Please run `agentstage init` first.');
35
- process.exit(1);
36
- }
37
- // 2. 读取运行时配置
38
- const config = await readRuntimeConfig();
39
- if (!config) {
40
- consola.info('Runtime is not running');
41
- return;
42
- }
43
- // 3. 停止进程
44
- try {
45
- await killProcess(config.pid);
46
- await removeRuntimeConfig();
47
- // 兼容旧版本:删除 pid 文件
48
- const pidFile = await getPidFile();
49
- if (existsSync(pidFile)) {
50
- await unlink(pidFile).catch(() => { });
51
- }
52
- consola.success('Runtime stopped');
53
- }
54
- catch (error) {
55
- consola.error('Failed to stop runtime:', error.message);
56
- process.exit(1);
57
- }
58
- });
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const watchCommand: Command;
@@ -1,54 +0,0 @@
1
- import { Command } from 'commander';
2
- import consola from 'consola';
3
- import c from 'picocolors';
4
- import { BridgeClient } from '@agentstage/bridge/sdk';
5
- import { readRuntimeConfig, isInitialized } from '../utils/paths.js';
6
- export const watchCommand = new Command('watch')
7
- .description('Watch a page for real-time changes')
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
- client.onEvent((event) => {
24
- const timestamp = new Date().toISOString();
25
- console.log(c.gray(`[${timestamp}]`), event);
26
- });
27
- await client.connect();
28
- // 查找并订阅所有该 page 的 stores
29
- const stores = await client.listStores();
30
- const pageStores = stores.filter(s => s.pageId === pageId);
31
- if (pageStores.length === 0) {
32
- consola.error(`Page "${pageId}" not found`);
33
- client.disconnect();
34
- process.exit(1);
35
- }
36
- for (const store of pageStores) {
37
- client.subscribe(store.id);
38
- }
39
- consola.success(`Watching ${c.cyan(pageId)}. Press Ctrl+C to exit.`);
40
- console.log();
41
- // 保持运行
42
- process.on('SIGINT', () => {
43
- console.log();
44
- consola.info('Stopped watching');
45
- client.disconnect();
46
- process.exit(0);
47
- });
48
- await new Promise(() => { });
49
- }
50
- catch (error) {
51
- consola.error('Failed to watch page:', error.message);
52
- process.exit(1);
53
- }
54
- });