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,132 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import c from 'picocolors';
|
|
4
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { join } from 'pathe';
|
|
7
|
+
import { getWorkspaceDir, isInitialized, readRuntimeConfig } from '../utils/paths.js';
|
|
8
|
+
export const addPageCommand = new Command('add-page')
|
|
9
|
+
.description('Add a new page with Bridge store')
|
|
10
|
+
.argument('<name>', 'Page name (e.g., counter, about)')
|
|
11
|
+
.action(async (name) => {
|
|
12
|
+
// 检查是否已初始化
|
|
13
|
+
if (!isInitialized()) {
|
|
14
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
18
|
+
consola.error('Page name must be lowercase letters, numbers, and hyphens');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const workspaceDir = await getWorkspaceDir();
|
|
23
|
+
const config = await readRuntimeConfig();
|
|
24
|
+
const routesDir = join(workspaceDir, 'src', 'routes');
|
|
25
|
+
const pageFile = join(routesDir, `${name}.tsx`);
|
|
26
|
+
// 确保 routes 目录存在
|
|
27
|
+
await mkdir(routesDir, { recursive: true });
|
|
28
|
+
if (existsSync(pageFile)) {
|
|
29
|
+
consola.error(`Page "${name}" already exists at src/routes/${name}.tsx`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const pageContent = `import { z } from 'zod';
|
|
33
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
34
|
+
import { createBridgeStore } from 'agent-stage-bridge/browser';
|
|
35
|
+
import { useEffect } from 'react';
|
|
36
|
+
import { Button } from '../components/ui/button';
|
|
37
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
|
|
38
|
+
|
|
39
|
+
// Define the page state schema
|
|
40
|
+
const StateSchema = z.object({
|
|
41
|
+
count: z.number().default(0),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
type State = z.infer<typeof StateSchema>;
|
|
45
|
+
|
|
46
|
+
// Create the bridge store for this page
|
|
47
|
+
const bridgeStore = createBridgeStore<State, { increment: { payload?: number }; decrement: {} }>({
|
|
48
|
+
pageId: '${name}',
|
|
49
|
+
storeKey: 'main',
|
|
50
|
+
description: {
|
|
51
|
+
schema: StateSchema,
|
|
52
|
+
actions: {
|
|
53
|
+
increment: {
|
|
54
|
+
description: 'Increment the counter by a specified amount (default: 1)',
|
|
55
|
+
payload: z.number().optional(),
|
|
56
|
+
},
|
|
57
|
+
decrement: {
|
|
58
|
+
description: 'Decrement the counter by 1',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
createState: (set) => ({
|
|
63
|
+
count: 0,
|
|
64
|
+
dispatch: (action) => {
|
|
65
|
+
switch (action.type) {
|
|
66
|
+
case 'increment':
|
|
67
|
+
set((state) => ({ count: state.count + (action.payload ?? 1) }));
|
|
68
|
+
break;
|
|
69
|
+
case 'decrement':
|
|
70
|
+
set((state) => ({ count: state.count - 1 }));
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const Route = createFileRoute('/${name}')({
|
|
78
|
+
component: ${toPascalCase(name)}Page,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function ${toPascalCase(name)}Page() {
|
|
82
|
+
// Connect to the bridge when the component mounts
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
bridgeStore.connect().catch(console.error);
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="min-h-screen bg-background p-8">
|
|
89
|
+
<div className="mx-auto max-w-2xl">
|
|
90
|
+
<Card>
|
|
91
|
+
<CardHeader>
|
|
92
|
+
<CardTitle>${toTitleCase(name)}</CardTitle>
|
|
93
|
+
<CardDescription>
|
|
94
|
+
This page is connected to the Agentstage Bridge.
|
|
95
|
+
</CardDescription>
|
|
96
|
+
</CardHeader>
|
|
97
|
+
<CardContent className="space-y-4">
|
|
98
|
+
<p className="text-sm text-muted-foreground">
|
|
99
|
+
Edit this page at src/routes/${name}.tsx
|
|
100
|
+
</p>
|
|
101
|
+
<div className="flex gap-2">
|
|
102
|
+
<Button variant="outline">Action 1</Button>
|
|
103
|
+
<Button>Action 2</Button>
|
|
104
|
+
</div>
|
|
105
|
+
</CardContent>
|
|
106
|
+
</Card>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
await writeFile(pageFile, pageContent);
|
|
113
|
+
consola.success(`Page "${name}" created`);
|
|
114
|
+
console.log(` File: ${c.cyan(`src/routes/${name}.tsx`)}`);
|
|
115
|
+
const port = config?.port || 3000;
|
|
116
|
+
console.log(` URL: ${c.cyan(`http://localhost:${port}/${name}`)}`);
|
|
117
|
+
console.log(` Route: ${c.gray(`/${name}`)} (file-based, auto-registered by TanStack Router)`);
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(` The route is automatically registered by TanStack Router.`);
|
|
120
|
+
console.log(` No manual route configuration needed!`);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
consola.error('Failed to create page:', error.message);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
function toPascalCase(str) {
|
|
128
|
+
return str.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
129
|
+
}
|
|
130
|
+
function toTitleCase(str) {
|
|
131
|
+
return str.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
132
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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 { readdir } from 'fs/promises';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'pathe';
|
|
9
|
+
import { getWorkspaceDir, isInitialized } from '../utils/paths.js';
|
|
10
|
+
export const componentsCommand = new Command('components')
|
|
11
|
+
.description('Manage shadcn/ui components');
|
|
12
|
+
// 检查初始化中间件
|
|
13
|
+
const checkInit = () => {
|
|
14
|
+
if (!isInitialized()) {
|
|
15
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
componentsCommand
|
|
20
|
+
.command('list')
|
|
21
|
+
.description('List installed components')
|
|
22
|
+
.action(async () => {
|
|
23
|
+
checkInit();
|
|
24
|
+
try {
|
|
25
|
+
const workspaceDir = await getWorkspaceDir();
|
|
26
|
+
// 支持 src/components/ui (TanStack Start) 和 components/ui 两种结构
|
|
27
|
+
let componentsDir = join(workspaceDir, 'src', 'components', 'ui');
|
|
28
|
+
if (!existsSync(componentsDir)) {
|
|
29
|
+
componentsDir = join(workspaceDir, 'components', 'ui');
|
|
30
|
+
}
|
|
31
|
+
let installed = [];
|
|
32
|
+
if (existsSync(componentsDir)) {
|
|
33
|
+
const files = await readdir(componentsDir);
|
|
34
|
+
installed = files.filter(f => f.endsWith('.tsx')).map(f => f.replace('.tsx', ''));
|
|
35
|
+
}
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(c.bold(`Installed (${installed.length}):`));
|
|
38
|
+
if (installed.length > 0) {
|
|
39
|
+
console.log(' ' + installed.map(n => c.green(n)).join(' '));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(c.gray(' None'));
|
|
43
|
+
}
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(`Use ${c.cyan('agentstage components available')} to see available components`);
|
|
46
|
+
console.log(`Use ${c.cyan('agentstage components add <component>')} to install`);
|
|
47
|
+
console.log();
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
consola.error('Failed to list components:', error.message);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
componentsCommand
|
|
54
|
+
.command('available')
|
|
55
|
+
.description('List available components from shadcn/ui registry')
|
|
56
|
+
.action(async () => {
|
|
57
|
+
checkInit();
|
|
58
|
+
try {
|
|
59
|
+
const workspaceDir = await getWorkspaceDir();
|
|
60
|
+
console.log();
|
|
61
|
+
consola.info('Fetching available components from shadcn/ui registry...');
|
|
62
|
+
console.log();
|
|
63
|
+
// 使用 shadcn 原生的 list 命令
|
|
64
|
+
await execa('npx', ['shadcn@latest', 'list', '@shadcn'], {
|
|
65
|
+
cwd: workspaceDir,
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
consola.error('Failed to list available components:', error.message);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
componentsCommand
|
|
74
|
+
.command('search')
|
|
75
|
+
.description('Search for components in shadcn/ui registry')
|
|
76
|
+
.argument('<query>', 'Search query')
|
|
77
|
+
.action(async (query) => {
|
|
78
|
+
checkInit();
|
|
79
|
+
try {
|
|
80
|
+
const workspaceDir = await getWorkspaceDir();
|
|
81
|
+
console.log();
|
|
82
|
+
consola.info(`Searching for "${query}"...`);
|
|
83
|
+
console.log();
|
|
84
|
+
// 使用 shadcn 原生的 search 命令
|
|
85
|
+
await execa('npx', ['shadcn@latest', 'search', '@shadcn', '-q', query], {
|
|
86
|
+
cwd: workspaceDir,
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
consola.error('Failed to search:', error.message);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
componentsCommand
|
|
95
|
+
.command('add')
|
|
96
|
+
.description('Add a shadcn/ui component')
|
|
97
|
+
.argument('<component>', 'Component name (e.g., button, card, dialog)')
|
|
98
|
+
.action(async (component) => {
|
|
99
|
+
checkInit();
|
|
100
|
+
const s = p.spinner();
|
|
101
|
+
try {
|
|
102
|
+
const workspaceDir = await getWorkspaceDir();
|
|
103
|
+
s.start(`Installing ${component}...`);
|
|
104
|
+
await execa('npx', ['shadcn@latest', 'add', component, '-y'], {
|
|
105
|
+
cwd: workspaceDir,
|
|
106
|
+
stdio: 'pipe',
|
|
107
|
+
});
|
|
108
|
+
s.stop(`${c.green('✓')} ${component} installed`);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
s.stop('Installation failed');
|
|
112
|
+
consola.error(error.message);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
componentsCommand
|
|
117
|
+
.command('view')
|
|
118
|
+
.description('View a component before installing')
|
|
119
|
+
.argument('<component>', 'Component name to view')
|
|
120
|
+
.action(async (component) => {
|
|
121
|
+
checkInit();
|
|
122
|
+
try {
|
|
123
|
+
const workspaceDir = await getWorkspaceDir();
|
|
124
|
+
console.log();
|
|
125
|
+
consola.info(`Viewing ${component}...`);
|
|
126
|
+
console.log();
|
|
127
|
+
// 使用 shadcn 原生的 view 命令
|
|
128
|
+
await execa('npx', ['shadcn@latest', 'view', component], {
|
|
129
|
+
cwd: workspaceDir,
|
|
130
|
+
stdio: 'inherit',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
consola.error('Failed to view component:', error.message);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import c from 'picocolors';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { getWorkspaceDir, readRuntimeConfig, isInitialized } from '../utils/paths.js';
|
|
7
|
+
export const doctorCommand = new Command('doctor')
|
|
8
|
+
.description('Diagnose Agentstage environment and runtime issues')
|
|
9
|
+
.option('--fix', 'Attempt to fix common issues')
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(c.bold('Agentstage Doctor'));
|
|
13
|
+
console.log(c.gray('─'.repeat(50)));
|
|
14
|
+
console.log();
|
|
15
|
+
const results = [];
|
|
16
|
+
// 1. Check workspace
|
|
17
|
+
if (!isInitialized()) {
|
|
18
|
+
results.push({
|
|
19
|
+
name: 'Workspace',
|
|
20
|
+
status: 'error',
|
|
21
|
+
message: 'Not initialized',
|
|
22
|
+
details: ['Run: agentstage init'],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
try {
|
|
27
|
+
const workspaceDir = await getWorkspaceDir();
|
|
28
|
+
results.push({
|
|
29
|
+
name: 'Workspace',
|
|
30
|
+
status: 'ok',
|
|
31
|
+
message: `Found at ${workspaceDir}`,
|
|
32
|
+
});
|
|
33
|
+
// Check project structure
|
|
34
|
+
const checks = [
|
|
35
|
+
{ path: 'src/routes', name: 'Routes directory' },
|
|
36
|
+
{ path: 'src/components/ui', name: 'UI components' },
|
|
37
|
+
{ path: 'package.json', name: 'Package.json' },
|
|
38
|
+
];
|
|
39
|
+
for (const check of checks) {
|
|
40
|
+
const fullPath = join(workspaceDir, check.path);
|
|
41
|
+
if (existsSync(fullPath)) {
|
|
42
|
+
results.push({
|
|
43
|
+
name: check.name,
|
|
44
|
+
status: 'ok',
|
|
45
|
+
message: `Found (${check.path})`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
results.push({
|
|
50
|
+
name: check.name,
|
|
51
|
+
status: 'error',
|
|
52
|
+
message: `Missing: ${check.path}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Check bridge plugin in vite.config.ts
|
|
57
|
+
const viteConfigPath = join(workspaceDir, 'vite.config.ts');
|
|
58
|
+
if (existsSync(viteConfigPath)) {
|
|
59
|
+
const viteConfig = await import('fs/promises').then(fs => fs.readFile(viteConfigPath, 'utf8'));
|
|
60
|
+
if (viteConfig.includes('bridgePlugin')) {
|
|
61
|
+
results.push({
|
|
62
|
+
name: 'Bridge plugin',
|
|
63
|
+
status: 'ok',
|
|
64
|
+
message: 'Configured in vite.config.ts',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
results.push({
|
|
69
|
+
name: 'Bridge plugin',
|
|
70
|
+
status: 'error',
|
|
71
|
+
message: 'Missing bridgePlugin() in vite.config.ts',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
results.push({
|
|
77
|
+
name: 'Bridge plugin',
|
|
78
|
+
status: 'error',
|
|
79
|
+
message: 'vite.config.ts not found',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
results.push({
|
|
85
|
+
name: 'Workspace',
|
|
86
|
+
status: 'error',
|
|
87
|
+
message: error.message,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 2. Check runtime status
|
|
92
|
+
const config = await readRuntimeConfig();
|
|
93
|
+
if (config) {
|
|
94
|
+
try {
|
|
95
|
+
process.kill(config.pid, 0);
|
|
96
|
+
results.push({
|
|
97
|
+
name: 'Runtime',
|
|
98
|
+
status: 'ok',
|
|
99
|
+
message: 'Running',
|
|
100
|
+
details: [`PID: ${config.pid}`, `Port: ${config.port}`],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
results.push({
|
|
105
|
+
name: 'Runtime',
|
|
106
|
+
status: 'error',
|
|
107
|
+
message: 'Not running (stale config file)',
|
|
108
|
+
details: ['Run: agentstage start'],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
results.push({
|
|
114
|
+
name: 'Runtime',
|
|
115
|
+
status: 'warn',
|
|
116
|
+
message: 'Not started',
|
|
117
|
+
details: ['Run: agentstage start'],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// 3. Check ports
|
|
121
|
+
const portChecks = [
|
|
122
|
+
{ port: 3000, name: 'Web Port (3000)' },
|
|
123
|
+
{ port: 42069, name: 'DevTools Port (42069)' },
|
|
124
|
+
];
|
|
125
|
+
for (const { port, name } of portChecks) {
|
|
126
|
+
try {
|
|
127
|
+
const { stdout } = await execa('lsof', ['-ti', `:${port}`], { reject: false });
|
|
128
|
+
if (stdout.trim()) {
|
|
129
|
+
results.push({
|
|
130
|
+
name,
|
|
131
|
+
status: 'warn',
|
|
132
|
+
message: `Port ${port} is in use`,
|
|
133
|
+
details: [`PID: ${stdout.trim()}`, 'May cause startup issues'],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
results.push({
|
|
138
|
+
name,
|
|
139
|
+
status: 'ok',
|
|
140
|
+
message: `Port ${port} is available`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
results.push({
|
|
146
|
+
name,
|
|
147
|
+
status: 'ok',
|
|
148
|
+
message: `Port ${port} (unable to check)`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 4. Check dependencies
|
|
153
|
+
try {
|
|
154
|
+
const workspaceDir = await getWorkspaceDir();
|
|
155
|
+
const nodeModulesPath = join(workspaceDir, 'node_modules');
|
|
156
|
+
const bridgePath = join(nodeModulesPath, '@agentstage', 'bridge');
|
|
157
|
+
if (!existsSync(nodeModulesPath)) {
|
|
158
|
+
results.push({
|
|
159
|
+
name: 'Dependencies',
|
|
160
|
+
status: 'error',
|
|
161
|
+
message: 'node_modules not found',
|
|
162
|
+
details: ['Run: npm install'],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else if (!existsSync(bridgePath)) {
|
|
166
|
+
results.push({
|
|
167
|
+
name: 'Dependencies',
|
|
168
|
+
status: 'error',
|
|
169
|
+
message: 'agent-stage-bridge not installed',
|
|
170
|
+
details: ['Run: npm install @agentstage/bridge'],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
results.push({
|
|
175
|
+
name: 'Dependencies',
|
|
176
|
+
status: 'ok',
|
|
177
|
+
message: 'Core dependencies installed',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
results.push({
|
|
183
|
+
name: 'Dependencies',
|
|
184
|
+
status: 'error',
|
|
185
|
+
message: error.message,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// Print results
|
|
189
|
+
console.log();
|
|
190
|
+
for (const result of results) {
|
|
191
|
+
const icon = result.status === 'ok' ? c.green('✓') :
|
|
192
|
+
result.status === 'warn' ? c.yellow('⚠') : c.red('✗');
|
|
193
|
+
const statusColor = result.status === 'ok' ? c.green :
|
|
194
|
+
result.status === 'warn' ? c.yellow : c.red;
|
|
195
|
+
console.log(`${icon} ${c.bold(result.name)}: ${statusColor(result.message)}`);
|
|
196
|
+
if (result.details) {
|
|
197
|
+
for (const detail of result.details) {
|
|
198
|
+
console.log(` ${c.gray('→')} ${detail}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Summary
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(c.gray('─'.repeat(50)));
|
|
205
|
+
const errors = results.filter(r => r.status === 'error').length;
|
|
206
|
+
const warnings = results.filter(r => r.status === 'warn').length;
|
|
207
|
+
if (errors > 0) {
|
|
208
|
+
console.log(c.red(`Found ${errors} error(s), ${warnings} warning(s)`));
|
|
209
|
+
console.log();
|
|
210
|
+
console.log('To fix issues, try:');
|
|
211
|
+
console.log(` ${c.cyan('agentstage doctor --fix')} (attempt automatic fixes)`);
|
|
212
|
+
console.log(` ${c.cyan('agentstage stop && agentstage start')} (restart runtime)`);
|
|
213
|
+
}
|
|
214
|
+
else if (warnings > 0) {
|
|
215
|
+
console.log(c.yellow(`Found ${warnings} warning(s), all checks passed`));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log(c.green('All checks passed!'));
|
|
219
|
+
}
|
|
220
|
+
console.log();
|
|
221
|
+
// Fix mode
|
|
222
|
+
if (options.fix && (errors > 0 || warnings > 0)) {
|
|
223
|
+
console.log(c.bold('Attempting fixes...'));
|
|
224
|
+
console.log();
|
|
225
|
+
// Try to kill stale processes
|
|
226
|
+
try {
|
|
227
|
+
const { stdout } = await execa('lsof', ['-ti', ':3000'], { reject: false });
|
|
228
|
+
if (stdout.trim()) {
|
|
229
|
+
console.log(`Killing process on port 3000: ${stdout.trim()}`);
|
|
230
|
+
try {
|
|
231
|
+
process.kill(parseInt(stdout.trim()), 'SIGKILL');
|
|
232
|
+
console.log(c.green('✓ Killed'));
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
console.log(c.red('✗ Failed to kill'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// ignore
|
|
241
|
+
}
|
|
242
|
+
console.log();
|
|
243
|
+
console.log('Fixes applied. Run ' + c.cyan('agentstage start') + ' to start runtime.');
|
|
244
|
+
console.log();
|
|
245
|
+
}
|
|
246
|
+
process.exit(errors > 0 ? 1 : 0);
|
|
247
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { BridgeClient } from 'agent-stage-bridge/sdk';
|
|
4
|
+
import { readRuntimeConfig, isInitialized } from '../utils/paths.js';
|
|
5
|
+
export const execCommand = new Command('exec')
|
|
6
|
+
.description('Execute an action on a page or set state')
|
|
7
|
+
.argument('<page>', 'Page ID')
|
|
8
|
+
.argument('[action]', 'Action name')
|
|
9
|
+
.argument('[payload]', 'Action payload as JSON')
|
|
10
|
+
.option('-s, --state <json>', 'Set state directly')
|
|
11
|
+
.action(async (pageId, action, payload, options) => {
|
|
12
|
+
try {
|
|
13
|
+
// 1. 检查是否已初始化
|
|
14
|
+
if (!isInitialized()) {
|
|
15
|
+
consola.error('Project not initialized. Please run `agentstage init` first.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// 2. 检查是否已启动
|
|
19
|
+
const config = await readRuntimeConfig();
|
|
20
|
+
if (!config) {
|
|
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
|
+
}
|
|
36
|
+
if (options.state) {
|
|
37
|
+
// 设置 state
|
|
38
|
+
const state = JSON.parse(options.state);
|
|
39
|
+
await client.setState(store.id, state);
|
|
40
|
+
consola.success('State updated');
|
|
41
|
+
}
|
|
42
|
+
else if (action) {
|
|
43
|
+
// 执行 action
|
|
44
|
+
const actionPayload = payload ? JSON.parse(payload) : undefined;
|
|
45
|
+
await client.dispatch(store.id, { type: action, payload: actionPayload });
|
|
46
|
+
consola.success(`Action "${action}" executed`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
consola.error('Please specify an action or use --state');
|
|
50
|
+
client.disconnect();
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
client.disconnect();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
consola.error('Failed to execute:', error.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|