@yuchenrx/pmt-cli 0.1.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/AGENTS.md ADDED
@@ -0,0 +1,128 @@
1
+ # AGENTS.md
2
+
3
+ ## Purpose
4
+ - This file provides build, test, and style guidance for agentic coding.
5
+ - Follow these conventions unless a user request overrides them.
6
+
7
+ ## Workspace
8
+ - Root: `E:\study\node\md-cli`
9
+ - Runtime: Bun (ESM, `type: module`).
10
+ - Entry: `src/cli.ts`.
11
+
12
+ ## Build and Run
13
+ - Run CLI locally: `bun src/cli.ts`
14
+ - NPM script: `bun run pmt`
15
+ - Build binary: `bun run build`
16
+ - Build output: `dist/<cliName>.exe` (default: `dist/pmt.exe`)
17
+
18
+ ## Test
19
+ - Run all tests: `bun test`
20
+ - Run a single test file: `bun test tests/<file>.test.ts`
21
+ - Run by name pattern (Bun supports filter): `bun test --filter "name"`
22
+ - Coverage (optional): `bun test --coverage`
23
+
24
+ ## Lint/Format
25
+ - No explicit lint or formatter configured.
26
+ - Do not introduce new linters/formatters without request.
27
+
28
+ ## Repo Structure
29
+ - `src/cli.ts` initializes Commander and registers commands.
30
+ - `src/commands.ts` declares CLI commands and arguments.
31
+ - `src/command-*.ts` holds command implementations.
32
+ - `src/config.ts` loads/merges config and resolves paths.
33
+ - `src/helpers.ts` provides filesystem/path helpers.
34
+ - `src/link*.ts` handles link metadata and linking logic.
35
+ - `src/completion.ts` handles shell completion.
36
+ - `scripts/build.ts` builds the binary.
37
+ - Tests live in `tests/*.test.ts`.
38
+
39
+ ## Configuration Files
40
+ - Project config: `.promptslotrc.json` in repo root.
41
+ - Global config: `~/.promptslotrc.json`.
42
+ - Link store: `.promptslot-links.json` in repo root.
43
+ - Index: `.promptslot-index.json` in repo root.
44
+
45
+ ## CLI Conventions
46
+ - Use Commander `Command` with chained calls.
47
+ - Use `.argument()` for positional arguments.
48
+ - Use `.option()` only when truly optional.
49
+ - Use async handlers: `.action(async () => { ... })`.
50
+ - Set `process.exitCode = 1` on errors instead of throwing.
51
+ - Print errors via `console.error`, success via `console.log`.
52
+
53
+ ## Imports and Modules
54
+ - Use ESM import syntax.
55
+ - Prefer `node:` prefix for Node builtins.
56
+ - Prefer `import type` for types.
57
+ - Avoid unused imports; keep import order stable.
58
+
59
+ ## Naming Conventions
60
+ - Functions: lowerCamelCase.
61
+ - Files: kebab-case for command files (`command-*.ts`).
62
+ - Config keys: snake_case (e.g., `prompts_root`).
63
+ - Link default name: `AGENTS.md`.
64
+
65
+ ## Error Handling
66
+ - Use small `fail()` helpers that set `process.exitCode`.
67
+ - Avoid throwing for normal CLI errors.
68
+ - Return early after error reporting.
69
+ - For filesystem errors, check existence explicitly before use.
70
+
71
+ ## Async and I/O
72
+ - Prefer `fs.promises` APIs.
73
+ - Avoid synchronous FS unless required (e.g., `spawnSync` for git).
74
+ - When reading remote sources, treat them as read-only.
75
+
76
+ ## Completion
77
+ - Autocomplete uses `omelette` and `enquirer` for interactive search.
78
+ - Completion is initialized in `src/cli.ts`.
79
+ - Avoid heavy work in completion handlers.
80
+
81
+ ## Link Behavior
82
+ - `link` attempts hardlink -> symlink -> meta-link fallback.
83
+ - Hard/soft links are real-time (filesystem-level).
84
+ - Meta-link uses a header with source path + hash.
85
+ - Meta-link content is read-only unless explicitly changed by CLI logic.
86
+
87
+ ## Index and Auto-Repair
88
+ - Link index maps source -> targets and roots.
89
+ - Auto-repair runs at CLI start and updates broken paths.
90
+ - Do not scan full disks; use index roots only.
91
+
92
+ ## Testing Conventions
93
+ - Use `bun:test` (`describe`, `it`, `expect`).
94
+ - Keep tests deterministic and filesystem-safe.
95
+ - Use `os.tmpdir()` for temp directories.
96
+ - Clean up watchers or temporary resources in tests.
97
+
98
+ ## Style Notes
99
+ - Keep functions small and focused.
100
+ - Avoid deep nesting; use early returns.
101
+ - Avoid inline comments unless needed for clarity.
102
+ - Keep output messages user-facing and concise.
103
+
104
+ ## Changes to Avoid
105
+ - Do not add new CLI commands unless requested.
106
+ - Do not change config schema without migration logic.
107
+ - Do not modify `dist/` outputs manually.
108
+ - Do not add network dependencies without approval.
109
+
110
+ ## Single-Test Workflow
111
+ - Use `bun test tests/xyz.test.ts`.
112
+ - If needed, add `--filter` for a single case.
113
+
114
+ ## Release (GitHub Actions)
115
+ - Tag push `vX.Y.Z` triggers build + release + npm publish.
116
+ - Workflow sets `package.json` version from tag; uses `NPM_TOKEN` secret.
117
+
118
+
119
+ ## Notes on External Libraries
120
+ - Commander for CLI parsing.
121
+ - Cosmiconfig for configuration loading.
122
+ - Enquirer for interactive selection.
123
+ - Omelette for shell completion.
124
+
125
+ ## Platform Notes
126
+ - Windows is the primary platform.
127
+ - Prefer cross-platform APIs; avoid shell-specific commands.
128
+
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # pmt
2
+
3
+ Prompt/spec manager CLI that keeps shared rules synchronized across projects.
4
+
5
+ [中文文档](./README.zh.md)
6
+
7
+ ## What it does
8
+
9
+ - Centralizes prompt/spec files in a global repo (`prompts_root`).
10
+ - Creates link files in any project (hardlink -> symlink -> meta-link fallback).
11
+ - Tracks link moves/renames in background (no full-disk scans).
12
+ - Shows link status without scanning the filesystem.
13
+
14
+ ## Install
15
+
16
+ ### npm (Windows)
17
+
18
+ ```bash
19
+ npm i -g @yuchenrx/pmt-cli
20
+ ```
21
+
22
+ This package publishes a Windows binary. For other platforms, build from source.
23
+
24
+ ### From source
25
+
26
+ ```bash
27
+ bun install
28
+ ```
29
+
30
+ ## Run (global)
31
+
32
+ ```bash
33
+ pmt
34
+ ```
35
+
36
+ ## Run (dev)
37
+
38
+ ```bash
39
+ bun run pmt
40
+ ```
41
+
42
+ ## Build
43
+
44
+ ```bash
45
+ bun run build
46
+ ```
47
+
48
+ Output: `dist/pmt.exe`
49
+
50
+ ## Quick Start
51
+
52
+ ### 1) Init
53
+
54
+ Global prompt repo:
55
+
56
+ ```bash
57
+ pmt init
58
+ ```
59
+
60
+ `init` initializes the global prompt repo and runs `git init` under `prompts_root` if needed.
61
+
62
+ ### 2) Link
63
+
64
+ Create a link file using the source filename as default:
65
+
66
+ ```bash
67
+ pmt link 规范
68
+ ```
69
+
70
+ Create to a specific path:
71
+
72
+ ```bash
73
+ pmt link 规范 docs/AGENTS.md
74
+ ```
75
+
76
+ Interactive search:
77
+
78
+ ```bash
79
+ pmt link
80
+ pmt link -i
81
+ ```
82
+
83
+ ### 3) Status
84
+
85
+ ```bash
86
+ pmt status
87
+ ```
88
+
89
+ Example output:
90
+
91
+ ```
92
+ docs/AGENTS.md -> C:\...\prompts\规范.md [hardlink] ok
93
+ AGENTS.md -> C:\...\prompts\rules.md [meta] missing
94
+ roots: C:\repo, D:\shared
95
+ ```
96
+
97
+ ## Config
98
+
99
+ Config files:
100
+
101
+ - Project: `.promptslotrc.json`
102
+ - Global: `~/.promptslotrc.json`
103
+ - Link store: `.promptslot-links.json`
104
+ - Link index: `.promptslot-index.json`
105
+
106
+ Common keys:
107
+
108
+ - `prompts_root`: global prompt repo directory
109
+ - `prompts_dir`: project prompt directory (fallback)
110
+ - `link_default`: default link filename
111
+ - `sources`: name → file path/URL
112
+ - `current`: active source name
113
+
114
+ ## Link modes
115
+
116
+ 1) **Hardlink**: real-time sync at filesystem level
117
+ 2) **Symlink**: real-time sync at filesystem level
118
+ 3) **Meta-link**: header + hash (used when linking is not possible)
119
+
120
+ Meta-link is for read-only or cross-device cases (e.g. URL sources).
121
+
122
+ ## Rename tracking
123
+
124
+ - A background rename daemon starts after creating a meta-link.
125
+ - It only watches directories that already contain link files.
126
+ - No full-disk scanning; updates `.promptslot-links.json` on rename.
127
+
128
+ ## CLI name override
129
+
130
+ ```bash
131
+ CLI_NAME=pmt
132
+ ```
133
+
134
+
package/README.zh.md ADDED
@@ -0,0 +1,132 @@
1
+ # pmt
2
+
3
+ 用于统一管理/同步规范与提示文件的 CLI。
4
+
5
+ [English](./README.md)
6
+
7
+ ## 功能概述
8
+
9
+ - 全局集中管理 prompt/spec 文件(`prompts_root`)。
10
+ - 在任意项目创建 link 文件(硬链接 → 软链接 → meta-link 降级)。
11
+ - 后台追踪 link 重命名/移动(只监听已有目录,不全盘扫描)。
12
+ - `status` 查看所有 link 状态(不扫描磁盘)。
13
+
14
+ ## 安装
15
+
16
+ ### npm(Windows)
17
+
18
+ ```bash
19
+ npm i -g @yuchenrx/pmt-cli
20
+ ```
21
+
22
+ 该包发布的是 Windows 二进制文件,其他平台请从源码构建。
23
+
24
+ ### 源码安装
25
+
26
+ ```bash
27
+ bun install
28
+ ```
29
+
30
+ ## 运行(全局)
31
+
32
+ ```bash
33
+ pmt
34
+ ```
35
+
36
+ ## 运行(开发)
37
+
38
+ ```bash
39
+ bun run pmt
40
+ ```
41
+
42
+ ## 构建
43
+
44
+ ```bash
45
+ bun run build
46
+ ```
47
+
48
+ 输出:`dist/pmt.exe`
49
+
50
+ ## 快速开始
51
+
52
+ ### 1)初始化
53
+
54
+ 全局 prompt 仓库:
55
+
56
+ ```bash
57
+ pmt init
58
+ ```
59
+
60
+ `init` 会在 `prompts_root` 下自动执行 `git init`(若未初始化)。
61
+
62
+ ### 2)创建链接
63
+
64
+ 默认使用源文件名作为目标名:
65
+
66
+ ```bash
67
+ pmt link 规范
68
+ ```
69
+
70
+ 指定目标路径:
71
+
72
+ ```bash
73
+ pmt link 规范 docs/AGENTS.md
74
+ ```
75
+
76
+ 交互搜索:
77
+
78
+ ```bash
79
+ pmt link
80
+ pmt link -i
81
+ ```
82
+
83
+ ### 3)查看状态
84
+
85
+ ```bash
86
+ pmt status
87
+ ```
88
+
89
+ 示例输出:
90
+
91
+ ```
92
+ docs/AGENTS.md -> C:\...\prompts\规范.md [hardlink] ok
93
+ AGENTS.md -> C:\...\prompts\rules.md [meta] missing
94
+ roots: C:\repo, D:\shared
95
+ ```
96
+
97
+ ## 配置
98
+
99
+ 配置文件:
100
+
101
+ - 项目配置:`.promptslotrc.json`
102
+ - 全局配置:`~/.promptslotrc.json`
103
+ - 链接记录:`.promptslot-links.json`
104
+ - 索引文件:`.promptslot-index.json`
105
+
106
+ 常用字段:
107
+
108
+ - `prompts_root`:全局 prompt 仓库目录
109
+ - `prompts_dir`:项目 prompts 目录(fallback)
110
+ - `link_default`:默认 link 文件名
111
+ - `sources`:name → 文件路径/URL
112
+ - `current`:当前激活的 source 名
113
+
114
+ ## 链接模式
115
+
116
+ 1)**硬链接**:文件系统级实时同步
117
+ 2)**软链接**:文件系统级实时同步
118
+ 3)**Meta-link**:头部 + hash(无法链接时降级)
119
+
120
+ Meta-link 用于跨盘、只读或 URL 等场景。
121
+
122
+ ## 重命名追踪
123
+
124
+ - 创建 meta-link 后自动启动后台重命名守护。
125
+ - 仅监听已有 link 文件所在目录。
126
+ - 不做全盘扫描,仅更新 `.promptslot-links.json`。
127
+
128
+ ## CLI 名称覆盖
129
+
130
+ ```bash
131
+ CLI_NAME=pmt
132
+ ```
package/dist/pmt.exe ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@yuchenrx/pmt-cli",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "bin": {
6
+ "pmt": "dist/pmt.exe"
7
+ },
8
+ "scripts": {
9
+ "pmt": "bun src/cli.ts",
10
+ "build": "bun scripts/build.ts",
11
+ "test": "bun test"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src",
16
+ "README.md",
17
+ "README.zh.md",
18
+ "AGENTS.md"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+ssh://git@github.com/YuChenRX/pmt.git"
23
+ },
24
+ "homepage": "https://github.com/YuChenRX/pmt",
25
+ "bugs": {
26
+ "url": "https://github.com/YuChenRX/pmt/issues"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "dependencies": {
32
+ "commander": "^14.0.2",
33
+ "cosmiconfig": "^9.0.0",
34
+ "dotenv": "^17.2.3",
35
+ "enquirer": "^2.4.1",
36
+ "omelette": "^0.4.17",
37
+ "watcher": "^2.3.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/bun": "^1.3.6",
41
+ "@types/node": "^25.0.8",
42
+ "typescript": "^5.9.3"
43
+ }
44
+ }
package/src/apply.ts ADDED
@@ -0,0 +1,59 @@
1
+ import path from 'node:path'
2
+ import { promises as fs } from 'node:fs'
3
+ import type { PromptslotConfig } from './config'
4
+
5
+ const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
6
+
7
+ const buildBlock = (value: string, markers: PromptslotConfig['markers']) => {
8
+ const trimmed = value.replace(/\s+$/, '')
9
+ return `${markers.start}\n${trimmed}\n${markers.end}`
10
+ }
11
+
12
+ const replaceInContent = (
13
+ content: string,
14
+ value: string,
15
+ config: PromptslotConfig,
16
+ ) => {
17
+ const block = buildBlock(value, config.markers)
18
+ const start = escapeRegExp(config.markers.start)
19
+ const end = escapeRegExp(config.markers.end)
20
+ const blockRegex = new RegExp(`${start}[\\s\\S]*?${end}`, 'g')
21
+
22
+ if (blockRegex.test(content)) {
23
+ const next = content.replace(blockRegex, block)
24
+ return { next, changed: next !== content }
25
+ }
26
+
27
+ if (content.includes(config.placeholder)) {
28
+ const next = content.split(config.placeholder).join(block)
29
+ return { next, changed: next !== content }
30
+ }
31
+
32
+ return { next: content, changed: false }
33
+ }
34
+
35
+ const fileExists = async (filePath: string) =>
36
+ fs.access(filePath).then(() => true).catch(() => false)
37
+
38
+ export const applyToFiles = async (
39
+ targets: string[],
40
+ value: string,
41
+ config: PromptslotConfig,
42
+ ) => {
43
+ const changedFiles: string[] = []
44
+
45
+ for (const target of targets) {
46
+ const resolved = path.resolve(target)
47
+ const exists = await fileExists(resolved)
48
+ if (!exists) continue
49
+
50
+ const content = await fs.readFile(resolved, 'utf8')
51
+ const { next, changed } = replaceInContent(content, value, config)
52
+ if (!changed) continue
53
+
54
+ await fs.writeFile(resolved, next)
55
+ changedFiles.push(target)
56
+ }
57
+
58
+ return changedFiles
59
+ }
@@ -0,0 +1,13 @@
1
+ import dotenv from 'dotenv'
2
+
3
+ dotenv.config({ quiet: true })
4
+
5
+ const DEFAULT_CLI_NAME = 'pmt'
6
+
7
+ export const getCliName = () => {
8
+ const envName = process.env.CLI_NAME?.trim()
9
+ if (envName) return envName
10
+ return DEFAULT_CLI_NAME
11
+ }
12
+
13
+ export const formatCommand = (command: string) => `${getCliName()} ${command}`
package/src/cli.ts ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from 'commander'
3
+ import { getCliName } from './cli-name'
4
+ import { initCompletion } from './completion'
5
+ import { registerCommands } from './commands'
6
+ import { autoRepairLinks } from './link-repair'
7
+
8
+ const program = new Command()
9
+
10
+ program
11
+ .name(getCliName())
12
+ .description('占位符 prompt 管理 CLI')
13
+ .helpOption('-h, --help', '显示帮助')
14
+ .version('0.1.0', '-v, --version', '显示版本')
15
+
16
+ registerCommands(program)
17
+ initCompletion()
18
+
19
+ const args = process.argv.slice(2)
20
+ if (!args.length) {
21
+ program.outputHelp()
22
+ process.exit(0)
23
+ }
24
+
25
+ if (args.length === 1 && (args[0] === '-h' || args[0] === '--help')) {
26
+ program.outputHelp()
27
+ process.exit(0)
28
+ }
29
+
30
+ await autoRepairLinks()
31
+ await program.parseAsync(process.argv)
@@ -0,0 +1,84 @@
1
+ import { promises as fs } from 'node:fs'
2
+ import { applyToFiles } from './apply'
3
+ import { formatCommand } from './cli-name'
4
+ import { readSourceText } from './content-source'
5
+ import { loadMergedConfig } from './config'
6
+ import { fileExists, isUrl, resolvePath, resolveTargets } from './helpers'
7
+
8
+ const log = (text: string) => console.log(text)
9
+ const fail = (text: string) => {
10
+ console.error(text)
11
+ process.exitCode = 1
12
+ }
13
+
14
+ const requireMergedConfig = async () => {
15
+ const merged = await loadMergedConfig()
16
+ if (!merged.hasConfig) {
17
+ fail(`未找到配置文件,请先运行: ${formatCommand('init')}`)
18
+ return null
19
+ }
20
+ return merged
21
+ }
22
+
23
+ export const insertPlaceholder = async (target?: string) => {
24
+ if (!target) {
25
+ fail(`用法: ${formatCommand('insert <file>')}`)
26
+ return
27
+ }
28
+
29
+ const merged = await requireMergedConfig()
30
+ if (!merged) return
31
+
32
+ const resolved = resolvePath(target)
33
+ const exists = await fileExists(resolved)
34
+ if (!exists) {
35
+ fail(`文件不存在: ${resolved}`)
36
+ return
37
+ }
38
+
39
+ const content = await fs.readFile(resolved, 'utf8')
40
+ if (content.includes(merged.config.placeholder)) {
41
+ log('文件已包含占位符')
42
+ return
43
+ }
44
+
45
+ const next = content.replace(/\s*$/, `\n${merged.config.placeholder}\n`)
46
+ await fs.writeFile(resolved, next)
47
+ log(`已插入占位符: ${target}`)
48
+ }
49
+
50
+ export const applyCurrent = async (targets: string[]) => {
51
+ const merged = await requireMergedConfig()
52
+ if (!merged) return
53
+
54
+ const current = merged.config.current
55
+ if (!current) {
56
+ fail(`未设置当前值,请先运行: ${formatCommand('set <name> <file>')}`)
57
+ return
58
+ }
59
+
60
+ const sourcePath = merged.resolvedSources[current]
61
+ if (!sourcePath) {
62
+ fail(`当前值未配置路径: ${current}`)
63
+ return
64
+ }
65
+
66
+ if (!isUrl(sourcePath)) {
67
+ const sourceExists = await fileExists(sourcePath)
68
+ if (!sourceExists) {
69
+ fail(`当前文件不存在: ${sourcePath}`)
70
+ return
71
+ }
72
+ }
73
+
74
+ const value = await readSourceText(sourcePath)
75
+ const allTargets = await resolveTargets(targets, Object.values(merged.resolvedSources))
76
+ const changed = await applyToFiles(allTargets, value, merged.config)
77
+
78
+ if (!changed.length) {
79
+ log('未找到需要替换的文件')
80
+ return
81
+ }
82
+
83
+ log(`已更新 ${changed.length} 个文件`)
84
+ }
@@ -0,0 +1,31 @@
1
+ import { cleanupCompletion, installCompletion } from './completion'
2
+ import { formatCommand } from './cli-name'
3
+
4
+ const fail = (text: string) => {
5
+ console.error(text)
6
+ process.exitCode = 1
7
+ }
8
+
9
+ export const completionInstall = async () => {
10
+ try {
11
+ installCompletion()
12
+ } catch (error) {
13
+ const message = error instanceof Error ? error.message : String(error)
14
+ fail(`自动安装失败: ${message}`)
15
+ fail(`可手动执行: ${formatCommand('completion show')}`)
16
+ }
17
+ }
18
+
19
+ export const completionCleanup = async () => {
20
+ try {
21
+ cleanupCompletion()
22
+ } catch (error) {
23
+ const message = error instanceof Error ? error.message : String(error)
24
+ fail(`自动卸载失败: ${message}`)
25
+ }
26
+ }
27
+
28
+ export const completionShow = async () => {
29
+ process.stdout.write(`${formatCommand('--completion')}\n`)
30
+ process.stdout.write(`${formatCommand('--completion-fish')}\n`)
31
+ }