@nail00749/agent-gvozd 0.1.8 → 0.2.1

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 join12 } from "node:path";
991
992
  import { fileURLToPath as fileURLToPath2 } from "node:url";
992
993
 
993
994
  // src/config.ts
@@ -7229,8 +7230,8 @@ function loadConfig(projectDirectory, options = {}) {
7229
7230
  }
7230
7231
 
7231
7232
  // src/cli/doctor.ts
7232
- import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "node:fs";
7233
- import { join as join4, resolve as resolve4 } from "node:path";
7233
+ import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
7234
+ import { join as join5, resolve as resolve4 } from "node:path";
7234
7235
 
7235
7236
  // src/tool-permissions.ts
7236
7237
  function family(command, ...variants) {
@@ -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.8";
7340
+ var PACKAGE_VERSION = "0.2.1";
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
@@ -7433,12 +7434,15 @@ function redactDiagnostic(error) {
7433
7434
 
7434
7435
  // src/cli/opencode.ts
7435
7436
  import { spawn } from "node:child_process";
7436
- import { isAbsolute as isAbsolute4 } from "node:path";
7437
+ import { closeSync, mkdtempSync, openSync, readFileSync as readFileSync3, rmSync } from "node:fs";
7438
+ import { isAbsolute as isAbsolute4, join as join4 } from "node:path";
7439
+ import { tmpdir } from "node:os";
7437
7440
  var MAX_OUTPUT_BYTES = 64 * 1024;
7438
7441
  var AGENT_OUTPUT_BYTES = 4 * 1024 * 1024;
7439
7442
  var DEFAULT_TIMEOUT_MS = 15000;
7440
7443
  var defaultProcessRunner = {
7441
- run(executable, args, timeoutMs = DEFAULT_TIMEOUT_MS, maxOutputBytes = MAX_OUTPUT_BYTES) {
7444
+ run(executable, args, timeoutMs = DEFAULT_TIMEOUT_MS, maxOutputBytes = MAX_OUTPUT_BYTES, options) {
7445
+ const stdoutFile = options?.stdoutFile;
7442
7446
  const appendBounded = (current, chunk) => {
7443
7447
  if (Buffer.byteLength(current) >= maxOutputBytes)
7444
7448
  return current;
@@ -7447,11 +7451,21 @@ var defaultProcessRunner = {
7447
7451
  };
7448
7452
  return new Promise((resolve, reject) => {
7449
7453
  const grouped = process.platform !== "win32";
7454
+ let stdoutFd;
7455
+ try {
7456
+ if (stdoutFile)
7457
+ stdoutFd = openSync(stdoutFile, "w");
7458
+ } catch (error) {
7459
+ reject(error);
7460
+ return;
7461
+ }
7450
7462
  const child = spawn(executable, [...args], {
7451
7463
  detached: grouped,
7452
7464
  shell: false,
7453
- stdio: ["ignore", "pipe", "pipe"]
7465
+ stdio: ["ignore", stdoutFile ? stdoutFd : "pipe", "pipe"]
7454
7466
  });
7467
+ if (stdoutFd !== undefined)
7468
+ closeSync(stdoutFd);
7455
7469
  let stdout = "";
7456
7470
  let stderr = "";
7457
7471
  let settled = false;
@@ -7494,10 +7508,10 @@ var defaultProcessRunner = {
7494
7508
  settled = true;
7495
7509
  clearTimers();
7496
7510
  if (destroy) {
7497
- child.stdout.off("data", onStdout);
7498
- child.stderr.off("data", onStderr);
7499
- child.stdout.destroy();
7500
- child.stderr.destroy();
7511
+ child.stderr?.off("data", onStderr);
7512
+ child.stderr?.destroy();
7513
+ child.stdout?.off("data", onStdout);
7514
+ child.stdout?.destroy();
7501
7515
  }
7502
7516
  reject(timeoutError());
7503
7517
  };
@@ -7512,18 +7526,20 @@ var defaultProcessRunner = {
7512
7526
  hardDeadline.unref();
7513
7527
  }, timeoutMs);
7514
7528
  timer.unref();
7515
- child.stdout.on("data", onStdout);
7516
- child.stderr.on("data", onStderr);
7529
+ if (child.stdout)
7530
+ child.stdout.on("data", onStdout);
7531
+ if (child.stderr)
7532
+ child.stderr.on("data", onStderr);
7517
7533
  child.once("error", (error) => {
7518
7534
  clearTimers();
7519
7535
  if (settled)
7520
7536
  return;
7521
7537
  settled = true;
7522
7538
  if (timedOut) {
7523
- child.stdout.off("data", onStdout);
7524
- child.stderr.off("data", onStderr);
7525
- child.stdout.destroy();
7526
- child.stderr.destroy();
7539
+ child.stderr?.off("data", onStderr);
7540
+ child.stderr?.destroy();
7541
+ child.stdout?.off("data", onStdout);
7542
+ child.stdout?.destroy();
7527
7543
  }
7528
7544
  reject(error);
7529
7545
  });
@@ -7536,7 +7552,16 @@ var defaultProcessRunner = {
7536
7552
  return;
7537
7553
  }
7538
7554
  settled = true;
7539
- resolve({ code: code ?? 1, stdout, stderr });
7555
+ const finish = (stdoutText) => resolve({ code: code ?? 1, stdout: stdoutText, stderr });
7556
+ if (stdoutFile) {
7557
+ try {
7558
+ finish(bounded(readFileSync3(stdoutFile, "utf8"), maxOutputBytes));
7559
+ } catch (error) {
7560
+ reject(error);
7561
+ }
7562
+ return;
7563
+ }
7564
+ finish(stdout);
7540
7565
  });
7541
7566
  });
7542
7567
  }
@@ -7544,8 +7569,8 @@ var defaultProcessRunner = {
7544
7569
  function bounded(value, maxOutputBytes = MAX_OUTPUT_BYTES) {
7545
7570
  return Buffer.from(value).subarray(0, maxOutputBytes).toString("utf8");
7546
7571
  }
7547
- async function checked(runner, executable, args, timeoutMs, maxOutputBytes) {
7548
- const result = await runner.run(executable, args, timeoutMs, maxOutputBytes);
7572
+ async function checked(runner, executable, args, timeoutMs, maxOutputBytes, options) {
7573
+ const result = await runner.run(executable, args, timeoutMs, maxOutputBytes, options);
7549
7574
  if (result.code !== 0) {
7550
7575
  const detail = redactDiagnostic(bounded(result.stderr).trim());
7551
7576
  throw new Error(`OpenCode command exited ${result.code}${detail ? `: ${detail}` : ""}`);
@@ -7568,6 +7593,18 @@ function parseDebugPaths(output) {
7568
7593
  function parseOpenCodeVersion(output) {
7569
7594
  return output.match(/(?:^|\s)v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?=$|\s)/)?.[1];
7570
7595
  }
7596
+ function satisfiesOpenCodeRange(version, range) {
7597
+ if (!version)
7598
+ return false;
7599
+ const [prerelease] = version.split("-").slice(1);
7600
+ if (prerelease)
7601
+ return false;
7602
+ if (!range.includes("*"))
7603
+ return version === range;
7604
+ const [rangeMajor, rangeMinor] = range.split(".").slice(0, 2);
7605
+ const [major, minor] = version.split(".").slice(0, 2);
7606
+ return major === rangeMajor && minor === rangeMinor;
7607
+ }
7571
7608
  function parseAgentIdentifiers(output) {
7572
7609
  try {
7573
7610
  const parsed = JSON.parse(output);
@@ -7603,8 +7640,14 @@ function createClient(executable, runner) {
7603
7640
  return checked(runner, executable, spec ? ["plugin", "check", spec] : ["plugin", "check"], 30000);
7604
7641
  },
7605
7642
  async debugAgents() {
7606
- const raw = await checked(runner, executable, ["debug", "agents"], 60000, AGENT_OUTPUT_BYTES);
7607
- return parseAgentIdentifiers(raw);
7643
+ const directory = mkdtempSync(join4(tmpdir(), "gvozd-agents-"));
7644
+ const stdoutFile = join4(directory, "agents.out");
7645
+ try {
7646
+ const raw = await checked(runner, executable, ["debug", "agents"], 60000, AGENT_OUTPUT_BYTES, { stdoutFile });
7647
+ return parseAgentIdentifiers(raw);
7648
+ } finally {
7649
+ rmSync(directory, { recursive: true, force: true });
7650
+ }
7608
7651
  },
7609
7652
  serviceStatus() {
7610
7653
  return checked(runner, executable, ["service", "status"]);
@@ -7711,23 +7754,23 @@ function checkGlobalFiles(config, configRoot) {
7711
7754
  for (const [id, agent] of Object.entries(config.agents).sort(([left], [right]) => left.localeCompare(right))) {
7712
7755
  if (agent.disabled)
7713
7756
  continue;
7714
- const path = join4(configRoot, "agents", `${id}.md`);
7757
+ const path = join5(configRoot, "agents", `${id}.md`);
7715
7758
  if (!safeFile(path)) {
7716
7759
  missing.push(id);
7717
7760
  continue;
7718
7761
  }
7719
- const content = readFileSync3(path, "utf8");
7762
+ const content = readFileSync4(path, "utf8");
7720
7763
  if (!hasGeneratedAgentMarker(content) || content !== renderAgent(agent))
7721
7764
  stale.push(id);
7722
7765
  }
7723
7766
  const enabled = new Set(Object.entries(config.agents).filter(([, agent]) => !agent.disabled).map(([id]) => `${id}.md`));
7724
7767
  const orphans = [];
7725
- const directory = join4(configRoot, "agents");
7768
+ const directory = join5(configRoot, "agents");
7726
7769
  if (existsSync3(directory) && lstatSync3(directory).isDirectory() && !lstatSync3(directory).isSymbolicLink()) {
7727
7770
  for (const entry of readdirSync3(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
7728
7771
  if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
7729
7772
  continue;
7730
- const content = readFileSync3(join4(directory, entry.name), "utf8");
7773
+ const content = readFileSync4(join5(directory, entry.name), "utf8");
7731
7774
  if (hasGeneratedAgentMarker(content))
7732
7775
  orphans.push(entry.name.slice(0, -3));
7733
7776
  }
@@ -7744,10 +7787,10 @@ function checkGlobalFiles(config, configRoot) {
7744
7787
  }
7745
7788
  function checkLegacy(config) {
7746
7789
  const duplicates = [];
7747
- const plugin = join4(config.projectRoot, ".opencode", "plugins", "agent-gvozd", "index.ts");
7790
+ const plugin = join5(config.projectRoot, ".opencode", "plugins", "agent-gvozd", "index.ts");
7748
7791
  if (safeFile(plugin))
7749
7792
  duplicates.push("local plugin");
7750
- const agents = Object.keys(config.agents).filter((id) => safeFile(join4(config.projectRoot, ".opencode", "agents", `${id}.md`)));
7793
+ const agents = Object.keys(config.agents).filter((id) => safeFile(join5(config.projectRoot, ".opencode", "agents", `${id}.md`)));
7751
7794
  if (agents.length > 0)
7752
7795
  duplicates.push(`${agents.length} local agents`);
7753
7796
  if (duplicates.length === 0)
@@ -7766,7 +7809,7 @@ async function runDoctor(input) {
7766
7809
  const checks = [];
7767
7810
  try {
7768
7811
  const version = await input.client.version();
7769
- 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}` });
7812
+ 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
7813
  } catch (error) {
7771
7814
  checks.push({ id: "opencode-version", status: "fail", summary: `OpenCode version check failed: ${redactDiagnostic(error)}`, remediation: `Install OpenCode ${supportedVersion}` });
7772
7815
  }
@@ -7798,13 +7841,13 @@ async function runDoctor(input) {
7798
7841
  }
7799
7842
  let config;
7800
7843
  let globalConfig;
7801
- const configPath = join4(input.configRoot, "gvozd", "config.jsonc");
7802
- const schemaPath = join4(input.configRoot, "gvozd", "schema.json");
7844
+ const configPath = join5(input.configRoot, "gvozd", "config.jsonc");
7845
+ const schemaPath = join5(input.configRoot, "gvozd", "schema.json");
7803
7846
  try {
7804
7847
  if (!safeFile(configPath) || !safeFile(schemaPath))
7805
7848
  throw new Error("global config.jsonc or schema.json is missing");
7806
7849
  const schemaErrors = [];
7807
- const schema = parse2(readFileSync3(schemaPath, "utf8"), schemaErrors);
7850
+ const schema = parse2(readFileSync4(schemaPath, "utf8"), schemaErrors);
7808
7851
  if (schemaErrors.length > 0 || schema?.["x-agent-gvozd-schema-version"] !== CONFIG_SCHEMA_VERSION || schema?.$comment !== GENERATED_PLUGIN_MARKER) {
7809
7852
  throw new Error("global schema is invalid, incompatible, or unmanaged");
7810
7853
  }
@@ -7871,18 +7914,18 @@ function doctorOperationalFailure(error) {
7871
7914
  return { schemaVersion: CONFIG_SCHEMA_VERSION, status: "fail", checks };
7872
7915
  }
7873
7916
 
7874
- // src/cli/setup.ts
7875
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
7876
- import { dirname as dirname6, join as join8 } from "node:path";
7917
+ // src/cli/agents.ts
7918
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
7919
+ import { join as join8 } from "node:path";
7877
7920
 
7878
7921
  // src/cli/config-store.ts
7879
- import { accessSync, closeSync, constants as fsConstants, existsSync as existsSync4, lstatSync as lstatSync5, mkdirSync, openSync, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync } from "node:fs";
7922
+ import { accessSync, closeSync as closeSync2, constants as fsConstants, existsSync as existsSync4, lstatSync as lstatSync5, mkdirSync, openSync as openSync2, readFileSync as readFileSync5, renameSync, unlinkSync, writeFileSync } from "node:fs";
7880
7923
  import { randomUUID } from "node:crypto";
7881
- import { dirname as dirname3, join as join6 } from "node:path";
7924
+ import { dirname as dirname3, join as join7 } from "node:path";
7882
7925
 
7883
7926
  // src/secure-path.ts
7884
7927
  import { lstatSync as lstatSync4, realpathSync as realpathSync3 } from "node:fs";
7885
- import { isAbsolute as isAbsolute5, join as join5, parse as parse6, relative as relative3, resolve as resolve5, sep } from "node:path";
7928
+ import { isAbsolute as isAbsolute5, join as join6, parse as parse6, relative as relative3, resolve as resolve5, sep } from "node:path";
7886
7929
  function secureCanonicalPath(target, label = "Path") {
7887
7930
  if (!isAbsolute5(target))
7888
7931
  throw new Error(`${label} must be absolute: ${target}`);
@@ -7898,7 +7941,7 @@ function secureCanonicalPath(target, label = "Path") {
7898
7941
  throw new Error(`${label} has an unsafe filesystem root: ${root}`);
7899
7942
  const existingDirectories = [{ path: root, stat: rootStat }];
7900
7943
  for (const component of components) {
7901
- current = join5(current, component);
7944
+ current = join6(current, component);
7902
7945
  if (missing) {
7903
7946
  missingSuffix.push(component);
7904
7947
  continue;
@@ -7943,8 +7986,8 @@ function secureCanonicalPath(target, label = "Path") {
7943
7986
  }
7944
7987
 
7945
7988
  // src/cli/config-store.ts
7946
- var FAST_AGENT_IDS = ["back-fast", "front-fast", "review-fast", "researcher", "git", "docs", "verifier"];
7947
- var DEEP_AGENT_IDS = ["master", "planner", "back-deep", "front-deep", "review-deep", "debugger", "security", "devops"];
7989
+ var FAST_AGENT_IDS = ["back-fast", "front-fast", "review-fast", "researcher", "git", "docs"];
7990
+ var DEEP_AGENT_IDS = ["master", "master-trusted", "planner", "back-deep", "front-deep", "review-deep", "debugger", "security", "devops"];
7948
7991
  var ALL_AGENT_IDS = [...DEEP_AGENT_IDS, ...FAST_AGENT_IDS, "explorer"];
7949
7992
  var formattingOptions = { insertSpaces: true, tabSize: 2, eol: `
7950
7993
  ` };
@@ -7969,13 +8012,16 @@ function applyModelProfile(source, profile) {
7969
8012
  updated = setJsonc(updated, ["agents", "explorer", "models"], profile.agentOverrides.explorer ?? profile.fast);
7970
8013
  return updated;
7971
8014
  }
8015
+ function writeManagedGlobalFile(path, content, expected) {
8016
+ atomicWrite(path, content, expected);
8017
+ }
7972
8018
  function matchesSnapshot(path, expected) {
7973
8019
  if (!expected.exists)
7974
8020
  return !existsSync4(path);
7975
8021
  if (!existsSync4(path))
7976
8022
  return false;
7977
8023
  const stat = lstatSync5(path);
7978
- return stat.isFile() && !stat.isSymbolicLink() && stat.dev === expected.dev && stat.ino === expected.ino && readFileSync4(path, "utf8") === expected.bytes;
8024
+ return stat.isFile() && !stat.isSymbolicLink() && stat.dev === expected.dev && stat.ino === expected.ino && readFileSync5(path, "utf8") === expected.bytes;
7979
8025
  }
7980
8026
  function atomicWrite(path, content, expected) {
7981
8027
  const canonicalPath = secureCanonicalPath(path, "Managed global config path");
@@ -7986,16 +8032,16 @@ function atomicWrite(path, content, expected) {
7986
8032
  throw new Error(`Global config path changed during write: ${path}`);
7987
8033
  assertWriteable(dirname3(canonicalPath), "Managed global config directory");
7988
8034
  const temporary = `${canonicalPath}.tmp-${process.pid}-${randomUUID()}`;
7989
- const descriptor = openSync(temporary, "wx", 384);
8035
+ const descriptor = openSync2(temporary, "wx", 384);
7990
8036
  try {
7991
8037
  writeFileSync(descriptor, content);
7992
- closeSync(descriptor);
8038
+ closeSync2(descriptor);
7993
8039
  if (!matchesSnapshot(canonicalPath, expected))
7994
8040
  throw new Error(`Refusing to replace concurrently changed file: ${canonicalPath}`);
7995
8041
  renameSync(temporary, canonicalPath);
7996
8042
  } catch (error) {
7997
8043
  try {
7998
- closeSync(descriptor);
8044
+ closeSync2(descriptor);
7999
8045
  } catch {}
8000
8046
  try {
8001
8047
  unlinkSync(temporary);
@@ -8047,7 +8093,7 @@ function snapshot(path) {
8047
8093
  const stat = lstatSync5(path);
8048
8094
  if (!stat.isFile() || stat.isSymbolicLink())
8049
8095
  throw new Error(`Managed global config snapshot target is unsafe: ${path}`);
8050
- return Object.freeze({ path, exists: true, bytes: readFileSync4(path, "utf8"), dev: stat.dev, ino: stat.ino });
8096
+ return Object.freeze({ path, exists: true, bytes: readFileSync5(path, "utf8"), dev: stat.dev, ino: stat.ino });
8051
8097
  }
8052
8098
  function preflightGlobalConfig(configRoot, schemaSource) {
8053
8099
  const canonicalRoot = secureCanonicalPath(configRoot, "OpenCode config root");
@@ -8056,24 +8102,24 @@ function preflightGlobalConfig(configRoot, schemaSource) {
8056
8102
  throw new Error(`OpenCode config root is not a safe directory: ${canonicalRoot}`);
8057
8103
  }
8058
8104
  assertWriteable(canonicalRoot, "OpenCode config root");
8059
- const directory = secureCanonicalPath(join6(canonicalRoot, "gvozd"), "Global Gvozd directory");
8105
+ const directory = secureCanonicalPath(join7(canonicalRoot, "gvozd"), "Global Gvozd directory");
8060
8106
  const directoryStat = existsSync4(directory) ? lstatSync5(directory) : undefined;
8061
8107
  if (directoryStat && (directoryStat.isSymbolicLink() || !directoryStat.isDirectory())) {
8062
8108
  throw new Error(`Global Gvozd path is not a safe directory: ${directory}`);
8063
8109
  }
8064
8110
  assertWriteable(directory, "Global Gvozd directory");
8065
- const configPath = join6(directory, "config.jsonc");
8066
- const schemaPath = join6(directory, "schema.json");
8111
+ const configPath = join7(directory, "config.jsonc");
8112
+ const schemaPath = join7(directory, "schema.json");
8067
8113
  if (existsSync4(configPath)) {
8068
8114
  if (!isRegularFile(configPath))
8069
8115
  throw new Error(`Global Gvozd config is not a safe file: ${configPath}`);
8070
- assertValidJsonc(readFileSync4(configPath, "utf8"), configPath);
8116
+ assertValidJsonc(readFileSync5(configPath, "utf8"), configPath);
8071
8117
  assertWriteable(configPath, "Global Gvozd config");
8072
8118
  }
8073
8119
  if (existsSync4(schemaPath)) {
8074
8120
  if (!isRegularFile(schemaPath))
8075
8121
  throw new Error(`Refusing to overwrite unmanaged Gvozd schema: ${schemaPath}`);
8076
- const schema = readFileSync4(schemaPath, "utf8");
8122
+ const schema = readFileSync5(schemaPath, "utf8");
8077
8123
  if (!hasGeneratedSchemaMarker(schema) && !legacySchemaMatches(schema, schemaSource)) {
8078
8124
  throw new Error(`Refusing to overwrite unmanaged Gvozd schema: ${schemaPath}`);
8079
8125
  }
@@ -8086,9 +8132,9 @@ function writeGlobalConfig(input) {
8086
8132
  const canonicalRoot = secureCanonicalPath(input.configRoot, "OpenCode config root");
8087
8133
  if (state.configRoot !== canonicalRoot)
8088
8134
  throw new Error("Global config snapshot belongs to another config root");
8089
- const directory = join6(state.configRoot, "gvozd");
8090
- const configPath = join6(directory, "config.jsonc");
8091
- const schemaPath = join6(directory, "schema.json");
8135
+ const directory = join7(state.configRoot, "gvozd");
8136
+ const configPath = join7(directory, "config.jsonc");
8137
+ const schemaPath = join7(directory, "schema.json");
8092
8138
  const base = state.config.exists ? state.config.bytes : `{
8093
8139
  "$schema": "./schema.json",
8094
8140
  "agents": {}
@@ -8108,6 +8154,51 @@ function writeGlobalConfig(input) {
8108
8154
  return { configPath, schemaPath, config };
8109
8155
  }
8110
8156
 
8157
+ // src/cli/agents.ts
8158
+ var formattingOptions2 = { insertSpaces: true, tabSize: 2, eol: `
8159
+ ` };
8160
+ function assertValidJsonc2(source, label) {
8161
+ const errors = [];
8162
+ const value = parse2(source, errors, { allowTrailingComma: true, disallowComments: false });
8163
+ if (errors.length > 0 || value === undefined || value === null || Array.isArray(value) || typeof value !== "object") {
8164
+ const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
8165
+ throw new Error(`Invalid JSONC in ${label}${details ? `: ${details}` : ""}`);
8166
+ }
8167
+ }
8168
+ function listAgents(cwd) {
8169
+ const config = loadConfig(cwd, { configRoot: resolveOpenCodeConfigRoot() });
8170
+ return Object.entries(config.agents).map(([id, agent]) => ({
8171
+ id,
8172
+ mode: agent.mode,
8173
+ lease: agent.fileLease,
8174
+ disabled: agent.disabled,
8175
+ model: agent.models[0] ?? ""
8176
+ }));
8177
+ }
8178
+ function setAgentDisabled(source, agentID, disabled) {
8179
+ if (!ALL_AGENT_IDS.includes(agentID)) {
8180
+ throw new Error(`Unknown agent: ${agentID}`);
8181
+ }
8182
+ assertValidJsonc2(source, "global Gvozd config");
8183
+ return applyEdits(source, modify(source, ["agents", agentID, "disabled"], disabled, { formattingOptions: formattingOptions2 }));
8184
+ }
8185
+ function readGlobalConfig(configRoot) {
8186
+ const path = join8(configRoot, "gvozd", "config.jsonc");
8187
+ if (!existsSync5(path))
8188
+ return `{
8189
+ "$schema": "./schema.json",
8190
+ "agents": {}
8191
+ }
8192
+ `;
8193
+ const source = readFileSync6(path, "utf8");
8194
+ assertValidJsonc2(source, path);
8195
+ return source;
8196
+ }
8197
+
8198
+ // src/cli/setup.ts
8199
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
8200
+ import { dirname as dirname6, join as join10 } from "node:path";
8201
+
8111
8202
  // src/cli/configure.ts
8112
8203
  function availableProfile(profile, catalog) {
8113
8204
  if (!profile)
@@ -8177,9 +8268,9 @@ function profileFromAgents(agents, catalog) {
8177
8268
  }
8178
8269
 
8179
8270
  // src/cli/global-sync.ts
8180
- 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";
8271
+ import { accessSync as accessSync2, closeSync as closeSync3, constants as fsConstants2, existsSync as existsSync6, lstatSync as lstatSync6, mkdirSync as mkdirSync2, openSync as openSync3, readFileSync as readFileSync7, readdirSync as readdirSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
8181
8272
  import { randomUUID as randomUUID2 } from "node:crypto";
8182
- import { dirname as dirname4, join as join7 } from "node:path";
8273
+ import { dirname as dirname4, join as join9 } from "node:path";
8183
8274
  function stat(path) {
8184
8275
  try {
8185
8276
  return lstatSync6(path);
@@ -8191,7 +8282,7 @@ function stat(path) {
8191
8282
  }
8192
8283
  function assertWriteable2(path, label) {
8193
8284
  let candidate = path;
8194
- while (!existsSync5(candidate)) {
8285
+ while (!existsSync6(candidate)) {
8195
8286
  const parent = dirname4(candidate);
8196
8287
  if (parent === candidate)
8197
8288
  break;
@@ -8213,11 +8304,11 @@ function createFile(path, content) {
8213
8304
  const canonicalPath = secureCanonicalPath(path, "Managed global agent path");
8214
8305
  if (canonicalPath !== path)
8215
8306
  throw new Error(`Managed global agent path changed: ${path}`);
8216
- const descriptor = openSync2(canonicalPath, "wx", 384);
8307
+ const descriptor = openSync3(canonicalPath, "wx", 384);
8217
8308
  try {
8218
8309
  writeFileSync2(descriptor, content);
8219
8310
  } finally {
8220
- closeSync2(descriptor);
8311
+ closeSync3(descriptor);
8221
8312
  }
8222
8313
  }
8223
8314
  function replaceFile(path, content) {
@@ -8236,7 +8327,7 @@ function replaceFile(path, content) {
8236
8327
  }
8237
8328
  function writeManagedAgents(input) {
8238
8329
  const configRoot = secureCanonicalPath(input.configRoot, "OpenCode config root");
8239
- const agentsDirectory = secureCanonicalPath(join7(configRoot, "agents"), "OpenCode agents path");
8330
+ const agentsDirectory = secureCanonicalPath(join9(configRoot, "agents"), "OpenCode agents path");
8240
8331
  const result = { created: [], updated: [], unchanged: [], conflicts: [], removed: [] };
8241
8332
  const writes = [];
8242
8333
  const removals = new Map;
@@ -8255,21 +8346,21 @@ function writeManagedAgents(input) {
8255
8346
  for (const entry of readdirSync4(agentsDirectory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
8256
8347
  if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
8257
8348
  continue;
8258
- const path = join7(agentsDirectory, entry.name);
8349
+ const path = join9(agentsDirectory, entry.name);
8259
8350
  const currentStat = stat(path);
8260
8351
  if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile())
8261
8352
  continue;
8262
- if (hasGeneratedAgentMarker(readFileSync5(path, "utf8"))) {
8353
+ if (hasGeneratedAgentMarker(readFileSync7(path, "utf8"))) {
8263
8354
  assertWriteable2(path, "Managed global agent");
8264
8355
  result.removed.push(path);
8265
- removals.set(path, readFileSync5(path, "utf8"));
8356
+ removals.set(path, readFileSync7(path, "utf8"));
8266
8357
  }
8267
8358
  }
8268
8359
  }
8269
8360
  for (const [id, agent] of Object.entries(input.agents).sort(([left], [right]) => left.localeCompare(right))) {
8270
8361
  if (agent.disabled)
8271
8362
  continue;
8272
- const path = join7(agentsDirectory, `${id}.md`);
8363
+ const path = join9(agentsDirectory, `${id}.md`);
8273
8364
  const content = renderAgent(agent);
8274
8365
  const currentStat = stat(path);
8275
8366
  if (!currentStat) {
@@ -8281,7 +8372,7 @@ function writeManagedAgents(input) {
8281
8372
  result.conflicts.push(path);
8282
8373
  continue;
8283
8374
  }
8284
- const current = readFileSync5(path, "utf8");
8375
+ const current = readFileSync7(path, "utf8");
8285
8376
  if (!hasGeneratedAgentMarker(current)) {
8286
8377
  result.conflicts.push(path);
8287
8378
  continue;
@@ -8319,7 +8410,7 @@ function writeManagedAgents(input) {
8319
8410
  if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile()) {
8320
8411
  throw new Error(`Refusing to remove a changed or unmanaged global agent: ${path}`);
8321
8412
  }
8322
- const current = readFileSync5(path, "utf8");
8413
+ const current = readFileSync7(path, "utf8");
8323
8414
  if (!hasGeneratedAgentMarker(current) || current !== removals.get(path)) {
8324
8415
  throw new Error(`Refusing to remove a concurrently changed global agent: ${path}`);
8325
8416
  }
@@ -8328,7 +8419,7 @@ function writeManagedAgents(input) {
8328
8419
  for (const write of writes) {
8329
8420
  if (write.replace) {
8330
8421
  const currentStat = stat(write.path);
8331
- const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ? readFileSync5(write.path, "utf8") : undefined;
8422
+ const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ? readFileSync7(write.path, "utf8") : undefined;
8332
8423
  if (!current || !hasGeneratedAgentMarker(current) || current !== write.previous) {
8333
8424
  throw new Error(`Refusing to replace a concurrently changed global agent: ${write.path}`);
8334
8425
  }
@@ -8341,8 +8432,30 @@ function writeManagedAgents(input) {
8341
8432
 
8342
8433
  // src/file-lock.ts
8343
8434
  import { randomUUID as randomUUID3 } from "node:crypto";
8344
- 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";
8435
+ import { accessSync as accessSync3, closeSync as closeSync4, constants, existsSync as existsSync7, fstatSync, lstatSync as lstatSync7, mkdirSync as mkdirSync3, openSync as openSync4, readFileSync as readFileSync8, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
8345
8436
  import { dirname as dirname5 } from "node:path";
8437
+ var STALE_LOCK_AGE_MS = 30 * 60 * 1000;
8438
+ function lockOwnerAlive(path) {
8439
+ let contents;
8440
+ try {
8441
+ contents = readFileSync8(path, "utf8");
8442
+ } catch {
8443
+ return true;
8444
+ }
8445
+ const pid = Number(contents.split(":")[0]);
8446
+ if (!Number.isInteger(pid) || pid <= 0)
8447
+ return false;
8448
+ if (pid === process.pid)
8449
+ return true;
8450
+ try {
8451
+ process.kill(pid, 0);
8452
+ return true;
8453
+ } catch (error) {
8454
+ if (error.code === "ESRCH")
8455
+ return false;
8456
+ return true;
8457
+ }
8458
+ }
8346
8459
  function assertSecure(path) {
8347
8460
  const stat = lstatSync7(path);
8348
8461
  if (!stat.isDirectory() || stat.isSymbolicLink())
@@ -8364,22 +8477,48 @@ function acquire(path, operation) {
8364
8477
  `;
8365
8478
  let descriptor;
8366
8479
  try {
8367
- descriptor = openSync3(canonicalPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
8480
+ descriptor = openSync4(canonicalPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
8368
8481
  } catch (error) {
8369
- if (error.code === "EEXIST") {
8482
+ if (error.code !== "EEXIST")
8483
+ throw error;
8484
+ if (removeStaleLock(canonicalPath)) {
8485
+ descriptor = openSync4(canonicalPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
8486
+ } else {
8370
8487
  throw new Error(`Another Gvozd ${operation} is active or left a lock at ${canonicalPath}; inspect it manually and do not delete it while work may be running`);
8371
8488
  }
8372
- throw error;
8373
8489
  }
8374
8490
  const created = fstatSync(descriptor);
8375
8491
  writeFileSync3(descriptor, nonce);
8376
8492
  return { path: canonicalPath, descriptor, nonce, dev: created.dev, ino: created.ino };
8377
8493
  }
8494
+ function removeStaleLock(path) {
8495
+ let stat;
8496
+ try {
8497
+ stat = lstatSync7(path);
8498
+ } catch {
8499
+ return false;
8500
+ }
8501
+ if (!stat.isFile() || stat.isSymbolicLink())
8502
+ return false;
8503
+ if (Date.now() - stat.mtimeMs < STALE_LOCK_AGE_MS)
8504
+ return false;
8505
+ if (lockOwnerAlive(path))
8506
+ return false;
8507
+ try {
8508
+ const current = lstatSync7(path);
8509
+ if (current.isSymbolicLink() || !current.isFile() || current.ino !== stat.ino || current.dev !== stat.dev)
8510
+ return false;
8511
+ unlinkSync3(path);
8512
+ return true;
8513
+ } catch {
8514
+ return false;
8515
+ }
8516
+ }
8378
8517
  function release(lock) {
8379
- closeSync3(lock.descriptor);
8380
- if (existsSync6(lock.path)) {
8518
+ closeSync4(lock.descriptor);
8519
+ if (existsSync7(lock.path)) {
8381
8520
  const current = lstatSync7(lock.path);
8382
- if (current.isFile() && !current.isSymbolicLink() && current.dev === lock.dev && current.ino === lock.ino && readFileSync6(lock.path, "utf8") === lock.nonce)
8521
+ if (current.isFile() && !current.isSymbolicLink() && current.dev === lock.dev && current.ino === lock.ino && readFileSync8(lock.path, "utf8") === lock.nonce)
8383
8522
  unlinkSync3(lock.path);
8384
8523
  }
8385
8524
  }
@@ -8402,14 +8541,14 @@ function withExclusiveFileLockSync(path, callback, operation = "operation") {
8402
8541
 
8403
8542
  // src/cli/setup.ts
8404
8543
  function assertVersion(version) {
8405
- if (parseOpenCodeVersion(version) !== SUPPORTED_OPENCODE_VERSION) {
8544
+ if (!satisfiesOpenCodeRange(parseOpenCodeVersion(version), SUPPORTED_OPENCODE_VERSION)) {
8406
8545
  throw new Error(`Unsupported OpenCode version. Gvozd ${PACKAGE_VERSION} requires ${SUPPORTED_OPENCODE_VERSION}.`);
8407
8546
  }
8408
8547
  }
8409
8548
  async function selectProfile(input, client, configRoot) {
8410
8549
  const catalog = parseModels(await client.models());
8411
8550
  const config = loadConfig(input.cwd, { configRoot, includeProject: false });
8412
- const hasGlobalConfig = existsSync7(join8(configRoot, "gvozd", "config.jsonc"));
8551
+ const hasGlobalConfig = existsSync8(join10(configRoot, "gvozd", "config.jsonc"));
8413
8552
  return chooseModelProfile({
8414
8553
  catalog,
8415
8554
  ui: input.ui,
@@ -8435,6 +8574,22 @@ async function registeredPackageSpecs(client, packageName, target) {
8435
8574
  const specs = [...new Set(output.match(specPattern) ?? [])];
8436
8575
  return specs.filter((spec) => spec !== target);
8437
8576
  }
8577
+ async function awaitRegisteredPlugin(client, timeoutMs = 15000) {
8578
+ const target = PACKAGE_SPEC;
8579
+ const started = Date.now();
8580
+ for (;; ) {
8581
+ try {
8582
+ const output = await client.pluginList();
8583
+ const escapedName = PACKAGE_NAME.replace(/[/@]/g, "\\$&");
8584
+ const registered = new RegExp(`${escapedName}(?:@|\\s+)v?${PACKAGE_VERSION}(?=$|[^0-9.])`, "m").test(output);
8585
+ if (registered)
8586
+ return;
8587
+ } catch {}
8588
+ if (Date.now() - started >= timeoutMs)
8589
+ throw new Error(`OpenCode did not report ${target} within ${timeoutMs}ms after the service restart`);
8590
+ await new Promise((resolve) => setTimeout(resolve, 1000));
8591
+ }
8592
+ }
8438
8593
  async function runSetup(input) {
8439
8594
  const client = await (input.findClient ?? (() => findOpenCode()))();
8440
8595
  const paths = await client.debugPaths();
@@ -8445,8 +8600,8 @@ async function runSetup(input) {
8445
8600
  const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
8446
8601
  assertVersion(await client.version());
8447
8602
  const before = loadConfig(input.cwd, { configRoot, includeProject: false });
8448
- const schemaSource = join8(dirname6(before.sources[0]), "schema.json");
8449
- const packagedSchema = readFileSync7(schemaSource, "utf8");
8603
+ const schemaSource = join10(dirname6(before.sources[0]), "schema.json");
8604
+ const packagedSchema = readFileSync9(schemaSource, "utf8");
8450
8605
  const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
8451
8606
  const preview = writeManagedAgents({ configRoot, agents: before.agents, check: true });
8452
8607
  const profile = await selectProfile(input, client, configRoot);
@@ -8454,17 +8609,17 @@ async function runSetup(input) {
8454
8609
  return { status: "cancelled" };
8455
8610
  input.output?.([
8456
8611
  `Register ${PACKAGE_SPEC}`,
8457
- `Write ${join8(configRoot, "gvozd", "config.jsonc")}`,
8458
- `Write managed agents in ${join8(configRoot, "agents")}`,
8612
+ `Write ${join10(configRoot, "gvozd", "config.jsonc")}`,
8613
+ `Write managed agents in ${join10(configRoot, "agents")}`,
8459
8614
  ...preview.removed.length > 0 ? [`Remove ${preview.removed.length} stale or disabled managed agent(s)`] : []
8460
8615
  ].join(`
8461
8616
  `));
8462
8617
  if (!await confirm2(input, "Run setup?"))
8463
8618
  return { status: "cancelled" };
8464
- return withExclusiveFileLock(join8(configRoot, "gvozd", "setup.lock"), async () => {
8619
+ return withExclusiveFileLock(join10(configRoot, "gvozd", "setup.lock"), async () => {
8465
8620
  if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
8466
8621
  throw new Error("OpenCode config root changed while setup awaited the lock");
8467
- const lockedSchema = readFileSync7(schemaSource, "utf8");
8622
+ const lockedSchema = readFileSync9(schemaSource, "utf8");
8468
8623
  const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
8469
8624
  if (!sameSnapshot(previewSnapshot, snapshot))
8470
8625
  throw new Error("Global Gvozd configuration changed while setup awaited confirmation; review and rerun setup");
@@ -8486,6 +8641,7 @@ async function runSetup(input) {
8486
8641
  const configured = loadConfig(input.cwd, { configRoot, includeProject: false });
8487
8642
  writeManagedAgents({ configRoot, agents: configured.agents });
8488
8643
  await client.serviceRestart();
8644
+ await awaitRegisteredPlugin(client);
8489
8645
  const report = await runDoctor({ client, configRoot, runtimeConfigRoot, cwd: input.cwd });
8490
8646
  return { status: "complete", report };
8491
8647
  } catch (error) {
@@ -8504,16 +8660,16 @@ async function runConfigure(input) {
8504
8660
  const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
8505
8661
  assertVersion(await client.version());
8506
8662
  const config = loadConfig(input.cwd, { configRoot, includeProject: false });
8507
- const schemaSource = join8(dirname6(config.sources[0]), "schema.json");
8508
- const packagedSchema = readFileSync7(schemaSource, "utf8");
8663
+ const schemaSource = join10(dirname6(config.sources[0]), "schema.json");
8664
+ const packagedSchema = readFileSync9(schemaSource, "utf8");
8509
8665
  const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
8510
8666
  const profile = await selectProfile(input, client, configRoot);
8511
8667
  if (!profile || !await confirm2(input, "Apply model configuration?"))
8512
8668
  return { status: "cancelled" };
8513
- return withExclusiveFileLock(join8(configRoot, "gvozd", "setup.lock"), async () => {
8669
+ return withExclusiveFileLock(join10(configRoot, "gvozd", "setup.lock"), async () => {
8514
8670
  if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
8515
8671
  throw new Error("OpenCode config root changed while configuration awaited the lock");
8516
- const lockedSchema = readFileSync7(schemaSource, "utf8");
8672
+ const lockedSchema = readFileSync9(schemaSource, "utf8");
8517
8673
  const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
8518
8674
  if (!sameSnapshot(previewSnapshot, snapshot))
8519
8675
  throw new Error("Global Gvozd configuration changed while configuration awaited confirmation; review and rerun");
@@ -8537,13 +8693,13 @@ function setupExitCode(result) {
8537
8693
  }
8538
8694
 
8539
8695
  // src/sync.ts
8540
- 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";
8696
+ import { closeSync as closeSync5, lstatSync as lstatSync8, mkdirSync as mkdirSync4, openSync as openSync5, readFileSync as readFileSync10, readdirSync as readdirSync5, realpathSync as realpathSync4, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
8541
8697
  import { randomUUID as randomUUID4 } from "node:crypto";
8542
- import { basename, dirname as dirname7, join as join9, relative as relative4 } from "node:path";
8698
+ import { basename, dirname as dirname7, join as join11, relative as relative4 } from "node:path";
8543
8699
  function renderPluginEntrypoint(config, destination) {
8544
8700
  let moduleSpecifier = "agent-gvozd/server";
8545
8701
  if (realpathSync4(config.packageRoot) === realpathSync4(config.projectRoot)) {
8546
- moduleSpecifier = relative4(destination, join9(config.packageRoot, "src", "index")).replaceAll("\\", "/");
8702
+ moduleSpecifier = relative4(destination, join11(config.packageRoot, "src", "index")).replaceAll("\\", "/");
8547
8703
  if (!moduleSpecifier.startsWith("."))
8548
8704
  moduleSpecifier = `./${moduleSpecifier}`;
8549
8705
  }
@@ -8588,7 +8744,7 @@ function stat2(path) {
8588
8744
  function safeDirectory(root, segments, create) {
8589
8745
  let current = realpathSync4(root);
8590
8746
  for (const segment of segments) {
8591
- current = join9(current, segment);
8747
+ current = join11(current, segment);
8592
8748
  const currentStat = stat2(current);
8593
8749
  if (currentStat) {
8594
8750
  if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
@@ -8610,11 +8766,11 @@ function assertRegularFile(path) {
8610
8766
  return true;
8611
8767
  }
8612
8768
  function createFile2(path, content) {
8613
- const descriptor = openSync4(path, "wx", 384);
8769
+ const descriptor = openSync5(path, "wx", 384);
8614
8770
  try {
8615
8771
  writeFileSync4(descriptor, content);
8616
8772
  } finally {
8617
- closeSync4(descriptor);
8773
+ closeSync5(descriptor);
8618
8774
  }
8619
8775
  }
8620
8776
  function replaceFile2(path, content) {
@@ -8632,7 +8788,7 @@ function replaceFile2(path, content) {
8632
8788
  function planProjectTemplate(config, result, check) {
8633
8789
  const directory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], !check);
8634
8790
  const writes = [];
8635
- const configPath = join9(directory, "config.jsonc");
8791
+ const configPath = join11(directory, "config.jsonc");
8636
8792
  if (!assertRegularFile(configPath)) {
8637
8793
  result.created.push(configPath);
8638
8794
  writes.push({
@@ -8649,14 +8805,14 @@ function planProjectTemplate(config, result, check) {
8649
8805
  `)
8650
8806
  });
8651
8807
  }
8652
- const schemaSource = join9(dirname7(config.sources[0]), "schema.json");
8653
- const schemaTarget = join9(directory, "schema.json");
8654
- const schema = readFileSync8(schemaSource, "utf8");
8808
+ const schemaSource = join11(dirname7(config.sources[0]), "schema.json");
8809
+ const schemaTarget = join11(directory, "schema.json");
8810
+ const schema = readFileSync10(schemaSource, "utf8");
8655
8811
  if (!assertRegularFile(schemaTarget)) {
8656
8812
  result.created.push(schemaTarget);
8657
8813
  writes.push({ target: schemaTarget, content: schema, replace: false });
8658
8814
  } else {
8659
- const current = readFileSync8(schemaTarget, "utf8");
8815
+ const current = readFileSync10(schemaTarget, "utf8");
8660
8816
  if (current !== schema) {
8661
8817
  if (!hasGeneratedSchemaMarker(current) && !isEquivalentLegacySchema(current, schema)) {
8662
8818
  throw new Error(`Refusing to overwrite an unmanaged project schema: ${schemaTarget}`);
@@ -8681,8 +8837,8 @@ function syncAgentsUnlocked(config, options) {
8681
8837
  for (const entry of readdirSync5(destination, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
8682
8838
  if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
8683
8839
  continue;
8684
- const target = join9(destination, entry.name);
8685
- const current = readFileSync8(target, "utf8");
8840
+ const target = join11(destination, entry.name);
8841
+ const current = readFileSync10(target, "utf8");
8686
8842
  if (!hasGeneratedAgentMarker(current))
8687
8843
  continue;
8688
8844
  result.removed.push(target);
@@ -8693,14 +8849,14 @@ function syncAgentsUnlocked(config, options) {
8693
8849
  for (const [id, agent] of Object.entries(config.agents)) {
8694
8850
  if (agent.disabled)
8695
8851
  continue;
8696
- const target = join9(destination, `${id}.md`);
8852
+ const target = join11(destination, `${id}.md`);
8697
8853
  const content = renderAgent(agent);
8698
8854
  if (!assertRegularFile(target)) {
8699
8855
  result.created.push(target);
8700
8856
  writes.push({ target, content, replace: false });
8701
8857
  continue;
8702
8858
  }
8703
- const current = readFileSync8(target, "utf8");
8859
+ const current = readFileSync10(target, "utf8");
8704
8860
  if (current === content) {
8705
8861
  result.unchanged.push(target);
8706
8862
  continue;
@@ -8712,11 +8868,11 @@ function syncAgentsUnlocked(config, options) {
8712
8868
  options.onDiff?.(renderDiff(target, current, content));
8713
8869
  writes.push({ target, content, replace: true, previous: current });
8714
8870
  }
8715
- const pluginTarget = join9(pluginDestination, "index.ts");
8871
+ const pluginTarget = join11(pluginDestination, "index.ts");
8716
8872
  const pluginContent = renderPluginEntrypoint(config, pluginDestination);
8717
8873
  if (!options.devPlugin) {
8718
8874
  if (assertRegularFile(pluginTarget)) {
8719
- const current = readFileSync8(pluginTarget, "utf8");
8875
+ const current = readFileSync10(pluginTarget, "utf8");
8720
8876
  if (hasGeneratedPluginMarker(current)) {
8721
8877
  result.removed.push(pluginTarget);
8722
8878
  removals.set(pluginTarget, current);
@@ -8727,7 +8883,7 @@ function syncAgentsUnlocked(config, options) {
8727
8883
  result.created.push(pluginTarget);
8728
8884
  pluginWrite = { target: pluginTarget, content: pluginContent, replace: false };
8729
8885
  } else {
8730
- const current = readFileSync8(pluginTarget, "utf8");
8886
+ const current = readFileSync10(pluginTarget, "utf8");
8731
8887
  if (current === pluginContent) {
8732
8888
  result.unchanged.push(pluginTarget);
8733
8889
  } else {
@@ -8746,7 +8902,7 @@ function syncAgentsUnlocked(config, options) {
8746
8902
  if (!assertRegularFile(target)) {
8747
8903
  throw new Error(`Refusing to remove a changed or unsafe generated file: ${target}`);
8748
8904
  }
8749
- const current = readFileSync8(target, "utf8");
8905
+ const current = readFileSync10(target, "utf8");
8750
8906
  const generated = target.endsWith("index.ts") ? hasGeneratedPluginMarker(current) : hasGeneratedAgentMarker(current);
8751
8907
  if (!generated || current !== removals.get(target)) {
8752
8908
  throw new Error(`Refusing to remove a concurrently changed generated file: ${target}`);
@@ -8755,27 +8911,27 @@ function syncAgentsUnlocked(config, options) {
8755
8911
  }
8756
8912
  for (const write of writes) {
8757
8913
  if (!write.replace) {
8758
- createFile2(join9(writableDestination, basename(write.target)), write.content);
8914
+ createFile2(join11(writableDestination, basename(write.target)), write.content);
8759
8915
  continue;
8760
8916
  }
8761
8917
  if (!assertRegularFile(write.target)) {
8762
8918
  throw new Error(`Refusing to replace a changed or unsafe agent file: ${write.target}`);
8763
8919
  }
8764
- const current = readFileSync8(write.target, "utf8");
8920
+ const current = readFileSync10(write.target, "utf8");
8765
8921
  if (!hasGeneratedAgentMarker(current) || current !== write.previous) {
8766
8922
  throw new Error(`Refusing to replace a concurrently changed agent file: ${write.target}`);
8767
8923
  }
8768
8924
  replaceFile2(write.target, write.content);
8769
8925
  }
8770
8926
  if (pluginWrite) {
8771
- const target = join9(writablePluginDestination, basename(pluginWrite.target));
8927
+ const target = join11(writablePluginDestination, basename(pluginWrite.target));
8772
8928
  if (!pluginWrite.replace) {
8773
8929
  createFile2(target, pluginWrite.content);
8774
8930
  } else {
8775
8931
  if (!assertRegularFile(target)) {
8776
8932
  throw new Error(`Refusing to replace a changed or unsafe plugin entrypoint: ${target}`);
8777
8933
  }
8778
- const current = readFileSync8(target, "utf8");
8934
+ const current = readFileSync10(target, "utf8");
8779
8935
  if (!hasGeneratedPluginMarker(current) || current !== pluginWrite.previous) {
8780
8936
  throw new Error(`Refusing to replace a concurrently changed plugin entrypoint: ${target}`);
8781
8937
  }
@@ -8784,11 +8940,11 @@ function syncAgentsUnlocked(config, options) {
8784
8940
  }
8785
8941
  const templateDirectory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], true);
8786
8942
  for (const write of templateWrites) {
8787
- const target = join9(templateDirectory, basename(write.target));
8943
+ const target = join11(templateDirectory, basename(write.target));
8788
8944
  if (!write.replace)
8789
8945
  createFile2(target, write.content);
8790
8946
  else {
8791
- if (!assertRegularFile(target) || readFileSync8(target, "utf8") !== write.previous) {
8947
+ if (!assertRegularFile(target) || readFileSync10(target, "utf8") !== write.previous) {
8792
8948
  throw new Error(`Refusing to replace a concurrently changed project schema: ${target}`);
8793
8949
  }
8794
8950
  replaceFile2(target, write.content);
@@ -8803,7 +8959,7 @@ function syncAgents(config, options = {}) {
8803
8959
  if (options.check)
8804
8960
  return syncAgentsUnlocked(config, options);
8805
8961
  const root = realpathSync4(config.projectRoot);
8806
- return withExclusiveFileLockSync(join9(root, ".agent-gvozd-sync.lock"), () => syncAgentsUnlocked(config, options), "sync");
8962
+ return withExclusiveFileLockSync(join11(root, ".agent-gvozd-sync.lock"), () => syncAgentsUnlocked(config, options), "sync");
8807
8963
  }
8808
8964
  function formatSyncResult(result, check) {
8809
8965
  const lines = [check ? "agent-gvozd sync check" : "agent-gvozd sync complete"];
@@ -8838,6 +8994,8 @@ var HELP = [
8838
8994
  "",
8839
8995
  "Commands:",
8840
8996
  " setup [--yes] Install or upgrade the global agent team",
8997
+ " agents [list] Show the resolved agent team",
8998
+ " agents disable <id> | enable <id> Toggle an agent in the global config",
8841
8999
  " config [--yes] Configure model preferences",
8842
9000
  " doctor [--json] Diagnose the global installation",
8843
9001
  " sync [--check] [--dev-plugin] Maintain the project-local installation",
@@ -8922,6 +9080,32 @@ async function runCli(args, io = defaultIO, commands = { setup: runSetup, config
8922
9080
  io.stdout(formatSyncResult(result, check));
8923
9081
  return check && result.created.length + result.updated.length + result.removed.length > 0 ? 1 : 0;
8924
9082
  }
9083
+ if (command === "agents") {
9084
+ const [action, agentID] = rest;
9085
+ if (!action || action === "list") {
9086
+ if (rest.length > 1)
9087
+ return usage(io);
9088
+ const rows = listAgents(io.cwd());
9089
+ for (const row of rows) {
9090
+ io.stdout(`${row.disabled ? "✗" : " "} ${row.id.padEnd(12)} ${row.mode.padEnd(8)} ${row.lease.padEnd(12)} ${row.model}`);
9091
+ }
9092
+ return 0;
9093
+ }
9094
+ if (action === "disable" || action === "enable") {
9095
+ if (agentID === undefined || agentID.startsWith("--") || rest.length > 2)
9096
+ return usage(io);
9097
+ const configRoot = resolveOpenCodeConfigRoot();
9098
+ await withExclusiveFileLock(join12(configRoot, "gvozd", "setup.lock"), async () => {
9099
+ preflightGlobalConfig(configRoot);
9100
+ const snapshot2 = snapshot(join12(configRoot, "gvozd", "config.jsonc"));
9101
+ const next = setAgentDisabled(readGlobalConfig(configRoot), agentID, action === "disable");
9102
+ writeManagedGlobalFile(join12(configRoot, "gvozd", "config.jsonc"), next, snapshot2);
9103
+ });
9104
+ io.stdout(`${action}d ${agentID}; run gvozd sync and gvozd doctor to apply`);
9105
+ return 0;
9106
+ }
9107
+ return usage(io);
9108
+ }
8925
9109
  if (command === "trust-project") {
8926
9110
  const parsed = parseFlags(rest, []);
8927
9111
  if (!parsed || parsed.positional.length > 1)
@@ -8936,8 +9120,13 @@ async function runCli(args, io = defaultIO, commands = { setup: runSetup, config
8936
9120
  }
8937
9121
  }
8938
9122
  var invokedPath = process.argv[1];
8939
- if (invokedPath && realpathSync5(invokedPath) === realpathSync5(fileURLToPath2(import.meta.url))) {
8940
- process.exitCode = await runCli(process.argv.slice(2));
9123
+ try {
9124
+ if (invokedPath && realpathSync5(invokedPath) === realpathSync5(fileURLToPath2(import.meta.url))) {
9125
+ process.exitCode = await runCli(process.argv.slice(2));
9126
+ }
9127
+ } catch (error) {
9128
+ if (error.code !== "ENOENT")
9129
+ throw error;
8941
9130
  }
8942
9131
  export {
8943
9132
  runCli