@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/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.1.7";
7340
+ var PACKAGE_VERSION = "0.2.0";
7340
7341
  var PACKAGE_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
7341
- var SUPPORTED_OPENCODE_VERSION = "2.0.2";
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) === 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}` });
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/setup.ts
7872
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
7873
- import { dirname as dirname6, join as join8 } from "node:path";
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", "verifier"];
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 existsSync5, lstatSync as lstatSync6, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync5, readdirSync as readdirSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
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 join7 } from "node:path";
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 (!existsSync5(candidate)) {
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(join7(configRoot, "agents"), "OpenCode agents path");
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 = join7(agentsDirectory, entry.name);
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(readFileSync5(path, "utf8"))) {
8323
+ if (hasGeneratedAgentMarker(readFileSync6(path, "utf8"))) {
8260
8324
  assertWriteable2(path, "Managed global agent");
8261
8325
  result.removed.push(path);
8262
- removals.set(path, readFileSync5(path, "utf8"));
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 = join7(agentsDirectory, `${id}.md`);
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 = readFileSync5(path, "utf8");
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 = readFileSync5(path, "utf8");
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() ? readFileSync5(write.path, "utf8") : undefined;
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 existsSync6, fstatSync, lstatSync as lstatSync7, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
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 (existsSync6(lock.path)) {
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 && readFileSync6(lock.path, "utf8") === lock.nonce)
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) !== SUPPORTED_OPENCODE_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 = existsSync7(join8(configRoot, "gvozd", "config.jsonc"));
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 = join8(dirname6(before.sources[0]), "schema.json");
8440
- const packagedSchema = readFileSync7(schemaSource, "utf8");
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 ${join8(configRoot, "gvozd", "config.jsonc")}`,
8449
- `Write managed agents in ${join8(configRoot, "agents")}`,
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(join8(configRoot, "gvozd", "setup.lock"), async () => {
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 = readFileSync7(schemaSource, "utf8");
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 = join8(dirname6(config.sources[0]), "schema.json");
8496
- const packagedSchema = readFileSync7(schemaSource, "utf8");
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(join8(configRoot, "gvozd", "setup.lock"), async () => {
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 = readFileSync7(schemaSource, "utf8");
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 readFileSync8, readdirSync as readdirSync5, realpathSync as realpathSync4, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
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 join9, relative as relative4 } from "node:path";
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, join9(config.packageRoot, "src", "index")).replaceAll("\\", "/");
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 = join9(current, segment);
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 = join9(directory, "config.jsonc");
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 = join9(dirname7(config.sources[0]), "schema.json");
8641
- const schemaTarget = join9(directory, "schema.json");
8642
- const schema = readFileSync8(schemaSource, "utf8");
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 = readFileSync8(schemaTarget, "utf8");
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 = join9(destination, entry.name);
8673
- const current = readFileSync8(target, "utf8");
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 = join9(destination, `${id}.md`);
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 = readFileSync8(target, "utf8");
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 = join9(pluginDestination, "index.ts");
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 = readFileSync8(pluginTarget, "utf8");
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 = readFileSync8(pluginTarget, "utf8");
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 = readFileSync8(target, "utf8");
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(join9(writableDestination, basename(write.target)), write.content);
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 = readFileSync8(write.target, "utf8");
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 = join9(writablePluginDestination, basename(pluginWrite.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 = readFileSync8(target, "utf8");
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 = join9(templateDirectory, basename(write.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) || readFileSync8(target, "utf8") !== write.previous) {
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(join9(root, ".agent-gvozd-sync.lock"), () => syncAgentsUnlocked(config, options), "sync");
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)