create-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 +33 -48
- package/bin/create-promptex-js.mjs +5 -61
- package/dist/default-ref.d.ts +6 -0
- package/dist/default-ref.js +14 -0
- package/dist/index.d.ts +2 -22
- package/dist/index.js +213 -161
- package/dist/messages.generated.d.ts +9 -0
- package/dist/messages.generated.js +296 -0
- package/package.json +36 -16
package/README.md
CHANGED
|
@@ -1,55 +1,40 @@
|
|
|
1
|
-
# create-promptex-js
|
|
1
|
+
# create-promptex-js
|
|
2
2
|
|
|
3
|
-
promptex
|
|
4
|
-
|
|
3
|
+
Scaffold a [promptex](https://github.com/promptex-ai/promptex) TypeScript
|
|
4
|
+
project, plugin or adapter from the
|
|
5
|
+
[promptex-templates](https://github.com/promptex-ai/promptex-templates)
|
|
6
|
+
starters.
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
本套件是早期 alpha。骨架只有四個檔案、一份範例宣告,沒有多單元配置、沒有
|
|
9
|
-
多平台選擇、沒有外掛與轉接器。正式版把骨架收在核心(`promptex init`),
|
|
10
|
-
本原型自帶一份副本,正式版發布時會被整份取代。
|
|
11
|
-
|
|
12
|
-
介面與骨架內容在正式版之前都會變,不保證任何相容性。
|
|
13
|
-
|
|
14
|
-
## 用法
|
|
15
|
-
|
|
16
|
-
不需要事先安裝,`npm create` 會取得後直接執行。需要 Node.js 22.14 以上。
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm create promptex-js my-prompts
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
產出:
|
|
23
|
-
|
|
24
|
-
```text
|
|
25
|
-
my-prompts/
|
|
26
|
-
├── .gitignore
|
|
27
|
-
├── README.md
|
|
28
|
-
├── package.json
|
|
29
|
-
└── prompts/skill.ts
|
|
8
|
+
```console
|
|
9
|
+
npm create promptex-js
|
|
30
10
|
```
|
|
31
11
|
|
|
32
|
-
|
|
33
|
-
`prompts/skill.py`,`--lang rust` 產出 `Cargo.toml` 與 `src/main.rs`。)
|
|
34
|
-
|
|
35
|
-
### 選項
|
|
12
|
+
Interactive by default; every answer has a flag for non-interactive use:
|
|
36
13
|
|
|
37
|
-
```
|
|
38
|
-
npm create promptex-js
|
|
39
|
-
|
|
40
|
-
--lang <語言> 骨架語言,預設 ts
|
|
41
|
-
--yes 目標目錄已有同名檔案時仍覆寫
|
|
14
|
+
```console
|
|
15
|
+
npm create promptex-js my-prompts -- --template project --name my-prompts --no-git --yes
|
|
42
16
|
```
|
|
43
17
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
18
|
+
| Flag | Meaning |
|
|
19
|
+
| :--- | :--- |
|
|
20
|
+
| `--template <t>` | `project`(消費專案)、`plugin`、`adapter` |
|
|
21
|
+
| `--name <n>` | kebab-case 名稱;預設取目標目錄名正規化 |
|
|
22
|
+
| `--ref <r>` | 抓 promptex-templates 的指定 branch/tag;預設 `v<本套件版本>` |
|
|
23
|
+
| `--source <p>` | 改用本地 promptex-templates checkout(離線與開發用) |
|
|
24
|
+
| `--git` / `--no-git` | 是否 `git init`;互動預設是 |
|
|
25
|
+
| `--install` | 鷹架後直接 `npm install`;預設只印下一步 |
|
|
26
|
+
| `--yes` | 跳過互動,全部取預設或旗標值 |
|
|
27
|
+
| `--locale <l>` | 訊息與範例資源的語系:`en`(預設)或 `zh-Hant` |
|
|
28
|
+
|
|
29
|
+
語系未以 `--locale` 指定時,依序讀 `PROMPTEX_LOCALE`、`LANGUAGE`、`LC_ALL`、
|
|
30
|
+
`LC_MESSAGES`、`LANG`,都沒有就用英文;`LC_ALL=C` 意為關掉在地化。`zh-TW`、
|
|
31
|
+
`zh-HK`、`zh-MO` 與 `zh-Hant-*` 都對到 `zh-Hant`。它同時決定鋪出來的範例資源
|
|
32
|
+
用哪個語系寫([ADR-030](https://github.com/promptex-ai/ssot)):那份內容從產出
|
|
33
|
+
當下起就歸你,框架不再改寫它。plugin 與 adapter 骨架裡教你怎麼填的註解維持英文。
|
|
34
|
+
|
|
35
|
+
目標目錄非空時列出既有項目、零寫入退出。模板不內嵌:單一來源住
|
|
36
|
+
promptex-templates 倉庫,鷹架時以 git 取得對應版本 tag(需要 git;
|
|
37
|
+
`{{promptexVersion}}` 於鷹架當下以本套件版本填入,三套件與 SDK 同版發布)。
|
|
38
|
+
|
|
39
|
+
`promptex init` 與本工具的分工:init 在既有專案就地補上 promptex(非互動、
|
|
40
|
+
極簡骨架);本工具從零開一個 repo(互動、三種模板、git init)。
|
|
@@ -1,63 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// create-promptex-js
|
|
2
|
+
// create-promptex-js 的轉發殼。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
const USAGE = `create-promptex-js(alpha 原型)
|
|
10
|
-
|
|
11
|
-
用法:
|
|
12
|
-
npm create promptex-js <目錄> -- [--lang ts|python|rust] [--yes]
|
|
13
|
-
|
|
14
|
-
選項:
|
|
15
|
-
--lang <語言> 骨架語言,預設 ts
|
|
16
|
-
--yes 目標目錄已有同名檔案時仍覆寫
|
|
17
|
-
--help 顯示本說明
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
function parseArgs(argv) {
|
|
21
|
-
const args = { dir: null, lang: 'ts', assumeYes: false }
|
|
22
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
23
|
-
const token = argv[i]
|
|
24
|
-
if (token === '--yes' || token === '-y') args.assumeYes = true
|
|
25
|
-
else if (token === '--lang') {
|
|
26
|
-
const value = argv[i + 1]
|
|
27
|
-
if (value === undefined) throw new Error('--lang 後面缺少值')
|
|
28
|
-
if (!LANGS.includes(value)) throw new Error(`不支援的語言:${value}(可用:${LANGS.join('、')})`)
|
|
29
|
-
args.lang = value
|
|
30
|
-
i += 1
|
|
31
|
-
} else if (token.startsWith('-')) throw new Error(`未知的選項:${token}`)
|
|
32
|
-
else if (args.dir === null) args.dir = token
|
|
33
|
-
else throw new Error(`多餘的位置參數:${token}`)
|
|
34
|
-
}
|
|
35
|
-
if (args.dir === null) throw new Error('缺少目標目錄。用法:npm create promptex-js <目錄>')
|
|
36
|
-
return args
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function main() {
|
|
40
|
-
const argv = process.argv.slice(2)
|
|
41
|
-
if (argv.includes('--help') || argv.includes('-h')) {
|
|
42
|
-
process.stdout.write(USAGE)
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const args = parseArgs(argv)
|
|
47
|
-
const outcome = await scaffold(args.dir, args.lang, args.assumeYes)
|
|
48
|
-
|
|
49
|
-
if (outcome.aborted) {
|
|
50
|
-
process.stderr.write(`目標目錄已有同名檔案,未寫入任何檔案:\n`)
|
|
51
|
-
for (const path of outcome.conflicts) process.stderr.write(` ${path}\n`)
|
|
52
|
-
process.stderr.write(`確定要覆寫請加 --yes。\n`)
|
|
53
|
-
process.exitCode = 1
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
for (const path of outcome.written) process.stdout.write(`${path}\n`)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
await main().catch((error) => {
|
|
61
|
-
process.stderr.write(`${error.message}\n`)
|
|
62
|
-
process.exitCode = 1
|
|
63
|
-
})
|
|
4
|
+
// 邏輯住 `src/index.ts`(發布物為 `dist/index.js`),本檔只負責被 npm 當成
|
|
5
|
+
// bin 執行:鷹架的流程是頂層的一段腳本而非可呼叫的函式(它從 argv 讀到底、
|
|
6
|
+
// 逐步問完就結束),因此這裡 import 即執行,不另外呼叫誰。
|
|
7
|
+
import '../dist/index.js'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// 預設 templates ref 的判定(決策 d006)。三個 create-* 鷹架規則一致:
|
|
2
|
+
// GA 版號(X.Y.Z)抓對應 tag `v<版本>`;帶預發布後綴(-alpha.N、-beta.N、-rc.N 等)
|
|
3
|
+
// 的版本,tag 不一定存在,預設抓 `main`;使用者給 --ref 一律照用。
|
|
4
|
+
const GA_VERSION = /^\d+\.\d+\.\d+$/;
|
|
5
|
+
export function defaultRef(version) {
|
|
6
|
+
return GA_VERSION.test(version) ? `v${version}` : 'main';
|
|
7
|
+
}
|
|
8
|
+
/** `showCloneHint`:clone 失敗時是否附上「tag 可能尚未建立」提示,只在 GA 預設 ref 時為真。 */
|
|
9
|
+
export function resolveRef(explicit, version) {
|
|
10
|
+
if (explicit)
|
|
11
|
+
return { ref: explicit, showCloneHint: false };
|
|
12
|
+
const ref = defaultRef(version);
|
|
13
|
+
return { ref, showCloneHint: ref !== 'main' };
|
|
14
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export declare const LANGS: readonly Lang[];
|
|
4
|
-
/**
|
|
5
|
-
* 一份骨架的全部檔案,鍵為相對路徑。
|
|
6
|
-
*
|
|
7
|
-
* 純計算、不碰檔案系統:讓「骨架長什麼樣」可被直接比對,不必先寫到磁碟。
|
|
8
|
-
*/
|
|
9
|
-
export declare function skeleton(name: string, lang: Lang): Record<string, string>;
|
|
10
|
-
/** 一次初始化的處置結果。`aborted` 為真時 `written` 必為空。 */
|
|
11
|
-
export interface Outcome {
|
|
12
|
-
aborted: boolean;
|
|
13
|
-
conflicts: string[];
|
|
14
|
-
written: string[];
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* 在目標目錄產出骨架。
|
|
18
|
-
*
|
|
19
|
-
* 全有全無:有同名檔案且未帶 assumeYes 時一個檔案都不寫,避免留下半套骨架。
|
|
20
|
-
* 這與正式版 `promptex init` 的衝突處置一致。
|
|
21
|
-
*/
|
|
22
|
-
export declare function scaffold(targetDir: string, lang: Lang, assumeYes: boolean): Promise<Outcome>;
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare function normalizeName(raw: string): string;
|
package/dist/index.js
CHANGED
|
@@ -1,177 +1,229 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 鷹架 CLI:從 promptex-templates 倉庫開出 TypeScript 的 project/plugin/
|
|
3
|
+
// adapter 專案。六階段:環境檢查 → 互動收集 → 模板取得 → 具現化 → 後處理 →
|
|
4
|
+
// 稽核輸出;非互動時(--yes 或無 TTY)全部取預設或旗標值。
|
|
2
5
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
''
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
''
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
scripts: { build: 'promptex build' },
|
|
46
|
-
dependencies: { 'promptex-js': '0.0.1-alpha.1' },
|
|
47
|
-
}, null, 2) + '\n',
|
|
48
|
-
'prompts/skill.ts': [
|
|
49
|
-
"import { defineSkill } from 'promptex-js'",
|
|
50
|
-
'',
|
|
51
|
-
'export const commitHelper = defineSkill({',
|
|
52
|
-
" id: '" + SKILL_ID + "',",
|
|
53
|
-
" name: '" + SKILL_NAME + "',",
|
|
54
|
-
" config: { whenToUse: '" + SKILL_WHEN + "' },",
|
|
55
|
-
' content: [',
|
|
56
|
-
" '" + SKILL_P1 + "',",
|
|
57
|
-
" '" + SKILL_P2 + "',",
|
|
58
|
-
' ],',
|
|
59
|
-
'})',
|
|
60
|
-
'',
|
|
61
|
-
].join('\n'),
|
|
62
|
-
'.gitignore': 'node_modules/\n',
|
|
63
|
-
'README.md': readme(name, 'ts'),
|
|
64
|
-
});
|
|
65
|
-
const pythonSkeleton = (name) => ({
|
|
66
|
-
'pyproject.toml': [
|
|
67
|
-
'[project]',
|
|
68
|
-
'name = "' + name + '"',
|
|
69
|
-
'version = "0.0.0"',
|
|
70
|
-
'requires-python = ">=3.10"',
|
|
71
|
-
'dependencies = ["promptex-py==0.0.1a1"]',
|
|
72
|
-
'',
|
|
73
|
-
].join('\n'),
|
|
74
|
-
'prompts/skill.py': [
|
|
75
|
-
'from promptex import config, define_skill',
|
|
76
|
-
'',
|
|
77
|
-
'commit_helper = define_skill(',
|
|
78
|
-
' "' + SKILL_ID + '",',
|
|
79
|
-
' "' + SKILL_NAME + '",',
|
|
80
|
-
' config=config(when_to_use="' + SKILL_WHEN + '"),',
|
|
81
|
-
' content=[',
|
|
82
|
-
' "' + SKILL_P1 + '",',
|
|
83
|
-
' "' + SKILL_P2 + '",',
|
|
84
|
-
' ],',
|
|
85
|
-
')',
|
|
86
|
-
'',
|
|
87
|
-
].join('\n'),
|
|
88
|
-
'.gitignore': '__pycache__/\n',
|
|
89
|
-
'README.md': readme(name, 'python'),
|
|
90
|
-
});
|
|
91
|
-
const rustSkeleton = (name) => ({
|
|
92
|
-
'Cargo.toml': [
|
|
93
|
-
'[package]',
|
|
94
|
-
'name = "' + name + '"',
|
|
95
|
-
'version = "0.0.0"',
|
|
96
|
-
'edition = "2021"',
|
|
97
|
-
'',
|
|
98
|
-
'[dependencies]',
|
|
99
|
-
'promptex-rs = "0.0.1-alpha.1"',
|
|
100
|
-
'',
|
|
101
|
-
].join('\n'),
|
|
102
|
-
'src/main.rs': [
|
|
103
|
-
'use promptex_rs::{build, define_skill, CapabilityConfig, SkillDef};',
|
|
104
|
-
'',
|
|
105
|
-
'fn main() -> Result<(), Box<dyn std::error::Error>> {',
|
|
106
|
-
' define_skill(SkillDef {',
|
|
107
|
-
' id: "' + SKILL_ID + '".into(),',
|
|
108
|
-
' name: "' + SKILL_NAME + '".into(),',
|
|
109
|
-
' config: CapabilityConfig::new().when_to_use("' + SKILL_WHEN + '"),',
|
|
110
|
-
' content: vec![',
|
|
111
|
-
' "' + SKILL_P1 + '".into(),',
|
|
112
|
-
' "' + SKILL_P2 + '".into(),',
|
|
113
|
-
' ],',
|
|
114
|
-
' });',
|
|
115
|
-
'',
|
|
116
|
-
' for path in build(".")? {',
|
|
117
|
-
' println!("{}", path.display());',
|
|
118
|
-
' }',
|
|
119
|
-
' Ok(())',
|
|
120
|
-
'}',
|
|
121
|
-
'',
|
|
122
|
-
].join('\n'),
|
|
123
|
-
'.gitignore': 'target/\n',
|
|
124
|
-
'README.md': readme(name, 'rust'),
|
|
6
|
+
// 模板不內嵌:單一來源住 promptex-templates,GA 版預設抓與本套件版本對應的
|
|
7
|
+
// tag(v<版本>),預發布版預設抓 main,--ref 覆寫、--source 改用本地 checkout
|
|
8
|
+
// (離線與開發用)。
|
|
9
|
+
//
|
|
10
|
+
// 用法:
|
|
11
|
+
// npm create promptex-js [目標目錄] [-- --template project|plugin|adapter \
|
|
12
|
+
// --name <kebab 名> --ref <branch|tag> --source <本地路徑> \
|
|
13
|
+
// --git|--no-git --install --yes --locale <語系>]
|
|
14
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
15
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import * as p from '@clack/prompts';
|
|
20
|
+
import mri from 'mri';
|
|
21
|
+
import { resolveRef } from './default-ref.js';
|
|
22
|
+
import { resolveLocale, t } from './messages.generated.js';
|
|
23
|
+
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
24
|
+
const ownVersion = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')).version;
|
|
25
|
+
const TEMPLATES = ['project', 'plugin', 'adapter'];
|
|
26
|
+
// 與 promptex init 的單元名正規化同一套規則(crates/promptex-core/src/init.rs)。
|
|
27
|
+
const RESERVED_UNIT_NAMES = ['cache', 'preview', 'content', 'content-index', 'graph', 'main'];
|
|
28
|
+
export function normalizeName(raw) {
|
|
29
|
+
let out = '';
|
|
30
|
+
for (const ch of raw.toLowerCase()) {
|
|
31
|
+
if (/[a-z0-9]/.test(ch))
|
|
32
|
+
out += ch;
|
|
33
|
+
else if (!out.endsWith('-'))
|
|
34
|
+
out += '-';
|
|
35
|
+
}
|
|
36
|
+
out = out.replace(/^-+|-+$/g, '');
|
|
37
|
+
return out === '' || RESERVED_UNIT_NAMES.includes(out) ? 'main' : out;
|
|
38
|
+
}
|
|
39
|
+
function bail(message) {
|
|
40
|
+
console.error(message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const TEMPLATES_REPO = 'https://github.com/promptex-ai/promptex-templates.git';
|
|
44
|
+
const args = mri(process.argv.slice(2), {
|
|
45
|
+
boolean: ['install', 'yes', 'git', 'help'],
|
|
46
|
+
string: ['template', 'name', 'ref', 'source', 'locale'],
|
|
47
|
+
alias: { h: 'help' },
|
|
125
48
|
});
|
|
49
|
+
const locale = resolveLocale(args.locale);
|
|
50
|
+
const separator = t('common.list_separator', locale);
|
|
51
|
+
if (args.help) {
|
|
52
|
+
console.log(t('scaffold.usage.js', locale));
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
const interactive = process.stdin.isTTY && !args.yes;
|
|
56
|
+
p.intro(`create-promptex-js v${ownVersion}`);
|
|
57
|
+
// ── 互動收集(缺什麼問什麼;非互動時取預設) ──
|
|
126
58
|
/**
|
|
127
|
-
*
|
|
59
|
+
* 缺什麼問什麼:旗標已給就用它,非互動(`--yes` 或無 TTY)取預設,其餘才問。
|
|
128
60
|
*
|
|
129
|
-
*
|
|
61
|
+
* 泛型鎖住「問到的答案與預設值同型」:`p.text` 回字串、`p.confirm` 回布林,
|
|
62
|
+
* 兩者的呼叫點接著就把值餵給只收該型別的下游。
|
|
130
63
|
*/
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
64
|
+
async function ask(value, prompt) {
|
|
65
|
+
if (value !== undefined)
|
|
66
|
+
return value;
|
|
67
|
+
if (!interactive)
|
|
68
|
+
return prompt.fallback;
|
|
69
|
+
const answer = await prompt.run();
|
|
70
|
+
if (p.isCancel(answer)) {
|
|
71
|
+
p.cancel(t('scaffold.cancelled', locale));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
return answer;
|
|
75
|
+
}
|
|
76
|
+
const targetDirRaw = await ask(args._[0], {
|
|
77
|
+
fallback: 'promptex-project',
|
|
78
|
+
run: () => p.text({
|
|
79
|
+
message: t('scaffold.prompt.target_dir', locale),
|
|
80
|
+
placeholder: 'promptex-project',
|
|
81
|
+
defaultValue: 'promptex-project',
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
const targetDir = resolve(String(targetDirRaw));
|
|
85
|
+
const template = await ask(args.template, {
|
|
86
|
+
fallback: 'project',
|
|
87
|
+
run: () => p.select({
|
|
88
|
+
message: t('scaffold.prompt.template', locale),
|
|
89
|
+
options: [
|
|
90
|
+
{ value: 'project', label: t('scaffold.template_option.project', locale) },
|
|
91
|
+
{ value: 'plugin', label: t('scaffold.template_option.plugin', locale) },
|
|
92
|
+
{ value: 'adapter', label: t('scaffold.template_option.adapter', locale) },
|
|
93
|
+
],
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
if (!TEMPLATES.includes(template)) {
|
|
97
|
+
bail(t('scaffold.error.template_unsupported', locale, { template, choices: TEMPLATES.join(separator) }));
|
|
98
|
+
}
|
|
99
|
+
const defaultName = normalizeName(basename(targetDir));
|
|
100
|
+
const name = normalizeName(String(await ask(args.name, {
|
|
101
|
+
fallback: defaultName,
|
|
102
|
+
run: () => p.text({ message: t('scaffold.prompt.name', locale), defaultValue: defaultName, placeholder: defaultName }),
|
|
103
|
+
})));
|
|
104
|
+
const wantGit = args.git !== undefined
|
|
105
|
+
? args.git
|
|
106
|
+
: await ask(undefined, {
|
|
107
|
+
fallback: true,
|
|
108
|
+
run: () => p.confirm({ message: t('scaffold.prompt.git_init', locale), initialValue: true }),
|
|
109
|
+
});
|
|
110
|
+
// ── 環境檢查:目標目錄非空即列衝突、零寫入退出(比照 promptex init)。 ──
|
|
111
|
+
if (existsSync(targetDir)) {
|
|
112
|
+
const entries = readdirSync(targetDir);
|
|
113
|
+
if (entries.length > 0) {
|
|
114
|
+
const listing = `${entries.slice(0, 10).join(separator)}${entries.length > 10 ? '…' : ''}`;
|
|
115
|
+
bail(t('scaffold.error.dir_not_empty', locale, { path: targetDir, entries: listing }));
|
|
134
116
|
}
|
|
135
|
-
if (lang === 'ts')
|
|
136
|
-
return tsSkeleton(name);
|
|
137
|
-
if (lang === 'python')
|
|
138
|
-
return pythonSkeleton(name);
|
|
139
|
-
if (lang === 'rust')
|
|
140
|
-
return rustSkeleton(name);
|
|
141
|
-
throw new Error(`不支援的語言:${lang}(可用:${LANGS.join('、')})`);
|
|
142
117
|
}
|
|
143
|
-
const
|
|
118
|
+
const gitAvailable = spawnSync('git', ['--version'], { stdio: 'ignore' }).status === 0;
|
|
119
|
+
if (!args.source && !gitAvailable)
|
|
120
|
+
bail(t('scaffold.error.git_required', locale));
|
|
121
|
+
// ── 模板取得:--source 讀本地 checkout;否則淺 clone,GA 版預設 ref 綁本套件版本、預發布版取 main。 ──
|
|
122
|
+
let sourceRoot;
|
|
123
|
+
let sourceLabel;
|
|
124
|
+
let cloneDir;
|
|
125
|
+
if (args.source) {
|
|
126
|
+
sourceRoot = join(resolve(args.source), 'typescript');
|
|
127
|
+
sourceLabel = t('scaffold.source.local', locale, { path: resolve(args.source) });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const { ref, showCloneHint } = resolveRef(args.ref, ownVersion);
|
|
131
|
+
cloneDir = mkdtempSync(join(tmpdir(), 'promptex-templates-'));
|
|
144
132
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
133
|
+
execFileSync('git', ['clone', '--depth', '1', '--branch', ref, TEMPLATES_REPO, cloneDir], {
|
|
134
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
135
|
+
});
|
|
147
136
|
}
|
|
148
137
|
catch {
|
|
149
|
-
|
|
138
|
+
const hint = showCloneHint ? t('scaffold.error.clone_hint', locale) : '';
|
|
139
|
+
bail(t('scaffold.error.clone_failed', locale, { ref, hint }));
|
|
150
140
|
}
|
|
151
|
-
|
|
141
|
+
sourceRoot = join(cloneDir, 'typescript');
|
|
142
|
+
sourceLabel = `promptex-templates@${ref}`;
|
|
143
|
+
}
|
|
144
|
+
const starterDir = join(sourceRoot, `${template}-starter`);
|
|
145
|
+
if (!existsSync(starterDir))
|
|
146
|
+
bail(t('scaffold.error.starter_missing', locale, { template, path: starterDir }));
|
|
147
|
+
// ── 具現化:複製、佔位替換、_gitignore 改名。 ──
|
|
152
148
|
/**
|
|
153
|
-
*
|
|
149
|
+
* 把一則訊息文字變成可直接嵌進單引號字串字面量的形態。
|
|
150
|
+
*
|
|
151
|
+
* 本套件只鋪 TypeScript 模板,而模板一律以單引號包住文字佔位符,因此界定符固定
|
|
152
|
+
* 是單引號。反斜線必須第一個處理:先跳脫界定符會讓後補的反斜線又被自己跳脫一次。
|
|
154
153
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
154
|
+
* 此前不跳脫,於是英文的 `this skill's content` 一進模板就提前關閉字串,
|
|
155
|
+
* `npm create promptex-js` 鋪出來的 plugin 與 adapter 專案在第一次建置即失敗
|
|
156
|
+
* (中文的對應文字沒有 ASCII 引號,因此只有英文使用者踩得到)。核心的 `init`
|
|
157
|
+
* 骨架走另一條路徑,同一個缺陷在那裡另行修正(`promptex_core::init`)。
|
|
157
158
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
159
|
+
function escapeForLiteral(text) {
|
|
160
|
+
return text.replaceAll('\\', '\\\\').replaceAll('\n', '\\n').replaceAll("'", "\\'");
|
|
161
|
+
}
|
|
162
|
+
const tokens = {
|
|
163
|
+
'{{name}}': name,
|
|
164
|
+
'{{snakeName}}': name.replaceAll('-', '_'),
|
|
165
|
+
'{{unitName}}': name,
|
|
166
|
+
'{{promptexVersion}}': ownVersion,
|
|
167
|
+
// 模板的範例資源文字(ADR-030「一次性的起手內容」、AC-523):模板檔以佔位符留位,
|
|
168
|
+
// 寫檔當下代入本次語系的那一份。實作指引註解不在此列,它們在模板裡就是英文。
|
|
169
|
+
'{{adapterDemoRuleContent}}': escapeForLiteral(t('template.adapter_demo.rule_content', locale)),
|
|
170
|
+
'{{adapterDemoSkillContent}}': escapeForLiteral(t('template.adapter_demo.skill_content', locale)),
|
|
171
|
+
'{{adapterDemoSkillDescription}}': escapeForLiteral(t('template.adapter_demo.skill_description', locale)),
|
|
172
|
+
'{{adapterDemoSkillWhenToUse}}': escapeForLiteral(t('template.adapter_demo.skill_when_to_use', locale)),
|
|
173
|
+
'{{demoRuleName}}': escapeForLiteral(t('template.demo.rule_name', locale)),
|
|
174
|
+
'{{demoSkillName}}': escapeForLiteral(t('template.demo.skill_name', locale)),
|
|
175
|
+
'{{exampleRuleIntro}}': escapeForLiteral(t('template.example_rule.intro', locale)),
|
|
176
|
+
'{{exampleRuleName}}': escapeForLiteral(t('template.example_rule.name', locale)),
|
|
177
|
+
'{{exampleRuleOtherStates}}': escapeForLiteral(t('template.example_rule.other_states', locale)),
|
|
178
|
+
'{{exampleRuleScoped}}': escapeForLiteral(t('template.example_rule.scoped', locale)),
|
|
179
|
+
'{{exampleSkillDescription}}': escapeForLiteral(t('template.example_skill.description', locale)),
|
|
180
|
+
'{{exampleSkillIntro}}': escapeForLiteral(t('template.example_skill.intro', locale)),
|
|
181
|
+
'{{exampleSkillInvoked}}': escapeForLiteral(t('template.example_skill.invoked', locale)),
|
|
182
|
+
'{{exampleSkillInvokedRs}}': escapeForLiteral(t('template.example_skill.invoked_rs', locale)),
|
|
183
|
+
'{{exampleSkillName}}': escapeForLiteral(t('template.example_skill.name', locale)),
|
|
184
|
+
'{{exampleSkillWhenToUse}}': escapeForLiteral(t('template.example_skill.when_to_use', locale)),
|
|
185
|
+
'{{pluginDemoRuleContent}}': escapeForLiteral(t('template.plugin_demo.rule_content', locale)),
|
|
186
|
+
'{{pluginDemoSkillContent}}': escapeForLiteral(t('template.plugin_demo.skill_content', locale)),
|
|
187
|
+
'{{pluginDemoSkillDescription}}': escapeForLiteral(t('template.plugin_demo.skill_description', locale)),
|
|
188
|
+
'{{pluginDemoSkillWhenToUse}}': escapeForLiteral(t('template.plugin_demo.skill_when_to_use', locale)),
|
|
189
|
+
};
|
|
190
|
+
const substitute = (text) => Object.entries(tokens).reduce((acc, [token, value]) => acc.replaceAll(token, value), text);
|
|
191
|
+
function walk(dir, rel = '') {
|
|
192
|
+
const out = [];
|
|
193
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
194
|
+
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
195
|
+
if (entry.isDirectory())
|
|
196
|
+
out.push(...walk(join(dir, entry.name), relPath));
|
|
197
|
+
else
|
|
198
|
+
out.push(relPath);
|
|
175
199
|
}
|
|
176
|
-
return
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
const written = [];
|
|
203
|
+
for (const rel of walk(starterDir)) {
|
|
204
|
+
let destRel = substitute(rel);
|
|
205
|
+
if (destRel === '_gitignore')
|
|
206
|
+
destRel = '.gitignore';
|
|
207
|
+
const destPath = join(targetDir, destRel);
|
|
208
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
209
|
+
writeFileSync(destPath, substitute(readFileSync(join(starterDir, rel), 'utf8')));
|
|
210
|
+
written.push(destRel);
|
|
211
|
+
}
|
|
212
|
+
if (cloneDir)
|
|
213
|
+
rmSync(cloneDir, { recursive: true, force: true });
|
|
214
|
+
// ── 後處理:git init(可選)、--install 裝依賴。 ──
|
|
215
|
+
if (wantGit && gitAvailable)
|
|
216
|
+
spawnSync('git', ['init', '--quiet'], { cwd: targetDir, stdio: 'inherit' });
|
|
217
|
+
if (args.install) {
|
|
218
|
+
const r = spawnSync('npm', ['install'], { cwd: targetDir, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
219
|
+
if (r.status !== 0)
|
|
220
|
+
bail(t('scaffold.error.install_failed.js', locale));
|
|
177
221
|
}
|
|
222
|
+
// ── 稽核輸出:寫了什麼、來源為何、下一步。 ──
|
|
223
|
+
p.log.success(t('scaffold.done.written', locale, { count: written.length, path: targetDir, source: sourceLabel }));
|
|
224
|
+
for (const rel of written)
|
|
225
|
+
p.log.message(` ${rel}`);
|
|
226
|
+
const nextSteps = template === 'project'
|
|
227
|
+
? [`cd ${targetDirRaw}`, ...(args.install ? [] : ['npm install']), 'npm run install:prompts']
|
|
228
|
+
: [`cd ${targetDirRaw}`, ...(args.install ? [] : ['npm install']), 'npm run build', 'cd example && npx promptex build --install'];
|
|
229
|
+
p.outro(`${t('scaffold.done.next_steps', locale)}\n${nextSteps.map((s, i) => ` ${i + 1}. ${s}`).join('\n')}`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MESSAGES: Record<string, Record<string, string>>;
|
|
2
|
+
/** 目錄認得的語系標籤。 */
|
|
3
|
+
export type LocaleTag = 'en' | 'zh-Hant';
|
|
4
|
+
/** POSIX 形態轉 BCP 47 並收斂為目錄認得的標籤;認不出來回 undefined。 */
|
|
5
|
+
export declare function parseLocale(tag: string | undefined): LocaleTag | undefined;
|
|
6
|
+
/** 依旗標與環境算出本次的語系。三項都認不出時回退路。 */
|
|
7
|
+
export declare function resolveLocale(flag?: string): LocaleTag;
|
|
8
|
+
/** 依語系取一則訊息,並以 `%{name}` 形態代入具名參數。 */
|
|
9
|
+
export declare function t(key: string, locale: LocaleTag, params?: Record<string, string | number>): string;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// 本檔由 scripts/gen-messages.mjs 自動產生,請勿手改。
|
|
2
|
+
//
|
|
3
|
+
// 來源是核心的訊息目錄 crates/promptex-core/locales/(ADR-030)。要改文字就改那裡,
|
|
4
|
+
// 再跑 `npm run gen:messages` 重新生成;直接改本檔會被核對閘擋下。
|
|
5
|
+
export const MESSAGES = {
|
|
6
|
+
"common.list_separator": {
|
|
7
|
+
"en": ", ",
|
|
8
|
+
"zh-Hant": "、",
|
|
9
|
+
},
|
|
10
|
+
"scaffold.cancelled": {
|
|
11
|
+
"en": "Cancelled, no file was written.",
|
|
12
|
+
"zh-Hant": "已取消,未寫入任何檔案。",
|
|
13
|
+
},
|
|
14
|
+
"scaffold.done.next_steps": {
|
|
15
|
+
"en": "Next steps:",
|
|
16
|
+
"zh-Hant": "下一步:",
|
|
17
|
+
},
|
|
18
|
+
"scaffold.done.written": {
|
|
19
|
+
"en": "Wrote %{count} files → %{path} (template source: %{source})",
|
|
20
|
+
"zh-Hant": "已寫入 %{count} 個檔案 → %{path}(模板來源:%{source})",
|
|
21
|
+
},
|
|
22
|
+
"scaffold.error.clone_failed": {
|
|
23
|
+
"en": "git clone promptex-templates@%{ref} failed. %{hint}",
|
|
24
|
+
"zh-Hant": "git clone promptex-templates@%{ref} 失敗。%{hint}",
|
|
25
|
+
},
|
|
26
|
+
"scaffold.error.clone_hint": {
|
|
27
|
+
"en": "The tag for this version may not have been created yet: use --ref main to get the latest, or --source <local-path> to take it offline.",
|
|
28
|
+
"zh-Hant": "此版本的 tag 可能尚未建立:用 --ref main 抓最新、或 --source <本地路徑> 離線取用。",
|
|
29
|
+
},
|
|
30
|
+
"scaffold.error.dir_create": {
|
|
31
|
+
"en": "Creating %{path} failed: %{reason}",
|
|
32
|
+
"zh-Hant": "建立 %{path} 失敗:%{reason}",
|
|
33
|
+
},
|
|
34
|
+
"scaffold.error.dir_not_empty": {
|
|
35
|
+
"en": "Target directory is not empty: %{path}\nExisting entries: %{entries}\nNo file was written.",
|
|
36
|
+
"zh-Hant": "目標目錄非空:%{path}\n既有項目:%{entries}\n未寫入任何檔案。",
|
|
37
|
+
},
|
|
38
|
+
"scaffold.error.file_read": {
|
|
39
|
+
"en": "Reading %{path} failed: %{reason}",
|
|
40
|
+
"zh-Hant": "讀取 %{path} 失敗:%{reason}",
|
|
41
|
+
},
|
|
42
|
+
"scaffold.error.file_write": {
|
|
43
|
+
"en": "Writing %{path} failed: %{reason}",
|
|
44
|
+
"zh-Hant": "寫入 %{path} 失敗:%{reason}",
|
|
45
|
+
},
|
|
46
|
+
"scaffold.error.flag_missing_value": {
|
|
47
|
+
"en": "%{flag} is missing its value",
|
|
48
|
+
"zh-Hant": "%{flag} 缺值",
|
|
49
|
+
},
|
|
50
|
+
"scaffold.error.git_required": {
|
|
51
|
+
"en": "Getting the template needs git (or give a local promptex-templates path with --source).",
|
|
52
|
+
"zh-Hant": "取得模板需要 git(或用 --source 指定本地 promptex-templates 路徑)。",
|
|
53
|
+
},
|
|
54
|
+
"scaffold.error.install_failed.js": {
|
|
55
|
+
"en": "npm install failed; the scaffold files were written, the dependencies were not installed.",
|
|
56
|
+
"zh-Hant": "npm install 失敗,鷹架檔案已寫入、依賴未裝。",
|
|
57
|
+
},
|
|
58
|
+
"scaffold.error.install_failed.py": {
|
|
59
|
+
"en": "uv sync failed; the scaffold files were written, the dependencies were not installed.",
|
|
60
|
+
"zh-Hant": "uv sync 失敗,鷹架檔案已寫入、依賴未裝。",
|
|
61
|
+
},
|
|
62
|
+
"scaffold.error.install_failed.rs": {
|
|
63
|
+
"en": "cargo fetch failed; the scaffold files were written, the dependencies were not fetched.",
|
|
64
|
+
"zh-Hant": "cargo fetch 失敗,鷹架檔案已寫入、依賴未取。",
|
|
65
|
+
},
|
|
66
|
+
"scaffold.error.starter_missing": {
|
|
67
|
+
"en": "The template source has no %{template}-starter: %{path}",
|
|
68
|
+
"zh-Hant": "模板來源缺 %{template}-starter:%{path}",
|
|
69
|
+
},
|
|
70
|
+
"scaffold.error.template_dir_read": {
|
|
71
|
+
"en": "Reading the template directory failed: %{reason}",
|
|
72
|
+
"zh-Hant": "讀取模板目錄失敗:%{reason}",
|
|
73
|
+
},
|
|
74
|
+
"scaffold.error.template_unsupported": {
|
|
75
|
+
"en": "Unsupported template: %{template} (available: %{choices})",
|
|
76
|
+
"zh-Hant": "不支援的模板:%{template}(可用:%{choices})",
|
|
77
|
+
},
|
|
78
|
+
"scaffold.error.unknown_argument": {
|
|
79
|
+
"en": "Unrecognized argument: %{arg}",
|
|
80
|
+
"zh-Hant": "不認得的引數:%{arg}",
|
|
81
|
+
},
|
|
82
|
+
"scaffold.error.uv_missing": {
|
|
83
|
+
"en": "--install needs uv, but no uv executable was found; the scaffold files were written, the dependencies were not installed.",
|
|
84
|
+
"zh-Hant": "--install 需要 uv,但找不到 uv 執行檔;鷹架檔案已寫入、依賴未裝。",
|
|
85
|
+
},
|
|
86
|
+
"scaffold.help.install": {
|
|
87
|
+
"en": "run uv sync right after scaffolding",
|
|
88
|
+
"zh-Hant": "鷹架後直接 uv sync",
|
|
89
|
+
},
|
|
90
|
+
"scaffold.help.locale": {
|
|
91
|
+
"en": "message locale (en|zh-Hant), defaults to the environment variables then English",
|
|
92
|
+
"zh-Hant": "訊息語系(en|zh-Hant),預設依環境變數,再退回英文",
|
|
93
|
+
},
|
|
94
|
+
"scaffold.help.name": {
|
|
95
|
+
"en": "kebab-case name, defaults to the normalized target directory name",
|
|
96
|
+
"zh-Hant": "kebab-case 名稱,預設取目標目錄名正規化",
|
|
97
|
+
},
|
|
98
|
+
"scaffold.help.ref": {
|
|
99
|
+
"en": "take the given branch/tag of promptex-templates (defaults to v<this package's version>; pre-release versions default to main)",
|
|
100
|
+
"zh-Hant": "抓 promptex-templates 的指定 branch/tag(預設 v<本套件版本>;預發布版預設 main)",
|
|
101
|
+
},
|
|
102
|
+
"scaffold.help.source": {
|
|
103
|
+
"en": "use a local promptex-templates checkout instead (for offline and development use)",
|
|
104
|
+
"zh-Hant": "改用本地 promptex-templates checkout(離線與開發用)",
|
|
105
|
+
},
|
|
106
|
+
"scaffold.help.target_dir": {
|
|
107
|
+
"en": "target directory",
|
|
108
|
+
"zh-Hant": "目標目錄",
|
|
109
|
+
},
|
|
110
|
+
"scaffold.help.template": {
|
|
111
|
+
"en": "project|plugin|adapter",
|
|
112
|
+
"zh-Hant": "project|plugin|adapter",
|
|
113
|
+
},
|
|
114
|
+
"scaffold.help.yes": {
|
|
115
|
+
"en": "skip the interactive prompts, take defaults or flag values throughout",
|
|
116
|
+
"zh-Hant": "跳過互動,全部取預設或旗標值",
|
|
117
|
+
},
|
|
118
|
+
"scaffold.prompt.git_init": {
|
|
119
|
+
"en": "Initialize a git repository?",
|
|
120
|
+
"zh-Hant": "初始化 git 倉庫?",
|
|
121
|
+
},
|
|
122
|
+
"scaffold.prompt.git_init_yn": {
|
|
123
|
+
"en": "Initialize a git repository? (y/n)",
|
|
124
|
+
"zh-Hant": "初始化 git 倉庫?(y/n)",
|
|
125
|
+
},
|
|
126
|
+
"scaffold.prompt.name": {
|
|
127
|
+
"en": "Name (kebab-case)",
|
|
128
|
+
"zh-Hant": "名稱(kebab-case)",
|
|
129
|
+
},
|
|
130
|
+
"scaffold.prompt.target_dir": {
|
|
131
|
+
"en": "Target directory",
|
|
132
|
+
"zh-Hant": "目標目錄",
|
|
133
|
+
},
|
|
134
|
+
"scaffold.prompt.template": {
|
|
135
|
+
"en": "Template",
|
|
136
|
+
"zh-Hant": "模板",
|
|
137
|
+
},
|
|
138
|
+
"scaffold.prompt.template_choices": {
|
|
139
|
+
"en": "Template (%{choices})",
|
|
140
|
+
"zh-Hant": "模板(%{choices})",
|
|
141
|
+
},
|
|
142
|
+
"scaffold.source.local": {
|
|
143
|
+
"en": "local template (%{path})",
|
|
144
|
+
"zh-Hant": "本地模板(%{path})",
|
|
145
|
+
},
|
|
146
|
+
"scaffold.template_option.adapter": {
|
|
147
|
+
"en": "adapter: a publishable third-party platform adapter package",
|
|
148
|
+
"zh-Hant": "adapter:可發布的第三方平台 adapter 套件",
|
|
149
|
+
},
|
|
150
|
+
"scaffold.template_option.plugin": {
|
|
151
|
+
"en": "plugin: a publishable plugin package",
|
|
152
|
+
"zh-Hant": "plugin:可發布的 plugin 套件",
|
|
153
|
+
},
|
|
154
|
+
"scaffold.template_option.project": {
|
|
155
|
+
"en": "project: prompt source tree (a consuming project)",
|
|
156
|
+
"zh-Hant": "project:提示詞源碼樹(消費專案)",
|
|
157
|
+
},
|
|
158
|
+
"scaffold.usage.js": {
|
|
159
|
+
"en": "create-promptex-js [target-dir] --template project|plugin|adapter --name <kebab-name> [--ref <branch|tag>] [--source <local-path>] [--git|--no-git] [--install] [--yes] [--locale <locale>]",
|
|
160
|
+
"zh-Hant": "create-promptex-js [目標目錄] --template project|plugin|adapter --name <kebab 名> [--ref <branch|tag>] [--source <本地路徑>] [--git|--no-git] [--install] [--yes] [--locale <語系>]",
|
|
161
|
+
},
|
|
162
|
+
"scaffold.usage.py": {
|
|
163
|
+
"en": "uvx create-promptex-py [target-dir] [--template project|plugin|adapter] [--name <kebab-name>] [--ref <branch|tag>] [--source <local-path>] [--git|--no-git] [--install] [--yes] [--locale <locale>]",
|
|
164
|
+
"zh-Hant": "uvx create-promptex-py [目標目錄] [--template project|plugin|adapter] [--name <kebab 名>] [--ref <branch|tag>] [--source <本地路徑>] [--git|--no-git] [--install] [--yes] [--locale <語系>]",
|
|
165
|
+
},
|
|
166
|
+
"scaffold.usage.rs": {
|
|
167
|
+
"en": "cargo create-promptex-rs [target-dir] --template project|plugin|adapter --name <kebab-name> [--ref <branch|tag>] [--source <local-path>] [--git|--no-git] [--install] [--yes] [--locale <locale>]",
|
|
168
|
+
"zh-Hant": "cargo create-promptex-rs [目標目錄] --template project|plugin|adapter --name <kebab 名> [--ref <branch|tag>] [--source <本地路徑>] [--git|--no-git] [--install] [--yes] [--locale <語系>]",
|
|
169
|
+
},
|
|
170
|
+
"template.adapter_demo.rule_content": {
|
|
171
|
+
"en": "On the custom platform this rule degrades to always-on, with its scope noted in the body.",
|
|
172
|
+
"zh-Hant": "本規則在自訂平台會降級為常駐,並於內文標註適用範圍。",
|
|
173
|
+
},
|
|
174
|
+
"template.adapter_demo.skill_content": {
|
|
175
|
+
"en": "This skill is projected onto two placements at once: claude and the custom platform.",
|
|
176
|
+
"zh-Hant": "本技能會同時投影到 claude 與自訂平台兩個落點。",
|
|
177
|
+
},
|
|
178
|
+
"template.adapter_demo.skill_description": {
|
|
179
|
+
"en": "A demo skill the adapter example projects",
|
|
180
|
+
"zh-Hant": "adapter 範例用來輸出的示範技能",
|
|
181
|
+
},
|
|
182
|
+
"template.adapter_demo.skill_when_to_use": {
|
|
183
|
+
"en": "When checking what the custom adapter emits",
|
|
184
|
+
"zh-Hant": "驗證自訂 adapter 的產出時",
|
|
185
|
+
},
|
|
186
|
+
"template.demo.rule_name": {
|
|
187
|
+
"en": "Demo rule",
|
|
188
|
+
"zh-Hant": "示範規則",
|
|
189
|
+
},
|
|
190
|
+
"template.demo.skill_name": {
|
|
191
|
+
"en": "Demo skill",
|
|
192
|
+
"zh-Hant": "示範技能",
|
|
193
|
+
},
|
|
194
|
+
"template.example_rule.intro": {
|
|
195
|
+
"en": "This is the example resource produced by promptex init; change it or delete it as your project needs.",
|
|
196
|
+
"zh-Hant": "這是 promptex init 產出的範例資源,可依專案需求修改或刪除。",
|
|
197
|
+
},
|
|
198
|
+
"template.example_rule.name": {
|
|
199
|
+
"en": "Example rule",
|
|
200
|
+
"zh-Hant": "範例規則",
|
|
201
|
+
},
|
|
202
|
+
"template.example_rule.other_states": {
|
|
203
|
+
"en": "The other two states: declare the always-on flag instead and it loads unconditionally, regardless of what you are doing; declare neither and you get the default third state, where the resource is still installed on the target but never enters context on its own, appearing only when another resource references it explicitly. A skeleton that stopped at the third state would hand the author a rule that compiles and does nothing.",
|
|
204
|
+
"zh-Hant": "另兩態:改宣告常駐旗標即無條件載入,不看當下在做什麼;兩者都不宣告則是預設的第三態,資源照樣安裝到目標端,卻不會自動進入上下文,只在被其他資源顯式引用時登場。骨架若停在第三態,作者拿到的會是一份編得動、卻不會有任何作用的規則。",
|
|
205
|
+
},
|
|
206
|
+
"template.example_rule.scoped": {
|
|
207
|
+
"en": "This rule declares a scope (every file under the prompts source directory), which puts it in the scoped-loading state, one of the three loading states. The scope declaration appears in the artifact as that platform's own loading declaration, and the platform loads the rule into context only while working on a matching file.",
|
|
208
|
+
"zh-Hant": "本規則宣告了適用範圍(源碼目錄 prompts 下的全部檔案),因此屬載入宣告三態中的範圍載入態:範圍宣告在產物裡呈現為該平台的載入宣告,平台於符合的檔案上工作時才把規則載入上下文。",
|
|
209
|
+
},
|
|
210
|
+
"template.example_skill.description": {
|
|
211
|
+
"en": "Shows how an invoked resource is declared: a description of what it does and trigger text for when to use it",
|
|
212
|
+
"zh-Hant": "示範被喚起型資源的宣告:描述它做什麼、觸發語料說何時用",
|
|
213
|
+
},
|
|
214
|
+
"template.example_skill.intro": {
|
|
215
|
+
"en": "This is the example skill that ships with the project starter; change it or delete it as your project needs.",
|
|
216
|
+
"zh-Hant": "這是 project starter 附帶的範例技能,可依專案需求修改或刪除。",
|
|
217
|
+
},
|
|
218
|
+
"template.example_skill.invoked": {
|
|
219
|
+
"en": "A skill is an invoked resource: config.whenToUse is its trigger text (required), and the platform decides from it when to load the skill into context. Set against the rule next to it, which uses scoped loading, the two together show the most common pair of loading declarations.",
|
|
220
|
+
"zh-Hant": "技能屬被喚起型資源:config.whenToUse 是觸發語料(必填),平台依它判斷何時把本技能載入上下文。與相鄰的規則(範圍載入)對照,兩者合起來展示了載入宣告最常用的兩種形態。",
|
|
221
|
+
},
|
|
222
|
+
"template.example_skill.invoked_rs": {
|
|
223
|
+
"en": "A skill is an invoked resource: whenToUse on config is its trigger text (required, enforced by the typestate parameter), and the platform decides from it when to load the skill into context. Set against the rule next to it, which uses scoped loading, the two together show the most common pair of loading declarations.",
|
|
224
|
+
"zh-Hant": "技能屬被喚起型資源:config 的 whenToUse 是觸發語料(必填,型別狀態參數把關),平台依它判斷何時把本技能載入上下文。與相鄰的規則(範圍載入)對照,兩者合起來展示了載入宣告最常用的兩種形態。",
|
|
225
|
+
},
|
|
226
|
+
"template.example_skill.name": {
|
|
227
|
+
"en": "Example skill",
|
|
228
|
+
"zh-Hant": "範例技能",
|
|
229
|
+
},
|
|
230
|
+
"template.example_skill.when_to_use": {
|
|
231
|
+
"en": "When demonstrating how an invoked resource is declared",
|
|
232
|
+
"zh-Hant": "示範被喚起型資源的宣告形態時",
|
|
233
|
+
},
|
|
234
|
+
"template.plugin_demo.rule_content": {
|
|
235
|
+
"en": "The plugin appends a line to this rule's content during the transform stage.",
|
|
236
|
+
"zh-Hant": "本規則的內容會在 transform 階段被 plugin 追加一行。",
|
|
237
|
+
},
|
|
238
|
+
"template.plugin_demo.skill_content": {
|
|
239
|
+
"en": "The plugin appends a line to this skill's content during the transform stage.",
|
|
240
|
+
"zh-Hant": "本技能的內容會在 transform 階段被 plugin 追加一行。",
|
|
241
|
+
},
|
|
242
|
+
"template.plugin_demo.skill_description": {
|
|
243
|
+
"en": "A demo skill the plugin example builds against",
|
|
244
|
+
"zh-Hant": "plugin 範例用來建置的示範技能",
|
|
245
|
+
},
|
|
246
|
+
"template.plugin_demo.skill_when_to_use": {
|
|
247
|
+
"en": "When checking how the plugin rewrites a skill",
|
|
248
|
+
"zh-Hant": "驗證 plugin 對技能的改寫時",
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
const FALLBACK = 'en';
|
|
252
|
+
const LOCALE_ENV_ORDER = ["PROMPTEX_LOCALE", "LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
|
|
253
|
+
/** POSIX 形態轉 BCP 47 並收斂為目錄認得的標籤;認不出來回 undefined。 */
|
|
254
|
+
export function parseLocale(tag) {
|
|
255
|
+
const normalized = String(tag ?? '').trim().split(/[.@]/)[0].replaceAll('_', '-');
|
|
256
|
+
if (normalized === '')
|
|
257
|
+
return undefined;
|
|
258
|
+
const upper = normalized.toUpperCase();
|
|
259
|
+
if (upper === 'C' || upper === 'POSIX')
|
|
260
|
+
return 'en';
|
|
261
|
+
const [language, ...rest] = normalized.toLowerCase().split('-');
|
|
262
|
+
if (language === 'en')
|
|
263
|
+
return 'en';
|
|
264
|
+
if (language === 'zh' && rest.some((p) => ['hant', 'tw', 'hk', 'mo'].includes(p)))
|
|
265
|
+
return 'zh-Hant';
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
/** 依旗標與環境算出本次的語系。三項都認不出時回退路。 */
|
|
269
|
+
export function resolveLocale(flag) {
|
|
270
|
+
const fromFlag = parseLocale(flag);
|
|
271
|
+
if (fromFlag !== undefined)
|
|
272
|
+
return fromFlag;
|
|
273
|
+
for (const key of LOCALE_ENV_ORDER) {
|
|
274
|
+
const raw = process.env[key];
|
|
275
|
+
if (raw === undefined || raw.trim() === '')
|
|
276
|
+
continue;
|
|
277
|
+
for (const part of raw.split(':')) {
|
|
278
|
+
const found = parseLocale(part);
|
|
279
|
+
if (found !== undefined)
|
|
280
|
+
return found;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return FALLBACK;
|
|
284
|
+
}
|
|
285
|
+
/** 依語系取一則訊息,並以 `%{name}` 形態代入具名參數。 */
|
|
286
|
+
export function t(key, locale, params) {
|
|
287
|
+
const entry = MESSAGES[key];
|
|
288
|
+
if (entry === undefined)
|
|
289
|
+
return key;
|
|
290
|
+
const template = entry[locale] ?? entry[FALLBACK];
|
|
291
|
+
if (template === undefined)
|
|
292
|
+
return key;
|
|
293
|
+
if (params === undefined)
|
|
294
|
+
return template;
|
|
295
|
+
return template.replace(/%\{([^}]+)\}/g, (whole, name) => (name in params ? String(params[name]) : whole));
|
|
296
|
+
}
|
package/package.json
CHANGED
|
@@ -1,35 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-promptex-js",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "promptex
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a promptex TypeScript project, plugin or adapter.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
|
-
"
|
|
7
|
-
"homepage": "https://github.com/promptex-ai/promptex#readme",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/promptex-ai/promptex.git"
|
|
11
|
-
},
|
|
6
|
+
"homepage": "https://github.com/promptex-ai/promptex/tree/main/packages/create-promptex-js#readme",
|
|
12
7
|
"bugs": {
|
|
13
8
|
"url": "https://github.com/promptex-ai/promptex/issues"
|
|
14
9
|
},
|
|
15
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"promptex",
|
|
12
|
+
"create",
|
|
13
|
+
"scaffold",
|
|
14
|
+
"template",
|
|
15
|
+
"prompt",
|
|
16
|
+
"prompt-engineering",
|
|
17
|
+
"ai",
|
|
18
|
+
"llm",
|
|
19
|
+
"agent",
|
|
20
|
+
"codegen"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/promptex-ai/promptex.git",
|
|
25
|
+
"directory": "packages/create-promptex-js"
|
|
26
|
+
},
|
|
16
27
|
"type": "module",
|
|
17
|
-
"main": "./dist/index.js",
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
28
|
"bin": {
|
|
20
29
|
"create-promptex-js": "./bin/create-promptex-js.mjs"
|
|
21
30
|
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin",
|
|
33
|
+
"dist",
|
|
34
|
+
"LICENSE-MIT",
|
|
35
|
+
"LICENSE-APACHE"
|
|
36
|
+
],
|
|
22
37
|
"engines": {
|
|
23
|
-
"node": ">=
|
|
38
|
+
"node": ">=18"
|
|
24
39
|
},
|
|
25
|
-
"files": ["dist", "bin", "README.md", "LICENSE-MIT", "LICENSE-APACHE"],
|
|
26
40
|
"scripts": {
|
|
27
41
|
"build": "tsc -p tsconfig.json",
|
|
28
|
-
"
|
|
29
|
-
"test": "node --test
|
|
42
|
+
"pretest": "npm run build",
|
|
43
|
+
"test": "node --experimental-strip-types --test test/smoke.test.ts && npm run test:types",
|
|
44
|
+
"test:types": "tsc -p tsconfig.typelevel.json",
|
|
45
|
+
"prepack": "npm run build"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@clack/prompts": "^0.10.0",
|
|
49
|
+
"mri": "^1.2.0"
|
|
30
50
|
},
|
|
31
51
|
"devDependencies": {
|
|
32
52
|
"@types/node": "^22.10.0",
|
|
33
|
-
"typescript": "^
|
|
53
|
+
"typescript": "^7.0.2"
|
|
34
54
|
}
|
|
35
55
|
}
|