@nail00749/agent-gvozd 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -11
- package/defaults/agents/back-deep.jsonc +1923 -6
- package/defaults/agents/back-fast.jsonc +1908 -3
- package/defaults/agents/front-deep.jsonc +1978 -17
- package/defaults/agents/front-fast.jsonc +1963 -14
- package/defaults/agents/git.jsonc +20 -0
- package/defaults/agents/master-trusted.jsonc +32 -0
- package/defaults/agents/master.jsonc +0 -1
- package/defaults/agents/review-deep.jsonc +2443 -18
- package/defaults/agents/review-fast.jsonc +2428 -15
- package/defaults/agents/security.jsonc +2213 -29
- package/defaults/prompts/back-deep.md +1 -1
- package/defaults/prompts/back-fast.md +1 -1
- package/defaults/prompts/devops.md +1 -1
- package/defaults/prompts/front-deep.md +1 -1
- package/defaults/prompts/front-fast.md +1 -1
- package/defaults/prompts/master-trusted.md +7 -0
- package/defaults/prompts/master.md +2 -2
- package/defaults/prompts/review-deep.md +3 -1
- package/defaults/prompts/review-fast.md +1 -1
- package/defaults/prompts/security.md +3 -1
- package/dist/cli.js +162 -58
- package/dist/index.js +261 -2
- package/dist/tui.js +956 -0
- package/package.json +13 -3
- package/defaults/agents/verifier.jsonc +0 -1298
package/dist/cli.js
CHANGED
|
@@ -988,6 +988,7 @@ var i = `${styleText("gray", S_BAR)} `;
|
|
|
988
988
|
|
|
989
989
|
// src/cli.ts
|
|
990
990
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
991
|
+
import { join as join11 } from "node:path";
|
|
991
992
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
992
993
|
|
|
993
994
|
// src/config.ts
|
|
@@ -7336,9 +7337,9 @@ function buildAgentPermissions(agent, mcpServers) {
|
|
|
7336
7337
|
|
|
7337
7338
|
// src/release-metadata.ts
|
|
7338
7339
|
var PACKAGE_NAME = "@nail00749/agent-gvozd";
|
|
7339
|
-
var PACKAGE_VERSION = "0.
|
|
7340
|
+
var PACKAGE_VERSION = "0.2.0";
|
|
7340
7341
|
var PACKAGE_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
|
|
7341
|
-
var SUPPORTED_OPENCODE_VERSION = "2.0
|
|
7342
|
+
var SUPPORTED_OPENCODE_VERSION = "2.0.*";
|
|
7342
7343
|
var CONFIG_SCHEMA_VERSION = 2;
|
|
7343
7344
|
|
|
7344
7345
|
// src/constants.ts
|
|
@@ -7568,6 +7569,18 @@ function parseDebugPaths(output) {
|
|
|
7568
7569
|
function parseOpenCodeVersion(output) {
|
|
7569
7570
|
return output.match(/(?:^|\s)v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?=$|\s)/)?.[1];
|
|
7570
7571
|
}
|
|
7572
|
+
function satisfiesOpenCodeRange(version, range) {
|
|
7573
|
+
if (!version)
|
|
7574
|
+
return false;
|
|
7575
|
+
const [prerelease] = version.split("-").slice(1);
|
|
7576
|
+
if (prerelease)
|
|
7577
|
+
return false;
|
|
7578
|
+
if (!range.includes("*"))
|
|
7579
|
+
return version === range;
|
|
7580
|
+
const [rangeMajor, rangeMinor] = range.split(".").slice(0, 2);
|
|
7581
|
+
const [major, minor] = version.split(".").slice(0, 2);
|
|
7582
|
+
return major === rangeMajor && minor === rangeMinor;
|
|
7583
|
+
}
|
|
7571
7584
|
function parseAgentIdentifiers(output) {
|
|
7572
7585
|
try {
|
|
7573
7586
|
const parsed = JSON.parse(output);
|
|
@@ -7766,7 +7779,7 @@ async function runDoctor(input) {
|
|
|
7766
7779
|
const checks = [];
|
|
7767
7780
|
try {
|
|
7768
7781
|
const version = await input.client.version();
|
|
7769
|
-
checks.push(parseOpenCodeVersion(version)
|
|
7782
|
+
checks.push(satisfiesOpenCodeRange(parseOpenCodeVersion(version), supportedVersion) ? { id: "opencode-version", status: "pass", summary: `OpenCode ${supportedVersion} is available` } : { id: "opencode-version", status: "fail", summary: `unsupported OpenCode version: ${redactDiagnostic(version)}`, remediation: `Install OpenCode ${supportedVersion}` });
|
|
7770
7783
|
} catch (error) {
|
|
7771
7784
|
checks.push({ id: "opencode-version", status: "fail", summary: `OpenCode version check failed: ${redactDiagnostic(error)}`, remediation: `Install OpenCode ${supportedVersion}` });
|
|
7772
7785
|
}
|
|
@@ -7871,9 +7884,9 @@ function doctorOperationalFailure(error) {
|
|
|
7871
7884
|
return { schemaVersion: CONFIG_SCHEMA_VERSION, status: "fail", checks };
|
|
7872
7885
|
}
|
|
7873
7886
|
|
|
7874
|
-
// src/cli/
|
|
7875
|
-
import { existsSync as
|
|
7876
|
-
import {
|
|
7887
|
+
// src/cli/agents.ts
|
|
7888
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
7889
|
+
import { join as join7 } from "node:path";
|
|
7877
7890
|
|
|
7878
7891
|
// src/cli/config-store.ts
|
|
7879
7892
|
import { accessSync, closeSync, constants as fsConstants, existsSync as existsSync4, lstatSync as lstatSync5, mkdirSync, openSync, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -7943,8 +7956,8 @@ function secureCanonicalPath(target, label = "Path") {
|
|
|
7943
7956
|
}
|
|
7944
7957
|
|
|
7945
7958
|
// src/cli/config-store.ts
|
|
7946
|
-
var FAST_AGENT_IDS = ["back-fast", "front-fast", "review-fast", "researcher", "git", "docs"
|
|
7947
|
-
var DEEP_AGENT_IDS = ["master", "planner", "back-deep", "front-deep", "review-deep", "debugger", "security", "devops"];
|
|
7959
|
+
var FAST_AGENT_IDS = ["back-fast", "front-fast", "review-fast", "researcher", "git", "docs"];
|
|
7960
|
+
var DEEP_AGENT_IDS = ["master", "master-trusted", "planner", "back-deep", "front-deep", "review-deep", "debugger", "security", "devops"];
|
|
7948
7961
|
var ALL_AGENT_IDS = [...DEEP_AGENT_IDS, ...FAST_AGENT_IDS, "explorer"];
|
|
7949
7962
|
var formattingOptions = { insertSpaces: true, tabSize: 2, eol: `
|
|
7950
7963
|
` };
|
|
@@ -7969,6 +7982,9 @@ function applyModelProfile(source, profile) {
|
|
|
7969
7982
|
updated = setJsonc(updated, ["agents", "explorer", "models"], profile.agentOverrides.explorer ?? profile.fast);
|
|
7970
7983
|
return updated;
|
|
7971
7984
|
}
|
|
7985
|
+
function writeManagedGlobalFile(path, content, expected) {
|
|
7986
|
+
atomicWrite(path, content, expected);
|
|
7987
|
+
}
|
|
7972
7988
|
function matchesSnapshot(path, expected) {
|
|
7973
7989
|
if (!expected.exists)
|
|
7974
7990
|
return !existsSync4(path);
|
|
@@ -8108,6 +8124,51 @@ function writeGlobalConfig(input) {
|
|
|
8108
8124
|
return { configPath, schemaPath, config };
|
|
8109
8125
|
}
|
|
8110
8126
|
|
|
8127
|
+
// src/cli/agents.ts
|
|
8128
|
+
var formattingOptions2 = { insertSpaces: true, tabSize: 2, eol: `
|
|
8129
|
+
` };
|
|
8130
|
+
function assertValidJsonc2(source, label) {
|
|
8131
|
+
const errors = [];
|
|
8132
|
+
const value = parse2(source, errors, { allowTrailingComma: true, disallowComments: false });
|
|
8133
|
+
if (errors.length > 0 || value === undefined || value === null || Array.isArray(value) || typeof value !== "object") {
|
|
8134
|
+
const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
|
|
8135
|
+
throw new Error(`Invalid JSONC in ${label}${details ? `: ${details}` : ""}`);
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
function listAgents(cwd) {
|
|
8139
|
+
const config = loadConfig(cwd, { configRoot: resolveOpenCodeConfigRoot() });
|
|
8140
|
+
return Object.entries(config.agents).map(([id, agent]) => ({
|
|
8141
|
+
id,
|
|
8142
|
+
mode: agent.mode,
|
|
8143
|
+
lease: agent.fileLease,
|
|
8144
|
+
disabled: agent.disabled,
|
|
8145
|
+
model: agent.models[0] ?? ""
|
|
8146
|
+
}));
|
|
8147
|
+
}
|
|
8148
|
+
function setAgentDisabled(source, agentID, disabled) {
|
|
8149
|
+
if (!ALL_AGENT_IDS.includes(agentID)) {
|
|
8150
|
+
throw new Error(`Unknown agent: ${agentID}`);
|
|
8151
|
+
}
|
|
8152
|
+
assertValidJsonc2(source, "global Gvozd config");
|
|
8153
|
+
return applyEdits(source, modify(source, ["agents", agentID, "disabled"], disabled, { formattingOptions: formattingOptions2 }));
|
|
8154
|
+
}
|
|
8155
|
+
function readGlobalConfig(configRoot) {
|
|
8156
|
+
const path = join7(configRoot, "gvozd", "config.jsonc");
|
|
8157
|
+
if (!existsSync5(path))
|
|
8158
|
+
return `{
|
|
8159
|
+
"$schema": "./schema.json",
|
|
8160
|
+
"agents": {}
|
|
8161
|
+
}
|
|
8162
|
+
`;
|
|
8163
|
+
const source = readFileSync5(path, "utf8");
|
|
8164
|
+
assertValidJsonc2(source, path);
|
|
8165
|
+
return source;
|
|
8166
|
+
}
|
|
8167
|
+
|
|
8168
|
+
// src/cli/setup.ts
|
|
8169
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
|
|
8170
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
8171
|
+
|
|
8111
8172
|
// src/cli/configure.ts
|
|
8112
8173
|
function availableProfile(profile, catalog) {
|
|
8113
8174
|
if (!profile)
|
|
@@ -8177,9 +8238,9 @@ function profileFromAgents(agents, catalog) {
|
|
|
8177
8238
|
}
|
|
8178
8239
|
|
|
8179
8240
|
// src/cli/global-sync.ts
|
|
8180
|
-
import { accessSync as accessSync2, closeSync as closeSync2, constants as fsConstants2, existsSync as
|
|
8241
|
+
import { accessSync as accessSync2, closeSync as closeSync2, constants as fsConstants2, existsSync as existsSync6, lstatSync as lstatSync6, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync6, readdirSync as readdirSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8181
8242
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8182
|
-
import { dirname as dirname4, join as
|
|
8243
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
8183
8244
|
function stat(path) {
|
|
8184
8245
|
try {
|
|
8185
8246
|
return lstatSync6(path);
|
|
@@ -8191,7 +8252,7 @@ function stat(path) {
|
|
|
8191
8252
|
}
|
|
8192
8253
|
function assertWriteable2(path, label) {
|
|
8193
8254
|
let candidate = path;
|
|
8194
|
-
while (!
|
|
8255
|
+
while (!existsSync6(candidate)) {
|
|
8195
8256
|
const parent = dirname4(candidate);
|
|
8196
8257
|
if (parent === candidate)
|
|
8197
8258
|
break;
|
|
@@ -8236,7 +8297,7 @@ function replaceFile(path, content) {
|
|
|
8236
8297
|
}
|
|
8237
8298
|
function writeManagedAgents(input) {
|
|
8238
8299
|
const configRoot = secureCanonicalPath(input.configRoot, "OpenCode config root");
|
|
8239
|
-
const agentsDirectory = secureCanonicalPath(
|
|
8300
|
+
const agentsDirectory = secureCanonicalPath(join8(configRoot, "agents"), "OpenCode agents path");
|
|
8240
8301
|
const result = { created: [], updated: [], unchanged: [], conflicts: [], removed: [] };
|
|
8241
8302
|
const writes = [];
|
|
8242
8303
|
const removals = new Map;
|
|
@@ -8255,21 +8316,21 @@ function writeManagedAgents(input) {
|
|
|
8255
8316
|
for (const entry of readdirSync4(agentsDirectory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
8256
8317
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
8257
8318
|
continue;
|
|
8258
|
-
const path =
|
|
8319
|
+
const path = join8(agentsDirectory, entry.name);
|
|
8259
8320
|
const currentStat = stat(path);
|
|
8260
8321
|
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile())
|
|
8261
8322
|
continue;
|
|
8262
|
-
if (hasGeneratedAgentMarker(
|
|
8323
|
+
if (hasGeneratedAgentMarker(readFileSync6(path, "utf8"))) {
|
|
8263
8324
|
assertWriteable2(path, "Managed global agent");
|
|
8264
8325
|
result.removed.push(path);
|
|
8265
|
-
removals.set(path,
|
|
8326
|
+
removals.set(path, readFileSync6(path, "utf8"));
|
|
8266
8327
|
}
|
|
8267
8328
|
}
|
|
8268
8329
|
}
|
|
8269
8330
|
for (const [id, agent] of Object.entries(input.agents).sort(([left], [right]) => left.localeCompare(right))) {
|
|
8270
8331
|
if (agent.disabled)
|
|
8271
8332
|
continue;
|
|
8272
|
-
const path =
|
|
8333
|
+
const path = join8(agentsDirectory, `${id}.md`);
|
|
8273
8334
|
const content = renderAgent(agent);
|
|
8274
8335
|
const currentStat = stat(path);
|
|
8275
8336
|
if (!currentStat) {
|
|
@@ -8281,7 +8342,7 @@ function writeManagedAgents(input) {
|
|
|
8281
8342
|
result.conflicts.push(path);
|
|
8282
8343
|
continue;
|
|
8283
8344
|
}
|
|
8284
|
-
const current =
|
|
8345
|
+
const current = readFileSync6(path, "utf8");
|
|
8285
8346
|
if (!hasGeneratedAgentMarker(current)) {
|
|
8286
8347
|
result.conflicts.push(path);
|
|
8287
8348
|
continue;
|
|
@@ -8319,7 +8380,7 @@ function writeManagedAgents(input) {
|
|
|
8319
8380
|
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile()) {
|
|
8320
8381
|
throw new Error(`Refusing to remove a changed or unmanaged global agent: ${path}`);
|
|
8321
8382
|
}
|
|
8322
|
-
const current =
|
|
8383
|
+
const current = readFileSync6(path, "utf8");
|
|
8323
8384
|
if (!hasGeneratedAgentMarker(current) || current !== removals.get(path)) {
|
|
8324
8385
|
throw new Error(`Refusing to remove a concurrently changed global agent: ${path}`);
|
|
8325
8386
|
}
|
|
@@ -8328,7 +8389,7 @@ function writeManagedAgents(input) {
|
|
|
8328
8389
|
for (const write of writes) {
|
|
8329
8390
|
if (write.replace) {
|
|
8330
8391
|
const currentStat = stat(write.path);
|
|
8331
|
-
const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ?
|
|
8392
|
+
const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ? readFileSync6(write.path, "utf8") : undefined;
|
|
8332
8393
|
if (!current || !hasGeneratedAgentMarker(current) || current !== write.previous) {
|
|
8333
8394
|
throw new Error(`Refusing to replace a concurrently changed global agent: ${write.path}`);
|
|
8334
8395
|
}
|
|
@@ -8341,7 +8402,7 @@ function writeManagedAgents(input) {
|
|
|
8341
8402
|
|
|
8342
8403
|
// src/file-lock.ts
|
|
8343
8404
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8344
|
-
import { accessSync as accessSync3, closeSync as closeSync3, constants, existsSync as
|
|
8405
|
+
import { accessSync as accessSync3, closeSync as closeSync3, constants, existsSync as existsSync7, fstatSync, lstatSync as lstatSync7, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8345
8406
|
import { dirname as dirname5 } from "node:path";
|
|
8346
8407
|
function assertSecure(path) {
|
|
8347
8408
|
const stat = lstatSync7(path);
|
|
@@ -8377,9 +8438,9 @@ function acquire(path, operation) {
|
|
|
8377
8438
|
}
|
|
8378
8439
|
function release(lock) {
|
|
8379
8440
|
closeSync3(lock.descriptor);
|
|
8380
|
-
if (
|
|
8441
|
+
if (existsSync7(lock.path)) {
|
|
8381
8442
|
const current = lstatSync7(lock.path);
|
|
8382
|
-
if (current.isFile() && !current.isSymbolicLink() && current.dev === lock.dev && current.ino === lock.ino &&
|
|
8443
|
+
if (current.isFile() && !current.isSymbolicLink() && current.dev === lock.dev && current.ino === lock.ino && readFileSync7(lock.path, "utf8") === lock.nonce)
|
|
8383
8444
|
unlinkSync3(lock.path);
|
|
8384
8445
|
}
|
|
8385
8446
|
}
|
|
@@ -8402,14 +8463,14 @@ function withExclusiveFileLockSync(path, callback, operation = "operation") {
|
|
|
8402
8463
|
|
|
8403
8464
|
// src/cli/setup.ts
|
|
8404
8465
|
function assertVersion(version) {
|
|
8405
|
-
if (parseOpenCodeVersion(version)
|
|
8466
|
+
if (!satisfiesOpenCodeRange(parseOpenCodeVersion(version), SUPPORTED_OPENCODE_VERSION)) {
|
|
8406
8467
|
throw new Error(`Unsupported OpenCode version. Gvozd ${PACKAGE_VERSION} requires ${SUPPORTED_OPENCODE_VERSION}.`);
|
|
8407
8468
|
}
|
|
8408
8469
|
}
|
|
8409
8470
|
async function selectProfile(input, client, configRoot) {
|
|
8410
8471
|
const catalog = parseModels(await client.models());
|
|
8411
8472
|
const config = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8412
|
-
const hasGlobalConfig =
|
|
8473
|
+
const hasGlobalConfig = existsSync8(join9(configRoot, "gvozd", "config.jsonc"));
|
|
8413
8474
|
return chooseModelProfile({
|
|
8414
8475
|
catalog,
|
|
8415
8476
|
ui: input.ui,
|
|
@@ -8435,6 +8496,22 @@ async function registeredPackageSpecs(client, packageName, target) {
|
|
|
8435
8496
|
const specs = [...new Set(output.match(specPattern) ?? [])];
|
|
8436
8497
|
return specs.filter((spec) => spec !== target);
|
|
8437
8498
|
}
|
|
8499
|
+
async function awaitRegisteredPlugin(client, timeoutMs = 15000) {
|
|
8500
|
+
const target = PACKAGE_SPEC;
|
|
8501
|
+
const started = Date.now();
|
|
8502
|
+
for (;; ) {
|
|
8503
|
+
try {
|
|
8504
|
+
const output = await client.pluginList();
|
|
8505
|
+
const escapedName = PACKAGE_NAME.replace(/[/@]/g, "\\$&");
|
|
8506
|
+
const registered = new RegExp(`${escapedName}(?:@|\\s+)v?${PACKAGE_VERSION}(?=$|[^0-9.])`, "m").test(output);
|
|
8507
|
+
if (registered)
|
|
8508
|
+
return;
|
|
8509
|
+
} catch {}
|
|
8510
|
+
if (Date.now() - started >= timeoutMs)
|
|
8511
|
+
throw new Error(`OpenCode did not report ${target} within ${timeoutMs}ms after the service restart`);
|
|
8512
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8438
8515
|
async function runSetup(input) {
|
|
8439
8516
|
const client = await (input.findClient ?? (() => findOpenCode()))();
|
|
8440
8517
|
const paths = await client.debugPaths();
|
|
@@ -8445,8 +8522,8 @@ async function runSetup(input) {
|
|
|
8445
8522
|
const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
|
|
8446
8523
|
assertVersion(await client.version());
|
|
8447
8524
|
const before = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8448
|
-
const schemaSource =
|
|
8449
|
-
const packagedSchema =
|
|
8525
|
+
const schemaSource = join9(dirname6(before.sources[0]), "schema.json");
|
|
8526
|
+
const packagedSchema = readFileSync8(schemaSource, "utf8");
|
|
8450
8527
|
const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
|
|
8451
8528
|
const preview = writeManagedAgents({ configRoot, agents: before.agents, check: true });
|
|
8452
8529
|
const profile = await selectProfile(input, client, configRoot);
|
|
@@ -8454,17 +8531,17 @@ async function runSetup(input) {
|
|
|
8454
8531
|
return { status: "cancelled" };
|
|
8455
8532
|
input.output?.([
|
|
8456
8533
|
`Register ${PACKAGE_SPEC}`,
|
|
8457
|
-
`Write ${
|
|
8458
|
-
`Write managed agents in ${
|
|
8534
|
+
`Write ${join9(configRoot, "gvozd", "config.jsonc")}`,
|
|
8535
|
+
`Write managed agents in ${join9(configRoot, "agents")}`,
|
|
8459
8536
|
...preview.removed.length > 0 ? [`Remove ${preview.removed.length} stale or disabled managed agent(s)`] : []
|
|
8460
8537
|
].join(`
|
|
8461
8538
|
`));
|
|
8462
8539
|
if (!await confirm2(input, "Run setup?"))
|
|
8463
8540
|
return { status: "cancelled" };
|
|
8464
|
-
return withExclusiveFileLock(
|
|
8541
|
+
return withExclusiveFileLock(join9(configRoot, "gvozd", "setup.lock"), async () => {
|
|
8465
8542
|
if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
|
|
8466
8543
|
throw new Error("OpenCode config root changed while setup awaited the lock");
|
|
8467
|
-
const lockedSchema =
|
|
8544
|
+
const lockedSchema = readFileSync8(schemaSource, "utf8");
|
|
8468
8545
|
const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
|
|
8469
8546
|
if (!sameSnapshot(previewSnapshot, snapshot))
|
|
8470
8547
|
throw new Error("Global Gvozd configuration changed while setup awaited confirmation; review and rerun setup");
|
|
@@ -8486,6 +8563,7 @@ async function runSetup(input) {
|
|
|
8486
8563
|
const configured = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8487
8564
|
writeManagedAgents({ configRoot, agents: configured.agents });
|
|
8488
8565
|
await client.serviceRestart();
|
|
8566
|
+
await awaitRegisteredPlugin(client);
|
|
8489
8567
|
const report = await runDoctor({ client, configRoot, runtimeConfigRoot, cwd: input.cwd });
|
|
8490
8568
|
return { status: "complete", report };
|
|
8491
8569
|
} catch (error) {
|
|
@@ -8504,16 +8582,16 @@ async function runConfigure(input) {
|
|
|
8504
8582
|
const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
|
|
8505
8583
|
assertVersion(await client.version());
|
|
8506
8584
|
const config = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8507
|
-
const schemaSource =
|
|
8508
|
-
const packagedSchema =
|
|
8585
|
+
const schemaSource = join9(dirname6(config.sources[0]), "schema.json");
|
|
8586
|
+
const packagedSchema = readFileSync8(schemaSource, "utf8");
|
|
8509
8587
|
const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
|
|
8510
8588
|
const profile = await selectProfile(input, client, configRoot);
|
|
8511
8589
|
if (!profile || !await confirm2(input, "Apply model configuration?"))
|
|
8512
8590
|
return { status: "cancelled" };
|
|
8513
|
-
return withExclusiveFileLock(
|
|
8591
|
+
return withExclusiveFileLock(join9(configRoot, "gvozd", "setup.lock"), async () => {
|
|
8514
8592
|
if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
|
|
8515
8593
|
throw new Error("OpenCode config root changed while configuration awaited the lock");
|
|
8516
|
-
const lockedSchema =
|
|
8594
|
+
const lockedSchema = readFileSync8(schemaSource, "utf8");
|
|
8517
8595
|
const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
|
|
8518
8596
|
if (!sameSnapshot(previewSnapshot, snapshot))
|
|
8519
8597
|
throw new Error("Global Gvozd configuration changed while configuration awaited confirmation; review and rerun");
|
|
@@ -8537,13 +8615,13 @@ function setupExitCode(result) {
|
|
|
8537
8615
|
}
|
|
8538
8616
|
|
|
8539
8617
|
// src/sync.ts
|
|
8540
|
-
import { closeSync as closeSync4, lstatSync as lstatSync8, mkdirSync as mkdirSync4, openSync as openSync4, readFileSync as
|
|
8618
|
+
import { closeSync as closeSync4, lstatSync as lstatSync8, mkdirSync as mkdirSync4, openSync as openSync4, readFileSync as readFileSync9, readdirSync as readdirSync5, realpathSync as realpathSync4, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8541
8619
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
8542
|
-
import { basename, dirname as dirname7, join as
|
|
8620
|
+
import { basename, dirname as dirname7, join as join10, relative as relative4 } from "node:path";
|
|
8543
8621
|
function renderPluginEntrypoint(config, destination) {
|
|
8544
8622
|
let moduleSpecifier = "agent-gvozd/server";
|
|
8545
8623
|
if (realpathSync4(config.packageRoot) === realpathSync4(config.projectRoot)) {
|
|
8546
|
-
moduleSpecifier = relative4(destination,
|
|
8624
|
+
moduleSpecifier = relative4(destination, join10(config.packageRoot, "src", "index")).replaceAll("\\", "/");
|
|
8547
8625
|
if (!moduleSpecifier.startsWith("."))
|
|
8548
8626
|
moduleSpecifier = `./${moduleSpecifier}`;
|
|
8549
8627
|
}
|
|
@@ -8588,7 +8666,7 @@ function stat2(path) {
|
|
|
8588
8666
|
function safeDirectory(root, segments, create) {
|
|
8589
8667
|
let current = realpathSync4(root);
|
|
8590
8668
|
for (const segment of segments) {
|
|
8591
|
-
current =
|
|
8669
|
+
current = join10(current, segment);
|
|
8592
8670
|
const currentStat = stat2(current);
|
|
8593
8671
|
if (currentStat) {
|
|
8594
8672
|
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
|
|
@@ -8632,7 +8710,7 @@ function replaceFile2(path, content) {
|
|
|
8632
8710
|
function planProjectTemplate(config, result, check) {
|
|
8633
8711
|
const directory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], !check);
|
|
8634
8712
|
const writes = [];
|
|
8635
|
-
const configPath =
|
|
8713
|
+
const configPath = join10(directory, "config.jsonc");
|
|
8636
8714
|
if (!assertRegularFile(configPath)) {
|
|
8637
8715
|
result.created.push(configPath);
|
|
8638
8716
|
writes.push({
|
|
@@ -8649,14 +8727,14 @@ function planProjectTemplate(config, result, check) {
|
|
|
8649
8727
|
`)
|
|
8650
8728
|
});
|
|
8651
8729
|
}
|
|
8652
|
-
const schemaSource =
|
|
8653
|
-
const schemaTarget =
|
|
8654
|
-
const schema =
|
|
8730
|
+
const schemaSource = join10(dirname7(config.sources[0]), "schema.json");
|
|
8731
|
+
const schemaTarget = join10(directory, "schema.json");
|
|
8732
|
+
const schema = readFileSync9(schemaSource, "utf8");
|
|
8655
8733
|
if (!assertRegularFile(schemaTarget)) {
|
|
8656
8734
|
result.created.push(schemaTarget);
|
|
8657
8735
|
writes.push({ target: schemaTarget, content: schema, replace: false });
|
|
8658
8736
|
} else {
|
|
8659
|
-
const current =
|
|
8737
|
+
const current = readFileSync9(schemaTarget, "utf8");
|
|
8660
8738
|
if (current !== schema) {
|
|
8661
8739
|
if (!hasGeneratedSchemaMarker(current) && !isEquivalentLegacySchema(current, schema)) {
|
|
8662
8740
|
throw new Error(`Refusing to overwrite an unmanaged project schema: ${schemaTarget}`);
|
|
@@ -8681,8 +8759,8 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8681
8759
|
for (const entry of readdirSync5(destination, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
8682
8760
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
8683
8761
|
continue;
|
|
8684
|
-
const target =
|
|
8685
|
-
const current =
|
|
8762
|
+
const target = join10(destination, entry.name);
|
|
8763
|
+
const current = readFileSync9(target, "utf8");
|
|
8686
8764
|
if (!hasGeneratedAgentMarker(current))
|
|
8687
8765
|
continue;
|
|
8688
8766
|
result.removed.push(target);
|
|
@@ -8693,14 +8771,14 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8693
8771
|
for (const [id, agent] of Object.entries(config.agents)) {
|
|
8694
8772
|
if (agent.disabled)
|
|
8695
8773
|
continue;
|
|
8696
|
-
const target =
|
|
8774
|
+
const target = join10(destination, `${id}.md`);
|
|
8697
8775
|
const content = renderAgent(agent);
|
|
8698
8776
|
if (!assertRegularFile(target)) {
|
|
8699
8777
|
result.created.push(target);
|
|
8700
8778
|
writes.push({ target, content, replace: false });
|
|
8701
8779
|
continue;
|
|
8702
8780
|
}
|
|
8703
|
-
const current =
|
|
8781
|
+
const current = readFileSync9(target, "utf8");
|
|
8704
8782
|
if (current === content) {
|
|
8705
8783
|
result.unchanged.push(target);
|
|
8706
8784
|
continue;
|
|
@@ -8712,11 +8790,11 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8712
8790
|
options.onDiff?.(renderDiff(target, current, content));
|
|
8713
8791
|
writes.push({ target, content, replace: true, previous: current });
|
|
8714
8792
|
}
|
|
8715
|
-
const pluginTarget =
|
|
8793
|
+
const pluginTarget = join10(pluginDestination, "index.ts");
|
|
8716
8794
|
const pluginContent = renderPluginEntrypoint(config, pluginDestination);
|
|
8717
8795
|
if (!options.devPlugin) {
|
|
8718
8796
|
if (assertRegularFile(pluginTarget)) {
|
|
8719
|
-
const current =
|
|
8797
|
+
const current = readFileSync9(pluginTarget, "utf8");
|
|
8720
8798
|
if (hasGeneratedPluginMarker(current)) {
|
|
8721
8799
|
result.removed.push(pluginTarget);
|
|
8722
8800
|
removals.set(pluginTarget, current);
|
|
@@ -8727,7 +8805,7 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8727
8805
|
result.created.push(pluginTarget);
|
|
8728
8806
|
pluginWrite = { target: pluginTarget, content: pluginContent, replace: false };
|
|
8729
8807
|
} else {
|
|
8730
|
-
const current =
|
|
8808
|
+
const current = readFileSync9(pluginTarget, "utf8");
|
|
8731
8809
|
if (current === pluginContent) {
|
|
8732
8810
|
result.unchanged.push(pluginTarget);
|
|
8733
8811
|
} else {
|
|
@@ -8746,7 +8824,7 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8746
8824
|
if (!assertRegularFile(target)) {
|
|
8747
8825
|
throw new Error(`Refusing to remove a changed or unsafe generated file: ${target}`);
|
|
8748
8826
|
}
|
|
8749
|
-
const current =
|
|
8827
|
+
const current = readFileSync9(target, "utf8");
|
|
8750
8828
|
const generated = target.endsWith("index.ts") ? hasGeneratedPluginMarker(current) : hasGeneratedAgentMarker(current);
|
|
8751
8829
|
if (!generated || current !== removals.get(target)) {
|
|
8752
8830
|
throw new Error(`Refusing to remove a concurrently changed generated file: ${target}`);
|
|
@@ -8755,27 +8833,27 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8755
8833
|
}
|
|
8756
8834
|
for (const write of writes) {
|
|
8757
8835
|
if (!write.replace) {
|
|
8758
|
-
createFile2(
|
|
8836
|
+
createFile2(join10(writableDestination, basename(write.target)), write.content);
|
|
8759
8837
|
continue;
|
|
8760
8838
|
}
|
|
8761
8839
|
if (!assertRegularFile(write.target)) {
|
|
8762
8840
|
throw new Error(`Refusing to replace a changed or unsafe agent file: ${write.target}`);
|
|
8763
8841
|
}
|
|
8764
|
-
const current =
|
|
8842
|
+
const current = readFileSync9(write.target, "utf8");
|
|
8765
8843
|
if (!hasGeneratedAgentMarker(current) || current !== write.previous) {
|
|
8766
8844
|
throw new Error(`Refusing to replace a concurrently changed agent file: ${write.target}`);
|
|
8767
8845
|
}
|
|
8768
8846
|
replaceFile2(write.target, write.content);
|
|
8769
8847
|
}
|
|
8770
8848
|
if (pluginWrite) {
|
|
8771
|
-
const target =
|
|
8849
|
+
const target = join10(writablePluginDestination, basename(pluginWrite.target));
|
|
8772
8850
|
if (!pluginWrite.replace) {
|
|
8773
8851
|
createFile2(target, pluginWrite.content);
|
|
8774
8852
|
} else {
|
|
8775
8853
|
if (!assertRegularFile(target)) {
|
|
8776
8854
|
throw new Error(`Refusing to replace a changed or unsafe plugin entrypoint: ${target}`);
|
|
8777
8855
|
}
|
|
8778
|
-
const current =
|
|
8856
|
+
const current = readFileSync9(target, "utf8");
|
|
8779
8857
|
if (!hasGeneratedPluginMarker(current) || current !== pluginWrite.previous) {
|
|
8780
8858
|
throw new Error(`Refusing to replace a concurrently changed plugin entrypoint: ${target}`);
|
|
8781
8859
|
}
|
|
@@ -8784,11 +8862,11 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8784
8862
|
}
|
|
8785
8863
|
const templateDirectory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], true);
|
|
8786
8864
|
for (const write of templateWrites) {
|
|
8787
|
-
const target =
|
|
8865
|
+
const target = join10(templateDirectory, basename(write.target));
|
|
8788
8866
|
if (!write.replace)
|
|
8789
8867
|
createFile2(target, write.content);
|
|
8790
8868
|
else {
|
|
8791
|
-
if (!assertRegularFile(target) ||
|
|
8869
|
+
if (!assertRegularFile(target) || readFileSync9(target, "utf8") !== write.previous) {
|
|
8792
8870
|
throw new Error(`Refusing to replace a concurrently changed project schema: ${target}`);
|
|
8793
8871
|
}
|
|
8794
8872
|
replaceFile2(target, write.content);
|
|
@@ -8803,7 +8881,7 @@ function syncAgents(config, options = {}) {
|
|
|
8803
8881
|
if (options.check)
|
|
8804
8882
|
return syncAgentsUnlocked(config, options);
|
|
8805
8883
|
const root = realpathSync4(config.projectRoot);
|
|
8806
|
-
return withExclusiveFileLockSync(
|
|
8884
|
+
return withExclusiveFileLockSync(join10(root, ".agent-gvozd-sync.lock"), () => syncAgentsUnlocked(config, options), "sync");
|
|
8807
8885
|
}
|
|
8808
8886
|
function formatSyncResult(result, check) {
|
|
8809
8887
|
const lines = [check ? "agent-gvozd sync check" : "agent-gvozd sync complete"];
|
|
@@ -8838,6 +8916,8 @@ var HELP = [
|
|
|
8838
8916
|
"",
|
|
8839
8917
|
"Commands:",
|
|
8840
8918
|
" setup [--yes] Install or upgrade the global agent team",
|
|
8919
|
+
" agents [list] Show the resolved agent team",
|
|
8920
|
+
" agents disable <id> | enable <id> Toggle an agent in the global config",
|
|
8841
8921
|
" config [--yes] Configure model preferences",
|
|
8842
8922
|
" doctor [--json] Diagnose the global installation",
|
|
8843
8923
|
" sync [--check] [--dev-plugin] Maintain the project-local installation",
|
|
@@ -8922,6 +9002,30 @@ async function runCli(args, io = defaultIO, commands = { setup: runSetup, config
|
|
|
8922
9002
|
io.stdout(formatSyncResult(result, check));
|
|
8923
9003
|
return check && result.created.length + result.updated.length + result.removed.length > 0 ? 1 : 0;
|
|
8924
9004
|
}
|
|
9005
|
+
if (command === "agents") {
|
|
9006
|
+
const [action, agentID] = rest;
|
|
9007
|
+
if (!action || action === "list") {
|
|
9008
|
+
if (rest.length > 1)
|
|
9009
|
+
return usage(io);
|
|
9010
|
+
const rows = listAgents(io.cwd());
|
|
9011
|
+
for (const row of rows) {
|
|
9012
|
+
io.stdout(`${row.disabled ? "✗" : " "} ${row.id.padEnd(12)} ${row.mode.padEnd(8)} ${row.lease.padEnd(12)} ${row.model}`);
|
|
9013
|
+
}
|
|
9014
|
+
return 0;
|
|
9015
|
+
}
|
|
9016
|
+
if (action === "disable" || action === "enable") {
|
|
9017
|
+
if (agentID === undefined || agentID.startsWith("--") || rest.length > 2)
|
|
9018
|
+
return usage(io);
|
|
9019
|
+
const configRoot = resolveOpenCodeConfigRoot();
|
|
9020
|
+
preflightGlobalConfig(configRoot);
|
|
9021
|
+
const snapshot2 = snapshot(join11(configRoot, "gvozd", "config.jsonc"));
|
|
9022
|
+
const next = setAgentDisabled(readGlobalConfig(configRoot), agentID, action === "disable");
|
|
9023
|
+
writeManagedGlobalFile(join11(configRoot, "gvozd", "config.jsonc"), next, snapshot2);
|
|
9024
|
+
io.stdout(`${action}d ${agentID}; run gvozd sync and gvozd doctor to apply`);
|
|
9025
|
+
return 0;
|
|
9026
|
+
}
|
|
9027
|
+
return usage(io);
|
|
9028
|
+
}
|
|
8925
9029
|
if (command === "trust-project") {
|
|
8926
9030
|
const parsed = parseFlags(rest, []);
|
|
8927
9031
|
if (!parsed || parsed.positional.length > 1)
|