@danypops/tickets 0.10.4 → 0.10.5
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/README.md +2 -0
- package/package.json +4 -4
- package/src/agent-tools/tickets-vehicle.ts +2 -1
- package/src/cli/index.ts +1 -1
- package/src/github/github.ts +32 -3
- package/src/gitlab/gitlab.ts +30 -4
- package/src/index.ts +1 -1
- package/src/issue/repository.ts +25 -0
- package/src/issue/service.ts +19 -0
- package/src/issue/transport-error.ts +68 -0
- package/src/jira/jira.ts +22 -2
- package/src/rpc/ops.ts +2 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ bun run src/cli/index.ts daemon start
|
|
|
45
45
|
bun run src/cli/index.ts daemon stop # asks it to shut down gracefully
|
|
46
46
|
bun run src/cli/index.ts daemon restart
|
|
47
47
|
|
|
48
|
+
# Reports capabilities plus local read/write readiness and missing setting names.
|
|
49
|
+
# It never probes provider connectivity and never returns credential values.
|
|
48
50
|
bun run src/cli/index.ts backends
|
|
49
51
|
bun run src/cli/index.ts list -b github --status todo
|
|
50
52
|
# get includes fixVersions, issueLinks, externalLinks (Jira "Web Links", e.g.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/tickets",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Unified CLI, daemon, and TypeScript library for issue tracking across GitHub, GitLab, and Jira.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"typecheck": "tsc --noEmit"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@danypops/vehicle-core": "^0.
|
|
28
|
-
"@danypops/vehicle-server": "^0.18.
|
|
29
|
-
"@danypops/vehicle-client": "^0.7.
|
|
27
|
+
"@danypops/vehicle-core": "^0.13.0",
|
|
28
|
+
"@danypops/vehicle-server": "^0.18.4",
|
|
29
|
+
"@danypops/vehicle-client": "^0.7.1",
|
|
30
30
|
"@danypops/enigma-client": "^0.6.1",
|
|
31
31
|
"@gitbeaker/rest": "^43.8.0",
|
|
32
32
|
"commander": "^12.1.0",
|
|
@@ -63,7 +63,8 @@ function definedEntriesOnly(input: Record<string, unknown>): Record<string, unkn
|
|
|
63
63
|
const OPERATIONS: readonly OperationSpec[] = [
|
|
64
64
|
{
|
|
65
65
|
action: "backends.list",
|
|
66
|
-
description:
|
|
66
|
+
description:
|
|
67
|
+
"Lists configured backends with capabilities and local credential-safe read/write readiness. Connectivity is not probed and is always reported as not_checked.",
|
|
67
68
|
effect: "read",
|
|
68
69
|
properties: {},
|
|
69
70
|
required: [],
|
package/src/cli/index.ts
CHANGED
|
@@ -160,7 +160,7 @@ ledger
|
|
|
160
160
|
|
|
161
161
|
program
|
|
162
162
|
.command("backends")
|
|
163
|
-
.description("list configured
|
|
163
|
+
.description("list configured backends, capabilities, and local read/write readiness (no connectivity probe)")
|
|
164
164
|
.action(async () => {
|
|
165
165
|
await withClient((client) => client.call("backends.list", {}));
|
|
166
166
|
});
|
package/src/github/github.ts
CHANGED
|
@@ -23,6 +23,8 @@ import { RequestError } from "@octokit/request-error";
|
|
|
23
23
|
import { Octokit } from "octokit";
|
|
24
24
|
import { ApiError, AuthRequiredError, BackendConfigurationError, BackendConnectionError, IssueNotFoundError } from "../issue/errors.js";
|
|
25
25
|
import type { Comment, CreateInput, Issue, ListFilter, parsePriority, Status, UpdateInput } from "../issue/issue.js";
|
|
26
|
+
import type { BackendConfigurationReadiness } from "../issue/repository.js";
|
|
27
|
+
import { classifyBackendTransportFailure } from "../issue/transport-error.js";
|
|
26
28
|
|
|
27
29
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
28
30
|
|
|
@@ -104,6 +106,32 @@ export class GitHubRepository {
|
|
|
104
106
|
if (this.readOnly) throw new AuthRequiredError("github", "GITHUB_TOKEN");
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
configurationReadiness(): BackendConfigurationReadiness {
|
|
110
|
+
const repositoryMissing = this.repo ? [] : ["GITHUB_REPO"];
|
|
111
|
+
const writeMissing = [...repositoryMissing, ...(this.readOnly ? ["GITHUB_TOKEN"] : [])];
|
|
112
|
+
return {
|
|
113
|
+
backendType: "github",
|
|
114
|
+
connectivity: "not_checked",
|
|
115
|
+
read: this.repo
|
|
116
|
+
? { state: "ready", missingConfiguration: [] }
|
|
117
|
+
: {
|
|
118
|
+
state: "partial",
|
|
119
|
+
missingConfiguration: repositoryMissing,
|
|
120
|
+
recovery:
|
|
121
|
+
"Set GITHUB_REPO (or the backend's repo setting) for repository list/get/comment operations; organization search remains available.",
|
|
122
|
+
},
|
|
123
|
+
write:
|
|
124
|
+
writeMissing.length === 0
|
|
125
|
+
? { state: "ready", missingConfiguration: [] }
|
|
126
|
+
: {
|
|
127
|
+
state: "blocked",
|
|
128
|
+
missingConfiguration: writeMissing,
|
|
129
|
+
recovery:
|
|
130
|
+
"Configure the repository scope and GITHUB_TOKEN (or equivalent backend settings) before using live write operations.",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
async list(filter: ListFilter): Promise<Issue[]> {
|
|
108
136
|
const limit = filter.limit && filter.limit > 0 ? filter.limit : 50;
|
|
109
137
|
const raw = await this.call((signal) =>
|
|
@@ -216,12 +244,13 @@ export class GitHubRepository {
|
|
|
216
244
|
return res.data;
|
|
217
245
|
} catch (err) {
|
|
218
246
|
if (err instanceof BackendConfigurationError) throw err;
|
|
219
|
-
|
|
247
|
+
const transportKind = classifyBackendTransportFailure(err);
|
|
248
|
+
if (transportKind) throw new BackendConnectionError("github", transportKind, err);
|
|
249
|
+
if (err instanceof RequestError && err.response) {
|
|
220
250
|
if (err.status === 404) throw new IssueNotFoundError("github", err.request.url);
|
|
221
251
|
throw new ApiError("github", err.request.method, err.request.url, err.status, redact(err.message));
|
|
222
252
|
}
|
|
223
|
-
|
|
224
|
-
throw new BackendConnectionError("github", "unreachable", err);
|
|
253
|
+
throw err;
|
|
225
254
|
} finally {
|
|
226
255
|
clearTimeout(timer);
|
|
227
256
|
}
|
package/src/gitlab/gitlab.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { GitbeakerRequestError, type RequesterType, type ResourceOptions } from
|
|
|
16
16
|
import { Gitlab } from "@gitbeaker/rest";
|
|
17
17
|
import { ApiError, AuthRequiredError, BackendConnectionError, InvalidUrlError, IssueNotFoundError } from "../issue/errors.js";
|
|
18
18
|
import type { Comment, CreateInput, Issue, ListFilter, parsePriority, Status, UpdateInput } from "../issue/issue.js";
|
|
19
|
+
import type { BackendConfigurationReadiness } from "../issue/repository.js";
|
|
20
|
+
import { classifyBackendTransportFailure } from "../issue/transport-error.js";
|
|
19
21
|
|
|
20
22
|
export interface GitLabOptions {
|
|
21
23
|
projectId: string;
|
|
@@ -89,6 +91,27 @@ export class GitLabRepository {
|
|
|
89
91
|
if (this.readOnly) throw new AuthRequiredError("gitlab", "GITLAB_TOKEN");
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
configurationReadiness(): BackendConfigurationReadiness {
|
|
95
|
+
return {
|
|
96
|
+
backendType: "gitlab",
|
|
97
|
+
connectivity: "not_checked",
|
|
98
|
+
read: this.readOnly
|
|
99
|
+
? {
|
|
100
|
+
state: "partial",
|
|
101
|
+
missingConfiguration: ["GITLAB_TOKEN"],
|
|
102
|
+
recovery: "Configure GITLAB_TOKEN for private-project reads; unauthenticated reads remain limited to public projects.",
|
|
103
|
+
}
|
|
104
|
+
: { state: "ready", missingConfiguration: [] },
|
|
105
|
+
write: this.readOnly
|
|
106
|
+
? {
|
|
107
|
+
state: "blocked",
|
|
108
|
+
missingConfiguration: ["GITLAB_TOKEN"],
|
|
109
|
+
recovery: "Configure GITLAB_TOKEN (or delegated OAuth) before using live write operations.",
|
|
110
|
+
}
|
|
111
|
+
: { state: "ready", missingConfiguration: [] },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
async list(filter: ListFilter): Promise<Issue[]> {
|
|
93
116
|
const limit = filter.limit && filter.limit > 0 ? filter.limit : 50;
|
|
94
117
|
const raw = await this.call<GlIssue[]>(() =>
|
|
@@ -185,11 +208,14 @@ export class GitLabRepository {
|
|
|
185
208
|
if (err instanceof GitbeakerRequestError) {
|
|
186
209
|
const status = err.cause?.response?.status;
|
|
187
210
|
const url = err.cause?.request?.url ?? "";
|
|
188
|
-
if (status
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
if (status !== undefined) {
|
|
212
|
+
if (status === 404) throw new IssueNotFoundError("gitlab", url);
|
|
213
|
+
throw new ApiError("gitlab", err.cause?.request?.method ?? "?", url, status, redact(err.message));
|
|
214
|
+
}
|
|
191
215
|
}
|
|
192
|
-
|
|
216
|
+
const transportKind = classifyBackendTransportFailure(err);
|
|
217
|
+
if (transportKind) throw new BackendConnectionError("gitlab", transportKind, err);
|
|
218
|
+
throw err;
|
|
193
219
|
}
|
|
194
220
|
}
|
|
195
221
|
}
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,7 @@ export { type GitLabOptions, GitLabRepository } from "./gitlab/gitlab.js";
|
|
|
28
28
|
export * from "./issue/errors.js";
|
|
29
29
|
export * from "./issue/issue.js";
|
|
30
30
|
export * from "./issue/repository.js";
|
|
31
|
-
export { NotSupportedError, TicketService, UnknownBackendError } from "./issue/service.js";
|
|
31
|
+
export { type BackendCapabilities, NotSupportedError, TicketService, UnknownBackendError } from "./issue/service.js";
|
|
32
32
|
export { type JiraOptions, JiraRepository } from "./jira/jira.js";
|
|
33
33
|
export type { TicketOperation, TicketOpInputs, TicketOpOutputs } from "./rpc/ops.js";
|
|
34
34
|
export type { FocusStatus, TicketFocusState } from "./sqlite/focus.js";
|
package/src/issue/repository.ts
CHANGED
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
import type { Comment, CreateInput, Issue, ListFilter, UpdateInput } from "./issue.js";
|
|
6
6
|
import type { Template } from "./template.js";
|
|
7
7
|
|
|
8
|
+
export type BackendReadinessState = "ready" | "partial" | "blocked" | "unknown";
|
|
9
|
+
|
|
10
|
+
export interface BackendOperationReadiness {
|
|
11
|
+
readonly state: BackendReadinessState;
|
|
12
|
+
/** Names only; never configuration values. */
|
|
13
|
+
readonly missingConfiguration: readonly string[];
|
|
14
|
+
readonly recovery?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Local configuration assessment. Connectivity is intentionally never inferred or probed here. */
|
|
18
|
+
export interface BackendConfigurationReadiness {
|
|
19
|
+
readonly backendType: string;
|
|
20
|
+
readonly connectivity: "not_checked";
|
|
21
|
+
readonly read: BackendOperationReadiness;
|
|
22
|
+
readonly write: BackendOperationReadiness;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ConfigurationInspectable {
|
|
26
|
+
configurationReadiness(): BackendConfigurationReadiness;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function hasConfigurationReadiness(repo: IssueRepository): repo is IssueRepository & ConfigurationInspectable {
|
|
30
|
+
return typeof (repo as Partial<ConfigurationInspectable>).configurationReadiness === "function";
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
export interface IssueRepository {
|
|
9
34
|
/** Backend identifier used in refs, e.g. "github", "gitlab", "jira". */
|
|
10
35
|
readonly name: string;
|
package/src/issue/service.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
import type { Comment, CreateInput, Issue, ListFilter, UpdateInput } from "./issue.js";
|
|
8
8
|
import { parseRef } from "./issue.js";
|
|
9
9
|
import {
|
|
10
|
+
type BackendConfigurationReadiness,
|
|
10
11
|
hasBoardFilterDiscovery,
|
|
11
12
|
hasBoardQuickFilterDiscovery,
|
|
12
13
|
hasComments,
|
|
14
|
+
hasConfigurationReadiness,
|
|
13
15
|
hasFieldDiscovery,
|
|
14
16
|
hasRawQuery,
|
|
15
17
|
hasStatusDiscovery,
|
|
@@ -21,6 +23,7 @@ import type { Template } from "./template.js";
|
|
|
21
23
|
|
|
22
24
|
export interface BackendCapabilities {
|
|
23
25
|
readonly name: string;
|
|
26
|
+
readonly readiness: BackendConfigurationReadiness;
|
|
24
27
|
readonly supportsRawQuery: boolean;
|
|
25
28
|
readonly supportsFieldDiscovery: boolean;
|
|
26
29
|
readonly supportsStatusDiscovery: boolean;
|
|
@@ -54,6 +57,22 @@ export class TicketService {
|
|
|
54
57
|
backendCapabilities(): BackendCapabilities[] {
|
|
55
58
|
return Object.values(this.repos).map((repo) => ({
|
|
56
59
|
name: repo.name,
|
|
60
|
+
readiness: hasConfigurationReadiness(repo)
|
|
61
|
+
? repo.configurationReadiness()
|
|
62
|
+
: {
|
|
63
|
+
backendType: repo.name,
|
|
64
|
+
connectivity: "not_checked",
|
|
65
|
+
read: {
|
|
66
|
+
state: "unknown",
|
|
67
|
+
missingConfiguration: [],
|
|
68
|
+
recovery: "This adapter does not expose local configuration readiness.",
|
|
69
|
+
},
|
|
70
|
+
write: {
|
|
71
|
+
state: "unknown",
|
|
72
|
+
missingConfiguration: [],
|
|
73
|
+
recovery: "This adapter does not expose local configuration readiness.",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
57
76
|
supportsRawQuery: hasRawQuery(repo),
|
|
58
77
|
supportsFieldDiscovery: hasFieldDiscovery(repo),
|
|
59
78
|
supportsStatusDiscovery: hasStatusDiscovery(repo),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed transport classification shared by provider adapters.
|
|
3
|
+
*
|
|
4
|
+
* Only stable error names/codes emitted by the runtimes and HTTP clients we
|
|
5
|
+
* use are accepted. Messages are deliberately ignored: they are unstable,
|
|
6
|
+
* may contain credentials/URLs, and can make an arbitrary programming error
|
|
7
|
+
* look like a network outage.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type BackendTransportFailureKind = "unreachable" | "timeout";
|
|
11
|
+
|
|
12
|
+
const TIMEOUT_CODES = new Set([
|
|
13
|
+
"ABORT_ERR",
|
|
14
|
+
"ECONNABORTED",
|
|
15
|
+
"ERR_CANCELED",
|
|
16
|
+
"ESOCKETTIMEDOUT",
|
|
17
|
+
"ETIMEDOUT",
|
|
18
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
19
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
20
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const UNREACHABLE_CODES = new Set([
|
|
24
|
+
"CERT_HAS_EXPIRED",
|
|
25
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
26
|
+
"EAI_AGAIN",
|
|
27
|
+
"ECONNREFUSED",
|
|
28
|
+
"ECONNRESET",
|
|
29
|
+
"EHOSTUNREACH",
|
|
30
|
+
"ENETUNREACH",
|
|
31
|
+
"ENOTFOUND",
|
|
32
|
+
"EPIPE",
|
|
33
|
+
"ERR_NETWORK",
|
|
34
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
35
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
36
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
37
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
38
|
+
"UND_ERR_SOCKET",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const TIMEOUT_NAMES = new Set(["AbortError", "GitbeakerTimeoutError", "TimeoutError"]);
|
|
42
|
+
|
|
43
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
44
|
+
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Returns undefined for every unreviewed/unknown exception. */
|
|
48
|
+
export function classifyBackendTransportFailure(error: unknown): BackendTransportFailureKind | undefined {
|
|
49
|
+
let current: unknown = error;
|
|
50
|
+
const seen = new Set<unknown>();
|
|
51
|
+
|
|
52
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
53
|
+
const candidate = record(current);
|
|
54
|
+
if (!candidate || seen.has(current)) return undefined;
|
|
55
|
+
seen.add(current);
|
|
56
|
+
|
|
57
|
+
const name = typeof candidate.name === "string" ? candidate.name : undefined;
|
|
58
|
+
if (name && TIMEOUT_NAMES.has(name)) return "timeout";
|
|
59
|
+
|
|
60
|
+
const code = typeof candidate.code === "string" ? candidate.code.toUpperCase() : undefined;
|
|
61
|
+
if (code && TIMEOUT_CODES.has(code)) return "timeout";
|
|
62
|
+
if (code && UNREACHABLE_CODES.has(code)) return "unreachable";
|
|
63
|
+
|
|
64
|
+
current = candidate.cause;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
package/src/jira/jira.ts
CHANGED
|
@@ -18,8 +18,10 @@ import type { HttpException, Config as JiraClientConfig } from "jira.js";
|
|
|
18
18
|
import { AgileClient, Version2Client } from "jira.js";
|
|
19
19
|
import { ApiError, BackendConfigurationError, BackendConnectionError, IssueNotFoundError } from "../issue/errors.js";
|
|
20
20
|
import type { Comment, CreateInput, Issue, IssueLink, ListFilter, parsePriority, Status, UpdateInput } from "../issue/issue.js";
|
|
21
|
+
import type { BackendConfigurationReadiness } from "../issue/repository.js";
|
|
21
22
|
import type { Template } from "../issue/template.js";
|
|
22
23
|
import { buildTemplateBody, extractTemplateSections } from "../issue/template.js";
|
|
24
|
+
import { classifyBackendTransportFailure } from "../issue/transport-error.js";
|
|
23
25
|
import * as manifest from "./manifest.js";
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -180,6 +182,21 @@ export class JiraRepository {
|
|
|
180
182
|
this.client = new Version2Client(this.clientConfig);
|
|
181
183
|
}
|
|
182
184
|
|
|
185
|
+
configurationReadiness(): BackendConfigurationReadiness {
|
|
186
|
+
return {
|
|
187
|
+
backendType: "jira",
|
|
188
|
+
connectivity: "not_checked",
|
|
189
|
+
read: { state: "ready", missingConfiguration: [] },
|
|
190
|
+
write: this.project
|
|
191
|
+
? { state: "ready", missingConfiguration: [] }
|
|
192
|
+
: {
|
|
193
|
+
state: "partial",
|
|
194
|
+
missingConfiguration: ["JIRA_PROJECT"],
|
|
195
|
+
recovery: "Set JIRA_PROJECT (or pass input.project) for issue creation; updates and comments remain available.",
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
183
200
|
/**
|
|
184
201
|
* An explicit filter.project always wins and narrows to exactly that one
|
|
185
202
|
* project; with none given, defaults to every project this repository
|
|
@@ -219,12 +236,15 @@ export class JiraRepository {
|
|
|
219
236
|
return (await fn()) as T;
|
|
220
237
|
} catch (err) {
|
|
221
238
|
const status = (err as Partial<HttpException>)?.status;
|
|
222
|
-
|
|
239
|
+
const responseStatus = (err as { response?: { status?: unknown } })?.response?.status;
|
|
240
|
+
if (typeof status === "number" && typeof responseStatus === "number") {
|
|
223
241
|
if (status === 404) throw new IssueNotFoundError("jira", key ?? "?");
|
|
224
242
|
const message = err instanceof Error ? err.message : String(err);
|
|
225
243
|
throw new ApiError("jira", "?", key ?? "?", status, redact(message));
|
|
226
244
|
}
|
|
227
|
-
|
|
245
|
+
const transportKind = classifyBackendTransportFailure(err);
|
|
246
|
+
if (transportKind) throw new BackendConnectionError("jira", transportKind, err);
|
|
247
|
+
throw err;
|
|
228
248
|
}
|
|
229
249
|
}
|
|
230
250
|
|
package/src/rpc/ops.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* import from either side without pulling in bun:sqlite or Bun.serve.
|
|
6
6
|
*/
|
|
7
7
|
import type { Comment, CreateInput, Issue, ListFilter, UpdateInput } from "../issue/issue.js";
|
|
8
|
+
import type { BackendCapabilities } from "../issue/service.js";
|
|
8
9
|
import type { Template } from "../issue/template.js";
|
|
9
10
|
import type { TicketFocusState } from "../sqlite/focus.js";
|
|
10
11
|
import type { SavedQuery } from "../sqlite/saved-queries.js";
|
|
@@ -83,7 +84,7 @@ export interface TicketOpInputs extends Record<TicketOperation, unknown> {
|
|
|
83
84
|
export type StagePushResult = { issue: Issue } | { comment: Comment };
|
|
84
85
|
|
|
85
86
|
export interface TicketOpOutputs extends Record<TicketOperation, unknown> {
|
|
86
|
-
"backends.list": { backends:
|
|
87
|
+
"backends.list": { backends: BackendCapabilities[] };
|
|
87
88
|
"issue.list": { issues: Issue[] };
|
|
88
89
|
"issue.get": { issue: Issue };
|
|
89
90
|
"issue.create": { issue: Issue };
|