@nail00749/agent-gvozd 0.2.0 → 0.2.2
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 +200 -115
- package/dist/index.js +26 -23
- package/dist/tui.js +180 -56
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -988,7 +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
|
|
991
|
+
import { join as join12 } from "node:path";
|
|
992
992
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
993
993
|
|
|
994
994
|
// src/config.ts
|
|
@@ -7230,8 +7230,8 @@ function loadConfig(projectDirectory, options = {}) {
|
|
|
7230
7230
|
}
|
|
7231
7231
|
|
|
7232
7232
|
// src/cli/doctor.ts
|
|
7233
|
-
import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as
|
|
7234
|
-
import { join as
|
|
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";
|
|
7235
7235
|
|
|
7236
7236
|
// src/tool-permissions.ts
|
|
7237
7237
|
function family(command, ...variants) {
|
|
@@ -7337,7 +7337,7 @@ function buildAgentPermissions(agent, mcpServers) {
|
|
|
7337
7337
|
|
|
7338
7338
|
// src/release-metadata.ts
|
|
7339
7339
|
var PACKAGE_NAME = "@nail00749/agent-gvozd";
|
|
7340
|
-
var PACKAGE_VERSION = "0.2.
|
|
7340
|
+
var PACKAGE_VERSION = "0.2.2";
|
|
7341
7341
|
var PACKAGE_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
|
|
7342
7342
|
var SUPPORTED_OPENCODE_VERSION = "2.0.*";
|
|
7343
7343
|
var CONFIG_SCHEMA_VERSION = 2;
|
|
@@ -7434,12 +7434,15 @@ function redactDiagnostic(error) {
|
|
|
7434
7434
|
|
|
7435
7435
|
// src/cli/opencode.ts
|
|
7436
7436
|
import { spawn } from "node:child_process";
|
|
7437
|
-
import {
|
|
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";
|
|
7438
7440
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
7439
7441
|
var AGENT_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
7440
7442
|
var DEFAULT_TIMEOUT_MS = 15000;
|
|
7441
7443
|
var defaultProcessRunner = {
|
|
7442
|
-
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;
|
|
7443
7446
|
const appendBounded = (current, chunk) => {
|
|
7444
7447
|
if (Buffer.byteLength(current) >= maxOutputBytes)
|
|
7445
7448
|
return current;
|
|
@@ -7448,11 +7451,21 @@ var defaultProcessRunner = {
|
|
|
7448
7451
|
};
|
|
7449
7452
|
return new Promise((resolve, reject) => {
|
|
7450
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
|
+
}
|
|
7451
7462
|
const child = spawn(executable, [...args], {
|
|
7452
7463
|
detached: grouped,
|
|
7453
7464
|
shell: false,
|
|
7454
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
7465
|
+
stdio: ["ignore", stdoutFile ? stdoutFd : "pipe", "pipe"]
|
|
7455
7466
|
});
|
|
7467
|
+
if (stdoutFd !== undefined)
|
|
7468
|
+
closeSync(stdoutFd);
|
|
7456
7469
|
let stdout = "";
|
|
7457
7470
|
let stderr = "";
|
|
7458
7471
|
let settled = false;
|
|
@@ -7495,10 +7508,10 @@ var defaultProcessRunner = {
|
|
|
7495
7508
|
settled = true;
|
|
7496
7509
|
clearTimers();
|
|
7497
7510
|
if (destroy) {
|
|
7498
|
-
child.
|
|
7499
|
-
child.stderr
|
|
7500
|
-
child.stdout
|
|
7501
|
-
child.
|
|
7511
|
+
child.stderr?.off("data", onStderr);
|
|
7512
|
+
child.stderr?.destroy();
|
|
7513
|
+
child.stdout?.off("data", onStdout);
|
|
7514
|
+
child.stdout?.destroy();
|
|
7502
7515
|
}
|
|
7503
7516
|
reject(timeoutError());
|
|
7504
7517
|
};
|
|
@@ -7513,18 +7526,20 @@ var defaultProcessRunner = {
|
|
|
7513
7526
|
hardDeadline.unref();
|
|
7514
7527
|
}, timeoutMs);
|
|
7515
7528
|
timer.unref();
|
|
7516
|
-
child.stdout
|
|
7517
|
-
|
|
7529
|
+
if (child.stdout)
|
|
7530
|
+
child.stdout.on("data", onStdout);
|
|
7531
|
+
if (child.stderr)
|
|
7532
|
+
child.stderr.on("data", onStderr);
|
|
7518
7533
|
child.once("error", (error) => {
|
|
7519
7534
|
clearTimers();
|
|
7520
7535
|
if (settled)
|
|
7521
7536
|
return;
|
|
7522
7537
|
settled = true;
|
|
7523
7538
|
if (timedOut) {
|
|
7524
|
-
child.
|
|
7525
|
-
child.stderr
|
|
7526
|
-
child.stdout
|
|
7527
|
-
child.
|
|
7539
|
+
child.stderr?.off("data", onStderr);
|
|
7540
|
+
child.stderr?.destroy();
|
|
7541
|
+
child.stdout?.off("data", onStdout);
|
|
7542
|
+
child.stdout?.destroy();
|
|
7528
7543
|
}
|
|
7529
7544
|
reject(error);
|
|
7530
7545
|
});
|
|
@@ -7537,7 +7552,16 @@ var defaultProcessRunner = {
|
|
|
7537
7552
|
return;
|
|
7538
7553
|
}
|
|
7539
7554
|
settled = true;
|
|
7540
|
-
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);
|
|
7541
7565
|
});
|
|
7542
7566
|
});
|
|
7543
7567
|
}
|
|
@@ -7545,8 +7569,8 @@ var defaultProcessRunner = {
|
|
|
7545
7569
|
function bounded(value, maxOutputBytes = MAX_OUTPUT_BYTES) {
|
|
7546
7570
|
return Buffer.from(value).subarray(0, maxOutputBytes).toString("utf8");
|
|
7547
7571
|
}
|
|
7548
|
-
async function checked(runner, executable, args, timeoutMs, maxOutputBytes) {
|
|
7549
|
-
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);
|
|
7550
7574
|
if (result.code !== 0) {
|
|
7551
7575
|
const detail = redactDiagnostic(bounded(result.stderr).trim());
|
|
7552
7576
|
throw new Error(`OpenCode command exited ${result.code}${detail ? `: ${detail}` : ""}`);
|
|
@@ -7616,8 +7640,14 @@ function createClient(executable, runner) {
|
|
|
7616
7640
|
return checked(runner, executable, spec ? ["plugin", "check", spec] : ["plugin", "check"], 30000);
|
|
7617
7641
|
},
|
|
7618
7642
|
async debugAgents() {
|
|
7619
|
-
const
|
|
7620
|
-
|
|
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
|
+
}
|
|
7621
7651
|
},
|
|
7622
7652
|
serviceStatus() {
|
|
7623
7653
|
return checked(runner, executable, ["service", "status"]);
|
|
@@ -7724,23 +7754,23 @@ function checkGlobalFiles(config, configRoot) {
|
|
|
7724
7754
|
for (const [id, agent] of Object.entries(config.agents).sort(([left], [right]) => left.localeCompare(right))) {
|
|
7725
7755
|
if (agent.disabled)
|
|
7726
7756
|
continue;
|
|
7727
|
-
const path =
|
|
7757
|
+
const path = join5(configRoot, "agents", `${id}.md`);
|
|
7728
7758
|
if (!safeFile(path)) {
|
|
7729
7759
|
missing.push(id);
|
|
7730
7760
|
continue;
|
|
7731
7761
|
}
|
|
7732
|
-
const content =
|
|
7762
|
+
const content = readFileSync4(path, "utf8");
|
|
7733
7763
|
if (!hasGeneratedAgentMarker(content) || content !== renderAgent(agent))
|
|
7734
7764
|
stale.push(id);
|
|
7735
7765
|
}
|
|
7736
7766
|
const enabled = new Set(Object.entries(config.agents).filter(([, agent]) => !agent.disabled).map(([id]) => `${id}.md`));
|
|
7737
7767
|
const orphans = [];
|
|
7738
|
-
const directory =
|
|
7768
|
+
const directory = join5(configRoot, "agents");
|
|
7739
7769
|
if (existsSync3(directory) && lstatSync3(directory).isDirectory() && !lstatSync3(directory).isSymbolicLink()) {
|
|
7740
7770
|
for (const entry of readdirSync3(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
7741
7771
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
7742
7772
|
continue;
|
|
7743
|
-
const content =
|
|
7773
|
+
const content = readFileSync4(join5(directory, entry.name), "utf8");
|
|
7744
7774
|
if (hasGeneratedAgentMarker(content))
|
|
7745
7775
|
orphans.push(entry.name.slice(0, -3));
|
|
7746
7776
|
}
|
|
@@ -7757,10 +7787,10 @@ function checkGlobalFiles(config, configRoot) {
|
|
|
7757
7787
|
}
|
|
7758
7788
|
function checkLegacy(config) {
|
|
7759
7789
|
const duplicates = [];
|
|
7760
|
-
const plugin =
|
|
7790
|
+
const plugin = join5(config.projectRoot, ".opencode", "plugins", "agent-gvozd", "index.ts");
|
|
7761
7791
|
if (safeFile(plugin))
|
|
7762
7792
|
duplicates.push("local plugin");
|
|
7763
|
-
const agents = Object.keys(config.agents).filter((id) => safeFile(
|
|
7793
|
+
const agents = Object.keys(config.agents).filter((id) => safeFile(join5(config.projectRoot, ".opencode", "agents", `${id}.md`)));
|
|
7764
7794
|
if (agents.length > 0)
|
|
7765
7795
|
duplicates.push(`${agents.length} local agents`);
|
|
7766
7796
|
if (duplicates.length === 0)
|
|
@@ -7811,13 +7841,13 @@ async function runDoctor(input) {
|
|
|
7811
7841
|
}
|
|
7812
7842
|
let config;
|
|
7813
7843
|
let globalConfig;
|
|
7814
|
-
const configPath =
|
|
7815
|
-
const schemaPath =
|
|
7844
|
+
const configPath = join5(input.configRoot, "gvozd", "config.jsonc");
|
|
7845
|
+
const schemaPath = join5(input.configRoot, "gvozd", "schema.json");
|
|
7816
7846
|
try {
|
|
7817
7847
|
if (!safeFile(configPath) || !safeFile(schemaPath))
|
|
7818
7848
|
throw new Error("global config.jsonc or schema.json is missing");
|
|
7819
7849
|
const schemaErrors = [];
|
|
7820
|
-
const schema = parse2(
|
|
7850
|
+
const schema = parse2(readFileSync4(schemaPath, "utf8"), schemaErrors);
|
|
7821
7851
|
if (schemaErrors.length > 0 || schema?.["x-agent-gvozd-schema-version"] !== CONFIG_SCHEMA_VERSION || schema?.$comment !== GENERATED_PLUGIN_MARKER) {
|
|
7822
7852
|
throw new Error("global schema is invalid, incompatible, or unmanaged");
|
|
7823
7853
|
}
|
|
@@ -7885,17 +7915,17 @@ function doctorOperationalFailure(error) {
|
|
|
7885
7915
|
}
|
|
7886
7916
|
|
|
7887
7917
|
// src/cli/agents.ts
|
|
7888
|
-
import { existsSync as existsSync5, readFileSync as
|
|
7889
|
-
import { join as
|
|
7918
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
7919
|
+
import { join as join8 } from "node:path";
|
|
7890
7920
|
|
|
7891
7921
|
// src/cli/config-store.ts
|
|
7892
|
-
import { accessSync, closeSync, constants as fsConstants, existsSync as existsSync4, lstatSync as lstatSync5, mkdirSync, openSync, readFileSync as
|
|
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";
|
|
7893
7923
|
import { randomUUID } from "node:crypto";
|
|
7894
|
-
import { dirname as dirname3, join as
|
|
7924
|
+
import { dirname as dirname3, join as join7 } from "node:path";
|
|
7895
7925
|
|
|
7896
7926
|
// src/secure-path.ts
|
|
7897
7927
|
import { lstatSync as lstatSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
7898
|
-
import { isAbsolute as isAbsolute5, join as
|
|
7928
|
+
import { isAbsolute as isAbsolute5, join as join6, parse as parse6, relative as relative3, resolve as resolve5, sep } from "node:path";
|
|
7899
7929
|
function secureCanonicalPath(target, label = "Path") {
|
|
7900
7930
|
if (!isAbsolute5(target))
|
|
7901
7931
|
throw new Error(`${label} must be absolute: ${target}`);
|
|
@@ -7911,7 +7941,7 @@ function secureCanonicalPath(target, label = "Path") {
|
|
|
7911
7941
|
throw new Error(`${label} has an unsafe filesystem root: ${root}`);
|
|
7912
7942
|
const existingDirectories = [{ path: root, stat: rootStat }];
|
|
7913
7943
|
for (const component of components) {
|
|
7914
|
-
current =
|
|
7944
|
+
current = join6(current, component);
|
|
7915
7945
|
if (missing) {
|
|
7916
7946
|
missingSuffix.push(component);
|
|
7917
7947
|
continue;
|
|
@@ -7991,7 +8021,7 @@ function matchesSnapshot(path, expected) {
|
|
|
7991
8021
|
if (!existsSync4(path))
|
|
7992
8022
|
return false;
|
|
7993
8023
|
const stat = lstatSync5(path);
|
|
7994
|
-
return stat.isFile() && !stat.isSymbolicLink() && stat.dev === expected.dev && stat.ino === expected.ino &&
|
|
8024
|
+
return stat.isFile() && !stat.isSymbolicLink() && stat.dev === expected.dev && stat.ino === expected.ino && readFileSync5(path, "utf8") === expected.bytes;
|
|
7995
8025
|
}
|
|
7996
8026
|
function atomicWrite(path, content, expected) {
|
|
7997
8027
|
const canonicalPath = secureCanonicalPath(path, "Managed global config path");
|
|
@@ -8002,16 +8032,16 @@ function atomicWrite(path, content, expected) {
|
|
|
8002
8032
|
throw new Error(`Global config path changed during write: ${path}`);
|
|
8003
8033
|
assertWriteable(dirname3(canonicalPath), "Managed global config directory");
|
|
8004
8034
|
const temporary = `${canonicalPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
8005
|
-
const descriptor =
|
|
8035
|
+
const descriptor = openSync2(temporary, "wx", 384);
|
|
8006
8036
|
try {
|
|
8007
8037
|
writeFileSync(descriptor, content);
|
|
8008
|
-
|
|
8038
|
+
closeSync2(descriptor);
|
|
8009
8039
|
if (!matchesSnapshot(canonicalPath, expected))
|
|
8010
8040
|
throw new Error(`Refusing to replace concurrently changed file: ${canonicalPath}`);
|
|
8011
8041
|
renameSync(temporary, canonicalPath);
|
|
8012
8042
|
} catch (error) {
|
|
8013
8043
|
try {
|
|
8014
|
-
|
|
8044
|
+
closeSync2(descriptor);
|
|
8015
8045
|
} catch {}
|
|
8016
8046
|
try {
|
|
8017
8047
|
unlinkSync(temporary);
|
|
@@ -8063,7 +8093,7 @@ function snapshot(path) {
|
|
|
8063
8093
|
const stat = lstatSync5(path);
|
|
8064
8094
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
8065
8095
|
throw new Error(`Managed global config snapshot target is unsafe: ${path}`);
|
|
8066
|
-
return Object.freeze({ path, exists: true, bytes:
|
|
8096
|
+
return Object.freeze({ path, exists: true, bytes: readFileSync5(path, "utf8"), dev: stat.dev, ino: stat.ino });
|
|
8067
8097
|
}
|
|
8068
8098
|
function preflightGlobalConfig(configRoot, schemaSource) {
|
|
8069
8099
|
const canonicalRoot = secureCanonicalPath(configRoot, "OpenCode config root");
|
|
@@ -8072,24 +8102,24 @@ function preflightGlobalConfig(configRoot, schemaSource) {
|
|
|
8072
8102
|
throw new Error(`OpenCode config root is not a safe directory: ${canonicalRoot}`);
|
|
8073
8103
|
}
|
|
8074
8104
|
assertWriteable(canonicalRoot, "OpenCode config root");
|
|
8075
|
-
const directory = secureCanonicalPath(
|
|
8105
|
+
const directory = secureCanonicalPath(join7(canonicalRoot, "gvozd"), "Global Gvozd directory");
|
|
8076
8106
|
const directoryStat = existsSync4(directory) ? lstatSync5(directory) : undefined;
|
|
8077
8107
|
if (directoryStat && (directoryStat.isSymbolicLink() || !directoryStat.isDirectory())) {
|
|
8078
8108
|
throw new Error(`Global Gvozd path is not a safe directory: ${directory}`);
|
|
8079
8109
|
}
|
|
8080
8110
|
assertWriteable(directory, "Global Gvozd directory");
|
|
8081
|
-
const configPath =
|
|
8082
|
-
const schemaPath =
|
|
8111
|
+
const configPath = join7(directory, "config.jsonc");
|
|
8112
|
+
const schemaPath = join7(directory, "schema.json");
|
|
8083
8113
|
if (existsSync4(configPath)) {
|
|
8084
8114
|
if (!isRegularFile(configPath))
|
|
8085
8115
|
throw new Error(`Global Gvozd config is not a safe file: ${configPath}`);
|
|
8086
|
-
assertValidJsonc(
|
|
8116
|
+
assertValidJsonc(readFileSync5(configPath, "utf8"), configPath);
|
|
8087
8117
|
assertWriteable(configPath, "Global Gvozd config");
|
|
8088
8118
|
}
|
|
8089
8119
|
if (existsSync4(schemaPath)) {
|
|
8090
8120
|
if (!isRegularFile(schemaPath))
|
|
8091
8121
|
throw new Error(`Refusing to overwrite unmanaged Gvozd schema: ${schemaPath}`);
|
|
8092
|
-
const schema =
|
|
8122
|
+
const schema = readFileSync5(schemaPath, "utf8");
|
|
8093
8123
|
if (!hasGeneratedSchemaMarker(schema) && !legacySchemaMatches(schema, schemaSource)) {
|
|
8094
8124
|
throw new Error(`Refusing to overwrite unmanaged Gvozd schema: ${schemaPath}`);
|
|
8095
8125
|
}
|
|
@@ -8102,9 +8132,9 @@ function writeGlobalConfig(input) {
|
|
|
8102
8132
|
const canonicalRoot = secureCanonicalPath(input.configRoot, "OpenCode config root");
|
|
8103
8133
|
if (state.configRoot !== canonicalRoot)
|
|
8104
8134
|
throw new Error("Global config snapshot belongs to another config root");
|
|
8105
|
-
const directory =
|
|
8106
|
-
const configPath =
|
|
8107
|
-
const schemaPath =
|
|
8135
|
+
const directory = join7(state.configRoot, "gvozd");
|
|
8136
|
+
const configPath = join7(directory, "config.jsonc");
|
|
8137
|
+
const schemaPath = join7(directory, "schema.json");
|
|
8108
8138
|
const base = state.config.exists ? state.config.bytes : `{
|
|
8109
8139
|
"$schema": "./schema.json",
|
|
8110
8140
|
"agents": {}
|
|
@@ -8153,21 +8183,21 @@ function setAgentDisabled(source, agentID, disabled) {
|
|
|
8153
8183
|
return applyEdits(source, modify(source, ["agents", agentID, "disabled"], disabled, { formattingOptions: formattingOptions2 }));
|
|
8154
8184
|
}
|
|
8155
8185
|
function readGlobalConfig(configRoot) {
|
|
8156
|
-
const path =
|
|
8186
|
+
const path = join8(configRoot, "gvozd", "config.jsonc");
|
|
8157
8187
|
if (!existsSync5(path))
|
|
8158
8188
|
return `{
|
|
8159
8189
|
"$schema": "./schema.json",
|
|
8160
8190
|
"agents": {}
|
|
8161
8191
|
}
|
|
8162
8192
|
`;
|
|
8163
|
-
const source =
|
|
8193
|
+
const source = readFileSync6(path, "utf8");
|
|
8164
8194
|
assertValidJsonc2(source, path);
|
|
8165
8195
|
return source;
|
|
8166
8196
|
}
|
|
8167
8197
|
|
|
8168
8198
|
// src/cli/setup.ts
|
|
8169
|
-
import { existsSync as existsSync8, readFileSync as
|
|
8170
|
-
import { dirname as dirname6, join as
|
|
8199
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
8200
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
8171
8201
|
|
|
8172
8202
|
// src/cli/configure.ts
|
|
8173
8203
|
function availableProfile(profile, catalog) {
|
|
@@ -8238,9 +8268,9 @@ function profileFromAgents(agents, catalog) {
|
|
|
8238
8268
|
}
|
|
8239
8269
|
|
|
8240
8270
|
// src/cli/global-sync.ts
|
|
8241
|
-
import { accessSync as accessSync2, closeSync as
|
|
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";
|
|
8242
8272
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8243
|
-
import { dirname as dirname4, join as
|
|
8273
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
8244
8274
|
function stat(path) {
|
|
8245
8275
|
try {
|
|
8246
8276
|
return lstatSync6(path);
|
|
@@ -8274,11 +8304,11 @@ function createFile(path, content) {
|
|
|
8274
8304
|
const canonicalPath = secureCanonicalPath(path, "Managed global agent path");
|
|
8275
8305
|
if (canonicalPath !== path)
|
|
8276
8306
|
throw new Error(`Managed global agent path changed: ${path}`);
|
|
8277
|
-
const descriptor =
|
|
8307
|
+
const descriptor = openSync3(canonicalPath, "wx", 384);
|
|
8278
8308
|
try {
|
|
8279
8309
|
writeFileSync2(descriptor, content);
|
|
8280
8310
|
} finally {
|
|
8281
|
-
|
|
8311
|
+
closeSync3(descriptor);
|
|
8282
8312
|
}
|
|
8283
8313
|
}
|
|
8284
8314
|
function replaceFile(path, content) {
|
|
@@ -8297,7 +8327,7 @@ function replaceFile(path, content) {
|
|
|
8297
8327
|
}
|
|
8298
8328
|
function writeManagedAgents(input) {
|
|
8299
8329
|
const configRoot = secureCanonicalPath(input.configRoot, "OpenCode config root");
|
|
8300
|
-
const agentsDirectory = secureCanonicalPath(
|
|
8330
|
+
const agentsDirectory = secureCanonicalPath(join9(configRoot, "agents"), "OpenCode agents path");
|
|
8301
8331
|
const result = { created: [], updated: [], unchanged: [], conflicts: [], removed: [] };
|
|
8302
8332
|
const writes = [];
|
|
8303
8333
|
const removals = new Map;
|
|
@@ -8316,21 +8346,21 @@ function writeManagedAgents(input) {
|
|
|
8316
8346
|
for (const entry of readdirSync4(agentsDirectory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
8317
8347
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
8318
8348
|
continue;
|
|
8319
|
-
const path =
|
|
8349
|
+
const path = join9(agentsDirectory, entry.name);
|
|
8320
8350
|
const currentStat = stat(path);
|
|
8321
8351
|
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile())
|
|
8322
8352
|
continue;
|
|
8323
|
-
if (hasGeneratedAgentMarker(
|
|
8353
|
+
if (hasGeneratedAgentMarker(readFileSync7(path, "utf8"))) {
|
|
8324
8354
|
assertWriteable2(path, "Managed global agent");
|
|
8325
8355
|
result.removed.push(path);
|
|
8326
|
-
removals.set(path,
|
|
8356
|
+
removals.set(path, readFileSync7(path, "utf8"));
|
|
8327
8357
|
}
|
|
8328
8358
|
}
|
|
8329
8359
|
}
|
|
8330
8360
|
for (const [id, agent] of Object.entries(input.agents).sort(([left], [right]) => left.localeCompare(right))) {
|
|
8331
8361
|
if (agent.disabled)
|
|
8332
8362
|
continue;
|
|
8333
|
-
const path =
|
|
8363
|
+
const path = join9(agentsDirectory, `${id}.md`);
|
|
8334
8364
|
const content = renderAgent(agent);
|
|
8335
8365
|
const currentStat = stat(path);
|
|
8336
8366
|
if (!currentStat) {
|
|
@@ -8342,7 +8372,7 @@ function writeManagedAgents(input) {
|
|
|
8342
8372
|
result.conflicts.push(path);
|
|
8343
8373
|
continue;
|
|
8344
8374
|
}
|
|
8345
|
-
const current =
|
|
8375
|
+
const current = readFileSync7(path, "utf8");
|
|
8346
8376
|
if (!hasGeneratedAgentMarker(current)) {
|
|
8347
8377
|
result.conflicts.push(path);
|
|
8348
8378
|
continue;
|
|
@@ -8380,7 +8410,7 @@ function writeManagedAgents(input) {
|
|
|
8380
8410
|
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile()) {
|
|
8381
8411
|
throw new Error(`Refusing to remove a changed or unmanaged global agent: ${path}`);
|
|
8382
8412
|
}
|
|
8383
|
-
const current =
|
|
8413
|
+
const current = readFileSync7(path, "utf8");
|
|
8384
8414
|
if (!hasGeneratedAgentMarker(current) || current !== removals.get(path)) {
|
|
8385
8415
|
throw new Error(`Refusing to remove a concurrently changed global agent: ${path}`);
|
|
8386
8416
|
}
|
|
@@ -8389,7 +8419,7 @@ function writeManagedAgents(input) {
|
|
|
8389
8419
|
for (const write of writes) {
|
|
8390
8420
|
if (write.replace) {
|
|
8391
8421
|
const currentStat = stat(write.path);
|
|
8392
|
-
const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ?
|
|
8422
|
+
const current = currentStat?.isFile() && !currentStat.isSymbolicLink() ? readFileSync7(write.path, "utf8") : undefined;
|
|
8393
8423
|
if (!current || !hasGeneratedAgentMarker(current) || current !== write.previous) {
|
|
8394
8424
|
throw new Error(`Refusing to replace a concurrently changed global agent: ${write.path}`);
|
|
8395
8425
|
}
|
|
@@ -8402,8 +8432,30 @@ function writeManagedAgents(input) {
|
|
|
8402
8432
|
|
|
8403
8433
|
// src/file-lock.ts
|
|
8404
8434
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8405
|
-
import { accessSync as accessSync3, closeSync as
|
|
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";
|
|
8406
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
|
+
}
|
|
8407
8459
|
function assertSecure(path) {
|
|
8408
8460
|
const stat = lstatSync7(path);
|
|
8409
8461
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
@@ -8425,22 +8477,48 @@ function acquire(path, operation) {
|
|
|
8425
8477
|
`;
|
|
8426
8478
|
let descriptor;
|
|
8427
8479
|
try {
|
|
8428
|
-
descriptor =
|
|
8480
|
+
descriptor = openSync4(canonicalPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
|
|
8429
8481
|
} catch (error) {
|
|
8430
|
-
if (error.code
|
|
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 {
|
|
8431
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`);
|
|
8432
8488
|
}
|
|
8433
|
-
throw error;
|
|
8434
8489
|
}
|
|
8435
8490
|
const created = fstatSync(descriptor);
|
|
8436
8491
|
writeFileSync3(descriptor, nonce);
|
|
8437
8492
|
return { path: canonicalPath, descriptor, nonce, dev: created.dev, ino: created.ino };
|
|
8438
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
|
+
}
|
|
8439
8517
|
function release(lock) {
|
|
8440
|
-
|
|
8518
|
+
closeSync4(lock.descriptor);
|
|
8441
8519
|
if (existsSync7(lock.path)) {
|
|
8442
8520
|
const current = lstatSync7(lock.path);
|
|
8443
|
-
if (current.isFile() && !current.isSymbolicLink() && current.dev === lock.dev && current.ino === lock.ino &&
|
|
8521
|
+
if (current.isFile() && !current.isSymbolicLink() && current.dev === lock.dev && current.ino === lock.ino && readFileSync8(lock.path, "utf8") === lock.nonce)
|
|
8444
8522
|
unlinkSync3(lock.path);
|
|
8445
8523
|
}
|
|
8446
8524
|
}
|
|
@@ -8470,7 +8548,7 @@ function assertVersion(version) {
|
|
|
8470
8548
|
async function selectProfile(input, client, configRoot) {
|
|
8471
8549
|
const catalog = parseModels(await client.models());
|
|
8472
8550
|
const config = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8473
|
-
const hasGlobalConfig = existsSync8(
|
|
8551
|
+
const hasGlobalConfig = existsSync8(join10(configRoot, "gvozd", "config.jsonc"));
|
|
8474
8552
|
return chooseModelProfile({
|
|
8475
8553
|
catalog,
|
|
8476
8554
|
ui: input.ui,
|
|
@@ -8522,8 +8600,8 @@ async function runSetup(input) {
|
|
|
8522
8600
|
const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
|
|
8523
8601
|
assertVersion(await client.version());
|
|
8524
8602
|
const before = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8525
|
-
const schemaSource =
|
|
8526
|
-
const packagedSchema =
|
|
8603
|
+
const schemaSource = join10(dirname6(before.sources[0]), "schema.json");
|
|
8604
|
+
const packagedSchema = readFileSync9(schemaSource, "utf8");
|
|
8527
8605
|
const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
|
|
8528
8606
|
const preview = writeManagedAgents({ configRoot, agents: before.agents, check: true });
|
|
8529
8607
|
const profile = await selectProfile(input, client, configRoot);
|
|
@@ -8531,17 +8609,17 @@ async function runSetup(input) {
|
|
|
8531
8609
|
return { status: "cancelled" };
|
|
8532
8610
|
input.output?.([
|
|
8533
8611
|
`Register ${PACKAGE_SPEC}`,
|
|
8534
|
-
`Write ${
|
|
8535
|
-
`Write managed agents in ${
|
|
8612
|
+
`Write ${join10(configRoot, "gvozd", "config.jsonc")}`,
|
|
8613
|
+
`Write managed agents in ${join10(configRoot, "agents")}`,
|
|
8536
8614
|
...preview.removed.length > 0 ? [`Remove ${preview.removed.length} stale or disabled managed agent(s)`] : []
|
|
8537
8615
|
].join(`
|
|
8538
8616
|
`));
|
|
8539
8617
|
if (!await confirm2(input, "Run setup?"))
|
|
8540
8618
|
return { status: "cancelled" };
|
|
8541
|
-
return withExclusiveFileLock(
|
|
8619
|
+
return withExclusiveFileLock(join10(configRoot, "gvozd", "setup.lock"), async () => {
|
|
8542
8620
|
if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
|
|
8543
8621
|
throw new Error("OpenCode config root changed while setup awaited the lock");
|
|
8544
|
-
const lockedSchema =
|
|
8622
|
+
const lockedSchema = readFileSync9(schemaSource, "utf8");
|
|
8545
8623
|
const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
|
|
8546
8624
|
if (!sameSnapshot(previewSnapshot, snapshot))
|
|
8547
8625
|
throw new Error("Global Gvozd configuration changed while setup awaited confirmation; review and rerun setup");
|
|
@@ -8582,16 +8660,16 @@ async function runConfigure(input) {
|
|
|
8582
8660
|
const runtimeConfigRoot = input.runtimeConfigRoot ?? resolveOpenCodeConfigRoot();
|
|
8583
8661
|
assertVersion(await client.version());
|
|
8584
8662
|
const config = loadConfig(input.cwd, { configRoot, includeProject: false });
|
|
8585
|
-
const schemaSource =
|
|
8586
|
-
const packagedSchema =
|
|
8663
|
+
const schemaSource = join10(dirname6(config.sources[0]), "schema.json");
|
|
8664
|
+
const packagedSchema = readFileSync9(schemaSource, "utf8");
|
|
8587
8665
|
const previewSnapshot = preflightGlobalConfig(configRoot, packagedSchema);
|
|
8588
8666
|
const profile = await selectProfile(input, client, configRoot);
|
|
8589
8667
|
if (!profile || !await confirm2(input, "Apply model configuration?"))
|
|
8590
8668
|
return { status: "cancelled" };
|
|
8591
|
-
return withExclusiveFileLock(
|
|
8669
|
+
return withExclusiveFileLock(join10(configRoot, "gvozd", "setup.lock"), async () => {
|
|
8592
8670
|
if (secureCanonicalPath(configRoot, "OpenCode config root") !== configRoot)
|
|
8593
8671
|
throw new Error("OpenCode config root changed while configuration awaited the lock");
|
|
8594
|
-
const lockedSchema =
|
|
8672
|
+
const lockedSchema = readFileSync9(schemaSource, "utf8");
|
|
8595
8673
|
const snapshot = preflightGlobalConfig(configRoot, lockedSchema);
|
|
8596
8674
|
if (!sameSnapshot(previewSnapshot, snapshot))
|
|
8597
8675
|
throw new Error("Global Gvozd configuration changed while configuration awaited confirmation; review and rerun");
|
|
@@ -8615,13 +8693,13 @@ function setupExitCode(result) {
|
|
|
8615
8693
|
}
|
|
8616
8694
|
|
|
8617
8695
|
// src/sync.ts
|
|
8618
|
-
import { closeSync as
|
|
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";
|
|
8619
8697
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
8620
|
-
import { basename, dirname as dirname7, join as
|
|
8698
|
+
import { basename, dirname as dirname7, join as join11, relative as relative4 } from "node:path";
|
|
8621
8699
|
function renderPluginEntrypoint(config, destination) {
|
|
8622
8700
|
let moduleSpecifier = "agent-gvozd/server";
|
|
8623
8701
|
if (realpathSync4(config.packageRoot) === realpathSync4(config.projectRoot)) {
|
|
8624
|
-
moduleSpecifier = relative4(destination,
|
|
8702
|
+
moduleSpecifier = relative4(destination, join11(config.packageRoot, "src", "index")).replaceAll("\\", "/");
|
|
8625
8703
|
if (!moduleSpecifier.startsWith("."))
|
|
8626
8704
|
moduleSpecifier = `./${moduleSpecifier}`;
|
|
8627
8705
|
}
|
|
@@ -8666,7 +8744,7 @@ function stat2(path) {
|
|
|
8666
8744
|
function safeDirectory(root, segments, create) {
|
|
8667
8745
|
let current = realpathSync4(root);
|
|
8668
8746
|
for (const segment of segments) {
|
|
8669
|
-
current =
|
|
8747
|
+
current = join11(current, segment);
|
|
8670
8748
|
const currentStat = stat2(current);
|
|
8671
8749
|
if (currentStat) {
|
|
8672
8750
|
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
|
|
@@ -8688,11 +8766,11 @@ function assertRegularFile(path) {
|
|
|
8688
8766
|
return true;
|
|
8689
8767
|
}
|
|
8690
8768
|
function createFile2(path, content) {
|
|
8691
|
-
const descriptor =
|
|
8769
|
+
const descriptor = openSync5(path, "wx", 384);
|
|
8692
8770
|
try {
|
|
8693
8771
|
writeFileSync4(descriptor, content);
|
|
8694
8772
|
} finally {
|
|
8695
|
-
|
|
8773
|
+
closeSync5(descriptor);
|
|
8696
8774
|
}
|
|
8697
8775
|
}
|
|
8698
8776
|
function replaceFile2(path, content) {
|
|
@@ -8710,7 +8788,7 @@ function replaceFile2(path, content) {
|
|
|
8710
8788
|
function planProjectTemplate(config, result, check) {
|
|
8711
8789
|
const directory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], !check);
|
|
8712
8790
|
const writes = [];
|
|
8713
|
-
const configPath =
|
|
8791
|
+
const configPath = join11(directory, "config.jsonc");
|
|
8714
8792
|
if (!assertRegularFile(configPath)) {
|
|
8715
8793
|
result.created.push(configPath);
|
|
8716
8794
|
writes.push({
|
|
@@ -8727,14 +8805,14 @@ function planProjectTemplate(config, result, check) {
|
|
|
8727
8805
|
`)
|
|
8728
8806
|
});
|
|
8729
8807
|
}
|
|
8730
|
-
const schemaSource =
|
|
8731
|
-
const schemaTarget =
|
|
8732
|
-
const schema =
|
|
8808
|
+
const schemaSource = join11(dirname7(config.sources[0]), "schema.json");
|
|
8809
|
+
const schemaTarget = join11(directory, "schema.json");
|
|
8810
|
+
const schema = readFileSync10(schemaSource, "utf8");
|
|
8733
8811
|
if (!assertRegularFile(schemaTarget)) {
|
|
8734
8812
|
result.created.push(schemaTarget);
|
|
8735
8813
|
writes.push({ target: schemaTarget, content: schema, replace: false });
|
|
8736
8814
|
} else {
|
|
8737
|
-
const current =
|
|
8815
|
+
const current = readFileSync10(schemaTarget, "utf8");
|
|
8738
8816
|
if (current !== schema) {
|
|
8739
8817
|
if (!hasGeneratedSchemaMarker(current) && !isEquivalentLegacySchema(current, schema)) {
|
|
8740
8818
|
throw new Error(`Refusing to overwrite an unmanaged project schema: ${schemaTarget}`);
|
|
@@ -8759,8 +8837,8 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8759
8837
|
for (const entry of readdirSync5(destination, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
8760
8838
|
if (!entry.isFile() || !entry.name.endsWith(".md") || enabled.has(entry.name))
|
|
8761
8839
|
continue;
|
|
8762
|
-
const target =
|
|
8763
|
-
const current =
|
|
8840
|
+
const target = join11(destination, entry.name);
|
|
8841
|
+
const current = readFileSync10(target, "utf8");
|
|
8764
8842
|
if (!hasGeneratedAgentMarker(current))
|
|
8765
8843
|
continue;
|
|
8766
8844
|
result.removed.push(target);
|
|
@@ -8771,14 +8849,14 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8771
8849
|
for (const [id, agent] of Object.entries(config.agents)) {
|
|
8772
8850
|
if (agent.disabled)
|
|
8773
8851
|
continue;
|
|
8774
|
-
const target =
|
|
8852
|
+
const target = join11(destination, `${id}.md`);
|
|
8775
8853
|
const content = renderAgent(agent);
|
|
8776
8854
|
if (!assertRegularFile(target)) {
|
|
8777
8855
|
result.created.push(target);
|
|
8778
8856
|
writes.push({ target, content, replace: false });
|
|
8779
8857
|
continue;
|
|
8780
8858
|
}
|
|
8781
|
-
const current =
|
|
8859
|
+
const current = readFileSync10(target, "utf8");
|
|
8782
8860
|
if (current === content) {
|
|
8783
8861
|
result.unchanged.push(target);
|
|
8784
8862
|
continue;
|
|
@@ -8790,11 +8868,11 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8790
8868
|
options.onDiff?.(renderDiff(target, current, content));
|
|
8791
8869
|
writes.push({ target, content, replace: true, previous: current });
|
|
8792
8870
|
}
|
|
8793
|
-
const pluginTarget =
|
|
8871
|
+
const pluginTarget = join11(pluginDestination, "index.ts");
|
|
8794
8872
|
const pluginContent = renderPluginEntrypoint(config, pluginDestination);
|
|
8795
8873
|
if (!options.devPlugin) {
|
|
8796
8874
|
if (assertRegularFile(pluginTarget)) {
|
|
8797
|
-
const current =
|
|
8875
|
+
const current = readFileSync10(pluginTarget, "utf8");
|
|
8798
8876
|
if (hasGeneratedPluginMarker(current)) {
|
|
8799
8877
|
result.removed.push(pluginTarget);
|
|
8800
8878
|
removals.set(pluginTarget, current);
|
|
@@ -8805,7 +8883,7 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8805
8883
|
result.created.push(pluginTarget);
|
|
8806
8884
|
pluginWrite = { target: pluginTarget, content: pluginContent, replace: false };
|
|
8807
8885
|
} else {
|
|
8808
|
-
const current =
|
|
8886
|
+
const current = readFileSync10(pluginTarget, "utf8");
|
|
8809
8887
|
if (current === pluginContent) {
|
|
8810
8888
|
result.unchanged.push(pluginTarget);
|
|
8811
8889
|
} else {
|
|
@@ -8824,7 +8902,7 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8824
8902
|
if (!assertRegularFile(target)) {
|
|
8825
8903
|
throw new Error(`Refusing to remove a changed or unsafe generated file: ${target}`);
|
|
8826
8904
|
}
|
|
8827
|
-
const current =
|
|
8905
|
+
const current = readFileSync10(target, "utf8");
|
|
8828
8906
|
const generated = target.endsWith("index.ts") ? hasGeneratedPluginMarker(current) : hasGeneratedAgentMarker(current);
|
|
8829
8907
|
if (!generated || current !== removals.get(target)) {
|
|
8830
8908
|
throw new Error(`Refusing to remove a concurrently changed generated file: ${target}`);
|
|
@@ -8833,27 +8911,27 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8833
8911
|
}
|
|
8834
8912
|
for (const write of writes) {
|
|
8835
8913
|
if (!write.replace) {
|
|
8836
|
-
createFile2(
|
|
8914
|
+
createFile2(join11(writableDestination, basename(write.target)), write.content);
|
|
8837
8915
|
continue;
|
|
8838
8916
|
}
|
|
8839
8917
|
if (!assertRegularFile(write.target)) {
|
|
8840
8918
|
throw new Error(`Refusing to replace a changed or unsafe agent file: ${write.target}`);
|
|
8841
8919
|
}
|
|
8842
|
-
const current =
|
|
8920
|
+
const current = readFileSync10(write.target, "utf8");
|
|
8843
8921
|
if (!hasGeneratedAgentMarker(current) || current !== write.previous) {
|
|
8844
8922
|
throw new Error(`Refusing to replace a concurrently changed agent file: ${write.target}`);
|
|
8845
8923
|
}
|
|
8846
8924
|
replaceFile2(write.target, write.content);
|
|
8847
8925
|
}
|
|
8848
8926
|
if (pluginWrite) {
|
|
8849
|
-
const target =
|
|
8927
|
+
const target = join11(writablePluginDestination, basename(pluginWrite.target));
|
|
8850
8928
|
if (!pluginWrite.replace) {
|
|
8851
8929
|
createFile2(target, pluginWrite.content);
|
|
8852
8930
|
} else {
|
|
8853
8931
|
if (!assertRegularFile(target)) {
|
|
8854
8932
|
throw new Error(`Refusing to replace a changed or unsafe plugin entrypoint: ${target}`);
|
|
8855
8933
|
}
|
|
8856
|
-
const current =
|
|
8934
|
+
const current = readFileSync10(target, "utf8");
|
|
8857
8935
|
if (!hasGeneratedPluginMarker(current) || current !== pluginWrite.previous) {
|
|
8858
8936
|
throw new Error(`Refusing to replace a concurrently changed plugin entrypoint: ${target}`);
|
|
8859
8937
|
}
|
|
@@ -8862,11 +8940,11 @@ function syncAgentsUnlocked(config, options) {
|
|
|
8862
8940
|
}
|
|
8863
8941
|
const templateDirectory = safeDirectory(config.projectRoot, ["docs", ".gvozd"], true);
|
|
8864
8942
|
for (const write of templateWrites) {
|
|
8865
|
-
const target =
|
|
8943
|
+
const target = join11(templateDirectory, basename(write.target));
|
|
8866
8944
|
if (!write.replace)
|
|
8867
8945
|
createFile2(target, write.content);
|
|
8868
8946
|
else {
|
|
8869
|
-
if (!assertRegularFile(target) ||
|
|
8947
|
+
if (!assertRegularFile(target) || readFileSync10(target, "utf8") !== write.previous) {
|
|
8870
8948
|
throw new Error(`Refusing to replace a concurrently changed project schema: ${target}`);
|
|
8871
8949
|
}
|
|
8872
8950
|
replaceFile2(target, write.content);
|
|
@@ -8881,7 +8959,7 @@ function syncAgents(config, options = {}) {
|
|
|
8881
8959
|
if (options.check)
|
|
8882
8960
|
return syncAgentsUnlocked(config, options);
|
|
8883
8961
|
const root = realpathSync4(config.projectRoot);
|
|
8884
|
-
return withExclusiveFileLockSync(
|
|
8962
|
+
return withExclusiveFileLockSync(join11(root, ".agent-gvozd-sync.lock"), () => syncAgentsUnlocked(config, options), "sync");
|
|
8885
8963
|
}
|
|
8886
8964
|
function formatSyncResult(result, check) {
|
|
8887
8965
|
const lines = [check ? "agent-gvozd sync check" : "agent-gvozd sync complete"];
|
|
@@ -9017,10 +9095,12 @@ async function runCli(args, io = defaultIO, commands = { setup: runSetup, config
|
|
|
9017
9095
|
if (agentID === undefined || agentID.startsWith("--") || rest.length > 2)
|
|
9018
9096
|
return usage(io);
|
|
9019
9097
|
const configRoot = resolveOpenCodeConfigRoot();
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
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
|
+
});
|
|
9024
9104
|
io.stdout(`${action}d ${agentID}; run gvozd sync and gvozd doctor to apply`);
|
|
9025
9105
|
return 0;
|
|
9026
9106
|
}
|
|
@@ -9040,8 +9120,13 @@ async function runCli(args, io = defaultIO, commands = { setup: runSetup, config
|
|
|
9040
9120
|
}
|
|
9041
9121
|
}
|
|
9042
9122
|
var invokedPath = process.argv[1];
|
|
9043
|
-
|
|
9044
|
-
|
|
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;
|
|
9045
9130
|
}
|
|
9046
9131
|
export {
|
|
9047
9132
|
runCli
|
package/dist/index.js
CHANGED
|
@@ -78,16 +78,27 @@ function normalizeMcpName(name) {
|
|
|
78
78
|
return name.replaceAll(/[^A-Za-z0-9_-]/g, "_");
|
|
79
79
|
}
|
|
80
80
|
function wildcardMatch(pattern, value) {
|
|
81
|
-
let
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
let patternIndex = 0;
|
|
82
|
+
let valueIndex = 0;
|
|
83
|
+
let starPatternIndex = -1;
|
|
84
|
+
let restartValueIndex = 0;
|
|
85
|
+
while (valueIndex < value.length) {
|
|
86
|
+
if (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
87
|
+
starPatternIndex = patternIndex++;
|
|
88
|
+
restartValueIndex = valueIndex;
|
|
89
|
+
} else if (patternIndex < pattern.length && (pattern[patternIndex] === "?" || pattern[patternIndex] === value[valueIndex])) {
|
|
90
|
+
patternIndex++;
|
|
91
|
+
valueIndex++;
|
|
92
|
+
} else if (starPatternIndex >= 0) {
|
|
93
|
+
patternIndex = starPatternIndex + 1;
|
|
94
|
+
valueIndex = ++restartValueIndex;
|
|
95
|
+
} else {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
|
-
|
|
99
|
+
while (patternIndex < pattern.length && pattern[patternIndex] === "*")
|
|
100
|
+
patternIndex++;
|
|
101
|
+
return patternIndex === pattern.length;
|
|
91
102
|
}
|
|
92
103
|
function explicitMcpAccess(agent, action, resources) {
|
|
93
104
|
if (resources.length === 0)
|
|
@@ -6455,25 +6466,13 @@ var GvozdLeases = Rpc.define({
|
|
|
6455
6466
|
function evaluateEffect(rules, action, resource) {
|
|
6456
6467
|
let matched;
|
|
6457
6468
|
for (const rule of rules) {
|
|
6458
|
-
if (
|
|
6469
|
+
if (wildcardMatch(rule.action, action) && wildcardMatch(rule.resource, resource))
|
|
6459
6470
|
matched = rule;
|
|
6460
6471
|
}
|
|
6461
6472
|
if (!matched)
|
|
6462
6473
|
return { effect: "unknown", matchedRule: null };
|
|
6463
6474
|
return { effect: matched.effect, matchedRule: `${matched.action} ${matched.resource}` };
|
|
6464
6475
|
}
|
|
6465
|
-
function match(pattern, value) {
|
|
6466
|
-
let source = "^";
|
|
6467
|
-
for (const character of pattern) {
|
|
6468
|
-
if (character === "*")
|
|
6469
|
-
source += ".*";
|
|
6470
|
-
else if (character === "?")
|
|
6471
|
-
source += ".";
|
|
6472
|
-
else
|
|
6473
|
-
source += character.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
6474
|
-
}
|
|
6475
|
-
return new RegExp(`${source}$`).test(value);
|
|
6476
|
-
}
|
|
6477
6476
|
function evaluateInput(agentRules, input) {
|
|
6478
6477
|
const results = input.checks.flatMap((request) => (request.resources.length > 0 ? request.resources : ["*"]).map((resource) => {
|
|
6479
6478
|
const evaluation = agentRules ? evaluateEffect(agentRules, request.action, resource) : { effect: "unknown", matchedRule: null };
|
|
@@ -6544,7 +6543,9 @@ function modePermissions(mode) {
|
|
|
6544
6543
|
{ action: "shell", resource: "git filter-repo*", effect: "deny" },
|
|
6545
6544
|
{ action: "shell", resource: "git rebase*", effect: "deny" },
|
|
6546
6545
|
{ action: "shell", resource: "git checkout --*", effect: "deny" },
|
|
6547
|
-
{ action: "shell", resource: "git restore*", effect: "deny" }
|
|
6546
|
+
{ action: "shell", resource: "git restore*", effect: "deny" },
|
|
6547
|
+
{ action: "shell", resource: "git rebase --abort*", effect: "allow" },
|
|
6548
|
+
{ action: "shell", resource: "git rebase --continue*", effect: "allow" }
|
|
6548
6549
|
];
|
|
6549
6550
|
}
|
|
6550
6551
|
if (mode === "strict") {
|
|
@@ -6645,6 +6646,8 @@ function enforceFileLeasePermission(event, config, manager) {
|
|
|
6645
6646
|
if (event.action !== "shell" && event.action !== "bash")
|
|
6646
6647
|
return false;
|
|
6647
6648
|
if (role === "coordinator") {
|
|
6649
|
+
if (!manager.hasActiveLeases())
|
|
6650
|
+
return false;
|
|
6648
6651
|
return deny(event, `Agent ${event.agent} cannot use shell while file leases enforce structured mutations`);
|
|
6649
6652
|
}
|
|
6650
6653
|
if (role === "writer" && safeReadonlyShell(event.agent, event.resources)) {
|
package/dist/tui.js
CHANGED
|
@@ -9,6 +9,71 @@ import { usePlugin } from "@opencode/plugin/tui";
|
|
|
9
9
|
|
|
10
10
|
// src/permissions-rpc.ts
|
|
11
11
|
import { Rpc } from "@opencode/plugin/rpc";
|
|
12
|
+
|
|
13
|
+
// src/tool-permissions.ts
|
|
14
|
+
function family(command, ...variants) {
|
|
15
|
+
return [command, ...variants].map((entry) => ({
|
|
16
|
+
exact: entry,
|
|
17
|
+
wildcard: `${entry} *`
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
function exactOnly(command, ...variants) {
|
|
21
|
+
return [command, ...variants].map((entry) => ({ exact: entry, wildcard: entry }));
|
|
22
|
+
}
|
|
23
|
+
var INSPECTION_COMMANDS = [
|
|
24
|
+
...family("pwd", "true", "test"),
|
|
25
|
+
...family("cat", "head", "tail", "wc", "sort", "uniq"),
|
|
26
|
+
...family("grep", "rg", "find", "diff", "cmp"),
|
|
27
|
+
...family("ls", "du", "df", "stat", "file", "realpath", "basename", "dirname"),
|
|
28
|
+
...family("shasum", "sha256sum", "md5sum"),
|
|
29
|
+
...family("uname", "whoami", "hostname", "date", "printenv"),
|
|
30
|
+
...family("which", "command -v"),
|
|
31
|
+
...family("mktemp"),
|
|
32
|
+
...family("tr", "cut", "paste", "column"),
|
|
33
|
+
...family("node --version", "python3 --version", "python --version", "deno --version")
|
|
34
|
+
];
|
|
35
|
+
var TOOLCHAIN_COMMANDS = [
|
|
36
|
+
...family("bun test", "bun run test", "bun --version"),
|
|
37
|
+
...family("bun run typecheck", "bun run lint", "bun run build", "bun run check"),
|
|
38
|
+
...family("tsc --noEmit", "npx tsc --noEmit"),
|
|
39
|
+
...family("eslint", "biome check", "prettier --check"),
|
|
40
|
+
...family("npm test", "npm run test", "npm run typecheck", "npm run lint", "npm run build"),
|
|
41
|
+
...family("pnpm test", "pnpm run test", "pnpm run build"),
|
|
42
|
+
...family("yarn test", "yarn build"),
|
|
43
|
+
...family("vitest run", "jest", "playwright test"),
|
|
44
|
+
...family("cargo check", "cargo test", "cargo build", "cargo clippy", "cargo fmt --check", "cargo --version"),
|
|
45
|
+
...family("go build ./...", "go test ./...", "go vet ./...", "go version"),
|
|
46
|
+
...family("pytest", "python3 -m pytest", "python -m pytest"),
|
|
47
|
+
...family("ruff check", "mypy", "pyright"),
|
|
48
|
+
...family("mvn test", "mvn verify", "gradle test", "gradle check", "./gradlew test", "./gradlew check"),
|
|
49
|
+
...family("make test", "make check", "make build", "make --version"),
|
|
50
|
+
...family("just --list")
|
|
51
|
+
];
|
|
52
|
+
var GIT_READONLY_COMMANDS = [
|
|
53
|
+
...family("git status", "git status --short", "git status --short --branch", "git status --porcelain", "git status --porcelain=v1 --branch"),
|
|
54
|
+
...family("git diff", "git diff --stat", "git diff --cached", "git diff --check"),
|
|
55
|
+
...family("git log", "git show"),
|
|
56
|
+
...family("git rev-parse", "git rev-list", "git show-ref", "git cat-file"),
|
|
57
|
+
...exactOnly("git symbolic-ref HEAD", "git symbolic-ref --short HEAD"),
|
|
58
|
+
...family("git ls-files", "git ls-remote", "git grep"),
|
|
59
|
+
...exactOnly("git branch", "git tag", "git remote", "git reflog"),
|
|
60
|
+
...family("git branch --list", "git branch -l", "git branch -a", "git branch -r", "git branch -v", "git branch -vv", "git branch --all", "git branch --remotes", "git branch --show-current", "git branch --contains"),
|
|
61
|
+
...family("git tag --list", "git tag -l", "git tag -n"),
|
|
62
|
+
...family("git remote -v", "git remote --verbose", "git remote show", "git remote get-url"),
|
|
63
|
+
...family("git reflog show"),
|
|
64
|
+
...family("git stash list", "git describe", "git worktree list"),
|
|
65
|
+
...family("git config --get", "git config --get-regexp")
|
|
66
|
+
];
|
|
67
|
+
var GIT_MUTATING_COMMANDS = [
|
|
68
|
+
...family("git add", "git rm --cached"),
|
|
69
|
+
...family("git commit", "git merge --ff-only", "git merge --no-ff"),
|
|
70
|
+
...family("git push", "git fetch", "git pull --ff-only"),
|
|
71
|
+
...family("git stash", "git cherry-pick", "git revert"),
|
|
72
|
+
...family("git switch", "git checkout -b", "git worktree add"),
|
|
73
|
+
...exactOnly("git branch *", "git tag *", "git remote *", "git symbolic-ref *", "git reflog *")
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// src/permissions-rpc.ts
|
|
12
77
|
var GvozdPermissions = Rpc.define({
|
|
13
78
|
id: "gvozd-permissions",
|
|
14
79
|
events: {},
|
|
@@ -358,9 +423,9 @@ function useSessionInsights(sessionID) {
|
|
|
358
423
|
});
|
|
359
424
|
const [resource] = createResource(() => {
|
|
360
425
|
const id = sessionID();
|
|
361
|
-
version();
|
|
362
|
-
|
|
363
|
-
|
|
426
|
+
return { id, revision: version() };
|
|
427
|
+
}, async (key) => {
|
|
428
|
+
const id = key.id;
|
|
364
429
|
if (!id)
|
|
365
430
|
return EMPTY_INSIGHTS;
|
|
366
431
|
const [messages, pending] = await Promise.allSettled([
|
|
@@ -429,38 +494,114 @@ async function evaluatePermissions(agent, checks) {
|
|
|
429
494
|
function splitCommandPipeline(input) {
|
|
430
495
|
const segments = [];
|
|
431
496
|
let current = "";
|
|
432
|
-
let quote;
|
|
433
497
|
let escaped = false;
|
|
498
|
+
const contexts = [{ parenDepth: 0 }];
|
|
499
|
+
let heredoc;
|
|
434
500
|
const flush = () => {
|
|
435
501
|
const trimmed = current.trim();
|
|
436
502
|
if (trimmed)
|
|
437
503
|
segments.push(trimmed);
|
|
438
504
|
current = "";
|
|
439
505
|
};
|
|
506
|
+
const at = (index) => input[index] ?? "";
|
|
440
507
|
for (let index = 0;index < input.length; index++) {
|
|
441
508
|
const character = input[index];
|
|
509
|
+
if (heredoc) {
|
|
510
|
+
if (character === `
|
|
511
|
+
`) {
|
|
512
|
+
const line = current.slice(current.lastIndexOf(`
|
|
513
|
+
`) + 1).replace(/\r$/, "");
|
|
514
|
+
const candidate = heredoc.stripTabs ? line.replace(/^\t+/, "") : line;
|
|
515
|
+
if (candidate === heredoc.marker) {
|
|
516
|
+
heredoc = undefined;
|
|
517
|
+
current += character;
|
|
518
|
+
flush();
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
current += character;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const context = contexts[contexts.length - 1];
|
|
442
526
|
if (escaped) {
|
|
443
527
|
current += character;
|
|
444
528
|
escaped = false;
|
|
445
529
|
continue;
|
|
446
530
|
}
|
|
447
|
-
if (character === "\\" && quote !== "'") {
|
|
531
|
+
if (character === "\\" && context.quote !== "'") {
|
|
448
532
|
current += character;
|
|
449
533
|
escaped = true;
|
|
450
534
|
continue;
|
|
451
535
|
}
|
|
452
536
|
if (character === '"' || character === "'") {
|
|
453
|
-
if (quote === character)
|
|
454
|
-
quote = undefined;
|
|
455
|
-
else if (!quote)
|
|
456
|
-
quote = character;
|
|
537
|
+
if (context.quote === character)
|
|
538
|
+
context.quote = undefined;
|
|
539
|
+
else if (!context.quote)
|
|
540
|
+
context.quote = character;
|
|
541
|
+
current += character;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (context.quote === "'") {
|
|
545
|
+
current += character;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (character === "$" && at(index + 1) === "(") {
|
|
549
|
+
contexts.push({ closer: ")", parenDepth: 0 });
|
|
550
|
+
current += "$(";
|
|
551
|
+
index++;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (character === "`") {
|
|
555
|
+
if (context.closer === "`")
|
|
556
|
+
contexts.pop();
|
|
557
|
+
else
|
|
558
|
+
contexts.push({ closer: "`", parenDepth: 0 });
|
|
559
|
+
current += character;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (context.closer === ")" && character === "(") {
|
|
563
|
+
context.parenDepth++;
|
|
564
|
+
current += character;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (context.closer === ")" && character === ")") {
|
|
568
|
+
if (context.parenDepth > 0)
|
|
569
|
+
context.parenDepth--;
|
|
570
|
+
else
|
|
571
|
+
contexts.pop();
|
|
457
572
|
current += character;
|
|
458
573
|
continue;
|
|
459
574
|
}
|
|
460
|
-
if (quote) {
|
|
575
|
+
if (contexts.length > 1 || context.quote) {
|
|
461
576
|
current += character;
|
|
462
577
|
continue;
|
|
463
578
|
}
|
|
579
|
+
if (character === "<" && at(index - 1) !== "<" && at(index + 1) === "<" && at(index + 2) !== "<") {
|
|
580
|
+
let cursor = index + 2;
|
|
581
|
+
const stripTabs = input[cursor] === "-";
|
|
582
|
+
if (stripTabs)
|
|
583
|
+
cursor++;
|
|
584
|
+
let marker = "";
|
|
585
|
+
while (cursor < input.length && /[ \t]/.test(input[cursor]))
|
|
586
|
+
cursor++;
|
|
587
|
+
if (input[cursor] === '"' || input[cursor] === "'") {
|
|
588
|
+
const closeQuote = input[cursor];
|
|
589
|
+
cursor++;
|
|
590
|
+
while (cursor < input.length && input[cursor] !== closeQuote)
|
|
591
|
+
marker += input[cursor++];
|
|
592
|
+
if (input[cursor] === closeQuote)
|
|
593
|
+
cursor++;
|
|
594
|
+
} else {
|
|
595
|
+
while (cursor < input.length && !/[\s;|&<>()]/.test(input[cursor]))
|
|
596
|
+
marker += input[cursor++];
|
|
597
|
+
}
|
|
598
|
+
if (marker) {
|
|
599
|
+
heredoc = { marker, stripTabs };
|
|
600
|
+
current += input.slice(index, cursor);
|
|
601
|
+
index = cursor - 1;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
464
605
|
if (character === ";" || character === `
|
|
465
606
|
`) {
|
|
466
607
|
flush();
|
|
@@ -624,7 +765,7 @@ function SessionInsightsSlot(props) {
|
|
|
624
765
|
function DryRunPanel() {
|
|
625
766
|
const context = usePlugin2();
|
|
626
767
|
const [input, setInput] = createSignal2("");
|
|
627
|
-
const [agent
|
|
768
|
+
const [agent] = createSignal2("master");
|
|
628
769
|
const [rows, setRows] = createSignal2([]);
|
|
629
770
|
const [busy, setBusy] = createSignal2(false);
|
|
630
771
|
const run = async () => {
|
|
@@ -861,6 +1002,29 @@ var MODE_HINTS = {
|
|
|
861
1002
|
trusted: "allow all shell and edits; destructive git still denied",
|
|
862
1003
|
strict: "ask for every shell command and edit"
|
|
863
1004
|
};
|
|
1005
|
+
function KeymapCommands() {
|
|
1006
|
+
const context = usePlugin2();
|
|
1007
|
+
context.keymap.layer(() => ({
|
|
1008
|
+
mode: "global",
|
|
1009
|
+
commands: GVOZD_COMMANDS.map((command) => ({
|
|
1010
|
+
id: command.id,
|
|
1011
|
+
title: command.title,
|
|
1012
|
+
group: "Gvozd",
|
|
1013
|
+
palette: true,
|
|
1014
|
+
slash: { name: command.slash },
|
|
1015
|
+
run: () => {
|
|
1016
|
+
context.ui.panel.open(command.panel, { presentation: "fullscreen" });
|
|
1017
|
+
}
|
|
1018
|
+
}))
|
|
1019
|
+
}));
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
var GVOZD_COMMANDS = [
|
|
1023
|
+
{ id: "gvozd.insights", title: "Gvozd session insights", panel: "gvozd.insights", slash: "gvozd" },
|
|
1024
|
+
{ id: "gvozd.dryrun", title: "Gvozd permission dry-run", panel: "gvozd.dryrun", slash: "gvozd-dryrun" },
|
|
1025
|
+
{ id: "gvozd.leases", title: "Gvozd file leases", panel: "gvozd.leases", slash: "gvozd-leases" },
|
|
1026
|
+
{ id: "gvozd.mode", title: "Gvozd permission mode", panel: "gvozd.mode", slash: "gvozd-mode" }
|
|
1027
|
+
];
|
|
864
1028
|
var tui_default = Plugin.define({
|
|
865
1029
|
id: "agent-gvozd",
|
|
866
1030
|
setup(context) {
|
|
@@ -876,6 +1040,10 @@ var tui_default = Plugin.define({
|
|
|
876
1040
|
sessionID
|
|
877
1041
|
}, undefined, false, undefined, this) : null
|
|
878
1042
|
});
|
|
1043
|
+
const unregisterKeymapHost = context.ui.slot({
|
|
1044
|
+
append: "app",
|
|
1045
|
+
render: () => /* @__PURE__ */ jsxDEV(KeymapCommands, {}, undefined, false, undefined, this)
|
|
1046
|
+
});
|
|
879
1047
|
const unregisterPanelSlot = context.ui.slot({
|
|
880
1048
|
append: "session.panel",
|
|
881
1049
|
render: (panel) => /* @__PURE__ */ jsxDEV(Fragment, {
|
|
@@ -899,55 +1067,11 @@ var tui_default = Plugin.define({
|
|
|
899
1067
|
]
|
|
900
1068
|
}, undefined, true, undefined, this)
|
|
901
1069
|
});
|
|
902
|
-
const unregisterKeymap = context.keymap.layer(() => ({
|
|
903
|
-
mode: "global",
|
|
904
|
-
commands: [
|
|
905
|
-
{
|
|
906
|
-
id: "gvozd.insights",
|
|
907
|
-
title: "Gvozd session insights",
|
|
908
|
-
group: "Gvozd",
|
|
909
|
-
palette: true,
|
|
910
|
-
slash: { name: "gvozd" },
|
|
911
|
-
run: () => {
|
|
912
|
-
context.ui.panel.open("gvozd.insights", { presentation: "fullscreen" });
|
|
913
|
-
}
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
id: "gvozd.dryrun",
|
|
917
|
-
title: "Gvozd permission dry-run",
|
|
918
|
-
group: "Gvozd",
|
|
919
|
-
palette: true,
|
|
920
|
-
slash: { name: "gvozd-dryrun" },
|
|
921
|
-
run: () => {
|
|
922
|
-
context.ui.panel.open("gvozd.dryrun", { presentation: "fullscreen" });
|
|
923
|
-
}
|
|
924
|
-
},
|
|
925
|
-
{
|
|
926
|
-
id: "gvozd.leases",
|
|
927
|
-
title: "Gvozd file leases",
|
|
928
|
-
group: "Gvozd",
|
|
929
|
-
palette: true,
|
|
930
|
-
slash: { name: "gvozd-leases" },
|
|
931
|
-
run: () => {
|
|
932
|
-
context.ui.panel.open("gvozd.leases", { presentation: "fullscreen" });
|
|
933
|
-
}
|
|
934
|
-
},
|
|
935
|
-
{
|
|
936
|
-
id: "gvozd.mode",
|
|
937
|
-
title: "Gvozd permission mode",
|
|
938
|
-
group: "Gvozd",
|
|
939
|
-
palette: true,
|
|
940
|
-
slash: { name: "gvozd-mode" },
|
|
941
|
-
run: () => {
|
|
942
|
-
context.ui.panel.open("gvozd.mode", { presentation: "fullscreen" });
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
]
|
|
946
|
-
}));
|
|
947
1070
|
return () => {
|
|
948
1071
|
unregisterSidebar();
|
|
949
1072
|
unregisterFooter();
|
|
950
1073
|
unregisterPanelSlot();
|
|
1074
|
+
unregisterKeymapHost();
|
|
951
1075
|
};
|
|
952
1076
|
}
|
|
953
1077
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nail00749/agent-gvozd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A globally configured, permission-aware agent team for OpenCode V2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"verify:sync": "bun run ./scripts/verify-sync.ts",
|
|
49
49
|
"verify:package": "bun run ./scripts/verify-package.ts",
|
|
50
50
|
"verify:live": "bun run ./scripts/live-opencode-compat.ts",
|
|
51
|
-
"prepublishOnly": "bun run test:unit && bun run
|
|
51
|
+
"prepublishOnly": "bun run test:unit && bun run typecheck && bun run build && bun run verify:sync && bun run verify:package && bun run test:e2e",
|
|
52
52
|
"test:unit": "bun test --path-ignore-patterns \"**/package-smoke.test.ts\"",
|
|
53
53
|
"test:e2e": "bun test src/cli/package-smoke.test.ts",
|
|
54
|
-
"publish:fast": "bun run typecheck && bun run build && bun run verify:package && npm publish --ignore-scripts"
|
|
54
|
+
"publish:fast": "bun run test:unit && bun run typecheck && bun run build && bun run verify:package && npm publish --ignore-scripts"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@clack/prompts": "1.8.0",
|