rayprism 0.1.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 +98 -0
- package/bin/rayprism.js +116 -0
- package/package.json +35 -0
- package/src/commands/init.js +362 -0
- package/src/commands/list.js +31 -0
- package/src/commands/projects.js +33 -0
- package/src/commands/status.js +71 -0
- package/src/commands/unregister.js +15 -0
- package/src/commands/upgrade.js +59 -0
- package/src/utils/constants.js +36 -0
- package/src/utils/logger.js +21 -0
- package/src/utils/registry.js +46 -0
- package/src/utils/template.js +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# RayPrism
|
|
2
|
+
|
|
3
|
+
> 面向 AI Agent 工作流的多分支框架模板——选择分支、一键实例化、AI 即刻开工。
|
|
4
|
+
|
|
5
|
+
## 快速开始
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 方式一:npx(免安装,推荐尝鲜)
|
|
9
|
+
npx rayprism init dev my-app
|
|
10
|
+
|
|
11
|
+
# 方式二:全局安装(长期使用)
|
|
12
|
+
npm install -g rayprism
|
|
13
|
+
rayprism init dev my-app --git
|
|
14
|
+
|
|
15
|
+
# 进入项目,开始工作
|
|
16
|
+
cd ~/Projects/my-app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> 首次运行会自动从 GitHub 下载模板到 `~/.rayprism/framework/`,后续使用缓存。
|
|
20
|
+
|
|
21
|
+
## 核心概念
|
|
22
|
+
|
|
23
|
+
RayPrism 是**框架模板**,不是具体项目。
|
|
24
|
+
|
|
25
|
+
- `branches/` 下的四个分支模板是**只读的类定义**
|
|
26
|
+
- 通过 `rayprism init` 将模板**实例化**为独立的项目空间
|
|
27
|
+
- 框架升级通过 `rayprism upgrade` 一键完成,不影响项目产出
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
RayPrism/(框架,只读) 实例化项目(可写)
|
|
31
|
+
======================== ========================
|
|
32
|
+
branches/ my-app/
|
|
33
|
+
├── pro/ ├── framework/ → branches/dev/ 🔗
|
|
34
|
+
├── content/ rayprism init ├── .agents/skills/ (合并视图)
|
|
35
|
+
├── dev/ ──────────────→ ├── .claude/rules/ (合并视图)
|
|
36
|
+
└── ops/ ├── overrides/ (自定义扩展)
|
|
37
|
+
└── workspace/ (所有产出)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 四个分支
|
|
41
|
+
|
|
42
|
+
| 分支 | 适合场景 | 主力技能 |
|
|
43
|
+
|------|---------|---------|
|
|
44
|
+
| **pro** | 知识整理 / Obsidian 笔记 | obsidian-markdown, obsidian-bases, json-canvas |
|
|
45
|
+
| **content** | 公众号 / 内容创作 | ray-content-wechat-generating, ray-multi-party-mode |
|
|
46
|
+
| **dev** | 工程开发 / 代码 | ray-util-antigravity-bridging, vercel-react-best-practices |
|
|
47
|
+
| **ops** | 系统运维 / 排障 | tavily-search, agent-browser |
|
|
48
|
+
|
|
49
|
+
## 项目管理
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
rayprism list # 查看可用分支
|
|
53
|
+
rayprism projects # 列出所有已创建项目
|
|
54
|
+
rayprism status # 查看当前项目信息(在项目目录内)
|
|
55
|
+
rayprism upgrade # 升级框架到最新版本
|
|
56
|
+
rayprism unregister <name> # 从注册表移除项目
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 安装
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# npx(免安装)
|
|
63
|
+
npx rayprism <command>
|
|
64
|
+
|
|
65
|
+
# 全局安装
|
|
66
|
+
npm install -g rayprism
|
|
67
|
+
rayprism <command>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 框架结构
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
RayPrism/
|
|
74
|
+
├── package.json ← npm 包配置
|
|
75
|
+
├── bin/rayprism.js ← CLI 入口
|
|
76
|
+
├── src/ ← CLI 源码
|
|
77
|
+
│
|
|
78
|
+
├── README.md ← 当前文件
|
|
79
|
+
├── AGENTS.md ← AI Agent 主规则
|
|
80
|
+
├── CLAUDE.md ← Claude Code 规则入口
|
|
81
|
+
├── GEMINI.md ← Gemini CLI 规则入口
|
|
82
|
+
│
|
|
83
|
+
├── setup/ ← 旧版 bash 工具(保留兼容)
|
|
84
|
+
│
|
|
85
|
+
└── branches/ ← 四分支纯净模板(只读)
|
|
86
|
+
├── pro/ ← 知识管理 / Obsidian
|
|
87
|
+
├── content/ ← 内容创作流水线
|
|
88
|
+
├── dev/ ← 工程开发
|
|
89
|
+
└── ops/ ← 系统运维
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 文档
|
|
93
|
+
|
|
94
|
+
- [验证清单](setup/checklist.md) — 初始化后的检查项
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
package/bin/rayprism.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* rayprism — Multi-branch AI Agent Framework CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* rayprism init <branch> <name> [--path /dir] [--git]
|
|
8
|
+
* rayprism list
|
|
9
|
+
* rayprism projects
|
|
10
|
+
* rayprism status
|
|
11
|
+
* rayprism upgrade
|
|
12
|
+
* rayprism unregister <name>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { init } from '../src/commands/init.js';
|
|
16
|
+
import { list } from '../src/commands/list.js';
|
|
17
|
+
import { projects } from '../src/commands/projects.js';
|
|
18
|
+
import { status } from '../src/commands/status.js';
|
|
19
|
+
import { upgrade } from '../src/commands/upgrade.js';
|
|
20
|
+
import { unregister } from '../src/commands/unregister.js';
|
|
21
|
+
import { log } from '../src/utils/logger.js';
|
|
22
|
+
|
|
23
|
+
const HELP = `
|
|
24
|
+
\x1b[1mrayprism — Multi-branch AI Agent Framework\x1b[0m
|
|
25
|
+
|
|
26
|
+
命令:
|
|
27
|
+
rayprism init <branch> <name> [--path /dir] [--git] 初始化新项目
|
|
28
|
+
rayprism list 列出可用分支
|
|
29
|
+
rayprism projects 列出所有已注册项目
|
|
30
|
+
rayprism status 查看当前项目信息
|
|
31
|
+
rayprism upgrade 更新框架符号链接
|
|
32
|
+
rayprism unregister <name> 从注册表移除项目
|
|
33
|
+
|
|
34
|
+
分支: pro | content | dev | ops
|
|
35
|
+
|
|
36
|
+
示例:
|
|
37
|
+
npx rayprism init dev my-app
|
|
38
|
+
npx rayprism init content my-blog --git
|
|
39
|
+
rayprism list
|
|
40
|
+
rayprism projects
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const command = args[0] || 'help';
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
switch (command) {
|
|
49
|
+
case 'init': {
|
|
50
|
+
const branch = args[1];
|
|
51
|
+
const name = args[2];
|
|
52
|
+
if (!branch || !name) {
|
|
53
|
+
log.err('用法: rayprism init <branch> <name> [--path /dir] [--git]');
|
|
54
|
+
}
|
|
55
|
+
const opts = parseInitOpts(args.slice(3));
|
|
56
|
+
await init(branch, name, opts);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case 'list':
|
|
60
|
+
await list();
|
|
61
|
+
break;
|
|
62
|
+
case 'projects':
|
|
63
|
+
await projects();
|
|
64
|
+
break;
|
|
65
|
+
case 'status':
|
|
66
|
+
await status();
|
|
67
|
+
break;
|
|
68
|
+
case 'upgrade':
|
|
69
|
+
await upgrade();
|
|
70
|
+
break;
|
|
71
|
+
case 'unregister': {
|
|
72
|
+
const name = args[1];
|
|
73
|
+
if (!name) log.err('用法: rayprism unregister <name>');
|
|
74
|
+
await unregister(name);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case 'help':
|
|
78
|
+
case '--help':
|
|
79
|
+
case '-h':
|
|
80
|
+
console.log(HELP);
|
|
81
|
+
break;
|
|
82
|
+
case '--version':
|
|
83
|
+
case '-v': {
|
|
84
|
+
const { readFileSync } = await import('node:fs');
|
|
85
|
+
const { fileURLToPath } = await import('node:url');
|
|
86
|
+
const { dirname, join } = await import('node:path');
|
|
87
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
88
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
89
|
+
console.log(pkg.version);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
log.err(`未知命令: '${command}'。运行 rayprism help 查看帮助`);
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
if (e.message?.startsWith('EXIT:')) {
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
console.error(e);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseInitOpts(args) {
|
|
105
|
+
const opts = { path: '', git: false };
|
|
106
|
+
for (let i = 0; i < args.length; i++) {
|
|
107
|
+
if (args[i] === '--path' && args[i + 1]) {
|
|
108
|
+
opts.path = args[++i];
|
|
109
|
+
} else if (args[i] === '--git') {
|
|
110
|
+
opts.git = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return opts;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rayprism",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-branch AI Agent framework — one command to scaffold your AI workspace",
|
|
5
|
+
"bin": {
|
|
6
|
+
"rayprism": "bin/rayprism.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"src/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ai",
|
|
20
|
+
"agent",
|
|
21
|
+
"framework",
|
|
22
|
+
"scaffold",
|
|
23
|
+
"cli",
|
|
24
|
+
"claude",
|
|
25
|
+
"cursor",
|
|
26
|
+
"gemini"
|
|
27
|
+
],
|
|
28
|
+
"author": "961882",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/961882/RayPrism.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/961882/RayPrism"
|
|
35
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rayprism init <branch> <name> [--path /dir] [--git]
|
|
3
|
+
*
|
|
4
|
+
* Full port of rp.sh cmd_init logic to Node.js.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
existsSync, mkdirSync, symlinkSync, writeFileSync,
|
|
9
|
+
readFileSync, readdirSync, lstatSync, rmSync, readlinkSync, statSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { join, basename, resolve } from 'node:path';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { log, C } from '../utils/logger.js';
|
|
14
|
+
import {
|
|
15
|
+
VALID_BRANCHES, BRANCH_META, BRANCHES_DIR, WORKSPACE_DIR_DESC,
|
|
16
|
+
DEFAULT_PROJECTS_DIR,
|
|
17
|
+
} from '../utils/constants.js';
|
|
18
|
+
import { ensureFramework, getTemplateVersion } from '../utils/template.js';
|
|
19
|
+
import { registerProject } from '../utils/registry.js';
|
|
20
|
+
|
|
21
|
+
// ─── Exported helper: link .agents/ .claude/ (merged mode) ──────────
|
|
22
|
+
export function linkHiddenDirs(branch, branchDir, projectDir) {
|
|
23
|
+
// === .agents/skills/ merge ===
|
|
24
|
+
const branchSkills = join(branchDir, '.agents', 'skills');
|
|
25
|
+
if (existsSync(branchSkills)) {
|
|
26
|
+
const targetSkills = join(projectDir, '.agents', 'skills');
|
|
27
|
+
mkdirSync(targetSkills, { recursive: true });
|
|
28
|
+
|
|
29
|
+
// Remove old framework symlinks (preserve local dirs)
|
|
30
|
+
if (existsSync(targetSkills)) {
|
|
31
|
+
for (const item of readdirSync(targetSkills)) {
|
|
32
|
+
const p = join(targetSkills, item);
|
|
33
|
+
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Link framework skills
|
|
38
|
+
for (const skillName of readdirSync(branchSkills)) {
|
|
39
|
+
const skillDir = join(branchSkills, skillName);
|
|
40
|
+
if (!statSync(skillDir).isDirectory()) continue;
|
|
41
|
+
|
|
42
|
+
const overridePath = join(projectDir, 'overrides', 'skills', skillName);
|
|
43
|
+
if (existsSync(overridePath)) {
|
|
44
|
+
symlinkSync(resolve(overridePath), join(targetSkills, skillName));
|
|
45
|
+
log.info(` skills/${skillName} → overrides/ (本地覆盖)`);
|
|
46
|
+
} else {
|
|
47
|
+
symlinkSync(resolve(skillDir), join(targetSkills, skillName));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Link extra override skills
|
|
52
|
+
const overrideSkillsDir = join(projectDir, 'overrides', 'skills');
|
|
53
|
+
if (existsSync(overrideSkillsDir)) {
|
|
54
|
+
for (const skillName of readdirSync(overrideSkillsDir)) {
|
|
55
|
+
const target = join(targetSkills, skillName);
|
|
56
|
+
if (!existsSync(target)) {
|
|
57
|
+
symlinkSync(resolve(join(overrideSkillsDir, skillName)), target);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
log.ok('链接 .agents/skills/(框架 + 覆盖合并)');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// === .claude/rules/ merge ===
|
|
66
|
+
const branchRules = join(branchDir, '.claude', 'rules');
|
|
67
|
+
if (existsSync(branchRules)) {
|
|
68
|
+
const targetRules = join(projectDir, '.claude', 'rules');
|
|
69
|
+
mkdirSync(targetRules, { recursive: true });
|
|
70
|
+
|
|
71
|
+
// Remove old framework symlinks
|
|
72
|
+
if (existsSync(targetRules)) {
|
|
73
|
+
for (const item of readdirSync(targetRules)) {
|
|
74
|
+
const p = join(targetRules, item);
|
|
75
|
+
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Link framework rules
|
|
80
|
+
for (const ruleName of readdirSync(branchRules)) {
|
|
81
|
+
const ruleFile = join(branchRules, ruleName);
|
|
82
|
+
if (!statSync(ruleFile).isFile()) continue;
|
|
83
|
+
|
|
84
|
+
const overridePath = join(projectDir, 'overrides', 'rules', ruleName);
|
|
85
|
+
if (existsSync(overridePath)) {
|
|
86
|
+
symlinkSync(resolve(overridePath), join(targetRules, ruleName));
|
|
87
|
+
log.info(` rules/${ruleName} → overrides/ (本地覆盖)`);
|
|
88
|
+
} else {
|
|
89
|
+
symlinkSync(resolve(ruleFile), join(targetRules, ruleName));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Link extra override rules
|
|
94
|
+
const overrideRulesDir = join(projectDir, 'overrides', 'rules');
|
|
95
|
+
if (existsSync(overrideRulesDir)) {
|
|
96
|
+
for (const ruleName of readdirSync(overrideRulesDir)) {
|
|
97
|
+
const target = join(targetRules, ruleName);
|
|
98
|
+
if (!existsSync(target)) {
|
|
99
|
+
symlinkSync(resolve(join(overrideRulesDir, ruleName)), target);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
log.ok('链接 .claude/rules/(框架 + 覆盖合并)');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Generate AGENTS.md wrapper ─────────────────────────────────────
|
|
109
|
+
function writeAgentsMd(branch, projectName, branchDir, projectDir) {
|
|
110
|
+
const dirs = BRANCH_META[branch].dirs.split(' ');
|
|
111
|
+
const dirsTree = dirs.map(d => `├── ${d}/`).join('\n');
|
|
112
|
+
|
|
113
|
+
let branchAgents = '';
|
|
114
|
+
const branchAgentsPath = join(branchDir, 'AGENTS.md');
|
|
115
|
+
if (existsSync(branchAgentsPath)) {
|
|
116
|
+
branchAgents = readFileSync(branchAgentsPath, 'utf8')
|
|
117
|
+
.split('\n')
|
|
118
|
+
.filter(l => !l.startsWith('# '))
|
|
119
|
+
.slice(0, 60)
|
|
120
|
+
.join('\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const content = `# ${projectName} · AGENTS.md
|
|
124
|
+
|
|
125
|
+
> 本文件由 \`rayprism init\` 自动生成,适用于 Claude Code / Cursor / Gemini CLI 等 AI 工具。
|
|
126
|
+
|
|
127
|
+
## ⚠️ 框架只读声明(最高优先级)
|
|
128
|
+
|
|
129
|
+
\`framework/\` 目录是从 RayPrism \`${branch}\` 分支拉取的只读框架,
|
|
130
|
+
**禁止修改 \`framework/\` 下的任何文件**。框架升级请运行 \`rayprism upgrade\`。
|
|
131
|
+
|
|
132
|
+
## 📂 产出目录约束
|
|
133
|
+
|
|
134
|
+
所有 AI 产出、生成内容、中间文件,**必须写入 \`workspace/\` 目录**,
|
|
135
|
+
不得在项目根目录随意创建文件。
|
|
136
|
+
|
|
137
|
+
\`\`\`
|
|
138
|
+
workspace/
|
|
139
|
+
${dirsTree}
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
## 🔧 自定义扩展
|
|
143
|
+
|
|
144
|
+
项目级规则放在 \`overrides/\` 目录:
|
|
145
|
+
|
|
146
|
+
- \`overrides/rules/*.md\` — 追加行为约束(自动合并到 .claude/rules/)
|
|
147
|
+
- \`overrides/skills/\` — 项目专属 Skills(自动合并到 .agents/skills/)
|
|
148
|
+
- 同名文件/目录会覆盖框架版本(本地优先)
|
|
149
|
+
|
|
150
|
+
## 📋 分支规则
|
|
151
|
+
|
|
152
|
+
本项目使用 **${branch}** 分支规则,详见:
|
|
153
|
+
|
|
154
|
+
\`\`\`
|
|
155
|
+
framework/AGENTS.md
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
${branchAgents}
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
writeFileSync(join(projectDir, 'AGENTS.md'), content);
|
|
162
|
+
log.ok('AGENTS.md 已生成(含只读声明 + 覆盖说明 + 框架规则)');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Main init command ──────────────────────────────────────────────
|
|
166
|
+
export async function init(branch, projectName, opts = {}) {
|
|
167
|
+
// Validate branch
|
|
168
|
+
if (!VALID_BRANCHES.includes(branch)) {
|
|
169
|
+
log.err(`无效分支: '${branch}'。可用: ${VALID_BRANCHES.join(', ')}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Ensure templates are downloaded
|
|
173
|
+
await ensureFramework();
|
|
174
|
+
|
|
175
|
+
const branchDir = join(BRANCHES_DIR, branch);
|
|
176
|
+
if (!existsSync(branchDir)) {
|
|
177
|
+
log.err(`分支目录不存在: ${branchDir}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Determine project path
|
|
181
|
+
const projectPath = opts.path || join(DEFAULT_PROJECTS_DIR, projectName);
|
|
182
|
+
|
|
183
|
+
// Don't overwrite existing
|
|
184
|
+
if (existsSync(projectPath)) {
|
|
185
|
+
log.err(`目录已存在: ${projectPath}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const templateVer = getTemplateVersion(branch);
|
|
189
|
+
|
|
190
|
+
// Print banner
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(C.bold('🚀 RayPrism · 初始化新项目'));
|
|
193
|
+
console.log('══════════════════════════════════');
|
|
194
|
+
console.log(` 分支类型 : ${branch} (${BRANCH_META[branch].desc})`);
|
|
195
|
+
console.log(` 项目名称 : ${projectName}`);
|
|
196
|
+
console.log(` 项目路径 : ${projectPath}`);
|
|
197
|
+
console.log(` 框架来源 : ${branchDir}`);
|
|
198
|
+
console.log(` 模板版本 : v${templateVer}`);
|
|
199
|
+
if (opts.git) console.log(' Git 初始 : ✅');
|
|
200
|
+
console.log('');
|
|
201
|
+
|
|
202
|
+
mkdirSync(projectPath, { recursive: true });
|
|
203
|
+
|
|
204
|
+
// ① framework/ symlink
|
|
205
|
+
symlinkSync(resolve(branchDir), join(projectPath, 'framework'));
|
|
206
|
+
log.ok(`framework/ → ${branchDir}`);
|
|
207
|
+
|
|
208
|
+
// ② overrides/ 目录
|
|
209
|
+
mkdirSync(join(projectPath, 'overrides', 'rules'), { recursive: true });
|
|
210
|
+
mkdirSync(join(projectPath, 'overrides', 'skills'), { recursive: true });
|
|
211
|
+
writeFileSync(join(projectPath, 'overrides', 'README.md'), OVERRIDES_README);
|
|
212
|
+
log.ok('overrides/ 目录结构已创建');
|
|
213
|
+
|
|
214
|
+
// ③ Link hidden dirs (merge mode)
|
|
215
|
+
linkHiddenDirs(branch, branchDir, projectPath);
|
|
216
|
+
|
|
217
|
+
// ④ Link CLAUDE.md / GEMINI.md
|
|
218
|
+
for (const f of ['CLAUDE.md', 'GEMINI.md']) {
|
|
219
|
+
if (existsSync(join(branchDir, f))) {
|
|
220
|
+
symlinkSync(join('framework', f), join(projectPath, f));
|
|
221
|
+
log.ok(`链接 ${f}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ⑤ workspace/ directories
|
|
226
|
+
const dirs = BRANCH_META[branch].dirs.split(' ');
|
|
227
|
+
for (const d of dirs) {
|
|
228
|
+
mkdirSync(join(projectPath, 'workspace', d), { recursive: true });
|
|
229
|
+
}
|
|
230
|
+
log.ok('workspace/ 目录结构已创建');
|
|
231
|
+
|
|
232
|
+
// ⑥ workspace/README.md
|
|
233
|
+
const tableRows = dirs
|
|
234
|
+
.map(d => `| \`${d}/\` | ${WORKSPACE_DIR_DESC[d] || '—'} |`)
|
|
235
|
+
.join('\n');
|
|
236
|
+
const wsReadme = `# Workspace\n\n所有项目产出放在此目录,框架规则在 \`../framework/\`(只读)。\n\n| 目录 | 用途 |\n|------|------|\n${tableRows}\n`;
|
|
237
|
+
writeFileSync(join(projectPath, 'workspace', 'README.md'), wsReadme);
|
|
238
|
+
log.ok('workspace/README.md 已生成');
|
|
239
|
+
|
|
240
|
+
// ⑦ AGENTS.md
|
|
241
|
+
writeAgentsMd(branch, projectName, branchDir, projectPath);
|
|
242
|
+
|
|
243
|
+
// ⑧ .rayprism.json
|
|
244
|
+
const config = {
|
|
245
|
+
name: projectName,
|
|
246
|
+
branch,
|
|
247
|
+
source: branchDir,
|
|
248
|
+
rayprism_home: resolve(join(BRANCHES_DIR, '..')),
|
|
249
|
+
template_version: templateVer,
|
|
250
|
+
created: new Date().toISOString(),
|
|
251
|
+
};
|
|
252
|
+
writeFileSync(join(projectPath, '.rayprism.json'), JSON.stringify(config, null, 2));
|
|
253
|
+
log.ok(`.rayprism.json 元信息已写入(含模板版本 v${templateVer})`);
|
|
254
|
+
|
|
255
|
+
// ⑨ .gitignore
|
|
256
|
+
writeFileSync(join(projectPath, '.gitignore'), GITIGNORE_CONTENT);
|
|
257
|
+
log.ok('.gitignore 已创建');
|
|
258
|
+
|
|
259
|
+
// ⑩ Register
|
|
260
|
+
registerProject({
|
|
261
|
+
name: projectName,
|
|
262
|
+
branch,
|
|
263
|
+
path: projectPath,
|
|
264
|
+
source: branchDir,
|
|
265
|
+
templateVersion: templateVer,
|
|
266
|
+
});
|
|
267
|
+
log.ok('已注册到全局注册表 (~/.rayprism/registry.json)');
|
|
268
|
+
|
|
269
|
+
// ⑪ Post-init hook
|
|
270
|
+
const hookPath = join(branchDir, 'post-init.sh');
|
|
271
|
+
if (existsSync(hookPath)) {
|
|
272
|
+
log.info(`执行 ${branch} post-init hook...`);
|
|
273
|
+
try {
|
|
274
|
+
execSync(`bash "${hookPath}"`, { cwd: projectPath, stdio: 'inherit' });
|
|
275
|
+
log.ok('post-init hook 执行完成');
|
|
276
|
+
} catch {
|
|
277
|
+
log.warn('post-init hook 执行失败,跳过');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ⑫ Git init
|
|
282
|
+
if (opts.git) {
|
|
283
|
+
log.info('初始化 Git 仓库...');
|
|
284
|
+
execSync('git init -q', { cwd: projectPath, stdio: 'pipe' });
|
|
285
|
+
execSync('git add -A', { cwd: projectPath, stdio: 'pipe' });
|
|
286
|
+
execSync(`git commit -q -m "init: rayprism init ${branch} ${projectName} (v${templateVer})"`, {
|
|
287
|
+
cwd: projectPath, stdio: 'pipe',
|
|
288
|
+
});
|
|
289
|
+
log.ok('Git 仓库已初始化并完成首次提交');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Done
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log('══════════════════════════════════');
|
|
295
|
+
log.ok('项目初始化完成!');
|
|
296
|
+
console.log('');
|
|
297
|
+
console.log(` ${C.bold('下一步:')}`);
|
|
298
|
+
console.log(` cd ${projectPath}`);
|
|
299
|
+
console.log(' 用 Cursor / VS Code 打开即可');
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log(` ${C.cyan('产出路径')} → workspace/`);
|
|
302
|
+
console.log(` ${C.yellow('框架路径')} → framework/(只读,请勿修改)`);
|
|
303
|
+
console.log(` ${C.green('自定义扩展')} → overrides/(添加项目专属规则/Skills)`);
|
|
304
|
+
console.log('');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─── Static content ─────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
const OVERRIDES_README = `# overrides/ — 项目级自定义扩展
|
|
310
|
+
|
|
311
|
+
此目录用于添加项目特有的规则和 Skills,不影响框架模板。
|
|
312
|
+
|
|
313
|
+
## 使用方法
|
|
314
|
+
|
|
315
|
+
### 追加行为规则
|
|
316
|
+
|
|
317
|
+
在 \`rules/\` 下新建 \`.md\` 文件,会自动合并到 \`.claude/rules/\`:
|
|
318
|
+
|
|
319
|
+
\`\`\`bash
|
|
320
|
+
cat > overrides/rules/my-project-rule.md << 'EOF'
|
|
321
|
+
---
|
|
322
|
+
description: 项目专属规则
|
|
323
|
+
---
|
|
324
|
+
- 所有报告必须包含数据来源链接
|
|
325
|
+
- 代码注释使用中文
|
|
326
|
+
EOF
|
|
327
|
+
\`\`\`
|
|
328
|
+
|
|
329
|
+
### 追加项目 Skills
|
|
330
|
+
|
|
331
|
+
在 \`skills/\` 下新建 skill 目录,会自动合并到 \`.agents/skills/\`:
|
|
332
|
+
|
|
333
|
+
\`\`\`
|
|
334
|
+
overrides/skills/
|
|
335
|
+
└── my-custom-skill/
|
|
336
|
+
└── SKILL.md
|
|
337
|
+
\`\`\`
|
|
338
|
+
|
|
339
|
+
### 覆盖框架版本
|
|
340
|
+
|
|
341
|
+
如果本地文件与框架同名,本地版本优先(覆盖框架)。
|
|
342
|
+
|
|
343
|
+
### 生效方式
|
|
344
|
+
|
|
345
|
+
运行 \`rayprism upgrade\` 会重新合并框架 + overrides,自动生效。
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
const GITIGNORE_CONTENT = `# 环境与系统
|
|
349
|
+
.env
|
|
350
|
+
.DS_Store
|
|
351
|
+
|
|
352
|
+
# 框架符号链接(不跟踪,由 rayprism upgrade 管理)
|
|
353
|
+
framework
|
|
354
|
+
.agents
|
|
355
|
+
.claude
|
|
356
|
+
CLAUDE.md
|
|
357
|
+
GEMINI.md
|
|
358
|
+
|
|
359
|
+
# 临时产出
|
|
360
|
+
workspace/logs/
|
|
361
|
+
workspace/incidents/
|
|
362
|
+
`;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rayprism list — Show available branches
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { VALID_BRANCHES, BRANCH_META, BRANCHES_DIR } from '../utils/constants.js';
|
|
8
|
+
import { ensureFramework, getTemplateVersion } from '../utils/template.js';
|
|
9
|
+
import { C } from '../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
export async function list() {
|
|
12
|
+
await ensureFramework();
|
|
13
|
+
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(C.bold('📦 RayPrism 可用分支'));
|
|
16
|
+
console.log('══════════════════════════════════');
|
|
17
|
+
|
|
18
|
+
for (const b of VALID_BRANCHES) {
|
|
19
|
+
const branchDir = join(BRANCHES_DIR, b);
|
|
20
|
+
if (existsSync(branchDir)) {
|
|
21
|
+
const ver = getTemplateVersion(b);
|
|
22
|
+
console.log(` ${C.green('●')} ${C.bold(b)} ${C.dim('v' + ver)} ${BRANCH_META[b].desc}`);
|
|
23
|
+
} else {
|
|
24
|
+
console.log(` ${C.yellow('○')} ${b} (分支目录未找到)`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log('用法: rayprism init <branch> <project-name> [--git]');
|
|
30
|
+
console.log('');
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rayprism projects — List all registered projects
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { loadRegistry } from '../utils/registry.js';
|
|
7
|
+
import { C } from '../utils/logger.js';
|
|
8
|
+
|
|
9
|
+
export async function projects() {
|
|
10
|
+
const reg = loadRegistry();
|
|
11
|
+
|
|
12
|
+
if (reg.projects.length === 0) {
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(C.yellow('暂无已注册的项目'));
|
|
15
|
+
console.log('运行 rayprism init <branch> <name> 创建第一个项目');
|
|
16
|
+
console.log('');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(C.bold(`📋 RayPrism 已注册项目 (${reg.projects.length})`));
|
|
22
|
+
console.log('══════════════════════════════════════════════════════');
|
|
23
|
+
|
|
24
|
+
for (const p of reg.projects) {
|
|
25
|
+
const alive = existsSync(p.path) ? C.green('●') : C.red('○');
|
|
26
|
+
const ver = p.template_version || '?';
|
|
27
|
+
const name = p.name.padEnd(20);
|
|
28
|
+
const branch = p.branch.padEnd(8);
|
|
29
|
+
console.log(` ${alive} ${name} ${branch} v${ver.padEnd(6)} ${p.path}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log('');
|
|
33
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rayprism status — Show current project info
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, lstatSync, readdirSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { log, C } from '../utils/logger.js';
|
|
8
|
+
import { getTemplateVersion } from '../utils/template.js';
|
|
9
|
+
|
|
10
|
+
export async function status() {
|
|
11
|
+
const configPath = join(process.cwd(), '.rayprism.json');
|
|
12
|
+
if (!existsSync(configPath)) {
|
|
13
|
+
log.err('当前目录不是 RayPrism 项目(缺少 .rayprism.json)');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
17
|
+
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(C.bold('📋 RayPrism 项目信息'));
|
|
20
|
+
console.log('══════════════════════════════════');
|
|
21
|
+
console.log(` 项目名称 : ${config.name || '?'}`);
|
|
22
|
+
console.log(` 分支类型 : ${config.branch || '?'}`);
|
|
23
|
+
console.log(` 框架来源 : ${config.source || '?'}`);
|
|
24
|
+
console.log(` 模板版本 : ${config.template_version || '?'}`);
|
|
25
|
+
console.log(` 创建时间 : ${config.created || '?'}`);
|
|
26
|
+
|
|
27
|
+
// Version comparison
|
|
28
|
+
const currentVer = config.template_version || '?';
|
|
29
|
+
let latestVer = '?';
|
|
30
|
+
if (config.source && existsSync(join(config.source, 'VERSION'))) {
|
|
31
|
+
latestVer = readFileSync(join(config.source, 'VERSION'), 'utf8').trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('');
|
|
35
|
+
if (currentVer === latestVer) {
|
|
36
|
+
console.log(C.green(` 模板版本 : v${currentVer} ✅ 已是最新`));
|
|
37
|
+
} else {
|
|
38
|
+
console.log(C.yellow(` 模板版本 : v${currentVer} → v${latestVer} (可运行 rayprism upgrade)`));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Framework symlink
|
|
42
|
+
console.log('');
|
|
43
|
+
const fwPath = join(process.cwd(), 'framework');
|
|
44
|
+
if (existsSync(fwPath) && lstatSync(fwPath).isSymbolicLink()) {
|
|
45
|
+
const { readlinkSync } = await import('node:fs');
|
|
46
|
+
const target = readlinkSync(fwPath);
|
|
47
|
+
console.log(` ${C.green('framework/')} → ${target}`);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(` ${C.yellow('framework/')} (非符号链接,升级无效)`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Overrides status
|
|
53
|
+
const overridesPath = join(process.cwd(), 'overrides');
|
|
54
|
+
if (existsSync(overridesPath)) {
|
|
55
|
+
const rulesDir = join(overridesPath, 'rules');
|
|
56
|
+
const skillsDir = join(overridesPath, 'skills');
|
|
57
|
+
let ruleCount = 0, skillCount = 0;
|
|
58
|
+
try {
|
|
59
|
+
ruleCount = readdirSync(rulesDir).filter(f => f.endsWith('.md')).length;
|
|
60
|
+
} catch {}
|
|
61
|
+
try {
|
|
62
|
+
skillCount = readdirSync(skillsDir).filter((_, i, arr) => {
|
|
63
|
+
// count directories only
|
|
64
|
+
return true;
|
|
65
|
+
}).length;
|
|
66
|
+
} catch {}
|
|
67
|
+
console.log(` ${C.cyan('overrides/')} 规则: ${ruleCount}, Skills: ${skillCount}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('');
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rayprism unregister <name> — Remove project from registry
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { unregisterProject } from '../utils/registry.js';
|
|
6
|
+
import { log } from '../utils/logger.js';
|
|
7
|
+
|
|
8
|
+
export async function unregister(name) {
|
|
9
|
+
const removed = unregisterProject(name);
|
|
10
|
+
if (removed) {
|
|
11
|
+
log.ok(`已从注册表移除: ${name}`);
|
|
12
|
+
} else {
|
|
13
|
+
log.warn(`注册表中未找到: ${name}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rayprism upgrade — Update framework symlinks to latest
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, symlinkSync, readdirSync, lstatSync, readlinkSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { join, basename } from 'node:path';
|
|
7
|
+
import { log, C } from '../utils/logger.js';
|
|
8
|
+
import { ensureFramework, getTemplateVersion } from '../utils/template.js';
|
|
9
|
+
import { BRANCHES_DIR } from '../utils/constants.js';
|
|
10
|
+
import { registerProject } from '../utils/registry.js';
|
|
11
|
+
import { linkHiddenDirs } from './init.js';
|
|
12
|
+
|
|
13
|
+
export async function upgrade() {
|
|
14
|
+
const configPath = join(process.cwd(), '.rayprism.json');
|
|
15
|
+
if (!existsSync(configPath)) {
|
|
16
|
+
log.err('当前目录不是 RayPrism 项目(缺少 .rayprism.json)');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Force re-download templates
|
|
20
|
+
await ensureFramework({ force: true });
|
|
21
|
+
|
|
22
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
23
|
+
const branch = config.branch;
|
|
24
|
+
const branchDir = join(BRANCHES_DIR, branch);
|
|
25
|
+
const currentVer = config.template_version || '?';
|
|
26
|
+
const latestVer = getTemplateVersion(branch);
|
|
27
|
+
|
|
28
|
+
if (currentVer === latestVer) {
|
|
29
|
+
log.info(`模板已是最新版本: v${currentVer}`);
|
|
30
|
+
} else {
|
|
31
|
+
log.info(`版本变更: v${currentVer} → v${latestVer}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Re-link framework/
|
|
35
|
+
log.info(`重新链接 framework → ${branchDir}`);
|
|
36
|
+
const fwPath = join(process.cwd(), 'framework');
|
|
37
|
+
if (existsSync(fwPath)) rmSync(fwPath, { force: true });
|
|
38
|
+
symlinkSync(branchDir, fwPath);
|
|
39
|
+
|
|
40
|
+
// Re-link hidden dirs
|
|
41
|
+
linkHiddenDirs(branch, branchDir, process.cwd());
|
|
42
|
+
|
|
43
|
+
// Update .rayprism.json
|
|
44
|
+
config.template_version = latestVer;
|
|
45
|
+
config.source = branchDir;
|
|
46
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
47
|
+
log.ok(`模板版本已更新为 v${latestVer}`);
|
|
48
|
+
|
|
49
|
+
// Update registry
|
|
50
|
+
registerProject({
|
|
51
|
+
name: config.name,
|
|
52
|
+
branch,
|
|
53
|
+
path: process.cwd(),
|
|
54
|
+
source: branchDir,
|
|
55
|
+
templateVersion: latestVer,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
log.ok('框架已更新至最新版本');
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch metadata and path constants
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
// ─── Paths ──────────────────────────────────────────────────────────
|
|
9
|
+
export const RAYPRISM_CACHE = join(homedir(), '.rayprism');
|
|
10
|
+
export const FRAMEWORK_DIR = join(RAYPRISM_CACHE, 'framework');
|
|
11
|
+
export const BRANCHES_DIR = join(FRAMEWORK_DIR, 'branches');
|
|
12
|
+
export const REGISTRY_FILE = join(RAYPRISM_CACHE, 'registry.json');
|
|
13
|
+
export const DEFAULT_PROJECTS_DIR = join(homedir(), 'Projects');
|
|
14
|
+
|
|
15
|
+
export const GITHUB_REPO = '961882/RayPrism';
|
|
16
|
+
export const GITHUB_TARBALL = `https://github.com/${GITHUB_REPO}/archive/refs/heads/main.tar.gz`;
|
|
17
|
+
|
|
18
|
+
// ─── Branch definitions ─────────────────────────────────────────────
|
|
19
|
+
export const VALID_BRANCHES = ['pro', 'content', 'dev', 'ops'];
|
|
20
|
+
|
|
21
|
+
export const BRANCH_META = {
|
|
22
|
+
pro: { desc: '专业文档 / 策略规划 / 分析报告', dirs: 'reports strategy analysis drafts references' },
|
|
23
|
+
content: { desc: '内容创作 / 公众号 / 社媒运营', dirs: 'drafts published assets scheduled archive' },
|
|
24
|
+
dev: { desc: '软件开发 / 代码 / 架构设计', dirs: 'src docs tests artifacts experiments' },
|
|
25
|
+
ops: { desc: '运维 / 自动化 / 系统管理', dirs: 'scripts configs runbooks logs incidents' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const WORKSPACE_DIR_DESC = {
|
|
29
|
+
reports: '分析报告', strategy: '策略文档', analysis: '分析草稿',
|
|
30
|
+
drafts: '草稿、初稿', references: '参考资料', published: '已发布内容',
|
|
31
|
+
assets: '素材、图片', scheduled: '待发内容', archive: '历史归档',
|
|
32
|
+
src: '源代码', docs: '文档', tests: '测试',
|
|
33
|
+
artifacts: '构建产物', experiments: '实验代码',
|
|
34
|
+
scripts: '自动化脚本', configs: '配置文件', runbooks: '操作手册',
|
|
35
|
+
logs: '日志记录', incidents: '故障记录', output: '最终产出',
|
|
36
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colored terminal logger — zero dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const C = {
|
|
6
|
+
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
7
|
+
yellow: s => `\x1b[33m${s}\x1b[0m`,
|
|
8
|
+
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
9
|
+
red: s => `\x1b[31m${s}\x1b[0m`,
|
|
10
|
+
bold: s => `\x1b[1m${s}\x1b[0m`,
|
|
11
|
+
dim: s => `\x1b[2m${s}\x1b[0m`,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const log = {
|
|
15
|
+
ok: msg => console.log(C.green(`✅ ${msg}`)),
|
|
16
|
+
info: msg => console.log(C.cyan(`ℹ️ ${msg}`)),
|
|
17
|
+
warn: msg => console.log(C.yellow(`⚠️ ${msg}`)),
|
|
18
|
+
err: msg => { console.error(C.red(`❌ ${msg}`)); throw new Error('EXIT:' + msg); },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { C };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project registry — ~/.rayprism/registry.json
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
6
|
+
import { dirname } from 'node:path';
|
|
7
|
+
import { REGISTRY_FILE, RAYPRISM_CACHE } from './constants.js';
|
|
8
|
+
|
|
9
|
+
function ensure() {
|
|
10
|
+
mkdirSync(RAYPRISM_CACHE, { recursive: true });
|
|
11
|
+
if (!existsSync(REGISTRY_FILE)) {
|
|
12
|
+
writeFileSync(REGISTRY_FILE, JSON.stringify({ projects: [] }, null, 2));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadRegistry() {
|
|
17
|
+
ensure();
|
|
18
|
+
return JSON.parse(readFileSync(REGISTRY_FILE, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function saveRegistry(reg) {
|
|
22
|
+
ensure();
|
|
23
|
+
writeFileSync(REGISTRY_FILE, JSON.stringify(reg, null, 2));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function registerProject({ name, branch, path, source, templateVersion }) {
|
|
27
|
+
const reg = loadRegistry();
|
|
28
|
+
reg.projects = reg.projects.filter(p => p.name !== name);
|
|
29
|
+
reg.projects.push({
|
|
30
|
+
name,
|
|
31
|
+
branch,
|
|
32
|
+
path,
|
|
33
|
+
source,
|
|
34
|
+
template_version: templateVersion,
|
|
35
|
+
created: new Date().toISOString(),
|
|
36
|
+
});
|
|
37
|
+
saveRegistry(reg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function unregisterProject(name) {
|
|
41
|
+
const reg = loadRegistry();
|
|
42
|
+
const before = reg.projects.length;
|
|
43
|
+
reg.projects = reg.projects.filter(p => p.name !== name);
|
|
44
|
+
saveRegistry(reg);
|
|
45
|
+
return reg.projects.length < before;
|
|
46
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template download & cache management
|
|
3
|
+
*
|
|
4
|
+
* Downloads the RayPrism repo tarball from GitHub and extracts it to
|
|
5
|
+
* ~/.rayprism/framework/. Supports forced refresh for upgrade.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, mkdirSync, rmSync, createWriteStream, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { pipeline } from 'node:stream/promises';
|
|
11
|
+
import { createGunzip } from 'node:zlib';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { FRAMEWORK_DIR, BRANCHES_DIR, GITHUB_TARBALL, RAYPRISM_CACHE } from './constants.js';
|
|
14
|
+
import { log } from './logger.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ensure framework templates are cached locally.
|
|
18
|
+
* @param {object} opts
|
|
19
|
+
* @param {boolean} opts.force - Force re-download even if cached
|
|
20
|
+
* @returns {string} Path to BRANCHES_DIR
|
|
21
|
+
*/
|
|
22
|
+
export async function ensureFramework({ force = false } = {}) {
|
|
23
|
+
if (!force && existsSync(BRANCHES_DIR)) {
|
|
24
|
+
return BRANCHES_DIR;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log.info('正在从 GitHub 下载 RayPrism 模板...');
|
|
28
|
+
mkdirSync(RAYPRISM_CACHE, { recursive: true });
|
|
29
|
+
|
|
30
|
+
const tarPath = join(RAYPRISM_CACHE, 'main.tar.gz');
|
|
31
|
+
|
|
32
|
+
// Download tarball
|
|
33
|
+
const res = await fetch(GITHUB_TARBALL, { redirect: 'follow' });
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
log.err(`下载失败: HTTP ${res.status} — ${GITHUB_TARBALL}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Save to file
|
|
39
|
+
const fileStream = createWriteStream(tarPath);
|
|
40
|
+
await pipeline(res.body, fileStream);
|
|
41
|
+
|
|
42
|
+
// Remove old framework dir
|
|
43
|
+
if (existsSync(FRAMEWORK_DIR)) {
|
|
44
|
+
rmSync(FRAMEWORK_DIR, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Extract using system tar (works on macOS/Linux, simpler than node tar)
|
|
48
|
+
mkdirSync(FRAMEWORK_DIR, { recursive: true });
|
|
49
|
+
execSync(`tar -xzf "${tarPath}" --strip-components=1 -C "${FRAMEWORK_DIR}"`, {
|
|
50
|
+
stdio: 'pipe',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Clean up tarball
|
|
54
|
+
rmSync(tarPath, { force: true });
|
|
55
|
+
|
|
56
|
+
// Write download timestamp
|
|
57
|
+
const metaPath = join(RAYPRISM_CACHE, 'meta.json');
|
|
58
|
+
const meta = {
|
|
59
|
+
downloaded_at: new Date().toISOString(),
|
|
60
|
+
source: GITHUB_TARBALL,
|
|
61
|
+
};
|
|
62
|
+
const { writeFileSync } = await import('node:fs');
|
|
63
|
+
writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
64
|
+
|
|
65
|
+
log.ok('模板下载完成 → ~/.rayprism/framework/');
|
|
66
|
+
return BRANCHES_DIR;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the template version for a branch.
|
|
71
|
+
*/
|
|
72
|
+
export function getTemplateVersion(branch) {
|
|
73
|
+
const versionFile = join(BRANCHES_DIR, branch, 'VERSION');
|
|
74
|
+
if (existsSync(versionFile)) {
|
|
75
|
+
return readFileSync(versionFile, 'utf8').trim();
|
|
76
|
+
}
|
|
77
|
+
return '0.0.0';
|
|
78
|
+
}
|