@amonstack/gitea-mcp 0.3.0 → 0.3.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.
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Credential candidate model and state machine for fault-tolerant Gitea auth.
3
+ *
4
+ * A discovered credential is treated as an opaque candidate: the `secret`
5
+ * field might be a Gitea Personal Access Token, an account login password,
6
+ * or an OAuth token — the type cannot be determined statically from any
7
+ * source (`~/.git-credentials` stores whatever the user typed into git's
8
+ * password prompt, and `[gitea] token` / `GITEA_TOKEN` are by convention but
9
+ * not guarantee a PAT). The runtime therefore tries each candidate under
10
+ * one or more HTTP auth schemes, advancing on 401/403 until something works
11
+ * or every candidate × scheme is exhausted.
12
+ *
13
+ * This module is pure (no I/O, no fetch) so the state machine is unit-tested
14
+ * directly. Mutation is contained to per-candidate state fields; callers
15
+ * never replace the candidates array.
16
+ *
17
+ * SECURITY: `secret` is never logged, never interpolated into error
18
+ * messages, and never surfaced by `summarizeCandidates`. Diagnostic output
19
+ * only ever carries `secretPresent: boolean` and a masked `username`.
20
+ */
21
+ /**
22
+ * Decide scheme ordering for a credential-store entry based on username
23
+ * heuristic. Config/env candidates always use `["token"]` (per project
24
+ * decision to preserve their "simple token" semantics).
25
+ *
26
+ * - Username `oauth2`, `x-oauth-basic`, or empty → `["token", "basic"]`:
27
+ * these are git OAuth conventions; basic auth with such a username fails
28
+ * for real passwords, so try `token` first.
29
+ * - Any other (real-looking) username → `["basic", "token"]`: basic auth
30
+ * with the correct username works for both PATs and passwords, so it has
31
+ * the widest coverage and goes first.
32
+ */
33
+ export function orderSchemesForCredentialStore(username) {
34
+ if (username === undefined)
35
+ return ["token", "basic"];
36
+ const u = username.toLowerCase();
37
+ if (u === "oauth2" || u === "x-oauth-basic") {
38
+ return ["token", "basic"];
39
+ }
40
+ return ["basic", "token"];
41
+ }
42
+ /**
43
+ * Build the `Authorization` header value for one attempt. Mutates nothing.
44
+ *
45
+ * For `basic`, the username falls back to `oauth2` when absent (a missing
46
+ * username only happens for malformed credential-store entries; Gitea
47
+ * rejects basic auth without a username, so the attempt will fail and the
48
+ * state machine will advance).
49
+ */
50
+ export function buildAuthHeader(candidate, scheme) {
51
+ if (scheme === "basic") {
52
+ const user = candidate.username ?? "oauth2";
53
+ const encoded = Buffer.from(`${user}:${candidate.secret}`).toString("base64");
54
+ return `Basic ${encoded}`;
55
+ }
56
+ return `token ${candidate.secret}`;
57
+ }
58
+ /**
59
+ * Pick the next (candidate, scheme) to try. Skips exhausted candidates and
60
+ * candidates whose scheme list is fully tried. Does NOT mutate state — the
61
+ * caller records the attempt via `markAttemptFailed` / `markAttemptSucceeded`.
62
+ *
63
+ * Returns null when every candidate × scheme has been tried.
64
+ */
65
+ export function pickNextAttempt(candidates) {
66
+ for (let i = 0; i < candidates.length; i++) {
67
+ const c = candidates[i];
68
+ if (c.status === "exhausted")
69
+ continue;
70
+ // Active candidates are already locked — the caller short-circuits
71
+ // before entering this iteration loop (GiteaClient.request checks
72
+ // findActiveCandidateIndex first). Here we skip them defensively.
73
+ if (c.status === "active")
74
+ continue;
75
+ if (c.nextSchemeIndex >= c.schemes.length)
76
+ continue;
77
+ return { candidateIndex: i, scheme: c.schemes[c.nextSchemeIndex] };
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Record that an attempt failed with `error` (a short reason like "401").
83
+ * Advances the candidate's scheme index; when no schemes remain, marks the
84
+ * candidate as exhausted.
85
+ */
86
+ export function markAttemptFailed(candidates, candidateIndex, error) {
87
+ const c = candidates[candidateIndex];
88
+ c.lastError = error;
89
+ c.lastTriedScheme = c.schemes[c.nextSchemeIndex];
90
+ c.nextSchemeIndex += 1;
91
+ if (c.nextSchemeIndex >= c.schemes.length || c.status === "active") {
92
+ c.status = "exhausted";
93
+ c.activeScheme = undefined;
94
+ }
95
+ }
96
+ /**
97
+ * Record that an attempt succeeded. Marks the candidate as active, locks in
98
+ * `activeScheme`, and marks all PRIOR candidates as exhausted (they were
99
+ * tried and failed before this one succeeded).
100
+ */
101
+ export function markAttemptSucceeded(candidates, candidateIndex, scheme) {
102
+ for (let i = 0; i < candidateIndex; i++) {
103
+ if (candidates[i].status !== "active")
104
+ candidates[i].status = "exhausted";
105
+ }
106
+ const c = candidates[candidateIndex];
107
+ c.status = "active";
108
+ c.activeScheme = scheme;
109
+ c.lastError = undefined;
110
+ }
111
+ /**
112
+ * Mask a username for diagnostic output: first character plus `***`. Returns
113
+ * null when the candidate has no username (config/env sources, or malformed
114
+ * credential-store entries).
115
+ */
116
+ export function maskUsername(username) {
117
+ if (!username)
118
+ return null;
119
+ return `${username.charAt(0)}***`;
120
+ }
121
+ export function summarizeCandidates(candidates) {
122
+ return candidates.map((c) => ({
123
+ source: c.source,
124
+ schemes: c.schemes,
125
+ username: maskUsername(c.username),
126
+ secretPresent: c.secret.length > 0,
127
+ status: c.status,
128
+ lastTriedScheme: c.lastTriedScheme ?? null,
129
+ activeScheme: c.activeScheme ?? null,
130
+ lastError: c.lastError ?? null,
131
+ }));
132
+ }
133
+ /** Find the index of the active candidate, or null when none is active. */
134
+ export function findActiveCandidateIndex(candidates) {
135
+ for (let i = 0; i < candidates.length; i++) {
136
+ if (candidates[i].status === "active")
137
+ return i;
138
+ }
139
+ return null;
140
+ }
141
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type CredentialDiscoveryResult } from "./credentials.js";
1
2
  /** A single parsed git remote. `remote` is the remote name (`origin`, `upstream`, ...). */
2
3
  export interface ParsedRemote {
3
4
  remote: string;
@@ -18,15 +19,22 @@ export interface DiscoverOptions {
18
19
  /** Override credential-store paths (defaults to XDG then `~/.git-credentials`). */
19
20
  credentialsPaths?: string[];
20
21
  }
21
- export interface DiscoveredConfig {
22
- baseUrl: string;
23
- /** Undefined when no token source resolves — the server still starts and a Skill guides the user. */
24
- token?: string;
25
- defaultOwner?: string;
26
- defaultRepo?: string;
27
- /** Name of the remote the values were derived from (undefined when derived purely from env). */
28
- remote?: string;
29
- source: "env" | "git";
22
+ /**
23
+ * A parsed `~/.git-credentials` line. Both `username` and `password` are
24
+ * URL-decoded. Either may be absent:
25
+ * - `https://user:pass@host` → both present
26
+ * - `https://:pass@host` → only password
27
+ * - `https://tok@host` → only username (git stores a token here when the
28
+ * host accepts one as the "username"; the caller treats it as the secret)
29
+ *
30
+ * `path` is the URL pathname with leading/trailing slashes and any `.git`
31
+ * suffix stripped, used to narrow multiple host matches toward the target repo.
32
+ */
33
+ export interface ParsedCredentialEntry {
34
+ username?: string;
35
+ password?: string;
36
+ host: string;
37
+ path: string;
30
38
  }
31
39
  /**
32
40
  * Parse a git remote URL into host/baseUrl/owner/repo. Accepts `ssh://`, the
@@ -48,11 +56,18 @@ export declare function selectRemote(remotes: ParsedRemote[]): ParsedRemote | nu
48
56
  */
49
57
  export declare function readTokenFromGitConfig(content: string, baseUrl: string): string | undefined;
50
58
  /**
51
- * Find a credential for `host` in a `git-credentials` file. Each line is a URL;
52
- * the password (preferred) or username is returned as the token. Malformed and
53
- * non-matching lines are skipped.
59
+ * Parse every credential-store line whose host matches. Each line is a URL of
60
+ * the form `protocol://[user[:pass]]@host[:port][/path]`; malformed and
61
+ * non-matching lines are skipped. Returns entries in file order; callers
62
+ * narrow and re-sort by repo path specificity.
63
+ *
64
+ * The `password` field — when present — holds whatever the user typed into
65
+ * git's password prompt: a real account password, a Personal Access Token,
66
+ * or an OAuth token. Git itself does not distinguish, and neither does this
67
+ * parser; the GiteaClient runtime tries each entry under multiple auth
68
+ * schemes to discover what works.
54
69
  */
55
- export declare function parseGitCredentials(content: string, host: string): string | undefined;
70
+ export declare function parseGitCredentials(content: string, host: string): ParsedCredentialEntry[];
56
71
  /** Default credential-store paths: `$XDG_CONFIG_HOME/git/credentials` then `~/.git-credentials`. */
57
72
  export declare function defaultCredentialsPaths(): string[];
58
73
  /**
@@ -62,12 +77,21 @@ export declare function defaultCredentialsPaths(): string[];
62
77
  * remote (`upstream` → `origin` → first). Returns null only when neither is
63
78
  * available — callers should treat that as "do not start the server".
64
79
  *
65
- * token: `.git/config` `[gitea "<baseUrl>"] token` → bare `[gitea] token` →
66
- * matching entry in a git credential store `GITEA_TOKEN` (env). May be
67
- * undefined when nothing resolves; the server still starts and a Skill
68
- * guides the user to provide one.
80
+ * candidates (in priority order):
81
+ * 1. `[gitea "<baseUrl>"] token` / bare `[gitea] token` from `.git/config`
82
+ * (explicit user configuration; `token` scheme only).
83
+ * 2. `GITEA_TOKEN` env var (explicit env; `token` scheme only — preserves
84
+ * the simple-token semantics).
85
+ * 3. Every host-matching entry in a git credential store, narrowed by repo
86
+ * path specificity (most specific first). Each entry may be a PAT,
87
+ * password, or OAuth token; the client tries each under `basic` and/or
88
+ * `token` schemes per the username heuristic.
69
89
  *
70
90
  * owner/repo: `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO` (env) win; otherwise
71
- * taken from the selected remote.
91
+ * taken from the selected remote.
92
+ *
93
+ * The result may have an empty `candidates` array (anonymous mode); the server
94
+ * still starts and a Skill guides the user to provide one. Write tools will
95
+ * fail with 401/403 until a working credential is added.
72
96
  */
73
- export declare function discoverConfig(options?: DiscoverOptions): Promise<DiscoveredConfig | null>;
97
+ export declare function discoverConfig(options?: DiscoverOptions): Promise<CredentialDiscoveryResult | null>;
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
+ import { orderSchemesForCredentialStore, } from "./credentials.js";
4
5
  /**
5
6
  * Parse a git remote URL into host/baseUrl/owner/repo. Accepts `ssh://`, the
6
7
  * scp-like `user@host:owner/repo` form, and `http(s)://`. SSH URLs derive an
@@ -87,11 +88,19 @@ export function readTokenFromGitConfig(content, baseUrl) {
87
88
  return undefined;
88
89
  }
89
90
  /**
90
- * Find a credential for `host` in a `git-credentials` file. Each line is a URL;
91
- * the password (preferred) or username is returned as the token. Malformed and
92
- * non-matching lines are skipped.
91
+ * Parse every credential-store line whose host matches. Each line is a URL of
92
+ * the form `protocol://[user[:pass]]@host[:port][/path]`; malformed and
93
+ * non-matching lines are skipped. Returns entries in file order; callers
94
+ * narrow and re-sort by repo path specificity.
95
+ *
96
+ * The `password` field — when present — holds whatever the user typed into
97
+ * git's password prompt: a real account password, a Personal Access Token,
98
+ * or an OAuth token. Git itself does not distinguish, and neither does this
99
+ * parser; the GiteaClient runtime tries each entry under multiple auth
100
+ * schemes to discover what works.
93
101
  */
94
102
  export function parseGitCredentials(content, host) {
103
+ const entries = [];
95
104
  for (const rawLine of content.split(/\r?\n/)) {
96
105
  const line = rawLine.trim();
97
106
  if (!line || line.startsWith("#"))
@@ -100,15 +109,37 @@ export function parseGitCredentials(content, host) {
100
109
  const credUrl = new URL(line);
101
110
  if (credUrl.host !== host)
102
111
  continue;
103
- const tok = credUrl.password || credUrl.username;
104
- if (tok)
105
- return decodeURIComponent(tok);
112
+ entries.push({
113
+ username: credUrl.username ? decodeURIComponent(credUrl.username) : undefined,
114
+ password: credUrl.password ? decodeURIComponent(credUrl.password) : undefined,
115
+ host: credUrl.host,
116
+ path: credUrl.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, ""),
117
+ });
106
118
  }
107
119
  catch {
108
120
  continue;
109
121
  }
110
122
  }
111
- return undefined;
123
+ return entries;
124
+ }
125
+ /**
126
+ * Score a credential entry's path specificity against the target repo path
127
+ * (`owner/repo`). Higher = more specific. Used to narrow multiple host
128
+ * matches toward the most relevant entry first.
129
+ *
130
+ * - Path is empty (host-only entry) → 0
131
+ * - Path is a prefix of the repo path → length of the matching path
132
+ * - Path does not match → -1 (deprioritized but still tried as a fallback)
133
+ */
134
+ function scoreEntryPath(entryPath, repoPath) {
135
+ if (!entryPath)
136
+ return 0;
137
+ if (!repoPath)
138
+ return 0;
139
+ if (repoPath === entryPath || repoPath.startsWith(`${entryPath}/`)) {
140
+ return entryPath.length;
141
+ }
142
+ return -1;
112
143
  }
113
144
  /** Default credential-store paths: `$XDG_CONFIG_HOME/git/credentials` then `~/.git-credentials`. */
114
145
  export function defaultCredentialsPaths() {
@@ -129,6 +160,37 @@ async function readOptionalFile(path) {
129
160
  return "";
130
161
  }
131
162
  }
163
+ /**
164
+ * Build a `CandidateCredential` from a parsed credential-store entry.
165
+ *
166
+ * - `https://:pass@host` or `https://user:pass@host` → secret = pass,
167
+ * username preserved (basic auth needs it).
168
+ * - `https://tok@host` (username but no password) → secret = tok, no
169
+ * username: this is git's "store the token as the username" convention.
170
+ * Try `token` first (most common), fall back to `basic`.
171
+ */
172
+ function candidateFromEntry(entry) {
173
+ if (entry.password) {
174
+ return {
175
+ source: "credential-store",
176
+ username: entry.username,
177
+ secret: entry.password,
178
+ schemes: orderSchemesForCredentialStore(entry.username),
179
+ status: "pending",
180
+ nextSchemeIndex: 0,
181
+ };
182
+ }
183
+ if (entry.username) {
184
+ return {
185
+ source: "credential-store",
186
+ secret: entry.username,
187
+ schemes: ["token", "basic"],
188
+ status: "pending",
189
+ nextSchemeIndex: 0,
190
+ };
191
+ }
192
+ return null;
193
+ }
132
194
  /**
133
195
  * Discover the Gitea connection config from env + the local git context.
134
196
  *
@@ -136,13 +198,22 @@ async function readOptionalFile(path) {
136
198
  * remote (`upstream` → `origin` → first). Returns null only when neither is
137
199
  * available — callers should treat that as "do not start the server".
138
200
  *
139
- * token: `.git/config` `[gitea "<baseUrl>"] token` → bare `[gitea] token` →
140
- * matching entry in a git credential store `GITEA_TOKEN` (env). May be
141
- * undefined when nothing resolves; the server still starts and a Skill
142
- * guides the user to provide one.
201
+ * candidates (in priority order):
202
+ * 1. `[gitea "<baseUrl>"] token` / bare `[gitea] token` from `.git/config`
203
+ * (explicit user configuration; `token` scheme only).
204
+ * 2. `GITEA_TOKEN` env var (explicit env; `token` scheme only — preserves
205
+ * the simple-token semantics).
206
+ * 3. Every host-matching entry in a git credential store, narrowed by repo
207
+ * path specificity (most specific first). Each entry may be a PAT,
208
+ * password, or OAuth token; the client tries each under `basic` and/or
209
+ * `token` schemes per the username heuristic.
143
210
  *
144
211
  * owner/repo: `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO` (env) win; otherwise
145
- * taken from the selected remote.
212
+ * taken from the selected remote.
213
+ *
214
+ * The result may have an empty `candidates` array (anonymous mode); the server
215
+ * still starts and a Skill guides the user to provide one. Write tools will
216
+ * fail with 401/403 until a working credential is added.
146
217
  */
147
218
  export async function discoverConfig(options = {}) {
148
219
  const cwd = options.cwd ?? process.cwd();
@@ -166,30 +237,59 @@ export async function discoverConfig(options = {}) {
166
237
  else {
167
238
  host = selected?.host;
168
239
  }
169
- let token;
240
+ const candidates = [];
241
+ // Source 1: .git/config [gitea "<baseUrl>"] token / bare [gitea] token.
242
+ if (host) {
243
+ const configToken = readTokenFromGitConfig(gitConfigContent, baseUrl);
244
+ if (configToken) {
245
+ candidates.push({
246
+ source: "gitea-config",
247
+ secret: configToken,
248
+ schemes: ["token"],
249
+ status: "pending",
250
+ nextSchemeIndex: 0,
251
+ });
252
+ }
253
+ }
254
+ // Source 2: GITEA_TOKEN env (simple-token semantics — no scheme probing).
255
+ const envToken = env.GITEA_TOKEN;
256
+ if (envToken) {
257
+ candidates.push({
258
+ source: "env",
259
+ secret: envToken,
260
+ schemes: ["token"],
261
+ status: "pending",
262
+ nextSchemeIndex: 0,
263
+ });
264
+ }
265
+ // Source 3..N: credential-store entries, host-matched and path-narrowed.
170
266
  if (host) {
171
- token = readTokenFromGitConfig(gitConfigContent, baseUrl);
172
- if (!token) {
173
- const paths = options.credentialsPaths ?? defaultCredentialsPaths();
174
- for (const path of paths) {
175
- const cred = await readOptionalFile(path);
176
- if (cred) {
177
- token = parseGitCredentials(cred, host);
178
- if (token)
179
- break;
180
- }
267
+ const paths = options.credentialsPaths ?? defaultCredentialsPaths();
268
+ const repoPath = selected ? `${selected.owner}/${selected.repo}` : "";
269
+ const scored = [];
270
+ let order = 0;
271
+ for (const path of paths) {
272
+ const cred = await readOptionalFile(path);
273
+ if (!cred)
274
+ continue;
275
+ for (const entry of parseGitCredentials(cred, host)) {
276
+ scored.push({ entry, score: scoreEntryPath(entry.path, repoPath), order: order++ });
181
277
  }
182
278
  }
279
+ // Sort by score desc; stable within same score (preserve file/discovery order).
280
+ scored.sort((a, b) => b.score - a.score || a.order - b.order);
281
+ for (const { entry } of scored) {
282
+ const candidate = candidateFromEntry(entry);
283
+ if (candidate)
284
+ candidates.push(candidate);
285
+ }
183
286
  }
184
- if (!token)
185
- token = env.GITEA_TOKEN;
186
287
  return {
187
288
  baseUrl,
188
- token,
189
289
  defaultOwner: env.GITEA_DEFAULT_OWNER ?? selected?.owner,
190
290
  defaultRepo: env.GITEA_DEFAULT_REPO ?? selected?.repo,
191
291
  remote: selected?.remote,
192
- source: envBaseUrl ? "env" : "git",
292
+ candidates,
193
293
  };
194
294
  }
195
295
  //# sourceMappingURL=git-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-config.js","sourceRoot":"","sources":["../src/git-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git-config.js","sourceRoot":"","sources":["../src/git-config.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,29 @@
1
+ import { type CandidateCredential, type CandidateSummary } from "./credentials.js";
1
2
  export interface GiteaConfig {
2
3
  baseUrl: string;
3
- /** Omitted when no token source resolves; requests are then anonymous and a Skill guides the user. */
4
+ /**
5
+ * Legacy single-token mode. When `candidates` is omitted, this is wrapped
6
+ * as a one-element candidate list with the `token` scheme (preserving the
7
+ * pre-multi-credential behavior exactly).
8
+ */
4
9
  token?: string;
10
+ /**
11
+ * Credential candidates in priority order. When provided, enables the
12
+ * fault-tolerant auth state machine: each candidate × scheme is tried in
13
+ * order until one succeeds, with 401/403 advancing to the next attempt.
14
+ */
15
+ candidates?: CandidateCredential[];
16
+ }
17
+ /**
18
+ * HTTP error from the Gitea API. Carries `status` as a structured field so
19
+ * callers (the retry loop, tests) can branch on it without parsing the
20
+ * message string (AGENTS.md §2.3 forbids substring-based control flow).
21
+ */
22
+ export declare class GiteaApiError extends Error {
23
+ readonly status: number;
24
+ readonly statusText: string;
25
+ readonly body: string;
26
+ constructor(status: number, statusText: string, body: string);
5
27
  }
6
28
  export interface Issue {
7
29
  id: number;
@@ -141,8 +163,38 @@ export interface UpdateMilestoneParams {
141
163
  }
142
164
  export declare class GiteaClient {
143
165
  private baseUrl;
144
- private token?;
166
+ private candidates;
145
167
  constructor(config: GiteaConfig);
168
+ /**
169
+ * Snapshot of the credential state machine — for the `gitea_status` tool.
170
+ * Secrets are never included; only `secretPresent: boolean` and a masked
171
+ * `username`. See `summarizeCandidates` in `credentials.ts`.
172
+ */
173
+ getCredentialStatus(): {
174
+ candidates: CandidateSummary[];
175
+ activeIndex: number | null;
176
+ totalCandidates: number;
177
+ };
178
+ /**
179
+ * Single HTTP call. Throws `GiteaApiError` on non-2xx so the retry loop can
180
+ * branch on `status` (never on the message string). The `authHeader` is
181
+ * pre-built by the caller from the active candidate + scheme.
182
+ */
183
+ private doRequest;
184
+ /**
185
+ * Auth-aware request entry point. Three modes:
186
+ *
187
+ * 1. Active candidate exists (a prior attempt succeeded): reuse its locked
188
+ * scheme directly, no iteration.
189
+ * 2. No candidates at all: anonymous request (no Authorization header).
190
+ * 3. Otherwise: iterate (candidate, scheme) pairs in priority order, trying
191
+ * each until one succeeds. On 401/403 the current attempt is marked
192
+ * failed and the next is tried; non-auth errors propagate immediately
193
+ * (we do NOT mask 5xx / network errors as auth failures). When every
194
+ * candidate × scheme is exhausted, the most recent `GiteaApiError` is
195
+ * re-thrown so the caller sees the underlying status/body; the
196
+ * `gitea_status` tool surfaces the full attempt history.
197
+ */
146
198
  private request;
147
199
  listIssues(params: ListIssuesParams): Promise<Issue[]>;
148
200
  getIssue(owner: string, repo: string, index: number): Promise<Issue>;