@nail00749/agent-gvozd 0.1.7 → 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 +174 -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);
|
|
@@ -7593,6 +7606,9 @@ function createClient(executable, runner) {
|
|
|
7593
7606
|
async pluginAdd(spec) {
|
|
7594
7607
|
await checked(runner, executable, ["plugin", "add", spec], 60000);
|
|
7595
7608
|
},
|
|
7609
|
+
async pluginRemove(spec) {
|
|
7610
|
+
await checked(runner, executable, ["plugin", "remove", spec]);
|
|
7611
|
+
},
|
|
7596
7612
|
pluginList() {
|
|
7597
7613
|
return checked(runner, executable, ["plugin", "list"]);
|
|
7598
7614
|
},
|
|
@@ -7763,7 +7779,7 @@ async function runDoctor(input) {
|
|
|
7763
7779
|
const checks = [];
|
|
7764
7780
|
try {
|
|
7765
7781
|
const version = await input.client.version();
|
|
7766
|
-
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}` });
|
|
7767
7783
|
} catch (error) {
|
|
7768
7784
|
checks.push({ id: "opencode-version", status: "fail", summary: `OpenCode version check failed: ${redactDiagnostic(error)}`, remediation: `Install OpenCode ${supportedVersion}` });
|
|
7769
7785
|
}
|
|
@@ -7868,9 +7884,9 @@ function doctorOperationalFailure(error) {
|
|
|
7868
7884
|
return { schemaVersion: CONFIG_SCHEMA_VERSION, status: "fail", checks };
|
|
7869
7885
|
}
|
|
7870
7886
|
|
|
7871
|
-
// src/cli/
|
|
7872
|
-
import { existsSync as
|
|
7873
|
-
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";
|
|
7874
7890
|
|
|
7875
7891
|
// src/cli/config-store.ts
|
|
7876
7892
|
import { accessSync, closeSync, constants as fsConstants, existsSync as existsSync4, lstatSync as lstatSync5, mkdirSync, openSync, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -7940,8 +7956,8 @@ function secureCanonicalPath(target, label = "Path") {
|
|
|
7940
7956
|
}
|
|
7941
7957
|
|
|
7942
7958
|
// src/cli/config-store.ts
|
|
7943
|
-
var FAST_AGENT_IDS = ["back-fast", "front-fast", "review-fast", "researcher", "git", "docs"
|
|
7944
|
-
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"];
|
|
7945
7961
|
var ALL_AGENT_IDS = [...DEEP_AGENT_IDS, ...FAST_AGENT_IDS, "explorer"];
|
|
7946
7962
|
var formattingOptions = { insertSpaces: true, tabSize: 2, eol: `
|
|
7947
7963
|
` };
|
|
@@ -7966,6 +7982,9 @@ function applyModelProfile(source, profile) {
|
|
|
7966
7982
|
updated = setJsonc(updated, ["agents", "explorer", "models"], profile.agentOverrides.explorer ?? profile.fast);
|
|
7967
7983
|
return updated;
|
|
7968
7984
|
}
|
|
7985
|
+
function writeManagedGlobalFile(path, content, expected) {
|
|
7986
|
+
atomicWrite(path, content, expected);
|
|
7987
|
+
}
|
|
7969
7988
|
function matchesSnapshot(path, expected) {
|
|
7970
7989
|
if (!expected.exists)
|
|
7971
7990
|
return !existsSync4(path);
|
|
@@ -8105,6 +8124,51 @@ function writeGlobalConfig(input) {
|
|
|
8105
8124
|
return { configPath, schemaPath, config };
|
|
8106
8125
|
}
|
|
8107
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
|
+
|
|
8108
8172
|
// src/cli/configure.ts
|
|
8109
8173
|
function availableProfile(profile, catalog) {
|
|
8110
8174
|
if (!profile)
|
|
@@ -8174,9 +8238,9 @@ function profileFromAgents(agents, catalog) {
|
|
|
8174
8238
|
}
|
|
8175
8239
|
|
|
8176
8240
|
// src/cli/global-sync.ts
|
|
8177
|
-
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";
|
|
8178
8242
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8179
|
-
import { dirname as dirname4, join as
|
|
8243
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
8180
8244
|
function stat(path) {
|
|
8181
8245
|
try {
|
|
8182
8246
|
return lstatSync6(path);
|
|
@@ -8188,7 +8252,7 @@ function stat(path) {
|
|
|
8188
8252
|
}
|
|
8189
8253
|
function assertWriteable2(path, label) {
|
|
8190
8254
|
let candidate = path;
|
|
8191
|
-
while (!
|
|
8255
|
+
while (!existsSync6(candidate)) {
|
|
8192
8256
|
const parent = dirname4(candidate);
|
|
8193
8257
|
if (parent === candidate)
|
|
8194
8258
|
break;
|
|
@@ -8233,7 +8297,7 @@ function replaceFile(path, content) {
|
|
|
8233
8297
|
}
|
|
8234
8298
|
function writeManagedAgents(input) {
|
|
8235
8299
|
const configRoot = secureCanonicalPath(input.configRoot, "OpenCode config root");
|
|
8236
|
-
const agentsDirectory = secureCanonicalPath(
|
|
8300
|
+
const agentsDirectory = secureCanonicalPath(join8(configRoot, "agents"), "OpenCode agents path");
|
|
8237
8301
|
const result = { created: [], updated: [], unchanged: [], conflicts: [], removed: [] };
|
|
8238
8302
|
const writes = [];
|
|
8239
8303
|
const removals = new Map;
|
|
@@ -8252,21 +8316,21 @@ function writeManagedAgents(input) {
|
|
|
8252
8316
|
for (const entry of readdirSync4(agentsDirectory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
8253
8317
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
8254
8318
|
continue;
|
|
8255
|
-
const path =
|
|
8319
|
+
const path = join8(agentsDirectory, entry.name);
|
|
8256
8320
|
const currentStat = stat(path);
|
|
8257
8321
|
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile())
|
|
8258
8322
|
continue;
|
|
8259
|
-
if (hasGeneratedAgentMarker(
|
|
8323
|
+
if (hasGeneratedAgentMarker(readFileSync6(path, "utf8"))) {
|
|
8260
8324
|
assertWriteable2(path, "Managed global agent");
|
|
8261
8325
|
result.removed.push(path);
|
|
8262
|
-
removals.set(path,
|
|
8326
|
+
removals.set(path, readFileSync6(path, "utf8"));
|
|
8263
8327
|
}
|
|
8264
8328
|
}
|
|
8265
8329
|
}
|
|
8266
8330
|
for (const [id, agent] of Object.entries(input.agents).sort(([left], [right]) => left.localeCompare(right))) {
|
|
8267
8331
|
if (agent.disabled)
|
|
8268
8332
|
continue;
|
|
8269
|
-
const path =
|
|
8333
|
+
const path = join8(agentsDirectory, `${id}.md`);
|
|
8270
8334
|
const content = renderAgent(agent);
|
|
8271
8335
|
const currentStat = stat(path);
|
|
8272
8336
|
if (!currentStat) {
|
|
@@ -8278,7 +8342,7 @@ function writeManagedAgents(input) {
|
|
|
8278
8342
|
result.conflicts.push(path);
|
|
8279
8343
|
continue;
|
|
8280
8344
|
}
|
|
8281
|
-
const current =
|
|
8345
|
+
const current = readFileSync6(path, "utf8");
|
|
8282
8346
|
if (!hasGeneratedAgentMarker(current)) {
|
|
8283
8347
|
result.conflicts.push(path);
|
|
8284
8348
|
continue;
|
|
@@ -8316,7 +8380,7 @@ function writeManagedAgents(input) {
|
|
|
8316
8380
|
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile()) {
|
|
8317
8381
|
throw new Error(`Refusing to remove a changed or unmanaged global agent: ${path}`);
|
|
8318
8382
|
}
|
|
8319
|
-
const current =
|
|
8383
|
+
const current = readFileSync6(path, "utf8");
|
|
8320
8384
|
if (!hasGeneratedAgentMarker(current) || current !== removals.get(path)) {
|
|
8321
8385
|
throw new Error(`Refusing to remove a concurrently changed global agent: ${path}`);
|
|
8322
8386
|
}
|
|
@@ -8325,7 +8389,7 @@ function writeManagedAgents(input) {
|
|
|
8325
8389
|
for (const write of writes) {
|
|
8326
8390
|
if (write.replace) {
|
|
8327
8391
|
const currentStat = stat(write.path);
|
|
8328
|
-
const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ?
|
|
8392
|
+
const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ? readFileSync6(write.path, "utf8") : undefined;
|
|
8329
8393
|
if (!current || !hasGeneratedAgentMarker(current) || current !== write.previous) {
|
|
8330
8394
|
throw new Error(`Refusing to replace a concurrently changed global agent: ${write.path}`);
|
|
8331
8395
|
}
|
|
@@ -8338,7 +8402,7 @@ function writeManagedAgents(input) {
|
|
|
8338
8402
|
|
|
8339
8403
|
// src/file-lock.ts
|
|
8340
8404
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8341
|
-
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";
|
|
8342
8406
|
import { dirname as dirname5 } from "node:path";
|
|
8343
8407
|
function assertSecure(path) {
|
|
8344
8408
|
const stat = lstatSync7(path);
|
|
@@ -8374,9 +8438,9 @@ function acquire(path, operation) {
|
|
|
8374
8438
|
}
|
|
8375
8439
|
function release(lock) {
|
|
8376
8440
|
closeSync3(lock.descriptor);
|
|
8377
|
-
if (
|
|
8441
|
+
if (existsSync7(lock.path)) {
|
|
8378
8442
|
const current = lstatSync7(lock.path);
|
|
8379
|
-
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)
|
|
8380
8444
|
unlinkSync3(lock.path);
|
|
8381
8445
|
}
|
|
8382
8446
|
}
|
|
@@ -8399,14 +8463,14 @@ function withExclusiveFileLockSync(path, callback, operation = "operation") {
|
|
|
8399
8463
|
|
|
8400
8464
|
// src/cli/setup.ts
|
|
8401
8465
|
function assertVersion(version) {
|
|
8402
|
-
if (parseOpenCodeVersion(version)
|
|
8466
|
+
if (!satisfiesOpenCodeRange(parseOpenCodeVersion(version), SUPPORTED_OPENCODE_VERSION)) {
|
|
8403
8467
|
throw new Error(`Unsupported OpenCode version. Gvozd ${PACKAGE_VERSION} requires ${SUPPORTED_OPENCODE_VERSION}.`);
|
|
8404
8468
|
}
|
|
8405
8469
|
}
|
|
8406
8470
|
async function selectProfile(input, client, configRoot) {
|
|
8407
8471
|
const catalog = parseModels(await client.models());
|
|
8408
8472
|
const config = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8409
|
-
const hasGlobalConfig =
|
|
8473
|
+
const hasGlobalConfig = existsSync8(join9(configRoot, "gvozd", "config.jsonc"));
|
|
8410
8474
|
return chooseModelProfile({
|
|
8411
8475
|
catalog,
|
|
8412
8476
|
ui: input.ui,
|
|
@@ -8426,6 +8490,28 @@ async function confirm2(input, message) {
|
|
|
8426
8490
|
function sameSnapshot(left, right) {
|
|
8427
8491
|
return left.configRoot === right.configRoot && left.config.exists === right.config.exists && left.config.dev === right.config.dev && left.config.ino === right.config.ino && left.config.bytes === right.config.bytes && left.schema.exists === right.schema.exists && left.schema.dev === right.schema.dev && left.schema.ino === right.schema.ino && left.schema.bytes === right.schema.bytes;
|
|
8428
8492
|
}
|
|
8493
|
+
async function registeredPackageSpecs(client, packageName, target) {
|
|
8494
|
+
const output = await client.pluginList();
|
|
8495
|
+
const specPattern = new RegExp(`${packageName.replace(/[/@]/g, "\\$&")}@[^\\s]+`, "g");
|
|
8496
|
+
const specs = [...new Set(output.match(specPattern) ?? [])];
|
|
8497
|
+
return specs.filter((spec) => spec !== target);
|
|
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
|
+
}
|
|
8429
8515
|
async function runSetup(input) {
|
|
8430
8516
|
const client = await (input.findClient ?? (() => findOpenCode()))();
|
|
8431
8517
|
const paths = await client.debugPaths();
|
|
@@ -8436,8 +8522,8 @@ async function runSetup(input) {
|
|
|
8436
8522
|
const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
|
|
8437
8523
|
assertVersion(await client.version());
|
|
8438
8524
|
const before = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8439
|
-
const schemaSource =
|
|
8440
|
-
const packagedSchema =
|
|
8525
|
+
const schemaSource = join9(dirname6(before.sources[0]), "schema.json");
|
|
8526
|
+
const packagedSchema = readFileSync8(schemaSource, "utf8");
|
|
8441
8527
|
const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
|
|
8442
8528
|
const preview = writeManagedAgents({ configRoot, agents: before.agents, check: true });
|
|
8443
8529
|
const profile = await selectProfile(input, client, configRoot);
|
|
@@ -8445,17 +8531,17 @@ async function runSetup(input) {
|
|
|
8445
8531
|
return { status: "cancelled" };
|
|
8446
8532
|
input.output?.([
|
|
8447
8533
|
`Register ${PACKAGE_SPEC}`,
|
|
8448
|
-
`Write ${
|
|
8449
|
-
`Write managed agents in ${
|
|
8534
|
+
`Write ${join9(configRoot, "gvozd", "config.jsonc")}`,
|
|
8535
|
+
`Write managed agents in ${join9(configRoot, "agents")}`,
|
|
8450
8536
|
...preview.removed.length > 0 ? [`Remove ${preview.removed.length} stale or disabled managed agent(s)`] : []
|
|
8451
8537
|
].join(`
|
|
8452
8538
|
`));
|
|
8453
8539
|
if (!await confirm2(input, "Run setup?"))
|
|
8454
8540
|
return { status: "cancelled" };
|
|
8455
|
-
return withExclusiveFileLock(
|
|
8541
|
+
return withExclusiveFileLock(join9(configRoot, "gvozd", "setup.lock"), async () => {
|
|
8456
8542
|
if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
|
|
8457
8543
|
throw new Error("OpenCode config root changed while setup awaited the lock");
|
|
8458
|
-
const lockedSchema =
|
|
8544
|
+
const lockedSchema = readFileSync8(schemaSource, "utf8");
|
|
8459
8545
|
const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
|
|
8460
8546
|
if (!sameSnapshot(previewSnapshot, snapshot))
|
|
8461
8547
|
throw new Error("Global Gvozd configuration changed while setup awaited confirmation; review and rerun setup");
|
|
@@ -8468,12 +8554,16 @@ async function runSetup(input) {
|
|
|
8468
8554
|
throw new Error("Global Gvozd configuration changed during locked setup revalidation; review and rerun setup");
|
|
8469
8555
|
}
|
|
8470
8556
|
writeManagedAgents({ configRoot, agents: lockedBefore.agents, check: true });
|
|
8557
|
+
for (const stale of await registeredPackageSpecs(client, PACKAGE_NAME, PACKAGE_SPEC)) {
|
|
8558
|
+
await client.pluginRemove(stale);
|
|
8559
|
+
}
|
|
8471
8560
|
await client.pluginAdd(PACKAGE_SPEC);
|
|
8472
8561
|
try {
|
|
8473
8562
|
writeGlobalConfig({ configRoot, profile: lockedProfile, schemaSource: lockedSchema, snapshot });
|
|
8474
8563
|
const configured = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8475
8564
|
writeManagedAgents({ configRoot, agents: configured.agents });
|
|
8476
8565
|
await client.serviceRestart();
|
|
8566
|
+
await awaitRegisteredPlugin(client);
|
|
8477
8567
|
const report = await runDoctor({ client, configRoot, runtimeConfigRoot, cwd: input.cwd });
|
|
8478
8568
|
return { status: "complete", report };
|
|
8479
8569
|
} catch (error) {
|
|
@@ -8492,16 +8582,16 @@ async function runConfigure(input) {
|
|
|
8492
8582
|
const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
|
|
8493
8583
|
assertVersion(await client.version());
|
|
8494
8584
|
const config = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8495
|
-
const schemaSource =
|
|
8496
|
-
const packagedSchema =
|
|
8585
|
+
const schemaSource = join9(dirname6(config.sources[0]), "schema.json");
|
|
8586
|
+
const packagedSchema = readFileSync8(schemaSource, "utf8");
|
|
8497
8587
|
const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
|
|
8498
8588
|
const profile = await selectProfile(input, client, configRoot);
|
|
8499
8589
|
if (!profile || !await confirm2(input, "Apply model configuration?"))
|
|
8500
8590
|
return { status: "cancelled" };
|
|
8501
|
-
return withExclusiveFileLock(
|
|
8591
|
+
return withExclusiveFileLock(join9(configRoot, "gvozd", "setup.lock"), async () => {
|
|
8502
8592
|
if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
|
|
8503
8593
|
throw new Error("OpenCode config root changed while configuration awaited the lock");
|
|
8504
|
-
const lockedSchema =
|
|
8594
|
+
const lockedSchema = readFileSync8(schemaSource, "utf8");
|
|
8505
8595
|
const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
|
|
8506
8596
|
if (!sameSnapshot(previewSnapshot, snapshot))
|
|
8507
8597
|
throw new Error("Global Gvozd configuration changed while configuration awaited confirmation; review and rerun");
|
|
@@ -8525,13 +8615,13 @@ function setupExitCode(result) {
|
|
|
8525
8615
|
}
|
|
8526
8616
|
|
|
8527
8617
|
// src/sync.ts
|
|
8528
|
-
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";
|
|
8529
8619
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
8530
|
-
import { basename, dirname as dirname7, join as
|
|
8620
|
+
import { basename, dirname as dirname7, join as join10, relative as relative4 } from "node:path";
|
|
8531
8621
|
function renderPluginEntrypoint(config, destination) {
|
|
8532
8622
|
let moduleSpecifier = "agent-gvozd/server";
|
|
8533
8623
|
if (realpathSync4(config.packageRoot) === realpathSync4(config.projectRoot)) {
|
|
8534
|
-
moduleSpecifier = relative4(destination,
|
|
8624
|
+
moduleSpecifier = relative4(destination, join10(config.packageRoot, "src", "index")).replaceAll("\\", "/");
|
|
8535
8625
|
if (!moduleSpecifier.startsWith("."))
|
|
8536
8626
|
moduleSpecifier = `./${moduleSpecifier}`;
|
|
8537
8627
|
}
|
|
@@ -8576,7 +8666,7 @@ function stat2(path) {
|
|
|
8576
8666
|
function safeDirectory(root, segments, create) {
|
|
8577
8667
|
let current = realpathSync4(root);
|
|
8578
8668
|
for (const segment of segments) {
|
|
8579
|
-
current =
|
|
8669
|
+
current = join10(current, segment);
|
|
8580
8670
|
const currentStat = stat2(current);
|
|
8581
8671
|
if (currentStat) {
|
|
8582
8672
|
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
|
|
@@ -8620,7 +8710,7 @@ function replaceFile2(path, content) {
|
|
|
8620
8710
|
function planProjectTemplate(config, result, check) {
|
|
8621
8711
|
const directory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], !check);
|
|
8622
8712
|
const writes = [];
|
|
8623
|
-
const configPath =
|
|
8713
|
+
const configPath = join10(directory, "config.jsonc");
|
|
8624
8714
|
if (!assertRegularFile(configPath)) {
|
|
8625
8715
|
result.created.push(configPath);
|
|
8626
8716
|
writes.push({
|
|
@@ -8637,14 +8727,14 @@ function planProjectTemplate(config, result, check) {
|
|
|
8637
8727
|
`)
|
|
8638
8728
|
});
|
|
8639
8729
|
}
|
|
8640
|
-
const schemaSource =
|
|
8641
|
-
const schemaTarget =
|
|
8642
|
-
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");
|
|
8643
8733
|
if (!assertRegularFile(schemaTarget)) {
|
|
8644
8734
|
result.created.push(schemaTarget);
|
|
8645
8735
|
writes.push({ target: schemaTarget, content: schema, replace: false });
|
|
8646
8736
|
} else {
|
|
8647
|
-
const current =
|
|
8737
|
+
const current = readFileSync9(schemaTarget, "utf8");
|
|
8648
8738
|
if (current !== schema) {
|
|
8649
8739
|
if (!hasGeneratedSchemaMarker(current) && !isEquivalentLegacySchema(current, schema)) {
|
|
8650
8740
|
throw new Error(`Refusing to overwrite an unmanaged project schema: ${schemaTarget}`);
|
|
@@ -8669,8 +8759,8 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8669
8759
|
for (const entry of readdirSync5(destination, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
8670
8760
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
8671
8761
|
continue;
|
|
8672
|
-
const target =
|
|
8673
|
-
const current =
|
|
8762
|
+
const target = join10(destination, entry.name);
|
|
8763
|
+
const current = readFileSync9(target, "utf8");
|
|
8674
8764
|
if (!hasGeneratedAgentMarker(current))
|
|
8675
8765
|
continue;
|
|
8676
8766
|
result.removed.push(target);
|
|
@@ -8681,14 +8771,14 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8681
8771
|
for (const [id, agent] of Object.entries(config.agents)) {
|
|
8682
8772
|
if (agent.disabled)
|
|
8683
8773
|
continue;
|
|
8684
|
-
const target =
|
|
8774
|
+
const target = join10(destination, `${id}.md`);
|
|
8685
8775
|
const content = renderAgent(agent);
|
|
8686
8776
|
if (!assertRegularFile(target)) {
|
|
8687
8777
|
result.created.push(target);
|
|
8688
8778
|
writes.push({ target, content, replace: false });
|
|
8689
8779
|
continue;
|
|
8690
8780
|
}
|
|
8691
|
-
const current =
|
|
8781
|
+
const current = readFileSync9(target, "utf8");
|
|
8692
8782
|
if (current === content) {
|
|
8693
8783
|
result.unchanged.push(target);
|
|
8694
8784
|
continue;
|
|
@@ -8700,11 +8790,11 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8700
8790
|
options.onDiff?.(renderDiff(target, current, content));
|
|
8701
8791
|
writes.push({ target, content, replace: true, previous: current });
|
|
8702
8792
|
}
|
|
8703
|
-
const pluginTarget =
|
|
8793
|
+
const pluginTarget = join10(pluginDestination, "index.ts");
|
|
8704
8794
|
const pluginContent = renderPluginEntrypoint(config, pluginDestination);
|
|
8705
8795
|
if (!options.devPlugin) {
|
|
8706
8796
|
if (assertRegularFile(pluginTarget)) {
|
|
8707
|
-
const current =
|
|
8797
|
+
const current = readFileSync9(pluginTarget, "utf8");
|
|
8708
8798
|
if (hasGeneratedPluginMarker(current)) {
|
|
8709
8799
|
result.removed.push(pluginTarget);
|
|
8710
8800
|
removals.set(pluginTarget, current);
|
|
@@ -8715,7 +8805,7 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8715
8805
|
result.created.push(pluginTarget);
|
|
8716
8806
|
pluginWrite = { target: pluginTarget, content: pluginContent, replace: false };
|
|
8717
8807
|
} else {
|
|
8718
|
-
const current =
|
|
8808
|
+
const current = readFileSync9(pluginTarget, "utf8");
|
|
8719
8809
|
if (current === pluginContent) {
|
|
8720
8810
|
result.unchanged.push(pluginTarget);
|
|
8721
8811
|
} else {
|
|
@@ -8734,7 +8824,7 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8734
8824
|
if (!assertRegularFile(target)) {
|
|
8735
8825
|
throw new Error(`Refusing to remove a changed or unsafe generated file: ${target}`);
|
|
8736
8826
|
}
|
|
8737
|
-
const current =
|
|
8827
|
+
const current = readFileSync9(target, "utf8");
|
|
8738
8828
|
const generated = target.endsWith("index.ts") ? hasGeneratedPluginMarker(current) : hasGeneratedAgentMarker(current);
|
|
8739
8829
|
if (!generated || current !== removals.get(target)) {
|
|
8740
8830
|
throw new Error(`Refusing to remove a concurrently changed generated file: ${target}`);
|
|
@@ -8743,27 +8833,27 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8743
8833
|
}
|
|
8744
8834
|
for (const write of writes) {
|
|
8745
8835
|
if (!write.replace) {
|
|
8746
|
-
createFile2(
|
|
8836
|
+
createFile2(join10(writableDestination, basename(write.target)), write.content);
|
|
8747
8837
|
continue;
|
|
8748
8838
|
}
|
|
8749
8839
|
if (!assertRegularFile(write.target)) {
|
|
8750
8840
|
throw new Error(`Refusing to replace a changed or unsafe agent file: ${write.target}`);
|
|
8751
8841
|
}
|
|
8752
|
-
const current =
|
|
8842
|
+
const current = readFileSync9(write.target, "utf8");
|
|
8753
8843
|
if (!hasGeneratedAgentMarker(current) || current !== write.previous) {
|
|
8754
8844
|
throw new Error(`Refusing to replace a concurrently changed agent file: ${write.target}`);
|
|
8755
8845
|
}
|
|
8756
8846
|
replaceFile2(write.target, write.content);
|
|
8757
8847
|
}
|
|
8758
8848
|
if (pluginWrite) {
|
|
8759
|
-
const target =
|
|
8849
|
+
const target = join10(writablePluginDestination, basename(pluginWrite.target));
|
|
8760
8850
|
if (!pluginWrite.replace) {
|
|
8761
8851
|
createFile2(target, pluginWrite.content);
|
|
8762
8852
|
} else {
|
|
8763
8853
|
if (!assertRegularFile(target)) {
|
|
8764
8854
|
throw new Error(`Refusing to replace a changed or unsafe plugin entrypoint: ${target}`);
|
|
8765
8855
|
}
|
|
8766
|
-
const current =
|
|
8856
|
+
const current = readFileSync9(target, "utf8");
|
|
8767
8857
|
if (!hasGeneratedPluginMarker(current) || current !== pluginWrite.previous) {
|
|
8768
8858
|
throw new Error(`Refusing to replace a concurrently changed plugin entrypoint: ${target}`);
|
|
8769
8859
|
}
|
|
@@ -8772,11 +8862,11 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8772
8862
|
}
|
|
8773
8863
|
const templateDirectory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], true);
|
|
8774
8864
|
for (const write of templateWrites) {
|
|
8775
|
-
const target =
|
|
8865
|
+
const target = join10(templateDirectory, basename(write.target));
|
|
8776
8866
|
if (!write.replace)
|
|
8777
8867
|
createFile2(target, write.content);
|
|
8778
8868
|
else {
|
|
8779
|
-
if (!assertRegularFile(target) ||
|
|
8869
|
+
if (!assertRegularFile(target) || readFileSync9(target, "utf8") !== write.previous) {
|
|
8780
8870
|
throw new Error(`Refusing to replace a concurrently changed project schema: ${target}`);
|
|
8781
8871
|
}
|
|
8782
8872
|
replaceFile2(target, write.content);
|
|
@@ -8791,7 +8881,7 @@ function syncAgents(config, options = {}) {
|
|
|
8791
8881
|
if (options.check)
|
|
8792
8882
|
return syncAgentsUnlocked(config, options);
|
|
8793
8883
|
const root = realpathSync4(config.projectRoot);
|
|
8794
|
-
return withExclusiveFileLockSync(
|
|
8884
|
+
return withExclusiveFileLockSync(join10(root, ".agent-gvozd-sync.lock"), () => syncAgentsUnlocked(config, options), "sync");
|
|
8795
8885
|
}
|
|
8796
8886
|
function formatSyncResult(result, check) {
|
|
8797
8887
|
const lines = [check ? "agent-gvozd sync check" : "agent-gvozd sync complete"];
|
|
@@ -8826,6 +8916,8 @@ var HELP = [
|
|
|
8826
8916
|
"",
|
|
8827
8917
|
"Commands:",
|
|
8828
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",
|
|
8829
8921
|
" config [--yes] Configure model preferences",
|
|
8830
8922
|
" doctor [--json] Diagnose the global installation",
|
|
8831
8923
|
" sync [--check] [--dev-plugin] Maintain the project-local installation",
|
|
@@ -8910,6 +9002,30 @@ async function runCli(args, io = defaultIO, commands = { setup: runSetup, config
|
|
|
8910
9002
|
io.stdout(formatSyncResult(result, check));
|
|
8911
9003
|
return check && result.created.length + result.updated.length + result.removed.length > 0 ? 1 : 0;
|
|
8912
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
|
+
}
|
|
8913
9029
|
if (command === "trust-project") {
|
|
8914
9030
|
const parsed = parseFlags(rest, []);
|
|
8915
9031
|
if (!parsed || parsed.positional.length > 1)
|