@bovinphang/frontend-craft 2.5.0 → 2.7.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/.claude-plugin/marketplace.json +7 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +77 -1
- package/CHANGELOG.zh-CN.md +77 -1
- package/CONTRIBUTING.md +15 -10
- package/CONTRIBUTING.zh-CN.md +15 -10
- package/README.md +69 -43
- package/README.openclaw.md +11 -9
- package/README.openclaw.zh-CN.md +11 -9
- package/README.zh-CN.md +69 -43
- package/agents/fec-typescript-reviewer.md +1 -1
- package/commands/fec-doc-sync.md +7 -6
- package/dist/bin/frontend-craft.js +30 -27
- package/docs/example-prompts.md +82 -69
- package/docs/ja-JP/README.md +110 -84
- package/docs/ko-KR/README.md +100 -74
- package/docs/project-structure.md +8 -4
- package/docs/runtimes/antigravity.md +1 -1
- package/docs/runtimes/augment.md +1 -1
- package/docs/runtimes/claude.md +16 -10
- package/docs/runtimes/claude.zh-CN.md +16 -10
- package/docs/runtimes/cline.md +1 -1
- package/docs/runtimes/codebuddy.md +1 -1
- package/docs/runtimes/codex.md +1 -1
- package/docs/runtimes/copilot.md +1 -1
- package/docs/runtimes/cursor.md +1 -1
- package/docs/runtimes/gemini.md +1 -1
- package/docs/runtimes/kilo.md +1 -1
- package/docs/runtimes/openclaw.md +1 -1
- package/docs/runtimes/opencode.md +1 -1
- package/docs/runtimes/qoder.md +1 -1
- package/docs/runtimes/trae.md +1 -1
- package/docs/runtimes/windsurf.md +1 -1
- package/docs/zh-CN/example-prompts.md +82 -69
- package/docs/zh-CN/project-structure.md +8 -4
- package/docs/zh-TW/README.md +88 -62
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/skills/eval_queries.json +179 -56
- package/skills/fec-accessibility-check/SKILL.md +1 -1
- package/skills/fec-alchemy/SKILL.md +74 -0
- package/skills/fec-alchemy/assets/absorption-plan-template.md +47 -0
- package/skills/fec-alchemy/assets/intake-template.md +55 -0
- package/skills/fec-alchemy/assets/review-checklist.md +31 -0
- package/skills/fec-alchemy/references/methodology.md +94 -0
- package/skills/fec-alchemy/references/originality-and-licensing.md +57 -0
- package/skills/fec-backend-requirements-handoff/SKILL.md +45 -0
- package/skills/fec-browser-storage/SKILL.md +1 -1
- package/skills/fec-canvas-threejs/SKILL.md +1 -1
- package/skills/fec-code-review/SKILL.md +11 -3
- package/skills/fec-data-fetching/SKILL.md +15 -15
- package/skills/fec-data-fetching/references/query-patterns.md +27 -1
- package/skills/fec-debug-framework/SKILL.md +14 -19
- package/skills/fec-debug-framework/references/report-template.md +58 -0
- package/skills/fec-doc-sync/SKILL.md +19 -17
- package/skills/fec-drawio-studio/SKILL.md +69 -0
- package/skills/fec-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
- package/skills/fec-drawio-studio/data/brand-icons.json +878 -0
- package/skills/fec-drawio-studio/data/shape-index.json.gz +0 -0
- package/skills/fec-drawio-studio/references/data-residency.md +27 -0
- package/skills/fec-drawio-studio/references/diagram-patterns.md +41 -0
- package/skills/fec-drawio-studio/references/layout-and-export.md +57 -0
- package/skills/fec-drawio-studio/references/troubleshooting.md +29 -0
- package/skills/fec-drawio-studio/references/xml-and-mermaid.md +54 -0
- package/skills/fec-drawio-studio/scripts/brand-symbols.mjs +99 -0
- package/skills/fec-drawio-studio/scripts/diagram-lint.mjs +143 -0
- package/skills/fec-drawio-studio/scripts/diagram-url.mjs +102 -0
- package/skills/fec-drawio-studio/scripts/layout-graph.mjs +107 -0
- package/skills/fec-drawio-studio/scripts/png-embed-fix.mjs +26 -0
- package/skills/fec-drawio-studio/scripts/scan-go-packages.mjs +36 -0
- package/skills/fec-drawio-studio/scripts/scan-js-modules.mjs +52 -0
- package/skills/fec-drawio-studio/scripts/scan-python-classes.mjs +30 -0
- package/skills/fec-drawio-studio/scripts/scan-python-modules.mjs +55 -0
- package/skills/fec-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
- package/skills/fec-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
- package/skills/fec-drawio-studio/scripts/shape-query.mjs +81 -0
- package/skills/fec-drawio-studio/scripts/studio-core.mjs +139 -0
- package/skills/fec-drawio-studio/styles/built-in/corporate.json +49 -0
- package/skills/fec-drawio-studio/styles/built-in/default.json +49 -0
- package/skills/fec-drawio-studio/styles/built-in/handdrawn.json +49 -0
- package/skills/fec-drawio-studio/styles/schema.json +112 -0
- package/skills/fec-e2e-testing/SKILL.md +5 -3
- package/skills/fec-e2e-testing/references/e2e-ci-reporting.md +62 -0
- package/skills/fec-e2e-testing/references/e2e-special-scenarios.md +52 -0
- package/skills/fec-e2e-testing/references/e2e-visual-regression.md +60 -0
- package/skills/fec-form-handling/SKILL.md +11 -11
- package/skills/fec-form-handling/references/advanced-form-patterns.md +34 -5
- package/skills/fec-image-generation/SKILL.md +56 -0
- package/skills/fec-image-generation/references/artifact-routing.md +80 -0
- package/skills/fec-image-generation/references/diagram-workflows.md +43 -0
- package/skills/fec-image-generation/references/png-qa-autofix.md +30 -0
- package/skills/fec-image-generation/scripts/png-qa.mjs +431 -0
- package/skills/fec-implement-from-design/SKILL.md +9 -2
- package/skills/fec-legacy-to-modern-migration/SKILL.md +29 -163
- package/skills/fec-legacy-to-modern-migration/references/migration-execution-checklist.md +110 -0
- package/skills/fec-legacy-to-modern-migration/references/migration-strategy-and-mapping.md +48 -0
- package/skills/fec-legacy-web-standard/SKILL.md +1 -1
- package/skills/fec-list-virtualization/SKILL.md +3 -3
- package/skills/fec-list-virtualization/references/virtualization-patterns.md +32 -4
- package/skills/fec-motion-interaction/SKILL.md +7 -0
- package/skills/fec-nextjs-project-standard/SKILL.md +7 -1
- package/skills/fec-performance-optimization/SKILL.md +9 -1
- package/skills/fec-performance-optimization/references/framework-performance-patterns.md +38 -0
- package/skills/fec-performance-optimization/references/report-template.md +59 -0
- package/skills/fec-pwa-implementation/SKILL.md +3 -4
- package/skills/fec-react-project-standard/SKILL.md +12 -4
- package/skills/fec-react-project-standard/references/react-performance-patterns.md +35 -0
- package/skills/fec-react-project-standard/references/react-project-structure.md +197 -0
- package/skills/fec-react-project-standard/references/react-quality-patterns.md +131 -0
- package/skills/fec-react-project-standard/references/react-runtime-patterns.md +293 -0
- package/skills/fec-refactor-clean/SKILL.md +4 -1
- package/skills/fec-refactor-clean/references/report-template.md +42 -0
- package/skills/fec-responsive-layout/references/responsive-layout-patterns.md +11 -0
- package/skills/fec-security-review/SKILL.md +3 -3
- package/skills/fec-source-driven-development/SKILL.md +26 -0
- package/skills/fec-state-management/SKILL.md +1 -1
- package/skills/fec-storybook-component-doc/SKILL.md +2 -2
- package/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +5 -1
- package/skills/fec-typescript-project-standard/SKILL.md +54 -0
- package/skills/{fec-typescript-type-safety → fec-typescript-project-standard}/references/type-safety-patterns.md +1 -1
- package/skills/{fec-typescript-type-safety/SKILL.md → fec-typescript-project-standard/references/type-safety.md} +1 -10
- package/skills/fec-ui-design/SKILL.md +26 -8
- package/skills/fec-ui-design/data/style-archetypes.json +40 -0
- package/skills/fec-ui-design/data/ux-quality-rules.json +35 -0
- package/skills/fec-ui-design/references/design-intelligence.md +66 -6
- package/skills/fec-ui-design/references/pre-delivery-checklist.md +6 -0
- package/skills/fec-ui-design/scripts/design-system.mjs +119 -1
- package/skills/fec-validation-fix/SKILL.md +5 -2
- package/skills/fec-validation-fix/references/report-template.md +43 -0
- package/skills/fec-vue3-project-standard/SKILL.md +12 -3
- package/skills/fec-vue3-project-standard/references/vue3-composables-routing.md +163 -0
- package/skills/fec-vue3-project-standard/references/vue3-performance-patterns.md +13 -0
- package/skills/fec-vue3-project-standard/references/vue3-project-structure.md +177 -0
- package/skills/fec-vue3-project-standard/references/vue3-state-api-quality.md +216 -0
- package/skills/fec-web-video-presentation/SKILL.md +61 -0
- package/skills/fec-web-video-presentation/data/starter-themes.json +59 -0
- package/skills/fec-web-video-presentation/references/chapter-craft.md +37 -0
- package/skills/fec-web-video-presentation/references/content-plan.md +38 -0
- package/skills/fec-web-video-presentation/references/recording-and-audio.md +33 -0
- package/skills/fec-web-video-presentation/references/theme-system.md +36 -0
- package/skills/fec-web-video-presentation/scripts/create-video-presentation.mjs +430 -0
- package/skills/fec-web-workers/SKILL.md +3 -4
- package/skills/metadata.json +381 -168
- package/skills/relations.json +136 -25
- package/templates/shared/rules/fec-ci-cd.md +20 -108
- package/templates/shared/rules/fec-design-system.md +47 -223
- package/templates/shared/rules/fec-error-handling.md +35 -179
- package/templates/shared/rules/fec-i18n.md +43 -91
- package/templates/shared/rules/fec-refactoring.md +4 -0
- package/templates/shared/rules/fec-rendering-patterns.md +28 -171
- package/templates/shared/rules/fec-responsive-design.md +34 -211
- package/templates/shared/rules/fec-state-management.md +41 -144
- package/templates/shared/rules/fec-typescript.md +47 -290
- package/skills/fec-e2e-testing/references/e2e-advanced.md +0 -176
- package/skills/fec-react-project-standard/references/react-project-details.md +0 -642
- package/skills/fec-vue3-project-standard/references/vue3-project-details.md +0 -547
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://github.com/bovinphang/frontend-craft/stargazers)
|
|
10
10
|
[](https://github.com/bovinphang/frontend-craft/actions/workflows/ci.yml)
|
|
11
|
-
[](https://www.npmjs.com/package/frontend-craft)
|
|
11
|
+
[](https://www.npmjs.com/package/@bovinphang/frontend-craft)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|

|
|
14
14
|

|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
各运行时的路径与注意事项详见 [`docs/runtimes/`](docs/runtimes/)。
|
|
34
34
|
|
|
35
|
-
它将 **13 个专业 agent**、**
|
|
35
|
+
它将 **13 个专业 agent**、**45 个自动激活 skill**、**8 个斜杠命令**、**5 个事件驱动 hook**、面向 6 个设计工具端点的 **MCP 集成**以及一整套**规则库**打包为一个可安装单元。运行一条命令,团队里的每一次 AI 会话都将以相同的方式编写 React、Vue、Next.js 或 Nuxt——类型安全、可访问、安全、一致。
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
| 痛点 | frontend-craft 的解法 |
|
|
42
42
|
| --------------------------------------------- | --------------------------------------------------------------------- |
|
|
43
|
-
| AI 助手写出的前端代码风格不一、缺类型、不安全 | **
|
|
43
|
+
| AI 助手写出的前端代码风格不一、缺类型、不安全 | **45 个 skill** 将团队规范编码为可自动激活的工作流 |
|
|
44
44
|
| 每款 AI 工具都有自己的插件格式 | **一条 CLI 命令** 把相同的规则、agent 和 hook 安装到 15 个运行时 |
|
|
45
45
|
| 设计稿到代码的交接总有信息损失 | **MCP 集成** 从 Figma、Figma Desktop、Sketch、MasterGo、Pixso、墨刀引入更完整设计上下文 |
|
|
46
46
|
| 代码评审随意、浅层 | **13 个 agent** 输出分级报告:代码、安全、无障碍、性能、TS、UI 还原度 |
|
|
@@ -53,45 +53,59 @@
|
|
|
53
53
|
|
|
54
54
|
需要 **Node.js 22+**。完整支持 **Windows、macOS 和 Linux**(所有钩子和脚本均使用 Node.js 实现)。
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### 方式一:全局安装 fec CLI(推荐)
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
59
|
+
# 1. 全局安装 fec 命令
|
|
60
|
+
npm install -g @bovinphang/frontend-craft@latest
|
|
61
|
+
|
|
62
|
+
# 2. 进入你的前端项目
|
|
60
63
|
cd your-project
|
|
61
|
-
fec init
|
|
62
|
-
fec init codex # 初始化指定运行时
|
|
63
|
-
fec init claude # 初始化 Claude Code
|
|
64
|
-
```
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
# 3. 交互选择要接入的 AI 运行时
|
|
66
|
+
fec setup
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
# 4. 接入当前项目
|
|
69
|
+
fec setup codex
|
|
70
|
+
fec setup claude
|
|
71
|
+
fec setup all
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
# 5. 全局接入,适用于所有项目
|
|
74
|
+
fec setup codex --global
|
|
75
|
+
fec setup claude --global
|
|
76
|
+
fec setup all --global
|
|
77
|
+
|
|
78
|
+
# 6. 预览 / 查询
|
|
79
|
+
fec install --all --dry-run --global
|
|
80
|
+
fec list
|
|
72
81
|
```
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
`npm install -g` 只是安装 `fec` 终端命令。`fec setup` 是终端里的 CLI 项目接入命令,不是 AI 助手内的 `/fec-init` 斜杠命令。在交互式终端中,不带参数的 `fec setup` 会先让你选择 runtime;`fec setup <runtime>` 和 `fec setup all` 会默认接入当前项目。只有传入 `--global` 时,才会接入对应 AI 工具的全局配置目录,适用于所有项目。
|
|
75
84
|
|
|
76
|
-
###
|
|
85
|
+
### 方式二:用 npx 临时运行(免全局安装 fec)
|
|
77
86
|
|
|
78
87
|
```bash
|
|
79
|
-
#
|
|
80
|
-
npx frontend-craft@latest
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
npx frontend-craft@latest install --
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
npx frontend-craft@latest
|
|
88
|
+
# 1. 交互向导
|
|
89
|
+
npx @bovinphang/frontend-craft@latest
|
|
90
|
+
|
|
91
|
+
# 2. 接入当前项目
|
|
92
|
+
npx @bovinphang/frontend-craft@latest install --local codex
|
|
93
|
+
npx @bovinphang/frontend-craft@latest install --local claude
|
|
94
|
+
npx @bovinphang/frontend-craft@latest install --all --local
|
|
95
|
+
|
|
96
|
+
# 3. 全局接入,适用于所有项目
|
|
97
|
+
npx @bovinphang/frontend-craft@latest install --global codex
|
|
98
|
+
npx @bovinphang/frontend-craft@latest install --global claude
|
|
99
|
+
npx @bovinphang/frontend-craft@latest install --all --global
|
|
100
|
+
|
|
101
|
+
# 4. 预览 / 查询
|
|
102
|
+
npx @bovinphang/frontend-craft@latest install --all --dry-run --global
|
|
103
|
+
npx @bovinphang/frontend-craft@latest list
|
|
90
104
|
```
|
|
91
105
|
|
|
92
|
-
|
|
106
|
+
不想全局安装 `fec` 命令时使用 `npx`。不带参数会进入交互向导:先选择一个或多个运行时,再决定接入全局还是当前项目。脚本化安装全部 runtime 时使用 `install --all --local` 或 `install --all --global`,不要写成 `install all`。CI / 脚本场景请始终带上 `--global` / `-g` 或 `--local` / `-l`;非 TTY 且未指定时,CLI 默认安装到 `claude --global`。
|
|
93
107
|
|
|
94
|
-
###
|
|
108
|
+
### 方式三:Claude Code Marketplace
|
|
95
109
|
|
|
96
110
|
对 Claude Code 用户,**Claude Code Marketplace** 是推荐的单一来源安装方式。只有在需要跨运行时安装、脚本化/离线复制文件,或非 Marketplace 环境时,才建议对 Claude 使用 CLI。
|
|
97
111
|
|
|
@@ -133,7 +147,7 @@ npx frontend-craft@latest list
|
|
|
133
147
|
```text
|
|
134
148
|
你:「合并前请评审我最近的改动。重点看架构、类型安全、渲染行为、样式、无障碍和缺失测试。」
|
|
135
149
|
你:「先不要写代码,请规划账号账单功能:路由结构、组件边界、数据流、状态归属、校验流程和上线风险。」
|
|
136
|
-
|
|
150
|
+
你:「请构建多步注册表单:根据项目框架选择表单与 schema 校验方案,包含文件上传、条件字段、异步校验和可访问错误提示。」
|
|
137
151
|
你:「请按 Figma 节点 123:456 实现 UI。复用现有 design token 和组件,匹配间距与响应式状态,并说明假设。」
|
|
138
152
|
你:「`/fec-refactor-clean` 请清理这个模块里的死代码。」
|
|
139
153
|
```
|
|
@@ -155,13 +169,13 @@ npx frontend-craft@latest list
|
|
|
155
169
|
| `/fec-tdd` | 红 → 绿 → 重构的前端 TDD 循环 | — |
|
|
156
170
|
| `/fec-debug` | 前端问题诊断与修复:构建失败、运行时错误、UI 异常、接口问题 | `debug-*.md` |
|
|
157
171
|
| `/fec-refactor-clean` | 分类并安全清理死代码、未使用导出、样式和依赖 | `refactor-clean-*.md` |
|
|
158
|
-
| `/fec-doc-sync` | 同步 README、
|
|
172
|
+
| `/fec-doc-sync` | 同步 README、docs、环境变量、脚本、API/路由说明和部署文档 | — |
|
|
159
173
|
|
|
160
174
|
### 技能(Skills,自动激活)
|
|
161
175
|
|
|
162
176
|
技能是根据文件模式、框架或任务上下文**自动激活**的工作流定义,编码了评审维度、输出约定和报告格式。
|
|
163
177
|
|
|
164
|
-
|
|
178
|
+
下方按使用场景分组列出全部自动激活的技能,便于快速找到项目规范、实现、测试、评审、设计、迁移、项目演进和文档维护能力。
|
|
165
179
|
|
|
166
180
|
**项目规范** — 检测到对应框架时自动生效:
|
|
167
181
|
|
|
@@ -173,22 +187,23 @@ npx frontend-craft@latest list
|
|
|
173
187
|
| `fec-nuxt-project-standard` | Nuxt 3 SSR/SSG、组合式 API、数据获取、中间件 |
|
|
174
188
|
| `fec-vite-project-standard` | Vite 配置、环境变量安全、HMR、开发代理、构建优化 |
|
|
175
189
|
| `fec-monorepo-project-standard` | pnpm workspace、Turborepo、Nx 结构与任务编排 |
|
|
190
|
+
| `fec-typescript-project-standard` | TypeScript 配置、公开类型、声明文件、DTO 和泛型 |
|
|
176
191
|
|
|
177
192
|
**实现能力** — 构建特定前端能力时激活:
|
|
178
193
|
|
|
179
194
|
| 技能 | 范围 |
|
|
180
195
|
| ------------------------- | ---------------------------------------------------------- |
|
|
181
|
-
| `fec-data-fetching` |
|
|
196
|
+
| `fec-data-fetching` | 服务端状态获取、缓存、失效、SSR、无限加载 |
|
|
182
197
|
| `fec-api-integration` | 类型化 API client、鉴权刷新、上传、实时集成 |
|
|
183
198
|
| `fec-state-management` | 状态归属、store 选型、URL 状态、服务端/表单/本地状态边界 |
|
|
184
|
-
| `fec-form-handling` |
|
|
199
|
+
| `fec-form-handling` | 跨框架表单选型、schema 校验、动态字段、上传 |
|
|
185
200
|
| `fec-browser-storage` | localStorage / sessionStorage / IndexedDB / Cookies 选型 |
|
|
186
201
|
| `fec-route-protection` | React Router、Next.js、Vue Router、Nuxt 的登录态与权限路由 |
|
|
187
202
|
| `fec-pwa-implementation` | manifest、Service Worker、离线缓存、安装提示 |
|
|
188
203
|
| `fec-web-workers` | Web Worker、Transferable、Comlink、Worker 池 |
|
|
189
204
|
| `fec-canvas-threejs` | Canvas 2D、Three.js、React Three Fiber、WebGL |
|
|
190
205
|
| `fec-svg-animation` | CSS / Framer Motion / GSAP SVG 动画与 reduced-motion |
|
|
191
|
-
| `fec-list-virtualization` |
|
|
206
|
+
| `fec-list-virtualization` | 按框架选型的大列表虚拟滚动、测量、网格和无限滚动 |
|
|
192
207
|
|
|
193
208
|
**测试** — 规划或编写前端测试覆盖时激活:
|
|
194
209
|
|
|
@@ -204,7 +219,7 @@ npx frontend-craft@latest list
|
|
|
204
219
|
| 技能 | 范围 |
|
|
205
220
|
| ------------------------------ | ----------------------------------------------------- |
|
|
206
221
|
| `fec-code-review` | 架构、类型、渲染、样式、可访问性评审 |
|
|
207
|
-
| `fec-
|
|
222
|
+
| `fec-debug-framework` | 系统化诊断构建、运行时、UI 和 API/数据问题 |
|
|
208
223
|
| `fec-security-review` | XSS、CSRF、敏感数据泄露、输入校验 |
|
|
209
224
|
| `fec-accessibility-check` | WCAG 2.2、键盘、焦点、触控和屏幕阅读器行为 |
|
|
210
225
|
| `fec-dependency-upgrade` | 依赖升级、lockfile 评审、CVE 修复和迁移验证 |
|
|
@@ -216,11 +231,14 @@ npx frontend-craft@latest list
|
|
|
216
231
|
|
|
217
232
|
| 技能 | 范围 |
|
|
218
233
|
| ----------------------------- | -------------------------------------------------------- |
|
|
219
|
-
| `fec-ui-design` | UI
|
|
234
|
+
| `fec-ui-design` | 产品语境 UI 方向、反模板设计拨盘、媒体策略、状态、视觉 QA |
|
|
235
|
+
| `fec-image-generation` | 图表、图片生成/编辑、视觉资产、PNG 自检和修复循环 |
|
|
236
|
+
| `fec-drawio-studio` | 可编辑 draw.io / diagrams.net 技术图、XML/Mermaid/CSV URL 交付、形状检索、自动布局和代码结构图 |
|
|
237
|
+
| `fec-web-video-presentation` | 将文章、口播稿、课程和 demo 转成可录屏的 16:9 步进网页演示 |
|
|
220
238
|
| `fec-tailwind-design-system` | Tailwind token、主题扩展、组件变体、class 治理和暗色模式 |
|
|
221
239
|
| `fec-responsive-layout` | 移动优先布局、容器查询、数据密集型响应式界面 |
|
|
222
|
-
| `fec-motion-interaction` |
|
|
223
|
-
| `fec-implement-from-design` |
|
|
240
|
+
| `fec-motion-interaction` | 场景化动效强度、页面转场、滚动动画、reduced-motion |
|
|
241
|
+
| `fec-implement-from-design` | 基于设计工具、截图或分区级视觉参考实现 UI |
|
|
224
242
|
| `fec-storybook-component-doc` | Storybook 组件文档、设计系统呈现、隔离状态预览 |
|
|
225
243
|
|
|
226
244
|
**遗留迁移** — 现代化迁移时激活:
|
|
@@ -228,13 +246,20 @@ npx frontend-craft@latest list
|
|
|
228
246
|
| 技能 | 范围 |
|
|
229
247
|
| -------------------------------- | ---------------------------------------------- |
|
|
230
248
|
| `fec-legacy-web-standard` | JS + jQuery + HTML 遗留项目的开发与维护规范 |
|
|
231
|
-
| `fec-legacy-to-modern-migration` |
|
|
249
|
+
| `fec-legacy-to-modern-migration` | 遗留前端现代化、目标栈识别与分阶段迁移流程 |
|
|
250
|
+
|
|
251
|
+
**项目演进** — 吸收参考系统并原创化落到当前项目时激活:
|
|
252
|
+
|
|
253
|
+
| 技能 | 范围 |
|
|
254
|
+
| ------------- | ------------------------------------------ |
|
|
255
|
+
| `fec-alchemy` | 将参考系统能力原创化吸收到当前项目原生体系 |
|
|
232
256
|
|
|
233
257
|
**文档维护** — 文档维护时激活:
|
|
234
258
|
|
|
235
259
|
| 技能 | 范围 |
|
|
236
260
|
| ------------------------------- | ------------------------------------------ |
|
|
237
|
-
| `fec-
|
|
261
|
+
| `fec-backend-requirements-handoff` | 前端向后端交接 UI 数据需求、用户动作、状态、业务规则与待确认问题 |
|
|
262
|
+
| `fec-doc-sync` | 让前端文档与代码、配置、脚本、路由、API、环境变量和部署事实保持同步 |
|
|
238
263
|
| `fec-source-driven-development` | 以项目事实和官方来源验证版本敏感的前端决策 |
|
|
239
264
|
|
|
240
265
|
### 代理(Agents)
|
|
@@ -365,11 +390,12 @@ $env:MODAO_TOKEN = "your-modao-token"
|
|
|
365
390
|
每一次评审、分析和评估都会输出带时间戳的 Markdown 报告到 `reports/`,可作为审计记录和 PR 交付物。
|
|
366
391
|
|
|
367
392
|
<details>
|
|
368
|
-
<summary>查看全部
|
|
393
|
+
<summary>查看全部 16 种报告类型</summary>
|
|
369
394
|
|
|
370
395
|
| 报告类型 | 文件名模式 | 来源 |
|
|
371
396
|
| -------------- | -------------------------------------------- | ------------------------------------------------------------------- |
|
|
372
397
|
| 代码审查 | `code-review-YYYY-MM-DD-HHmmss.md` | `/fec-review`、`fec-code-review`、`fec-code-reviewer` |
|
|
398
|
+
| 调试诊断 | `debug-YYYY-MM-DD-HHmmss.md` | `/fec-debug`、`fec-debug-framework`、`fec-debugger` |
|
|
373
399
|
| TS/JS 专项评审 | `typescript-review-YYYY-MM-DD-HHmmss.md` | `fec-typescript-reviewer` |
|
|
374
400
|
| 安全审查 | `security-review-YYYY-MM-DD-HHmmss.md` | `fec-security-review`、`fec-security-reviewer` |
|
|
375
401
|
| 无障碍检查 | `accessibility-review-YYYY-MM-DD-HHmmss.md` | `fec-accessibility-check` |
|
|
@@ -393,7 +419,7 @@ $env:MODAO_TOKEN = "your-modao-token"
|
|
|
393
419
|
|
|
394
420
|
## 更新与移除
|
|
395
421
|
|
|
396
|
-
`fec` 是 `frontend-craft` 的短命令。如果没有全局安装 `fec`,可以把同样参数接在 `npx frontend-craft@latest` 后使用,例如 `npx frontend-craft@latest update`。
|
|
422
|
+
`fec` 是 `frontend-craft` 的短命令。如果没有全局安装 `fec`,可以把同样参数接在 `npx @bovinphang/frontend-craft@latest` 后使用,例如 `npx @bovinphang/frontend-craft@latest update`。
|
|
397
423
|
|
|
398
424
|
### 更新
|
|
399
425
|
|
|
@@ -437,7 +463,7 @@ npx skills check # 预览可用更新
|
|
|
437
463
|
|
|
438
464
|
| CLI | 安装内容 |
|
|
439
465
|
| -------------------- | --------------------------------------------- |
|
|
440
|
-
| `npx frontend-craft` | 技能 + 运行时专属代理、命令、钩子、规则、模板 |
|
|
466
|
+
| `npx @bovinphang/frontend-craft` | 技能 + 运行时专属代理、命令、钩子、规则、模板 |
|
|
441
467
|
| `npx skills` | 仅技能(适用于已有 Skills CLI 工作流) |
|
|
442
468
|
|
|
443
469
|
关闭遥测:`DISABLE_TELEMETRY=1`。详见 [skills.sh CLI 文档](https://skills.sh/docs/cli)。
|
package/commands/fec-doc-sync.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fec-doc-sync
|
|
3
|
-
description:
|
|
3
|
+
description: 从代码和项目事实源同步 README、docs、环境变量、脚本、API/路由/组件说明和部署说明。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
按 `fec-doc-sync` 同步前端工程文档。复杂多语言更新可委托 **`fec-doc-updater`**。
|
|
7
7
|
|
|
8
8
|
## 执行步骤
|
|
9
9
|
|
|
10
|
-
1. 读取事实来源:`package.json
|
|
11
|
-
2. 比对 README、
|
|
12
|
-
3.
|
|
13
|
-
4.
|
|
14
|
-
5.
|
|
10
|
+
1. 读取事实来源:`package.json`、lockfile、config、`.env.example`、路由、API clients/server routes、types/schemas、组件、测试、CI、build/deploy config。
|
|
11
|
+
2. 比对 README、docs、ADR、changelog、example prompts、setup/deploy/env docs。
|
|
12
|
+
3. 更新稳定的命令、环境变量、路由/API 行为、组件说明、部署步骤、版本约束和迁移说明。
|
|
13
|
+
4. 多语言文档保持关键信息一致。
|
|
14
|
+
5. 当目标仓库本身是 skill/agent/command 分发仓库时,也同步能力表、metadata、runtime docs 和 marketplace 描述。
|
|
15
|
+
6. 运行文档一致性、typecheck、测试、构建或相关打包检查。
|
|
15
16
|
|
|
16
17
|
## 输出要求
|
|
17
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import m from"node:fs";import d from"node:path";import
|
|
3
|
-
`,"utf8")}function
|
|
2
|
+
import m from"node:fs";import d from"node:path";import It from"node:readline";import j from"node:path";import z from"node:fs";import k from"node:fs";import v from"node:path";import Vn from"node:crypto";import qn from"node:os";var B;function Ge({baseDir:e,cwd:n=process.cwd(),mode:t,packageVersion:o,runtime:i,isGlobal:r}){let s=v.join(e,"frontend-craft.manifest.json");B={baseDir:v.resolve(e),cwd:v.resolve(n),homeDir:v.resolve(qn.homedir()),mode:t,packageVersion:o,runtime:i,scope:r?"global":"local",manifestPath:s,previousFiles:Bn(s),writtenFiles:new Map,skippedFiles:new Set}}function Le(){let e=B;if(B=void 0,!e)return;for(let[o,i]of e.previousFiles)e.skippedFiles.has(o)&&!e.writtenFiles.has(o)&&e.writtenFiles.set(o,i);let n=[...e.writtenFiles.entries()].map(([o,i])=>Be(o,i)).sort((o,i)=>o.path.localeCompare(i.path)),t={packageVersion:e.packageVersion,runtime:e.runtime,scope:e.scope,installedAt:new Date().toISOString(),files:n};f(v.dirname(e.manifestPath)),k.writeFileSync(e.manifestPath,`${JSON.stringify(t,null,2)}
|
|
3
|
+
`,"utf8")}function Ue(){B=void 0}function ae(e){return v.join(e,"frontend-craft.manifest.json")}function Ie(e){if(k.existsSync(e))try{let n=JSON.parse(k.readFileSync(e,"utf8"));return{...n,files:(n.files??[]).map(t=>({...t,path:ke(t.path)}))}}catch{return}}function Bn(e){let n=Ie(e);return new Map((n?.files??[]).map(t=>[qe(t.root??"baseDir",ke(t.path)),t.hash]))}function We(e){return Vn.createHash("sha256").update(e).digest("hex")}function ve(e){return We(k.readFileSync(e))}function Ve(e){let n=B;if(!n)return;let t=v.resolve(e),o=[["baseDir",n.baseDir],["cwd",n.cwd],["home",n.homeDir]];for(let[i,r]of o){let s=v.relative(r,t);if(s&&!s.startsWith("..")&&!v.isAbsolute(s))return qe(i,ke(s))}}function ke(e){return e.split(v.sep).join("/")}function qe(e,n){return`${e}:${n}`}function Be(e,n){let t=/^(baseDir|cwd|home):(.*)$/.exec(e);if(!t)return{path:e,hash:n};let o=t[1];return{...o==="baseDir"?{}:{root:o},path:t[2],hash:n}}function ze(e){let n=B,t=Ve(e);if(!n||!t||n.mode!=="update"||!k.existsSync(e))return!1;let o=n.previousFiles.get(t);return!o||ve(e)===o?!1:(n.skippedFiles.add(t),console.log(`Skipped modified file: ${Be(t,o).path}`),!0)}function He(e,n){let t=B,o=Ve(e);!t||!o||t.writtenFiles.set(o,We(n))}function f(e){k.existsSync(e)||k.mkdirSync(e,{recursive:!0})}function D(e,n){if(ze(n))return;f(v.dirname(n));let t=k.readFileSync(e);k.copyFileSync(e,n),He(n,t)}function p(e,n,t={}){if(k.existsSync(e)){f(n);for(let o of k.readdirSync(e)){let i=v.join(e,o),r=o;if(t.filter&&!t.filter(r))continue;let s=v.join(n,o);k.statSync(i).isDirectory()?p(i,s,t):D(i,s)}}}function C(e){return k.readFileSync(e,"utf8")}function y(e,n){ze(e)||(f(v.dirname(e)),k.writeFileSync(e,n,"utf8"),He(e,n))}function zn(e,n){let t=j.resolve(e);return n.replaceAll("${CLAUDE_PLUGIN_ROOT}",t).replaceAll("${FRONTEND_CRAFT_ROOT}",t)}function Hn(e){let n=j.join(e,"hooks","hooks.json"),t=C(n);return zn(e,t)}async function Ke(e){let{pluginRoot:n,baseDir:t,dryRun:o,cwd:i,isGlobal:r}=e;if(o){console.log(`[dry-run] would install Claude Code files into ${t}`);return}f(t),p(j.join(n,"skills"),j.join(t,"skills")),p(j.join(n,"agents"),j.join(t,"agents")),p(j.join(n,"commands"),j.join(t,"commands")),r||p(j.join(n,"templates","shared","rules"),j.join(t,"rules"));let s=j.join(n,"templates","claude");if(z.existsSync(s))for(let l of z.readdirSync(s)){let c=j.join(t,l);z.existsSync(c)||D(j.join(s,l),c)}if(y(j.join(t,"hooks.json"),Hn(n)),!r&&z.existsSync(j.join(n,".mcp.json"))){let l=j.join(i,".mcp.json");z.existsSync(l)||D(j.join(n,".mcp.json"),l)}let a=j.join(n,".claude-plugin","plugin.json");if(!r&&z.existsSync(a)){f(j.join(i,".claude-plugin")),D(a,j.join(i,".claude-plugin","plugin.json"));let l=j.join(n,".claude-plugin","marketplace.json");z.existsSync(l)&&D(l,j.join(i,".claude-plugin","marketplace.json"))}}import $ from"node:path";import se from"node:fs";import Kn from"node:os";function Je(e,n){let t=e.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);if(!t)throw new Error(`no frontmatter: ${n}`);let o=t[1],i=t[2].trim().replace(/\.claude\/rules/g,".codex/rules").replace(/\bCLAUDE\.md\b/g,"AGENTS.md"),r=o.match(/^name:\s*(.+)$/m);if(!r)throw new Error(`no name in frontmatter: ${n}`);let s=r[1].trim(),a=o.match(/^description:\s*(.+)$/m),l=a?a[1].trim():"";return`name = "${s}"
|
|
4
4
|
description = "${(u=>u.replace(/\\/g,"\\\\").replace(/"/g,'\\"'))(l)}"
|
|
5
5
|
|
|
6
6
|
model = "gpt-5.4"
|
|
@@ -9,50 +9,52 @@ model_reasoning_effort = "high"
|
|
|
9
9
|
developer_instructions = """
|
|
10
10
|
`+i+`
|
|
11
11
|
"""
|
|
12
|
-
`}async function
|
|
12
|
+
`}async function Ye(e){let{pluginRoot:n,baseDir:t,cwd:o,dryRun:i,isGlobal:r}=e,s=$.join(n,"agents"),a=$.join(t,"agents"),l=$.join(t,"rules"),c=r?$.join(Kn.homedir(),".agents","skills"):$.join(o,".agents","skills");if(i){console.log(`[dry-run] codex: ${t}, agents -> ${a}, skills -> ${c}`);return}f(a),f(c);for(let S of se.readdirSync(s)){if(!S.endsWith(".md"))continue;let M=C($.join(s,S)),I=Je(M,S);y($.join(a,S.replace(/\.md$/,".toml")),I)}p($.join(n,"skills"),c);let u=$.join(n,"templates","shared","rules");!r&&se.existsSync(u)&&p(u,l);let g=$.join(n,"templates","codex","AGENTS.md"),R=$.join(n,"templates","codex","config.toml");if(!r&&se.existsSync(g)){let S=$.join(o,"AGENTS.md");se.existsSync(S)||D(g,S)}se.existsSync(R)&&D(R,$.join(t,"config.toml"))}import Y from"node:path";import Qe from"node:fs";async function Xe(e){let{pluginRoot:n,baseDir:t,dryRun:o,isGlobal:i}=e;if(o){console.log(`[dry-run] cursor -> ${t}`);return}f(t);let r=Y.join(t,"skills");p(Y.join(n,"skills"),r);let s=Y.join(n,"templates","shared","rules"),a=Y.join(t,"rules");if(!i&&Qe.existsSync(s)){f(a);for(let l of Qe.readdirSync(s)){if(!l.endsWith(".md"))continue;let c=C(Y.join(s,l)),u=l.replace(/\.md$/,""),g=`---
|
|
13
13
|
description: Frontend Craft - ${u}
|
|
14
14
|
globs: "**/*.{ts,tsx,vue,js,jsx,css,scss,less,html}"
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
`+c+`
|
|
18
|
-
`;y(Y.join(a,`${u}.mdc`),g)}}}import E from"node:path";import
|
|
18
|
+
`;y(Y.join(a,`${u}.mdc`),g)}}}import E from"node:path";import we from"node:fs";async function Ze(e){let{pluginRoot:n,baseDir:t,dryRun:o,isGlobal:i}=e;if(o){console.log(`[dry-run] windsurf -> ${t}`);return}f(t);let r=E.join(t,"workflows"),s=E.join(t,"rules");f(r);let a=E.join(n,"commands");for(let c of we.readdirSync(a)){if(!c.endsWith(".md"))continue;let u=C(E.join(a,c));y(E.join(r,c),u)}let l=E.join(n,"templates","shared","rules");if(!i&&we.existsSync(l)){f(s);for(let c of we.readdirSync(l)){if(!c.endsWith(".md"))continue;let u=C(E.join(l,c));y(E.join(s,c),u)}}p(E.join(n,"skills"),E.join(t,"skills"))}import A from"node:path";import ie from"node:fs";async function ce(e){let{pluginRoot:n,baseDir:t,dryRun:o}=e,i=A.join(t,"command");if(o){console.log(`[dry-run] opencode/kilo commands -> ${i}`);return}f(i);let r=A.join(n,"commands");for(let c of ie.readdirSync(r)){if(!c.endsWith(".md"))continue;let u=C(A.join(r,c));y(A.join(i,c),u)}let s=A.join(t,"skills");f(s);let a=A.join(n,"skills");for(let c of ie.readdirSync(a)){let u=A.join(a,c);if(!ie.statSync(u).isDirectory())continue;let g=A.join(u,"SKILL.md");if(ie.existsSync(g)){let R=A.join(s,c);p(u,R)}}let l=A.join(t,"opencode.jsonc");e.runtime==="opencode"&&!ie.existsSync(l)&&y(l,`{
|
|
19
19
|
"$schema": "https://opencode.ai/config.json",
|
|
20
20
|
"permissions": { "bash": "allow" }
|
|
21
21
|
}
|
|
22
|
-
`)}async function
|
|
23
|
-
`];if(
|
|
22
|
+
`)}async function en(e){return ce(e)}async function nn(e){return ce(e)}import re from"node:path";import tn from"node:fs";async function on(e){let{pluginRoot:n,baseDir:t,cwd:o,dryRun:i,isGlobal:r}=e;if(i){console.log(`[dry-run] gemini -> ${t}`);return}f(t);let s=re.join(t,"extensions","frontend-craft");f(s),p(re.join(n,"skills"),re.join(s,"skills"));let a=re.join(n,"templates","claude","CLAUDE.md"),l=re.join(o,"GEMINI.md");if(!r&&!tn.existsSync(l)&&tn.existsSync(a)){let c=C(a).replaceAll(".claude/",".gemini/").replaceAll("CLAUDE.md","GEMINI.md");y(l,c)}}import L from"node:path";import Re from"node:fs";async function sn(e){let{pluginRoot:n,baseDir:t,dryRun:o,isGlobal:i}=e;if(o){console.log(`[dry-run] copilot -> ${t}`);return}f(t);let r=L.join(t,"instructions"),s=L.join(t,"prompts");if(f(s),!i){f(r);let l=L.join(n,"templates","shared","rules"),c=[`# Frontend Craft - Copilot instructions
|
|
23
|
+
`];if(Re.existsSync(l))for(let u of Re.readdirSync(l))u.endsWith(".md")&&c.push(`
|
|
24
24
|
## ${u}
|
|
25
25
|
|
|
26
|
-
`,C(
|
|
27
|
-
`];if(
|
|
26
|
+
`,C(L.join(l,u)));y(L.join(r,"frontend-craft.instructions.md"),c.join(""))}let a=L.join(n,"commands");for(let l of Re.readdirSync(a))l.endsWith(".md")&&y(L.join(s,l),C(L.join(a,l)))}import Q from"node:path";async function rn(e){let{pluginRoot:n,baseDir:t,dryRun:o}=e;if(o)return console.log(`[dry-run] antigravity -> ${t}`);f(t),p(Q.join(n,"skills"),Q.join(t,"skills"))}async function ln(e){let{pluginRoot:n,baseDir:t,dryRun:o}=e;if(o)return console.log(`[dry-run] augment -> ${t}`);f(t),p(Q.join(n,"skills"),Q.join(t,"skills"))}async function an(e){let{pluginRoot:n,baseDir:t,dryRun:o}=e;if(o)return console.log(`[dry-run] codebuddy -> ${t}`);f(t),p(Q.join(n,"skills"),Q.join(t,"skills"))}import ue from"node:path";import cn from"node:fs";async function un(e){let{pluginRoot:n,baseDir:t,dryRun:o,isGlobal:i}=e;if(o)return console.log(`[dry-run] trae -> ${t}`);if(i)return;let r=ue.join(t,"rules");f(r);let s=ue.join(n,"templates","shared","rules"),a=[`# Frontend Craft - Trae rules bundle
|
|
27
|
+
`];if(cn.existsSync(s))for(let l of cn.readdirSync(s))l.endsWith(".md")&&a.push(`
|
|
28
28
|
## ${l}
|
|
29
29
|
|
|
30
|
-
`,C(
|
|
31
|
-
`];if(
|
|
30
|
+
`,C(ue.join(s,l)));y(ue.join(r,"frontend-craft.md"),a.join(""))}import fe from"node:path";import fn from"node:fs";async function dn(e){let{pluginRoot:n,baseDir:t,dryRun:o,isGlobal:i}=e;if(o)return console.log(`[dry-run] cline -> ${fe.join(t,".clinerules")}`);if(i)return;let r=fe.join(n,"templates","shared","rules"),s=[`# Frontend Craft - Cline rules
|
|
31
|
+
`];if(fn.existsSync(r))for(let a of fn.readdirSync(r))a.endsWith(".md")&&s.push(`
|
|
32
32
|
## ${a}
|
|
33
33
|
|
|
34
|
-
`,C(
|
|
35
|
-
`)}function
|
|
36
|
-
`)}function
|
|
37
|
-
`):new Promise(n=>{let t=process.stdin,o=process.stdout,i=t.isRaw,r=0;function s(){r>0&&(
|
|
34
|
+
`,C(fe.join(r,a)));y(fe.join(t,".clinerules"),s.join(""))}import H from"node:path";import De from"node:fs";async function pn(e){let{pluginRoot:n,baseDir:t,cwd:o,dryRun:i}=e;if(i)return console.log(`[dry-run] openclaw -> ${t}`);f(t),p(H.join(n,"skills"),H.join(t,"skills")),p(H.join(n,"commands"),H.join(t,"commands"));let r=H.join(n,"templates","openclaw");if(De.existsSync(r))for(let s of De.readdirSync(r)){if(!s.endsWith(".md"))continue;let a=H.join(o,s);De.existsSync(a)||D(H.join(r,s),a)}}import O from"node:path";import mn from"node:fs";var Jn=["fec-security-check.js","fec-format-changed-file.js","fec-run-tests.js","fec-notify.js"];async function gn(e){let{pluginRoot:n,baseDir:t,dryRun:o,isGlobal:i}=e;if(o){console.log(`[dry-run] qoder -> ${t}`);return}f(t),p(O.join(n,"skills"),O.join(t,"skills")),p(O.join(n,"commands"),O.join(t,"commands")),p(O.join(n,"agents"),O.join(t,"agents")),i||p(O.join(n,"templates","shared","rules"),O.join(t,"rules")),Yn(n,O.join(t,"hooks")),y(O.join(t,"settings.json"),JSON.stringify(Qn(t),null,2)+`
|
|
35
|
+
`)}function Yn(e,n){f(n);for(let t of Jn){let o=O.join(e,"dist","hooks",t);if(!mn.existsSync(o))throw new Error(`Missing built hook script: ${o}. Run "npm run build" before installing.`);D(o,O.join(n,t))}}function Qn(e){let n=O.join(e,"settings.json"),t=Xn(n);return{...t,hooks:{...X(t.hooks),PreToolUse:[...pe(X(t.hooks).PreToolUse),de("Bash|Shell",".qoder/hooks/fec-security-check.js","Checking command safety...")],PostToolUse:[...pe(X(t.hooks).PostToolUse),de("Write|Edit|MultiEdit",".qoder/hooks/fec-format-changed-file.js","Running formatter...")],Stop:[...pe(X(t.hooks).Stop),de(".*",".qoder/hooks/fec-run-tests.js","Running final validation...")],Notification:[...pe(X(t.hooks).Notification),de(".*",".qoder/hooks/fec-notify.js")]}}}function Xn(e){if(!mn.existsSync(e))return{};try{let n=JSON.parse(C(e));return X(n)}catch{return{}}}function de(e,n,t){return{matcher:e,hooks:[{type:"command",command:`node "${n}"`,...t?{statusMessage:t}:{}}]}}function X(e){return e!=null&&typeof e=="object"&&!Array.isArray(e)?e:{}}function pe(e){return Array.isArray(e)?e:[]}var F=["claude","codex","cursor","windsurf","opencode","kilo","gemini","copilot","antigravity","augment","trae","codebuddy","cline","openclaw","qoder"],me={claude:Ke,codex:Ye,cursor:Xe,windsurf:Ze,opencode:en,kilo:nn,gemini:on,copilot:sn,antigravity:rn,augment:ln,trae:un,codebuddy:an,cline:dn,openclaw:pn,qoder:gn};import Zn from"node:fs";import et from"node:os";import nt from"node:path";import he from"node:readline";import hn from"node:os";import b from"node:path";function x(e){return e&&(e.startsWith("~/")||e==="~"?b.join(hn.homedir(),e.slice(1)):e)}function ge(e){let n=hn.homedir(),t=process.env;switch(e){case"claude":return t.CLAUDE_CONFIG_DIR?x(t.CLAUDE_CONFIG_DIR):b.join(n,".claude");case"cursor":return t.CURSOR_CONFIG_DIR?x(t.CURSOR_CONFIG_DIR):b.join(n,".cursor");case"gemini":return t.GEMINI_CONFIG_DIR?x(t.GEMINI_CONFIG_DIR):b.join(n,".gemini");case"codex":return t.CODEX_HOME?x(t.CODEX_HOME):b.join(n,".codex");case"copilot":return t.COPILOT_CONFIG_DIR?x(t.COPILOT_CONFIG_DIR):b.join(n,".copilot");case"antigravity":return t.ANTIGRAVITY_CONFIG_DIR?x(t.ANTIGRAVITY_CONFIG_DIR):b.join(n,".gemini","antigravity");case"windsurf":return t.WINDSURF_CONFIG_DIR?x(t.WINDSURF_CONFIG_DIR):b.join(n,".codeium","windsurf");case"augment":return t.AUGMENT_CONFIG_DIR?x(t.AUGMENT_CONFIG_DIR):b.join(n,".augment");case"trae":return t.TRAE_CONFIG_DIR?x(t.TRAE_CONFIG_DIR):b.join(n,".trae");case"codebuddy":return t.CODEBUDDY_CONFIG_DIR?x(t.CODEBUDDY_CONFIG_DIR):b.join(n,".codebuddy");case"cline":return t.CLINE_CONFIG_DIR?x(t.CLINE_CONFIG_DIR):b.join(n,".cline");case"opencode":return t.OPENCODE_CONFIG_DIR?x(t.OPENCODE_CONFIG_DIR):t.XDG_CONFIG_HOME?b.join(x(t.XDG_CONFIG_HOME),"opencode"):b.join(n,".config","opencode");case"kilo":return t.KILO_CONFIG_DIR?x(t.KILO_CONFIG_DIR):t.XDG_CONFIG_HOME?b.join(x(t.XDG_CONFIG_HOME),"kilo"):b.join(n,".config","kilo");case"openclaw":return t.OPENCLAW_CONFIG_DIR?x(t.OPENCLAW_CONFIG_DIR):b.join(n,".openclaw");case"qoder":return t.QODER_CONFIG_DIR?x(t.QODER_CONFIG_DIR):b.join(n,".qoder");default:return t.CLAUDE_CONFIG_DIR?x(t.CLAUDE_CONFIG_DIR):b.join(n,".claude")}}var $e={claude:".claude",codex:".codex",cursor:".cursor",windsurf:".windsurf",opencode:".opencode",kilo:".kilo",gemini:".gemini",copilot:".github",antigravity:".agent",augment:".augment",trae:".trae",codebuddy:".codebuddy",cline:"",openclaw:".openclaw",qoder:".qoder"};function Z({runtime:e,isGlobal:n,cwd:t}){if(n)return ge(e);if(e==="cline")return t;let o=$e[e]??".claude";return b.join(t,o)}var tt={claude:"Claude Code",codex:"Codex",cursor:"Cursor",windsurf:"Windsurf",opencode:"OpenCode",kilo:"Kilo",gemini:"Gemini",copilot:"Copilot",antigravity:"Antigravity",augment:"Augment",trae:"Trae",codebuddy:"CodeBuddy",cline:"Cline",openclaw:"OpenClaw",qoder:"Qoder"};function Cn(e){let n=et.homedir();return e===n?"~":e.startsWith(`${n}${nt.sep}`)?`~${e.slice(n.length)}`:e}function ot(e){let n=$e[e];return n?`./${n}`:"./"}var Ce=F.map((e,n)=>({choice:String(n+1),runtime:e,label:tt[e]??e})),st=String(Ce.length+1);function yn(e){return e.filter((n,t)=>e.indexOf(n)===t)}function it(e,n,t){return Math.max(n,Math.min(t,e))}function rt(e){return!e||e.ctrl||e.meta?"":typeof e.sequence=="string"&&e.sequence.length===1&&e.sequence>=" "?e.sequence:typeof e.name=="string"&&e.name.length===1?e.name:""}function lt(){return Ce.map(e=>{let n=Cn(ge(e.runtime));return{value:e.runtime,label:e.label,description:n,selectedSuffix:"selected"}})}function at(e){let n=yn(e.map(o=>Cn(ge(o)))).join(", "),t=yn(e.map(o=>ot(o))).join(", ");return[{value:"global",label:"Global",description:`${n} - available in all projects`,selectedSuffix:"selected"},{value:"local",label:"Local",description:`${t} - this project only`,selectedSuffix:"selected"}]}function jn({message:e,options:n,selected:t=[],multiple:o=!0,pageSize:i=15,validate:r=a=>a.length>0,transform:s=(a=>a)}){return{message:e,options:n,selected:t.filter((l,c)=>t.indexOf(l)===c),multiple:o,pageSize:i,search:"",cursor:0,error:"",visibleOptions(){let l=this.search.trim().toLowerCase();return l?this.options.filter(c=>c.label.toLowerCase().includes(l)||c.value.toLowerCase().includes(l)):this.options},move(l){let c=this.visibleOptions();this.cursor=it(this.cursor+l,0,Math.max(0,c.length-1)),this.error=""},toggle(){let l=this.visibleOptions()[this.cursor];l&&(this.multiple?this.selected.includes(l.value)?this.selected=this.selected.filter(c=>c!==l.value):this.selected=[...this.selected,l.value]:this.selected=[l.value],this.error="")},backspace(){this.search.length>0?(this.search=this.search.slice(0,-1),this.cursor=0):this.selected.length>0&&(this.selected=this.selected.slice(0,-1)),this.error=""},type(l){this.search+=l,this.cursor=0,this.error=""},confirm(){let l=r(this.selected);if(l!==!0){this.error=typeof l=="string"?l:"Select at least one option";return}return s(this.selected)}}}function Sn(e={}){return jn({message:`Which runtime(s) would you like to install for? (${Ce.length} available)`,options:lt(),selected:["claude"],multiple:!0,pageSize:e.pageSize??15,transform:n=>n})}function bn(e,n={}){return jn({message:"Where would you like to install?",options:at(e),selected:["global"],multiple:!1,pageSize:n.pageSize??15,transform:t=>t[0]!=="local"})}function ye(e){let n=e.visibleOptions(),t=new Set(e.selected),o=new Map(e.options.map(s=>[s.value,s])),i=e.selected.length?e.selected.map(s=>o.get(s)?.label??s).join(", "):"(none selected)",r=[`? ${e.message}`,` Selected: ${i}`,` Search: [${e.search||"type to filter"}]`," Up/Down navigate - Space toggle - Backspace remove - Enter confirm"];if(n.length===0)r.push(" No matches");else{let s=Math.max(0,Math.min(e.cursor-Math.floor(e.pageSize/2),n.length-e.pageSize)),a=Math.min(s+e.pageSize,n.length),l=n.slice(s,a);for(let c=0;c<l.length;c++){let u=l[c],R=s+c===e.cursor,S=t.has(u.value),M=R?">":" ",I=S?"[x]":"[ ]",oe=u.description?` (${u.description})`:"",W=S?` (${u.selectedSuffix??"selected"})`:oe;r.push(` ${M} ${I} ${u.label}${W}`)}if(n.length>e.pageSize){let c=Math.floor(e.cursor/e.pageSize)+1,u=Math.ceil(n.length/e.pageSize);r.push(` (${c}/${u})`)}}return e.error&&r.push(` ${e.error}`),r.join(`
|
|
36
|
+
`)}function ct(){return ye(Sn())}function ut(e){let t=((e==null?"":String(e)).trim()||"1").split(/[\s,]+/).filter(Boolean);if(t.includes(st))return F.slice();let o=[],i=new Set,r=new Map(Ce.map(s=>[s.choice,s.runtime]));for(let s of t){let a=r.get(s);!a||i.has(a)||(i.add(a),o.push(a))}return o.length?o:["claude"]}function ft(e){return ye(bn(e))}function dt(e){return((e==null?"":String(e)).trim()||"1")!=="2"}var Oe;function xn(e){return process.stdout.write(e),Oe||(Oe=Zn.readFileSync(0,"utf8").split(/\r?\n/)),Oe.shift()??""}function In(e){return process.env.FRONTEND_CRAFT_FORCE_INTERACTIVE==="1"&&!process.stdin.isTTY?Promise.resolve(xn(e)):new Promise(n=>{let t=he.createInterface({input:process.stdin,output:process.stdout});t.question(e,o=>{t.close(),n(o)}),t.on("close",()=>n(""))})}async function vn(e){return process.env.FRONTEND_CRAFT_FORCE_INTERACTIVE==="1"&&!process.stdin.isTTY?xn(`${ye(e)}
|
|
37
|
+
`):new Promise(n=>{let t=process.stdin,o=process.stdout,i=t.isRaw,r=0;function s(){r>0&&(he.moveCursor(o,0,-r),he.clearScreenDown(o));let c=ye(e);o.write(c),o.write(`
|
|
38
38
|
`),r=c.split(`
|
|
39
39
|
`).length}function a(){t.off("keypress",l),t.isTTY&&t.setRawMode(!!i),t.pause()}function l(c,u){if(u?.ctrl&&u.name==="c"){a(),o.write(`
|
|
40
|
-
`),process.exitCode=130,n(void 0);return}if(u?.name==="return"||u?.name==="enter"){let g=e.confirm();s(),g!==void 0&&(a(),n(g));return}if(u?.name==="up")e.move(-1);else if(u?.name==="down")e.move(1);else if(u?.name==="space")e.toggle();else if(u?.name==="backspace")e.backspace();else{let g=
|
|
41
|
-
`),
|
|
42
|
-
`),
|
|
43
|
-
`)}function
|
|
40
|
+
`),process.exitCode=130,n(void 0);return}if(u?.name==="return"||u?.name==="enter"){let g=e.confirm();s(),g!==void 0&&(a(),n(g));return}if(u?.name==="up")e.move(-1);else if(u?.name==="down")e.move(1);else if(u?.name==="space")e.toggle();else if(u?.name==="backspace")e.backspace();else{let g=rt(u);g&&e.type(g)}s()}he.emitKeypressEvents(t),t.isTTY&&t.setRawMode(!0),t.resume(),t.on("keypress",l),s()})}async function kn(){if(process.stdin.isTTY){let e=await vn(Sn());return Array.isArray(e)?e:["claude"]}return console.log(`${ct()}
|
|
41
|
+
`),ut(await In("Choice [1]: "))}async function wn(e){if(process.stdin.isTTY){let n=await vn(bn(e));return typeof n=="boolean"?n:!0}return console.log(`${ft(e)}
|
|
42
|
+
`),dt(await In("Choice [1]: "))}var je={claude:{tier:"full",skills:!0,agents:!0,commands:!0,hooks:!0,rules:!0,templates:!0,mcp:!0,reports:!0,init:"native"},codex:{tier:"full",skills:!0,agents:!0,commands:!1,hooks:!1,rules:!0,templates:!0,mcp:!1,reports:!0,init:"native"},openclaw:{tier:"full",skills:!0,agents:!1,commands:!0,hooks:!1,rules:!1,templates:!0,mcp:!1,reports:!0,init:"native"},qoder:{tier:"full",skills:!0,agents:!0,commands:!0,hooks:!0,rules:!0,templates:!0,mcp:!1,reports:!0,init:"native"},opencode:{tier:"medium",skills:!0,agents:!1,commands:!0,hooks:!1,rules:!1,templates:!0,mcp:!1,reports:!0,init:"rules"},kilo:{tier:"medium",skills:!0,agents:!1,commands:!0,hooks:!1,rules:!1,templates:!1,mcp:!1,reports:!0,init:"rules"},cursor:{tier:"medium",skills:!0,agents:!1,commands:!1,hooks:!1,rules:!0,templates:!1,mcp:!1,reports:!0,init:"rules"},windsurf:{tier:"medium",skills:!0,agents:!1,commands:!0,hooks:!1,rules:!0,templates:!1,mcp:!1,reports:!0,init:"rules"},gemini:{tier:"medium",skills:!0,agents:!1,commands:!1,hooks:!1,rules:!1,templates:!0,mcp:!1,reports:!0,init:"rules"},copilot:{tier:"medium",skills:!1,agents:!1,commands:!0,hooks:!1,rules:!0,templates:!1,mcp:!1,reports:!0,init:"rules"},antigravity:{tier:"skills-rules-only",skills:!0,agents:!1,commands:!1,hooks:!1,rules:!1,templates:!1,mcp:!1,reports:!0,init:"none"},augment:{tier:"skills-rules-only",skills:!0,agents:!1,commands:!1,hooks:!1,rules:!1,templates:!1,mcp:!1,reports:!0,init:"none"},trae:{tier:"skills-rules-only",skills:!1,agents:!1,commands:!1,hooks:!1,rules:!0,templates:!1,mcp:!1,reports:!0,init:"none"},codebuddy:{tier:"skills-rules-only",skills:!0,agents:!1,commands:!1,hooks:!1,rules:!1,templates:!1,mcp:!1,reports:!0,init:"none"},cline:{tier:"skills-rules-only",skills:!1,agents:!1,commands:!1,hooks:!1,rules:!0,templates:!1,mcp:!1,reports:!0,init:"none"}};function Rn(e){let n=["Runtime","Tier","Skills","Agents","Commands","Hooks","Rules","Templates","MCP","Reports","Init"],t=e.map(r=>{let s=je[r];if(!s)throw new Error(`Missing capability declaration for runtime: ${r}`);return[r,s.tier,U(s.skills),U(s.agents),U(s.commands),U(s.hooks),U(s.rules),U(s.templates),U(s.mcp),U(s.reports),s.init]}),o=n.map((r,s)=>Math.max(r.length,...t.map(a=>a[s].length))),i=r=>r.map((s,a)=>s.padEnd(o[a])).join(" ").trimEnd();return[i(n),...t.map(i)].join(`
|
|
43
|
+
`)}function U(e){return e?"yes":"no"}import Dn from"node:fs";import ee from"node:path";import{fileURLToPath as $n}from"node:url";function On(e=import.meta.url){let n=ee.dirname($n(e));for(;;){if(Dn.existsSync(ee.join(n,"package.json"))&&Dn.existsSync(ee.join(n,"skills")))return n;let t=ee.dirname(n);if(t===n)return ee.resolve(ee.dirname($n(e)),"../..");n=t}}import ne from"node:fs";import Nn from"node:os";import T from"node:path";function Se({cwd:e,runtimes:n=F,scopes:t=["local","global"]}){let o=[],i=new Set;for(let r of n)for(let s of t){let a=Z({runtime:r,isGlobal:s==="global",cwd:e}),l=ae(a),c=T.resolve(l);if(i.has(c))continue;i.add(c);let u=Ie(l);u&&(u.runtime!==r||u.scope!==s||o.push({runtime:r,scope:s,baseDir:a,manifestPath:l,manifest:u}))}return o}function Ne({install:e,cwd:n,dryRun:t,force:o}){let i={removed:[],skipped:[],missing:[]},r=new Set;for(let s of e.manifest.files){let a=pt({filePath:s.path,root:s.root??"baseDir",baseDir:e.baseDir,cwd:n});if(!a)continue;if(!ne.existsSync(a)){i.missing.push(s.path);continue}let l=ve(a)!==s.hash;if(l&&!o){i.skipped.push(s.path);continue}i.removed.push({path:s.path,modified:l}),r.add(T.dirname(a)),t||ne.rmSync(a,{force:!0})}return!t&&i.skipped.length===0&&(ne.rmSync(e.manifestPath,{force:!0}),mt([...r],[e.baseDir,n,Nn.homedir()])),i}function pt({filePath:e,root:n,baseDir:t,cwd:o}){let i=n==="baseDir"?t:n==="cwd"?o:Nn.homedir(),r=T.resolve(i),s=T.resolve(r,e),a=T.relative(r,s);if(!(a.startsWith("..")||T.isAbsolute(a)))return s}function mt(e,n){let t=n.map(o=>T.resolve(o));for(let o of e.sort((i,r)=>r.length-i.length)){let i=T.resolve(o);for(;gt(i,t)&&!t.includes(i)&&!(!ne.existsSync(i)||ne.readdirSync(i).length>0);)ne.rmdirSync(i),i=T.dirname(i)}}function gt(e,n){return n.some(t=>{let o=T.relative(t,e);return o!==""&&!o.startsWith("..")&&!T.isAbsolute(o)})}import N from"node:fs";import Fe from"node:os";import w from"node:path";function Me({claudeConfigDir:e=Te(),currentVersion:n}){let t=w.join(e,"plugins","cache","frontend-craft","frontend-craft"),o=yt(e),i=ht({cacheDir:t,currentInstallPaths:o.paths,currentVersion:n}),r=o.warning,s=!r&&o.paths.length>0;return{claudeConfigDir:e,cacheDir:t,currentInstallPaths:o.paths,entries:i.map(a=>({...a,shouldDelete:s&&a.shouldDelete,reasons:s?a.reasons:["metadata unavailable"]})),canDelete:s,warning:r}}function Tn({claudeConfigDir:e=Te(),currentVersion:n,dryRun:t=!1}){let o=Me({claudeConfigDir:e,currentVersion:n}),i=[];if(o.canDelete)for(let r of o.entries)r.shouldDelete&&(t||N.rmSync(r.path,{recursive:!0,force:!0}),t||i.push(r));return{...o,deleted:i}}function _e({claudeConfigDir:e=Te(),cwd:n=process.cwd()}={}){let t=Ct(e),o=[Fn(w.join(n,".claude","frontend-craft.manifest.json"),"local"),Fn(w.join(e,"frontend-craft.manifest.json"),"global")].filter(i=>i!==void 0);return{native:t,cli:o,hasMultipleActiveSources:t.length+o.length>1}}function le(e){let n=["install sources:"];if(e.native.length===0&&e.cli.length===0)return n.push(" none detected"),n.join(`
|
|
44
44
|
`);for(let t of e.native){let o=[t.version,t.scope].filter(Boolean).join(", ");n.push(` native plugin: ${o||"installed"}${t.installPath?` (${t.installPath})`:""}`)}for(let t of e.cli)n.push(` ${t.scope} CLI: ${t.packageVersion??"unknown"} (${t.manifestPath})`);return e.hasMultipleActiveSources&&n.push(" warning: multiple active sources detected; keep one Claude loader as the source of truth"),n.join(`
|
|
45
|
-
`)}function
|
|
45
|
+
`)}function be(e,n="report"){let t=["native cache:"];if(t.push(` current installs: ${e.currentInstallPaths.length?e.currentInstallPaths.join(", "):"unknown"}`),e.warning&&t.push(` warning: ${e.warning}`),e.entries.length===0)return t.push(" entries: none"),t.join(`
|
|
46
46
|
`);for(let o of e.entries){let i=o.isCurrent?"current install":o.reasons.length?o.reasons.join(", "):"healthy",r=n==="dry-run"&&o.shouldDelete?"would delete":n==="fix"&&o.shouldDelete?"delete":"keep";t.push(` ${r} ${o.version}: ${i}`)}return t.join(`
|
|
47
|
-
`)}function
|
|
48
|
-
`)}function
|
|
47
|
+
`)}function Pn(e,n){if(n)return be(e,"dry-run");let t=[be(e,"fix")];for(let o of e.deleted)t.push(` deleted ${o.version}: ${o.path}`);return e.deleted.length===0&&t.push(" deleted: none"),t.join(`
|
|
48
|
+
`)}function Te(){return xt(process.env.CLAUDE_CONFIG_DIR??w.join(Fe.homedir(),".claude"))}function ht({cacheDir:e,currentInstallPaths:n,currentVersion:t}){if(!N.existsSync(e))return[];let o=new Set(n.map(_n));return N.readdirSync(e,{withFileTypes:!0}).filter(i=>i.isDirectory()).map(i=>{let r=w.join(e,i.name),s=St(r),a=N.existsSync(w.join(r,"dist")),l=N.existsSync(w.join(r,".orphaned_at")),c=o.has(_n(r)),u=[];return l&&u.push("orphaned"),a||u.push("missing dist"),s&&bt(s,t)<0&&u.push(`package ${s} < ${t}`),{version:i.name,path:r,isCurrent:c,hasDist:a,orphaned:l,packageVersion:s,reasons:u,shouldDelete:!c&&u.length>0}}).sort((i,r)=>i.version.localeCompare(r.version,void 0,{numeric:!0}))}function yt(e){let n=w.join(e,"plugins","installed_plugins.json");if(!N.existsSync(n))return{paths:[],warning:`missing ${n}; refusing to delete cache entries`};try{let t=JSON.parse(N.readFileSync(n,"utf8")),o=(t.plugins?.["frontend-craft@bovinphang/frontend-craft"]??t.plugins?.["frontend-craft@frontend-craft"]??[]).map(i=>i.installPath).filter(i=>typeof i=="string"&&i.length>0).map(i=>w.resolve(i));return o.length===0?{paths:o,warning:`missing frontend-craft plugin entry in ${n}; refusing to delete cache entries`}:{paths:o}}catch{return{paths:[],warning:`invalid ${n}; refusing to delete cache entries`}}}function Ct(e){let n=w.join(e,"plugins","installed_plugins.json");if(!N.existsSync(n))return[];try{let t=JSON.parse(N.readFileSync(n,"utf8"));return Object.entries(t.plugins??{}).flatMap(([o,i])=>jt(o)?i.map(r=>({key:o,installPath:typeof r.installPath=="string"?w.resolve(r.installPath):void 0,scope:typeof r.scope=="string"?r.scope:void 0,version:typeof r.version=="string"?r.version:void 0})):[])}catch{return[]}}function jt(e){return e==="frontend-craft@bovinphang/frontend-craft"||e==="frontend-craft@frontend-craft"||e.startsWith("frontend-craft@")}function Fn(e,n){if(N.existsSync(e))try{let t=JSON.parse(N.readFileSync(e,"utf8"));return t.runtime!=="claude"?void 0:{scope:n,manifestPath:e,packageVersion:typeof t.packageVersion=="string"?t.packageVersion:void 0}}catch{return{scope:n,manifestPath:e}}}function St(e){let n=w.join(e,"package.json");if(N.existsSync(n))try{let t=JSON.parse(N.readFileSync(n,"utf8"));return typeof t.version=="string"?t.version:void 0}catch{return}}function bt(e,n){let t=Mn(e),o=Mn(n);for(let i=0;i<3;i+=1)if(t[i]!==o[i])return t[i]-o[i];return 0}function Mn(e){let n=e.match(/^(\d+)\.(\d+)\.(\d+)/);return n?[Number(n[1]),Number(n[2]),Number(n[3])]:[0,0,0]}function xt(e){return e==="~"?Fe.homedir():e.startsWith("~/")||e.startsWith("~\\")?w.join(Fe.homedir(),e.slice(2)):e}function _n(e){let n=w.resolve(e);return process.platform==="win32"?n.toLowerCase():n}function Ae(e){try{let n=d.join(e,"package.json");return JSON.parse(m.readFileSync(n,"utf8")).version??"0.0.0"}catch{return"0.0.0"}}function vt(e){let n=null,t=!1,o=!1,i=!1,r=!1,s=!1,a=[];for(let l of e)l==="--global"||l==="-g"?(t=!0,n="global"):l==="--local"||l==="-l"?(o=!0,n="local"):l==="--dry-run"?i=!0:l==="--force"?r=!0:l==="--all"?s=!0:l.startsWith("-")||a.push(l);return{runtime:a[0],installLocation:n,dryRun:i,force:r,all:s,hasGlobal:t,hasLocal:o}}function Pe(){console.log(`frontend-craft - universal installer
|
|
49
49
|
|
|
50
50
|
Usage:
|
|
51
51
|
frontend-craft install <runtime> [options]
|
|
52
52
|
frontend-craft install --all [options]
|
|
53
53
|
frontend-craft install
|
|
54
|
-
frontend-craft
|
|
55
|
-
|
|
54
|
+
frontend-craft setup [runtime] [options]
|
|
55
|
+
frontend-craft setup all [options]
|
|
56
|
+
fec setup [runtime] [options]
|
|
57
|
+
fec setup all [options]
|
|
56
58
|
frontend-craft update [runtime] [options]
|
|
57
59
|
frontend-craft upgrade [runtime] [options]
|
|
58
60
|
frontend-craft uninstall|remove [runtime] [options]
|
|
@@ -71,12 +73,13 @@ Options:
|
|
|
71
73
|
--all Install for every supported runtime
|
|
72
74
|
|
|
73
75
|
Notes:
|
|
74
|
-
|
|
76
|
+
setup connects frontend-craft to the current project. Omit runtime in an interactive terminal to choose runtimes.
|
|
77
|
+
setup <runtime> and setup all install locally by default unless --global is explicitly provided.
|
|
75
78
|
update without a runtime refreshes discovered frontend-craft manifests.
|
|
76
79
|
uninstall/remove deletes manifest-managed files and skips modified files unless --force is provided.
|
|
77
|
-
/fec-init is the in-assistant slash command; fec
|
|
80
|
+
/fec-init is the in-assistant slash command; fec setup is the terminal CLI command.
|
|
78
81
|
|
|
79
82
|
Runtimes:
|
|
80
83
|
${F.join(", ")}
|
|
81
|
-
`)}function
|
|
82
|
-
`)}function te(e,n){return n?e?"ok":"missing":"not expected"}function
|
|
84
|
+
`)}function En(e){let n=e[0];return n==null||["--dry-run","--all","--global","-g","--local","-l"].includes(n)}async function Wn(e){let n=En(e)?"install":e[0],t=En(e)?e:e.slice(1),o=n==="setup",i=On(import.meta.url);if(n==="version"||n==="-v"||n==="--version"){console.log(Ae(i));return}if(n==="list"){F.forEach(h=>console.log(h));return}if(n==="matrix"){console.log(Rn(F));return}if(n==="doctor"){let h=t[0]??"claude",_=t.includes("--global")||t.includes("-g"),P=t.includes("--fix-cache"),V=t.includes("--dry-run"),q=process.cwd(),G=Z({runtime:h,isGlobal:_,cwd:q}),xe=je[h];if(!xe){console.error(`Unknown runtime: ${h}`),process.exitCode=1;return}console.log(Ot({runtime:h,baseDir:G,cwd:q,cap:xe,pluginRoot:i,fixCache:P,dryRun:V,isGlobal:_}));return}if(n==="sync-metadata"){let h=t.includes("--check"),_=Pt(i);if(_.length>0){for(let P of _)console.error(P);process.exitCode=1;return}console.log(h?"metadata is synchronized":"metadata is synchronized; no changes needed");return}if(n==="help"||n==="-h"||n==="--help"){Pe();return}let r=n==="update"||n==="upgrade"?"update":"install";if(n!=="install"&&n!=="setup"&&n!=="update"&&n!=="upgrade"&&n!=="uninstall"&&n!=="remove"){console.error(`Unknown command: ${n}`),Pe(),process.exitCode=1;return}if(t.some(h=>h==="help"||h==="--help"||h==="-h")){Pe();return}let s=vt(t),a=s.runtime,l=s.all,{installLocation:c,dryRun:u,force:g,hasGlobal:R,hasLocal:S}=s;o&&a==="all"&&(a=void 0,l=!0);let M=c??(o?"local":null);if(R&&S){console.error("--global and --local cannot be used together."),process.exitCode=1;return}let I=process.cwd();if(n==="uninstall"||n==="remove"){let h=Ln(S,R),P=Se({cwd:I,runtimes:a?[a]:l?F:void 0,scopes:h});if(a&&!me[a]){console.error(`Unknown runtime: ${a}`),process.exitCode=1;return}if(P.length===0){console.log(Un("uninstall",a,h));return}for(let V of P){console.log(`Uninstalling frontend-craft for "${V.runtime}" -> ${V.baseDir}${V.scope==="global"?" (global)":""}`);let q=Ne({install:V,cwd:I,dryRun:u,force:g});for(let G of q.removed)console.log(`${u?"[dry-run] would remove":"Removed"}${G.modified?" modified file":" file"}: ${G.path}`);for(let G of q.skipped)console.log(`Skipped modified file: ${G}`);for(let G of q.missing)console.log(`Missing file: ${G}`);u&&q.skipped.length===0&&console.log(`[dry-run] would remove manifest: ${V.manifestPath}`)}return}let oe=!!process.stdin.isTTY||process.env.FRONTEND_CRAFT_FORCE_INTERACTIVE==="1";if(r==="update"&&!a){let h=Ln(S,R),_=Se({cwd:I,runtimes:l?F:void 0,scopes:h});if(_.length===0){console.log(Un("update",void 0,h));return}for(let P of _)await An({rt:P.runtime,mode:r,isGlobal:P.scope==="global",baseDir:P.baseDir,pluginRoot:i,cwd:I,dryRun:u});return}let W=l?F:a?[a]:[];W.length||(oe?W=await kn():(console.log("Non-interactive terminal detected, defaulting to claude runtime."),W=["claude"]));let J;M==="global"?J=!0:M==="local"?J=!1:oe?J=await wn(W):(console.log("Non-interactive terminal detected, defaulting to global install."),J=!0);for(let h of W){if(!me[h]){console.error(`Unknown runtime: ${h}`),process.exitCode=1;continue}let _=Z({runtime:h,isGlobal:J,cwd:I});await An({rt:h,mode:r,isGlobal:J,baseDir:_,pluginRoot:i,cwd:I,dryRun:u,force:g,canPrompt:oe})}}async function An({rt:e,mode:n,isGlobal:t,baseDir:o,pluginRoot:i,cwd:r,dryRun:s,force:a,canPrompt:l}){let c=await kt({rt:e,mode:n,isGlobal:t,cwd:r,dryRun:s,force:!!a,canPrompt:!!l});if(!c)return;c.redirect&&(t=c.redirect.isGlobal,o=c.redirect.baseDir,n="update");let u={pluginRoot:i,cwd:r,runtime:e,isGlobal:t,baseDir:o,dryRun:s,mode:n,capabilities:je[e]};n==="install"?(console.log(`Installing frontend-craft for "${e}" -> ${o}${t?" (global)":""}`),!s&&m.existsSync(ae(o))&&console.log(`Existing frontend-craft manifest found; use "frontend-craft update ${e}" to refresh managed files.`)):console.log(`Updating frontend-craft for "${e}" -> ${o}${t?" (global)":""}`),s||Ge({baseDir:o,cwd:r,mode:n,packageVersion:Ae(i),runtime:e,isGlobal:t});try{await me[e](u),s||Le()}catch(g){throw s||Ue(),g}}async function kt({rt:e,mode:n,isGlobal:t,cwd:o,dryRun:i,force:r,canPrompt:s}){if(e!=="claude")return{};let a=_e({cwd:o});if(a.native.length>0){console.log("Claude Code Marketplace install detected for frontend-craft."),console.log(le(a)),console.log("Update frontend-craft through Claude Code Marketplace. CLI install/update is disabled while Marketplace is active."),process.exitCode=1;return}let l=t?"cli-global":"cli-local",c=a.cli.filter(M=>Ee(M)!==l);if(c.length===0)return a.hasMultipleActiveSources&&console.log(le(a)),{};let u=c[0],g=Ee(u);if(console.log(`${K(g)} install detected for frontend-craft.`),console.log(le(a)),i){console.log(`[dry-run] resolve the existing ${K(g)} source before using ${K(l)}.`),process.exitCode=1;return}if(!s){Rt(g,l),process.exitCode=1;return}if(await Dt(g,l)==="switch"){if(!wt({installs:c,cwd:o,dryRun:i,force:r})){process.exitCode=1;return}return{}}let S=g==="cli-global";return{redirect:{isGlobal:S,baseDir:Z({runtime:"claude",isGlobal:S,cwd:o})}}}function wt({installs:e,cwd:n,dryRun:t,force:o}){for(let i of e){let r=Se({cwd:n,runtimes:["claude"],scopes:[i.scope]})[0];if(!r)continue;console.log(`Uninstalling existing ${K(Ee(i))} install before switching source.`);let s=Ne({install:r,cwd:n,dryRun:t,force:o});for(let a of s.removed)console.log(`${t?"[dry-run] would remove":"Removed"}${a.modified?" modified file":" file"}: ${a.path}`);for(let a of s.skipped)console.log(`Skipped modified file: ${a}`);for(let a of s.missing)console.log(`Missing file: ${a}`);if(s.skipped.length>0)return console.log("Resolve skipped modified files or rerun with --force before switching Claude CLI install source."),!1}return!0}function Rt(e,n){let t=Gn(e),o=Gn(n);console.log(`Keep the existing source updated with: frontend-craft update claude ${t}`),console.log(`Or switch sources with: frontend-craft uninstall claude ${t} && frontend-craft install claude ${o}`)}async function Dt(e,n){return console.log("Choose how to resolve the Claude CLI install source conflict:"),console.log(` 1. Keep ${K(e)} and update it`),console.log(` 2. Uninstall ${K(e)} and install ${K(n)}`),(await $t("Choice [1]: ")).trim()==="2"?"switch":"keep"}function $t(e){return process.env.FRONTEND_CRAFT_FORCE_INTERACTIVE==="1"&&!process.stdin.isTTY?(process.stdout.write(e),Promise.resolve(m.readFileSync(0,"utf8").split(/\r?\n/)[0]??"")):new Promise(n=>{let t=It.createInterface({input:process.stdin,output:process.stdout});t.question(e,o=>{t.close(),n(o)}),t.on("close",()=>n(""))})}function Ee(e){return e.scope==="global"?"cli-global":"cli-local"}function K(e){return e==="marketplace"?"Claude Code Marketplace":e==="cli-global"?"Claude CLI global":"Claude CLI local"}function Gn(e){return e==="cli-global"?"--global":"--local"}function Ln(e,n){return e?["local"]:n?["global"]:["local","global"]}function Un(e,n,t){return`No frontend-craft installs found${n?` for "${n}"`:""} to ${e} (${t.join(", ")}).`}function Ot({runtime:e,baseDir:n,cwd:t,cap:o,pluginRoot:i,fixCache:r,dryRun:s,isGlobal:a}){let l=[`frontend-craft doctor: ${e}`,`baseDir: ${n}`,`tier: ${o.tier}`];if(l.push(`skills: ${te(Nt(e,n,t),o.skills)}`),l.push(`agents: ${te(m.existsSync(d.join(n,"agents")),o.agents)}`),l.push(`commands: ${te(Ft(e,n),o.commands)}`),l.push(`hooks: ${te(Mt(e,n),o.hooks)}`),l.push(`rules: ${te(_t(e,n),o.rules&&!a)}`),l.push(`templates: ${te(Tt(e,n,t),o.templates)}`),e==="claude"){let c=Ae(i);l.push(le(_e({cwd:t}))),r?l.push(Pn(Tn({currentVersion:c,dryRun:s}),s)):l.push(be(Me({currentVersion:c})))}return l.join(`
|
|
85
|
+
`)}function te(e,n){return n?e?"ok":"missing":"not expected"}function Nt(e,n,t){return e==="codex"?m.existsSync(d.join(t,".agents","skills","fec-react-project-standard")):e==="gemini"?m.existsSync(d.join(n,"extensions","frontend-craft","skills","fec-react-project-standard")):m.existsSync(d.join(n,"skills","fec-react-project-standard"))}function Ft(e,n){let t=e==="windsurf"?d.join(n,"workflows"):e==="opencode"||e==="kilo"?d.join(n,"command"):e==="copilot"?d.join(n,"prompts"):d.join(n,"commands");return m.existsSync(d.join(t,"fec-init.md"))}function Mt(e,n){return e==="qoder"?m.existsSync(d.join(n,"settings.json"))&&m.existsSync(d.join(n,"hooks","fec-security-check.js")):m.existsSync(d.join(n,"hooks.json"))}function _t(e,n){return e==="copilot"?m.existsSync(d.join(n,"instructions","frontend-craft.instructions.md")):e==="cline"?m.existsSync(d.join(n,".clinerules")):m.existsSync(d.join(n,"rules"))}function Tt(e,n,t){return e==="claude"?m.existsSync(d.join(t,".claude-plugin","plugin.json")):e==="codex"?m.existsSync(d.join(t,"AGENTS.md"))||m.existsSync(d.join(n,"config.toml")):e==="gemini"?m.existsSync(d.join(t,"GEMINI.md")):e==="openclaw"?m.existsSync(d.join(t,"AGENTS.md")):e==="qoder"?m.existsSync(d.join(n,"settings.json")):e==="opencode"?m.existsSync(d.join(n,"opencode.jsonc")):!1}function Pt(e){let n=[],t=JSON.parse(m.readFileSync(d.join(e,"package.json"),"utf8")),o=[[".claude-plugin/plugin.json","version"],[".claude-plugin/marketplace.json","plugins.0.version"],["openclaw.plugin.json","version"]];for(let[c,u]of o){let g=d.join(e,c),R=JSON.parse(m.readFileSync(g,"utf8")),S=u.split(".").reduce((M,I)=>M?.[Number.isNaN(Number(I))?I:Number(I)],R);S!==t.version&&n.push(`${c} ${u} is ${S}, expected ${t.version}`)}let i=JSON.parse(m.readFileSync(d.join(e,"skills","metadata.json"),"utf8"));for(let c of i)c.version!==t.version&&n.push(`skills/metadata.json ${c.id}.version is ${c.version}, expected ${t.version}`);let r=m.readdirSync(d.join(e,"commands")).filter(c=>c.endsWith(".md")).length,s=m.readdirSync(d.join(e,"skills"),{withFileTypes:!0}).filter(c=>c.isDirectory()).length,a=m.readdirSync(d.join(e,"agents")).filter(c=>c.endsWith(".md")).length,l=m.readFileSync(d.join(e,".claude-plugin","marketplace.json"),"utf8");return l.includes(`${a} agents`)||n.push(`marketplace description must mention ${a} agents`),l.includes(`${s} skills`)||n.push(`marketplace description must mention ${s} skills`),l.includes(`${r} commands`)||n.push(`marketplace description must mention ${r} commands`),n}Wn(process.argv.slice(2)).catch(e=>{console.error(e),process.exit(1)});
|