create-dwy 0.10.0 → 0.12.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/package.json +3 -2
- package/src/prompts/grouped-checkbox.js +223 -0
- package/src/sync.js +61 -39
- package/templates/claude-global/hooks/pre-git-commit-sensitive-check.sh +0 -0
- package/templates/claude-global/rules/{dwy-python-backend.md → Python/dwy-python-backend.md} +43 -3
- package/templates/claude-global/rules/{dwy-python-core.md → Python/dwy-python-core.md} +35 -2
- package/templates/claude-global/skills//345/237/272/347/241/200/345/272/223/dwy-eapi/SKILL.md +226 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/SKILL.md +144 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/create-database-table.md +219 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/crud-operations.md +281 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/data-import.md +271 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/docker-deployment.md +63 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/performance-tuning.md +223 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/pitfalls-and-best-practices.md +352 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/python-api.md +306 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/setup-and-connection.md +143 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/sources.md +46 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/sql-batch-compute.md +206 -0
- package/templates/claude-global/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-cibuildwheel/SKILL.md +354 -0
- package/templates/claude-global/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-cibuildwheel/references/release-workflow-template.yml +99 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_base.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_capacity.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_db.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_docker.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_env.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_https.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_logs.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_nginx.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_resilience.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_secrets.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_services.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_ssh.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/run_all.sh +0 -0
- package/templates/claude-global/skills/{dwy-docker → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker}/scripts/query_dockerhub.py +0 -0
- package/templates/claude-global/skills/{dwy-docker → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker}/scripts/scan_images.sh +0 -0
- package/templates/claude-global/skills/{dwy-mirror-source → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source}/scripts/apply_mirrors.py +0 -0
- package/templates/claude-global/skills/{dwy-mirror-source → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source}/scripts/check_mirrors.py +0 -0
- package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +0 -98
- package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +0 -67
- package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +0 -441
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +0 -495
- package/templates/claude-global/skills/dwy-eapi/references/health.md +0 -47
- package/templates/claude-global/skills/dwy-eapi/references/logger.md +0 -60
- /package/templates/claude-global/rules/{dwy-android-core.md → Android/dwy-android-core.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-core.md → Flutter/dwy-flutter-core.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-network.md → Flutter/dwy-flutter-network.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-router.md → Flutter/dwy-flutter-router.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-testing.md → Flutter/dwy-flutter-testing.md} +0 -0
- /package/templates/claude-global/rules/{dwy-python-testing.md → Python/dwy-python-testing.md} +0 -0
- /package/templates/claude-global/rules/{dwy-vue-core.md → Vue/dwy-vue-core.md} +0 -0
- /package/templates/claude-global/rules/{dwy-vue-pinia.md → Vue/dwy-vue-pinia.md} +0 -0
- /package/templates/claude-global/rules/{dwy-vue-testing.md → Vue/dwy-vue-testing.md} +0 -0
- /package/templates/claude-global/rules/{dwy-tdd-development.md → /345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md"} +0 -0
- /package/templates/claude-global/rules/{dwy-db-migration.md → /346/225/260/346/215/256/345/272/223/dwy-db-migration.md"} +0 -0
- /package/templates/claude-global/rules/{dwy-postgres.md → /346/225/260/346/215/256/345/272/223/dwy-postgres.md"} +0 -0
- /package/templates/claude-global/rules/{dwy-redis.md → /346/225/260/346/215/256/345/272/223/dwy-redis.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eapi/references/tasks-integration-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eapi/references/tasks-integration-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-ekit/SKILL.md → /345/237/272/347/241/200/345/272/223/dwy-ekit/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/SKILL.md → /345/237/272/347/241/200/345/272/223/dwy-eui/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/references/eui-design-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eui/references/eui-design-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/references/eui-integration-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eui/references/eui-integration-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/references/eui-landing-design-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eui/references/eui-landing-design-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/SKILL.md → /345/256/211/345/205/250/dwy-pentest/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/.python-version → /345/256/211/345/205/250/dwy-pentest/scripts/.python-version"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__init__.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__init__.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/config.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/config.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/executor.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/executor.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/severity.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/severity.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/validator.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/validator.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_auth.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_auth.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_recon.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_recon.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_report.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_report.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_scan.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_scan.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_ssl.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_ssl.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_vuln.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_vuln.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_web.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_web.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pyproject.toml → /345/256/211/345/205/250/dwy-pentest/scripts/pyproject.toml"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/setup_tools.sh → /345/256/211/345/205/250/dwy-pentest/scripts/setup_tools.sh"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/uv.lock → /345/256/211/345/205/250/dwy-pentest/scripts/uv.lock"} +0 -0
- /package/templates/claude-global/skills/{dwy-dev-qa/SKILL.md → /345/274/200/345/217/221/346/265/201/347/250/213/dwy-dev-qa/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-git-commit/SKILL.md → /345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-fullstack-scaffold/SKILL.md → /350/204/232/346/211/213/346/236/266/dwy-fullstack-scaffold/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-tech-stack/SKILL.md → /350/204/232/346/211/213/346/236/266/dwy-tech-stack/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/references/checks-data.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/references/checks-data.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/references/checks-network.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/references/checks-network.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/references/checks-runtime.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/references/checks-runtime.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/references/ask-templates.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/references/ask-templates.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/references/templates.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/references/templates.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/references/version-rules.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/references/version-rules.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/references/dockerfile-snippets.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/references/dockerfile-snippets.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/references/mirror-providers.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/references/mirror-providers.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-publish/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-publish/SKILL.md"} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-dwy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "项目脚手架与 Claude Code 配置同步工具",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"author": "dwydev",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@inquirer/core": "^10.0.0",
|
|
23
24
|
"commander": "^12.0.0",
|
|
24
25
|
"inquirer": "^9.0.0",
|
|
25
26
|
"handlebars": "^4.7.0",
|
|
@@ -31,4 +32,4 @@
|
|
|
31
32
|
"registry": "https://registry.npmjs.org/",
|
|
32
33
|
"access": "public"
|
|
33
34
|
}
|
|
34
|
-
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPrompt,
|
|
3
|
+
useState,
|
|
4
|
+
useMemo,
|
|
5
|
+
useKeypress,
|
|
6
|
+
usePrefix,
|
|
7
|
+
usePagination,
|
|
8
|
+
isUpKey,
|
|
9
|
+
isDownKey,
|
|
10
|
+
isSpaceKey,
|
|
11
|
+
isBackspaceKey,
|
|
12
|
+
isEnterKey,
|
|
13
|
+
makeTheme,
|
|
14
|
+
} from '@inquirer/core'
|
|
15
|
+
import chalk from 'chalk'
|
|
16
|
+
|
|
17
|
+
const OTHER = '其他'
|
|
18
|
+
|
|
19
|
+
function sortCategories(cats) {
|
|
20
|
+
const others = cats.includes(OTHER) ? [OTHER] : []
|
|
21
|
+
const rest = cats.filter(c => c !== OTHER).sort((a, b) => a.localeCompare(b, 'zh'))
|
|
22
|
+
return [...rest, ...others]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildVisibleRows(items, filter, selected) {
|
|
26
|
+
const kw = filter.toLowerCase()
|
|
27
|
+
const filtered = kw
|
|
28
|
+
? items.filter(it => it.name.toLowerCase().includes(kw)
|
|
29
|
+
|| (it.description || '').toLowerCase().includes(kw))
|
|
30
|
+
: items
|
|
31
|
+
const groups = new Map()
|
|
32
|
+
for (const it of filtered) {
|
|
33
|
+
const cat = it.category || OTHER
|
|
34
|
+
if (!groups.has(cat)) groups.set(cat, [])
|
|
35
|
+
groups.get(cat).push(it)
|
|
36
|
+
}
|
|
37
|
+
const rows = []
|
|
38
|
+
for (const cat of sortCategories([...groups.keys()])) {
|
|
39
|
+
const groupItems = groups.get(cat).sort((a, b) => a.name.localeCompare(b.name))
|
|
40
|
+
const selectedInGroup = groupItems.filter(it => selected.has(it.name)).length
|
|
41
|
+
rows.push({ type: 'header', category: cat, total: groupItems.length, selectedInGroup, items: groupItems })
|
|
42
|
+
for (const it of groupItems) rows.push({ type: 'item', item: it, category: cat })
|
|
43
|
+
}
|
|
44
|
+
return rows
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isPrintable(key) {
|
|
48
|
+
return typeof key.sequence === 'string'
|
|
49
|
+
&& key.sequence.length === 1
|
|
50
|
+
&& key.sequence >= ' '
|
|
51
|
+
&& key.sequence <= '~'
|
|
52
|
+
&& !key.ctrl
|
|
53
|
+
&& !key.meta
|
|
54
|
+
&& key.name !== 'space'
|
|
55
|
+
&& key.name !== 'return'
|
|
56
|
+
&& key.name !== 'enter'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isEscapeKey(key) {
|
|
60
|
+
return key.name === 'escape'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isCtrlA(key) {
|
|
64
|
+
return key.ctrl && key.name === 'a'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function nextCursor(rows, cursor, delta) {
|
|
68
|
+
if (rows.length === 0) return 0
|
|
69
|
+
const n = rows.length
|
|
70
|
+
let i = ((cursor + delta) % n + n) % n
|
|
71
|
+
// 防止死循环(理论上 rows 永不为空时不会触发)
|
|
72
|
+
return i
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const groupedCheckbox = createPrompt((config, done) => {
|
|
76
|
+
const theme = makeTheme(config.theme)
|
|
77
|
+
const pageSize = config.pageSize ?? 15
|
|
78
|
+
const allItems = config.items
|
|
79
|
+
const totalCount = allItems.length
|
|
80
|
+
|
|
81
|
+
const [filter, setFilter] = useState('')
|
|
82
|
+
const [cursor, setCursor] = useState(0)
|
|
83
|
+
const [selected, setSelected] = useState(new Set(config.defaultSelected ?? []))
|
|
84
|
+
const [status, setStatus] = useState('idle') // 'idle' | 'done'
|
|
85
|
+
|
|
86
|
+
const rows = useMemo(
|
|
87
|
+
() => buildVisibleRows(allItems, filter, selected),
|
|
88
|
+
[filter, selected],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// cursor 越界保护
|
|
92
|
+
const safeCursor = rows.length === 0 ? 0 : Math.min(cursor, rows.length - 1)
|
|
93
|
+
|
|
94
|
+
useKeypress((key) => {
|
|
95
|
+
if (status === 'done') return
|
|
96
|
+
|
|
97
|
+
if (isEnterKey(key)) {
|
|
98
|
+
const result = allItems.filter(it => selected.has(it.name))
|
|
99
|
+
setStatus('done')
|
|
100
|
+
done(result)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isEscapeKey(key)) {
|
|
105
|
+
if (filter) {
|
|
106
|
+
setFilter('')
|
|
107
|
+
setCursor(0)
|
|
108
|
+
} else {
|
|
109
|
+
setStatus('done')
|
|
110
|
+
done([])
|
|
111
|
+
}
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (isUpKey(key)) {
|
|
116
|
+
setCursor(nextCursor(rows, safeCursor, -1))
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
if (isDownKey(key)) {
|
|
120
|
+
setCursor(nextCursor(rows, safeCursor, +1))
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isCtrlA(key)) {
|
|
125
|
+
// 全选/全不选当前可见 item
|
|
126
|
+
const visibleItems = rows.filter(r => r.type === 'item').map(r => r.item)
|
|
127
|
+
if (visibleItems.length === 0) return
|
|
128
|
+
const allSelected = visibleItems.every(it => selected.has(it.name))
|
|
129
|
+
const next = new Set(selected)
|
|
130
|
+
if (allSelected) visibleItems.forEach(it => next.delete(it.name))
|
|
131
|
+
else visibleItems.forEach(it => next.add(it.name))
|
|
132
|
+
setSelected(next)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isSpaceKey(key)) {
|
|
137
|
+
if (rows.length === 0) return
|
|
138
|
+
const row = rows[safeCursor]
|
|
139
|
+
const next = new Set(selected)
|
|
140
|
+
if (row.type === 'item') {
|
|
141
|
+
if (next.has(row.item.name)) next.delete(row.item.name)
|
|
142
|
+
else next.add(row.item.name)
|
|
143
|
+
} else {
|
|
144
|
+
// header:组内全选/全不选
|
|
145
|
+
const allSelected = row.items.every(it => next.has(it.name))
|
|
146
|
+
if (allSelected) row.items.forEach(it => next.delete(it.name))
|
|
147
|
+
else row.items.forEach(it => next.add(it.name))
|
|
148
|
+
}
|
|
149
|
+
setSelected(next)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isBackspaceKey(key)) {
|
|
154
|
+
if (filter) {
|
|
155
|
+
setFilter(filter.slice(0, -1))
|
|
156
|
+
setCursor(0)
|
|
157
|
+
}
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isPrintable(key)) {
|
|
162
|
+
setFilter(filter + key.sequence)
|
|
163
|
+
setCursor(0)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const prefix = usePrefix({ theme })
|
|
169
|
+
|
|
170
|
+
if (status === 'done') {
|
|
171
|
+
const picked = allItems.filter(it => selected.has(it.name))
|
|
172
|
+
const summary = picked.length === 0
|
|
173
|
+
? chalk.gray('(未选)')
|
|
174
|
+
: chalk.green(`已选 ${picked.length} 项`)
|
|
175
|
+
return `${prefix} ${theme.style.message(config.message, 'done')} ${summary}`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const filterLine = filter
|
|
179
|
+
? `${chalk.cyan('🔍')} ${chalk.yellow(filter)}${chalk.gray('▏')} ${chalk.gray(`${rows.filter(r => r.type === 'item').length}/${totalCount} 匹配`)}`
|
|
180
|
+
: `${chalk.gray('🔍 输入即过滤 ')}${chalk.gray(`${totalCount} 项`)}`
|
|
181
|
+
|
|
182
|
+
const hint = chalk.gray('↑↓ 移动 · space 切换/组全选 · ctrl+a 全选可见 · enter 确认 · esc 清搜索/退出')
|
|
183
|
+
|
|
184
|
+
const renderItem = ({ item: row, isActive }) => {
|
|
185
|
+
if (row.type === 'header') {
|
|
186
|
+
const fullSelected = row.selectedInGroup === row.total && row.total > 0
|
|
187
|
+
const partial = row.selectedInGroup > 0 && !fullSelected
|
|
188
|
+
const marker = fullSelected ? chalk.green('✓') : partial ? chalk.yellow('◐') : chalk.gray('○')
|
|
189
|
+
const arrow = isActive ? chalk.cyan('▶') : ' '
|
|
190
|
+
const label = chalk.bold(`${arrow} ${marker} ${row.category}`)
|
|
191
|
+
const count = chalk.gray(`[${row.selectedInGroup}/${row.total}]`)
|
|
192
|
+
return isActive ? chalk.bgGray.white(`${label} ${count}`) : `${label} ${count}`
|
|
193
|
+
}
|
|
194
|
+
const checked = selected.has(row.item.name)
|
|
195
|
+
const box = checked ? chalk.green('◉') : chalk.gray('◯')
|
|
196
|
+
const cursorMark = isActive ? chalk.cyan('❯') : ' '
|
|
197
|
+
const name = checked ? chalk.green(row.item.name) : chalk.cyan(row.item.name)
|
|
198
|
+
const desc = row.item.description || ''
|
|
199
|
+
const descShort = desc.length > 60 ? desc.slice(0, 60) + '…' : desc
|
|
200
|
+
const line = ` ${cursorMark} ${box} ${name} ${chalk.gray('- ' + descShort)}`
|
|
201
|
+
return isActive ? chalk.bgBlackBright(line) : line
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const paginated = usePagination({
|
|
205
|
+
items: rows.length > 0 ? rows : [{ type: 'empty' }],
|
|
206
|
+
active: safeCursor,
|
|
207
|
+
renderItem: (ctx) => ctx.item.type === 'empty'
|
|
208
|
+
? chalk.gray(' (无匹配项,按 esc 清空搜索)')
|
|
209
|
+
: renderItem(ctx),
|
|
210
|
+
pageSize,
|
|
211
|
+
loop: false,
|
|
212
|
+
})
|
|
213
|
+
const page = paginated
|
|
214
|
+
|
|
215
|
+
const message = theme.style.message(config.message, 'idle')
|
|
216
|
+
|
|
217
|
+
return [
|
|
218
|
+
`${prefix} ${message}`,
|
|
219
|
+
filterLine,
|
|
220
|
+
page,
|
|
221
|
+
hint,
|
|
222
|
+
].join('\n')
|
|
223
|
+
})
|
package/src/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import inquirer from 'inquirer'
|
|
5
5
|
import { ensureRepoCache, chalk } from './utils.js'
|
|
6
|
+
import { groupedCheckbox } from './prompts/grouped-checkbox.js'
|
|
6
7
|
|
|
7
8
|
const CATEGORIES = [
|
|
8
9
|
{ key: 'skills', label: 'Skills' },
|
|
@@ -14,29 +15,51 @@ const CATEGORIES = [
|
|
|
14
15
|
function extractDescription(content) {
|
|
15
16
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---/)
|
|
16
17
|
if (!match) return ''
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const lines = match[1].split('\n')
|
|
19
|
+
let i = 0
|
|
20
|
+
while (i < lines.length) {
|
|
21
|
+
const kv = lines[i].match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$/)
|
|
22
|
+
if (!kv) { i++; continue }
|
|
23
|
+
if (kv[1] === 'description') {
|
|
24
|
+
const trimmed = kv[2].trim()
|
|
25
|
+
if (trimmed === '>' || trimmed === '|') {
|
|
26
|
+
i++
|
|
27
|
+
const parts = []
|
|
28
|
+
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].trim() === '')) {
|
|
29
|
+
parts.push(lines[i].trim())
|
|
30
|
+
i++
|
|
31
|
+
}
|
|
32
|
+
return parts.filter(Boolean).join(' ')
|
|
33
|
+
}
|
|
34
|
+
return trimmed.replace(/^["']|["']$/g, '')
|
|
35
|
+
}
|
|
36
|
+
i++
|
|
37
|
+
}
|
|
38
|
+
return ''
|
|
20
39
|
}
|
|
21
40
|
|
|
22
41
|
async function scanSkills(sourceDir) {
|
|
23
42
|
const skillsDir = path.join(sourceDir, 'skills')
|
|
24
43
|
if (!await fs.pathExists(skillsDir)) return []
|
|
25
44
|
|
|
26
|
-
const entries = await fs.readdir(skillsDir, { withFileTypes: true })
|
|
27
45
|
const skills = []
|
|
28
|
-
for (const
|
|
29
|
-
if (!
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
for (const cat of await fs.readdir(skillsDir, { withFileTypes: true })) {
|
|
47
|
+
if (!cat.isDirectory()) continue
|
|
48
|
+
const catDir = path.join(skillsDir, cat.name)
|
|
49
|
+
for (const entry of await fs.readdir(catDir, { withFileTypes: true })) {
|
|
50
|
+
if (!entry.isDirectory()) continue
|
|
51
|
+
const skillPath = path.join(catDir, entry.name)
|
|
52
|
+
const skillMd = path.join(skillPath, 'SKILL.md')
|
|
53
|
+
if (!await fs.pathExists(skillMd)) continue
|
|
54
|
+
const content = await fs.readFile(skillMd, 'utf-8')
|
|
55
|
+
skills.push({
|
|
56
|
+
name: entry.name,
|
|
57
|
+
description: extractDescription(content) || '(无描述)',
|
|
58
|
+
category: cat.name,
|
|
59
|
+
sourcePath: skillPath,
|
|
60
|
+
type: 'skill',
|
|
61
|
+
})
|
|
62
|
+
}
|
|
40
63
|
}
|
|
41
64
|
return skills.sort((a, b) => a.name.localeCompare(b.name))
|
|
42
65
|
}
|
|
@@ -45,18 +68,22 @@ async function scanRules(sourceDir) {
|
|
|
45
68
|
const rulesDir = path.join(sourceDir, 'rules')
|
|
46
69
|
if (!await fs.pathExists(rulesDir)) return []
|
|
47
70
|
|
|
48
|
-
const entries = await fs.readdir(rulesDir, { withFileTypes: true })
|
|
49
71
|
const rules = []
|
|
50
|
-
for (const
|
|
51
|
-
if (!
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
for (const cat of await fs.readdir(rulesDir, { withFileTypes: true })) {
|
|
73
|
+
if (!cat.isDirectory()) continue
|
|
74
|
+
const catDir = path.join(rulesDir, cat.name)
|
|
75
|
+
for (const entry of await fs.readdir(catDir, { withFileTypes: true })) {
|
|
76
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue
|
|
77
|
+
const rulePath = path.join(catDir, entry.name)
|
|
78
|
+
const content = await fs.readFile(rulePath, 'utf-8')
|
|
79
|
+
rules.push({
|
|
80
|
+
name: entry.name,
|
|
81
|
+
description: extractDescription(content) || '(无描述)',
|
|
82
|
+
category: cat.name,
|
|
83
|
+
sourcePath: rulePath,
|
|
84
|
+
type: 'rule',
|
|
85
|
+
})
|
|
86
|
+
}
|
|
60
87
|
}
|
|
61
88
|
return rules.sort((a, b) => a.name.localeCompare(b.name))
|
|
62
89
|
}
|
|
@@ -71,6 +98,7 @@ async function scanCommands(sourceDir) {
|
|
|
71
98
|
.map(e => ({
|
|
72
99
|
name: e.name,
|
|
73
100
|
description: e.isDirectory() ? '命令目录' : '命令文件',
|
|
101
|
+
category: '其他',
|
|
74
102
|
sourcePath: path.join(commandsDir, e.name),
|
|
75
103
|
type: 'command',
|
|
76
104
|
}))
|
|
@@ -87,6 +115,7 @@ async function scanHooks(sourceDir) {
|
|
|
87
115
|
.map(e => ({
|
|
88
116
|
name: e.name,
|
|
89
117
|
description: e.isDirectory() ? '钩子目录' : '钩子脚本',
|
|
118
|
+
category: '其他',
|
|
90
119
|
sourcePath: path.join(hooksDir, e.name),
|
|
91
120
|
type: 'hook',
|
|
92
121
|
}))
|
|
@@ -102,19 +131,12 @@ async function scanExisting(projectTargetDir, typePlural) {
|
|
|
102
131
|
|
|
103
132
|
async function promptSelection(items, label, defaultNames) {
|
|
104
133
|
if (items.length === 0) return []
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
choices: items.map(item => ({
|
|
110
|
-
name: `${chalk.cyan(item.name)} ${chalk.gray('- ' + item.description.slice(0, 60))}${item.description.length > 60 ? '...' : ''}`,
|
|
111
|
-
value: item,
|
|
112
|
-
short: item.name,
|
|
113
|
-
checked: defaultNames.has(item.name),
|
|
114
|
-
})),
|
|
134
|
+
return groupedCheckbox({
|
|
135
|
+
message: `选择 ${label}`,
|
|
136
|
+
items,
|
|
137
|
+
defaultSelected: defaultNames,
|
|
115
138
|
pageSize: 15,
|
|
116
|
-
}
|
|
117
|
-
return selected
|
|
139
|
+
})
|
|
118
140
|
}
|
|
119
141
|
|
|
120
142
|
/**
|
|
File without changes
|
package/templates/claude-global/rules/{dwy-python-backend.md → Python/dwy-python-backend.md}
RENAMED
|
@@ -200,7 +200,7 @@ class UserBase(BaseModel):
|
|
|
200
200
|
email: EmailStr
|
|
201
201
|
|
|
202
202
|
class UserCreate(UserBase):
|
|
203
|
-
password: str = Field(min_length=8, max_length=
|
|
203
|
+
password: str = Field(min_length=8, max_length=72) # 72 = bcrypt 输入上限
|
|
204
204
|
|
|
205
205
|
class UserUpdate(BaseModel):
|
|
206
206
|
name: str | None = Field(default=None, max_length=50)
|
|
@@ -330,15 +330,55 @@ user = result.scalar_one_or_none()
|
|
|
330
330
|
|------|------|
|
|
331
331
|
| 存储算法 | **只允许 bcrypt**,必须走 dwyeapi 安全模块,**禁止**手写 bcrypt 调用或使用 MD5 / SHA / 明文 |
|
|
332
332
|
| 接口返回 | **永远不返回**密码字段,Response Schema 中不包含 |
|
|
333
|
-
| 密码强度 |
|
|
333
|
+
| 密码强度 | **长度 ≥ 8 字符**,且**必须**包含 **大写字母、小写字母、数字、特殊符号** 中的至少 **3 类** |
|
|
334
|
+
| 密码长度上限 | Schema 必须 `max_length=72`(bcrypt 输入硬上限,超过会被静默截断),除非 dwyeapi 已做 SHA-256 预哈希 |
|
|
334
335
|
| 登录错误 | 返回"用户名或密码错误",**不区分**是用户名不存在还是密码错误 |
|
|
336
|
+
| 强度校验位置 | Pydantic Schema 层用 `field_validator` 校验复杂度,**禁止**只校长度 |
|
|
337
|
+
|
|
338
|
+
**密码复杂度校验示例**:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
import re
|
|
342
|
+
from pydantic import BaseModel, Field, field_validator
|
|
343
|
+
|
|
344
|
+
PASSWORD_PATTERNS = [
|
|
345
|
+
re.compile(r"[A-Z]"), # 大写
|
|
346
|
+
re.compile(r"[a-z]"), # 小写
|
|
347
|
+
re.compile(r"\d"), # 数字
|
|
348
|
+
re.compile(r"[^A-Za-z0-9]"), # 特殊符号
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
class UserCreate(BaseModel):
|
|
352
|
+
password: str = Field(min_length=8, max_length=72)
|
|
353
|
+
|
|
354
|
+
@field_validator("password")
|
|
355
|
+
@classmethod
|
|
356
|
+
def check_complexity(cls, v: str) -> str:
|
|
357
|
+
"""密码必须包含大写/小写/数字/特殊符号中至少 3 类。"""
|
|
358
|
+
matched = sum(1 for p in PASSWORD_PATTERNS if p.search(v))
|
|
359
|
+
if matched < 3:
|
|
360
|
+
raise ValueError("密码必须包含大写、小写、数字、特殊符号中至少 3 类")
|
|
361
|
+
return v
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
> 该 validator 建议下沉到 dwyeapi 提供统一的 `PasswordStr` 类型,避免每个项目重复实现。
|
|
335
365
|
|
|
336
366
|
### 7.3 接口认证与授权
|
|
337
367
|
|
|
338
368
|
- 所有 API **必须** JWT 认证(白名单除外:`/health` / `/auth/login` / `/auth/register`)
|
|
339
369
|
- JWT 创建与解码**必须**走 dwyeapi 安全模块,**禁止**直接调用 PyJWT
|
|
340
370
|
- Token 黑名单存 Redis(登出后失效,TTL = 剩余有效期)
|
|
341
|
-
- Secret Key
|
|
371
|
+
- Secret Key 必须从环境变量读取,**禁止**硬编码
|
|
372
|
+
- Secret Key 长度与生成方式(按字节算,不按字符算):
|
|
373
|
+
|
|
374
|
+
| JWT 算法 | 最小长度 | 推荐生成命令 |
|
|
375
|
+
|---------|---------|------------|
|
|
376
|
+
| HS256 | **32 字节 / 256 bit** | `openssl rand -hex 32`(输出 64 个十六进制字符 = 32 字节) |
|
|
377
|
+
| HS512 | **64 字节 / 512 bit** | `openssl rand -hex 64`(输出 128 个十六进制字符 = 64 字节) |
|
|
378
|
+
| Python 等价 | — | `python -c "import secrets; print(secrets.token_urlsafe(32))"` |
|
|
379
|
+
|
|
380
|
+
- **禁止**使用:`uuid.uuid4().hex`(熵不足且非密码学 PRNG 推荐用途)、`random` 模块、手写字符串拼接
|
|
381
|
+
- 轮换策略:每 90 天 / 安全事件后立即轮换;新旧 Key 共存 1 个 Access Token 有效期窗口,避免在线用户被强制登出
|
|
342
382
|
|
|
343
383
|
| Token 类型 | 有效期 |
|
|
344
384
|
|------------|--------|
|
|
@@ -18,7 +18,10 @@ paths:
|
|
|
18
18
|
### 环境变量
|
|
19
19
|
|
|
20
20
|
- **禁止**在 shell 前台 `export FOO=...` 注入环境变量
|
|
21
|
-
-
|
|
21
|
+
- **禁止**在代码中硬编码以下配置值:
|
|
22
|
+
- **基础设施**:数据库地址、Redis / MQ 地址、密钥、端口、外部 API 地址
|
|
23
|
+
- **业务参数**:缓存 TTL、HTTP 超时、重试次数 / 间隔、批处理大小、限流阈值(rate / burst)、分页默认值与上限、文件大小上限、Token 有效期、定时任务周期
|
|
24
|
+
- 判定标准:**任何"运维 / 业务可能想调整、且不希望发版"的值,都必须走配置**。代码中只允许出现:算法常量(PI、HTTP 状态码)、协议规定的固定值、纯类型枚举(如 `Status.ACTIVE = 1`)
|
|
22
25
|
- 环境变量统一通过 `.env` 文件加载,由 Pydantic Settings 读取(见「配置管理」节)
|
|
23
26
|
- `.env` 必须列入 `.gitignore`,提交 `.env.example` 作为模板
|
|
24
27
|
|
|
@@ -149,6 +152,36 @@ class Settings(BaseSettings):
|
|
|
149
152
|
| 只有 1 个字段 | 保持顶层,不必嵌套 |
|
|
150
153
|
| 3 个以上同前缀字段 | 必须嵌套 |
|
|
151
154
|
|
|
155
|
+
### 什么必须走配置(判定表)
|
|
156
|
+
|
|
157
|
+
写代码时遇到字面量数字 / 字符串,先对照此表判断是否抽到 Settings:
|
|
158
|
+
|
|
159
|
+
| 类别 | 示例 | 是否走配置 |
|
|
160
|
+
|------|------|-----------|
|
|
161
|
+
| 基础设施地址 | `postgresql://...` / `redis://...` / `https://api.xxx.com` | ✅ 必须 |
|
|
162
|
+
| 凭证 | SECRET_KEY / API_KEY / DB 密码 / OSS AK·SK | ✅ 必须(致命) |
|
|
163
|
+
| 缓存 TTL | `redis.setex(key, 43200, v)` 中的 `43200` | ✅ 必须 → `settings.cache.user_token_ttl` |
|
|
164
|
+
| HTTP 超时 / 重试 | `httpx.AsyncClient(timeout=30)` 中的 `30` | ✅ 必须 |
|
|
165
|
+
| 限流阈值 | `@rate_limit(100, per=60)` 中的 `100` / `60` | ✅ 必须 |
|
|
166
|
+
| 分页默认 / 上限 | `page_size: int = 20` / `le=100` 的 `100` | ✅ 必须 |
|
|
167
|
+
| 文件大小上限 | `if size > 10 * 1024 * 1024` 的 `10MB` | ✅ 必须 |
|
|
168
|
+
| Token 有效期 | JWT `exp` 的 7 天 / 30 天 | ✅ 必须 |
|
|
169
|
+
| 定时任务周期 | Celery / APScheduler 的 cron / interval | ✅ 必须 |
|
|
170
|
+
| 算法常量 | `PI = 3.14159` / `EARTH_RADIUS = 6371` | ❌ 不走配置 |
|
|
171
|
+
| 协议规定值 | HTTP 状态码 `200` / `404` | ❌ 不走配置 |
|
|
172
|
+
| 纯枚举值 | `class Status: ACTIVE = 1` | ❌ 不走配置 |
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# ❌ 错误:缓存 TTL 硬编码
|
|
176
|
+
await redis.setex(f"user:{uid}", 43200, json.dumps(user))
|
|
177
|
+
|
|
178
|
+
# ✅ 正确:走 Settings
|
|
179
|
+
class CacheConfig(BaseModel):
|
|
180
|
+
user_ttl: int = 43200 # 默认 12h,可通过 CACHE__USER_TTL 覆盖
|
|
181
|
+
|
|
182
|
+
await redis.setex(f"user:{uid}", settings.cache.user_ttl, json.dumps(user))
|
|
183
|
+
```
|
|
184
|
+
|
|
152
185
|
## 六、函数与类设计
|
|
153
186
|
|
|
154
187
|
### 强制规则
|
|
@@ -290,6 +323,6 @@ user = cache.get_user(user_id)
|
|
|
290
323
|
| 8 | 嵌套不超过 3 层,函数体不超过 50 行 |
|
|
291
324
|
| 9 | 所有模块/类/函数有 docstring(Google 风格) |
|
|
292
325
|
| 10 | 外部输入用 Pydantic 校验,不手动校验 dict |
|
|
293
|
-
| 11 | 配置必须 Pydantic Settings
|
|
326
|
+
| 11 | 配置必须 Pydantic Settings 嵌套分组;缓存 TTL / 超时 / 重试 / 限流 / 分页上限 / 文件上限 / Token 有效期等业务参数禁止硬编码 |
|
|
294
327
|
|
|
295
328
|
**不执行自检就提交代码 = 违规。**
|