@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-github",
3
- "version": "0.1.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": {
6
6
  "name": "craftsamo",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-github",
3
- "version": "0.1.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": {
6
6
  "name": "craftsamo",
@@ -10,7 +10,7 @@
10
10
  "repository": "https://github.com/craftsamo/agent-github",
11
11
  "license": "MIT",
12
12
  "mcpServers": {
13
- "github": {
13
+ "agent-github": {
14
14
  "command": "node",
15
15
  "args": ["${CLAUDE_PLUGIN_ROOT}/dist/mcp.js"]
16
16
  }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "agent-github": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/dist/mcp.js"]
6
+ }
7
+ }
8
+ }
package/README.md CHANGED
@@ -15,6 +15,9 @@ agent harness in the form that suits it best.
15
15
  | Hermes Agent | native plugin (Python, calls the command line) | the release commit, as the SHA |
16
16
  | other MCP host | MCP server from npm | the version in the package |
17
17
 
18
+ 0.2.0 renamed the MCP server from `github` to `agent-github`, so allow rules
19
+ and MCP configurations written for 0.1.0 change.
20
+
18
21
  The tools need [Node.js](https://nodejs.org/) 24 or later and the
19
22
  [GitHub CLI](https://cli.github.com/), authenticated as you.
20
23
  [`docs/conventions.md`](docs/conventions.md) describes how the tools are named
@@ -42,7 +45,8 @@ OpenCode V2 takes the package and its options in `opencode.json`:
42
45
  "package": "@craftsamo/agent-github@<version>",
43
46
  "options": { "toolsets": ["issues", "pull_requests", "projects"] }
44
47
  }
45
- ]
48
+ ],
49
+ "permissions": [{ "action": "github_*", "resource": "*", "effect": "ask" }]
46
50
  }
47
51
  ```
48
52
 
@@ -50,12 +54,25 @@ OpenCode V1 takes the package as a string, or as a pair with its options:
50
54
 
51
55
  ```json
52
56
  {
53
- "plugin": [["@craftsamo/agent-github@<version>", { "toolsets": ["issues", "pull_requests", "projects"] }]]
57
+ "plugin": [["@craftsamo/agent-github@<version>", { "toolsets": ["issues", "pull_requests", "projects"] }]],
58
+ "permission": { "github_*": "ask" }
54
59
  }
55
60
  ```
56
61
 
57
- V1 registers no skills. Unpack the package (`npm pack @craftsamo/agent-github@<version>`)
58
- and copy its `skills/*` folders into `~/.config/opencode/skills/`.
62
+ OpenCode allows any action no rule names, so the plugin's request is granted
63
+ silently unless a rule says `ask`. Read tools never ask, so the `github_*` glob
64
+ is safe.
65
+
66
+ V1 registers no skills. Unpack the package and copy its `skills/*` folders into
67
+ `~/.config/opencode/skills/`:
68
+
69
+ ```sh
70
+ npm pack @craftsamo/agent-github@<version>
71
+ tar -xzf craftsamo-agent-github-<version>.tgz
72
+ mkdir -p ~/.config/opencode/skills
73
+ cp -r package/skills/* ~/.config/opencode/skills/
74
+ rm -rf package craftsamo-agent-github-<version>.tgz
75
+ ```
59
76
 
60
77
  ### Claude Code
61
78
 
@@ -71,6 +88,10 @@ codex plugin marketplace add craftsamo/agent-github --ref v<version>
71
88
  codex plugin add agent-github@craftsamo
72
89
  ```
73
90
 
91
+ Codex starts the MCP server in the plugin's install directory, so name issues
92
+ as `owner/repo#N` or pass `repo` (a bare number fails there with a message saying
93
+ so).
94
+
74
95
  ### Grok Build
75
96
 
76
97
  ```sh
@@ -89,10 +110,11 @@ agy plugin install https://github.com/craftsamo/agent-github/tree/v<version>
89
110
 
90
111
  ### Hermes Agent
91
112
 
92
- Hermes installs a commit, not a tag:
113
+ Hermes installs a commit, not a tag, and the plugin installs disabled:
93
114
 
94
115
  ```sh
95
116
  hermes plugins install craftsamo/agent-github --ref <sha>
117
+ hermes plugins enable agent-github
96
118
  ```
97
119
 
98
120
  ### Any other MCP host
@@ -102,7 +124,7 @@ Add the server to the host's MCP configuration. Flags go in `args`:
102
124
  ```json
103
125
  {
104
126
  "mcpServers": {
105
- "github": {
127
+ "agent-github": {
106
128
  "command": "npx",
107
129
  "args": ["-y", "@craftsamo/agent-github@<version>", "--toolsets", "issues,pull_requests,projects"]
108
130
  }
@@ -112,7 +134,8 @@ Add the server to the host's MCP configuration. Flags go in `args`:
112
134
 
113
135
  ## Tools and skills
114
136
 
115
- Each toolset comes with a skill that tells an agent how to use its tools.
137
+ Each toolset comes with a skill that tells an agent how to use its tools. A
138
+ toolset with no tools yet registers none (0.2.0 ships the `issues` tools).
116
139
 
117
140
  ### Issues (`issues`)
118
141
 
@@ -131,19 +154,28 @@ The skill is [`github-issues`](skills/github-issues/SKILL.md). An issue's kind
131
154
  is the organization's Issue Type of that name when it defines one, otherwise a
132
155
  `type:<kind>` label.
133
156
 
134
- ## Read tools without asking
157
+ ## Reads and writes
158
+
159
+ Every read tool is named `github_<domain>_read`. Reads can run unasked; writes
160
+ are confirmed by the harness.
161
+
162
+ | Harness | Reads without asking |
163
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
164
+ | OpenCode | always; the plugin asks only before a write |
165
+ | Claude Code | the rule `mcp__plugin_agent-github_agent-github__*_read` in `permissions.allow` of `~/.claude/settings.json` or a project |
166
+ | Codex | always; Codex runs tools that declare `readOnlyHint` unasked, as the read tools do |
167
+ | Grok Build | `grok --allow 'MCPTool(agent-github__*_read)'` |
168
+ | Antigravity | an Allow rule `mcp(agent-github/<tool>)` for each read tool; not yet verified |
169
+ | Hermes | always; the plugin asks only before a write |
135
170
 
136
- Every read tool is named `github_<domain>_read`. Writes are confirmed by the
137
- harness.
171
+ Where a write is asked about, and where it is not:
138
172
 
139
- | Harness | Reads without asking |
140
- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
141
- | OpenCode | always; the plugin asks only before a write |
142
- | Claude Code | `permissions.allow` rule `mcp__plugin_agent-github_github__*_read`; a glob is allowed after the literal `mcp__<server>__` prefix |
143
- | Codex | always; Codex runs tools that declare `readOnlyHint` unasked, as the read tools do |
144
- | Grok Build | `grok --allow 'MCPTool(github__*_read)'` |
145
- | Antigravity | an Allow rule `mcp(github/<tool>)` for each read tool; not yet verified |
146
- | Hermes | always; the plugin asks only before a write |
173
+ - **OpenCode** grants any action no rule names, so a write runs unasked unless
174
+ a rule asks for it: add the `github_*` rule shown under [OpenCode](#opencode).
175
+ - **Codex** under `codex exec`, and **Grok Build** under `grok -p`, refuse a
176
+ write they cannot ask about, unless it is allowed beforehand.
177
+ - **Hermes** `hermes chat -q` refuses a write it cannot ask about; `hermes -z`
178
+ and `--yolo` skip approval, so writes then run unasked.
147
179
 
148
180
  ## Settings
149
181
 
@@ -174,9 +206,13 @@ Lists are comma-separated in a flag or a variable. Where each harness reads them
174
206
  exclude_tools: [github_pr_merge]
175
207
  ```
176
208
 
177
- - **The plugins for Claude Code, Codex, Grok Build and Antigravity** start the
178
- server without flags, so set the variables in the environment the harness runs
179
- in, or use the MCP configuration above, which takes flags.
209
+ - **Claude Code and Antigravity** start the plugin's server without flags; Claude
210
+ Code passes the client's environment, so set the variables in the environment
211
+ the harness runs in.
212
+ - **Codex and Grok Build** pass no environment to a plugin's MCP server, so the
213
+ `AGENT_GITHUB_*` variables have no effect there. To change settings in Codex,
214
+ add the server under `[mcp_servers]` in Codex's config with flags in `args` (as
215
+ in [Any other MCP host](#any-other-mcp-host)) instead of using the plugin.
180
216
 
181
217
  ## Layout
182
218
 
@@ -188,11 +224,12 @@ Lists are comma-separated in a flag or a variable. Where each harness reads them
188
224
  - `src/hermes/`, `plugin.yaml` and `__init__.py` — the Hermes plugin.
189
225
  - `skills/` — one skill per toolset, `skills/<name>/SKILL.md`, which every
190
226
  harness that takes skills loads from the package or plugin root.
191
- - `plugin.json`, `mcp.json`, `.claude-plugin/`, `.agents/plugins/`,
227
+ - `plugin.json`, `mcp.json`, `.mcp.json`, `.claude-plugin/`, `.agents/plugins/`,
192
228
  `.antigravity-plugin/` and `mcp_config.json` — the plugin manifests of the
193
- other harnesses. Claude Code's server is declared inside
194
- `.claude-plugin/plugin.json`, since a `.mcp.json` in the repository root would
195
- be read as this project's own MCP configuration.
229
+ other harnesses. The root `.mcp.json` is the configuration Grok Build reads
230
+ (with `${CLAUDE_PLUGIN_ROOT}`, the variable both Grok Build and Claude Code
231
+ expand); when working on this repository in Claude Code, decline the project
232
+ MCP server it offers from that file.
196
233
 
197
234
  How a release is made is in [`RELEASING.md`](RELEASING.md).
198
235
 
package/dist/cli.js CHANGED
@@ -5,6 +5,13 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // src/entry/package-root.ts
9
+ import { dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ function packageRoot(bundleUrl) {
12
+ return dirname(dirname(fileURLToPath(bundleUrl)));
13
+ }
14
+
8
15
  // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/classic/external.js
9
16
  var external_exports = {};
10
17
  __export(external_exports, {
@@ -20205,13 +20212,13 @@ async function ensureLabel(repo, name, ctx) {
20205
20212
  function isTypeFailure(error62) {
20206
20213
  return isToolError(error62) && TYPE_FAILURE_CODES.includes(error62.code) && TYPE_MENTION.test(error62.message);
20207
20214
  }
20208
- async function withKindFallback(kind, placement, attempt) {
20215
+ async function withKindFallback(kind, placement, attempt2) {
20209
20216
  try {
20210
- return { placement, result: await attempt(placement) };
20217
+ return { placement, result: await attempt2(placement) };
20211
20218
  } catch (error62) {
20212
20219
  if (placement.via !== "type" || !isTypeFailure(error62)) throw error62;
20213
20220
  const fallback = labelKind(kind);
20214
- return { placement: fallback, result: await attempt(fallback) };
20221
+ return { placement: fallback, result: await attempt2(fallback) };
20215
20222
  }
20216
20223
  }
20217
20224
  async function kindFlags(placement, repo, ctx) {
@@ -20513,6 +20520,8 @@ async function readSubIssues(parent, ctx) {
20513
20520
  }
20514
20521
 
20515
20522
  // src/core/refs.ts
20523
+ import { realpath } from "node:fs/promises";
20524
+ import { isAbsolute, relative, sep } from "node:path";
20516
20525
  var DEFAULT_HOST = "github.com";
20517
20526
  var SSH_ALIAS_HOST = "ssh.github.com";
20518
20527
  var GIT_CONFIG_ENV = ["GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM", "GIT_CONFIG_NOSYSTEM"];
@@ -20596,11 +20605,25 @@ function gitEnv() {
20596
20605
  }
20597
20606
  return { ...env, GIT_TERMINAL_PROMPT: "0" };
20598
20607
  }
20599
- async function repoFromOrigin(cwd, signal) {
20608
+ async function isWithin(dir, root) {
20609
+ try {
20610
+ const path = relative(await realpath(root), await realpath(dir));
20611
+ return path === "" || !(path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path));
20612
+ } catch {
20613
+ return false;
20614
+ }
20615
+ }
20616
+ async function repoFromOrigin(cwd, signal, ownDir) {
20600
20617
  const env = gitEnv();
20601
- const unresolved = (why) => invalid(
20602
- `Cannot tell which repository to use: ${why}. Run the call inside a clone of a GitHub repository, or pass \`repo\` as owner/name.`
20618
+ const unresolved = (why, advice = "") => invalid(
20619
+ `Cannot tell which repository to use: ${why}. Run the call inside a clone of a GitHub repository, or pass \`repo\` as owner/name.${advice}`
20603
20620
  );
20621
+ if (ownDir !== void 0 && await isWithin(cwd, ownDir)) {
20622
+ throw unresolved(
20623
+ `${cwd} is where these tools are installed, not a project`,
20624
+ " An issue can be given as `owner/repo#N`."
20625
+ );
20626
+ }
20604
20627
  let result;
20605
20628
  try {
20606
20629
  result = await exec(["git", "remote", "get-url", "origin"], { cwd, env, signal });
@@ -20615,7 +20638,10 @@ async function repoFromOrigin(cwd, signal) {
20615
20638
  return repo;
20616
20639
  }
20617
20640
  async function resolveRepo(repo, ctx) {
20618
- return repo === void 0 || repo.trim() === "" ? repoFromOrigin(ctx.cwd, ctx.signal) : parseRepo(repo);
20641
+ return repo === void 0 || repo.trim() === "" ? repoFromOrigin(ctx.cwd, ctx.signal, ctx.ownDir) : parseRepo(repo);
20642
+ }
20643
+ function isBareNumber(ref) {
20644
+ return BARE_NUMBER_PATTERN.test(ref.trim());
20619
20645
  }
20620
20646
  function parseNumber(digits, ref) {
20621
20647
  const number4 = Number.parseInt(digits, 10);
@@ -20666,10 +20692,9 @@ async function parseNumberRef(ref, repoArg, ctx, options = {}) {
20666
20692
  }
20667
20693
 
20668
20694
  // src/core/github/issue-refs.ts
20669
- var BARE_NUMBER = /^#?\d+$/;
20670
20695
  async function resolveRelatedIssue(ref, target, ctx) {
20671
20696
  const text = ref.trim();
20672
- return parseNumberRef(text, BARE_NUMBER.test(text) ? target.full : void 0, ctx, { accept: "issue" });
20697
+ return parseNumberRef(text, isBareNumber(text) ? target.full : void 0, ctx, { accept: "issue" });
20673
20698
  }
20674
20699
  function issueLabel(issue2) {
20675
20700
  return `${issue2.repo.full}#${issue2.number}`;
@@ -20697,6 +20722,10 @@ function defineTool(spec) {
20697
20722
  if (!(spec.output instanceof external_exports.ZodObject)) {
20698
20723
  throw new Error(`Tool "${spec.id}" must have an object output schema, since a result is an object.`);
20699
20724
  }
20725
+ const unknownTarget = Object.keys(spec.targets ?? {}).find((name) => !Object.hasOwn(spec.input.shape, name));
20726
+ if (unknownTarget !== void 0) {
20727
+ throw new Error(`Tool "${spec.id}" declares the target "${unknownTarget}", which is not one of its arguments.`);
20728
+ }
20700
20729
  return spec;
20701
20730
  }
20702
20731
  var CLASS_HINTS = {
@@ -20802,6 +20831,7 @@ var StepErrors = class {
20802
20831
  // src/core/issues/write-support.ts
20803
20832
  var REPO_DESCRIPTION = "owner/name; defaults to the working tree's repository (its origin). Used for a bare number.";
20804
20833
  var ISSUE_DESCRIPTION = "The issue, as 12, #12, owner/repo#12 or its URL.";
20834
+ var ISSUE_TARGETS = { issue: "issue", repo: "repo" };
20805
20835
  var LIST_SYNTAX = /[,"\r\n]/;
20806
20836
  var URL_LINE = /^https?:\/\/\S+$/;
20807
20837
  function invalid2(message) {
@@ -20871,6 +20901,7 @@ var issueClose = defineTool({
20871
20901
  ].join(" "),
20872
20902
  toolset: "issues",
20873
20903
  class: "overwrite",
20904
+ targets: ISSUE_TARGETS,
20874
20905
  input: input2,
20875
20906
  output: output2,
20876
20907
  async execute(args, ctx) {
@@ -20927,6 +20958,7 @@ var issueComment = defineTool({
20927
20958
  ].join(" "),
20928
20959
  toolset: "issues",
20929
20960
  class: "add",
20961
+ targets: ISSUE_TARGETS,
20930
20962
  input: input3,
20931
20963
  output: output3,
20932
20964
  async execute(args, ctx) {
@@ -21044,6 +21076,7 @@ var issueCreate = defineTool({
21044
21076
  ].join(" "),
21045
21077
  toolset: "issues",
21046
21078
  class: "add",
21079
+ targets: { repo: "repo", parent: "issue", blocked_by: "issues" },
21047
21080
  input: input4,
21048
21081
  output: output4,
21049
21082
  async execute(args, ctx) {
@@ -21191,6 +21224,7 @@ var issueDevelop = defineTool({
21191
21224
  ].join(" "),
21192
21225
  toolset: "issues",
21193
21226
  class: "idempotent",
21227
+ targets: { ...ISSUE_TARGETS, branch_repo: "repo" },
21194
21228
  input: input5,
21195
21229
  output: output5,
21196
21230
  async execute(args, ctx) {
@@ -21338,6 +21372,7 @@ var issueEdit = defineTool({
21338
21372
  ].join(" "),
21339
21373
  toolset: "issues",
21340
21374
  class: "overwrite",
21375
+ targets: ISSUE_TARGETS,
21341
21376
  input: input6,
21342
21377
  output: output6,
21343
21378
  async execute(args, ctx) {
@@ -21362,14 +21397,14 @@ var issueEdit = defineTool({
21362
21397
  if (change.removeMilestone) flags.push("--remove-milestone");
21363
21398
  const named = new Set([...change.addLabels, ...change.removeLabels].map((label) => label.toLowerCase()));
21364
21399
  const swapped = (where) => where.via === "label" ? otherKindLabels(current?.labels ?? [], where.name).filter((label) => !named.has(label.toLowerCase())).flatMap((label) => ["--remove-label", label]) : [];
21365
- const attempt = async (where) => editIssue(
21400
+ const attempt2 = async (where) => editIssue(
21366
21401
  target,
21367
21402
  issue2.number,
21368
21403
  where ? [...flags, ...await kindFlags(where, target, ctx), ...swapped(where)] : flags,
21369
21404
  ctx,
21370
21405
  body
21371
21406
  );
21372
- const applied = kind ? await withKindFallback(kind.name, kind.placement, attempt) : { placement: void 0, result: await attempt() };
21407
+ const applied = kind ? await withKindFallback(kind.name, kind.placement, attempt2) : { placement: void 0, result: await attempt2() };
21373
21408
  return {
21374
21409
  repo: target.full,
21375
21410
  issue: issue2.number,
@@ -21696,6 +21731,16 @@ var issueRelate = defineTool({
21696
21731
  ].join(" "),
21697
21732
  toolset: "issues",
21698
21733
  class: "idempotent",
21734
+ targets: {
21735
+ ...ISSUE_TARGETS,
21736
+ parent: "issue",
21737
+ add_sub_issues: "issues",
21738
+ remove_sub_issues: "issues",
21739
+ add_blocked_by: "issues",
21740
+ remove_blocked_by: "issues",
21741
+ add_blocking: "issues",
21742
+ remove_blocking: "issues"
21743
+ },
21699
21744
  input: input8,
21700
21745
  output: output8,
21701
21746
  async execute(args, ctx) {
@@ -21794,6 +21839,7 @@ var issueReopen = defineTool({
21794
21839
  ].join(" "),
21795
21840
  toolset: "issues",
21796
21841
  class: "overwrite",
21842
+ targets: ISSUE_TARGETS,
21797
21843
  input: input9,
21798
21844
  output: output9,
21799
21845
  async execute(args, ctx) {
@@ -21824,17 +21870,69 @@ var ALL_TOOLS = [
21824
21870
  issueDevelop
21825
21871
  ];
21826
21872
 
21827
- // src/entry/permission.ts
21828
- var ANY_RESOURCE = "*";
21829
- async function permissionResource(args, ctx) {
21830
- const repo = typeof args === "object" && args !== null ? args.repo : void 0;
21831
- if (repo !== void 0 && typeof repo !== "string") return ANY_RESOURCE;
21873
+ // src/core/targets.ts
21874
+ var ANY_TARGET = "*";
21875
+ var ISSUE_ARGUMENT = "issue";
21876
+ async function attempt(fn) {
21832
21877
  try {
21833
- return (await resolveRepo(repo, ctx)).full.toLowerCase();
21878
+ return await fn();
21834
21879
  } catch (error62) {
21835
21880
  if (isAbortError(error62)) throw error62;
21836
- return ANY_RESOURCE;
21881
+ return void 0;
21882
+ }
21883
+ }
21884
+ function isBlank(value) {
21885
+ return typeof value === "string" && value.trim() === "";
21886
+ }
21887
+ function ownRepository(spec, args, ctx) {
21888
+ const repo = args.repo;
21889
+ const issue2 = args[ISSUE_ARGUMENT];
21890
+ let pending;
21891
+ async function resolve() {
21892
+ if (spec.targets?.[ISSUE_ARGUMENT] === "issue" && typeof issue2 === "string" && !isBareNumber(issue2)) {
21893
+ const named = await attempt(() => parseNumberRef(issue2, void 0, ctx, { accept: "issue" }));
21894
+ if (named) return named.repo;
21895
+ }
21896
+ if (repo !== void 0 && typeof repo !== "string") return void 0;
21897
+ return attempt(() => resolveRepo(repo, ctx));
21898
+ }
21899
+ return () => pending ??= resolve();
21900
+ }
21901
+ async function issueRepository(value, own2, ctx) {
21902
+ if (typeof value !== "string") return void 0;
21903
+ const bare = isBareNumber(value);
21904
+ const repoArg = bare ? (await own2())?.full : void 0;
21905
+ if (bare && repoArg === void 0) return void 0;
21906
+ return attempt(async () => (await parseNumberRef(value, repoArg, ctx, { accept: "issue" })).repo);
21907
+ }
21908
+ async function targetRepository(kind, value, own2, ctx) {
21909
+ if (kind !== "repo") return issueRepository(value, own2, ctx);
21910
+ return typeof value === "string" ? attempt(() => parseRepo(value)) : void 0;
21911
+ }
21912
+ async function writeTargets(spec, args, ctx) {
21913
+ const given3 = typeof args === "object" && args !== null ? args : {};
21914
+ const own2 = ownRepository(spec, given3, ctx);
21915
+ const found = /* @__PURE__ */ new Set();
21916
+ const add = (repo) => found.add(repo ? repo.full.toLowerCase() : ANY_TARGET);
21917
+ add(await own2());
21918
+ for (const [name, kind] of Object.entries(spec.targets ?? {})) {
21919
+ const value = given3[name];
21920
+ if (value === void 0) continue;
21921
+ if (kind === "issues" && !Array.isArray(value)) {
21922
+ add(void 0);
21923
+ continue;
21924
+ }
21925
+ for (const item of kind === "issues" ? value : [value]) {
21926
+ if (!isBlank(item)) add(await targetRepository(kind, item, own2, ctx));
21927
+ }
21837
21928
  }
21929
+ const repos = [...found].filter((repo) => repo !== ANY_TARGET).sort();
21930
+ return found.has(ANY_TARGET) ? [...repos, ANY_TARGET] : repos;
21931
+ }
21932
+
21933
+ // src/entry/permission.ts
21934
+ function permissionResources(spec, args, ctx) {
21935
+ return writeTargets(spec, args, ctx);
21838
21936
  }
21839
21937
 
21840
21938
  // src/entry/settings.ts
@@ -22014,7 +22112,11 @@ async function runTool(command, io) {
22014
22112
  const spec = command.selected.find((candidate) => candidate.id === command.toolId);
22015
22113
  if (!spec) return writeError(io, "invalid_argument", `Unknown tool "${command.toolId}".`);
22016
22114
  try {
22017
- const result = await invoke(spec, await readArguments(io), { cwd: command.cwd, signal: io.signal });
22115
+ const result = await invoke(spec, await readArguments(io), {
22116
+ cwd: command.cwd,
22117
+ ownDir: io.ownDir,
22118
+ signal: io.signal
22119
+ });
22018
22120
  writeJson(io, { result });
22019
22121
  return EXIT_OK;
22020
22122
  } catch (error62) {
@@ -22026,9 +22128,15 @@ async function runTool(command, io) {
22026
22128
  }
22027
22129
  }
22028
22130
  async function targetOf(command, io) {
22131
+ const spec = io.specs.find((candidate) => candidate.id === command.toolId);
22132
+ if (!spec) return writeError(io, "invalid_argument", `Unknown tool "${command.toolId}".`);
22029
22133
  try {
22030
- const resource = await permissionResource(await readArguments(io), { cwd: command.cwd, signal: io.signal });
22031
- writeJson(io, { resource });
22134
+ const resources = await permissionResources(spec, await readArguments(io), {
22135
+ cwd: command.cwd,
22136
+ ownDir: io.ownDir,
22137
+ signal: io.signal
22138
+ });
22139
+ writeJson(io, { resources });
22032
22140
  return EXIT_OK;
22033
22141
  } catch (error62) {
22034
22142
  if (isToolError(error62)) return writeError(io, error62.code, error62.message);
@@ -22076,6 +22184,7 @@ async function main() {
22076
22184
  process.exitCode = await runCli(process.argv.slice(2), {
22077
22185
  specs: ALL_TOOLS,
22078
22186
  env: process.env,
22187
+ ownDir: packageRoot(import.meta.url),
22079
22188
  readStdin,
22080
22189
  stdout: (text) => process.stdout.write(text),
22081
22190
  stderr: (text) => process.stderr.write(text),