@craftsamo/agent-github 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.antigravity-plugin/plugin.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.mcp.json +8 -0
- package/README.md +62 -25
- package/dist/cli.js +130 -21
- package/dist/mcp.js +88 -39
- package/dist/opencode.js +143 -40
- package/mcp.json +1 -1
- package/mcp_config.json +1 -1
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/plugin.yaml +1 -1
- package/src/hermes/agent_github_hermes/approval.py +18 -14
package/dist/opencode.js
CHANGED
|
@@ -4,6 +4,13 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/entry/package-root.ts
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
function packageRoot(bundleUrl) {
|
|
11
|
+
return dirname(dirname(fileURLToPath(bundleUrl)));
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
// node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/classic/external.js
|
|
8
15
|
var external_exports = {};
|
|
9
16
|
__export(external_exports, {
|
|
@@ -20204,13 +20211,13 @@ async function ensureLabel(repo, name, ctx) {
|
|
|
20204
20211
|
function isTypeFailure(error62) {
|
|
20205
20212
|
return isToolError(error62) && TYPE_FAILURE_CODES.includes(error62.code) && TYPE_MENTION.test(error62.message);
|
|
20206
20213
|
}
|
|
20207
|
-
async function withKindFallback(kind, placement,
|
|
20214
|
+
async function withKindFallback(kind, placement, attempt2) {
|
|
20208
20215
|
try {
|
|
20209
|
-
return { placement, result: await
|
|
20216
|
+
return { placement, result: await attempt2(placement) };
|
|
20210
20217
|
} catch (error62) {
|
|
20211
20218
|
if (placement.via !== "type" || !isTypeFailure(error62)) throw error62;
|
|
20212
20219
|
const fallback = labelKind(kind);
|
|
20213
|
-
return { placement: fallback, result: await
|
|
20220
|
+
return { placement: fallback, result: await attempt2(fallback) };
|
|
20214
20221
|
}
|
|
20215
20222
|
}
|
|
20216
20223
|
async function kindFlags(placement, repo, ctx) {
|
|
@@ -20512,6 +20519,8 @@ async function readSubIssues(parent, ctx) {
|
|
|
20512
20519
|
}
|
|
20513
20520
|
|
|
20514
20521
|
// src/core/refs.ts
|
|
20522
|
+
import { realpath } from "node:fs/promises";
|
|
20523
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
20515
20524
|
var DEFAULT_HOST = "github.com";
|
|
20516
20525
|
var SSH_ALIAS_HOST = "ssh.github.com";
|
|
20517
20526
|
var GIT_CONFIG_ENV = ["GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM", "GIT_CONFIG_NOSYSTEM"];
|
|
@@ -20595,11 +20604,25 @@ function gitEnv() {
|
|
|
20595
20604
|
}
|
|
20596
20605
|
return { ...env, GIT_TERMINAL_PROMPT: "0" };
|
|
20597
20606
|
}
|
|
20598
|
-
async function
|
|
20607
|
+
async function isWithin(dir, root) {
|
|
20608
|
+
try {
|
|
20609
|
+
const path = relative(await realpath(root), await realpath(dir));
|
|
20610
|
+
return path === "" || !(path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path));
|
|
20611
|
+
} catch {
|
|
20612
|
+
return false;
|
|
20613
|
+
}
|
|
20614
|
+
}
|
|
20615
|
+
async function repoFromOrigin(cwd, signal, ownDir) {
|
|
20599
20616
|
const env = gitEnv();
|
|
20600
|
-
const unresolved = (why) => invalid(
|
|
20601
|
-
`Cannot tell which repository to use: ${why}. Run the call inside a clone of a GitHub repository, or pass \`repo\` as owner/name
|
|
20617
|
+
const unresolved = (why, advice = "") => invalid(
|
|
20618
|
+
`Cannot tell which repository to use: ${why}. Run the call inside a clone of a GitHub repository, or pass \`repo\` as owner/name.${advice}`
|
|
20602
20619
|
);
|
|
20620
|
+
if (ownDir !== void 0 && await isWithin(cwd, ownDir)) {
|
|
20621
|
+
throw unresolved(
|
|
20622
|
+
`${cwd} is where these tools are installed, not a project`,
|
|
20623
|
+
" An issue can be given as `owner/repo#N`."
|
|
20624
|
+
);
|
|
20625
|
+
}
|
|
20603
20626
|
let result;
|
|
20604
20627
|
try {
|
|
20605
20628
|
result = await exec(["git", "remote", "get-url", "origin"], { cwd, env, signal });
|
|
@@ -20614,7 +20637,10 @@ async function repoFromOrigin(cwd, signal) {
|
|
|
20614
20637
|
return repo;
|
|
20615
20638
|
}
|
|
20616
20639
|
async function resolveRepo(repo, ctx) {
|
|
20617
|
-
return repo === void 0 || repo.trim() === "" ? repoFromOrigin(ctx.cwd, ctx.signal) : parseRepo(repo);
|
|
20640
|
+
return repo === void 0 || repo.trim() === "" ? repoFromOrigin(ctx.cwd, ctx.signal, ctx.ownDir) : parseRepo(repo);
|
|
20641
|
+
}
|
|
20642
|
+
function isBareNumber(ref) {
|
|
20643
|
+
return BARE_NUMBER_PATTERN.test(ref.trim());
|
|
20618
20644
|
}
|
|
20619
20645
|
function parseNumber(digits, ref) {
|
|
20620
20646
|
const number4 = Number.parseInt(digits, 10);
|
|
@@ -20665,10 +20691,9 @@ async function parseNumberRef(ref, repoArg, ctx, options = {}) {
|
|
|
20665
20691
|
}
|
|
20666
20692
|
|
|
20667
20693
|
// src/core/github/issue-refs.ts
|
|
20668
|
-
var BARE_NUMBER = /^#?\d+$/;
|
|
20669
20694
|
async function resolveRelatedIssue(ref, target, ctx) {
|
|
20670
20695
|
const text = ref.trim();
|
|
20671
|
-
return parseNumberRef(text,
|
|
20696
|
+
return parseNumberRef(text, isBareNumber(text) ? target.full : void 0, ctx, { accept: "issue" });
|
|
20672
20697
|
}
|
|
20673
20698
|
function issueLabel(issue2) {
|
|
20674
20699
|
return `${issue2.repo.full}#${issue2.number}`;
|
|
@@ -20696,6 +20721,10 @@ function defineTool(spec) {
|
|
|
20696
20721
|
if (!(spec.output instanceof external_exports.ZodObject)) {
|
|
20697
20722
|
throw new Error(`Tool "${spec.id}" must have an object output schema, since a result is an object.`);
|
|
20698
20723
|
}
|
|
20724
|
+
const unknownTarget = Object.keys(spec.targets ?? {}).find((name) => !Object.hasOwn(spec.input.shape, name));
|
|
20725
|
+
if (unknownTarget !== void 0) {
|
|
20726
|
+
throw new Error(`Tool "${spec.id}" declares the target "${unknownTarget}", which is not one of its arguments.`);
|
|
20727
|
+
}
|
|
20699
20728
|
return spec;
|
|
20700
20729
|
}
|
|
20701
20730
|
function knownIds(setting, ids, known) {
|
|
@@ -20788,6 +20817,7 @@ var StepErrors = class {
|
|
|
20788
20817
|
// src/core/issues/write-support.ts
|
|
20789
20818
|
var REPO_DESCRIPTION = "owner/name; defaults to the working tree's repository (its origin). Used for a bare number.";
|
|
20790
20819
|
var ISSUE_DESCRIPTION = "The issue, as 12, #12, owner/repo#12 or its URL.";
|
|
20820
|
+
var ISSUE_TARGETS = { issue: "issue", repo: "repo" };
|
|
20791
20821
|
var LIST_SYNTAX = /[,"\r\n]/;
|
|
20792
20822
|
var URL_LINE = /^https?:\/\/\S+$/;
|
|
20793
20823
|
function invalid2(message) {
|
|
@@ -20857,6 +20887,7 @@ var issueClose = defineTool({
|
|
|
20857
20887
|
].join(" "),
|
|
20858
20888
|
toolset: "issues",
|
|
20859
20889
|
class: "overwrite",
|
|
20890
|
+
targets: ISSUE_TARGETS,
|
|
20860
20891
|
input: input2,
|
|
20861
20892
|
output: output2,
|
|
20862
20893
|
async execute(args, ctx) {
|
|
@@ -20913,6 +20944,7 @@ var issueComment = defineTool({
|
|
|
20913
20944
|
].join(" "),
|
|
20914
20945
|
toolset: "issues",
|
|
20915
20946
|
class: "add",
|
|
20947
|
+
targets: ISSUE_TARGETS,
|
|
20916
20948
|
input: input3,
|
|
20917
20949
|
output: output3,
|
|
20918
20950
|
async execute(args, ctx) {
|
|
@@ -21030,6 +21062,7 @@ var issueCreate = defineTool({
|
|
|
21030
21062
|
].join(" "),
|
|
21031
21063
|
toolset: "issues",
|
|
21032
21064
|
class: "add",
|
|
21065
|
+
targets: { repo: "repo", parent: "issue", blocked_by: "issues" },
|
|
21033
21066
|
input: input4,
|
|
21034
21067
|
output: output4,
|
|
21035
21068
|
async execute(args, ctx) {
|
|
@@ -21177,6 +21210,7 @@ var issueDevelop = defineTool({
|
|
|
21177
21210
|
].join(" "),
|
|
21178
21211
|
toolset: "issues",
|
|
21179
21212
|
class: "idempotent",
|
|
21213
|
+
targets: { ...ISSUE_TARGETS, branch_repo: "repo" },
|
|
21180
21214
|
input: input5,
|
|
21181
21215
|
output: output5,
|
|
21182
21216
|
async execute(args, ctx) {
|
|
@@ -21324,6 +21358,7 @@ var issueEdit = defineTool({
|
|
|
21324
21358
|
].join(" "),
|
|
21325
21359
|
toolset: "issues",
|
|
21326
21360
|
class: "overwrite",
|
|
21361
|
+
targets: ISSUE_TARGETS,
|
|
21327
21362
|
input: input6,
|
|
21328
21363
|
output: output6,
|
|
21329
21364
|
async execute(args, ctx) {
|
|
@@ -21348,14 +21383,14 @@ var issueEdit = defineTool({
|
|
|
21348
21383
|
if (change.removeMilestone) flags.push("--remove-milestone");
|
|
21349
21384
|
const named = new Set([...change.addLabels, ...change.removeLabels].map((label) => label.toLowerCase()));
|
|
21350
21385
|
const swapped = (where) => where.via === "label" ? otherKindLabels(current?.labels ?? [], where.name).filter((label) => !named.has(label.toLowerCase())).flatMap((label) => ["--remove-label", label]) : [];
|
|
21351
|
-
const
|
|
21386
|
+
const attempt2 = async (where) => editIssue(
|
|
21352
21387
|
target,
|
|
21353
21388
|
issue2.number,
|
|
21354
21389
|
where ? [...flags, ...await kindFlags(where, target, ctx), ...swapped(where)] : flags,
|
|
21355
21390
|
ctx,
|
|
21356
21391
|
body
|
|
21357
21392
|
);
|
|
21358
|
-
const applied = kind ? await withKindFallback(kind.name, kind.placement,
|
|
21393
|
+
const applied = kind ? await withKindFallback(kind.name, kind.placement, attempt2) : { placement: void 0, result: await attempt2() };
|
|
21359
21394
|
return {
|
|
21360
21395
|
repo: target.full,
|
|
21361
21396
|
issue: issue2.number,
|
|
@@ -21682,6 +21717,16 @@ var issueRelate = defineTool({
|
|
|
21682
21717
|
].join(" "),
|
|
21683
21718
|
toolset: "issues",
|
|
21684
21719
|
class: "idempotent",
|
|
21720
|
+
targets: {
|
|
21721
|
+
...ISSUE_TARGETS,
|
|
21722
|
+
parent: "issue",
|
|
21723
|
+
add_sub_issues: "issues",
|
|
21724
|
+
remove_sub_issues: "issues",
|
|
21725
|
+
add_blocked_by: "issues",
|
|
21726
|
+
remove_blocked_by: "issues",
|
|
21727
|
+
add_blocking: "issues",
|
|
21728
|
+
remove_blocking: "issues"
|
|
21729
|
+
},
|
|
21685
21730
|
input: input8,
|
|
21686
21731
|
output: output8,
|
|
21687
21732
|
async execute(args, ctx) {
|
|
@@ -21780,6 +21825,7 @@ var issueReopen = defineTool({
|
|
|
21780
21825
|
].join(" "),
|
|
21781
21826
|
toolset: "issues",
|
|
21782
21827
|
class: "overwrite",
|
|
21828
|
+
targets: ISSUE_TARGETS,
|
|
21783
21829
|
input: input9,
|
|
21784
21830
|
output: output9,
|
|
21785
21831
|
async execute(args, ctx) {
|
|
@@ -21810,18 +21856,71 @@ var ALL_TOOLS = [
|
|
|
21810
21856
|
issueDevelop
|
|
21811
21857
|
];
|
|
21812
21858
|
|
|
21813
|
-
// src/
|
|
21814
|
-
var
|
|
21815
|
-
|
|
21816
|
-
|
|
21817
|
-
if (repo !== void 0 && typeof repo !== "string") return ANY_RESOURCE;
|
|
21859
|
+
// src/core/targets.ts
|
|
21860
|
+
var ANY_TARGET = "*";
|
|
21861
|
+
var ISSUE_ARGUMENT = "issue";
|
|
21862
|
+
async function attempt(fn) {
|
|
21818
21863
|
try {
|
|
21819
|
-
return
|
|
21864
|
+
return await fn();
|
|
21820
21865
|
} catch (error62) {
|
|
21821
21866
|
if (isAbortError(error62)) throw error62;
|
|
21822
|
-
return
|
|
21867
|
+
return void 0;
|
|
21823
21868
|
}
|
|
21824
21869
|
}
|
|
21870
|
+
function isBlank(value) {
|
|
21871
|
+
return typeof value === "string" && value.trim() === "";
|
|
21872
|
+
}
|
|
21873
|
+
function ownRepository(spec, args, ctx) {
|
|
21874
|
+
const repo = args.repo;
|
|
21875
|
+
const issue2 = args[ISSUE_ARGUMENT];
|
|
21876
|
+
let pending;
|
|
21877
|
+
async function resolve2() {
|
|
21878
|
+
if (spec.targets?.[ISSUE_ARGUMENT] === "issue" && typeof issue2 === "string" && !isBareNumber(issue2)) {
|
|
21879
|
+
const named = await attempt(() => parseNumberRef(issue2, void 0, ctx, { accept: "issue" }));
|
|
21880
|
+
if (named) return named.repo;
|
|
21881
|
+
}
|
|
21882
|
+
if (repo !== void 0 && typeof repo !== "string") return void 0;
|
|
21883
|
+
return attempt(() => resolveRepo(repo, ctx));
|
|
21884
|
+
}
|
|
21885
|
+
return () => pending ??= resolve2();
|
|
21886
|
+
}
|
|
21887
|
+
async function issueRepository(value, own2, ctx) {
|
|
21888
|
+
if (typeof value !== "string") return void 0;
|
|
21889
|
+
const bare = isBareNumber(value);
|
|
21890
|
+
const repoArg = bare ? (await own2())?.full : void 0;
|
|
21891
|
+
if (bare && repoArg === void 0) return void 0;
|
|
21892
|
+
return attempt(async () => (await parseNumberRef(value, repoArg, ctx, { accept: "issue" })).repo);
|
|
21893
|
+
}
|
|
21894
|
+
async function targetRepository(kind, value, own2, ctx) {
|
|
21895
|
+
if (kind !== "repo") return issueRepository(value, own2, ctx);
|
|
21896
|
+
return typeof value === "string" ? attempt(() => parseRepo(value)) : void 0;
|
|
21897
|
+
}
|
|
21898
|
+
async function writeTargets(spec, args, ctx) {
|
|
21899
|
+
const given3 = typeof args === "object" && args !== null ? args : {};
|
|
21900
|
+
const own2 = ownRepository(spec, given3, ctx);
|
|
21901
|
+
const found = /* @__PURE__ */ new Set();
|
|
21902
|
+
const add = (repo) => found.add(repo ? repo.full.toLowerCase() : ANY_TARGET);
|
|
21903
|
+
add(await own2());
|
|
21904
|
+
for (const [name, kind] of Object.entries(spec.targets ?? {})) {
|
|
21905
|
+
const value = given3[name];
|
|
21906
|
+
if (value === void 0) continue;
|
|
21907
|
+
if (kind === "issues" && !Array.isArray(value)) {
|
|
21908
|
+
add(void 0);
|
|
21909
|
+
continue;
|
|
21910
|
+
}
|
|
21911
|
+
for (const item of kind === "issues" ? value : [value]) {
|
|
21912
|
+
if (!isBlank(item)) add(await targetRepository(kind, item, own2, ctx));
|
|
21913
|
+
}
|
|
21914
|
+
}
|
|
21915
|
+
const repos = [...found].filter((repo) => repo !== ANY_TARGET).sort();
|
|
21916
|
+
return found.has(ANY_TARGET) ? [...repos, ANY_TARGET] : repos;
|
|
21917
|
+
}
|
|
21918
|
+
|
|
21919
|
+
// src/entry/permission.ts
|
|
21920
|
+
var ANY_RESOURCE = ANY_TARGET;
|
|
21921
|
+
function permissionResources(spec, args, ctx) {
|
|
21922
|
+
return writeTargets(spec, args, ctx);
|
|
21923
|
+
}
|
|
21825
21924
|
|
|
21826
21925
|
// src/entry/settings.ts
|
|
21827
21926
|
var ALL_TOOLSETS = "all";
|
|
@@ -21910,11 +22009,14 @@ function settingsFromOptions(options, env) {
|
|
|
21910
22009
|
}
|
|
21911
22010
|
|
|
21912
22011
|
// src/opencode/shared.ts
|
|
21913
|
-
function permissionMessage(spec,
|
|
21914
|
-
return
|
|
22012
|
+
function permissionMessage(spec, resources) {
|
|
22013
|
+
return resources.length === 1 && resources[0] === ANY_RESOURCE ? spec.title : `${spec.title} on ${resources.join(", ")}`;
|
|
22014
|
+
}
|
|
22015
|
+
function persistableResources(resources) {
|
|
22016
|
+
return resources.filter((resource) => resource !== ANY_RESOURCE);
|
|
21915
22017
|
}
|
|
21916
|
-
function permissionMetadata(spec, args,
|
|
21917
|
-
return { arguments: args, class: spec.class, message: permissionMessage(spec,
|
|
22018
|
+
function permissionMetadata(spec, args, resources) {
|
|
22019
|
+
return { arguments: args, class: spec.class, message: permissionMessage(spec, resources) };
|
|
21918
22020
|
}
|
|
21919
22021
|
function needsPermission(spec) {
|
|
21920
22022
|
return spec.class !== "read";
|
|
@@ -21952,19 +22054,19 @@ ${lines.join("\n")}` : "";
|
|
|
21952
22054
|
function workingDir(context) {
|
|
21953
22055
|
return context.worktree && context.worktree !== "/" ? context.worktree : context.directory;
|
|
21954
22056
|
}
|
|
21955
|
-
function v1Tool(spec) {
|
|
22057
|
+
function v1Tool(spec, ownDir) {
|
|
21956
22058
|
return {
|
|
21957
22059
|
description: `${spec.description}${describeArguments(spec)}`,
|
|
21958
22060
|
args: spec.input.shape,
|
|
21959
22061
|
async execute(args, context) {
|
|
21960
|
-
const ctx = { cwd: workingDir(context), signal: context.abort };
|
|
22062
|
+
const ctx = { cwd: workingDir(context), ownDir, signal: context.abort };
|
|
21961
22063
|
if (needsPermission(spec)) {
|
|
21962
|
-
const
|
|
22064
|
+
const resources = await permissionResources(spec, args, ctx);
|
|
21963
22065
|
await context.ask({
|
|
21964
22066
|
permission: spec.id,
|
|
21965
|
-
patterns:
|
|
21966
|
-
always:
|
|
21967
|
-
metadata: permissionMetadata(spec, args,
|
|
22067
|
+
patterns: resources,
|
|
22068
|
+
always: persistableResources(resources),
|
|
22069
|
+
metadata: permissionMetadata(spec, args, resources)
|
|
21968
22070
|
});
|
|
21969
22071
|
}
|
|
21970
22072
|
const result = await runTool(spec, args, ctx);
|
|
@@ -21975,7 +22077,7 @@ function v1Tool(spec) {
|
|
|
21975
22077
|
function createV1Server(specs, deps = {}) {
|
|
21976
22078
|
return async function server(_input, options) {
|
|
21977
22079
|
const tool = {};
|
|
21978
|
-
for (const spec of selectedTools(specs, options, deps.env)) tool[spec.id] = v1Tool(spec);
|
|
22080
|
+
for (const spec of selectedTools(specs, options, deps.env)) tool[spec.id] = v1Tool(spec, deps.ownDir);
|
|
21979
22081
|
return { tool };
|
|
21980
22082
|
};
|
|
21981
22083
|
}
|
|
@@ -21983,7 +22085,7 @@ function createV1Server(specs, deps = {}) {
|
|
|
21983
22085
|
// src/opencode/skills.ts
|
|
21984
22086
|
import { readFile, readdir } from "node:fs/promises";
|
|
21985
22087
|
import { join, resolve } from "node:path";
|
|
21986
|
-
import { fileURLToPath } from "node:url";
|
|
22088
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21987
22089
|
var SKILL_FILE = "SKILL.md";
|
|
21988
22090
|
var FRONTMATTER = /^---[ \t]*\r?\n([\s\S]*?\r?\n)?---[ \t]*(?:\r?\n|$)/;
|
|
21989
22091
|
var KEY_LINE = /^(name|description)[ \t]*:[ \t]*(.*?)[ \t]*$/;
|
|
@@ -22043,7 +22145,7 @@ async function readSkillFile(path) {
|
|
|
22043
22145
|
}
|
|
22044
22146
|
}
|
|
22045
22147
|
async function loadSkills(dir) {
|
|
22046
|
-
const root = resolve(typeof dir === "string" ? dir :
|
|
22148
|
+
const root = resolve(typeof dir === "string" ? dir : fileURLToPath2(dir));
|
|
22047
22149
|
let entries;
|
|
22048
22150
|
try {
|
|
22049
22151
|
entries = (await readdir(root, { withFileTypes: true })).filter((entry) => !entry.isFile()).map((entry) => entry.name);
|
|
@@ -22070,7 +22172,7 @@ async function sessionDirectory(ctx, sessionID) {
|
|
|
22070
22172
|
return void 0;
|
|
22071
22173
|
}
|
|
22072
22174
|
}
|
|
22073
|
-
function v2Tool(ctx, spec) {
|
|
22175
|
+
function v2Tool(ctx, spec, ownDir) {
|
|
22074
22176
|
return {
|
|
22075
22177
|
name: spec.id,
|
|
22076
22178
|
description: spec.description,
|
|
@@ -22084,23 +22186,23 @@ function v2Tool(ctx, spec) {
|
|
|
22084
22186
|
`invalid_argument: The session's directory could not be read, so ${spec.id} cannot tell which repository it changes; pass \`repo\` as owner/name or retry.`
|
|
22085
22187
|
);
|
|
22086
22188
|
}
|
|
22087
|
-
const
|
|
22189
|
+
const resources = await permissionResources(spec, input10, { cwd: directory, ownDir, signal });
|
|
22088
22190
|
await ctx.permission.assert({
|
|
22089
22191
|
action: spec.id,
|
|
22090
|
-
resources
|
|
22091
|
-
save:
|
|
22092
|
-
metadata: permissionMetadata(spec, input10,
|
|
22192
|
+
resources,
|
|
22193
|
+
save: persistableResources(resources),
|
|
22194
|
+
metadata: permissionMetadata(spec, input10, resources),
|
|
22093
22195
|
sessionID
|
|
22094
22196
|
});
|
|
22095
22197
|
}
|
|
22096
|
-
const result = await runTool(spec, input10, { cwd: directory ?? process.cwd(), signal });
|
|
22198
|
+
const result = await runTool(spec, input10, { cwd: directory ?? process.cwd(), ownDir, signal });
|
|
22097
22199
|
return { content: JSON.stringify(result), output: result };
|
|
22098
22200
|
}
|
|
22099
22201
|
};
|
|
22100
22202
|
}
|
|
22101
22203
|
function createV2Setup(specs, deps = {}) {
|
|
22102
22204
|
return async function setup(ctx) {
|
|
22103
|
-
const tools = selectedTools(specs, ctx.options, deps.env).map((spec) => v2Tool(ctx, spec));
|
|
22205
|
+
const tools = selectedTools(specs, ctx.options, deps.env).map((spec) => v2Tool(ctx, spec, deps.ownDir));
|
|
22104
22206
|
const skills = deps.skillsDir === void 0 ? [] : await loadSkills(deps.skillsDir);
|
|
22105
22207
|
if (tools.length > 0) {
|
|
22106
22208
|
await ctx.tool.transform((editor) => {
|
|
@@ -22118,10 +22220,11 @@ function createV2Setup(specs, deps = {}) {
|
|
|
22118
22220
|
// src/opencode/index.ts
|
|
22119
22221
|
var PLUGIN_ID = "agent-github";
|
|
22120
22222
|
var SKILLS_DIR = new URL("../skills/", import.meta.url);
|
|
22223
|
+
var OWN_DIR = packageRoot(import.meta.url);
|
|
22121
22224
|
var plugin = {
|
|
22122
22225
|
id: PLUGIN_ID,
|
|
22123
|
-
setup: createV2Setup(ALL_TOOLS, { skillsDir: SKILLS_DIR }),
|
|
22124
|
-
server: createV1Server(ALL_TOOLS)
|
|
22226
|
+
setup: createV2Setup(ALL_TOOLS, { skillsDir: SKILLS_DIR, ownDir: OWN_DIR }),
|
|
22227
|
+
server: createV1Server(ALL_TOOLS, { ownDir: OWN_DIR })
|
|
22125
22228
|
};
|
|
22126
22229
|
var index_default = plugin;
|
|
22127
22230
|
export {
|
package/mcp.json
CHANGED
package/mcp_config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftsamo/agent-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
|
|
5
5
|
"author": "craftsamo",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"skills",
|
|
27
27
|
"plugin.json",
|
|
28
28
|
"mcp.json",
|
|
29
|
+
".mcp.json",
|
|
29
30
|
".claude-plugin",
|
|
30
31
|
".agents/plugins",
|
|
31
32
|
".antigravity-plugin",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "agent-github",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "craftsamo",
|
package/plugin.yaml
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import uuid
|
|
6
7
|
from collections.abc import Callable
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -13,25 +14,27 @@ from .runtime import Runtime, agent_cwd
|
|
|
13
14
|
#: How long ``target`` may take, in seconds. Hermes stops a ``pre_tool_call`` hook after 30 s and blocks the call.
|
|
14
15
|
TARGET_TIMEOUT_SECONDS = 20
|
|
15
16
|
|
|
16
|
-
#: What an approval rule matches when
|
|
17
|
+
#: What an approval rule matches when a repository a call writes to cannot be told.
|
|
17
18
|
ANY_RESOURCE = "*"
|
|
18
19
|
|
|
19
20
|
#: The Hermes hook that can hold a tool call for the person's approval.
|
|
20
21
|
HOOK_NAME = "pre_tool_call"
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
def
|
|
24
|
-
"""The
|
|
24
|
+
def resolve_resources(runtime: Runtime, tool_id: str, args: Any) -> list[str]:
|
|
25
|
+
"""The repositories a call writes to, in lowercase, or ``["*"]`` when they cannot be told."""
|
|
25
26
|
try:
|
|
26
27
|
result = runtime.run(
|
|
27
28
|
["target", tool_id, "--cwd", agent_cwd()],
|
|
28
29
|
stdin=json.dumps(args if isinstance(args, dict) else {}),
|
|
29
30
|
timeout=TARGET_TIMEOUT_SECONDS,
|
|
30
31
|
)
|
|
31
|
-
|
|
32
|
-
except Exception: # an approval is asked for either way; only its
|
|
33
|
-
return ANY_RESOURCE
|
|
34
|
-
|
|
32
|
+
resources = json.loads(result.stdout).get("resources") if result.code == 0 else None
|
|
33
|
+
except Exception: # an approval is asked for either way; only its resources are lost
|
|
34
|
+
return [ANY_RESOURCE]
|
|
35
|
+
if isinstance(resources, list) and resources and all(isinstance(item, str) and item for item in resources):
|
|
36
|
+
return resources
|
|
37
|
+
return [ANY_RESOURCE]
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def make_hook(runtime: Runtime, tools: list[ToolInfo]) -> Callable[..., dict[str, str] | None]:
|
|
@@ -46,13 +49,14 @@ def make_hook(runtime: Runtime, tools: list[ToolInfo]) -> Callable[..., dict[str
|
|
|
46
49
|
tool = by_id.get(tool_name)
|
|
47
50
|
if tool is None or tool.write_class == READ_CLASS:
|
|
48
51
|
return None
|
|
49
|
-
|
|
50
|
-
subject = tool.title if
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
resources = resolve_resources(runtime, tool.id, args)
|
|
53
|
+
subject = tool.title if resources == [ANY_RESOURCE] else f"{tool.title} on {', '.join(resources)}"
|
|
54
|
+
rule_key = f"agent-github:{tool.id}:{','.join(resources)}"
|
|
55
|
+
if ANY_RESOURCE in resources:
|
|
56
|
+
# Hermes falls back to the bare tool name for a missing rule_key, which an "always" would turn into a
|
|
57
|
+
# grant for every repository; a key no later call repeats leaves nothing reusable.
|
|
58
|
+
rule_key = f"{rule_key}:once-{uuid.uuid4().hex}"
|
|
59
|
+
return {"action": "approve", "message": f"{subject} ({tool.id})", "rule_key": rule_key}
|
|
56
60
|
|
|
57
61
|
return hook
|
|
58
62
|
|