@jspg-ai/coding-bb 0.0.1 → 0.0.2-beta.23
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/cbb/bin/cbb.js +6 -0
- package/cbb/bin/cbbm.js +6 -0
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +103 -103
- package/cbb/dev-standards/rules/cbb-priority.md +55 -55
- package/cbb/lib/install/claude-code.js +2 -0
- package/cbb/lib/install/init.js +496 -290
- package/cbb/lib/install/opencode.js +31 -0
- package/cbb/lib/install/qoder.js +2 -0
- package/cbb/lib/install/workspaces.js +53 -13
- package/cbb/lib/openspec/index.js +3 -3
- package/cbb/lib/superpowers/index.js +1 -1
- package/cbb/lib/utils/check-update.js +16 -14
- package/cbb/lib/utils/gitignore.js +2 -1
- package/cbb/lib/utils/output.js +92 -4
- package/cbb/lib/utils/version.js +2 -31
- package/cbb/lib/wiki/cli.js +25 -25
- package/cbb/lib/wiki/index.js +16 -16
- package/cbb/tools/cbb-design-to-wiki/SKILL.md +14 -14
- package/cbb/tools/cbb-wiki-ops/SKILL.md +22 -22
- package/cbb/worktrees/_shared/scripts/find-target-worktree.js +2 -2
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/_shared/scripts/push-core.js +18 -0
- package/cbb/worktrees/commands/{close.md → worktree-close.md} +8 -8
- package/cbb/worktrees/commands/{init.md → worktree-init.md} +9 -9
- package/cbb/worktrees/commands/{push.md → worktree-push.md} +3 -3
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/SKILL.md +20 -20
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/check-env.js +4 -4
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/check-unarchived.js +1 -1
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/delete-branches.js +3 -3
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/discover-apps.js +6 -6
- package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-close}/scripts/find-target-worktree.js +2 -2
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-close}/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/remove-worktrees.js +4 -4
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/safety-check.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/SKILL.md +81 -107
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/auto-open.js +1 -1
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-env-deep.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-env.js +6 -6
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-repos.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/create-branches.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/create-worktrees.js +8 -7
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-init}/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/install-ai.js +33 -33
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/parse-config.js +7 -7
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/push-branches.js +9 -7
- package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-init}/scripts/push-core.js +18 -0
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/sync-repos.js +10 -12
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/update-gitignore.js +19 -6
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/SKILL.md +16 -16
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/commit-worktrees.js +2 -2
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/find-target-worktree.js +2 -2
- package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-push}/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/push-core.js +18 -0
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/push-worktrees.js +7 -5
- package/config/workspace-config.sample.json +18 -0
- package/config/workspaces.json +11 -6
- package/openspec/.version +6 -6
- package/openspec/commands/apply.md +7 -7
- package/openspec/commands/archive.md +7 -7
- package/openspec/commands/bulk-archive.md +7 -7
- package/openspec/commands/continue.md +7 -7
- package/openspec/commands/explore.md +7 -7
- package/openspec/commands/ff.md +7 -7
- package/openspec/commands/new.md +7 -7
- package/openspec/commands/onboard.md +7 -7
- package/openspec/commands/propose.md +7 -7
- package/openspec/commands/sync.md +7 -7
- package/openspec/commands/update.md +7 -7
- package/openspec/commands/verify.md +7 -7
- package/openspec/skills/openspec-apply-change/SKILL.md +187 -187
- package/openspec/skills/openspec-archive-change/SKILL.md +181 -181
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -338
- package/openspec/skills/openspec-continue-change/SKILL.md +117 -117
- package/openspec/skills/openspec-explore/SKILL.md +342 -342
- package/openspec/skills/openspec-ff-change/SKILL.md +116 -116
- package/openspec/skills/openspec-new-change/SKILL.md +76 -76
- package/openspec/skills/openspec-onboard/SKILL.md +560 -560
- package/openspec/skills/openspec-propose/SKILL.md +162 -162
- package/openspec/skills/openspec-sync-specs/SKILL.md +261 -261
- package/openspec/skills/openspec-update-change/SKILL.md +90 -90
- package/openspec/skills/openspec-verify-change/SKILL.md +174 -174
- package/package.json +4 -3
- package/superpowers/.version +6 -6
- package/superpowers/skills/brainstorming/SKILL.md +250 -250
- package/superpowers/skills/brainstorming/scripts/helper.js +167 -167
- package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -209
- package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -120
- package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -49
- package/superpowers/skills/brainstorming/visual-companion.md +299 -299
- package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -167
- package/superpowers/skills/executing-plans/SKILL.md +64 -64
- package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -225
- package/superpowers/skills/receiving-code-review/SKILL.md +205 -205
- package/superpowers/skills/requesting-code-review/SKILL.md +95 -95
- package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -181
- package/superpowers/skills/subagent-driven-development/SKILL.md +568 -568
- package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -154
- package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -115
- package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -207
- package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/superpowers/skills/systematic-debugging/SKILL.md +283 -283
- package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -72
- package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/superpowers/skills/systematic-debugging/test-academic.md +14 -14
- package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/superpowers/skills/test-driven-development/SKILL.md +320 -320
- package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -198
- package/superpowers/skills/using-git-worktrees/SKILL.md +167 -167
- package/superpowers/skills/using-superpowers/SKILL.md +63 -63
- package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -23
- package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -108
- package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -63
- package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -56
- package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -16
- package/superpowers/skills/verification-before-completion/SKILL.md +120 -120
- package/superpowers/skills/writing-plans/SKILL.md +171 -171
- package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -49
- package/superpowers/skills/writing-skills/SKILL.md +679 -679
- package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -1150
- package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/superpowers/skills/writing-skills/persuasion-principles.md +187 -187
- package/superpowers/skills/writing-skills/render-graphs.js +169 -169
- package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -384
- package/cbb/worktrees/commands/extend.md +0 -65
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +0 -96
- package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +0 -390
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +0 -95
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +0 -98
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +0 -135
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +0 -150
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +0 -88
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +0 -93
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +0 -67
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +0 -93
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +0 -136
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +0 -23
- package/config/config.sample.json +0 -16
- /package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/silence-popup.js +0 -0
- /package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-init}/scripts/silence-popup.js +0 -0
- /package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-push}/scripts/silence-popup.js +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opencode 适配器
|
|
3
|
+
*
|
|
4
|
+
* 将编码规范分发到 .opencode/ 目录(opencode 项目级配置域)。
|
|
5
|
+
*
|
|
6
|
+
* 与 Claude Code / Qoder 的映射差异:
|
|
7
|
+
* - rules:opencode 无原生 rules 目录,部署到 .opencode/rules/ 并由安装流程
|
|
8
|
+
* 将 glob 注册进项目根 opencode.json 的 instructions 字段(官方支持的加载方式)
|
|
9
|
+
* - skills:.opencode/skills/<name>/SKILL.md,结构与 Claude 同构
|
|
10
|
+
* - commands:命令名 = 文件名(嵌套目录无命名空间,且 init.md 等会撞内置 /init),
|
|
11
|
+
* 因此扁平化为 opsx-<name>.md(/opsx-propose);worktree 管理命令同理扁平化为
|
|
12
|
+
* cbb-<name>.md(/cbb-worktree-init),部署副本中的 /opsx: 与 /cbb: 引用同步改写
|
|
13
|
+
* - hooks:opencode 无 UserPromptSubmit 等价事件,版本检查 hook 不安装(settingsFile 留空即跳过)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
name: 'opencode',
|
|
18
|
+
displayName: 'opencode',
|
|
19
|
+
rulesDir: '.opencode/rules',
|
|
20
|
+
skillsDir: '.opencode/skills',
|
|
21
|
+
commandsDir: '.opencode/commands',
|
|
22
|
+
// 部署时给命令文件名加此前缀(规避内置命令名冲突)
|
|
23
|
+
commandPrefix: 'opsx-',
|
|
24
|
+
// worktree 管理命令(非 openspec 上游)落在同一扁平目录,用 cbb- 前缀区分
|
|
25
|
+
worktreeCommandsDir: '.opencode/commands',
|
|
26
|
+
worktreeCommandPrefix: 'cbb-',
|
|
27
|
+
// 部署副本中将该引用串改写为 opencode 的命令风格(配合 commandPrefix)
|
|
28
|
+
contentRewrites: [['/opsx:', '/opsx-'], ['/cbb:', '/cbb-']],
|
|
29
|
+
// rules 加载登记:注入项目根 opencode.json 的 instructions(glob,相对项目根)
|
|
30
|
+
instructionsPattern: '.opencode/rules/*.md',
|
|
31
|
+
};
|
package/cbb/lib/install/qoder.js
CHANGED
|
@@ -13,6 +13,8 @@ module.exports = {
|
|
|
13
13
|
rulesDir: '.qoder/rules',
|
|
14
14
|
skillsDir: '.qoder/skills',
|
|
15
15
|
commandsDir: '.qoder/commands/opsx',
|
|
16
|
+
// worktree 管理命令(非 openspec 上游):独立命名空间 /cbb:worktree-*
|
|
17
|
+
worktreeCommandsDir: '.qoder/commands/cbb',
|
|
16
18
|
// 用户级 settings,写入全局生效,不污染项目目录
|
|
17
19
|
settingsFile: path.join(os.homedir(), '.qoder', 'settings.json'),
|
|
18
20
|
settingsFileIsUserLevel: true,
|
|
@@ -15,7 +15,7 @@ const path = require('path');
|
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
16
|
|
|
17
17
|
const DEFAULT_CONFIG = 'config/workspaces.json';
|
|
18
|
-
const SAMPLE_CONFIG = 'config/config.sample.json';
|
|
18
|
+
const SAMPLE_CONFIG = 'config/workspace-config.sample.json';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* 归一化 git remote URL:提取 "namespace/repo" 形式(去掉 .git 后缀)。
|
|
@@ -102,39 +102,40 @@ function loadWorkspaces(configPath = DEFAULT_CONFIG) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* 校验业务工作空间
|
|
105
|
+
* 校验业务工作空间 workspace-config.json 的格式是否合法。
|
|
106
106
|
*
|
|
107
107
|
* 校验项:
|
|
108
108
|
* - 文件存在且为合法 JSON
|
|
109
109
|
* - 含 apps 数组且非空
|
|
110
110
|
* - 每个 app 对象必须含 name / repo / side 三个字段
|
|
111
111
|
* - name、repo 为非空字符串;side 必须为 'front' 或 'back'
|
|
112
|
+
* - desc 为可选字段;一旦出现必须是非空字符串(应用职责描述)
|
|
112
113
|
*
|
|
113
|
-
* @param {string} configPath
|
|
114
|
+
* @param {string} configPath workspace-config.json 的绝对路径
|
|
114
115
|
* @returns {{ valid: boolean, message?: string, config?: object }}
|
|
115
116
|
*/
|
|
116
117
|
function validateWorkspaceConfig(configPath) {
|
|
117
118
|
if (!fs.existsSync(configPath)) {
|
|
118
|
-
return { valid: false, message: `
|
|
119
|
+
return { valid: false, message: `workspace-config.json 不存在:${configPath}` };
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
let raw;
|
|
122
123
|
try {
|
|
123
124
|
raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
124
125
|
} catch (e) {
|
|
125
|
-
return { valid: false, message: `
|
|
126
|
+
return { valid: false, message: `workspace-config.json 不是合法 JSON:${e.message}` };
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
if (!raw || typeof raw !== 'object') {
|
|
129
|
-
return { valid: false, message: '
|
|
130
|
+
return { valid: false, message: 'workspace-config.json 内容应为 JSON 对象' };
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
if (!Array.isArray(raw.apps)) {
|
|
133
|
-
return { valid: false, message: '
|
|
134
|
+
return { valid: false, message: 'workspace-config.json 缺少 apps 数组' };
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
if (raw.apps.length === 0) {
|
|
137
|
-
return { valid: false, message: '
|
|
138
|
+
return { valid: false, message: 'workspace-config.json 中 apps 数组为空,请至少配置一个关联应用' };
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
const VALID_SIDES = ['front', 'back'];
|
|
@@ -143,19 +144,22 @@ function validateWorkspaceConfig(configPath) {
|
|
|
143
144
|
const label = `第 ${i + 1} 项`;
|
|
144
145
|
|
|
145
146
|
if (!app || typeof app !== 'object') {
|
|
146
|
-
return { valid: false, message: `
|
|
147
|
+
return { valid: false, message: `workspace-config.json ${label}不是对象` };
|
|
147
148
|
}
|
|
148
149
|
if (typeof app.name !== 'string' || !app.name.trim()) {
|
|
149
|
-
return { valid: false, message: `
|
|
150
|
+
return { valid: false, message: `workspace-config.json ${label}缺少 name(应用名)` };
|
|
150
151
|
}
|
|
151
152
|
if (typeof app.repo !== 'string' || !app.repo.trim()) {
|
|
152
|
-
return { valid: false, message: `
|
|
153
|
+
return { valid: false, message: `workspace-config.json "${app.name}" 缺少 repo(Git 仓库地址)` };
|
|
153
154
|
}
|
|
154
155
|
if (typeof app.side !== 'string' || !app.side.trim()) {
|
|
155
|
-
return { valid: false, message: `
|
|
156
|
+
return { valid: false, message: `workspace-config.json "${app.name}" 缺少 side(仓库类型,可选值:front / back)` };
|
|
156
157
|
}
|
|
157
158
|
if (!VALID_SIDES.includes(app.side.trim())) {
|
|
158
|
-
return { valid: false, message: `
|
|
159
|
+
return { valid: false, message: `workspace-config.json "${app.name}" 的 side 字段值无效:"${app.side}"(可选值:front / back)` };
|
|
160
|
+
}
|
|
161
|
+
if (app.desc !== undefined && (typeof app.desc !== 'string' || !app.desc.trim())) {
|
|
162
|
+
return { valid: false, message: `workspace-config.json "${app.name}" 的 desc 字段应为非空字符串(该应用的职责描述),不需要可整条删除` };
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
|
|
@@ -219,6 +223,39 @@ function extractRepoBasename(url) {
|
|
|
219
223
|
return parts.length > 0 ? parts[parts.length - 1] : '';
|
|
220
224
|
}
|
|
221
225
|
|
|
226
|
+
/**
|
|
227
|
+
* 判断 repo 是否为样例占位地址(RFC 2606 保留域名)。
|
|
228
|
+
* 样例 workspace-config.json 的 repo 用 example.com 等占位,clone 必然失败;
|
|
229
|
+
* setup 据此逐条跳过空跑并给出明确编辑提示。
|
|
230
|
+
* 支持 SSH(git@host:path)与 URL(ssh://、https://、git://)两种形式的 host 提取。
|
|
231
|
+
*
|
|
232
|
+
* 注意:不含 `.localhost`——本地 Git 服务(`git@localhost:<ns>/<repo>.git`)是真实可用地址,
|
|
233
|
+
* 误判会导致正常应用的代码被跳过。
|
|
234
|
+
*/
|
|
235
|
+
const SAMPLE_HOST_RE = /(?:^|\.)(?:example\.(?:com|org|net)|test|invalid)$/i;
|
|
236
|
+
|
|
237
|
+
function getRepoHost(repo) {
|
|
238
|
+
if (typeof repo !== 'string') return '';
|
|
239
|
+
let s = repo.trim().replace(/^[a-z]+:\/\//i, '');
|
|
240
|
+
const at = s.lastIndexOf('@');
|
|
241
|
+
if (at >= 0) s = s.slice(at + 1);
|
|
242
|
+
const m = s.match(/^[^:/]+/);
|
|
243
|
+
return m ? m[0].toLowerCase() : '';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function isSampleRepo(repo) {
|
|
247
|
+
const host = getRepoHost(repo);
|
|
248
|
+
return !!host && SAMPLE_HOST_RE.test(host);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** 从 apps 数组中挑出占位条目,返回 [{ name, repo }](非数组输入返回 []) */
|
|
252
|
+
function findSampleApps(apps) {
|
|
253
|
+
if (!Array.isArray(apps)) return [];
|
|
254
|
+
return apps
|
|
255
|
+
.filter(a => a && typeof a === 'object' && isSampleRepo(a.repo))
|
|
256
|
+
.map(a => ({ name: a.name, repo: a.repo }));
|
|
257
|
+
}
|
|
258
|
+
|
|
222
259
|
module.exports = {
|
|
223
260
|
DEFAULT_CONFIG,
|
|
224
261
|
SAMPLE_CONFIG,
|
|
@@ -229,4 +266,7 @@ module.exports = {
|
|
|
229
266
|
readGitRemotes,
|
|
230
267
|
detectRepo,
|
|
231
268
|
extractRepoBasename,
|
|
269
|
+
getRepoHost,
|
|
270
|
+
isSampleRepo,
|
|
271
|
+
findSampleApps,
|
|
232
272
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 负责从 GitHub (Fission-AI/OpenSpec) 拉取官方 workflow 模板,
|
|
7
7
|
* 渲染并部署到本项目的 openspec/{commands,skills} 目录。
|
|
8
|
-
* 提供 `
|
|
8
|
+
* 提供 `cbbm update --upstream-openspec` 选项。
|
|
9
9
|
*
|
|
10
10
|
* 升级范围严格限定:只有 WORKFLOWS 白名单中的 commands/*.md 和
|
|
11
11
|
* skills/<workflow>/SKILL.md 会被覆盖写入。config/openspec/(schemas + config.yaml)、
|
|
@@ -75,9 +75,9 @@ function assertSafeDestination(destPath, op) {
|
|
|
75
75
|
for (const prefix of PROTECTED_PATH_PREFIXES) {
|
|
76
76
|
if (abs === prefix || abs.startsWith(prefix + path.sep)) {
|
|
77
77
|
throw new Error(
|
|
78
|
-
`[
|
|
78
|
+
`[cbbm update --upstream-openspec] ${op} 试图写入受保护路径: ${abs}\n` +
|
|
79
79
|
`命中前缀: ${prefix}\n` +
|
|
80
|
-
`该路径属于项目自有扩展位,绝对禁止被
|
|
80
|
+
`该路径属于项目自有扩展位,绝对禁止被 cbbm update --upstream-openspec 覆盖。\n` +
|
|
81
81
|
`详见 AGENTS.md「原生内容保护与扩展位」一节。`
|
|
82
82
|
);
|
|
83
83
|
}
|
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
13
14
|
const path = require('path');
|
|
14
15
|
|
|
15
16
|
// 共享版本工具(readInstalledVersion / fetchLatest / compareVersions / 常量)
|
|
16
17
|
const versionUtil = require('./version');
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// 缓存提升到用户级:registry 最新版本号是全局属性(npm 全局包只有一个版本),
|
|
20
|
+
// 团队多 workspace / 多 worktree 沙箱共享一份,每日真实请求一次;
|
|
21
|
+
// 「本项目是否落后」仍按项目清单版本在本地判定。
|
|
22
|
+
const CACHE_FILE = path.join(os.homedir(), '.cbb', '.update-check-cache.json');
|
|
19
23
|
const CACHE_TTL = 24 * 60 * 60 * 1000;
|
|
20
24
|
|
|
21
25
|
function getProjectDir() {
|
|
@@ -48,20 +52,18 @@ function readInstalledVersion(projectDir, adapterName) {
|
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
function readCache(
|
|
52
|
-
|
|
53
|
-
if (!fs.existsSync(cachePath)) return null;
|
|
55
|
+
function readCache() {
|
|
56
|
+
if (!fs.existsSync(CACHE_FILE)) return null;
|
|
54
57
|
try {
|
|
55
|
-
return JSON.parse(fs.readFileSync(
|
|
58
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
56
59
|
} catch (_) {
|
|
57
60
|
return null;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
function writeCache(
|
|
62
|
-
|
|
63
|
-
fs.
|
|
64
|
-
fs.writeFileSync(cachePath, JSON.stringify(data) + '\n');
|
|
64
|
+
function writeCache(data) {
|
|
65
|
+
fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
|
|
66
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data) + '\n');
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/** 读取 hook 传入的 stdin(session_id);TTY 或异常返回空对象 */
|
|
@@ -104,7 +106,7 @@ async function main() {
|
|
|
104
106
|
const isPrerelease = versionUtil.detectIsPrerelease(installed);
|
|
105
107
|
const tag = isPrerelease ? 'beta' : 'latest';
|
|
106
108
|
const now = Date.now();
|
|
107
|
-
let cache = readCache(
|
|
109
|
+
let cache = readCache() || {};
|
|
108
110
|
|
|
109
111
|
// 决定最新版本:命中每日缓存且 tag 一致则复用,否则请求 registry
|
|
110
112
|
let latest = cache.latest;
|
|
@@ -116,7 +118,7 @@ async function main() {
|
|
|
116
118
|
return; // 网络失败静默
|
|
117
119
|
}
|
|
118
120
|
cache = { ...cache, lastCheck: now, latest, tag };
|
|
119
|
-
writeCache(
|
|
121
|
+
writeCache(cache);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
if (!latest || versionUtil.compareVersions(latest, installed) <= 0) return; // 已是最新
|
|
@@ -125,8 +127,8 @@ async function main() {
|
|
|
125
127
|
|
|
126
128
|
// Hook 调用(有 sessionId):以 additionalContext 注入,由 AI 助手提醒用户
|
|
127
129
|
if (sessionId) {
|
|
128
|
-
//
|
|
129
|
-
const notifiedKey = adapterName || '_default'
|
|
130
|
+
// 按「项目+适配器」分别去重,同一会话同一项目同一适配器只提示一次
|
|
131
|
+
const notifiedKey = `${projectDir}::${adapterName || '_default'}`;
|
|
130
132
|
if (!cache.notifiedSessions) cache.notifiedSessions = {};
|
|
131
133
|
if (cache.notifiedSessions[notifiedKey] === sessionId) return; // 本会话本适配器已提示过
|
|
132
134
|
process.stdout.write(Buffer.from(JSON.stringify({
|
|
@@ -136,7 +138,7 @@ async function main() {
|
|
|
136
138
|
},
|
|
137
139
|
}) + '\n', 'utf8'));
|
|
138
140
|
cache.notifiedSessions[notifiedKey] = sessionId;
|
|
139
|
-
writeCache(
|
|
141
|
+
writeCache(cache);
|
|
140
142
|
return;
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -17,7 +17,8 @@ const GITIGNORE_RULES = [
|
|
|
17
17
|
{ add: '.claude', accept: ['.claude', '.claude/'] },
|
|
18
18
|
{ add: '.qoder', accept: ['.qoder', '.qoder/'] },
|
|
19
19
|
{ add: '.cbb/', accept: ['.cbb/'] },
|
|
20
|
-
{ add: '.
|
|
20
|
+
{ add: '.codespace/', accept: ['.codespace/', '.codespace'] },
|
|
21
|
+
{ add: '.opencode', accept: ['.opencode', '.opencode/'] },
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
/** 将 .gitignore 文本拆为行数组。 */
|
package/cbb/lib/utils/output.js
CHANGED
|
@@ -48,9 +48,94 @@ function section(icon, title) {
|
|
|
48
48
|
console.log(c('gray', '─'.repeat(48)));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
function
|
|
53
|
-
|
|
51
|
+
/** 视觉宽度:CJK / 全角字符按 2 列计,其余按 1 列(终端对齐用) */
|
|
52
|
+
function visualWidth(text) {
|
|
53
|
+
let w = 0;
|
|
54
|
+
for (const ch of String(text)) {
|
|
55
|
+
const cp = ch.codePointAt(0);
|
|
56
|
+
const wide =
|
|
57
|
+
(cp >= 0x1100 && cp <= 0x115f) ||
|
|
58
|
+
cp === 0x2329 || cp === 0x232a ||
|
|
59
|
+
(cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
|
|
60
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
61
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
62
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
63
|
+
(cp >= 0xff00 && cp <= 0xff60) ||
|
|
64
|
+
(cp >= 0xffe0 && cp <= 0xffe6);
|
|
65
|
+
w += wide ? 2 : 1;
|
|
66
|
+
}
|
|
67
|
+
return w;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 按视觉宽度右补空格(中文标签对齐用) */
|
|
71
|
+
function padVisual(text, width) {
|
|
72
|
+
return text + ' '.repeat(Math.max(0, width - visualWidth(text)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 实时进度行的 spinner 帧(TTY 下循环播放,表示进程仍活着)
|
|
76
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 长耗时步骤的单行实时进度:显示「正在做什么 + 已耗时」,避免看起来像卡死。
|
|
80
|
+
* TTY:spinner 原地刷新(\r + 清行);非 TTY:内容变化时换行输出(CI 日志友好)。
|
|
81
|
+
* 结束时必须调用 done() 清掉本行,再由调用方补一条最终 step()。
|
|
82
|
+
* @param {string} label 步骤标签(与 step 同宽,如「应用」)
|
|
83
|
+
* @param {{stream?:NodeJS.WriteStream,indent?:number,labelWidth?:number,intervalMs?:number}} [opts]
|
|
84
|
+
* @returns {{update(text:string):void, done():void}}
|
|
85
|
+
*/
|
|
86
|
+
function progress(label, opts = {}) {
|
|
87
|
+
const { stream = process.stdout, indent = 2, labelWidth = 10, intervalMs = 80 } = opts;
|
|
88
|
+
const tty = stream.isTTY === true;
|
|
89
|
+
const startedAt = Date.now();
|
|
90
|
+
let frame = 0;
|
|
91
|
+
let text = '';
|
|
92
|
+
let timer = null;
|
|
93
|
+
|
|
94
|
+
const head = icon => ' '.repeat(indent) + c('cyan', icon) + ' ' + c('gray', padVisual(label, labelWidth));
|
|
95
|
+
const elapsed = () => Math.round((Date.now() - startedAt) / 1000);
|
|
96
|
+
|
|
97
|
+
const render = () => {
|
|
98
|
+
const sec = elapsed();
|
|
99
|
+
const value = sec > 0 ? `${text} ${c('gray', sec + 's')}` : text;
|
|
100
|
+
stream.write(`\r\x1b[2K${head(SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length])} ${value}`);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
update(next) {
|
|
105
|
+
if (next === text) return;
|
|
106
|
+
text = next;
|
|
107
|
+
if (tty) {
|
|
108
|
+
render();
|
|
109
|
+
if (!timer) {
|
|
110
|
+
timer = setInterval(render, intervalMs);
|
|
111
|
+
if (timer.unref) timer.unref();
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
stream.write(`${head('·')} ${text}\n`);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
done() {
|
|
118
|
+
if (timer) {
|
|
119
|
+
clearInterval(timer);
|
|
120
|
+
timer = null;
|
|
121
|
+
}
|
|
122
|
+
if (tty) stream.write('\r\x1b[2K');
|
|
123
|
+
text = '';
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 缩进步进行:` ✓ 标签 值`
|
|
130
|
+
* @param {string} icon 图标(✓ / ! / ✗ / ⏳ / ▸ …)
|
|
131
|
+
* @param {string} label 标签(中文;按视觉宽度对齐到 labelWidth)
|
|
132
|
+
* @param {string} [value] 值文本
|
|
133
|
+
* @param {{iconColor?:string,labelColor?:string,valueColor?:string,indent?:number,labelWidth?:number}} [opts]
|
|
134
|
+
*/
|
|
135
|
+
function step(icon, label, value = '', opts = {}) {
|
|
136
|
+
const { iconColor = 'green', labelColor = 'gray', valueColor = null, indent = 2, labelWidth = 10 } = opts;
|
|
137
|
+
const head = ' '.repeat(indent) + c(iconColor, icon) + ' ' + c(labelColor, padVisual(label, labelWidth));
|
|
138
|
+
console.log(value ? `${head} ${valueColor ? c(valueColor, value) : value}` : head);
|
|
54
139
|
}
|
|
55
140
|
|
|
56
141
|
module.exports = {
|
|
@@ -60,5 +145,8 @@ module.exports = {
|
|
|
60
145
|
LOG_STYLES,
|
|
61
146
|
log,
|
|
62
147
|
section,
|
|
63
|
-
|
|
148
|
+
visualWidth,
|
|
149
|
+
padVisual,
|
|
150
|
+
progress,
|
|
151
|
+
step,
|
|
64
152
|
};
|
package/cbb/lib/utils/version.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const
|
|
15
|
+
const https = require('https');
|
|
16
16
|
|
|
17
17
|
const PKG_NAME = '@jspg-ai/coding-bb';
|
|
18
18
|
const REGISTRY = 'https://registry.npmjs.org';
|
|
@@ -41,7 +41,7 @@ function readInstalledVersion(projectDir) {
|
|
|
41
41
|
function fetchLatest(tag) {
|
|
42
42
|
return new Promise((resolve, reject) => {
|
|
43
43
|
const url = `${REGISTRY}/${encodeURIComponent(PKG_NAME).replace('%2F', '/')}`;
|
|
44
|
-
const req =
|
|
44
|
+
const req = https.get(url, { timeout: REQUEST_TIMEOUT }, res => {
|
|
45
45
|
if (res.statusCode !== 200) { res.resume(); reject(new Error(`status ${res.statusCode}`)); return; }
|
|
46
46
|
let body = '';
|
|
47
47
|
res.setEncoding('utf-8');
|
|
@@ -94,34 +94,6 @@ function detectIsPrerelease(version) {
|
|
|
94
94
|
return typeof version === 'string' && version.includes('-');
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/**
|
|
98
|
-
* 反查指定版本所属的 dist-tag:从 registry 获取全量 tag 映射,
|
|
99
|
-
* 找到指向该版本的 tag 名称(如 zjc / beta / latest)。
|
|
100
|
-
* - 不依赖版本号格式,兼容任意 prerelease 命名
|
|
101
|
-
* - 网络失败 / 找不到 / 多个 tag 指向同一版本(选第一个)→ 返回 null,由调用方降级
|
|
102
|
-
*/
|
|
103
|
-
function findTagForVersion(version) {
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
const url = `${REGISTRY}/${encodeURIComponent(PKG_NAME).replace('%2F', '/')}`;
|
|
106
|
-
const req = http.get(url, { timeout: REQUEST_TIMEOUT }, res => {
|
|
107
|
-
if (res.statusCode !== 200) { res.resume(); reject(new Error(`status ${res.statusCode}`)); return; }
|
|
108
|
-
let body = '';
|
|
109
|
-
res.setEncoding('utf-8');
|
|
110
|
-
res.on('data', chunk => { body += chunk; });
|
|
111
|
-
res.on('end', () => {
|
|
112
|
-
try {
|
|
113
|
-
const distTags = JSON.parse(body)['dist-tags'] || {};
|
|
114
|
-
// 找到指向目标版本的 tag(取第一个,避免重复)
|
|
115
|
-
const found = Object.keys(distTags).find(k => distTags[k] === version);
|
|
116
|
-
resolve(found || null);
|
|
117
|
-
} catch (err) { reject(err); }
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
req.on('timeout', () => req.destroy(new Error('timeout')));
|
|
121
|
-
req.on('error', reject);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
97
|
module.exports = {
|
|
126
98
|
PKG_NAME,
|
|
127
99
|
REGISTRY,
|
|
@@ -131,5 +103,4 @@ module.exports = {
|
|
|
131
103
|
fetchLatest,
|
|
132
104
|
compareVersions,
|
|
133
105
|
detectIsPrerelease,
|
|
134
|
-
findTagForVersion,
|
|
135
106
|
};
|
package/cbb/lib/wiki/cli.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* cbbm wiki 子命令 — Wiki 查询与管理。
|
|
5
5
|
*
|
|
6
6
|
* 用法:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
7
|
+
* cbbm wiki get <pageId> [--expand=...] [--full]
|
|
8
|
+
* cbbm wiki children <pageId> [--limit=50] [--start=0]
|
|
9
|
+
* cbbm wiki search "<cql>" [--limit=20] [--excerpt=highlight] [--order=lastmodified desc]
|
|
10
|
+
* cbbm wiki spaces [--limit=100] [--type=global]
|
|
11
|
+
* cbbm wiki comments <pageId> [--limit=50]
|
|
12
|
+
* cbbm wiki user [--current] [--account-id=<id>] [--username=<name>]
|
|
13
|
+
* cbbm wiki versions <pageId> [--limit=20]
|
|
14
|
+
* cbbm wiki attachments <pageId> list [--limit=50]
|
|
15
|
+
* cbbm wiki attachments <pageId> upload --file=<path> [--comment="..."]
|
|
16
|
+
* cbbm wiki attachments <pageId> delete --attachment-id=<id>
|
|
17
|
+
* cbbm wiki comment <pageId> --body-file=<path> [--parent=<parentCommentId>]
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const fs = require('fs');
|
|
@@ -79,7 +79,7 @@ function parseArgs(argv) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function showHelp() {
|
|
82
|
-
out(`
|
|
82
|
+
out(`cbbm wiki — Wiki 操作(查询 + 发布 + 管理)
|
|
83
83
|
|
|
84
84
|
参数格式:
|
|
85
85
|
本工具同时支持两种写法,请按习惯任选其一:
|
|
@@ -104,9 +104,9 @@ function showHelp() {
|
|
|
104
104
|
comment <pageId> --body-file=<path> 创建评论
|
|
105
105
|
|
|
106
106
|
示例:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
cbbm wiki create --parent-id 123456 --title "方案" --file design.md
|
|
108
|
+
cbbm wiki get 123456 --full
|
|
109
|
+
cbbm wiki search 'type=page AND title~"freight"'
|
|
110
110
|
|
|
111
111
|
认证:环境变量 CBB_WIKI_EMAIL + CBB_WIKI_TOKEN,或 ~/.cbb/wiki-config.json
|
|
112
112
|
令牌获取: https://wiki.amh-group.com/plugins/tokens/view.action`);
|
|
@@ -115,7 +115,7 @@ function showHelp() {
|
|
|
115
115
|
// ── 子命令实现 ────────────────────────────────────────
|
|
116
116
|
|
|
117
117
|
async function cmdGet({ pos, opts }) {
|
|
118
|
-
if (!pos[0]) { err('用法:
|
|
118
|
+
if (!pos[0]) { err('用法: cbbm wiki get <pageId> [--expand=...] [--full]'); process.exit(1); }
|
|
119
119
|
const result = await api.getPage(pos[0], { includeBody: opts.full });
|
|
120
120
|
if (!result.success) { err(`获取失败: ${result.error}`); process.exit(1); }
|
|
121
121
|
|
|
@@ -141,7 +141,7 @@ async function cmdGet({ pos, opts }) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
async function cmdChildren({ pos, opts }) {
|
|
144
|
-
if (!pos[0]) { err('用法:
|
|
144
|
+
if (!pos[0]) { err('用法: cbbm wiki children <pageId> [--limit=50] [--start=0]'); process.exit(1); }
|
|
145
145
|
const result = await api.getChildren(pos[0], { limit: Number(opts.limit) || 50, start: Number(opts.start) || 0 });
|
|
146
146
|
if (!result.success) { err(`获取失败: ${result.error}`); process.exit(1); }
|
|
147
147
|
|
|
@@ -162,7 +162,7 @@ async function cmdChildren({ pos, opts }) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
async function cmdSearch({ pos, opts }) {
|
|
165
|
-
if (!pos[0]) { err('用法:
|
|
165
|
+
if (!pos[0]) { err('用法: cbbm wiki search "<cql>" [--limit=20] [--excerpt=highlight] [--order=lastmodified desc]'); process.exit(1); }
|
|
166
166
|
const result = await api.search(pos[0], {
|
|
167
167
|
limit: Number(opts.limit) || 20,
|
|
168
168
|
excerpt: opts.excerpt,
|
|
@@ -204,7 +204,7 @@ async function cmdSpaces({ opts }) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
async function cmdComments({ pos, opts }) {
|
|
207
|
-
if (!pos[0]) { err('用法:
|
|
207
|
+
if (!pos[0]) { err('用法: cbbm wiki comments <pageId> [--limit=50]'); process.exit(1); }
|
|
208
208
|
const result = await api.getComments(pos[0], { limit: Number(opts.limit) || 50 });
|
|
209
209
|
if (!result.success) { err(`获取失败: ${result.error}`); process.exit(1); }
|
|
210
210
|
|
|
@@ -246,7 +246,7 @@ async function cmdUser({ opts }) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
async function cmdVersions({ pos, opts }) {
|
|
249
|
-
if (!pos[0]) { err('用法:
|
|
249
|
+
if (!pos[0]) { err('用法: cbbm wiki versions <pageId> [--limit=20]'); process.exit(1); }
|
|
250
250
|
const result = await api.getVersions(pos[0], { limit: Number(opts.limit) || 20 });
|
|
251
251
|
if (!result.success) { err(`获取失败: ${result.error}`); process.exit(1); }
|
|
252
252
|
|
|
@@ -267,7 +267,7 @@ async function cmdVersions({ pos, opts }) {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
async function cmdAttachments({ pos, opts }) {
|
|
270
|
-
if (!pos[0] || !pos[1]) { err('用法:
|
|
270
|
+
if (!pos[0] || !pos[1]) { err('用法: cbbm wiki attachments <pageId> <list|upload|delete> [选项]'); process.exit(1); }
|
|
271
271
|
const pageId = pos[0];
|
|
272
272
|
const action = pos[1];
|
|
273
273
|
|
|
@@ -320,7 +320,7 @@ async function cmdAttachments({ pos, opts }) {
|
|
|
320
320
|
|
|
321
321
|
async function cmdComment({ pos, opts }) {
|
|
322
322
|
if (!pos[0] || !opts['body-file']) {
|
|
323
|
-
err('用法:
|
|
323
|
+
err('用法: cbbm wiki comment <pageId> --body-file=<path> [--parent=<parentCommentId>]');
|
|
324
324
|
process.exit(1);
|
|
325
325
|
}
|
|
326
326
|
const body = fs.readFileSync(opts['body-file'], 'utf8');
|
|
@@ -347,7 +347,7 @@ function readAndConvert(filePath) {
|
|
|
347
347
|
|
|
348
348
|
async function cmdCreate({ pos, opts }) {
|
|
349
349
|
if (!opts['parent-id'] || !opts.title || !opts.file) {
|
|
350
|
-
err('用法:
|
|
350
|
+
err('用法: cbbm wiki create --parent-id <id> --title "..." --file <md>');
|
|
351
351
|
process.exit(1);
|
|
352
352
|
}
|
|
353
353
|
const xhtml = readAndConvert(opts.file);
|
|
@@ -369,7 +369,7 @@ async function cmdCreate({ pos, opts }) {
|
|
|
369
369
|
|
|
370
370
|
async function cmdUpdate({ pos, opts }) {
|
|
371
371
|
if (!opts['page-id'] || !opts.file) {
|
|
372
|
-
err('用法:
|
|
372
|
+
err('用法: cbbm wiki update --page-id <id> --file <md>');
|
|
373
373
|
process.exit(1);
|
|
374
374
|
}
|
|
375
375
|
const xhtml = readAndConvert(opts.file);
|