@ranger1/dx 0.1.40 → 0.1.41

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.
@@ -18,7 +18,7 @@ pnpm add -g @ranger1/dx@latest && dx initial
18
18
 
19
19
  ## Step 1: 并行检测
20
20
 
21
- **同时执行以下 3 个 Bash 调用(真正并行):**
21
+ **同时执行以下 4 个 Bash 调用(真正并行):**
22
22
 
23
23
  ```bash
24
24
  # 批次 1: CLI 版本检测
@@ -32,24 +32,20 @@ echo "agent-browser:" && (which agent-browser && agent-browser --version 2>/dev/
32
32
  # 批次 2: 项目文件检测
33
33
  echo "=== PROJECT_FILES ===";
34
34
  echo "AGENTS.md:" && (test -f AGENTS.md && echo "FOUND" || echo "NOT_FOUND");
35
- echo "instructions:" && (grep -q '"AGENTS.md"' ~/.config/opencode/opencode.json 2>/dev/null && grep -q '"ruler/' ~/.config/opencode/opencode.json 2>/dev/null && echo "CONFIGURED" || echo "NOT_CONFIGURED");
36
35
  ```
37
36
 
38
37
  ```bash
39
38
  # 批次 3: OpenCode 插件检测
40
39
  # 注意:插件名可能带版本号(如 @1.3.0),使用模糊匹配
41
40
  echo "=== OPENCODE_PLUGINS ===";
42
- echo "oh-my-opencode:" && (grep -q 'oh-my-opencode' ~/.config/opencode/opencode.json 2>/dev/null && echo "INSTALLED" || echo "NOT_INSTALLED");
43
- echo "opencode-openai-codex-auth:" && (grep -q 'opencode-openai-codex-auth' ~/.config/opencode/opencode.json 2>/dev/null && echo "INSTALLED" || echo "NOT_INSTALLED");
41
+ echo "oh-my-opencode:" && (opencode plugin list 2>/dev/null | grep -q 'oh-my-opencode' && echo "INSTALLED" || echo "NOT_INSTALLED");
42
+ echo "opencode-openai-codex-auth:" && (opencode plugin list 2>/dev/null | grep -q 'opencode-openai-codex-auth' && echo "INSTALLED" || echo "NOT_INSTALLED");
44
43
  ```
45
44
 
46
45
  ```bash
47
- # 批次 4: oh-my-opencode.json 配置检测
48
- echo "=== OMO_CONFIG ===";
49
- echo "sisyphus_agent:" && (grep -q '"sisyphus_agent"' ~/.config/opencode/oh-my-opencode.json 2>/dev/null && echo "CONFIGURED" || echo "NOT_CONFIGURED");
50
- echo "agents.sisyphus.variant:" && (node -e "const fs=require('node:fs');const os=require('node:os');const p=os.homedir()+'/.config/opencode/oh-my-opencode.json';try{const j=JSON.parse(fs.readFileSync(p,'utf8'));process.exit(j?.agents?.sisyphus?.variant==='none'?0:1)}catch(e){process.exit(1)}" 2>/dev/null && echo "CONFIGURED" || echo "NOT_CONFIGURED");
51
- echo "agent.quick:" && (grep -Eq '"agent"[[:space:]]*:' ~/.config/opencode/opencode.json 2>/dev/null && grep -Eq '"quick"[[:space:]]*:' ~/.config/opencode/opencode.json 2>/dev/null && echo "CONFIGURED" || echo "NOT_CONFIGURED");
52
- echo "agent.middle:" && (grep -Eq '"agent"[[:space:]]*:' ~/.config/opencode/opencode.json 2>/dev/null && grep -Eq '"middle"[[:space:]]*:' ~/.config/opencode/opencode.json 2>/dev/null && echo "CONFIGURED" || echo "NOT_CONFIGURED");
46
+ # 批次 4: attach 配置(统一)
47
+ echo "=== OPENCODE_ATTACH ===";
48
+ echo "attach:" && (python3 ~/.opencode/commands/opencode_attach.py --dry-run >/dev/null 2>&1 && echo "READY" || echo "NOT_READY");
53
49
  ```
54
50
 
55
51
  ```bash
@@ -72,14 +68,10 @@ dx | <状态> | <版本>
72
68
  python3 | <状态> | <版本>
73
69
  python(软链接) | <状态> | <版本>
74
70
  AGENTS.md | <状态> | -
75
- 全局 instructions 配置 | <状态> | -
76
71
  oh-my-opencode 插件 | <状态> | -
77
72
  opencode-openai-codex-auth 插件 | <状态> | -
78
73
  agent-browser | <状态> | <版本>
79
- 全局 sisyphus_agent 配置 | <状态> | -
80
- 全局 agents.sisyphus.variant 配置 | <状态> | -
81
- 全局 agent.quick 配置 | <状态> | -
82
- 全局 agent.middle 配置 | <状态> | -
74
+ attach(全局配置写入) | <状态> | -
83
75
  ```
84
76
 
85
77
  ---
@@ -107,62 +99,6 @@ brew install opencode || npm install -g opencode
107
99
 
108
100
  - AGENTS.md 文件不存在,OpenCode 需要此文件作为项目指令入口
109
101
  - 建议创建或检查文件路径
110
- - AGENTS.md 应位于项目根目录,并在全局 `~/.config/opencode/opencode.json` 的 `instructions` 中引用
111
-
112
- ### 3.3 全局 opencode.json instructions 配置缺失
113
-
114
- **注意:instructions 配置应在全局配置文件 `~/.config/opencode/opencode.json` 中,而非项目根目录。项目根目录不需要 opencode.json 文件。**
115
-
116
- 1. 先读取现有全局配置:
117
-
118
- ```bash
119
- cat ~/.config/opencode/opencode.json
120
- ```
121
-
122
- 2. 使用 Edit 工具在 `~/.config/opencode/opencode.json` 中添加或修改 `instructions` 配置:
123
-
124
- ```json
125
- {
126
- "$schema": "https://opencode.ai/config.json",
127
- "instructions": ["AGENTS.md", "ruler/**/*.md"]
128
- }
129
- ```
130
-
131
- ### 3.4 全局配置指令无效
132
-
133
- 使用 Edit 工具修复 `~/.config/opencode/opencode.json`,确保包含:
134
-
135
- - `"AGENTS.md"`: 主配置文件
136
- - `"ruler/**/*.md"`: 自动加载 ruler 目录下所有 .md 文件(因 OpenCode 不支持 @ 引用)
137
-
138
- ### 3.5 全局 OpenCode 插件安装
139
-
140
- **注意:OpenCode 插件配置应在全局配置文件 `~/.config/opencode/opencode.json` 中,而非项目根目录。**
141
-
142
- 1. 先读取现有全局配置:
143
-
144
- ```bash
145
- cat ~/.config/opencode/opencode.json
146
- ```
147
-
148
- 2. 使用 Edit 工具在 `~/.config/opencode/opencode.json` 的 `plugin` 数组中添加缺失的插件:
149
- - `oh-my-opencode`
150
- - `opencode-openai-codex-auth`
151
-
152
- 示例 plugin 配置:
153
-
154
- ```json
155
- "plugin": [
156
- "oh-my-opencode",
157
- "opencode-openai-codex-auth"
158
- ]
159
- ```
160
-
161
- 3. 验证安装:
162
-
163
- ```bash
164
- grep -E 'oh-my-opencode|opencode-openai-codex-auth' ~/.config/opencode/opencode.json
165
- ```
166
102
 
167
103
  ### 3.6 agent-browser 未安装
168
104
 
@@ -212,87 +148,15 @@ fi
212
148
  python --version
213
149
  ```
214
150
 
215
- ### 3.7 全局 oh-my-opencode.json 配置缺失
216
-
217
- **注意:oh-my-opencode 配置应在全局配置文件 `~/.config/opencode/oh-my-opencode.json` 中,而非项目根目录。**
151
+ ### 3.9 自动 attach(推荐)
218
152
 
219
- 1. 先读取现有全局配置:
153
+ 执行 attach(会自动覆盖/新建对应节点,其它不动,并生成备份文件):
220
154
 
221
155
  ```bash
222
- cat ~/.config/opencode/oh-my-opencode.json
223
- ```
224
-
225
- 2. 使用 Edit 工具添加缺失的配置节点:
226
-
227
- #### 3.7.1 sisyphus_agent 配置缺失
228
-
229
- 如果根节点缺少 `sisyphus_agent`,使用 Edit 工具添加:
230
-
231
- ```json
232
- {
233
- "sisyphus_agent": {
234
- "disabled": false,
235
- "default_builder_enabled": true,
236
- "planner_enabled": true,
237
- "replace_plan": false
238
- }
239
- }
156
+ python3 ~/.opencode/commands/opencode_attach.py
240
157
  ```
241
158
 
242
- 注意:这是根节点配置,应添加到 JSON 的第一层级。
243
159
 
244
- #### 3.7.2 agents.sisyphus.variant 不是 none
245
-
246
- 如果 `~/.config/opencode/oh-my-opencode.json` 的 `agents.sisyphus.variant` 缺失或不是 `none`,使用 Edit 工具修复为:
247
-
248
- ```json
249
- {
250
- "agents": {
251
- "sisyphus": {
252
- "variant": "none"
253
- }
254
- }
255
- }
256
- ```
257
-
258
- 3. 验证配置:
259
-
260
- ```bash
261
- # 检查 sisyphus_agent
262
- grep -q '"sisyphus_agent"' ~/.config/opencode/oh-my-opencode.json && echo "✅ sisyphus_agent 已配置" || echo "❌ sisyphus_agent 缺失"
263
-
264
- # 检查 agents.sisyphus.variant
265
- node -e "const fs=require('node:fs');const os=require('node:os');const p=os.homedir()+'/.config/opencode/oh-my-opencode.json';const j=JSON.parse(fs.readFileSync(p,'utf8'));console.log(j?.agents?.sisyphus?.variant||'MISSING');process.exit(j?.agents?.sisyphus?.variant==='none'?0:1)" && echo "✅ agents.sisyphus.variant=none" || echo "❌ agents.sisyphus.variant 不是 none"
266
- ```
267
-
268
- ### 3.8 全局 opencode.json agent 配置缺失
269
-
270
- **注意:agent 配置应在全局配置文件 `~/.config/opencode/opencode.json` 中,而非项目根目录。**
271
-
272
- 如果 `~/.config/opencode/opencode.json` 缺少 `agent.quick` 或 `agent.middle`,使用 Edit 工具添加:
273
-
274
- ```json
275
- {
276
- "agent": {
277
- "quick": {
278
- "model": "github-copilot/claude-haiku-4.5"
279
- },
280
- "middle": {
281
- "model": "github-copilot/claude-sonnet-4.5"
282
- }
283
- }
284
- }
285
- ```
286
-
287
- 验证配置:
288
-
289
- ```bash
290
- # 检查 agent.quick
291
- grep -Eq '"agent"[[:space:]]*:' ~/.config/opencode/opencode.json && grep -Eq '"quick"[[:space:]]*:' ~/.config/opencode/opencode.json && echo "✅ agent.quick 已配置" || echo "❌ agent.quick 缺失"
292
-
293
- # 检查 agent.middle
294
- grep -Eq '"agent"[[:space:]]*:' ~/.config/opencode/opencode.json && grep -Eq '"middle"[[:space:]]*:' ~/.config/opencode/opencode.json && echo "✅ agent.middle 已配置" || echo "❌ agent.middle 缺失"
295
- ```
296
160
 
297
161
  ---
298
162
 
@@ -0,0 +1,92 @@
1
+ {
2
+ "sisyphus_agent": {
3
+ "disabled": false,
4
+ "default_builder_enabled": true,
5
+ "planner_enabled": true,
6
+ "replace_plan": false
7
+ },
8
+ "agents": {
9
+ "sisyphus": {
10
+ "model": "openai/gpt-5.2",
11
+ "variant": "none"
12
+ },
13
+ "oracle": {
14
+ "model": "openai/gpt-5.2",
15
+ "variant": "high"
16
+ },
17
+ "librarian": {
18
+ "model": "github-copilot/gpt-5-mini"
19
+ },
20
+ "explore": {
21
+ "model": "github-copilot/gpt-5-mini"
22
+ },
23
+ "multimodal-looker": {
24
+ "model": "github-copilot/gemini-3-flash-preview"
25
+ },
26
+ "prometheus": {
27
+ "model": "openai/gpt-5.2",
28
+ "variant": "max"
29
+ },
30
+ "metis": {
31
+ "model": "openai/gpt-5.2",
32
+ "variant": "max"
33
+ },
34
+ "momus": {
35
+ "model": "openai/gpt-5.2",
36
+ "variant": "medium"
37
+ },
38
+ "atlas": {
39
+ "model": "openai/gpt-5.2-codex"
40
+ },
41
+ "codex-reviewer": {
42
+ "model": "openai/gpt-5.2-codex",
43
+ "variant": "xhigh",
44
+ "temperature": 0.1
45
+ },
46
+ "gemini-reviewer": {
47
+ "model": "github-copilot/gemini-3-pro-preview",
48
+ "variant": "max"
49
+ },
50
+ "claude-reviewer": {
51
+ "model": "github-copilot/claude-sonnet-4.5",
52
+ "variant": "high"
53
+ },
54
+ "pr-fixer": {
55
+ "model": "openai/gpt-5.2-codex",
56
+ "variant": "xhigh",
57
+ "temperature": 0.1
58
+ }
59
+ },
60
+ "concurrency": 5,
61
+ "categories": {
62
+ "visual-engineering": {
63
+ "model": "github-copilot/gemini-3-pro-preview",
64
+ "variant": "high"
65
+ },
66
+ "ultrabrain": {
67
+ "model": "openai/gpt-5.2-codex",
68
+ "variant": "xhigh"
69
+ },
70
+ "artistry": {
71
+ "model": "github-copilot/gemini-3-pro-preview",
72
+ "variant": "max"
73
+ },
74
+ "quick": {
75
+ "model": "github-copilot/claude-haiku-4.5"
76
+ },
77
+ "middle": {
78
+ "model": "github-copilot/claude-sonnet-4.5"
79
+ },
80
+ "unspecified-low": {
81
+ "model": "github-copilot/claude-sonnet-4.5",
82
+ "variant": "medium"
83
+ },
84
+ "unspecified-high": {
85
+ "model": "openai/gpt-5.2",
86
+ "variant": "medium"
87
+ },
88
+ "writing": {
89
+ "model": "github-copilot/gemini-3-flash-preview"
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "instructions": [
3
+ "AGENTS.md",
4
+ "ruler/**/*.md"
5
+ ],
6
+ "plugin": [
7
+ "opencode-antigravity-auth",
8
+ "oh-my-opencode",
9
+ "opencode-openai-codex-auth"
10
+ ],
11
+ "agent": {
12
+ "quick": {
13
+ "model": "github-copilot/gpt-5-mini"
14
+ },
15
+ "middle": {
16
+ "model": "github-copilot/claude-sonnet-4.5"
17
+ }
18
+ },
19
+ "permission": {
20
+ "read": {
21
+ "*.env": "allow",
22
+ "*.env.*": "allow"
23
+ }
24
+ },
25
+ "model": "openai/gpt-5.2"
26
+ }
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import shutil
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+
11
+
12
+ def _is_plain_object(value):
13
+ return isinstance(value, dict)
14
+
15
+
16
+ def deep_merge_in_place(target, source):
17
+ if not _is_plain_object(target) or not _is_plain_object(source):
18
+ raise TypeError("deep_merge_in_place expects dicts")
19
+
20
+ for key, s_val in source.items():
21
+ if _is_plain_object(s_val):
22
+ t_val = target.get(key)
23
+ if not _is_plain_object(t_val):
24
+ t_val = {}
25
+ target[key] = t_val
26
+ deep_merge_in_place(t_val, s_val)
27
+ else:
28
+ target[key] = s_val
29
+
30
+ return target
31
+
32
+
33
+ def load_json_file(path):
34
+ raw = path.read_text(encoding="utf-8")
35
+ return json.loads(raw)
36
+
37
+
38
+ def atomic_write_json(path, data):
39
+ path.parent.mkdir(parents=True, exist_ok=True)
40
+ tmp_path = path.with_name(f"{path.name}.tmp.{os.getpid()}")
41
+ serialized = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
42
+ tmp_path.write_text(serialized, encoding="utf-8")
43
+ os.replace(tmp_path, path)
44
+
45
+
46
+ def backup_file(path):
47
+ if not path.exists():
48
+ return None
49
+ ts = time.strftime("%Y%m%d%H%M%S")
50
+ bak = path.with_name(f"{path.name}.bak.{ts}")
51
+ shutil.copy2(path, bak)
52
+ return bak
53
+
54
+
55
+ def attach(source_path, target_path, *, make_backup=True, dry_run=False):
56
+ source = load_json_file(source_path)
57
+ if not _is_plain_object(source):
58
+ raise ValueError(f"Source JSON root must be an object: {source_path}")
59
+
60
+ if target_path.exists():
61
+ target = load_json_file(target_path)
62
+ if not _is_plain_object(target):
63
+ raise ValueError(f"Target JSON root must be an object: {target_path}")
64
+ else:
65
+ target = {}
66
+
67
+ merged = deep_merge_in_place(target, source)
68
+
69
+ bak = None
70
+ if make_backup and target_path.exists() and not dry_run:
71
+ bak = backup_file(target_path)
72
+
73
+ if not dry_run:
74
+ atomic_write_json(target_path, merged)
75
+
76
+ return bak
77
+
78
+
79
+ def main(argv):
80
+ parser = argparse.ArgumentParser(
81
+ description=(
82
+ "Attach JSON fragments into OpenCode global config files. "
83
+ "Rule: deep-merge objects; replace arrays/primitives; preserve other keys."
84
+ )
85
+ )
86
+ parser.add_argument(
87
+ "--oh-source",
88
+ default=str(Path(__file__).resolve().parents[1] / "commands" / "oh_attach.json"),
89
+ help="Path to oh_attach.json",
90
+ )
91
+ parser.add_argument(
92
+ "--opencode-source",
93
+ default=str(Path(__file__).resolve().parents[1] / "commands" / "opencode_attach.json"),
94
+ help="Path to opencode_attach.json",
95
+ )
96
+ parser.add_argument(
97
+ "--config-dir",
98
+ default=str(Path.home() / ".config" / "opencode"),
99
+ help="Config directory (default: ~/.config/opencode)",
100
+ )
101
+ parser.add_argument("--no-backup", action="store_true", help="Do not create .bak files")
102
+ parser.add_argument("--dry-run", action="store_true", help="Do not write files")
103
+
104
+ args = parser.parse_args(argv)
105
+
106
+ config_dir = Path(os.path.expanduser(args.config_dir)).resolve()
107
+ oh_target = config_dir / "oh-my-opencode.json"
108
+ opencode_target = config_dir / "opencode.json"
109
+
110
+ oh_source = Path(args.oh_source).resolve()
111
+ opencode_source = Path(args.opencode_source).resolve()
112
+
113
+ if not oh_source.exists():
114
+ raise FileNotFoundError(f"Missing source file: {oh_source}")
115
+ if not opencode_source.exists():
116
+ raise FileNotFoundError(f"Missing source file: {opencode_source}")
117
+
118
+ make_backup = not args.no_backup
119
+ dry_run = args.dry_run
120
+
121
+ bak1 = attach(oh_source, oh_target, make_backup=make_backup, dry_run=dry_run)
122
+ bak2 = attach(opencode_source, opencode_target, make_backup=make_backup, dry_run=dry_run)
123
+
124
+ if dry_run:
125
+ print("DRY_RUN: no files written")
126
+ return 0
127
+
128
+ if bak1:
129
+ print(f"backup: {bak1}")
130
+ if bak2:
131
+ print(f"backup: {bak2}")
132
+ print(f"updated: {oh_target}")
133
+ print(f"updated: {opencode_target}")
134
+ return 0
135
+
136
+
137
+ if __name__ == "__main__":
138
+ try:
139
+ raise SystemExit(main(sys.argv[1:]))
140
+ except Exception as e:
141
+ print(f"ERROR: {e}", file=sys.stderr)
142
+ raise SystemExit(1)
@@ -27,8 +27,8 @@ async function collectTemplateFiles(dir) {
27
27
  }
28
28
  if (!entry.isFile()) continue
29
29
  const lowerName = entry.name.toLowerCase()
30
- // 拷贝 .md .py 文件
31
- if (!lowerName.endsWith('.md') && !lowerName.endsWith('.py')) continue
30
+ // 拷贝 .md / .py / .json 文件
31
+ if (!lowerName.endsWith('.md') && !lowerName.endsWith('.py') && !lowerName.endsWith('.json')) continue
32
32
  // 跳过 Python 编译文件
33
33
  if (lowerName.endsWith('.pyc') || lowerName.endsWith('.pyo') || lowerName.endsWith('.pyd')) continue
34
34
  out.push(full)
@@ -59,6 +59,7 @@ async function copyTemplateTree({ srcDir, dstDir }) {
59
59
  const files = await collectTemplateFiles(srcDir)
60
60
  let mdCount = 0
61
61
  let pyCount = 0
62
+ let jsonCount = 0
62
63
 
63
64
  for (const file of files) {
64
65
  const rel = relative(srcDir, file)
@@ -75,10 +76,12 @@ async function copyTemplateTree({ srcDir, dstDir }) {
75
76
  } catch {
76
77
  // 忽略权限设置失败
77
78
  }
79
+ } else if (file.endsWith('.json')) {
80
+ jsonCount++
78
81
  }
79
82
  }
80
83
 
81
- return { total: files.length, md: mdCount, py: pyCount }
84
+ return { total: files.length, md: mdCount, py: pyCount, json: jsonCount }
82
85
  }
83
86
 
84
87
  function resolveTemplateRoot(packageRoot) {
@@ -125,6 +128,14 @@ export async function runOpenCodeInitial(options = {}) {
125
128
  const commandsStats = await copyTemplateTree({ srcDir: srcCommands, dstDir: dstCommands })
126
129
 
127
130
  logger.success(`已初始化 OpenCode 模板到: ${dstRoot}`)
128
- logger.info(`agents: ${agentsStats.md} 个 .md 文件${agentsStats.py > 0 ? ` + ${agentsStats.py} 个 .py 文件` : ''}`)
129
- logger.info(`commands: ${commandsStats.md} 个 .md 文件${commandsStats.py > 0 ? ` + ${commandsStats.py} 个 .py 文件` : ''}`)
131
+ logger.info(
132
+ `agents: ${agentsStats.md} 个 .md 文件` +
133
+ `${agentsStats.py > 0 ? ` + ${agentsStats.py} 个 .py 文件` : ''}` +
134
+ `${agentsStats.json > 0 ? ` + ${agentsStats.json} 个 .json 文件` : ''}`
135
+ )
136
+ logger.info(
137
+ `commands: ${commandsStats.md} 个 .md 文件` +
138
+ `${commandsStats.py > 0 ? ` + ${commandsStats.py} 个 .py 文件` : ''}` +
139
+ `${commandsStats.json > 0 ? ` + ${commandsStats.json} 个 .json 文件` : ''}`
140
+ )
130
141
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {