create-dwy 0.6.0 → 0.6.2

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/bin/index.js CHANGED
@@ -5,33 +5,29 @@ import { fileURLToPath } from 'url'
5
5
  import { dirname, resolve } from 'path'
6
6
  import { program } from 'commander'
7
7
  import { createProject } from '../src/create.js'
8
- import { syncClaude, syncProjectClaude } from '../src/sync.js'
8
+ import { syncClaude, syncProjectClaudeMd } from '../src/sync.js'
9
9
 
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url))
11
11
  const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'))
12
12
 
13
13
  program
14
14
  .name('dwy')
15
- .description('Project scaffolding and Claude Code config sync')
15
+ .description('项目脚手架与 Claude Code 配置同步工具')
16
16
  .version(pkg.version)
17
17
 
18
18
  program
19
19
  .command('create [name]')
20
- .description('Create a new project from template')
20
+ .description('从模板创建新项目')
21
21
  .action(createProject)
22
22
 
23
23
  program
24
- .command('sync <target>')
25
- .description('Sync Claude configuration (claude | project-claude)')
26
- .action(async (target) => {
27
- if (target === 'claude') {
28
- await syncClaude()
29
- } else if (target === 'project-claude') {
30
- await syncProjectClaude()
31
- } else {
32
- console.error(`Unknown sync target: ${target}. Use "claude" or "project-claude".`)
33
- process.exit(1)
34
- }
35
- })
24
+ .command('sync')
25
+ .description('交互式选择并同步 Claude Code 配置(skills/rules/commands/settings.json → 项目 .claude/;CLAUDE.md → 全局 ~/.claude/)')
26
+ .action(syncClaude)
27
+
28
+ program
29
+ .command('sync-claude-md')
30
+ .description('单独同步 CLAUDE.md 到当前目录的 .claude/')
31
+ .action(syncProjectClaudeMd)
36
32
 
37
33
  program.parse()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-dwy",
3
- "version": "0.6.0",
4
- "description": "Project scaffolding and Claude Code config sync CLI",
3
+ "version": "0.6.2",
4
+ "description": "项目脚手架与 Claude Code 配置同步工具",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "dwy": "./bin/index.js"
@@ -11,7 +11,12 @@
11
11
  "src",
12
12
  "templates"
13
13
  ],
14
- "keywords": ["cli", "scaffold", "fastapi", "vue"],
14
+ "keywords": [
15
+ "cli",
16
+ "scaffold",
17
+ "fastapi",
18
+ "vue"
19
+ ],
15
20
  "license": "MIT",
16
21
  "author": "dwydev",
17
22
  "dependencies": {
package/src/sync.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs-extra'
2
2
  import path from 'path'
3
+ import os from 'os'
3
4
  import inquirer from 'inquirer'
4
5
  import { ensureRepoCache, copyDir, chalk, PACKAGE_ROOT } from './utils.js'
5
6
 
@@ -59,7 +60,7 @@ async function scanRules(sourceDir) {
59
60
  const content = await fs.readFile(rulePath, 'utf-8')
60
61
  const description = extractDescription(content)
61
62
  rules.push({
62
- name: entry.name.replace(/\.md$/, ''),
63
+ name: entry.name,
63
64
  description: description || '(无描述)',
64
65
  sourcePath: rulePath,
65
66
  type: 'rule',
@@ -189,13 +190,15 @@ async function resolveSourceDir() {
189
190
  }
190
191
 
191
192
  /**
192
- * 主同步函数:交互式选择 + 同步到当前目录 .claude/
193
+ * 主同步函数:交互式选择 + 同步到当前目录 .claude/(不含 CLAUDE.md)
194
+ * CLAUDE.md 同步到全局 ~/.claude/
193
195
  */
194
196
  export async function syncClaude() {
195
197
  console.log(chalk.blue('\nScanning available Claude configuration...\n'))
196
198
 
197
199
  const sourceDir = await resolveSourceDir()
198
- const targetDir = path.join(process.cwd(), '.claude')
200
+ const projectTargetDir = path.join(process.cwd(), '.claude')
201
+ const globalTargetDir = path.join(os.homedir(), '.claude')
199
202
 
200
203
  if (!await fs.pathExists(sourceDir)) {
201
204
  console.error(chalk.red('Error: claude-global templates not found in repo'))
@@ -216,28 +219,50 @@ export async function syncClaude() {
216
219
 
217
220
  const totalSelected = selectedSkills.length + selectedRules.length + selectedCommands.length
218
221
  if (totalSelected === 0) {
219
- console.log(chalk.yellow('\nNo items selected. Nothing to sync.'))
220
- return
222
+ console.log(chalk.yellow('\nNo skills/rules/commands selected.'))
221
223
  }
222
224
 
223
- console.log(chalk.blue(`\nSyncing ${totalSelected} items to ${targetDir}...\n`))
225
+ console.log(chalk.blue(`\nSyncing to ${projectTargetDir}...\n`))
224
226
 
225
- await fs.ensureDir(targetDir)
227
+ await fs.ensureDir(projectTargetDir)
226
228
 
227
229
  let syncedCount = 0
228
- syncedCount += await syncSelectedItems(selectedSkills, targetDir)
229
- syncedCount += await syncSelectedItems(selectedRules, targetDir)
230
- syncedCount += await syncSelectedItems(selectedCommands, targetDir)
231
- syncedCount += await syncClaudeMd(sourceDir, targetDir)
232
- syncedCount += await syncSettings(sourceDir, targetDir)
230
+ if (totalSelected > 0) {
231
+ syncedCount += await syncSelectedItems(selectedSkills, projectTargetDir)
232
+ syncedCount += await syncSelectedItems(selectedRules, projectTargetDir)
233
+ syncedCount += await syncSelectedItems(selectedCommands, projectTargetDir)
234
+ }
235
+ syncedCount += await syncSettings(sourceDir, projectTargetDir)
236
+
237
+ // CLAUDE.md 同步到全局
238
+ await fs.ensureDir(globalTargetDir)
239
+ syncedCount += await syncClaudeMd(sourceDir, globalTargetDir)
233
240
 
234
- console.log(chalk.blue(`\nDone! Synced ${syncedCount} items to ${targetDir}`))
241
+ console.log(chalk.blue(`\nDone! Synced ${syncedCount} items.`))
242
+ console.log(chalk.gray(` Project config → ${projectTargetDir}`))
243
+ console.log(chalk.gray(` CLAUDE.md → ${globalTargetDir}`))
235
244
  }
236
245
 
237
246
  /**
238
- * 保留旧的 project-claude 同步逻辑
247
+ * 单独同步 CLAUDE.md 到当前项目 .claude/
239
248
  */
240
- export async function syncProjectClaude() {
241
- console.log(chalk.yellow('"dwy sync project-claude" is deprecated. Use "dwy sync claude" instead.'))
242
- await syncClaude()
249
+ export async function syncProjectClaudeMd() {
250
+ const sourceDir = await resolveSourceDir()
251
+ const targetDir = path.join(process.cwd(), '.claude')
252
+
253
+ if (!await fs.pathExists(sourceDir)) {
254
+ console.error(chalk.red('Error: claude-global templates not found in repo'))
255
+ process.exit(1)
256
+ }
257
+
258
+ await fs.ensureDir(targetDir)
259
+ const synced = await syncClaudeMd(sourceDir, targetDir)
260
+
261
+ if (synced) {
262
+ console.log(chalk.blue(`\nDone! CLAUDE.md synced to ${targetDir}`))
263
+ } else {
264
+ console.log(chalk.yellow('\nCLAUDE.md not found in templates, nothing to sync.'))
265
+ }
243
266
  }
267
+
268
+
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dwy-eapi
3
- description: "dwyeapi FastAPI 后端基础设施速查。触发条件:使用 FastAPI 构建后端、配置数据库/Redis/JWT/异常处理、耗时任务处理时。"
3
+ description: "dwyeapi FastAPI 后端基础设施速查。当用户使用 FastAPI 构建后端、配置数据库/Redis/JWT/异常处理、写 API 响应/分页/日志/健康检查/数据脱敏、处理异步耗时任务、接入邮件验证码发送时,**必须**使用此 skill。即使用户没有明确说'dwyeapi'或'eapi',只要涉及 FastAPI 项目的基础设施搭建、统一响应封装、异常处理、数据库会话管理、Redis 缓存、JWT 认证、后台任务队列、PII 脱敏、日志配置,也应触发。"
4
4
  ---
5
5
 
6
6
  # dwyeapi 后端基础设施速查
@@ -431,3 +431,65 @@ app.include_router(task_router)
431
431
  | POST | `/tasks/{task_id}/cancel` | 取消任务 |
432
432
 
433
433
  > **完整文档:** [tasks-integration-guide.md](references/tasks-integration-guide.md) — 包含 TaskContext API、数据模型、Worker 配置、取消机制等详细说明。
434
+
435
+ ---
436
+
437
+ ## 其他模块速查
438
+
439
+ ### logger — 基于 loguru 的全局日志
440
+
441
+ ```python
442
+ from dwyeapi import logger
443
+
444
+ logger.configure(level="INFO", log_dir="./logs", filename="app")
445
+ log = logger.get_logger("users")
446
+ log.info("用户 %s 登录", user_id)
447
+ ```
448
+
449
+ | 函数 | 说明 |
450
+ |------|------|
451
+ | `logger.configure(...)` | 启动时调用一次;支持按天+按大小轮转、JSON 序列化、拦截 stdlib logging |
452
+ | `logger.get_logger(name?)` | 返回 Logger 门面,支持 `%s` 占位符和 `exc_info=True` |
453
+ | `logger.close()` | 关闭并恢复 stdlib logger(lifespan 结束时调用) |
454
+
455
+ **详细配置参数**:读 `references/logger.md`
456
+
457
+ ### health — 健康检查路由工厂
458
+
459
+ ```python
460
+ from dwyeapi.health import create_health_router
461
+
462
+ router = create_health_router(service_name="my-api", version="1.0.0")
463
+ app.include_router(router)
464
+ # GET /health → {"service": "my-api", "version": "1.0.0", "status": "alive"}
465
+ ```
466
+
467
+ 只探活、不探依赖(防 DoS 放大)。业务如需 readiness 自行实现并限制内网访问。
468
+
469
+ ### providers.email — 邮件验证码发送(可插拔)
470
+
471
+ 内置仅 `resend`,业务可继承 `EmailProviderBase` 注入自定义通道(腾讯云 SES、AWS SES、SMTP 等)。验证码 + Redis + 品牌化模板由基类复用。
472
+
473
+ ```python
474
+ from dwyeapi.providers.email import EmailSettings, make_email_provider, register_email_provider
475
+ ```
476
+
477
+ | 导出项 | 说明 |
478
+ |--------|------|
479
+ | `EmailSettings` | 配置模型(code_ttl, brand_name, resend) |
480
+ | `make_email_provider(settings)` | 工厂函数,按 `.env EMAIL__PROVIDER` 创建实例 |
481
+ | `register_email_provider(name, factory)` | 注册自定义 provider |
482
+ | `EmailProviderBase` | 抽象基类,继承后实现 `_send` 即可 |
483
+
484
+ ---
485
+
486
+ ## 查阅源码
487
+
488
+ 所有模块为单文件设计,直接阅读:
489
+
490
+ ```
491
+ backend/src/dwyeapi/{module}.py
492
+ backend/src/dwyeapi/providers/email/
493
+ ```
494
+
495
+ 模块清单:config、database、dependencies、security、exceptions、response、pagination、cache、dt、masking、logger、health、tasks、providers/email。
@@ -0,0 +1,47 @@
1
+ # health — 健康检查路由工厂
2
+
3
+ > 何时读这份:当用户需要添加健康检查端点、配置 k8s livenessProbe、或讨论 readiness vs liveness 设计时读取。
4
+
5
+ ```python
6
+ from dwyeapi.health import create_health_router
7
+
8
+ router = create_health_router(
9
+ service_name="my-api",
10
+ version="1.0.0",
11
+ path="/health",
12
+ include_in_schema=True,
13
+ )
14
+ app.include_router(router)
15
+ ```
16
+
17
+ ## 设计取舍
18
+
19
+ 端点**只**报告进程是否存活(liveness),**不连接 PostgreSQL / Redis** 等依赖。
20
+
21
+ 原因:健康端点通常对公网开放,若内置 readiness / dependency check,攻击者可用低频高并发请求放大到数据库/缓存层,变成 DoS 放大器。
22
+
23
+ 需要 readiness 语义的业务(如 k8s readinessProbe)应在业务项目里自行实现,并把该端点限制在内网或挂鉴权。
24
+
25
+ ## create_health_router 参数
26
+
27
+ | 参数 | 类型 | 默认值 | 说明 |
28
+ |------|------|--------|------|
29
+ | service_name | str | **必填** | 服务名,通常取自 `Settings.service_name` |
30
+ | version | str | **必填** | 版本号,通常取自 `__version__` |
31
+ | path | str | `"/health"` | 端点路径 |
32
+ | include_in_schema | bool | True | 是否在 OpenAPI 文档暴露 |
33
+
34
+ ## 响应示例
35
+
36
+ ```json
37
+ {
38
+ "code": 200,
39
+ "message": "success",
40
+ "data": {
41
+ "service": "my-api",
42
+ "version": "1.0.0",
43
+ "status": "alive"
44
+ },
45
+ "timestamp": 1775625000
46
+ }
47
+ ```
@@ -0,0 +1,60 @@
1
+ # logger — 基于 loguru 的全局日志
2
+
3
+ > 何时读这份:当用户需要配置日志输出、轮转策略、拦截 uvicorn/SQLAlchemy 日志、或使用 JSON Lines 格式对接日志聚合系统时读取。
4
+
5
+ ```python
6
+ from dwyeapi import logger
7
+
8
+ # 启动时调用一次(通常在 lifespan 中)
9
+ logger.configure(
10
+ level="INFO",
11
+ log_dir="./logs",
12
+ filename="app",
13
+ max_bytes=100 * 1024 * 1024, # 100 MB
14
+ retention="30 days",
15
+ console=True,
16
+ serialize=False,
17
+ enqueue=True,
18
+ intercept_stdlib=True,
19
+ intercept_loggers=["uvicorn", "sqlalchemy"],
20
+ )
21
+
22
+ # 获取命名 logger
23
+ log = logger.get_logger("users")
24
+ log.info("用户 %s 登录", user_id)
25
+ log.error("操作失败", exc_info=True)
26
+ log.exception("未捕获异常") # 自动 exc_info=True
27
+
28
+ # 关闭(lifespan 结束时)
29
+ logger.close()
30
+ ```
31
+
32
+ ## configure 参数
33
+
34
+ | 参数 | 类型 | 默认值 | 说明 |
35
+ |------|------|--------|------|
36
+ | level | str | `"INFO"` | 最低级别:DEBUG/INFO/WARNING/ERROR/CRITICAL |
37
+ | log_dir | str/Path/None | None | 日志文件目录,None 表示关闭文件输出 |
38
+ | filename | str | `"app"` | 文件基础名,最终为 `{filename}_YYYY-MM-DD.log` |
39
+ | max_bytes | int | `100*1024*1024` | 单文件大小上限(字节) |
40
+ | retention | str/int | `"30 days"` | 保留策略(如 `"1 week"`、整数个数) |
41
+ | console | bool | True | 是否输出彩色日志到 stderr |
42
+ | console_format | str/None | None | 自定义控制台格式 |
43
+ | file_format | str/None | None | 自定义文件格式 |
44
+ | serialize | bool | False | 文件 sink 是否按 JSON Lines 序列化 |
45
+ | enqueue | bool | True | 是否异步写入(非阻塞、多进程安全) |
46
+ | intercept_stdlib | bool | True | 是否拦截 stdlib `logging` |
47
+ | intercept_loggers | list[str]/None | None | 限定拦截的 logger 名前缀,None 表示拦截 root |
48
+
49
+ ## Logger 门面 API
50
+
51
+ `get_logger()` 返回的 `Logger` 兼容 stdlib 语义:
52
+
53
+ - `debug(msg, *args, exc_info=False)`
54
+ - `info(msg, *args, exc_info=False)`
55
+ - `warning(msg, *args, exc_info=False)`
56
+ - `error(msg, *args, exc_info=False)`
57
+ - `exception(msg, *args)` — 等价于 `error(msg, *args, exc_info=True)`
58
+ - `critical(msg, *args, exc_info=False)`
59
+
60
+ 所有方法支持 `%s` / `%d` 等 stdlib 占位符,空 args 时保留 msg 原样。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dwy-eui
3
- description: "@dwydev/eui Vue 3 组件库速查。触发条件:使用 Vue 3 构建页面、选择 UI 组件、查询 eui 组件 API 时。"
3
+ description: "@dwydev/eui Vue 3 组件库速查。当用户在使用 Vue 3 构建页面、选择 UI 组件、查询组件 API、写表单/表格/弹层/导航、配置主题或国际化、使用中后台或落地页设计规范时,**必须**使用此 skill。即使用户没有明确说'eui',只要涉及 Vue 3 组件选型、页面布局、表单设计、数据展示、弹层交互、主题切换,也应触发。"
4
4
  ---
5
5
 
6
6
  # @dwydev/eui 组件速查
@@ -150,15 +150,16 @@ EConfigProvider 内部做了两件事:
150
150
  - 样式合并工具:`cn()` = clsx + tailwind-merge
151
151
  - 尺寸统一类型:`Size = 'sm' | 'default' | 'lg'`
152
152
 
153
- ## 查阅完整 API
153
+ ## 查阅完整 API(按需读取源码)
154
154
 
155
- 每个组件的完整 props/slots/events 定义在源码中:
155
+ 当需要某个组件的精确 props/slots/events/类型定义时,**必须**读取对应源码文件:
156
156
 
157
157
  ```
158
158
  frontend/eui/src/components/{component-name}/types.ts
159
+ frontend/eui/src/components/{component-name}/EName.vue # 模板和逻辑
159
160
  ```
160
161
 
161
- 需要具体某个组件的详细 API 时,直接读取对应的 `types.ts` 文件。
162
+ 组件目录清单(89 个组件):`frontend/eui/src/components/` 下包含 button、input、table、dialog、date-picker、chart、admin-layout、ai-chat 等。不确定组件名时,先 `ls frontend/eui/src/components/` 查看。
162
163
 
163
164
  ---
164
165
 
@@ -307,9 +308,11 @@ frontend/eui/src/components/{component-name}/types.ts
307
308
  | useMessage() | 全局消息提示 | { success, warning, error, info } — 每个接收 string 或 MessageOptions |
308
309
  | useNotification() | 通知 | { success, warning, error, info } — 接收 NotificationOptions(title, message, position, duration, onClick, closable) |
309
310
  | useMessageBox() | 对话确认 | { alert, confirm, prompt } — 返回 Promise<'confirm' \| 'cancel'> |
311
+ | useToast() | Toast 通知(与 EToast 组件配合使用) | { toast, dismiss, toasts } — toast 接收 ToastOptions(title, description, variant) |
310
312
  | useTheme() | 主题管理 | { isDark, theme, setTheme, toggleDark, colorTheme, setColorTheme } |
311
313
  | useConfigProvider() | 读取全局配置 | { size, zIndex, locale } |
312
314
  | useFormField() | 表单字段上下文 | { id, name, formItemId, valid, isDirty, isTouched, error }(需在 FormField 内使用) |
315
+ | useSecureValue() | 敏感输入安全绑定 | { isComposing, recordCursor, setCursor, setNativeValue, onCompositionStart, onCompositionEnd } — 通过 DOM property 赋值避免密码泄露到 HTML attribute |
313
316
 
314
317
  ## 主题系统
315
318