@agimon-ai/po-style-system-tooling 0.0.0-stage → 0.2.14-alpha.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/LICENSE +21 -0
- package/README.md +148 -2
- package/dist/cli.cjs +495 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +476 -0
- package/dist/index.cjs +173 -0
- package/dist/index.d.cts +2027 -0
- package/dist/index.d.mts +2027 -0
- package/dist/index.mjs +88 -0
- package/dist/shims/expo-symbols.ts +9 -0
- package/dist/shims/expo-vector-icons.ts +36 -0
- package/dist/shims/react-native-reanimated.ts +57 -0
- package/dist/stdio-BlnEoZEK.mjs +5243 -0
- package/dist/stdio-DOYXz-Fj.cjs +5503 -0
- package/package.json +90 -3
- package/skills/style-system/SKILL.md +83 -0
- package/skills/style-system/agents/openai.yaml +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vuong Ngo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agimon-ai/po-style-system-tooling
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server and CLI for exploring themes, Tailwind CSS classes, and UI components from Storybook, with
|
|
4
|
+
standalone component rendering.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm exec nx run @agimon-ai/po-style-system-tooling:build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## CLI
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
style-system --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Command | Purpose |
|
|
20
|
+
| ------------------------ | ----------------------------------------------------------------------- |
|
|
21
|
+
| `get-css-classes` | Extract the valid CSS classes from a theme CSS file |
|
|
22
|
+
| `get-ui-component` | Render a component to a screenshot using the app's design-system config |
|
|
23
|
+
| `list-app-components` | List app-specific components and package components |
|
|
24
|
+
| `polish-component` | Design-system-aware component polish |
|
|
25
|
+
| `generate-oxlint-config` | Generate an oxlint `no-restricted-classes` config from theme tokens |
|
|
26
|
+
| `mcp-serve` | Run as an MCP server over stdio |
|
|
27
|
+
|
|
28
|
+
## MCP server
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"style-system": {
|
|
34
|
+
"command": "pnpm",
|
|
35
|
+
"args": ["exec", "style-system", "mcp-serve"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
Configuration lives in files named `style-system.config.yaml`, at two levels:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
<workspace-root>/style-system.config.yaml # defines defaults and presets
|
|
47
|
+
<project>/style-system.config.yaml # selects a preset, applies overrides
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The workspace root is found by walking up from the current working directory until a `.git`
|
|
51
|
+
directory is found, falling back to the working directory itself. It is your repository root, not
|
|
52
|
+
the installed package location.
|
|
53
|
+
|
|
54
|
+
### Resolution order
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
built-in defaults -> root `defaults` -> preset chain (parents before child) -> project config
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Last wins, and **fields replace wholesale**. Lists and objects are never merged element-wise, so a
|
|
61
|
+
project that sets `cssFiles` replaces the inherited list entirely. Use `cssFiles: []` to clear one.
|
|
62
|
+
|
|
63
|
+
Every field is optional in any individual file. Required-ness is enforced against the merged result,
|
|
64
|
+
so a project config can be valid while inheriting nearly everything.
|
|
65
|
+
|
|
66
|
+
Both schemas are strict: an unknown or misspelled key is a hard error rather than a silent no-op. The
|
|
67
|
+
project schema additionally rejects `root`, `defaults` and `presets`, so the two file types can never
|
|
68
|
+
be confused for one another.
|
|
69
|
+
|
|
70
|
+
### Workspace root file
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
# <workspace-root>/style-system.config.yaml
|
|
74
|
+
root: true # required literal; marks this as the workspace file
|
|
75
|
+
|
|
76
|
+
# Story tags that mark a component as shared/design-system.
|
|
77
|
+
sharedComponentTags:
|
|
78
|
+
- style-system
|
|
79
|
+
|
|
80
|
+
# Optional service overrides. Paths are workspace-root relative.
|
|
81
|
+
# getCssClasses:
|
|
82
|
+
# customService: packages/.../CustomCSSClassesService.ts
|
|
83
|
+
# bundler:
|
|
84
|
+
# customService: packages/.../CustomBundlerService.ts
|
|
85
|
+
|
|
86
|
+
# Applied to every project, beneath its preset chain.
|
|
87
|
+
defaults:
|
|
88
|
+
type: tailwind
|
|
89
|
+
colorScheme: automatic
|
|
90
|
+
|
|
91
|
+
presets:
|
|
92
|
+
web-app:
|
|
93
|
+
viewport: { width: 1280, height: 800 }
|
|
94
|
+
cssFiles: ['src/assets/main.css']
|
|
95
|
+
componentLibrary: '@acme/web-ui'
|
|
96
|
+
mcp-app:
|
|
97
|
+
extends: web-app # presets may extend other presets
|
|
98
|
+
rootComponent: 'src/components/Root/index.tsx'
|
|
99
|
+
native-app:
|
|
100
|
+
bundler: vite-react-native
|
|
101
|
+
unistylesConfig: unistyles.ts
|
|
102
|
+
viewport: { width: 393, height: 852 }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Project file
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
# <project>/style-system.config.yaml
|
|
109
|
+
extends: web-app # a preset name, or a list: [base, overrides]
|
|
110
|
+
viewport: { width: 1440, height: 900 }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Fields
|
|
114
|
+
|
|
115
|
+
| Field | Type | Purpose |
|
|
116
|
+
| --------------------- | -------------------------------- | -------------------------------------------------- |
|
|
117
|
+
| `type` | `tailwind` \| `shadcn` | Design system flavour |
|
|
118
|
+
| `bundler` | string | Bundler strategy, e.g. `vite-react-native` |
|
|
119
|
+
| `brand` | string | Brand identifier |
|
|
120
|
+
| `unistylesConfig` | string | Path to the Unistyles config, for native rendering |
|
|
121
|
+
| `colorScheme` | `automatic` \| `light` \| `dark` | Rendering colour scheme |
|
|
122
|
+
| `viewport` | `{ width, height }` | Screenshot viewport, positive integers |
|
|
123
|
+
| `tailwindConfig` | string | Path to a Tailwind config |
|
|
124
|
+
| `themeProvider` | string | Module providing the theme provider component |
|
|
125
|
+
| `rootComponent` | string | Root component wrapped around rendered stories |
|
|
126
|
+
| `cssFiles` | string[] | Stylesheets loaded before rendering |
|
|
127
|
+
| `componentLibrary` | string | Package holding shared components |
|
|
128
|
+
| `themePath` | string | Path to the theme CSS |
|
|
129
|
+
| `sharedComponentTags` | string[] | Story tags marking a component as shared |
|
|
130
|
+
|
|
131
|
+
`bundler` is shape-dependent on level: in `defaults`, a preset, or a project file it is a **string**
|
|
132
|
+
naming the strategy; at the workspace root, alongside `getCssClasses`, it is a **service override
|
|
133
|
+
object** taking `customService`.
|
|
134
|
+
|
|
135
|
+
## Native rendering
|
|
136
|
+
|
|
137
|
+
Rendering React Native stories additionally requires the application being rendered to provide
|
|
138
|
+
`react-native`, `react-native-unistyles`, `react-native-reanimated`, `@babel/core`,
|
|
139
|
+
`expo-modules-core` and `@expo/vector-icons`. These are resolved from the target application's
|
|
140
|
+
directory, not from this package, so a React Native app already satisfies them.
|
|
141
|
+
|
|
142
|
+
## Agent skill
|
|
143
|
+
|
|
144
|
+
The published package ships a `skills/style-system` skill describing when and how an agent should use
|
|
145
|
+
these commands.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const require_stdio = require("./stdio-DOYXz-Fj.cjs");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_stdio.__toESM(node_path, 1);
|
|
6
|
+
let zod = require("zod");
|
|
7
|
+
let _agimon_ai_foundation_exec = require("@agimon-ai/foundation-exec");
|
|
8
|
+
let _modelcontextprotocol_server = require("@modelcontextprotocol/server");
|
|
9
|
+
require("reflect-metadata");
|
|
10
|
+
let commander = require("commander");
|
|
11
|
+
let chalk = require("chalk");
|
|
12
|
+
chalk = require_stdio.__toESM(chalk, 1);
|
|
13
|
+
let node_http = require("node:http");
|
|
14
|
+
let _modelcontextprotocol_node = require("@modelcontextprotocol/node");
|
|
15
|
+
//#region src/utils/print.ts
|
|
16
|
+
function resolveChalk(value, depth = 0) {
|
|
17
|
+
if (value && (typeof value === "object" || typeof value === "function") && "red" in value && typeof value.red === "function") return value;
|
|
18
|
+
if (depth < 3 && value && (typeof value === "object" || typeof value === "function") && "default" in value) return resolveChalk(value.default, depth + 1);
|
|
19
|
+
throw new Error("Unable to resolve chalk instance");
|
|
20
|
+
}
|
|
21
|
+
const chalk$1 = resolveChalk(chalk);
|
|
22
|
+
const print = {
|
|
23
|
+
info: (message) => {
|
|
24
|
+
console.log(chalk$1.cyan(message));
|
|
25
|
+
},
|
|
26
|
+
success: (message) => {
|
|
27
|
+
console.log(chalk$1.green(message));
|
|
28
|
+
},
|
|
29
|
+
warning: (message) => {
|
|
30
|
+
console.log(chalk$1.yellow(message));
|
|
31
|
+
},
|
|
32
|
+
error: (message, error) => {
|
|
33
|
+
if (error) {
|
|
34
|
+
const errorMsg = error instanceof Error ? error.message : error;
|
|
35
|
+
console.error(chalk$1.red(message), errorMsg);
|
|
36
|
+
} else console.error(chalk$1.red(message));
|
|
37
|
+
},
|
|
38
|
+
debug: (message) => {
|
|
39
|
+
console.log(chalk$1.gray(message));
|
|
40
|
+
},
|
|
41
|
+
header: (message) => {
|
|
42
|
+
console.log(chalk$1.bold.cyan(message));
|
|
43
|
+
},
|
|
44
|
+
item: (message) => {
|
|
45
|
+
console.log(chalk$1.white(` - ${message}`));
|
|
46
|
+
},
|
|
47
|
+
indent: (message) => {
|
|
48
|
+
console.log(chalk$1.white(` ${message}`));
|
|
49
|
+
},
|
|
50
|
+
highlight: (message) => {
|
|
51
|
+
console.log(chalk$1.bold.green(message));
|
|
52
|
+
},
|
|
53
|
+
newline: () => {
|
|
54
|
+
console.log();
|
|
55
|
+
},
|
|
56
|
+
divider: () => {
|
|
57
|
+
console.log(chalk$1.gray(".".repeat(60)));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/commands/generate-oxlint-config.ts
|
|
62
|
+
/**
|
|
63
|
+
* Generate Oxlint Config Command
|
|
64
|
+
*
|
|
65
|
+
* DESIGN PATTERNS:
|
|
66
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
67
|
+
* - Async/await pattern for asynchronous operations
|
|
68
|
+
* - Error handling pattern with try-catch and proper exit codes
|
|
69
|
+
*
|
|
70
|
+
* CODING STANDARDS:
|
|
71
|
+
* - Use async action handlers for asynchronous operations
|
|
72
|
+
* - Provide clear option descriptions and default values
|
|
73
|
+
* - Handle errors gracefully with process.exit()
|
|
74
|
+
* - Log progress and errors to console
|
|
75
|
+
* - Use Commander's .option() and .argument() for inputs
|
|
76
|
+
*
|
|
77
|
+
* AVOID:
|
|
78
|
+
* - Synchronous blocking operations in action handlers
|
|
79
|
+
* - Missing error handling (always use try-catch)
|
|
80
|
+
* - Hardcoded values (use options or environment variables)
|
|
81
|
+
* - Not exiting with appropriate exit codes on errors
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Generate oxlint no-restricted-classes config from theme CSS design tokens
|
|
85
|
+
*/
|
|
86
|
+
const generateOxlintConfigCommand = new commander.Command("generate-oxlint-config").description("Generate oxlint no-restricted-classes config from theme CSS design tokens").option("-a, --app-path <path>", "App path to read theme path from its style-system.config.yaml").option("-t, --theme-path <path>", "Theme CSS file path relative to workspace root").option("-o, --output <path>", "Output file path (defaults to stdout)").action(async (options) => {
|
|
87
|
+
try {
|
|
88
|
+
const themePath = (options.appPath ? (await require_stdio.getAppDesignSystemConfig(options.appPath)).themePath : void 0) ?? options.themePath;
|
|
89
|
+
if (!themePath) throw new Error("No theme CSS path resolved. Pass --theme-path, or pass --app-path for an app that configures themePath.");
|
|
90
|
+
const resolvedThemePath = await require_stdio.resolveStyleSystemThemePath({
|
|
91
|
+
appPath: options.appPath ?? null,
|
|
92
|
+
themePath
|
|
93
|
+
});
|
|
94
|
+
const cssResult = await new require_stdio.TailwindCSSClassesService(require_stdio.DEFAULT_STYLE_SYSTEM_CONFIG).extractClasses("colors", resolvedThemePath);
|
|
95
|
+
const config = require_stdio.generateRestrictedClassesConfig(cssResult);
|
|
96
|
+
const jsonOutput = JSON.stringify(config, null, 2);
|
|
97
|
+
if (options.output) {
|
|
98
|
+
const outputPath = node_path.default.resolve(options.output);
|
|
99
|
+
await node_fs.promises.writeFile(outputPath, `${jsonOutput}\n`, "utf-8");
|
|
100
|
+
print.success(`Config written to ${outputPath}`);
|
|
101
|
+
} else print.info(jsonOutput);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
print.error("Error generating oxlint config:", error instanceof Error ? error.message : String(error));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/commands/get-css-classes.ts
|
|
109
|
+
/**
|
|
110
|
+
* Get CSS Classes CLI Command
|
|
111
|
+
*/
|
|
112
|
+
const getCSSClassesCommand = new commander.Command("get-css-classes").description("Extract and return all valid CSS classes from a theme CSS file").option("-c, --category <category>", "Category filter: 'colors', 'typography', 'spacing', 'effects', 'all'", "all").option("-a, --app-path <path>", "App path to read theme path from style-system.config.yaml (e.g., \"apps/agiflow-app\")").option("-t, --theme-path <path>", "Theme CSS file path relative to workspace root (required unless appPath resolves themePath from style-system.config.yaml)").action(async (options) => {
|
|
113
|
+
try {
|
|
114
|
+
const result = await require_stdio.createContainer({ defaultThemePath: options.themePath }).get(require_stdio.STYLE_SYSTEM_TYPES.GetCSSClassesTool).execute({
|
|
115
|
+
category: options.category,
|
|
116
|
+
appPath: options.appPath
|
|
117
|
+
});
|
|
118
|
+
if (result.isError) {
|
|
119
|
+
const firstContent = result.content[0];
|
|
120
|
+
const errorText = firstContent?.type === "text" ? firstContent.text : "Unknown error";
|
|
121
|
+
throw new Error(errorText);
|
|
122
|
+
}
|
|
123
|
+
const firstContent = result.content[0];
|
|
124
|
+
print.info(firstContent?.type === "text" ? firstContent.text : "");
|
|
125
|
+
} catch (error) {
|
|
126
|
+
print.error("Error getting CSS classes:", error instanceof Error ? error.message : String(error));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/commands/get-ui-component.ts
|
|
132
|
+
/**
|
|
133
|
+
* Get UI Component CLI Command
|
|
134
|
+
*/
|
|
135
|
+
const LIGHT_COLOR_SCHEME = "light";
|
|
136
|
+
const DARK_COLOR_SCHEME = "dark";
|
|
137
|
+
const REQUESTABLE_COLOR_SCHEMES = [LIGHT_COLOR_SCHEME, DARK_COLOR_SCHEME];
|
|
138
|
+
const getUiComponentCommand = new commander.Command("get-ui-component").description("Get a screenshot of a UI component rendered with app-specific design system configuration. Returns screenshot path and story file content.").requiredOption("-c, --component-name <name>", "The name of the component to capture (e.g., \"Button\", \"Card\")").requiredOption("-a, --app-path <path>", "The app path (relative or absolute) to load design system config from (e.g., \"apps/agiflow-app\")").option("-s, --story-name <name>", "The story name to render (e.g., \"Playground\", \"Default\")", "Playground").option("-d, --dark-mode", "Request dark mode for automatic-theme apps").option("--color-scheme <scheme>", "Request light or dark mode for automatic-theme apps").option("--width <pixels>", "Override the configured viewport width", Number).option("--height <pixels>", "Override the configured viewport height", Number).action(async (options) => {
|
|
139
|
+
try {
|
|
140
|
+
if (options.colorScheme !== void 0 && !REQUESTABLE_COLOR_SCHEMES.includes(options.colorScheme)) throw new Error("--color-scheme must be light or dark");
|
|
141
|
+
const tool = require_stdio.createContainer().get(require_stdio.STYLE_SYSTEM_TYPES.GetComponentVisualTool);
|
|
142
|
+
const darkMode = options.colorScheme === DARK_COLOR_SCHEME ? true : options.colorScheme === LIGHT_COLOR_SCHEME ? false : options.darkMode || void 0;
|
|
143
|
+
const result = await tool.execute({
|
|
144
|
+
componentName: options.componentName,
|
|
145
|
+
appPath: options.appPath,
|
|
146
|
+
storyName: options.storyName,
|
|
147
|
+
darkMode,
|
|
148
|
+
width: options.width,
|
|
149
|
+
height: options.height
|
|
150
|
+
});
|
|
151
|
+
if (result.isError) {
|
|
152
|
+
const firstContent = result.content[0];
|
|
153
|
+
const errorText = firstContent?.type === "text" ? firstContent.text : "Unknown error";
|
|
154
|
+
throw new Error(errorText);
|
|
155
|
+
}
|
|
156
|
+
const firstContent = result.content[0];
|
|
157
|
+
process.stdout.write(`${firstContent?.type === "text" ? firstContent.text : ""}\n`);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
160
|
+
require_stdio.log.error("Error:", errorMessage);
|
|
161
|
+
process.stderr.write(`${errorMessage.startsWith("Error:") ? errorMessage : `Error: ${errorMessage}`}\n`);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/commands/list-app-components.ts
|
|
167
|
+
/**
|
|
168
|
+
* List App Components CLI Command
|
|
169
|
+
*/
|
|
170
|
+
const listAppComponentsCommand = new commander.Command("list-app-components").description("List app-specific components and package components. Reads the app's package.json to find workspace dependencies.").requiredOption("-a, --app-path <path>", "The app path (relative or absolute) to list components for (e.g., \"apps/agiflow-app\")").option("-c, --cursor <cursor>", "Pagination cursor to fetch the next page").action(async (options) => {
|
|
171
|
+
try {
|
|
172
|
+
const result = await require_stdio.createContainer().get(require_stdio.STYLE_SYSTEM_TYPES.ListAppComponentsTool).execute({
|
|
173
|
+
appPath: options.appPath,
|
|
174
|
+
cursor: options.cursor
|
|
175
|
+
});
|
|
176
|
+
if (result.isError) {
|
|
177
|
+
const firstContent = result.content[0];
|
|
178
|
+
const errorText = firstContent?.type === "text" ? firstContent.text : "Unknown error";
|
|
179
|
+
throw new Error(errorText);
|
|
180
|
+
}
|
|
181
|
+
const firstContent = result.content[0];
|
|
182
|
+
print.info(firstContent?.type === "text" ? firstContent.text : "");
|
|
183
|
+
} catch (error) {
|
|
184
|
+
print.error("Error:", error instanceof Error ? error.message : String(error));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/commands/list-themes.ts
|
|
190
|
+
/**
|
|
191
|
+
* List Themes Command
|
|
192
|
+
*
|
|
193
|
+
* DESIGN PATTERNS:
|
|
194
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
195
|
+
* - Async/await pattern for asynchronous operations
|
|
196
|
+
* - Error handling pattern with try-catch and proper exit codes
|
|
197
|
+
*
|
|
198
|
+
* CODING STANDARDS:
|
|
199
|
+
* - Use async action handlers for asynchronous operations
|
|
200
|
+
* - Provide clear option descriptions and default values
|
|
201
|
+
* - Handle errors gracefully with process.exit()
|
|
202
|
+
* - Log progress and errors to console
|
|
203
|
+
* - Use Commander's .option() and .argument() for inputs
|
|
204
|
+
*
|
|
205
|
+
* AVOID:
|
|
206
|
+
* - Synchronous blocking operations in action handlers
|
|
207
|
+
* - Missing error handling (always use try-catch)
|
|
208
|
+
* - Hardcoded values (use options or environment variables)
|
|
209
|
+
* - Not exiting with appropriate exit codes on errors
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* List all available theme configurations from CSS files
|
|
213
|
+
*/
|
|
214
|
+
const listThemesCommand = new commander.Command("list-themes").description("List all available theme configurations from CSS files configured in style-system.config.yaml").option("-a, --app-path <path>", "App path to read theme config from style-system.config.yaml (e.g., \"apps/my-app\")").action(async (options) => {
|
|
215
|
+
try {
|
|
216
|
+
const result = await require_stdio.createContainer().get(require_stdio.STYLE_SYSTEM_TYPES.ListThemesTool).execute({ appPath: options.appPath });
|
|
217
|
+
if (result.isError) {
|
|
218
|
+
const firstContent = result.content[0];
|
|
219
|
+
const errorText = firstContent?.type === "text" ? firstContent.text : "Unknown error";
|
|
220
|
+
throw new Error(errorText);
|
|
221
|
+
}
|
|
222
|
+
const firstContent = result.content[0];
|
|
223
|
+
print.info(firstContent?.type === "text" ? firstContent.text : "");
|
|
224
|
+
} catch (error) {
|
|
225
|
+
print.error("Error listing themes:", error instanceof Error ? error.message : String(error));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/commands/list-shared-components.ts
|
|
231
|
+
/**
|
|
232
|
+
* List Shared Components CLI Command
|
|
233
|
+
*/
|
|
234
|
+
const listSharedComponentsCommand = new commander.Command("list-shared-components").description("List all shared UI components available in the design system").option("-c, --cursor <cursor>", "Pagination cursor to fetch the next page").action(async (options) => {
|
|
235
|
+
try {
|
|
236
|
+
const result = await require_stdio.createContainer().get(require_stdio.STYLE_SYSTEM_TYPES.ListSharedComponentsTool).execute({ cursor: options.cursor });
|
|
237
|
+
if (result.isError) {
|
|
238
|
+
const firstContent = result.content[0];
|
|
239
|
+
const errorText = firstContent?.type === "text" ? firstContent.text : "Unknown error";
|
|
240
|
+
throw new Error(errorText);
|
|
241
|
+
}
|
|
242
|
+
const firstContent = result.content[0];
|
|
243
|
+
print.info(firstContent?.type === "text" ? firstContent.text : "");
|
|
244
|
+
} catch (error) {
|
|
245
|
+
print.error("Error listing shared components:", error instanceof Error ? error.message : String(error));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/transports/http.ts
|
|
251
|
+
/** Request-scoped modern Streamable HTTP. No legacy HTTP/SSE endpoint is registered. */
|
|
252
|
+
var HttpTransportHandler = class {
|
|
253
|
+
serverFactory;
|
|
254
|
+
host;
|
|
255
|
+
port;
|
|
256
|
+
server = null;
|
|
257
|
+
handler = null;
|
|
258
|
+
constructor(serverFactory, port, host = "localhost") {
|
|
259
|
+
this.serverFactory = serverFactory;
|
|
260
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error("Invalid HTTP port");
|
|
261
|
+
this.port = port;
|
|
262
|
+
this.host = host;
|
|
263
|
+
}
|
|
264
|
+
async start() {
|
|
265
|
+
if (this.server) throw new Error("MCP HTTP transport already running");
|
|
266
|
+
const handler = (0, _modelcontextprotocol_server.createMcpHandler)(this.serverFactory, { legacy: "reject" });
|
|
267
|
+
const serve = (0, _modelcontextprotocol_node.toNodeHandler)(handler);
|
|
268
|
+
const validateHost = (0, _modelcontextprotocol_node.localhostHostValidation)();
|
|
269
|
+
const validateOrigin = (0, _modelcontextprotocol_node.localhostOriginValidation)();
|
|
270
|
+
const loopback = [
|
|
271
|
+
"localhost",
|
|
272
|
+
"127.0.0.1",
|
|
273
|
+
"::1",
|
|
274
|
+
"[::1]"
|
|
275
|
+
].includes(this.host);
|
|
276
|
+
const server = (0, node_http.createServer)((req, res) => {
|
|
277
|
+
if (req.url?.split("?")[0] !== "/mcp" || ![
|
|
278
|
+
"POST",
|
|
279
|
+
"GET",
|
|
280
|
+
"DELETE"
|
|
281
|
+
].includes(req.method ?? "")) {
|
|
282
|
+
res.writeHead(404).end();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (loopback && (!validateHost(req, res) || !validateOrigin(req, res))) return;
|
|
286
|
+
serve(req, res).catch((error) => {
|
|
287
|
+
process.stderr.write(`MCP HTTP request failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
288
|
+
if (!res.headersSent) res.writeHead(500).end();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
try {
|
|
292
|
+
await new Promise((resolve, reject) => {
|
|
293
|
+
server.once("error", reject);
|
|
294
|
+
server.listen(this.port, this.host, () => resolve());
|
|
295
|
+
});
|
|
296
|
+
this.server = server;
|
|
297
|
+
this.handler = handler;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
await handler.close();
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async stop() {
|
|
304
|
+
const server = this.server;
|
|
305
|
+
this.server = null;
|
|
306
|
+
const handler = this.handler;
|
|
307
|
+
this.handler = null;
|
|
308
|
+
await handler?.close();
|
|
309
|
+
if (server) await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
|
310
|
+
}
|
|
311
|
+
getPort() {
|
|
312
|
+
const address = this.server?.address();
|
|
313
|
+
return address && typeof address !== "string" ? address.port : this.port;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/commands/mcp-serve.ts
|
|
318
|
+
/**
|
|
319
|
+
* MCP Serve Command
|
|
320
|
+
*
|
|
321
|
+
* DESIGN PATTERNS:
|
|
322
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
323
|
+
* - Transport abstraction pattern for modern stdio and HTTP
|
|
324
|
+
* - Factory pattern for creating transport handlers
|
|
325
|
+
* - Graceful shutdown pattern with signal handling
|
|
326
|
+
*
|
|
327
|
+
* CODING STANDARDS:
|
|
328
|
+
* - Use async/await for asynchronous operations
|
|
329
|
+
* - Implement proper error handling with try-catch blocks
|
|
330
|
+
* - Handle process signals for graceful shutdown
|
|
331
|
+
* - Provide clear CLI options and help messages
|
|
332
|
+
*
|
|
333
|
+
* AVOID:
|
|
334
|
+
* - Hardcoded configuration values (use CLI options or environment variables)
|
|
335
|
+
* - Missing error handling for transport startup
|
|
336
|
+
* - Not cleaning up resources on shutdown
|
|
337
|
+
*/
|
|
338
|
+
/**
|
|
339
|
+
* Start MCP server with given transport handler
|
|
340
|
+
*/
|
|
341
|
+
async function startServer(handler, bundlerService, devServerPort) {
|
|
342
|
+
await handler.start();
|
|
343
|
+
let releaseProcess = async () => {};
|
|
344
|
+
try {
|
|
345
|
+
releaseProcess = await (0, _agimon_ai_foundation_exec.registerManagedProcess)({
|
|
346
|
+
repositoryPath: process.cwd(),
|
|
347
|
+
serviceName: "style-system-mcp",
|
|
348
|
+
serviceType: "tool",
|
|
349
|
+
environment: process.env["NODE_ENV"] ?? "development",
|
|
350
|
+
pid: process.pid,
|
|
351
|
+
port: devServerPort,
|
|
352
|
+
command: "style-system mcp-serve",
|
|
353
|
+
killOnRelease: true,
|
|
354
|
+
releasePortOnRelease: false,
|
|
355
|
+
metadata: devServerPort ? { devServerPort } : {}
|
|
356
|
+
});
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error("Process registry unavailable:", error);
|
|
359
|
+
}
|
|
360
|
+
const shutdown = async (signal) => {
|
|
361
|
+
console.error(`\nReceived ${signal}, shutting down gracefully...`);
|
|
362
|
+
try {
|
|
363
|
+
if (bundlerService.isServerRunning()) {
|
|
364
|
+
console.error("Stopping bundler dev server...");
|
|
365
|
+
await bundlerService.cleanup();
|
|
366
|
+
}
|
|
367
|
+
await releaseProcess();
|
|
368
|
+
await handler.stop();
|
|
369
|
+
process.exit(0);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error("Error during shutdown:", error);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
376
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* MCP Serve command
|
|
380
|
+
*
|
|
381
|
+
* Note: Design system configuration is now app-specific and read from each app's project.json.
|
|
382
|
+
* No global theme configuration is needed at the server level.
|
|
383
|
+
*/
|
|
384
|
+
const mcpServeCommand = new commander.Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", "Transport type: stdio or http", "stdio").option("-p, --port <port>", "HTTP port (required for HTTP)").option("--host <host>", "HTTP bind host", "localhost").option("--theme-path <path>", "Theme CSS file path relative to workspace root for get-css-classes when app config does not define style-system.themePath").option("--dev", "Start Vite dev server for component hot reload and caching").option("--app-path <path>", "App path for dev server (e.g., apps/agiflow-app)").action(async (options) => {
|
|
385
|
+
try {
|
|
386
|
+
const parsed = zod.z.object({
|
|
387
|
+
type: zod.z.enum(["stdio", "http"]),
|
|
388
|
+
port: zod.z.coerce.number().int().min(0).max(65535).optional(),
|
|
389
|
+
host: zod.z.string().min(1),
|
|
390
|
+
themePath: zod.z.string().optional(),
|
|
391
|
+
dev: zod.z.boolean().optional(),
|
|
392
|
+
appPath: zod.z.string().optional()
|
|
393
|
+
}).parse(options);
|
|
394
|
+
const transportType = parsed.type;
|
|
395
|
+
let devServerPort;
|
|
396
|
+
const bundlerService = await require_stdio.getBundlerServiceFromConfig();
|
|
397
|
+
if (parsed.dev) {
|
|
398
|
+
if (!parsed.appPath) {
|
|
399
|
+
console.error("Error: --app-path is required when using --dev flag");
|
|
400
|
+
console.error(`Example: ${require_stdio.STYLE_SYSTEM_CLI_NAME} mcp-serve --dev --app-path apps/agiflow-app`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
const { url, port } = await bundlerService.startDevServer(parsed.appPath);
|
|
404
|
+
devServerPort = port;
|
|
405
|
+
console.error(`Dev server started at ${url} (port: ${port})`);
|
|
406
|
+
}
|
|
407
|
+
if (transportType === "stdio") {
|
|
408
|
+
const container = require_stdio.createContainer({ defaultThemePath: parsed.themePath });
|
|
409
|
+
await startServer(new require_stdio.StdioTransportHandler(() => require_stdio.createServer(container)), bundlerService, devServerPort);
|
|
410
|
+
} else if (transportType === "http" && parsed.port !== void 0) {
|
|
411
|
+
const container = require_stdio.createContainer({ defaultThemePath: parsed.themePath });
|
|
412
|
+
await startServer(new HttpTransportHandler(() => require_stdio.createServer(container), parsed.port, parsed.host), bundlerService, devServerPort);
|
|
413
|
+
} else {
|
|
414
|
+
console.error("HTTP requires --port");
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error("Failed to start MCP server:", error);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/commands/polish-component.ts
|
|
424
|
+
/**
|
|
425
|
+
* Polish Component Command
|
|
426
|
+
*
|
|
427
|
+
* DESIGN PATTERNS:
|
|
428
|
+
* - Command pattern with Commander for CLI argument parsing
|
|
429
|
+
* - Async/await pattern for asynchronous operations
|
|
430
|
+
* - Error handling pattern with try-catch and proper exit codes
|
|
431
|
+
*
|
|
432
|
+
* CODING STANDARDS:
|
|
433
|
+
* - Use async action handlers for asynchronous operations
|
|
434
|
+
* - Handle errors gracefully with process.exit()
|
|
435
|
+
*
|
|
436
|
+
* AVOID:
|
|
437
|
+
* - Synchronous blocking operations in action handlers
|
|
438
|
+
* - Missing error handling (always use try-catch)
|
|
439
|
+
* - Hardcoded values (use options or environment variables)
|
|
440
|
+
*/
|
|
441
|
+
const polishComponentCommand = new commander.Command("polish-component").description("Polish a UI component using Antigravity with design system context").argument("<file>", "Path to the component .tsx file to polish").option("-i, --instructions <text>", "Specific polishing instructions (e.g., \"improve spacing\")").action(async (file, options) => {
|
|
442
|
+
try {
|
|
443
|
+
const result = await require_stdio.createContainer().get(require_stdio.STYLE_SYSTEM_TYPES.PolishComponentTool).execute({
|
|
444
|
+
filePath: file,
|
|
445
|
+
instructions: options.instructions
|
|
446
|
+
});
|
|
447
|
+
if (result.isError) {
|
|
448
|
+
const firstContent = result.content[0];
|
|
449
|
+
const errorText = firstContent?.type === "text" ? firstContent.text : "Unknown error";
|
|
450
|
+
throw new Error(errorText);
|
|
451
|
+
}
|
|
452
|
+
for (const content of result.content) if (content.type === "text") console.error(content.text);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/cli.ts
|
|
460
|
+
/**
|
|
461
|
+
* MCP Server CLI Entry Point
|
|
462
|
+
*
|
|
463
|
+
* DESIGN PATTERNS:
|
|
464
|
+
* - CLI pattern with Commander for argument parsing
|
|
465
|
+
* - Command pattern for organizing CLI commands
|
|
466
|
+
* - Transport abstraction for multiple communication methods
|
|
467
|
+
*
|
|
468
|
+
* CODING STANDARDS:
|
|
469
|
+
* - Use async/await for asynchronous operations
|
|
470
|
+
* - Handle errors gracefully with try-catch
|
|
471
|
+
* - Log important events for debugging
|
|
472
|
+
* - Register all commands in main entry point
|
|
473
|
+
*
|
|
474
|
+
* AVOID:
|
|
475
|
+
* - Hardcoding command logic in index.ts (use separate command files)
|
|
476
|
+
* - Missing error handling for command execution
|
|
477
|
+
*/
|
|
478
|
+
/**
|
|
479
|
+
* Main entry point
|
|
480
|
+
*/
|
|
481
|
+
async function main() {
|
|
482
|
+
const program = new commander.Command();
|
|
483
|
+
program.name(require_stdio.STYLE_SYSTEM_CLI_NAME).description("MCP server for design system tools").version(require_stdio.STYLE_SYSTEM_VERSION);
|
|
484
|
+
program.addCommand(generateOxlintConfigCommand);
|
|
485
|
+
program.addCommand(getCSSClassesCommand);
|
|
486
|
+
program.addCommand(getUiComponentCommand);
|
|
487
|
+
program.addCommand(listAppComponentsCommand);
|
|
488
|
+
program.addCommand(listThemesCommand);
|
|
489
|
+
program.addCommand(listSharedComponentsCommand);
|
|
490
|
+
program.addCommand(mcpServeCommand);
|
|
491
|
+
program.addCommand(polishComponentCommand);
|
|
492
|
+
await program.parseAsync(process.argv);
|
|
493
|
+
}
|
|
494
|
+
main();
|
|
495
|
+
//#endregion
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "reflect-metadata";
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "reflect-metadata";
|