@nerviq/cli 1.20.0 → 1.21.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/LICENSE +23 -23
- package/README.md +2 -2
- package/bin/cli.js +1 -0
- package/package.json +2 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +6 -2
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +312 -67
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/context.js +10 -4
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/context.js +120 -10
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/windsurf/techniques.js +155 -33
- package/src/workspace.js +375 -375
package/src/opencode/context.js
CHANGED
|
@@ -1,247 +1,247 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenCode Project Context
|
|
3
|
-
*
|
|
4
|
-
* Reads and caches OpenCode-specific project surfaces:
|
|
5
|
-
* - opencode.json / opencode.jsonc (JSONC config)
|
|
6
|
-
* - AGENTS.md (shared with Codex/Copilot)
|
|
7
|
-
* - Permission configuration
|
|
8
|
-
* - Plugin system
|
|
9
|
-
* - 6-level config merge hierarchy
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const os = require('os');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const { spawnSync } = require('child_process');
|
|
16
|
-
const { ProjectContext } = require('../context');
|
|
17
|
-
const { tryParseJsonc, getValueByPath } = require('./config-parser');
|
|
18
|
-
|
|
19
|
-
let opencodeVersionCache = null;
|
|
20
|
-
|
|
21
|
-
function detectOpencodeVersion() {
|
|
22
|
-
if (opencodeVersionCache !== null) {
|
|
23
|
-
return opencodeVersionCache;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const result = spawnSync('opencode', ['--version'], { encoding: 'utf8' });
|
|
28
|
-
const output = `${result.stdout || ''} ${result.stderr || ''}`.trim();
|
|
29
|
-
const match = output.match(/opencode\s+v?([^\s]+)/i);
|
|
30
|
-
opencodeVersionCache = match ? match[1] : (output || null);
|
|
31
|
-
return opencodeVersionCache;
|
|
32
|
-
} catch {
|
|
33
|
-
opencodeVersionCache = null;
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function listDirs(fullPath) {
|
|
39
|
-
try {
|
|
40
|
-
return fs.readdirSync(fullPath, { withFileTypes: true }).filter(entry => entry.isDirectory());
|
|
41
|
-
} catch {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
class OpenCodeProjectContext extends ProjectContext {
|
|
47
|
-
configContent() {
|
|
48
|
-
return this.fileContent('opencode.json') || this.fileContent('opencode.jsonc');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
configFileName() {
|
|
52
|
-
if (this.fileContent('opencode.json')) return 'opencode.json';
|
|
53
|
-
if (this.fileContent('opencode.jsonc')) return 'opencode.jsonc';
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
globalConfigContent() {
|
|
58
|
-
const homeDir = os.homedir();
|
|
59
|
-
const globalPaths = [
|
|
60
|
-
path.join(homeDir, '.config', 'opencode', 'opencode.json'),
|
|
61
|
-
path.join(homeDir, '.config', 'opencode', 'opencode.jsonc'),
|
|
62
|
-
];
|
|
63
|
-
for (const globalPath of globalPaths) {
|
|
64
|
-
try {
|
|
65
|
-
return fs.readFileSync(globalPath, 'utf8');
|
|
66
|
-
} catch {
|
|
67
|
-
// continue
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
agentsMdContent() {
|
|
74
|
-
const direct = this.fileContent('AGENTS.md');
|
|
75
|
-
if (direct) return direct;
|
|
76
|
-
|
|
77
|
-
// OpenCode fallback: CLAUDE.md if no AGENTS.md
|
|
78
|
-
const claudeMd = this.fileContent('CLAUDE.md');
|
|
79
|
-
if (claudeMd) return claudeMd;
|
|
80
|
-
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
agentsMdPath() {
|
|
85
|
-
if (this.fileContent('AGENTS.md')) return 'AGENTS.md';
|
|
86
|
-
if (this.fileContent('CLAUDE.md')) return 'CLAUDE.md';
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
hasAgentsMdAndClaudeMd() {
|
|
91
|
-
return Boolean(this.fileContent('AGENTS.md')) && Boolean(this.fileContent('CLAUDE.md'));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
globalAgentsMdContent() {
|
|
95
|
-
const homeDir = os.homedir();
|
|
96
|
-
const paths = [
|
|
97
|
-
path.join(homeDir, '.config', 'opencode', 'AGENTS.md'),
|
|
98
|
-
path.join(homeDir, '.claude', 'CLAUDE.md'),
|
|
99
|
-
];
|
|
100
|
-
for (const p of paths) {
|
|
101
|
-
try {
|
|
102
|
-
return fs.readFileSync(p, 'utf8');
|
|
103
|
-
} catch {
|
|
104
|
-
// continue
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
configJson() {
|
|
111
|
-
const content = this.configContent();
|
|
112
|
-
if (!content) {
|
|
113
|
-
return { ok: false, data: null, error: 'missing project config', source: this.configFileName() || 'opencode.json' };
|
|
114
|
-
}
|
|
115
|
-
const parsed = tryParseJsonc(content);
|
|
116
|
-
return { ...parsed, source: this.configFileName() };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
globalConfigJson() {
|
|
120
|
-
const content = this.globalConfigContent();
|
|
121
|
-
if (!content) {
|
|
122
|
-
return { ok: false, data: null, error: 'missing global config', source: '~/.config/opencode/opencode.json' };
|
|
123
|
-
}
|
|
124
|
-
const parsed = tryParseJsonc(content);
|
|
125
|
-
return { ...parsed, source: '~/.config/opencode/opencode.json' };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
configValue(dottedPath) {
|
|
129
|
-
// 6-level merge: project wins over global
|
|
130
|
-
const project = this.configJson();
|
|
131
|
-
if (project.ok) {
|
|
132
|
-
const projectValue = getValueByPath(project.data, dottedPath);
|
|
133
|
-
if (projectValue !== undefined) return projectValue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const globalConfig = this.globalConfigJson();
|
|
137
|
-
if (globalConfig.ok) {
|
|
138
|
-
return getValueByPath(globalConfig.data, dottedPath);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
permissions() {
|
|
145
|
-
const config = this.configJson();
|
|
146
|
-
if (!config.ok || !config.data) return {};
|
|
147
|
-
return config.data.permissions || {};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
toolPermissions() {
|
|
151
|
-
const perms = this.permissions();
|
|
152
|
-
return perms.tools || {};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
plugins() {
|
|
156
|
-
const config = this.configJson();
|
|
157
|
-
if (!config.ok || !config.data) return [];
|
|
158
|
-
return config.data.plugins || [];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
pluginFiles() {
|
|
162
|
-
const pluginsDir = path.join(this.dir, '.opencode', 'plugins');
|
|
163
|
-
try {
|
|
164
|
-
return fs.readdirSync(pluginsDir).filter(f => /\.(js|ts|mjs)$/.test(f));
|
|
165
|
-
} catch {
|
|
166
|
-
return [];
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
tuiConfigContent() {
|
|
171
|
-
return this.fileContent('tui.json') || this.fileContent('tui.jsonc') ||
|
|
172
|
-
this.fileContent('.opencode/tui.json') || this.fileContent('.opencode/tui.jsonc');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
tuiConfigJson() {
|
|
176
|
-
const content = this.tuiConfigContent();
|
|
177
|
-
if (!content) return { ok: false, data: null, error: 'missing tui config' };
|
|
178
|
-
return tryParseJsonc(content);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
mcpServers() {
|
|
182
|
-
return this.configValue('mcp') || {};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
customAgents() {
|
|
186
|
-
const config = this.configJson();
|
|
187
|
-
if (!config.ok || !config.data) return {};
|
|
188
|
-
return config.data.agents || {};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
commandDirs() {
|
|
192
|
-
const commandsDir = path.join(this.dir, '.opencode', 'commands');
|
|
193
|
-
return listDirs(commandsDir).map(entry => entry.name);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
commandFiles() {
|
|
197
|
-
const commandsDir = path.join(this.dir, '.opencode', 'commands');
|
|
198
|
-
try {
|
|
199
|
-
return fs.readdirSync(commandsDir).filter(f => /\.(md|yaml|yml)$/.test(f));
|
|
200
|
-
} catch {
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
skillDirs() {
|
|
206
|
-
const skillsDir = path.join(this.dir, '.opencode', 'commands');
|
|
207
|
-
return listDirs(skillsDir).map(entry => entry.name);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
skillMetadata(name) {
|
|
211
|
-
return this.fileContent(path.join('.opencode', 'commands', name, 'SKILL.md'));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
themeFiles() {
|
|
215
|
-
const themesDir = path.join(this.dir, '.opencode', 'themes');
|
|
216
|
-
try {
|
|
217
|
-
return fs.readdirSync(themesDir).filter(f => f.endsWith('.json'));
|
|
218
|
-
} catch {
|
|
219
|
-
return [];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
instructionsArray() {
|
|
224
|
-
return this.configValue('instructions') || [];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
workflowFiles() {
|
|
228
|
-
return this.dirFiles('.github/workflows')
|
|
229
|
-
.filter(file => /\.ya?ml$/i.test(file))
|
|
230
|
-
.map(file => path.join('.github', 'workflows', file).replace(/\\/g, '/'));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
static isOpenCodeRepo(dir) {
|
|
234
|
-
try {
|
|
235
|
-
return fs.existsSync(path.join(dir, 'opencode.json')) ||
|
|
236
|
-
fs.existsSync(path.join(dir, 'opencode.jsonc')) ||
|
|
237
|
-
fs.existsSync(path.join(dir, '.opencode'));
|
|
238
|
-
} catch {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
module.exports = {
|
|
245
|
-
OpenCodeProjectContext,
|
|
246
|
-
detectOpencodeVersion,
|
|
247
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Project Context
|
|
3
|
+
*
|
|
4
|
+
* Reads and caches OpenCode-specific project surfaces:
|
|
5
|
+
* - opencode.json / opencode.jsonc (JSONC config)
|
|
6
|
+
* - AGENTS.md (shared with Codex/Copilot)
|
|
7
|
+
* - Permission configuration
|
|
8
|
+
* - Plugin system
|
|
9
|
+
* - 6-level config merge hierarchy
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawnSync } = require('child_process');
|
|
16
|
+
const { ProjectContext } = require('../context');
|
|
17
|
+
const { tryParseJsonc, getValueByPath } = require('./config-parser');
|
|
18
|
+
|
|
19
|
+
let opencodeVersionCache = null;
|
|
20
|
+
|
|
21
|
+
function detectOpencodeVersion() {
|
|
22
|
+
if (opencodeVersionCache !== null) {
|
|
23
|
+
return opencodeVersionCache;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = spawnSync('opencode', ['--version'], { encoding: 'utf8' });
|
|
28
|
+
const output = `${result.stdout || ''} ${result.stderr || ''}`.trim();
|
|
29
|
+
const match = output.match(/opencode\s+v?([^\s]+)/i);
|
|
30
|
+
opencodeVersionCache = match ? match[1] : (output || null);
|
|
31
|
+
return opencodeVersionCache;
|
|
32
|
+
} catch {
|
|
33
|
+
opencodeVersionCache = null;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function listDirs(fullPath) {
|
|
39
|
+
try {
|
|
40
|
+
return fs.readdirSync(fullPath, { withFileTypes: true }).filter(entry => entry.isDirectory());
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class OpenCodeProjectContext extends ProjectContext {
|
|
47
|
+
configContent() {
|
|
48
|
+
return this.fileContent('opencode.json') || this.fileContent('opencode.jsonc');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
configFileName() {
|
|
52
|
+
if (this.fileContent('opencode.json')) return 'opencode.json';
|
|
53
|
+
if (this.fileContent('opencode.jsonc')) return 'opencode.jsonc';
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
globalConfigContent() {
|
|
58
|
+
const homeDir = os.homedir();
|
|
59
|
+
const globalPaths = [
|
|
60
|
+
path.join(homeDir, '.config', 'opencode', 'opencode.json'),
|
|
61
|
+
path.join(homeDir, '.config', 'opencode', 'opencode.jsonc'),
|
|
62
|
+
];
|
|
63
|
+
for (const globalPath of globalPaths) {
|
|
64
|
+
try {
|
|
65
|
+
return fs.readFileSync(globalPath, 'utf8');
|
|
66
|
+
} catch {
|
|
67
|
+
// continue
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
agentsMdContent() {
|
|
74
|
+
const direct = this.fileContent('AGENTS.md');
|
|
75
|
+
if (direct) return direct;
|
|
76
|
+
|
|
77
|
+
// OpenCode fallback: CLAUDE.md if no AGENTS.md
|
|
78
|
+
const claudeMd = this.fileContent('CLAUDE.md');
|
|
79
|
+
if (claudeMd) return claudeMd;
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
agentsMdPath() {
|
|
85
|
+
if (this.fileContent('AGENTS.md')) return 'AGENTS.md';
|
|
86
|
+
if (this.fileContent('CLAUDE.md')) return 'CLAUDE.md';
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
hasAgentsMdAndClaudeMd() {
|
|
91
|
+
return Boolean(this.fileContent('AGENTS.md')) && Boolean(this.fileContent('CLAUDE.md'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
globalAgentsMdContent() {
|
|
95
|
+
const homeDir = os.homedir();
|
|
96
|
+
const paths = [
|
|
97
|
+
path.join(homeDir, '.config', 'opencode', 'AGENTS.md'),
|
|
98
|
+
path.join(homeDir, '.claude', 'CLAUDE.md'),
|
|
99
|
+
];
|
|
100
|
+
for (const p of paths) {
|
|
101
|
+
try {
|
|
102
|
+
return fs.readFileSync(p, 'utf8');
|
|
103
|
+
} catch {
|
|
104
|
+
// continue
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
configJson() {
|
|
111
|
+
const content = this.configContent();
|
|
112
|
+
if (!content) {
|
|
113
|
+
return { ok: false, data: null, error: 'missing project config', source: this.configFileName() || 'opencode.json' };
|
|
114
|
+
}
|
|
115
|
+
const parsed = tryParseJsonc(content);
|
|
116
|
+
return { ...parsed, source: this.configFileName() };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
globalConfigJson() {
|
|
120
|
+
const content = this.globalConfigContent();
|
|
121
|
+
if (!content) {
|
|
122
|
+
return { ok: false, data: null, error: 'missing global config', source: '~/.config/opencode/opencode.json' };
|
|
123
|
+
}
|
|
124
|
+
const parsed = tryParseJsonc(content);
|
|
125
|
+
return { ...parsed, source: '~/.config/opencode/opencode.json' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
configValue(dottedPath) {
|
|
129
|
+
// 6-level merge: project wins over global
|
|
130
|
+
const project = this.configJson();
|
|
131
|
+
if (project.ok) {
|
|
132
|
+
const projectValue = getValueByPath(project.data, dottedPath);
|
|
133
|
+
if (projectValue !== undefined) return projectValue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const globalConfig = this.globalConfigJson();
|
|
137
|
+
if (globalConfig.ok) {
|
|
138
|
+
return getValueByPath(globalConfig.data, dottedPath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
permissions() {
|
|
145
|
+
const config = this.configJson();
|
|
146
|
+
if (!config.ok || !config.data) return {};
|
|
147
|
+
return config.data.permissions || {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
toolPermissions() {
|
|
151
|
+
const perms = this.permissions();
|
|
152
|
+
return perms.tools || {};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
plugins() {
|
|
156
|
+
const config = this.configJson();
|
|
157
|
+
if (!config.ok || !config.data) return [];
|
|
158
|
+
return config.data.plugins || [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pluginFiles() {
|
|
162
|
+
const pluginsDir = path.join(this.dir, '.opencode', 'plugins');
|
|
163
|
+
try {
|
|
164
|
+
return fs.readdirSync(pluginsDir).filter(f => /\.(js|ts|mjs)$/.test(f));
|
|
165
|
+
} catch {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
tuiConfigContent() {
|
|
171
|
+
return this.fileContent('tui.json') || this.fileContent('tui.jsonc') ||
|
|
172
|
+
this.fileContent('.opencode/tui.json') || this.fileContent('.opencode/tui.jsonc');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
tuiConfigJson() {
|
|
176
|
+
const content = this.tuiConfigContent();
|
|
177
|
+
if (!content) return { ok: false, data: null, error: 'missing tui config' };
|
|
178
|
+
return tryParseJsonc(content);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
mcpServers() {
|
|
182
|
+
return this.configValue('mcp') || {};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
customAgents() {
|
|
186
|
+
const config = this.configJson();
|
|
187
|
+
if (!config.ok || !config.data) return {};
|
|
188
|
+
return config.data.agents || {};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
commandDirs() {
|
|
192
|
+
const commandsDir = path.join(this.dir, '.opencode', 'commands');
|
|
193
|
+
return listDirs(commandsDir).map(entry => entry.name);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
commandFiles() {
|
|
197
|
+
const commandsDir = path.join(this.dir, '.opencode', 'commands');
|
|
198
|
+
try {
|
|
199
|
+
return fs.readdirSync(commandsDir).filter(f => /\.(md|yaml|yml)$/.test(f));
|
|
200
|
+
} catch {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
skillDirs() {
|
|
206
|
+
const skillsDir = path.join(this.dir, '.opencode', 'commands');
|
|
207
|
+
return listDirs(skillsDir).map(entry => entry.name);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
skillMetadata(name) {
|
|
211
|
+
return this.fileContent(path.join('.opencode', 'commands', name, 'SKILL.md'));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
themeFiles() {
|
|
215
|
+
const themesDir = path.join(this.dir, '.opencode', 'themes');
|
|
216
|
+
try {
|
|
217
|
+
return fs.readdirSync(themesDir).filter(f => f.endsWith('.json'));
|
|
218
|
+
} catch {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
instructionsArray() {
|
|
224
|
+
return this.configValue('instructions') || [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
workflowFiles() {
|
|
228
|
+
return this.dirFiles('.github/workflows')
|
|
229
|
+
.filter(file => /\.ya?ml$/i.test(file))
|
|
230
|
+
.map(file => path.join('.github', 'workflows', file).replace(/\\/g, '/'));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static isOpenCodeRepo(dir) {
|
|
234
|
+
try {
|
|
235
|
+
return fs.existsSync(path.join(dir, 'opencode.json')) ||
|
|
236
|
+
fs.existsSync(path.join(dir, 'opencode.jsonc')) ||
|
|
237
|
+
fs.existsSync(path.join(dir, '.opencode'));
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
OpenCodeProjectContext,
|
|
246
|
+
detectOpencodeVersion,
|
|
247
|
+
};
|