@osovv/vv-opencode 1.4.5 → 1.6.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/CHANGELOG.md +23 -0
- package/README.md +47 -16
- package/dist/commands/patch-provider.d.ts +5 -5
- package/dist/commands/patch-provider.js +10 -6
- package/dist/commands/patch-provider.js.map +1 -1
- package/dist/lib/agent-tool-catalog.d.ts +144 -0
- package/dist/lib/agent-tool-catalog.js +2551 -0
- package/dist/lib/agent-tool-catalog.js.map +1 -0
- package/dist/lib/agent-tool-contract.d.ts +200 -0
- package/dist/lib/agent-tool-contract.js +407 -0
- package/dist/lib/agent-tool-contract.js.map +1 -0
- package/dist/lib/opencode/agent-registrations.d.ts +33 -0
- package/dist/lib/opencode/agent-registrations.js +469 -0
- package/dist/lib/opencode/agent-registrations.js.map +1 -0
- package/dist/lib/opencode/inspection.d.ts +84 -0
- package/dist/lib/opencode/inspection.js +367 -0
- package/dist/lib/opencode/inspection.js.map +1 -0
- package/dist/lib/opencode/model-overrides.d.ts +19 -0
- package/dist/lib/opencode/model-overrides.js +221 -0
- package/dist/lib/opencode/model-overrides.js.map +1 -0
- package/dist/lib/opencode/paths.d.ts +21 -0
- package/dist/lib/opencode/paths.js +62 -0
- package/dist/lib/opencode/paths.js.map +1 -0
- package/dist/lib/opencode/plugin-registration.d.ts +17 -0
- package/dist/lib/opencode/plugin-registration.js +210 -0
- package/dist/lib/opencode/plugin-registration.js.map +1 -0
- package/dist/lib/opencode/shared-utils.d.ts +30 -0
- package/dist/lib/opencode/shared-utils.js +183 -0
- package/dist/lib/opencode/shared-utils.js.map +1 -0
- package/dist/lib/opencode/vvoc-config-io.d.ts +9 -0
- package/dist/lib/opencode/vvoc-config-io.js +58 -0
- package/dist/lib/opencode/vvoc-config-io.js.map +1 -0
- package/dist/lib/opencode.d.ts +15 -174
- package/dist/lib/opencode.js +19 -1387
- package/dist/lib/opencode.js.map +1 -1
- package/dist/lib/spec-lint/cross-file-rules.d.ts +2 -0
- package/dist/lib/spec-lint/cross-file-rules.js +33 -0
- package/dist/lib/spec-lint/cross-file-rules.js.map +1 -0
- package/dist/lib/spec-lint/delegated-extraction.d.ts +8 -0
- package/dist/lib/spec-lint/delegated-extraction.js +112 -0
- package/dist/lib/spec-lint/delegated-extraction.js.map +1 -0
- package/dist/lib/spec-lint/lint.d.ts +7 -0
- package/dist/lib/spec-lint/lint.js +92 -0
- package/dist/lib/spec-lint/lint.js.map +1 -0
- package/dist/lib/spec-lint/parser.d.ts +101 -0
- package/dist/lib/spec-lint/parser.js +482 -0
- package/dist/lib/spec-lint/parser.js.map +1 -0
- package/dist/lib/spec-lint/plan-rules.d.ts +38 -0
- package/dist/lib/spec-lint/plan-rules.js +745 -0
- package/dist/lib/spec-lint/plan-rules.js.map +1 -0
- package/dist/lib/spec-lint/spec-rules.d.ts +8 -0
- package/dist/lib/spec-lint/spec-rules.js +188 -0
- package/dist/lib/spec-lint/spec-rules.js.map +1 -0
- package/dist/lib/spec-lint.d.ts +4 -63
- package/dist/lib/spec-lint.js +13 -1505
- package/dist/lib/spec-lint.js.map +1 -1
- package/dist/lib/vvoc-preset-registry.d.ts +7 -7
- package/dist/lib/vvoc-preset-registry.js +8 -8
- package/dist/lib/vvoc-preset-registry.js.map +1 -1
- package/dist/lib/workflow-contract.d.ts +12 -0
- package/dist/lib/workflow-contract.js +71 -18
- package/dist/lib/workflow-contract.js.map +1 -1
- package/dist/plugins/hashline-edit/index.js +102 -97
- package/dist/plugins/hashline-edit/index.js.map +1 -1
- package/dist/plugins/hashline-edit/normalize-edits.d.ts +2 -8
- package/dist/plugins/hashline-edit/normalize-edits.js +32 -17
- package/dist/plugins/hashline-edit/normalize-edits.js.map +1 -1
- package/dist/plugins/hashline-edit/schemas.d.ts +246 -0
- package/dist/plugins/hashline-edit/schemas.js +659 -0
- package/dist/plugins/hashline-edit/schemas.js.map +1 -0
- package/dist/plugins/hashline-edit/str-replace-editor.d.ts +6 -17
- package/dist/plugins/hashline-edit/str-replace-editor.js +19 -21
- package/dist/plugins/hashline-edit/str-replace-editor.js.map +1 -1
- package/dist/plugins/hashline-edit/tool-description.d.ts +1 -0
- package/dist/plugins/hashline-edit/tool-description.js +20 -4
- package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
- package/dist/plugins/web-tools/fetch-service.d.ts +8 -5
- package/dist/plugins/web-tools/fetch-service.js +30 -45
- package/dist/plugins/web-tools/fetch-service.js.map +1 -1
- package/dist/plugins/web-tools/index.js +23 -5
- package/dist/plugins/web-tools/index.js.map +1 -1
- package/dist/plugins/web-tools/schemas.d.ts +469 -0
- package/dist/plugins/web-tools/schemas.js +523 -0
- package/dist/plugins/web-tools/schemas.js.map +1 -0
- package/dist/plugins/web-tools/search-service.d.ts +6 -3
- package/dist/plugins/web-tools/search-service.js +24 -29
- package/dist/plugins/web-tools/search-service.js.map +1 -1
- package/dist/plugins/workflow/authority.d.ts +6 -0
- package/dist/plugins/workflow/authority.js +45 -10
- package/dist/plugins/workflow/authority.js.map +1 -1
- package/dist/plugins/workflow/authorization.d.ts +30 -0
- package/dist/plugins/workflow/authorization.js +147 -0
- package/dist/plugins/workflow/authorization.js.map +1 -0
- package/dist/plugins/workflow/checkpoints.d.ts +41 -2
- package/dist/plugins/workflow/checkpoints.js +176 -47
- package/dist/plugins/workflow/checkpoints.js.map +1 -1
- package/dist/plugins/workflow/delegated.d.ts +15 -1
- package/dist/plugins/workflow/delegated.js +65 -35
- package/dist/plugins/workflow/delegated.js.map +1 -1
- package/dist/plugins/workflow/execution.d.ts +15 -30
- package/dist/plugins/workflow/execution.js +133 -33
- package/dist/plugins/workflow/execution.js.map +1 -1
- package/dist/plugins/workflow/index.js +197 -435
- package/dist/plugins/workflow/index.js.map +1 -1
- package/dist/plugins/workflow/input-validation.d.ts +48 -0
- package/dist/plugins/workflow/input-validation.js +536 -0
- package/dist/plugins/workflow/input-validation.js.map +1 -0
- package/dist/plugins/workflow/inspection.d.ts +60 -0
- package/dist/plugins/workflow/inspection.js +316 -0
- package/dist/plugins/workflow/inspection.js.map +1 -0
- package/dist/plugins/workflow/protocol.d.ts +2 -0
- package/dist/plugins/workflow/protocol.js +26 -4
- package/dist/plugins/workflow/protocol.js.map +1 -1
- package/dist/plugins/workflow/recovery.d.ts +18 -0
- package/dist/plugins/workflow/recovery.js +217 -0
- package/dist/plugins/workflow/recovery.js.map +1 -0
- package/dist/plugins/workflow/repair.d.ts +1 -1
- package/dist/plugins/workflow/repair.js +9 -10
- package/dist/plugins/workflow/repair.js.map +1 -1
- package/dist/plugins/workflow/results.d.ts +2566 -0
- package/dist/plugins/workflow/results.js +1111 -0
- package/dist/plugins/workflow/results.js.map +1 -0
- package/dist/plugins/workflow/schemas.d.ts +398 -0
- package/dist/plugins/workflow/schemas.js +399 -0
- package/dist/plugins/workflow/schemas.js.map +1 -0
- package/dist/plugins/workflow/state.d.ts +3 -2
- package/dist/plugins/workflow/state.js +15 -8
- package/dist/plugins/workflow/state.js.map +1 -1
- package/dist/plugins/workflow/system-instruction.md +17 -1
- package/dist/plugins/workflow/tooling.d.ts +11 -82
- package/dist/plugins/workflow/tooling.js +478 -461
- package/dist/plugins/workflow/tooling.js.map +1 -1
- package/dist/plugins/workflow/transactions.d.ts +13 -0
- package/dist/plugins/workflow/transactions.js +13 -3
- package/dist/plugins/workflow/transactions.js.map +1 -1
- package/package.json +6 -3
- package/schemas/vvoc/v3.json +1 -1
- package/templates/agents/vv-code-reviewer.md +13 -4
- package/templates/agents/vv-implementer.md +16 -9
- package/templates/agents/vv-spec-reviewer.md +13 -4
- package/templates/skills/vv-execute/SKILL.md +3 -3
- package/templates/skills/vv-execute/references/tool-contracts.md +1750 -0
- package/templates/skills/vv-review/SKILL.md +1 -1
package/dist/lib/opencode.js
CHANGED
|
@@ -1,1399 +1,31 @@
|
|
|
1
1
|
// FILE: src/lib/opencode.ts
|
|
2
|
-
// VERSION: 1.
|
|
2
|
+
// VERSION: 1.5.0
|
|
3
3
|
// START_MODULE_CONTRACT
|
|
4
|
-
// PURPOSE:
|
|
5
|
-
// SCOPE:
|
|
6
|
-
// DEPENDS: [
|
|
4
|
+
// PURPOSE: Public re-export barrel for OpenCode runtime/TUI config mutation, host compatibility diagnostics, provider patching, and scoped vvoc.json config files.
|
|
5
|
+
// SCOPE: Re-export the complete public API over the concern-scoped zone modules in src/lib/opencode/ (shared-utils, paths, plugin-registration, agent-registrations, model-overrides, vvoc-config-io, inspection) so every ../lib/opencode.js and ./opencode.js import keeps resolving unchanged under moduleResolution NodeNext, where directory imports are not available.
|
|
6
|
+
// DEPENDS: [src/lib/package.ts, src/lib/vvoc-config.ts, src/lib/opencode/shared-utils.ts, src/lib/opencode/paths.ts, src/lib/opencode/plugin-registration.ts, src/lib/opencode/agent-registrations.ts, src/lib/opencode/model-overrides.ts, src/lib/opencode/vvoc-config-io.ts, src/lib/opencode/inspection.ts]
|
|
7
7
|
// LINKS: [M-CLI-CONFIG, M-ORCHESTRATION-PROFILES]
|
|
8
|
-
// ROLE:
|
|
9
|
-
// MAP_MODE:
|
|
8
|
+
// ROLE: BARREL
|
|
9
|
+
// MAP_MODE: SUMMARY
|
|
10
10
|
// END_MODULE_CONTRACT
|
|
11
11
|
//
|
|
12
12
|
// START_MODULE_MAP
|
|
13
|
-
//
|
|
14
|
-
// PACKAGE_NAME -
|
|
15
|
-
//
|
|
16
|
-
// OPENCODE_TUI_SCHEMA_URL - OpenCode TUI config schema URL.
|
|
17
|
-
// TUI_PACKAGE_SPECIFIER - Pinned base package specifier registered in tui.json(c) so OpenCode selects its ./tui export.
|
|
18
|
-
// MINIMUM_TUI_OPENCODE_VERSION - Minimum OpenCode host version supported by the managed TUI plugin.
|
|
19
|
-
// OpenCodeDefaultModelKey - Supported top-level OpenCode default model fields.
|
|
20
|
-
// Scope - Supported installation scopes for vvoc config writes.
|
|
21
|
-
// ResolvedPaths - Scope-aware path bundle for OpenCode runtime/TUI and vvoc config locations.
|
|
22
|
-
// WriteResult - Result shape returned by managed config write operations.
|
|
23
|
-
// TuiPluginEntry - Supported TUI plugin string or tuple entry.
|
|
24
|
-
// OpenCodeRuntimeInspection - Installed OpenCode version and TUI compatibility snapshot.
|
|
25
|
-
// InstallationInspection - Current OpenCode runtime/TUI and vvoc installation status snapshot.
|
|
26
|
-
// resolvePaths - Resolves OpenCode runtime/TUI and vvoc config paths for global/project scopes.
|
|
27
|
-
// ensurePackageConfigText - Ensures OpenCode config contains the pinned vvoc plugin specifier.
|
|
28
|
-
// ensureTuiPackageConfigText - Ensures TUI config contains the pinned vvoc base package while preserving tuple options.
|
|
29
|
-
// readOpenCodeDefaultModel - Reads a top-level OpenCode model or small_model override.
|
|
30
|
-
// writeOpenCodeDefaultModel - Writes or removes a top-level OpenCode model or small_model override.
|
|
31
|
-
// writeOpenCodeProviderObject - Writes or merges a provider.<id> object override.
|
|
32
|
-
// ensureProviderBaseUrlConfigText - Ensures OpenCode config contains the requested provider options.baseURL override.
|
|
33
|
-
// ensureManagedAgentRegistrationsConfigText - Ensures OpenCode config contains the vvoc-managed default agent, agent registrations, and tool gating.
|
|
34
|
-
// readVvocConfig - Loads the canonical vvoc.json document when present.
|
|
35
|
-
// ensurePackageInstalled - Writes the pinned vvoc plugin specifier into OpenCode config.
|
|
36
|
-
// ensureTuiPackageInstalled - Writes the pinned vvoc base package into dedicated tui.json(c).
|
|
37
|
-
// installVvocConfig - Creates or refreshes the canonical vvoc.json document.
|
|
38
|
-
// syncVvocConfig - Rewrites the canonical vvoc.json document while preserving valid current values.
|
|
39
|
-
// writeProviderBaseUrl - Writes a provider options.baseURL override into OpenCode config.
|
|
40
|
-
// syncManagedAgentRegistrations - Syncs the canonical vvoc-managed OpenCode agent registrations and tool gating into OpenCode config.
|
|
41
|
-
// installManagedAgentPrompts - Creates managed vvoc prompt files for the bundled Guardian and managed OpenCode agents when missing.
|
|
42
|
-
// syncManagedAgentPrompts - Rewrites managed vvoc prompt files for the bundled Guardian and managed OpenCode agents.
|
|
43
|
-
// installManagedSkillFiles - Creates managed vvoc skill files from bundled templates.
|
|
44
|
-
// syncManagedSkillFiles - Rewrites managed vvoc skill files from bundled templates.
|
|
45
|
-
// ensureManagedSkillSymlink - Creates symlink from OpenCode skills dir to vvoc skills dir for skill discovery.
|
|
46
|
-
// readOpenCodeAgentModel - Reads a model override for any OpenCode agent from config.
|
|
47
|
-
// readOpenCodeAgentOverride - Reads a model override for any OpenCode agent from config.
|
|
48
|
-
// writeOpenCodeAgentModel - Writes or removes a model override for any OpenCode agent in config.
|
|
49
|
-
// readManagedAgentModels - Reads model overrides for the bundled vvoc-managed OpenCode agents from OpenCode config.
|
|
50
|
-
// readManagedAgentOverrides - Reads model overrides for the bundled vvoc-managed OpenCode agents.
|
|
51
|
-
// writeManagedAgentModel - Writes or removes a bundled vvoc-managed OpenCode agent model override in OpenCode config.
|
|
52
|
-
// writeGuardianConfig - Writes the guardian section into the canonical vvoc.json document.
|
|
53
|
-
// inspectOpenCodeRuntime - Reads the installed OpenCode version and evaluates TUI compatibility.
|
|
54
|
-
// extractOpenCodeVersion - Extracts the first semantic version found in `opencode --version` output.
|
|
55
|
-
// isTuiOpenCodeVersionCompatible - Compares an OpenCode version with the managed TUI minimum.
|
|
56
|
-
// inspectInstallation - Reads current OpenCode/vvoc installation state for status and doctor commands.
|
|
57
|
-
// inspectInstallationForScope - Reads installation state using strict/effective layered source resolution.
|
|
58
|
-
// describeWriteResult - Formats config write outcomes for CLI output.
|
|
59
|
-
// ManagedAgentModelMap - Map of managed agent names to model selections.
|
|
60
|
-
// OpenCodeAgentOverride - Agent override config for OpenCode.
|
|
61
|
-
// ManagedAgentOverrideMap - Map of agent override configs.
|
|
62
|
-
// parseGuardianConfigText - Parse guardian section JSON.
|
|
63
|
-
// renderGuardianConfig - Render guardian section JSON.
|
|
64
|
-
// GuardianConfigOverrides - Guardian config override type.
|
|
13
|
+
// resolvePaths, ensurePackageConfigText, ensureTuiPackageConfigText, ensureTuiPackageInstalled, ensureManagedAgentRegistrationsConfigText, syncManagedAgentRegistrations, installManagedAgentPrompts, syncManagedAgentPrompts, installManagedSkillFiles, syncManagedSkillFiles, ensureManagedSkillSymlink, readManagedAgentModels, readManagedAgentOverrides, readOpenCodeAgentModel, readOpenCodeAgentOverride, readOpenCodeDefaultModel, writeOpenCodeAgentModel, writeOpenCodeDefaultModel, writeOpenCodeProviderObject, writeManagedAgentModel, ensurePackageInstalled, ensureProviderBaseUrlConfigText, writeProviderBaseUrl, readVvocConfig, installVvocConfig, syncVvocConfig, writeGuardianConfig, inspectOpenCodeRuntime, isTuiOpenCodeVersionCompatible, extractOpenCodeVersion, inspectInstallation, inspectInstallationForScope, describeWriteResult - Public functions of the former monolith, now owned by zone modules.
|
|
14
|
+
// CLI_NAME, PACKAGE_NAME, OPENCODE_SCHEMA_URL, OPENCODE_TUI_SCHEMA_URL, TUI_PACKAGE_SPECIFIER, MINIMUM_TUI_OPENCODE_VERSION - Public constants owned by zone modules and package.ts.
|
|
15
|
+
// Scope, ResolvedPaths, WriteResult, TuiPluginEntry, OpenCodeDefaultModelKey, ManagedAgentModelMap, OpenCodeAgentOverride, ManagedAgentOverrideMap, OpenCodeRuntimeInspection, InstallationInspection, GuardianConfigOverrides - Public types owned by zone modules and vvoc-config.ts.
|
|
65
16
|
// END_MODULE_MAP
|
|
66
17
|
//
|
|
67
18
|
// START_CHANGE_SUMMARY
|
|
68
|
-
// LAST_CHANGE: [C-
|
|
19
|
+
// LAST_CHANGE: [C-OPENCODE-MODULE-SPLIT-R1 - Replaced the 2045-line monolith body with a re-export-only barrel over concern-scoped zone modules in src/lib/opencode/; the public API and every consumer import are unchanged.]
|
|
69
20
|
// END_CHANGE_SUMMARY
|
|
70
|
-
|
|
71
|
-
import { lstat, mkdir, readFile, readlink, symlink, unlink, writeFile } from "node:fs/promises";
|
|
72
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
73
|
-
import { MANAGED_AGENT_PROMPT_NAMES, MANAGED_OPENCODE_AGENTS, getManagedAgentPromptPath, getManagedOpenCodeAgentDefinition, loadManagedAgentPromptTemplate, } from "./managed-agents.js";
|
|
74
|
-
import { MANAGED_SKILL_NAMES, getManagedSkillFilePath, listManagedSkillReferenceNames, loadManagedSkillReference, loadManagedSkillTemplate, } from "./managed-skills.js";
|
|
75
|
-
import { BUILTIN_ROLE_NAMES, getBuiltInRoleBindings, ROLE_REFERENCE_PREFIX, } from "./model-roles.js";
|
|
76
|
-
import { resolveOrchestrationPolicy } from "./orchestration.js";
|
|
77
|
-
import { createDefaultVvocConfig, createGuardianConfig, parseVvocConfigText, parseVersionedVvocConfigText, renderVvocConfig, } from "./vvoc-config.js";
|
|
78
|
-
import { getPinnedPackageSpecifier, PACKAGE_NAME, PACKAGE_VERSION } from "./package.js";
|
|
79
|
-
import { resolveConfigWriteTargets, resolveOpenCodeConfigSource, resolveOpenCodeTuiConfigSource, resolveVvocConfigSource, } from "./config-layers.js";
|
|
80
|
-
import { materializeHashlineEditEntry, materializeToolHistoryCompactionEntry, materializePeakHoursEntry, materializeSpecGuardEntry, } from "./plugin-toggle-config.js";
|
|
81
|
-
import { getConfigHome, getGlobalOpencodeSkillsDir, getGlobalVvocDir, getVvocAgentsDir, getVvocSkillsDir, } from "./vvoc-paths.js";
|
|
82
|
-
export const CLI_NAME = "vvoc";
|
|
83
|
-
export { PACKAGE_NAME };
|
|
84
|
-
export const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
85
|
-
export const OPENCODE_TUI_SCHEMA_URL = "https://opencode.ai/tui.json";
|
|
86
|
-
export const TUI_PACKAGE_SPECIFIER = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
|
|
87
|
-
export const MINIMUM_TUI_OPENCODE_VERSION = "1.18.2";
|
|
88
|
-
const MANAGED_MARKER = "Managed by vvoc";
|
|
89
|
-
const OPENCODE_CONFIG_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
90
|
-
const OPENCODE_TUI_CONFIG_FILE_NAMES = ["tui.json", "tui.jsonc"];
|
|
91
|
-
const MANAGED_DEFAULT_AGENT = "vv-controller";
|
|
92
|
-
const JSON_FORMAT = {
|
|
93
|
-
insertSpaces: true,
|
|
94
|
-
tabSize: 2,
|
|
95
|
-
eol: "\n",
|
|
96
|
-
};
|
|
97
|
-
// START_BLOCK_RESOLVE_CONFIG_PATHS
|
|
98
|
-
export async function resolvePaths(options) {
|
|
99
|
-
const targets = await resolveConfigWriteTargets(options);
|
|
100
|
-
const configHome = getConfigHome(options.configDir);
|
|
101
|
-
const managedAgentsDirPath = getVvocAgentsDir(targets.vvocBaseDir);
|
|
102
|
-
const managedSkillsDirPath = getVvocSkillsDir(targets.vvocBaseDir);
|
|
103
|
-
return {
|
|
104
|
-
scope: options.scope,
|
|
105
|
-
cwd: options.cwd,
|
|
106
|
-
configHome,
|
|
107
|
-
projectRoot: targets.projectRoot,
|
|
108
|
-
opencodeBaseDir: targets.opencodeBaseDir,
|
|
109
|
-
vvocBaseDir: targets.vvocBaseDir,
|
|
110
|
-
vvocConfigPath: targets.vvocConfigPath,
|
|
111
|
-
managedAgentsDirPath,
|
|
112
|
-
managedSkillsDirPath,
|
|
113
|
-
opencodeConfigPath: targets.opencodeConfigPath,
|
|
114
|
-
opencodeAlternatePaths: await resolveOpenCodeAlternates(targets.opencodeBaseDir, targets.opencodeConfigPath),
|
|
115
|
-
opencodeTuiConfigPath: targets.opencodeTuiConfigPath,
|
|
116
|
-
opencodeTuiAlternatePaths: await resolveConfigAlternates(targets.opencodeBaseDir, targets.opencodeTuiConfigPath, OPENCODE_TUI_CONFIG_FILE_NAMES),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
// END_BLOCK_RESOLVE_CONFIG_PATHS
|
|
120
|
-
// START_BLOCK_ENSURE_OPENCODE_PLUGIN_CONFIG
|
|
121
|
-
export function ensurePackageConfigText(text, packageSpecifier = PACKAGE_NAME) {
|
|
122
|
-
if (!text?.trim()) {
|
|
123
|
-
return renderJson({
|
|
124
|
-
$schema: OPENCODE_SCHEMA_URL,
|
|
125
|
-
plugin: [packageSpecifier],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
const document = parseObjectDocument(text, "OpenCode config");
|
|
129
|
-
const currentPlugins = readPluginList(document, "OpenCode config");
|
|
130
|
-
let nextText = text;
|
|
131
|
-
if (!Object.hasOwn(document, "$schema")) {
|
|
132
|
-
nextText = applyEdits(nextText, modify(nextText, ["$schema"], OPENCODE_SCHEMA_URL, {
|
|
133
|
-
formattingOptions: JSON_FORMAT,
|
|
134
|
-
getInsertionIndex: () => 0,
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
const nextPlugins = normalizePluginList(currentPlugins, packageSpecifier);
|
|
138
|
-
if (JSON.stringify(nextPlugins) !== JSON.stringify(currentPlugins)) {
|
|
139
|
-
nextText = applyEdits(nextText, modify(nextText, ["plugin"], nextPlugins, {
|
|
140
|
-
formattingOptions: JSON_FORMAT,
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
144
|
-
}
|
|
145
|
-
// END_BLOCK_ENSURE_OPENCODE_PLUGIN_CONFIG
|
|
146
|
-
// START_BLOCK_ENSURE_TUI_PLUGIN_CONFIG
|
|
147
|
-
export function ensureTuiPackageConfigText(text, packageSpecifier = TUI_PACKAGE_SPECIFIER) {
|
|
148
|
-
if (!text?.trim()) {
|
|
149
|
-
return renderJson({
|
|
150
|
-
$schema: OPENCODE_TUI_SCHEMA_URL,
|
|
151
|
-
plugin: [packageSpecifier],
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
const document = parseObjectDocument(text, "OpenCode TUI config");
|
|
155
|
-
const currentPlugins = readTuiPluginList(document, "OpenCode TUI config");
|
|
156
|
-
let nextText = text;
|
|
157
|
-
if (!Object.hasOwn(document, "$schema")) {
|
|
158
|
-
nextText = applyEdits(nextText, modify(nextText, ["$schema"], OPENCODE_TUI_SCHEMA_URL, {
|
|
159
|
-
formattingOptions: JSON_FORMAT,
|
|
160
|
-
getInsertionIndex: () => 0,
|
|
161
|
-
}));
|
|
162
|
-
}
|
|
163
|
-
const nextPlugins = normalizeTuiPluginList(currentPlugins, packageSpecifier);
|
|
164
|
-
if (JSON.stringify(nextPlugins) !== JSON.stringify(currentPlugins)) {
|
|
165
|
-
nextText = applyEdits(nextText, modify(nextText, ["plugin"], nextPlugins, {
|
|
166
|
-
formattingOptions: JSON_FORMAT,
|
|
167
|
-
}));
|
|
168
|
-
}
|
|
169
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
170
|
-
}
|
|
171
|
-
export async function ensureTuiPackageInstalled(paths) {
|
|
172
|
-
const currentText = await readOptionalText(paths.opencodeTuiConfigPath);
|
|
173
|
-
const nextText = ensureTuiPackageConfigText(currentText);
|
|
174
|
-
if ((currentText ?? "") === nextText) {
|
|
175
|
-
return { action: "kept", path: paths.opencodeTuiConfigPath };
|
|
176
|
-
}
|
|
177
|
-
await writeText(paths.opencodeTuiConfigPath, nextText);
|
|
178
|
-
return {
|
|
179
|
-
action: currentText ? "updated" : "created",
|
|
180
|
-
path: paths.opencodeTuiConfigPath,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
// END_BLOCK_ENSURE_TUI_PLUGIN_CONFIG
|
|
184
|
-
// START_BLOCK_ENSURE_MANAGED_AGENT_CONFIG
|
|
185
|
-
export function ensureManagedAgentRegistrationsConfigText(text, paths) {
|
|
186
|
-
const builtInRoleBindings = getBuiltInRoleBindings();
|
|
187
|
-
const rootRoleRefs = {
|
|
188
|
-
model: createRoleReference(builtInRoleBindings.opencodeDefaults.model),
|
|
189
|
-
small_model: createRoleReference(builtInRoleBindings.opencodeDefaults.smallModel),
|
|
190
|
-
};
|
|
191
|
-
const builtInAgentModelRefs = {
|
|
192
|
-
explore: createRoleReference(builtInRoleBindings.opencodeAgents.explore),
|
|
193
|
-
};
|
|
194
|
-
if (!text?.trim()) {
|
|
195
|
-
const managedRegistrations = Object.fromEntries(MANAGED_OPENCODE_AGENTS.map((definition) => [
|
|
196
|
-
definition.name,
|
|
197
|
-
getManagedOpenCodeAgentRegistration(paths, definition.name),
|
|
198
|
-
]));
|
|
199
|
-
return renderJson({
|
|
200
|
-
$schema: OPENCODE_SCHEMA_URL,
|
|
201
|
-
model: rootRoleRefs.model,
|
|
202
|
-
small_model: rootRoleRefs.small_model,
|
|
203
|
-
default_agent: MANAGED_DEFAULT_AGENT,
|
|
204
|
-
tools: {
|
|
205
|
-
apply_patch: false,
|
|
206
|
-
},
|
|
207
|
-
agent: {
|
|
208
|
-
...Object.fromEntries(Object.entries(builtInAgentModelRefs).map(([name, model]) => [name, { model }])),
|
|
209
|
-
...managedRegistrations,
|
|
210
|
-
},
|
|
211
|
-
skills: {
|
|
212
|
-
paths: [getManagedSkillsPathReference(paths)],
|
|
213
|
-
},
|
|
214
|
-
command: {},
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
const document = parseObjectDocument(text, "OpenCode config");
|
|
218
|
-
const currentAgents = readAgentMap(document, "OpenCode config");
|
|
219
|
-
const currentTools = readOptionalObject(document, "tools", "OpenCode config");
|
|
220
|
-
let nextText = text;
|
|
221
|
-
if (!Object.hasOwn(document, "$schema")) {
|
|
222
|
-
nextText = applyEdits(nextText, modify(nextText, ["$schema"], OPENCODE_SCHEMA_URL, {
|
|
223
|
-
formattingOptions: JSON_FORMAT,
|
|
224
|
-
getInsertionIndex: () => 0,
|
|
225
|
-
}));
|
|
226
|
-
}
|
|
227
|
-
if (document.model !== rootRoleRefs.model) {
|
|
228
|
-
nextText = applyEdits(nextText, modify(nextText, ["model"], rootRoleRefs.model, {
|
|
229
|
-
formattingOptions: JSON_FORMAT,
|
|
230
|
-
}));
|
|
231
|
-
}
|
|
232
|
-
if (document.small_model !== rootRoleRefs.small_model) {
|
|
233
|
-
nextText = applyEdits(nextText, modify(nextText, ["small_model"], rootRoleRefs.small_model, {
|
|
234
|
-
formattingOptions: JSON_FORMAT,
|
|
235
|
-
}));
|
|
236
|
-
}
|
|
237
|
-
if (document.default_agent !== MANAGED_DEFAULT_AGENT) {
|
|
238
|
-
nextText = applyEdits(nextText, modify(nextText, ["default_agent"], MANAGED_DEFAULT_AGENT, {
|
|
239
|
-
formattingOptions: JSON_FORMAT,
|
|
240
|
-
}));
|
|
241
|
-
}
|
|
242
|
-
if (currentTools?.apply_patch !== false) {
|
|
243
|
-
nextText = applyEdits(nextText, modify(nextText, ["tools", "apply_patch"], false, {
|
|
244
|
-
formattingOptions: JSON_FORMAT,
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
for (const [agentName, modelRef] of Object.entries(builtInAgentModelRefs)) {
|
|
248
|
-
const currentEntry = currentAgents[agentName];
|
|
249
|
-
if (!currentEntry) {
|
|
250
|
-
nextText = applyEdits(nextText, modify(nextText, ["agent", agentName], { model: modelRef }, {
|
|
251
|
-
formattingOptions: JSON_FORMAT,
|
|
252
|
-
}));
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
if (currentEntry.model !== modelRef) {
|
|
256
|
-
nextText = applyEdits(nextText, modify(nextText, ["agent", agentName, "model"], modelRef, {
|
|
257
|
-
formattingOptions: JSON_FORMAT,
|
|
258
|
-
}));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
for (const definition of MANAGED_OPENCODE_AGENTS) {
|
|
262
|
-
const currentEntry = currentAgents[definition.name];
|
|
263
|
-
const registration = getManagedOpenCodeAgentRegistration(paths, definition.name);
|
|
264
|
-
if (!currentEntry) {
|
|
265
|
-
nextText = applyEdits(nextText, modify(nextText, ["agent", definition.name], registration, {
|
|
266
|
-
formattingOptions: JSON_FORMAT,
|
|
267
|
-
}));
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
for (const [field, nextValue] of Object.entries(registration)) {
|
|
271
|
-
if (JSON.stringify(currentEntry[field]) === JSON.stringify(nextValue)) {
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
nextText = applyEdits(nextText, modify(nextText, ["agent", definition.name, field], nextValue, {
|
|
275
|
-
formattingOptions: JSON_FORMAT,
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return ensureManagedSkillsPathConfigText(ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT))), paths);
|
|
280
|
-
}
|
|
281
|
-
export async function syncManagedAgentRegistrations(paths) {
|
|
282
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
283
|
-
const nextText = ensureManagedAgentRegistrationsConfigText(currentText, paths);
|
|
284
|
-
if (currentText === nextText) {
|
|
285
|
-
return { path: paths.opencodeConfigPath, changed: false };
|
|
286
|
-
}
|
|
287
|
-
await writeText(paths.opencodeConfigPath, nextText);
|
|
288
|
-
return { path: paths.opencodeConfigPath, changed: true };
|
|
289
|
-
}
|
|
290
|
-
// END_BLOCK_ENSURE_MANAGED_AGENT_CONFIG
|
|
291
|
-
export async function installManagedAgentPrompts(paths, options) {
|
|
292
|
-
const results = [];
|
|
293
|
-
for (const agentName of MANAGED_AGENT_PROMPT_NAMES) {
|
|
294
|
-
const promptPath = getManagedPromptPath(paths, agentName);
|
|
295
|
-
const currentText = await readOptionalText(promptPath);
|
|
296
|
-
if (!currentText) {
|
|
297
|
-
await writeText(promptPath, await renderManagedPrompt(agentName));
|
|
298
|
-
results.push({ action: "created", path: promptPath });
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (!options.force) {
|
|
302
|
-
if (!isManagedFile(currentText)) {
|
|
303
|
-
results.push({
|
|
304
|
-
action: "skipped",
|
|
305
|
-
path: promptPath,
|
|
306
|
-
reason: "existing file is not managed by vvoc",
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
results.push({ action: "kept", path: promptPath });
|
|
311
|
-
}
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
results.push(await syncManagedPrompt(paths, agentName, options));
|
|
315
|
-
}
|
|
316
|
-
return results;
|
|
317
|
-
}
|
|
318
|
-
export async function syncManagedAgentPrompts(paths, options) {
|
|
319
|
-
const results = [];
|
|
320
|
-
for (const agentName of MANAGED_AGENT_PROMPT_NAMES) {
|
|
321
|
-
results.push(await syncManagedPrompt(paths, agentName, options));
|
|
322
|
-
}
|
|
323
|
-
return results;
|
|
324
|
-
}
|
|
325
|
-
// START_BLOCK_MANAGED_SKILL_FUNCTIONS
|
|
326
|
-
export async function installManagedSkillFiles(paths, options) {
|
|
327
|
-
const results = [];
|
|
328
|
-
for (const skillName of MANAGED_SKILL_NAMES) {
|
|
329
|
-
const skillPath = getManagedSkillFilePath(paths.managedSkillsDirPath, skillName);
|
|
330
|
-
const currentText = await readOptionalText(skillPath);
|
|
331
|
-
if (!currentText) {
|
|
332
|
-
await writeText(skillPath, await renderManagedSkill(skillName));
|
|
333
|
-
results.push({ action: "created", path: skillPath });
|
|
334
|
-
}
|
|
335
|
-
else if (!options.force) {
|
|
336
|
-
if (!hasYamlFrontmatter(currentText)) {
|
|
337
|
-
results.push({
|
|
338
|
-
action: "skipped",
|
|
339
|
-
path: skillPath,
|
|
340
|
-
reason: "existing file has no YAML frontmatter — might not be a skill",
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
results.push({ action: "kept", path: skillPath });
|
|
345
|
-
}
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
results.push(await syncManagedSkill(paths, skillName, options));
|
|
350
|
-
}
|
|
351
|
-
const refResults = await syncManagedSkillReferences(paths.managedSkillsDirPath, skillName);
|
|
352
|
-
results.push(...refResults);
|
|
353
|
-
}
|
|
354
|
-
return results;
|
|
355
|
-
}
|
|
356
|
-
export async function syncManagedSkillFiles(paths, options) {
|
|
357
|
-
const results = [];
|
|
358
|
-
for (const skillName of MANAGED_SKILL_NAMES) {
|
|
359
|
-
const skillResult = await syncManagedSkill(paths, skillName, options);
|
|
360
|
-
results.push(skillResult);
|
|
361
|
-
// Only sync references when the parent skill was not skipped (user-owned/custom)
|
|
362
|
-
if (skillResult.action !== "skipped") {
|
|
363
|
-
const refResults = await syncManagedSkillReferences(paths.managedSkillsDirPath, skillName);
|
|
364
|
-
results.push(...refResults);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return results;
|
|
368
|
-
}
|
|
369
|
-
// END_BLOCK_MANAGED_SKILL_FUNCTIONS
|
|
370
|
-
// START_BLOCK_MANAGED_SKILL_SYMLINK
|
|
371
|
-
export async function ensureManagedSkillSymlink(configDir) {
|
|
372
|
-
const globalSkillsDir = getVvocSkillsDir(getGlobalVvocDir(configDir));
|
|
373
|
-
const opencodeSkillsDir = getGlobalOpencodeSkillsDir(configDir);
|
|
374
|
-
const symlinkPath = join(opencodeSkillsDir, "vvoc");
|
|
375
|
-
// Create the OpenCode skills parent directory
|
|
376
|
-
await mkdir(opencodeSkillsDir, { recursive: true });
|
|
377
|
-
// Never destroy a non-symlink at this path: a regular file or directory here
|
|
378
|
-
// is user-owned content, not a vvoc-managed link, and must not be clobbered.
|
|
379
|
-
let wasStale = false;
|
|
380
|
-
try {
|
|
381
|
-
const stats = await lstat(symlinkPath);
|
|
382
|
-
if (!stats.isSymbolicLink()) {
|
|
383
|
-
return { action: "skipped", path: symlinkPath };
|
|
384
|
-
}
|
|
385
|
-
if (resolve(await readlink(symlinkPath)) === resolve(globalSkillsDir)) {
|
|
386
|
-
// Already the current vvoc-managed link — nothing to do.
|
|
387
|
-
return { action: "kept", path: symlinkPath };
|
|
388
|
-
}
|
|
389
|
-
// A stale or foreign symlink: replace it with the current vvoc link.
|
|
390
|
-
await unlink(symlinkPath);
|
|
391
|
-
wasStale = true;
|
|
392
|
-
}
|
|
393
|
-
catch {
|
|
394
|
-
// Path does not exist — will be created fresh.
|
|
395
|
-
}
|
|
396
|
-
// Create symlink: opencode/skills/vvoc -> vvoc/skills
|
|
397
|
-
await symlink(globalSkillsDir, symlinkPath);
|
|
398
|
-
return { action: wasStale ? "updated" : "created", path: symlinkPath };
|
|
399
|
-
}
|
|
400
|
-
// END_BLOCK_MANAGED_SKILL_SYMLINK
|
|
401
|
-
export async function readManagedAgentModels(paths) {
|
|
402
|
-
const overrides = await readManagedAgentOverrides(paths);
|
|
403
|
-
return Object.fromEntries(Object.entries(overrides).map(([name, entry]) => [name, entry.model]));
|
|
404
|
-
}
|
|
405
|
-
export async function readManagedAgentOverrides(paths) {
|
|
406
|
-
const overrides = Object.fromEntries(MANAGED_OPENCODE_AGENTS.map((definition) => [definition.name, {}]));
|
|
407
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
408
|
-
if (!currentText) {
|
|
409
|
-
return overrides;
|
|
410
|
-
}
|
|
411
|
-
const document = parseObjectDocument(currentText, paths.opencodeConfigPath);
|
|
412
|
-
const agentMap = readAgentMap(document, paths.opencodeConfigPath);
|
|
413
|
-
for (const definition of MANAGED_OPENCODE_AGENTS) {
|
|
414
|
-
overrides[definition.name] = readAgentOverride(agentMap[definition.name], definition.name);
|
|
415
|
-
}
|
|
416
|
-
return overrides;
|
|
417
|
-
}
|
|
418
|
-
export async function readOpenCodeAgentModel(paths, agentName) {
|
|
419
|
-
return (await readOpenCodeAgentOverride(paths, agentName)).model;
|
|
420
|
-
}
|
|
421
|
-
export async function readOpenCodeAgentOverride(paths, agentName) {
|
|
422
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
423
|
-
if (!currentText) {
|
|
424
|
-
return {};
|
|
425
|
-
}
|
|
426
|
-
const document = parseObjectDocument(currentText, paths.opencodeConfigPath);
|
|
427
|
-
const agentMap = readAgentMap(document, paths.opencodeConfigPath);
|
|
428
|
-
return readAgentOverride(agentMap[agentName], `${paths.opencodeConfigPath}: agent.${agentName}`);
|
|
429
|
-
}
|
|
430
|
-
export async function readOpenCodeDefaultModel(paths, key) {
|
|
431
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
432
|
-
if (!currentText) {
|
|
433
|
-
return undefined;
|
|
434
|
-
}
|
|
435
|
-
const document = parseObjectDocument(currentText, paths.opencodeConfigPath);
|
|
436
|
-
const value = document[key];
|
|
437
|
-
if (value === undefined) {
|
|
438
|
-
return undefined;
|
|
439
|
-
}
|
|
440
|
-
return readNonEmptyString(value, `${paths.opencodeConfigPath}: ${key}`);
|
|
441
|
-
}
|
|
442
|
-
export async function writeOpenCodeAgentModel(paths, agentName, options) {
|
|
443
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
444
|
-
if (!currentText && !options.ensureEntry) {
|
|
445
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
446
|
-
}
|
|
447
|
-
const baseText = options.ensureEntry ? ensureAgentConfigText(currentText) : currentText;
|
|
448
|
-
if (!baseText) {
|
|
449
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
450
|
-
}
|
|
451
|
-
const document = parseObjectDocument(baseText, paths.opencodeConfigPath);
|
|
452
|
-
const agentMap = readAgentMap(document, paths.opencodeConfigPath);
|
|
453
|
-
const currentEntry = agentMap[agentName];
|
|
454
|
-
if (!currentEntry && !options.ensureEntry) {
|
|
455
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
456
|
-
}
|
|
457
|
-
const nextEntry = currentEntry ? { ...currentEntry } : {};
|
|
458
|
-
if (options.model) {
|
|
459
|
-
nextEntry.model = options.model;
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
delete nextEntry.model;
|
|
463
|
-
}
|
|
464
|
-
const nextText = updateAgentEntryText(baseText, agentName, nextEntry);
|
|
465
|
-
if ((currentText ?? "") === nextText) {
|
|
466
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
467
|
-
}
|
|
468
|
-
await writeText(paths.opencodeConfigPath, nextText);
|
|
469
|
-
return {
|
|
470
|
-
action: currentText ? "updated" : "created",
|
|
471
|
-
path: paths.opencodeConfigPath,
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
export async function writeOpenCodeDefaultModel(paths, key, options) {
|
|
475
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
476
|
-
if (!currentText && !options.ensureEntry) {
|
|
477
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
478
|
-
}
|
|
479
|
-
const baseText = options.ensureEntry ? ensureOpenCodeConfigText(currentText) : currentText;
|
|
480
|
-
if (!baseText) {
|
|
481
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
482
|
-
}
|
|
483
|
-
const nextText = updateTopLevelStringFieldText(baseText, key, options.model);
|
|
484
|
-
if ((currentText ?? "") === nextText) {
|
|
485
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
486
|
-
}
|
|
487
|
-
await writeText(paths.opencodeConfigPath, nextText);
|
|
488
|
-
return {
|
|
489
|
-
action: currentText ? "updated" : "created",
|
|
490
|
-
path: paths.opencodeConfigPath,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
export async function writeOpenCodeProviderObject(paths, providerID, value) {
|
|
494
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
495
|
-
const nextText = ensureProviderObjectConfigText(currentText, providerID, value);
|
|
496
|
-
if ((currentText ?? "") === nextText) {
|
|
497
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
498
|
-
}
|
|
499
|
-
await writeText(paths.opencodeConfigPath, nextText);
|
|
500
|
-
return {
|
|
501
|
-
action: currentText ? "updated" : "created",
|
|
502
|
-
path: paths.opencodeConfigPath,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
// START_BLOCK_MANAGED_AGENT_MODEL_IO
|
|
506
|
-
export async function writeManagedAgentModel(paths, agentName, options) {
|
|
507
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
508
|
-
if (!currentText && !options.ensureEntry) {
|
|
509
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
510
|
-
}
|
|
511
|
-
const baseText = options.ensureEntry
|
|
512
|
-
? ensureManagedAgentRegistrationsConfigText(currentText, paths)
|
|
513
|
-
: currentText;
|
|
514
|
-
if (!baseText) {
|
|
515
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
516
|
-
}
|
|
517
|
-
const document = parseObjectDocument(baseText, paths.opencodeConfigPath);
|
|
518
|
-
const agentMap = readAgentMap(document, paths.opencodeConfigPath);
|
|
519
|
-
const currentEntry = agentMap[agentName];
|
|
520
|
-
if (!currentEntry && !options.ensureEntry) {
|
|
521
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
522
|
-
}
|
|
523
|
-
const nextEntry = {
|
|
524
|
-
...getManagedOpenCodeAgentRegistration(paths, agentName),
|
|
525
|
-
...currentEntry,
|
|
526
|
-
};
|
|
527
|
-
if (options.model) {
|
|
528
|
-
nextEntry.model = options.model;
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
delete nextEntry.model;
|
|
532
|
-
}
|
|
533
|
-
const nextText = updateAgentEntryText(baseText, agentName, nextEntry);
|
|
534
|
-
if ((currentText ?? "") === nextText) {
|
|
535
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
536
|
-
}
|
|
537
|
-
await writeText(paths.opencodeConfigPath, nextText);
|
|
538
|
-
return {
|
|
539
|
-
action: currentText ? "updated" : "created",
|
|
540
|
-
path: paths.opencodeConfigPath,
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
// END_BLOCK_MANAGED_AGENT_MODEL_IO
|
|
21
|
+
export { PACKAGE_NAME } from "./package.js";
|
|
544
22
|
export { parseGuardianConfigText, renderGuardianConfig, } from "./vvoc-config.js";
|
|
545
|
-
|
|
546
|
-
export
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return { path: paths.opencodeConfigPath, changed: true };
|
|
554
|
-
}
|
|
555
|
-
// END_BLOCK_INSTALL_PACKAGE_AND_GUARDIAN_CONFIG
|
|
556
|
-
// START_BLOCK_ENSURE_PROVIDER_BASE_URL_CONFIG
|
|
557
|
-
export function ensureProviderBaseUrlConfigText(text, providerID, baseURL) {
|
|
558
|
-
if (!text?.trim()) {
|
|
559
|
-
return renderJson({
|
|
560
|
-
$schema: OPENCODE_SCHEMA_URL,
|
|
561
|
-
provider: {
|
|
562
|
-
[providerID]: {
|
|
563
|
-
options: {
|
|
564
|
-
baseURL,
|
|
565
|
-
},
|
|
566
|
-
},
|
|
567
|
-
},
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
const document = parseObjectDocument(text, "OpenCode config");
|
|
571
|
-
const currentProviders = readProviderMap(document, "OpenCode config");
|
|
572
|
-
const currentProvider = currentProviders[providerID];
|
|
573
|
-
const currentOptions = currentProvider
|
|
574
|
-
? readOptionalObject(currentProvider, "options", `OpenCode config: provider.${providerID}`)
|
|
575
|
-
: undefined;
|
|
576
|
-
let nextText = text;
|
|
577
|
-
if (!Object.hasOwn(document, "$schema")) {
|
|
578
|
-
nextText = applyEdits(nextText, modify(nextText, ["$schema"], OPENCODE_SCHEMA_URL, {
|
|
579
|
-
formattingOptions: JSON_FORMAT,
|
|
580
|
-
getInsertionIndex: () => 0,
|
|
581
|
-
}));
|
|
582
|
-
}
|
|
583
|
-
if (currentOptions?.baseURL !== baseURL) {
|
|
584
|
-
nextText = applyEdits(nextText, modify(nextText, ["provider", providerID, "options", "baseURL"], baseURL, {
|
|
585
|
-
formattingOptions: JSON_FORMAT,
|
|
586
|
-
}));
|
|
587
|
-
}
|
|
588
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
589
|
-
}
|
|
590
|
-
export async function writeProviderBaseUrl(paths, providerID, baseURL) {
|
|
591
|
-
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
592
|
-
const nextText = ensureProviderBaseUrlConfigText(currentText, providerID, baseURL);
|
|
593
|
-
if ((currentText ?? "") === nextText) {
|
|
594
|
-
return { action: "kept", path: paths.opencodeConfigPath };
|
|
595
|
-
}
|
|
596
|
-
await writeText(paths.opencodeConfigPath, nextText);
|
|
597
|
-
return {
|
|
598
|
-
action: currentText ? "updated" : "created",
|
|
599
|
-
path: paths.opencodeConfigPath,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
// END_BLOCK_ENSURE_PROVIDER_BASE_URL_CONFIG
|
|
603
|
-
// START_BLOCK_VVOC_CONFIG_IO
|
|
604
|
-
export async function readVvocConfig(paths) {
|
|
605
|
-
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
606
|
-
return currentText ? parseVvocConfigText(currentText, paths.vvocConfigPath) : undefined;
|
|
607
|
-
}
|
|
608
|
-
export async function installVvocConfig(paths) {
|
|
609
|
-
return syncVvocConfig(paths);
|
|
610
|
-
}
|
|
611
|
-
export async function syncVvocConfig(paths) {
|
|
612
|
-
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
613
|
-
const nextConfig = currentText
|
|
614
|
-
? parseVvocConfigText(currentText, paths.vvocConfigPath)
|
|
615
|
-
: createDefaultVvocConfig();
|
|
616
|
-
nextConfig.plugins["hashline-edit"] = materializeHashlineEditEntry(nextConfig.plugins["hashline-edit"]);
|
|
617
|
-
nextConfig.plugins["tool-history-compaction"] = materializeToolHistoryCompactionEntry(nextConfig.plugins["tool-history-compaction"]);
|
|
618
|
-
nextConfig.plugins["peak-hours"] = materializePeakHoursEntry(nextConfig.plugins["peak-hours"]);
|
|
619
|
-
nextConfig.plugins["spec-guard"] = materializeSpecGuardEntry(nextConfig.plugins["spec-guard"]);
|
|
620
|
-
return writeResolvedVvocConfig(paths.vvocConfigPath, currentText, nextConfig);
|
|
621
|
-
}
|
|
622
|
-
export async function writeGuardianConfig(paths, overrides, options = {}) {
|
|
623
|
-
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
624
|
-
const currentConfig = currentText
|
|
625
|
-
? parseVvocConfigText(currentText, paths.vvocConfigPath)
|
|
626
|
-
: createDefaultVvocConfig();
|
|
627
|
-
const nextConfig = {
|
|
628
|
-
...currentConfig,
|
|
629
|
-
guardian: options.merge
|
|
630
|
-
? createGuardianConfig({ ...currentConfig.guardian, ...overrides })
|
|
631
|
-
: createGuardianConfig(overrides),
|
|
632
|
-
};
|
|
633
|
-
return writeResolvedVvocConfig(paths.vvocConfigPath, currentText, nextConfig);
|
|
634
|
-
}
|
|
635
|
-
// END_BLOCK_VVOC_CONFIG_IO
|
|
636
|
-
// START_BLOCK_INSPECT_OPENCODE_RUNTIME
|
|
637
|
-
export async function inspectOpenCodeRuntime(run = runOpenCodeVersionCommand) {
|
|
638
|
-
try {
|
|
639
|
-
const result = await run();
|
|
640
|
-
if (result.exitCode !== 0) {
|
|
641
|
-
const detail = result.stderr.trim() || result.stdout.trim() || `exit code ${result.exitCode}`;
|
|
642
|
-
return {
|
|
643
|
-
minimumTuiVersion: MINIMUM_TUI_OPENCODE_VERSION,
|
|
644
|
-
error: `opencode --version failed: ${detail}`,
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
const version = extractOpenCodeVersion(`${result.stdout}\n${result.stderr}`);
|
|
648
|
-
if (!version) {
|
|
649
|
-
return {
|
|
650
|
-
minimumTuiVersion: MINIMUM_TUI_OPENCODE_VERSION,
|
|
651
|
-
error: "opencode --version did not return a semantic version",
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
return {
|
|
655
|
-
version,
|
|
656
|
-
minimumTuiVersion: MINIMUM_TUI_OPENCODE_VERSION,
|
|
657
|
-
tuiCompatible: isTuiOpenCodeVersionCompatible(version),
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
catch (error) {
|
|
661
|
-
return {
|
|
662
|
-
minimumTuiVersion: MINIMUM_TUI_OPENCODE_VERSION,
|
|
663
|
-
error: error instanceof Error ? error.message : String(error),
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
export function isTuiOpenCodeVersionCompatible(version) {
|
|
668
|
-
const current = parseSemanticVersion(version);
|
|
669
|
-
const minimum = parseSemanticVersion(MINIMUM_TUI_OPENCODE_VERSION);
|
|
670
|
-
if (!current || !minimum)
|
|
671
|
-
return false;
|
|
672
|
-
for (let index = 0; index < 3; index += 1) {
|
|
673
|
-
const currentPart = current.parts[index] ?? 0;
|
|
674
|
-
const minimumPart = minimum.parts[index] ?? 0;
|
|
675
|
-
if (currentPart > minimumPart)
|
|
676
|
-
return true;
|
|
677
|
-
if (currentPart < minimumPart)
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
return !current.prerelease || Boolean(minimum.prerelease);
|
|
681
|
-
}
|
|
682
|
-
async function runOpenCodeVersionCommand() {
|
|
683
|
-
const subprocess = Bun.spawn({
|
|
684
|
-
cmd: ["opencode", "--version"],
|
|
685
|
-
stdout: "pipe",
|
|
686
|
-
stderr: "pipe",
|
|
687
|
-
});
|
|
688
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
689
|
-
new Response(subprocess.stdout).text(),
|
|
690
|
-
new Response(subprocess.stderr).text(),
|
|
691
|
-
subprocess.exited,
|
|
692
|
-
]);
|
|
693
|
-
return { exitCode, stdout, stderr };
|
|
694
|
-
}
|
|
695
|
-
/** Extracts the first semantic version found in `opencode --version` output. */
|
|
696
|
-
export function extractOpenCodeVersion(output) {
|
|
697
|
-
const match = output.match(/(?:^|\s)v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)(?=\s|$)/);
|
|
698
|
-
return match?.[1];
|
|
699
|
-
}
|
|
700
|
-
function parseSemanticVersion(value) {
|
|
701
|
-
const match = value
|
|
702
|
-
.trim()
|
|
703
|
-
.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
704
|
-
if (!match)
|
|
705
|
-
return undefined;
|
|
706
|
-
return {
|
|
707
|
-
parts: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
708
|
-
prerelease: match[4],
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
// END_BLOCK_INSPECT_OPENCODE_RUNTIME
|
|
712
|
-
// START_BLOCK_INSPECT_INSTALLATION_STATE
|
|
713
|
-
export async function inspectInstallation(paths, options = {}) {
|
|
714
|
-
const warnings = [];
|
|
715
|
-
const problems = [];
|
|
716
|
-
const runtime = options.runtime ?? {
|
|
717
|
-
minimumTuiVersion: MINIMUM_TUI_OPENCODE_VERSION,
|
|
718
|
-
};
|
|
719
|
-
if (runtime.error) {
|
|
720
|
-
problems.push(`OpenCode version unavailable: ${runtime.error}`);
|
|
721
|
-
}
|
|
722
|
-
else if (runtime.tuiCompatible === false) {
|
|
723
|
-
problems.push(`OpenCode ${runtime.version ?? "unknown"} is incompatible with /context; ${runtime.minimumTuiVersion} or newer is required`);
|
|
724
|
-
}
|
|
725
|
-
if (paths.opencodeAlternatePaths.length > 0) {
|
|
726
|
-
warnings.push(`multiple OpenCode config files exist: ${[paths.opencodeConfigPath, ...paths.opencodeAlternatePaths].join(", ")}`);
|
|
727
|
-
}
|
|
728
|
-
if (paths.opencodeTuiAlternatePaths.length > 0) {
|
|
729
|
-
warnings.push(`multiple OpenCode TUI config files exist: ${[paths.opencodeTuiConfigPath, ...paths.opencodeTuiAlternatePaths].join(", ")}`);
|
|
730
|
-
}
|
|
731
|
-
const opencodeText = await readOptionalText(paths.opencodeConfigPath);
|
|
732
|
-
let opencodeParseError;
|
|
733
|
-
let plugins = [];
|
|
734
|
-
let pluginConfigured = false;
|
|
735
|
-
if (opencodeText) {
|
|
736
|
-
try {
|
|
737
|
-
const document = parseObjectDocument(opencodeText, paths.opencodeConfigPath);
|
|
738
|
-
plugins = readPluginList(document, paths.opencodeConfigPath);
|
|
739
|
-
pluginConfigured = plugins.some(isPackagePluginSpecifier);
|
|
740
|
-
}
|
|
741
|
-
catch (error) {
|
|
742
|
-
opencodeParseError = error instanceof Error ? error.message : String(error);
|
|
743
|
-
problems.push(opencodeParseError);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
const tuiText = await readOptionalText(paths.opencodeTuiConfigPath);
|
|
747
|
-
let tuiParseError;
|
|
748
|
-
let tuiPlugins = [];
|
|
749
|
-
let tuiPluginConfigured = false;
|
|
750
|
-
if (tuiText) {
|
|
751
|
-
try {
|
|
752
|
-
const document = parseObjectDocument(tuiText, paths.opencodeTuiConfigPath);
|
|
753
|
-
tuiPlugins = readTuiPluginList(document, paths.opencodeTuiConfigPath);
|
|
754
|
-
tuiPluginConfigured = tuiPlugins.some((entry) => isTuiPackageSpecifier(readTuiPluginName(entry)));
|
|
755
|
-
}
|
|
756
|
-
catch (error) {
|
|
757
|
-
tuiParseError = error instanceof Error ? error.message : String(error);
|
|
758
|
-
problems.push(tuiParseError);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
const vvocText = await readOptionalText(paths.vvocConfigPath);
|
|
762
|
-
let vvocParseError;
|
|
763
|
-
let vvocConfig;
|
|
764
|
-
let vvocSourceSchema;
|
|
765
|
-
let vvocSourceVersion;
|
|
766
|
-
let roleAssignments = [];
|
|
767
|
-
if (vvocText) {
|
|
768
|
-
try {
|
|
769
|
-
const parsedConfig = parseVersionedVvocConfigText(vvocText, paths.vvocConfigPath);
|
|
770
|
-
vvocConfig = parsedConfig.config;
|
|
771
|
-
vvocSourceSchema = parsedConfig.sourceSchema;
|
|
772
|
-
vvocSourceVersion = parsedConfig.sourceVersion;
|
|
773
|
-
roleAssignments = listRoleAssignments(vvocConfig.roles);
|
|
774
|
-
}
|
|
775
|
-
catch (error) {
|
|
776
|
-
vvocParseError = error instanceof Error ? error.message : String(error);
|
|
777
|
-
problems.push(vvocParseError);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
const unresolvedRoleReferences = opencodeText && !opencodeParseError
|
|
781
|
-
? collectUnresolvedRoleReferences(opencodeText, paths.opencodeConfigPath, vvocConfig?.roles ?? {})
|
|
782
|
-
: [];
|
|
783
|
-
for (const unresolved of unresolvedRoleReferences) {
|
|
784
|
-
problems.push(`unresolved role reference at ${unresolved.fieldPath}: ${unresolved.roleRef} (missing role: ${unresolved.roleId})`);
|
|
785
|
-
}
|
|
786
|
-
if (!pluginConfigured) {
|
|
787
|
-
problems.push(`${PACKAGE_NAME} is not configured in ${paths.opencodeConfigPath}`);
|
|
788
|
-
}
|
|
789
|
-
if (!tuiPluginConfigured) {
|
|
790
|
-
problems.push(`${TUI_PACKAGE_SPECIFIER} is not configured in ${paths.opencodeTuiConfigPath}`);
|
|
791
|
-
}
|
|
792
|
-
if (!vvocText) {
|
|
793
|
-
problems.push(`vvoc config is missing at ${paths.vvocConfigPath}`);
|
|
794
|
-
}
|
|
795
|
-
return {
|
|
796
|
-
scope: paths.scope,
|
|
797
|
-
runtime,
|
|
798
|
-
opencode: {
|
|
799
|
-
path: paths.opencodeConfigPath,
|
|
800
|
-
exists: Boolean(opencodeText),
|
|
801
|
-
alternates: paths.opencodeAlternatePaths,
|
|
802
|
-
parseError: opencodeParseError,
|
|
803
|
-
pluginConfigured,
|
|
804
|
-
plugins,
|
|
805
|
-
},
|
|
806
|
-
tui: {
|
|
807
|
-
path: paths.opencodeTuiConfigPath,
|
|
808
|
-
exists: Boolean(tuiText),
|
|
809
|
-
alternates: paths.opencodeTuiAlternatePaths,
|
|
810
|
-
parseError: tuiParseError,
|
|
811
|
-
pluginConfigured: tuiPluginConfigured,
|
|
812
|
-
plugins: tuiPlugins,
|
|
813
|
-
},
|
|
814
|
-
vvoc: {
|
|
815
|
-
path: paths.vvocConfigPath,
|
|
816
|
-
exists: Boolean(vvocText),
|
|
817
|
-
parseError: vvocParseError,
|
|
818
|
-
schema: vvocSourceSchema,
|
|
819
|
-
version: vvocSourceVersion,
|
|
820
|
-
},
|
|
821
|
-
guardian: {
|
|
822
|
-
config: vvocConfig?.guardian,
|
|
823
|
-
},
|
|
824
|
-
secretsRedaction: {
|
|
825
|
-
config: vvocConfig?.secretsRedaction,
|
|
826
|
-
},
|
|
827
|
-
orchestration: {
|
|
828
|
-
profile: vvocConfig ? resolveOrchestrationPolicy(vvocConfig).profile : undefined,
|
|
829
|
-
},
|
|
830
|
-
roles: {
|
|
831
|
-
assignments: roleAssignments,
|
|
832
|
-
unresolvedReferences: unresolvedRoleReferences,
|
|
833
|
-
},
|
|
834
|
-
warnings,
|
|
835
|
-
problems,
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
export async function inspectInstallationForScope(options) {
|
|
839
|
-
const [opencodeSource, opencodeTuiSource, vvocSource, runtime] = await Promise.all([
|
|
840
|
-
resolveOpenCodeConfigSource({
|
|
841
|
-
scope: options.scope,
|
|
842
|
-
cwd: options.cwd,
|
|
843
|
-
configDir: options.configDir,
|
|
844
|
-
}),
|
|
845
|
-
resolveOpenCodeTuiConfigSource({
|
|
846
|
-
scope: options.scope,
|
|
847
|
-
cwd: options.cwd,
|
|
848
|
-
configDir: options.configDir,
|
|
849
|
-
}),
|
|
850
|
-
resolveVvocConfigSource({
|
|
851
|
-
scope: options.scope,
|
|
852
|
-
cwd: options.cwd,
|
|
853
|
-
configDir: options.configDir,
|
|
854
|
-
allowDefault: options.scope === "effective",
|
|
855
|
-
}),
|
|
856
|
-
(options.inspectRuntime ?? inspectOpenCodeRuntime)(),
|
|
857
|
-
]);
|
|
858
|
-
if (options.scope === "project") {
|
|
859
|
-
const missingSource = [opencodeSource, vvocSource].find((source) => source.kind === "missing");
|
|
860
|
-
if (missingSource) {
|
|
861
|
-
throw new Error(missingSource.reason ?? "project config missing; run vvoc install --scope project");
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
const fallbackPaths = await resolvePaths({
|
|
865
|
-
scope: options.scope === "global" ? "global" : "project",
|
|
866
|
-
cwd: options.cwd,
|
|
867
|
-
configDir: options.configDir,
|
|
868
|
-
});
|
|
869
|
-
const opencodeConfigPath = opencodeSource.path ?? fallbackPaths.opencodeConfigPath;
|
|
870
|
-
const opencodeTuiConfigPath = opencodeTuiSource.path ?? fallbackPaths.opencodeTuiConfigPath;
|
|
871
|
-
const vvocConfigPath = vvocSource.path ?? fallbackPaths.vvocConfigPath;
|
|
872
|
-
const scopedPaths = {
|
|
873
|
-
...fallbackPaths,
|
|
874
|
-
opencodeBaseDir: dirname(opencodeConfigPath),
|
|
875
|
-
vvocBaseDir: dirname(vvocConfigPath),
|
|
876
|
-
opencodeConfigPath,
|
|
877
|
-
opencodeTuiConfigPath,
|
|
878
|
-
vvocConfigPath,
|
|
879
|
-
opencodeAlternatePaths: [],
|
|
880
|
-
opencodeTuiAlternatePaths: [],
|
|
881
|
-
managedAgentsDirPath: getVvocAgentsDir(dirname(vvocConfigPath)),
|
|
882
|
-
managedSkillsDirPath: getVvocSkillsDir(dirname(vvocConfigPath)),
|
|
883
|
-
};
|
|
884
|
-
const inspection = await inspectInstallation(scopedPaths, { runtime });
|
|
885
|
-
return {
|
|
886
|
-
...inspection,
|
|
887
|
-
orchestration: {
|
|
888
|
-
profile: inspection.orchestration.profile ??
|
|
889
|
-
(vvocSource.kind === "default"
|
|
890
|
-
? resolveOrchestrationPolicy(createDefaultVvocConfig()).profile
|
|
891
|
-
: undefined),
|
|
892
|
-
},
|
|
893
|
-
scope: options.scope,
|
|
894
|
-
opencodeSource,
|
|
895
|
-
opencodeTuiSource,
|
|
896
|
-
vvocSource,
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
// END_BLOCK_INSPECT_INSTALLATION_STATE
|
|
900
|
-
export function describeWriteResult(result) {
|
|
901
|
-
let message = "";
|
|
902
|
-
switch (result.action) {
|
|
903
|
-
case "created":
|
|
904
|
-
message = `Created ${result.path}`;
|
|
905
|
-
break;
|
|
906
|
-
case "updated":
|
|
907
|
-
message = `Updated ${result.path}`;
|
|
908
|
-
break;
|
|
909
|
-
case "kept":
|
|
910
|
-
message = `Kept ${result.path}`;
|
|
911
|
-
break;
|
|
912
|
-
case "skipped":
|
|
913
|
-
message = `Skipped ${result.path}`;
|
|
914
|
-
break;
|
|
915
|
-
}
|
|
916
|
-
return result.reason ? `${message} (${result.reason})` : message;
|
|
917
|
-
}
|
|
918
|
-
// START_BLOCK_PARSE_AND_NORMALIZE_CONFIG_VALUES
|
|
919
|
-
function parseObjectDocument(text, label) {
|
|
920
|
-
const errors = [];
|
|
921
|
-
const value = parse(text, errors, {
|
|
922
|
-
allowEmptyContent: false,
|
|
923
|
-
allowTrailingComma: true,
|
|
924
|
-
disallowComments: false,
|
|
925
|
-
});
|
|
926
|
-
if (errors.length > 0) {
|
|
927
|
-
throw new Error(`${label}: failed to parse JSONC (${errors.length} error(s))`);
|
|
928
|
-
}
|
|
929
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
930
|
-
throw new Error(`${label}: expected a top-level object`);
|
|
931
|
-
}
|
|
932
|
-
return value;
|
|
933
|
-
}
|
|
934
|
-
function readPluginList(document, label) {
|
|
935
|
-
const raw = document.plugin;
|
|
936
|
-
if (raw === undefined)
|
|
937
|
-
return [];
|
|
938
|
-
if (!Array.isArray(raw) || raw.some((entry) => typeof entry !== "string")) {
|
|
939
|
-
throw new Error(`${label}: expected "plugin" to be an array of strings`);
|
|
940
|
-
}
|
|
941
|
-
return raw.slice();
|
|
942
|
-
}
|
|
943
|
-
function readTuiPluginList(document, label) {
|
|
944
|
-
const raw = document.plugin;
|
|
945
|
-
if (raw === undefined)
|
|
946
|
-
return [];
|
|
947
|
-
if (!Array.isArray(raw)) {
|
|
948
|
-
throw new Error(`${label}: expected "plugin" to be an array`);
|
|
949
|
-
}
|
|
950
|
-
return raw.map((entry, index) => {
|
|
951
|
-
if (typeof entry === "string") {
|
|
952
|
-
return entry;
|
|
953
|
-
}
|
|
954
|
-
if (Array.isArray(entry) &&
|
|
955
|
-
entry.length === 2 &&
|
|
956
|
-
typeof entry[0] === "string" &&
|
|
957
|
-
isJsonObject(entry[1])) {
|
|
958
|
-
return [entry[0], entry[1]];
|
|
959
|
-
}
|
|
960
|
-
throw new Error(`${label}: expected "plugin[${index}]" to be a string or [string, options] tuple`);
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
function readStringArray(value, label) {
|
|
964
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
965
|
-
throw new Error(`${label}: expected an array of strings`);
|
|
966
|
-
}
|
|
967
|
-
return value.slice();
|
|
968
|
-
}
|
|
969
|
-
function normalizePluginList(currentPlugins, packageSpecifier) {
|
|
970
|
-
const nextPlugins = [];
|
|
971
|
-
const seen = new Set();
|
|
972
|
-
let insertedPackage = false;
|
|
973
|
-
const push = (value) => {
|
|
974
|
-
if (!seen.has(value)) {
|
|
975
|
-
seen.add(value);
|
|
976
|
-
nextPlugins.push(value);
|
|
977
|
-
}
|
|
978
|
-
};
|
|
979
|
-
for (const plugin of currentPlugins) {
|
|
980
|
-
if (isPackagePluginSpecifier(plugin)) {
|
|
981
|
-
if (!insertedPackage) {
|
|
982
|
-
push(packageSpecifier);
|
|
983
|
-
insertedPackage = true;
|
|
984
|
-
}
|
|
985
|
-
continue;
|
|
986
|
-
}
|
|
987
|
-
push(plugin);
|
|
988
|
-
}
|
|
989
|
-
if (!insertedPackage) {
|
|
990
|
-
push(packageSpecifier);
|
|
991
|
-
}
|
|
992
|
-
return nextPlugins;
|
|
993
|
-
}
|
|
994
|
-
function normalizeTuiPluginList(currentPlugins, packageSpecifier) {
|
|
995
|
-
const nextPlugins = [];
|
|
996
|
-
let insertedPackage = false;
|
|
997
|
-
for (const entry of currentPlugins) {
|
|
998
|
-
if (!isManagedTuiPackageSpecifier(readTuiPluginName(entry))) {
|
|
999
|
-
nextPlugins.push(entry);
|
|
1000
|
-
continue;
|
|
1001
|
-
}
|
|
1002
|
-
if (insertedPackage) {
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
nextPlugins.push(typeof entry === "string" ? packageSpecifier : [packageSpecifier, entry[1]]);
|
|
1006
|
-
insertedPackage = true;
|
|
1007
|
-
}
|
|
1008
|
-
if (!insertedPackage) {
|
|
1009
|
-
nextPlugins.push(packageSpecifier);
|
|
1010
|
-
}
|
|
1011
|
-
return nextPlugins;
|
|
1012
|
-
}
|
|
1013
|
-
function isPackagePluginSpecifier(value) {
|
|
1014
|
-
return value === PACKAGE_NAME || value.startsWith(`${PACKAGE_NAME}@`);
|
|
1015
|
-
}
|
|
1016
|
-
function readTuiPluginName(entry) {
|
|
1017
|
-
return typeof entry === "string" ? entry : entry[0];
|
|
1018
|
-
}
|
|
1019
|
-
function isManagedTuiPackageSpecifier(value) {
|
|
1020
|
-
return (isBasePackageSpecifier(value) ||
|
|
1021
|
-
value === `${PACKAGE_NAME}/tui` ||
|
|
1022
|
-
(value.startsWith(`${PACKAGE_NAME}@`) && value.endsWith("/tui")));
|
|
1023
|
-
}
|
|
1024
|
-
function isTuiPackageSpecifier(value) {
|
|
1025
|
-
return value === TUI_PACKAGE_SPECIFIER;
|
|
1026
|
-
}
|
|
1027
|
-
function isBasePackageSpecifier(value) {
|
|
1028
|
-
if (value === PACKAGE_NAME)
|
|
1029
|
-
return true;
|
|
1030
|
-
const prefix = `${PACKAGE_NAME}@`;
|
|
1031
|
-
return value.startsWith(prefix) && !value.slice(prefix.length).includes("/");
|
|
1032
|
-
}
|
|
1033
|
-
// END_BLOCK_PARSE_AND_NORMALIZE_CONFIG_VALUES
|
|
1034
|
-
// START_BLOCK_MANAGED_AGENT_HELPERS
|
|
1035
|
-
function readAgentMap(document, label) {
|
|
1036
|
-
const raw = document.agent;
|
|
1037
|
-
if (raw === undefined) {
|
|
1038
|
-
return {};
|
|
1039
|
-
}
|
|
1040
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1041
|
-
throw new Error(`${label}: expected "agent" to be an object`);
|
|
1042
|
-
}
|
|
1043
|
-
const entries = {};
|
|
1044
|
-
for (const [name, value] of Object.entries(raw)) {
|
|
1045
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1046
|
-
throw new Error(`${label}: expected "agent.${name}" to be an object`);
|
|
1047
|
-
}
|
|
1048
|
-
entries[name] = value;
|
|
1049
|
-
}
|
|
1050
|
-
return entries;
|
|
1051
|
-
}
|
|
1052
|
-
function readProviderMap(document, label) {
|
|
1053
|
-
const raw = document.provider;
|
|
1054
|
-
if (raw === undefined) {
|
|
1055
|
-
return {};
|
|
1056
|
-
}
|
|
1057
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1058
|
-
throw new Error(`${label}: expected "provider" to be an object`);
|
|
1059
|
-
}
|
|
1060
|
-
const entries = {};
|
|
1061
|
-
for (const [name, value] of Object.entries(raw)) {
|
|
1062
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1063
|
-
throw new Error(`${label}: expected "provider.${name}" to be an object`);
|
|
1064
|
-
}
|
|
1065
|
-
entries[name] = value;
|
|
1066
|
-
}
|
|
1067
|
-
return entries;
|
|
1068
|
-
}
|
|
1069
|
-
function listRoleAssignments(roles) {
|
|
1070
|
-
const listed = [];
|
|
1071
|
-
for (const roleId of BUILTIN_ROLE_NAMES) {
|
|
1072
|
-
if (typeof roles[roleId] === "string") {
|
|
1073
|
-
listed.push({ roleId, model: roles[roleId], builtIn: true });
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
const customRoleIds = Object.keys(roles)
|
|
1077
|
-
.filter((roleId) => !BUILTIN_ROLE_NAMES.includes(roleId))
|
|
1078
|
-
.sort((left, right) => left.localeCompare(right));
|
|
1079
|
-
for (const roleId of customRoleIds) {
|
|
1080
|
-
listed.push({ roleId, model: roles[roleId], builtIn: false });
|
|
1081
|
-
}
|
|
1082
|
-
return listed;
|
|
1083
|
-
}
|
|
1084
|
-
function collectUnresolvedRoleReferences(opencodeText, label, roleMap) {
|
|
1085
|
-
const document = parseObjectDocument(opencodeText, label);
|
|
1086
|
-
const unresolved = [];
|
|
1087
|
-
const collectFromField = (fieldPath, value) => {
|
|
1088
|
-
if (typeof value !== "string") {
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
const trimmed = value.trim();
|
|
1092
|
-
if (!trimmed.startsWith(ROLE_REFERENCE_PREFIX)) {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
const roleId = trimmed.slice(ROLE_REFERENCE_PREFIX.length).trim();
|
|
1096
|
-
if (!roleId || !Object.hasOwn(roleMap, roleId)) {
|
|
1097
|
-
unresolved.push({ fieldPath, roleRef: trimmed, roleId: roleId || "<missing>" });
|
|
1098
|
-
}
|
|
1099
|
-
};
|
|
1100
|
-
collectFromField("model", document.model);
|
|
1101
|
-
collectFromField("small_model", document.small_model);
|
|
1102
|
-
for (const parentName of ["agent", "command"]) {
|
|
1103
|
-
const parent = document[parentName];
|
|
1104
|
-
if (!parent || typeof parent !== "object" || Array.isArray(parent)) {
|
|
1105
|
-
continue;
|
|
1106
|
-
}
|
|
1107
|
-
for (const [entryName, entry] of Object.entries(parent)) {
|
|
1108
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1111
|
-
collectFromField(`${parentName}.${entryName}.model`, entry.model);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
return unresolved;
|
|
1115
|
-
}
|
|
1116
|
-
function getManagedPromptPath(paths, agentName) {
|
|
1117
|
-
return getManagedAgentPromptPath(paths.managedAgentsDirPath, agentName);
|
|
1118
|
-
}
|
|
1119
|
-
function ensureAgentConfigText(text) {
|
|
1120
|
-
const nextText = ensureOpenCodeConfigText(text);
|
|
1121
|
-
const document = parseObjectDocument(nextText, "OpenCode config");
|
|
1122
|
-
const currentAgents = readAgentMap(document, "OpenCode config");
|
|
1123
|
-
let nextAgentText = nextText;
|
|
1124
|
-
if (!Object.hasOwn(document, "agent")) {
|
|
1125
|
-
nextAgentText = applyEdits(nextAgentText, modify(nextAgentText, ["agent"], currentAgents, {
|
|
1126
|
-
formattingOptions: JSON_FORMAT,
|
|
1127
|
-
}));
|
|
1128
|
-
}
|
|
1129
|
-
return ensureTrailingNewline(applyEdits(nextAgentText, format(nextAgentText, undefined, JSON_FORMAT)));
|
|
1130
|
-
}
|
|
1131
|
-
function getManagedOpenCodeAgentPromptReference(paths, agentName) {
|
|
1132
|
-
const promptPath = getManagedPromptPath(paths, agentName);
|
|
1133
|
-
const promptRef = relative(dirname(paths.opencodeConfigPath), promptPath).replaceAll("\\", "/");
|
|
1134
|
-
return `{file:${promptRef.startsWith(".") ? promptRef : `./${promptRef}`}}`;
|
|
1135
|
-
}
|
|
1136
|
-
function getManagedSkillsPathReference(paths) {
|
|
1137
|
-
const skillsRef = relative(dirname(paths.opencodeConfigPath), paths.managedSkillsDirPath).replaceAll("\\", "/");
|
|
1138
|
-
return skillsRef.startsWith(".") ? skillsRef : `./${skillsRef}`;
|
|
1139
|
-
}
|
|
1140
|
-
function ensureManagedSkillsPathConfigText(text, paths) {
|
|
1141
|
-
const document = parseObjectDocument(text, "OpenCode config");
|
|
1142
|
-
const skills = readOptionalObject(document, "skills", "OpenCode config");
|
|
1143
|
-
const rawPaths = skills?.paths;
|
|
1144
|
-
const currentPaths = rawPaths === undefined ? [] : readStringArray(rawPaths, "OpenCode config: skills.paths");
|
|
1145
|
-
const managedSkillsPath = getManagedSkillsPathReference(paths);
|
|
1146
|
-
if (currentPaths.includes(managedSkillsPath)) {
|
|
1147
|
-
return text;
|
|
1148
|
-
}
|
|
1149
|
-
const nextPaths = [...currentPaths, managedSkillsPath];
|
|
1150
|
-
const nextText = applyEdits(text, modify(text, ["skills", "paths"], nextPaths, {
|
|
1151
|
-
formattingOptions: JSON_FORMAT,
|
|
1152
|
-
}));
|
|
1153
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
1154
|
-
}
|
|
1155
|
-
function getManagedOpenCodeAgentRegistration(paths, agentName) {
|
|
1156
|
-
const definition = getManagedOpenCodeAgentDefinition(agentName);
|
|
1157
|
-
const builtInBindings = getBuiltInRoleBindings();
|
|
1158
|
-
const registration = {
|
|
1159
|
-
description: definition.description,
|
|
1160
|
-
mode: definition.mode,
|
|
1161
|
-
prompt: getManagedOpenCodeAgentPromptReference(paths, agentName),
|
|
1162
|
-
model: createRoleReference(builtInBindings.managedAgents[agentName]),
|
|
1163
|
-
};
|
|
1164
|
-
if (definition.permission) {
|
|
1165
|
-
registration.permission = definition.permission;
|
|
1166
|
-
}
|
|
1167
|
-
return registration;
|
|
1168
|
-
}
|
|
1169
|
-
async function renderManagedPrompt(agentName) {
|
|
1170
|
-
const template = stripMarkdownFrontmatter(await loadManagedAgentPromptTemplate(agentName)).trim();
|
|
1171
|
-
const header = [
|
|
1172
|
-
"<!-- Managed by vvoc.",
|
|
1173
|
-
"`vvoc sync` rewrites files with this marker while preserving agent registration and model settings elsewhere.",
|
|
1174
|
-
"Remove this comment if you want to manage the file manually.",
|
|
1175
|
-
"-->",
|
|
1176
|
-
"",
|
|
1177
|
-
].join("\n");
|
|
1178
|
-
return `${header}${template}\n`;
|
|
1179
|
-
}
|
|
1180
|
-
async function syncManagedPrompt(paths, agentName, options) {
|
|
1181
|
-
const promptPath = getManagedPromptPath(paths, agentName);
|
|
1182
|
-
const currentText = await readOptionalText(promptPath);
|
|
1183
|
-
if (!currentText) {
|
|
1184
|
-
await writeText(promptPath, await renderManagedPrompt(agentName));
|
|
1185
|
-
return { action: "created", path: promptPath };
|
|
1186
|
-
}
|
|
1187
|
-
if (!options.force && !isManagedFile(currentText)) {
|
|
1188
|
-
return {
|
|
1189
|
-
action: "skipped",
|
|
1190
|
-
path: promptPath,
|
|
1191
|
-
reason: "existing file is not managed by vvoc",
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
const nextText = await renderManagedPrompt(agentName);
|
|
1195
|
-
if (currentText === nextText) {
|
|
1196
|
-
return { action: "kept", path: promptPath };
|
|
1197
|
-
}
|
|
1198
|
-
await writeText(promptPath, nextText);
|
|
1199
|
-
return { action: "updated", path: promptPath };
|
|
1200
|
-
}
|
|
1201
|
-
async function renderManagedSkill(skillName) {
|
|
1202
|
-
return loadManagedSkillTemplate(skillName);
|
|
1203
|
-
}
|
|
1204
|
-
async function syncManagedSkillReferences(skillsDirPath, skillName) {
|
|
1205
|
-
const results = [];
|
|
1206
|
-
const referenceNames = await listManagedSkillReferenceNames(skillName);
|
|
1207
|
-
for (const refName of referenceNames) {
|
|
1208
|
-
const templateContent = await loadManagedSkillReference(skillName, refName);
|
|
1209
|
-
const targetPath = join(skillsDirPath, skillName, "references", refName);
|
|
1210
|
-
const currentContent = await readOptionalText(targetPath);
|
|
1211
|
-
if (currentContent === templateContent) {
|
|
1212
|
-
results.push({ action: "kept", path: targetPath });
|
|
1213
|
-
continue;
|
|
1214
|
-
}
|
|
1215
|
-
await writeText(targetPath, templateContent);
|
|
1216
|
-
results.push({
|
|
1217
|
-
action: currentContent ? "updated" : "created",
|
|
1218
|
-
path: targetPath,
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
return results;
|
|
1222
|
-
}
|
|
1223
|
-
async function syncManagedSkill(paths, skillName, options) {
|
|
1224
|
-
const skillPath = getManagedSkillFilePath(paths.managedSkillsDirPath, skillName);
|
|
1225
|
-
const currentText = await readOptionalText(skillPath);
|
|
1226
|
-
if (!currentText) {
|
|
1227
|
-
await writeText(skillPath, await renderManagedSkill(skillName));
|
|
1228
|
-
return { action: "created", path: skillPath };
|
|
1229
|
-
}
|
|
1230
|
-
if (!options.force && !hasYamlFrontmatter(currentText)) {
|
|
1231
|
-
return {
|
|
1232
|
-
action: "skipped",
|
|
1233
|
-
path: skillPath,
|
|
1234
|
-
reason: "existing file has no YAML frontmatter — might not be a skill",
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
const nextText = await renderManagedSkill(skillName);
|
|
1238
|
-
if (currentText === nextText) {
|
|
1239
|
-
return { action: "kept", path: skillPath };
|
|
1240
|
-
}
|
|
1241
|
-
await writeText(skillPath, nextText);
|
|
1242
|
-
return { action: "updated", path: skillPath };
|
|
1243
|
-
}
|
|
1244
|
-
function updateAgentEntryText(text, agentName, entry) {
|
|
1245
|
-
const nextText = applyEdits(text, modify(text, ["agent", agentName], entry, {
|
|
1246
|
-
formattingOptions: JSON_FORMAT,
|
|
1247
|
-
}));
|
|
1248
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
1249
|
-
}
|
|
1250
|
-
function readAgentOverride(entry, label) {
|
|
1251
|
-
if (!entry) {
|
|
1252
|
-
return {};
|
|
1253
|
-
}
|
|
1254
|
-
return {
|
|
1255
|
-
model: entry.model === undefined ? undefined : readNonEmptyString(entry.model, `${label}.model`),
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
function ensureOpenCodeConfigText(text) {
|
|
1259
|
-
if (!text?.trim()) {
|
|
1260
|
-
return renderJson({
|
|
1261
|
-
$schema: OPENCODE_SCHEMA_URL,
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
|
-
const document = parseObjectDocument(text, "OpenCode config");
|
|
1265
|
-
let nextText = text;
|
|
1266
|
-
if (!Object.hasOwn(document, "$schema")) {
|
|
1267
|
-
nextText = applyEdits(nextText, modify(nextText, ["$schema"], OPENCODE_SCHEMA_URL, {
|
|
1268
|
-
formattingOptions: JSON_FORMAT,
|
|
1269
|
-
getInsertionIndex: () => 0,
|
|
1270
|
-
}));
|
|
1271
|
-
}
|
|
1272
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
1273
|
-
}
|
|
1274
|
-
function ensureProviderObjectConfigText(text, providerID, value) {
|
|
1275
|
-
if (!text?.trim()) {
|
|
1276
|
-
return renderJson({
|
|
1277
|
-
$schema: OPENCODE_SCHEMA_URL,
|
|
1278
|
-
provider: {
|
|
1279
|
-
[providerID]: value,
|
|
1280
|
-
},
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
const document = parseObjectDocument(text, "OpenCode config");
|
|
1284
|
-
const currentProviders = readProviderMap(document, "OpenCode config");
|
|
1285
|
-
const currentValue = currentProviders[providerID];
|
|
1286
|
-
const nextValue = currentValue ? mergeJsonObjects(currentValue, value) : value;
|
|
1287
|
-
let nextText = text;
|
|
1288
|
-
if (!Object.hasOwn(document, "$schema")) {
|
|
1289
|
-
nextText = applyEdits(nextText, modify(nextText, ["$schema"], OPENCODE_SCHEMA_URL, {
|
|
1290
|
-
formattingOptions: JSON_FORMAT,
|
|
1291
|
-
getInsertionIndex: () => 0,
|
|
1292
|
-
}));
|
|
1293
|
-
}
|
|
1294
|
-
if (JSON.stringify(currentValue) !== JSON.stringify(nextValue)) {
|
|
1295
|
-
nextText = applyEdits(nextText, modify(nextText, ["provider", providerID], nextValue, {
|
|
1296
|
-
formattingOptions: JSON_FORMAT,
|
|
1297
|
-
}));
|
|
1298
|
-
}
|
|
1299
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
1300
|
-
}
|
|
1301
|
-
function updateTopLevelStringFieldText(text, key, value) {
|
|
1302
|
-
const nextText = applyEdits(text, modify(text, [key], value, {
|
|
1303
|
-
formattingOptions: JSON_FORMAT,
|
|
1304
|
-
}));
|
|
1305
|
-
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
1306
|
-
}
|
|
1307
|
-
function createRoleReference(roleId) {
|
|
1308
|
-
return `${ROLE_REFERENCE_PREFIX}${roleId}`;
|
|
1309
|
-
}
|
|
1310
|
-
// END_BLOCK_MANAGED_AGENT_HELPERS
|
|
1311
|
-
// START_BLOCK_JSON_VALUE_HELPERS
|
|
1312
|
-
function readNonEmptyString(value, label) {
|
|
1313
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
1314
|
-
throw new Error(`${label}: expected a non-empty string`);
|
|
1315
|
-
}
|
|
1316
|
-
return value.trim();
|
|
1317
|
-
}
|
|
1318
|
-
function isJsonObject(value) {
|
|
1319
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1320
|
-
}
|
|
1321
|
-
function mergeJsonObjects(current, patch) {
|
|
1322
|
-
const merged = { ...current };
|
|
1323
|
-
for (const [key, patchValue] of Object.entries(patch)) {
|
|
1324
|
-
const currentValue = merged[key];
|
|
1325
|
-
merged[key] =
|
|
1326
|
-
isJsonObject(currentValue) && isJsonObject(patchValue)
|
|
1327
|
-
? mergeJsonObjects(currentValue, patchValue)
|
|
1328
|
-
: patchValue;
|
|
1329
|
-
}
|
|
1330
|
-
return merged;
|
|
1331
|
-
}
|
|
1332
|
-
function readOptionalObject(document, key, label) {
|
|
1333
|
-
const value = document[key];
|
|
1334
|
-
if (value === undefined) {
|
|
1335
|
-
return undefined;
|
|
1336
|
-
}
|
|
1337
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1338
|
-
throw new Error(`${label}: expected "${key}" to be an object`);
|
|
1339
|
-
}
|
|
1340
|
-
return value;
|
|
1341
|
-
}
|
|
1342
|
-
// END_BLOCK_JSON_VALUE_HELPERS
|
|
1343
|
-
// START_BLOCK_FILESYSTEM_HELPERS
|
|
1344
|
-
function isManagedFile(text) {
|
|
1345
|
-
return text.includes(MANAGED_MARKER);
|
|
1346
|
-
}
|
|
1347
|
-
function hasYamlFrontmatter(text) {
|
|
1348
|
-
return text.startsWith("---\n");
|
|
1349
|
-
}
|
|
1350
|
-
function renderJson(value) {
|
|
1351
|
-
return `${JSON.stringify(value, null, 2)}\n`;
|
|
1352
|
-
}
|
|
1353
|
-
function stripMarkdownFrontmatter(text) {
|
|
1354
|
-
const normalized = text.replaceAll("\r\n", "\n");
|
|
1355
|
-
return normalized.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
1356
|
-
}
|
|
1357
|
-
function ensureTrailingNewline(text) {
|
|
1358
|
-
return text.endsWith("\n") ? text : `${text}\n`;
|
|
1359
|
-
}
|
|
1360
|
-
async function writeResolvedVvocConfig(path, currentText, config) {
|
|
1361
|
-
const nextText = renderVvocConfig(config);
|
|
1362
|
-
if ((currentText ?? "") === nextText) {
|
|
1363
|
-
return { action: "kept", path };
|
|
1364
|
-
}
|
|
1365
|
-
await writeText(path, nextText);
|
|
1366
|
-
return {
|
|
1367
|
-
action: currentText ? "updated" : "created",
|
|
1368
|
-
path,
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
async function readOptionalText(path) {
|
|
1372
|
-
try {
|
|
1373
|
-
return await readFile(path, "utf8");
|
|
1374
|
-
}
|
|
1375
|
-
catch (error) {
|
|
1376
|
-
if (error.code === "ENOENT") {
|
|
1377
|
-
return undefined;
|
|
1378
|
-
}
|
|
1379
|
-
throw error;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
async function writeText(path, text) {
|
|
1383
|
-
await mkdir(dirname(path), { recursive: true });
|
|
1384
|
-
await writeFile(path, text, "utf8");
|
|
1385
|
-
}
|
|
1386
|
-
async function resolveOpenCodeAlternates(opencodeBaseDir, selectedPath) {
|
|
1387
|
-
return resolveConfigAlternates(opencodeBaseDir, selectedPath, OPENCODE_CONFIG_FILE_NAMES);
|
|
1388
|
-
}
|
|
1389
|
-
async function resolveConfigAlternates(baseDir, selectedPath, fileNames) {
|
|
1390
|
-
const alternates = [];
|
|
1391
|
-
for (const candidate of fileNames.map((name) => join(baseDir, name))) {
|
|
1392
|
-
if (candidate !== selectedPath && (await readOptionalText(candidate)) !== undefined) {
|
|
1393
|
-
alternates.push(candidate);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
return alternates;
|
|
1397
|
-
}
|
|
1398
|
-
// END_BLOCK_FILESYSTEM_HELPERS
|
|
23
|
+
export { CLI_NAME, OPENCODE_SCHEMA_URL, OPENCODE_TUI_SCHEMA_URL } from "./opencode/shared-utils.js";
|
|
24
|
+
export { resolvePaths } from "./opencode/paths.js";
|
|
25
|
+
export { ensurePackageConfigText, ensureTuiPackageConfigText, ensureTuiPackageInstalled, ensurePackageInstalled, } from "./opencode/plugin-registration.js";
|
|
26
|
+
export { TUI_PACKAGE_SPECIFIER, MINIMUM_TUI_OPENCODE_VERSION, } from "./opencode/plugin-registration.js";
|
|
27
|
+
export { ensureManagedAgentRegistrationsConfigText, syncManagedAgentRegistrations, installManagedAgentPrompts, syncManagedAgentPrompts, installManagedSkillFiles, syncManagedSkillFiles, ensureManagedSkillSymlink, readManagedAgentModels, readManagedAgentOverrides, writeManagedAgentModel, } from "./opencode/agent-registrations.js";
|
|
28
|
+
export { readOpenCodeAgentModel, readOpenCodeAgentOverride, readOpenCodeDefaultModel, writeOpenCodeAgentModel, writeOpenCodeDefaultModel, writeOpenCodeProviderObject, ensureProviderBaseUrlConfigText, writeProviderBaseUrl, } from "./opencode/model-overrides.js";
|
|
29
|
+
export { readVvocConfig, installVvocConfig, syncVvocConfig, writeGuardianConfig, } from "./opencode/vvoc-config-io.js";
|
|
30
|
+
export { inspectOpenCodeRuntime, isTuiOpenCodeVersionCompatible, extractOpenCodeVersion, inspectInstallation, inspectInstallationForScope, describeWriteResult, } from "./opencode/inspection.js";
|
|
1399
31
|
//# sourceMappingURL=opencode.js.map
|