promptex-js 0.0.1-alpha.1 → 1.0.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/README.md +89 -52
- package/bin/promptex.mjs +17 -111
- package/dist/adapter.d.ts +151 -0
- package/dist/adapter.js +127 -0
- package/dist/cli/compile-engine.d.ts +123 -0
- package/dist/cli/compile-engine.js +410 -0
- package/dist/cli/core-types.d.ts +197 -0
- package/dist/cli/core-types.js +27 -0
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.js +24 -0
- package/dist/cli/main.d.ts +109 -0
- package/dist/cli/main.js +1883 -0
- package/dist/cli/scratch.d.ts +10 -0
- package/dist/cli/scratch.js +24 -0
- package/dist/cli/stdout.d.ts +2 -0
- package/dist/cli/stdout.js +30 -0
- package/dist/cli/teardown.d.ts +22 -0
- package/dist/cli/teardown.js +74 -0
- package/dist/cli/unit-scope-loader.d.ts +9 -0
- package/dist/cli/unit-scope-loader.js +63 -0
- package/dist/cli/watch-loader.d.ts +12 -0
- package/dist/cli/watch-loader.js +37 -0
- package/dist/cli/watch-pass.d.ts +2 -0
- package/dist/cli/watch-pass.js +162 -0
- package/dist/cli/watch-stamp.d.ts +2 -0
- package/dist/cli/watch-stamp.js +15 -0
- package/dist/cli/watch.d.ts +93 -0
- package/dist/cli/watch.js +301 -0
- package/dist/config-rewrite.d.ts +145 -0
- package/dist/config-rewrite.js +912 -0
- package/dist/config.d.ts +241 -0
- package/dist/config.js +94 -0
- package/dist/content.d.ts +161 -0
- package/dist/content.js +349 -0
- package/dist/diag.d.ts +56 -0
- package/dist/diag.js +134 -0
- package/dist/externals.d.ts +32 -0
- package/dist/externals.js +9 -0
- package/dist/i18n.d.ts +22 -0
- package/dist/i18n.js +55 -0
- package/dist/index.d.ts +391 -34
- package/dist/index.js +108 -49
- package/dist/ir.d.ts +106 -0
- package/dist/ir.js +7 -0
- package/dist/messages.generated.d.ts +2 -0
- package/dist/messages.generated.js +403 -0
- package/dist/native.d.ts +103 -0
- package/dist/native.js +73 -0
- package/dist/paths.d.ts +25 -0
- package/dist/paths.js +39 -0
- package/dist/plugin.d.ts +160 -0
- package/dist/plugin.js +21 -0
- package/dist/registry.d.ts +213 -0
- package/dist/registry.js +619 -0
- package/dist/sources.d.ts +12 -0
- package/dist/sources.js +45 -0
- package/dist/version.d.ts +44 -0
- package/dist/version.js +116 -0
- package/package.json +40 -13
package/README.md
CHANGED
|
@@ -1,84 +1,121 @@
|
|
|
1
|
-
# promptex-js
|
|
1
|
+
# promptex-js
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Declare prompt resources in TypeScript, compile them into whatever your AI coding
|
|
4
|
+
tool expects on disk.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Skills, rules, agents, hooks, MCP servers and permissions are ordinary files in a
|
|
7
|
+
directory tree, and every tool wants that tree laid out its own way. This package
|
|
8
|
+
lets you declare each resource once and project the result onto a target platform.
|
|
9
|
+
It ships the TypeScript SDK, a precompiled native binding to the shared core, and
|
|
10
|
+
the `promptex` command line tool.
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
預編譯二進位綁定與完整命令列工具三者;本原型只有 SDK 的最窄一條路徑,命令列
|
|
10
|
-
工具只有 `build` 一個動詞,沒有原生綁定、沒有多平台投影、沒有引用與關係圖、
|
|
11
|
-
沒有安裝與所有權登記。
|
|
12
|
+
## Install
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
## 安裝
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install promptex-js@alpha
|
|
14
|
+
```console
|
|
15
|
+
npm install promptex-js
|
|
20
16
|
```
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## 用法
|
|
18
|
+
## Minimal example
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
Declarations live in a `prompts/` directory. Anything you `define*` at module
|
|
21
|
+
scope is registered when the file is evaluated.
|
|
27
22
|
|
|
28
23
|
```ts
|
|
29
|
-
// prompts/
|
|
24
|
+
// my-project/prompts/review.ts
|
|
30
25
|
import { defineSkill } from 'promptex-js'
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
id: '
|
|
34
|
-
name: '
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'subject 用繁體中文、50 字內,不以句號結尾;body 說明變更動機而非變更內容。',
|
|
39
|
-
],
|
|
27
|
+
defineSkill({
|
|
28
|
+
id: 'code-review',
|
|
29
|
+
name: 'Code Review',
|
|
30
|
+
description: 'Reviews a pull request for correctness and readability',
|
|
31
|
+
config: { whenToUse: 'Reviewing a pull request' },
|
|
32
|
+
content: 'Check correctness first, then readability.',
|
|
40
33
|
})
|
|
41
34
|
```
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
Point the command line tool at the directory that *contains* `prompts/`:
|
|
44
37
|
|
|
45
|
-
```
|
|
46
|
-
npx promptex build
|
|
38
|
+
```console
|
|
39
|
+
$ npx promptex build --install my-project --out my-project --platform claude
|
|
40
|
+
✓ Metadata written (graph + content)
|
|
41
|
+
wrote .claude/skills/code-review/SKILL.md
|
|
42
|
+
Install report (claude)
|
|
43
|
+
Projected artifacts, 1: .claude/skills/code-review/SKILL.md
|
|
47
44
|
```
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
Which writes:
|
|
50
47
|
|
|
51
48
|
```text
|
|
49
|
+
my-project/.claude/skills/code-review/SKILL.md
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
52
53
|
---
|
|
53
|
-
name:
|
|
54
|
-
description:
|
|
54
|
+
name: code-review
|
|
55
|
+
description: Reviews a pull request for correctness and readability
|
|
56
|
+
when_to_use: Reviewing a pull request
|
|
55
57
|
---
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
subject 用繁體中文、50 字內,不以句號結尾;body 說明變更動機而非變更內容。
|
|
59
|
+
Check correctness first, then readability.
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Switch `--platform codex` and the same declaration lands in Codex's layout instead.
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
## Commands
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
| Command | What it does |
|
|
67
|
+
| :--- | :--- |
|
|
68
|
+
| `promptex build <dir>` | Compile and refresh the framework metadata under `.promptex/`. No platform artifacts, no ownership registry, no pruning. |
|
|
69
|
+
| `promptex build --install <dir>` | Compile, write the platform artifacts, record ownership, and prune what you deleted. This is the one to use for real installs. |
|
|
70
|
+
| `promptex build --install --dry-run <dir>` | Show what `--install` would change, without writing anything. Add `--content --json` to get the bytes it would write. |
|
|
71
|
+
| `promptex build --export <export-dir> <dir>` | Write the exact tree `--install` would write under `<export-dir>/<unit>/`, with no registry. The unit subtree is cleared first. |
|
|
72
|
+
| `promptex build --watch <dir>` | Rerun the same flags (`build` alone, `--install` or `--export`) as sources change. |
|
|
73
|
+
| `promptex manifest <dir>` | Answer registry queries from the current projection. |
|
|
74
|
+
| `promptex init <dir>` | Scaffold a new project (`--lang ts`, `python` or `rust`). |
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
--out <目錄> 產物輸出目錄,預設當前目錄
|
|
71
|
-
```
|
|
76
|
+
Common flags: `--out <install root>` (single-directory mode), `--platform <name>` (`manifest` only), `--unit <unit name>`, `--json`, `--locale <tag>`.
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
## What you can declare
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
`defineSkill`, `defineRule`, `defineAgent`, `defineInstruction`, `defineResource`,
|
|
81
|
+
`defineHook`, `defineMcp`, `definePermission`, `defineAsset`, `defineDir`.
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
Per-platform native settings are not declared in your sources: they belong to the
|
|
84
|
+
target, so you write them as `claude({ settings: { … } })` in `promptex.config.ts`.
|
|
85
|
+
Keys pass through verbatim into that platform's own settings file and the framework
|
|
86
|
+
keeps no allowlist.
|
|
87
|
+
|
|
88
|
+
Content is composed with builders rather than hand-written markdown:
|
|
89
|
+
`section`, `list`, `ordered`, `table`, `code`, `check`, `link`, `file`, `image`,
|
|
90
|
+
`quote`, `details`, `divider`, plus the inline containers `inline`, `bold`,
|
|
91
|
+
`italic`, `inlineCode` and `inlineLink`.
|
|
92
|
+
|
|
93
|
+
Anything that takes children takes the same set: plain text, another builder's
|
|
94
|
+
result, or a reference. That includes list items and table cells, so a step can
|
|
95
|
+
link straight to the resource it depends on. `inline` is the unstyled container:
|
|
96
|
+
it joins its children with no separator and emits no markup, which is what a
|
|
97
|
+
whole sentence made of text and a reference needs.
|
|
98
|
+
|
|
99
|
+
Larger projects add a `promptex.config.ts` with `defineConfig` to split the tree
|
|
100
|
+
into units, and register third-party platforms with `defineTarget`.
|
|
101
|
+
|
|
102
|
+
## Notes
|
|
103
|
+
|
|
104
|
+
The command line tool runs your declarations directly with Node's built-in type
|
|
105
|
+
stripping, so there is no separate transpile step and promptex does not impose a
|
|
106
|
+
build toolchain on your project.
|
|
107
|
+
|
|
108
|
+
Everything past evaluation — projection, install, registry, graph, diagnostics —
|
|
109
|
+
happens in a shared Rust core called through an in-process native binding, not a
|
|
110
|
+
subprocess. The Python and Rust packages call the same core, so identical
|
|
111
|
+
declarations produce byte-identical output across all three.
|
|
112
|
+
|
|
113
|
+
## Links
|
|
114
|
+
|
|
115
|
+
- Source and issues: <https://github.com/promptex-ai/promptex>
|
|
116
|
+
- Architecture notes: <https://github.com/promptex-ai/promptex/blob/main/docs/architecture.md>
|
|
81
117
|
|
|
82
|
-
##
|
|
118
|
+
## License
|
|
83
119
|
|
|
84
|
-
MIT
|
|
120
|
+
Dual-licensed under either of MIT or Apache-2.0, at your option. See `LICENSE-MIT`
|
|
121
|
+
and `LICENSE-APACHE`.
|
package/bin/promptex.mjs
CHANGED
|
@@ -1,113 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// promptex
|
|
2
|
+
// promptex 的命令列轉發殼。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
--out <目錄> 產物輸出目錄,預設當前目錄
|
|
21
|
-
--help 顯示本說明
|
|
22
|
-
`
|
|
23
|
-
|
|
24
|
-
/** 宣告檔的副檔名。`.ts` 依賴 Node 的型別剝離,不經過額外的建置步驟。 */
|
|
25
|
-
const SOURCE_EXTENSIONS = ['.ts', '.mts', '.js', '.mjs']
|
|
26
|
-
|
|
27
|
-
function parseArgs(argv) {
|
|
28
|
-
const args = { verb: argv[0], src: 'prompts', out: '.' }
|
|
29
|
-
for (let i = 1; i < argv.length; i += 1) {
|
|
30
|
-
const flag = argv[i]
|
|
31
|
-
if (flag === '--src' || flag === '--out') {
|
|
32
|
-
const value = argv[i + 1]
|
|
33
|
-
if (value === undefined) throw new Error(`${flag} 後面缺少值`)
|
|
34
|
-
args[flag.slice(2)] = value
|
|
35
|
-
i += 1
|
|
36
|
-
} else {
|
|
37
|
-
throw new Error(`未知的選項:${flag}`)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return args
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** 遞迴列出來源目錄下的宣告檔,依路徑排序讓載入順序不隨檔案系統而變。 */
|
|
44
|
-
async function collectSources(dir) {
|
|
45
|
-
const entries = await readdir(dir, { withFileTypes: true })
|
|
46
|
-
const found = []
|
|
47
|
-
for (const entry of entries) {
|
|
48
|
-
const path = join(dir, entry.name)
|
|
49
|
-
if (entry.isDirectory()) found.push(...(await collectSources(path)))
|
|
50
|
-
else if (SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) found.push(path)
|
|
51
|
-
}
|
|
52
|
-
return found.sort()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function main() {
|
|
56
|
-
const argv = process.argv.slice(2)
|
|
57
|
-
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
58
|
-
process.stdout.write(USAGE)
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const args = parseArgs(argv)
|
|
63
|
-
if (args.verb !== 'build') {
|
|
64
|
-
process.stderr.write(`未知的動詞:${args.verb}\n\n${USAGE}`)
|
|
65
|
-
process.exitCode = 1
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const srcDir = resolve(args.src)
|
|
70
|
-
try {
|
|
71
|
-
if (!(await stat(srcDir)).isDirectory()) throw new Error('不是目錄')
|
|
72
|
-
} catch {
|
|
73
|
-
process.stderr.write(`找不到來源目錄:${srcDir}\n`)
|
|
74
|
-
process.exitCode = 1
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const sources = await collectSources(srcDir)
|
|
79
|
-
if (sources.length === 0) {
|
|
80
|
-
process.stderr.write(`來源目錄沒有宣告檔(${SOURCE_EXTENSIONS.join('、')}):${srcDir}\n`)
|
|
81
|
-
process.exitCode = 1
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 載入宣告檔與呼叫 build 必須共用同一個模組實例,登記處才看得到宣告;
|
|
86
|
-
// 兩邊都經 `promptex-js` 的 dist 進入點解析,因此指向同一個模組。
|
|
87
|
-
const { build, declaredCount } = await import('../dist/index.js')
|
|
88
|
-
for (const source of sources) await import(pathToFileURL(source).href)
|
|
89
|
-
|
|
90
|
-
if (declaredCount() === 0) {
|
|
91
|
-
process.stderr.write(`載入了 ${sources.length} 份宣告檔,但沒有任何 skill 被宣告。\n`)
|
|
92
|
-
process.exitCode = 1
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
for (const path of await build(resolve(args.out))) process.stdout.write(`${path}\n`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Node 未內建型別剝離時(22.18 之前)以旗標重跑一次,讓 `.ts` 宣告檔不必
|
|
100
|
-
// 先經過建置步驟。已內建的版本不重跑,避免多開一個行程。
|
|
101
|
-
if (!process.features.typescript) {
|
|
102
|
-
const result = spawnSync(
|
|
103
|
-
process.execPath,
|
|
104
|
-
['--experimental-strip-types', new URL(import.meta.url).pathname, ...process.argv.slice(2)],
|
|
105
|
-
{ stdio: 'inherit' },
|
|
106
|
-
)
|
|
107
|
-
process.exit(result.status ?? 1)
|
|
108
|
-
} else {
|
|
109
|
-
await main().catch((error) => {
|
|
110
|
-
process.stderr.write(`${error.message}\n`)
|
|
111
|
-
process.exitCode = 1
|
|
112
|
-
})
|
|
113
|
-
}
|
|
4
|
+
// 邏輯住 `src/cli/main.ts`(發布物為 `dist/cli/main.js`),本檔只把 argv 交過去:
|
|
5
|
+
// 命令列的接線與指令分派是 TypeScript 源碼的一部分,與 SDK 同受型別檢查
|
|
6
|
+
// 與同一次 `tsc` 建置,不再是套件裡唯一沒有型別的那一批 `.mjs`。
|
|
7
|
+
//
|
|
8
|
+
// 靠 Node 內建的型別剝除執行作者源碼:作者的提示詞源碼是 TypeScript,求值即
|
|
9
|
+
// 執行,用內建能力才不必把某套建置工具鏈的偏好強加給使用者專案。
|
|
10
|
+
//
|
|
11
|
+
// shebang 不帶 --experimental-strip-types:型別剝除自 Node 22.18 起已是預設,
|
|
12
|
+
// 旗標多餘;而它留在 shebang 裡會讓本套件在 Linux 上完全無法執行 ── GNU 的
|
|
13
|
+
// env 不拆解 shebang 後面的參數(需 -S),症狀是
|
|
14
|
+
// `env: 'node --experimental-strip-types': No such file or directory`。
|
|
15
|
+
// macOS 的 env 會拆,因此這個缺陷只在 Linux 顯現。下界由 package.json 的
|
|
16
|
+
// engines 宣告。
|
|
17
|
+
import { main } from '../dist/cli/main.js'
|
|
18
|
+
|
|
19
|
+
await main(process.argv)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Kind } from './ir.js';
|
|
2
|
+
import type { PluginEntry } from './plugin.js';
|
|
3
|
+
import { type SelectSpec } from './registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* 降級或不支援的一筆記錄,與核心 `adapter::DegradeEntry` 同型(AC-098):
|
|
6
|
+
* 欄位名與序列化形態逐一對應,`formatReport` 因此可以把整份報告原樣交給
|
|
7
|
+
* 核心格式化。
|
|
8
|
+
*/
|
|
9
|
+
export interface DegradeItem {
|
|
10
|
+
readonly kind: Kind;
|
|
11
|
+
readonly nodeId: string;
|
|
12
|
+
readonly feature: string;
|
|
13
|
+
/**
|
|
14
|
+
* 降級前後的強制力層級(AC-093 的三段封閉列舉);純機制缺失(不支援)
|
|
15
|
+
* 而無強制力落差時省略。
|
|
16
|
+
*
|
|
17
|
+
* 第三方適配同樣填得出這兩個欄位,不是內建平台專屬:報告的形態一旦讓
|
|
18
|
+
* 內建適配表達得比第三方多,「官方與第三方使用同一組公開能力」
|
|
19
|
+
* (AC-097)就只剩宣稱。
|
|
20
|
+
*/
|
|
21
|
+
readonly from?: Enforcement;
|
|
22
|
+
readonly to?: Enforcement;
|
|
23
|
+
readonly note: string;
|
|
24
|
+
}
|
|
25
|
+
/** 強制力階梯(AC-093):機械閘 > 流程閘 > 措辭層,列舉封閉。 */
|
|
26
|
+
export type Enforcement = 'mechanical' | 'processGate' | 'wording';
|
|
27
|
+
/**
|
|
28
|
+
* 一次第三方投影的安裝報告,與內建平台的 `Report`/`EmitReport` 同一形態
|
|
29
|
+
* (AC-098):降級與不支援兩類皆進報告,不得靜默丟棄,因此第三方與內建
|
|
30
|
+
* 平台的產物能一併呈現在同一份人讀輸出中,不需要另一套報告格式。
|
|
31
|
+
*/
|
|
32
|
+
export interface EmitReport {
|
|
33
|
+
readonly platform: string;
|
|
34
|
+
readonly emitted: readonly string[];
|
|
35
|
+
readonly degraded: readonly DegradeItem[];
|
|
36
|
+
readonly unsupported: readonly DegradeItem[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 第三方 adapter 的公開介面(AC-097):官方與第三方適配使用同一組方法。
|
|
40
|
+
* `entries` 讀本平台要產出的節點;`render` 依落點表渲染節點內容;
|
|
41
|
+
* `overrides`/`reserved` 讀本節點的平台覆寫與框架保留鍵;
|
|
42
|
+
* `unsupportedOverride` 記下沒有鍵值面時那筆不支援;`degrade`/`unsupported`
|
|
43
|
+
* 記錄能力落差;`report`/`hasNotes` 取出累積的落差記錄。
|
|
44
|
+
*
|
|
45
|
+
* 兩份累積清單為內部狀態、經 `report` 取出,不另以同名屬性暴露:兩個記錄
|
|
46
|
+
* 方法的名稱(`degrade`/`unsupported`)本身即介面的一部分,把清單也用同名
|
|
47
|
+
* 屬性暴露會與方法名相撞。三個 SDK 的方法集合完全相同,目前沒有自動比對;
|
|
48
|
+
* 少一個方法就是「這個生態的第三方適配做得到的事比另兩個少」。
|
|
49
|
+
*/
|
|
50
|
+
export interface AdapterContext {
|
|
51
|
+
/**
|
|
52
|
+
* 本平台要產出的節點(省略 kind 即全部):已套用節點自身宣告的平台限定
|
|
53
|
+
* (`meta.targets`)與配置單元的分發選取(`select`),見
|
|
54
|
+
* `createAdapterContext`。
|
|
55
|
+
*/
|
|
56
|
+
entries(kind?: Kind): readonly PluginEntry[];
|
|
57
|
+
/** 依落點表渲染節點內容(AC-100):第三個參數是落點對照表,不是回呼函式。 */
|
|
58
|
+
render(entry: PluginEntry, selfPath: string, layout: ReadonlyMap<string, string>): string;
|
|
59
|
+
/**
|
|
60
|
+
* 本節點為本平台宣告的平台原生鍵覆寫(`config.platforms.{平台}`),未宣告
|
|
61
|
+
* 或宣告了空物件時回傳 `undefined`(ADR-004「空物件視同未宣告」)。
|
|
62
|
+
*
|
|
63
|
+
* 框架保留鍵(`promptex:` 前綴)已濾掉,回傳的每個鍵都是可原樣併進本平台
|
|
64
|
+
* 產物的原生鍵;保留鍵另由 `reserved` 取出。
|
|
65
|
+
*
|
|
66
|
+
* 這個方法存在的理由是「官方適配也只使用這組公開介面」(ADR-004)此前在
|
|
67
|
+
* 覆寫這一格只是宣稱:內建適配讀的是核心的 `adapter::platform_overrides`,
|
|
68
|
+
* 第三方適配只能自己走 `entry.def.config.platforms[平台]`,於是「空物件視同
|
|
69
|
+
* 未宣告」的判定與保留鍵的過濾各自重造。兩種失效都沒有訊號:判定漂移只在
|
|
70
|
+
* 特定宣告下現形,保留鍵漏濾則直接把框架的指令洩漏成平台不認得的欄位。
|
|
71
|
+
*/
|
|
72
|
+
overrides(entry: PluginEntry): Readonly<Record<string, unknown>> | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* 本節點宣告的框架保留鍵(`promptex:` 命名空間),鍵是保留鍵名、值是它的
|
|
75
|
+
* 字串值;未宣告時回傳 `undefined`。
|
|
76
|
+
*
|
|
77
|
+
* 保留鍵是「覆寫的鍵即平台原生鍵」這條形態唯一的例外:它是寫給框架的指令,
|
|
78
|
+
* 由框架消費、不寫進產物(ADR-004)。宣告位置與值形態的錯誤已在投影入口以
|
|
79
|
+
* `E203` 阻斷,本方法回傳的都是合法且可直接使用的值。現行保留鍵只有
|
|
80
|
+
* `promptex:event`(僅 hook 合法)。
|
|
81
|
+
*/
|
|
82
|
+
reserved(entry: PluginEntry): Readonly<Record<string, string>> | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* 本節點在本平台的產物沒有承載鍵的位置時,把該筆記進不支援清單;節點未宣告
|
|
85
|
+
* 覆寫時什麼都不做。
|
|
86
|
+
*
|
|
87
|
+
* `form` 由呼叫端寫出本平台該類型的產物形態:哪些類型在本平台沒有鍵值面是
|
|
88
|
+
* 平台知識、住各適配(ADR-009),但「宣告的覆寫沒有落點」這件事對作者的意義
|
|
89
|
+
* 每個平台相同,訊息模板因此共用核心那一份。
|
|
90
|
+
*/
|
|
91
|
+
unsupportedOverride(entry: PluginEntry, form: string): void;
|
|
92
|
+
/** 記錄能力降級,進安裝報告。 */
|
|
93
|
+
degrade(item: DegradeItem): void;
|
|
94
|
+
/** 記錄平台不支援而未產出的節點,進安裝報告。 */
|
|
95
|
+
unsupported(item: DegradeItem): void;
|
|
96
|
+
/**
|
|
97
|
+
* 把本次累積的降級與不支援併成安裝報告;`emitted` 由呼叫端依實際寫出的
|
|
98
|
+
* 產物決定,不在本介面判斷。
|
|
99
|
+
*/
|
|
100
|
+
report(platform: string, emitted: readonly string[]): EmitReport;
|
|
101
|
+
/**
|
|
102
|
+
* 是否累積了任何降級或不支援記錄:擴充路徑據此決定要不要印報告(無落差時
|
|
103
|
+
* 多印一份空報告只是噪音)。
|
|
104
|
+
*/
|
|
105
|
+
hasNotes(): boolean;
|
|
106
|
+
}
|
|
107
|
+
export type AdapterEmit = (ctx: AdapterContext) => Map<string, string>;
|
|
108
|
+
/**
|
|
109
|
+
* 第三方適配的 target:與內建 `claude()`/`codex()` 回傳同一種 `{ platform
|
|
110
|
+
* }` 形狀,多帶一個 `emit` 實作(AC-099)。`defineConfig` 的 `targets`
|
|
111
|
+
* 陣列因此可與內建平台原樣並列宣告,schema 不因擴充而改變。
|
|
112
|
+
*/
|
|
113
|
+
export interface AdapterTarget {
|
|
114
|
+
readonly platform: string;
|
|
115
|
+
readonly emit: AdapterEmit;
|
|
116
|
+
/**
|
|
117
|
+
* 本適配接受哪些建構參數,標準 JSON Schema(ADR-013)。形態與寫法見
|
|
118
|
+
* `Plugin.configSchema`;`emit` 帶函式本體、不進中介表示,不得寫進宣告。
|
|
119
|
+
*/
|
|
120
|
+
readonly configSchema?: unknown;
|
|
121
|
+
}
|
|
122
|
+
/** 第三方套件用本函式建構 target。 */
|
|
123
|
+
export declare function defineTarget(platform: string, emit: AdapterEmit, options?: {
|
|
124
|
+
readonly configSchema?: unknown;
|
|
125
|
+
}): AdapterTarget;
|
|
126
|
+
/**
|
|
127
|
+
* 格式化為人讀文字:委派核心 `adapter::Report::format`(經
|
|
128
|
+
* `promptex-js-binding` 的 `formatReport` 匯出,ADR-008),不在 SDK 側重寫。
|
|
129
|
+
*
|
|
130
|
+
* 這裡原本是一份 TypeScript 對等實作。它與核心那份已經分岔:核心會印強制力
|
|
131
|
+
* 階梯(`,強制力 {from} → {to}`),SDK 這份沒有,因為當時的 `DegradeItem`
|
|
132
|
+
* 連 `from`/`to` 兩個欄位都沒有。改為委派之後,第三方適配與內建平台的報告
|
|
133
|
+
* 是同一份機器碼產出的,不再有「寫的當下對得上、核心改了才分岔」的缺口。
|
|
134
|
+
*/
|
|
135
|
+
export declare function formatReport(report: EmitReport): string;
|
|
136
|
+
/**
|
|
137
|
+
* 建立一份 `AdapterContext` 供命令列呼叫第三方 `emit` 或 `extend` 函式。
|
|
138
|
+
*
|
|
139
|
+
* 收平台名與配置單元的 `select`,兩者一起決定 `entries` 回傳哪些節點
|
|
140
|
+
* (見 `registry.ts` 的 `entriesFor`):節點過濾在內建平台由核心的投影承擔
|
|
141
|
+
* (`meta.targets` 走 `adapter::is_distributed_to`、`select` 走 `toIR`),
|
|
142
|
+
* 第三方適配不經核心投影,同樣兩道排除因此必須在建構脈絡時就帶進來。
|
|
143
|
+
* 少了它們,宣告 `targets: ['claude']` 的節點會被第三方適配照樣產出,多個
|
|
144
|
+
* 配置單元共用同一個 `srcDir` 時每個落點還會收到全庫節點。
|
|
145
|
+
*
|
|
146
|
+
* 累積的落差經 `ctx.report(platform, emitted)` 取出,不再回傳兩個裸陣列:
|
|
147
|
+
* 裸陣列讓呼叫端能繞過介面直接組裝報告,作者的 `emit` 實作卻拿不到同一條
|
|
148
|
+
* 路徑,Python/Rust 兩側則是兩者都走 `report()`。同一件事在三個生態有兩種
|
|
149
|
+
* 形態,就是「這個生態的第三方適配做得到的事比另兩個少」。
|
|
150
|
+
*/
|
|
151
|
+
export declare function createAdapterContext(platform: string, select?: SelectSpec): AdapterContext;
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// 第三方 adapter 的公開介面(AC-097 至 AC-101,platform-adaptation BC 的
|
|
2
|
+
// 適配可擴充性):官方(claude/codex)與第三方適配使用同一組
|
|
3
|
+
// `AdapterContext` 方法,不存在「內建能用而擴充不能用」的私有 API。
|
|
4
|
+
//
|
|
5
|
+
// 兩條 adapter 路徑分工不同軸:內建平台的實際投影住 Rust core
|
|
6
|
+
// (crates/promptex-core/src/adapter),以核心語言隨二進位分發;第三方
|
|
7
|
+
// 適配住這裡(SDK 層),以作者選用的語言撰寫(本 SDK 為 TypeScript),
|
|
8
|
+
// 不需要修改核心程式碼即可分發(AC-099):command-line 工具
|
|
9
|
+
// (src/cli/main.ts)偵測到 target 帶有 `emit` 時改呼叫這裡的函式,不
|
|
10
|
+
// 透過核心二進位。第三方適配因此綁語言:以 TypeScript 實作者只服務
|
|
11
|
+
// TypeScript 專案,這是擴充自由度的顯性代價(AC-101)。
|
|
12
|
+
//
|
|
13
|
+
// 渲染(`render`)接受落點表而非回呼函式(AC-100):算 `[名稱](相對路徑)`
|
|
14
|
+
// 需要同時知道引用方與目標各在哪,若介面設計成接受回呼,跨語言邊界就得
|
|
15
|
+
// 傳函式指標;改為接受落點表後只有資料進出。渲染邏輯本身(`Piece[]` →
|
|
16
|
+
// markdown 字串,含 `ref` 解析與相對路徑計算)委派 Rust core 的
|
|
17
|
+
// `crates/promptex-core/src/project/mod.rs`(`render_pieces`/`render_ref`/
|
|
18
|
+
// `relative_path`,經 `promptex-js-binding` 的 `renderPieces` 匯出,ADR-008):
|
|
19
|
+
// 本檔只做「求值期概念(`Content`,含函式呼叫、引用建構子)轉成純資料
|
|
20
|
+
// `Piece[]`」這一步,那一步依賴 SDK 側的註冊表(`engine`),核心無從得知;
|
|
21
|
+
// 轉換完成後的渲染是同一份機器碼,不再有第二份 TypeScript 對等實作要與
|
|
22
|
+
// 核心人工核對是否同步。
|
|
23
|
+
import { contentToPieces, engine, entriesFor } from './registry.js';
|
|
24
|
+
import { currentLocale } from './i18n.js';
|
|
25
|
+
import { loadNativeCore } from './native.js';
|
|
26
|
+
/** 第三方套件用本函式建構 target。 */
|
|
27
|
+
export function defineTarget(platform, emit, options = {}) {
|
|
28
|
+
return {
|
|
29
|
+
platform,
|
|
30
|
+
emit,
|
|
31
|
+
...(options.configSchema !== undefined ? { configSchema: options.configSchema } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 格式化為人讀文字:委派核心 `adapter::Report::format`(經
|
|
36
|
+
* `promptex-js-binding` 的 `formatReport` 匯出,ADR-008),不在 SDK 側重寫。
|
|
37
|
+
*
|
|
38
|
+
* 這裡原本是一份 TypeScript 對等實作。它與核心那份已經分岔:核心會印強制力
|
|
39
|
+
* 階梯(`,強制力 {from} → {to}`),SDK 這份沒有,因為當時的 `DegradeItem`
|
|
40
|
+
* 連 `from`/`to` 兩個欄位都沒有。改為委派之後,第三方適配與內建平台的報告
|
|
41
|
+
* 是同一份機器碼產出的,不再有「寫的當下對得上、核心改了才分岔」的缺口。
|
|
42
|
+
*/
|
|
43
|
+
export function formatReport(report) {
|
|
44
|
+
return loadNativeCore().formatReport(JSON.stringify(report), currentLocale());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 建立一份 `AdapterContext` 供命令列呼叫第三方 `emit` 或 `extend` 函式。
|
|
48
|
+
*
|
|
49
|
+
* 收平台名與配置單元的 `select`,兩者一起決定 `entries` 回傳哪些節點
|
|
50
|
+
* (見 `registry.ts` 的 `entriesFor`):節點過濾在內建平台由核心的投影承擔
|
|
51
|
+
* (`meta.targets` 走 `adapter::is_distributed_to`、`select` 走 `toIR`),
|
|
52
|
+
* 第三方適配不經核心投影,同樣兩道排除因此必須在建構脈絡時就帶進來。
|
|
53
|
+
* 少了它們,宣告 `targets: ['claude']` 的節點會被第三方適配照樣產出,多個
|
|
54
|
+
* 配置單元共用同一個 `srcDir` 時每個落點還會收到全庫節點。
|
|
55
|
+
*
|
|
56
|
+
* 累積的落差經 `ctx.report(platform, emitted)` 取出,不再回傳兩個裸陣列:
|
|
57
|
+
* 裸陣列讓呼叫端能繞過介面直接組裝報告,作者的 `emit` 實作卻拿不到同一條
|
|
58
|
+
* 路徑,Python/Rust 兩側則是兩者都走 `report()`。同一件事在三個生態有兩種
|
|
59
|
+
* 形態,就是「這個生態的第三方適配做得到的事比另兩個少」。
|
|
60
|
+
*/
|
|
61
|
+
export function createAdapterContext(platform, select) {
|
|
62
|
+
const degraded = [];
|
|
63
|
+
const unsupported = [];
|
|
64
|
+
return {
|
|
65
|
+
entries: (kind) => entriesFor(platform, select, kind),
|
|
66
|
+
render: (entry, selfPath, layout) => renderEntry(entry, selfPath, layout),
|
|
67
|
+
overrides: (entry) => nonEmpty(resolveOverrides(entry, platform).native),
|
|
68
|
+
reserved: (entry) => nonEmpty(resolveOverrides(entry, platform).reserved),
|
|
69
|
+
unsupportedOverride: (entry, form) => {
|
|
70
|
+
const nodeJson = JSON.stringify(overridePayload(entry));
|
|
71
|
+
const item = JSON.parse(loadNativeCore().unsupportedOverride(nodeJson, platform, form, currentLocale()));
|
|
72
|
+
if (item)
|
|
73
|
+
unsupported.push(item);
|
|
74
|
+
},
|
|
75
|
+
degrade: (item) => degraded.push(item),
|
|
76
|
+
unsupported: (item) => unsupported.push(item),
|
|
77
|
+
report: (platform, emitted) => ({ platform, emitted: [...emitted], degraded: [...degraded], unsupported: [...unsupported] }),
|
|
78
|
+
hasNotes: () => degraded.length > 0 || unsupported.length > 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// ── 覆寫讀取:委派核心 adapter::resolve_overrides(ADR-004),SDK 不自行解析 ──
|
|
82
|
+
/**
|
|
83
|
+
* 交給核心的節點形態:只要 `kind`/`id`/`config` 三欄。
|
|
84
|
+
*
|
|
85
|
+
* 不序列化整個節點:`name` 與 `content` 與覆寫無關,而 `content` 在求值期
|
|
86
|
+
* 含函式呼叫與引用建構子,要求它先轉成 `Piece[]` 才讀得到覆寫,等於為了讀
|
|
87
|
+
* 幾個鍵付一次完整的內容正規化。
|
|
88
|
+
*/
|
|
89
|
+
function overridePayload(entry) {
|
|
90
|
+
return { kind: entry.kind, id: entry.id, config: entry.def.config ?? null };
|
|
91
|
+
}
|
|
92
|
+
/** 委派核心把覆寫拆成平台原生鍵與框架保留鍵兩份。 */
|
|
93
|
+
function resolveOverrides(entry, platform) {
|
|
94
|
+
const nodeJson = JSON.stringify(overridePayload(entry));
|
|
95
|
+
return JSON.parse(loadNativeCore().platformOverrides(nodeJson, platform, currentLocale()));
|
|
96
|
+
}
|
|
97
|
+
/** 空物件與未宣告在介面上是同一件事(ADR-004),一律回 `undefined`。 */
|
|
98
|
+
function nonEmpty(map) {
|
|
99
|
+
return Object.keys(map).length > 0 ? map : undefined;
|
|
100
|
+
}
|
|
101
|
+
// ── 渲染:委派核心 project::render_pieces(AC-100),SDK 不再重複實作 ──
|
|
102
|
+
/**
|
|
103
|
+
* `Content` → `Piece[]` 這一步留在 SDK(依賴 `entry.def.content` 與求值期
|
|
104
|
+
* 概念,核心無從得知),轉換完成後把純資料連同落點表、名稱表一併交給
|
|
105
|
+
* 原生綁定的 `renderPieces`,回傳的字串就是最終 markdown。
|
|
106
|
+
*
|
|
107
|
+
* 名稱表覆蓋 `engine.entries()` 當下的全部節點(依 `kind:id` 複合鍵,與核心
|
|
108
|
+
* `key()` 同構):渲染發生在單次呼叫裡,無法像原本的 TypeScript 實作那樣
|
|
109
|
+
* 在遇到 `ref` 當下才惰性查一次,因此在呼叫前把可能用得到的名稱全部帶過去。
|
|
110
|
+
*/
|
|
111
|
+
function renderEntry(entry, selfPath, layout) {
|
|
112
|
+
const pieces = contentToPieces(entry.def.content);
|
|
113
|
+
const layoutObj = {};
|
|
114
|
+
for (const [k, v] of layout)
|
|
115
|
+
layoutObj[k] = v;
|
|
116
|
+
const namesObj = {};
|
|
117
|
+
for (const e of engine.entries())
|
|
118
|
+
namesObj[`${e.kind}:${e.id}`] = e.name;
|
|
119
|
+
// 逐個引數先接成綁定那側的參數名再傳,讓位置引數的對應關係在源碼上看得出
|
|
120
|
+
// 來;三方的參數順序目前沒有自動對拍。
|
|
121
|
+
// 內文從第幾級標題起算不在這裡給:三個 SDK 的 `ctx.render` 必須渲染出逐字
|
|
122
|
+
// 相同的 markdown,該值住核心(`ops::THIRD_PARTY_BODY_DEPTH`)。
|
|
123
|
+
const piecesJson = JSON.stringify(pieces);
|
|
124
|
+
const layoutJson = JSON.stringify(layoutObj);
|
|
125
|
+
const namesJson = JSON.stringify(namesObj);
|
|
126
|
+
return loadNativeCore().renderPieces(piecesJson, selfPath, layoutJson, namesJson, currentLocale());
|
|
127
|
+
}
|