@timmo001/effect-gh 0.1.0-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.
@@ -0,0 +1,138 @@
1
+ import { Effect, Schema, Stream } from "effect";
2
+ import { Gh } from "./gh.js";
3
+ const positiveInteger = Schema.Int.check(Schema.isGreaterThan(0));
4
+ const repository = Schema.String.check(Schema.isPattern(/^(?:[a-zA-Z0-9.-]+(?::[0-9]+)?\/)?[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/));
5
+ export const Run = Schema.Struct({
6
+ databaseId: positiveInteger,
7
+ attempt: positiveInteger,
8
+ headBranch: Schema.NullOr(Schema.String),
9
+ headSha: Schema.String,
10
+ status: Schema.String,
11
+ conclusion: Schema.NullOr(Schema.String),
12
+ url: Schema.String,
13
+ workflowName: Schema.NullOr(Schema.String),
14
+ });
15
+ const fields = Object.keys(Run.fields).join(",");
16
+ const listOptions = Schema.Struct({
17
+ repo: repository,
18
+ limit: Schema.optionalKey(positiveInteger),
19
+ branch: Schema.optionalKey(Schema.NonEmptyString),
20
+ commit: Schema.optionalKey(Schema.NonEmptyString),
21
+ workflow: Schema.optionalKey(Schema.NonEmptyString),
22
+ status: Schema.optionalKey(Schema.Literals([
23
+ "queued",
24
+ "completed",
25
+ "in_progress",
26
+ "requested",
27
+ "waiting",
28
+ "pending",
29
+ "action_required",
30
+ "cancelled",
31
+ "failure",
32
+ "neutral",
33
+ "skipped",
34
+ "stale",
35
+ "startup_failure",
36
+ "success",
37
+ "timed_out",
38
+ ])),
39
+ });
40
+ const viewOptions = Schema.Struct({
41
+ repo: repository,
42
+ runId: positiveInteger,
43
+ attempt: Schema.optionalKey(positiveInteger),
44
+ });
45
+ const logsOptions = Schema.Struct({
46
+ ...viewOptions.fields,
47
+ failedOnly: Schema.optionalKey(Schema.Boolean),
48
+ job: Schema.optionalKey(positiveInteger),
49
+ });
50
+ const watchOptions = Schema.Struct({
51
+ repo: repository,
52
+ runId: positiveInteger,
53
+ interval: Schema.optionalKey(positiveInteger),
54
+ });
55
+ export class InvalidOptions extends Schema.TaggedError()("WorkflowInvalidOptions", { cause: Schema.Defect() }) {
56
+ }
57
+ const decodeOptions = Effect.fn("Workflow.decodeOptions")(function* (schema, input) {
58
+ return yield* Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => new InvalidOptions({ cause })));
59
+ });
60
+ /** Fetch at most `limit` recent runs (default 20), without unbounded pagination. */
61
+ export const list = Effect.fn("Workflow.list")(function* (input, options) {
62
+ const query = yield* decodeOptions(listOptions, input);
63
+ const gh = yield* Gh;
64
+ const args = [
65
+ "run",
66
+ "list",
67
+ "--repo",
68
+ query.repo,
69
+ "--limit",
70
+ String(query.limit ?? 20),
71
+ "--json",
72
+ fields,
73
+ ];
74
+ if (query.branch !== undefined)
75
+ args.push("--branch", query.branch);
76
+ if (query.commit !== undefined)
77
+ args.push("--commit", query.commit);
78
+ if (query.workflow !== undefined)
79
+ args.push("--workflow", query.workflow);
80
+ if (query.status !== undefined)
81
+ args.push("--status", query.status);
82
+ return yield* gh.json(args, Schema.Array(Run), options);
83
+ });
84
+ /** View the latest attempt unless an explicit attempt is supplied. */
85
+ export const view = Effect.fn("Workflow.view")(function* (input, options) {
86
+ const query = yield* decodeOptions(viewOptions, input);
87
+ const gh = yield* Gh;
88
+ const args = [
89
+ "run",
90
+ "view",
91
+ String(query.runId),
92
+ "--repo",
93
+ query.repo,
94
+ "--json",
95
+ fields,
96
+ ];
97
+ if (query.attempt !== undefined)
98
+ args.push("--attempt", String(query.attempt));
99
+ return yield* gh.json(args, Run, options);
100
+ });
101
+ /** Fetch full logs or failed steps, optionally for one attempt and job. */
102
+ export const logs = Effect.fn("Workflow.logs")(function* (input, options) {
103
+ const query = yield* decodeOptions(logsOptions, input);
104
+ const gh = yield* Gh;
105
+ const args = [
106
+ "run",
107
+ "view",
108
+ String(query.runId),
109
+ "--repo",
110
+ query.repo,
111
+ query.failedOnly ? "--log-failed" : "--log",
112
+ ];
113
+ if (query.attempt !== undefined)
114
+ args.push("--attempt", String(query.attempt));
115
+ if (query.job !== undefined)
116
+ args.push("--job", String(query.job));
117
+ return (yield* gh.execute(args, options)).stdout;
118
+ });
119
+ /**
120
+ * Stream native CLI progress, failing if the run fails. Requires gh with
121
+ * `--compact` support; gh watch cannot authenticate with fine-grained PATs.
122
+ * Inherits the core timeout; pass `{ timeout: null }` to disable it.
123
+ */
124
+ export const watch = (input, options) => Stream.unwrap(Effect.gen(function* () {
125
+ const query = yield* decodeOptions(watchOptions, input);
126
+ const gh = yield* Gh;
127
+ return gh.stream([
128
+ "run",
129
+ "watch",
130
+ String(query.runId),
131
+ "--repo",
132
+ query.repo,
133
+ "--compact",
134
+ "--exit-status",
135
+ "--interval",
136
+ String(query.interval ?? 3),
137
+ ], options);
138
+ }));
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@timmo001/effect-gh",
3
+ "version": "0.1.0-0",
4
+ "description": "An Effect v4 SDK for the GitHub CLI",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "bun": "./src/index.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "src",
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "sideEffects": false,
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "provenance": false
27
+ },
28
+ "packageManager": "bun@1.4.2",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/timmo001/effect-gh.git"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "check": "bun run lint && bun run typecheck && bun run format:check && bun run test",
36
+ "lint": "oxlint .",
37
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
38
+ "test": "bun test",
39
+ "format": "prettier --write .",
40
+ "format:check": "prettier --check .",
41
+ "prepare": "effect-tsgo patch",
42
+ "prepack": "bun run build"
43
+ },
44
+ "peerDependencies": {
45
+ "effect": "4.0.0-rc.117"
46
+ },
47
+ "devDependencies": {
48
+ "@effect/platform-node": "4.0.0-rc.117",
49
+ "@effect/tsgo": "0.46.1",
50
+ "@oxlint/plugins": "1.85.0",
51
+ "@timmo001/oxlint-rules": "0.3.6",
52
+ "@types/bun": "1.4.2",
53
+ "effect": "4.0.0-rc.117",
54
+ "oxlint": "1.85.0",
55
+ "oxlint-tsgolint": "7.0.2003",
56
+ "prettier": "3.9.9",
57
+ "typescript": "7.0.2"
58
+ },
59
+ "trustedDependencies": [
60
+ "@effect/tsgo"
61
+ ]
62
+ }
package/src/api.ts ADDED
@@ -0,0 +1,165 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { GhDecodeError, type GhError } from "./errors.js";
3
+ import { Gh, type GhOptions, type GhOutput } from "./gh.js";
4
+
5
+ export const Method = Schema.Literals([
6
+ "GET",
7
+ "HEAD",
8
+ "POST",
9
+ "PUT",
10
+ "PATCH",
11
+ "DELETE",
12
+ "OPTIONS",
13
+ "TRACE",
14
+ "CONNECT",
15
+ ]);
16
+
17
+ export type Method = typeof Method.Type;
18
+
19
+ const queryValue = Schema.Union([Schema.String, Schema.Finite, Schema.Boolean]);
20
+
21
+ const requestSchema = Schema.Struct({
22
+ endpoint: Schema.NonEmptyString,
23
+ method: Method,
24
+ hostname: Schema.optionalKey(Schema.NonEmptyString),
25
+ query: Schema.optionalKey(
26
+ Schema.Record(
27
+ Schema.String,
28
+ Schema.Union([queryValue, Schema.Array(queryValue)]),
29
+ ),
30
+ ),
31
+ headers: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
32
+ body: Schema.optionalKey(Schema.Json),
33
+ });
34
+
35
+ export interface Request extends Schema.Schema.Type<typeof requestSchema> {
36
+ /** Gh overrides. Stdin is owned by body; bodyless calls clear any layer stdin. */
37
+ readonly options?: Omit<GhOptions, "stdin">;
38
+ }
39
+
40
+ export interface PageRequest extends Omit<Request, "method" | "body"> {
41
+ readonly method: "GET";
42
+ readonly body?: never;
43
+ }
44
+
45
+ const pageRequestSchema = Schema.Struct({
46
+ ...requestSchema.fields,
47
+ endpoint: Schema.NonEmptyString.check(
48
+ Schema.isPattern(/^(?!\/?graphql(?:[?#]|$))/),
49
+ ),
50
+ method: Schema.Literal("GET"),
51
+ body: Schema.optionalKey(Schema.Never),
52
+ });
53
+
54
+ const prepare = Effect.fn("Api.prepare")(function* (request: Request) {
55
+ const input = yield* Schema.decodeEffect(requestSchema)(request).pipe(
56
+ Effect.mapError((cause) => new GhDecodeError({ cause })),
57
+ );
58
+
59
+ const endpoint = yield* Effect.try({
60
+ try: () => {
61
+ const query: Array<string> = [];
62
+
63
+ for (const [key, value] of Object.entries(input.query ?? {})) {
64
+ for (const item of Array.isArray(value) ? value : [value]) {
65
+ query.push(
66
+ `${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`,
67
+ );
68
+ }
69
+ }
70
+
71
+ if (query.length === 0) return input.endpoint;
72
+ const fragmentIndex = input.endpoint.indexOf("#");
73
+
74
+ const path =
75
+ fragmentIndex === -1
76
+ ? input.endpoint
77
+ : input.endpoint.slice(0, fragmentIndex);
78
+
79
+ const fragment =
80
+ fragmentIndex === -1 ? "" : input.endpoint.slice(fragmentIndex);
81
+
82
+ const separator = !path.includes("?")
83
+ ? "?"
84
+ : path.endsWith("?") || path.endsWith("&")
85
+ ? ""
86
+ : "&";
87
+
88
+ return `${path}${separator}${query.join("&")}${fragment}`;
89
+ },
90
+ catch: (cause) => new GhDecodeError({ cause }),
91
+ });
92
+
93
+ const args = ["api", "--method", input.method];
94
+
95
+ if (input.hostname !== undefined) args.push(`--hostname=${input.hostname}`);
96
+
97
+ for (const [name, value] of Object.entries(input.headers ?? {})) {
98
+ args.push(`--header=${name}: ${value}`);
99
+ }
100
+
101
+ let stdin: string | undefined;
102
+
103
+ if (input.body !== undefined) {
104
+ stdin = yield* Schema.encodeEffect(Schema.fromJsonString(Schema.Json))(
105
+ input.body,
106
+ ).pipe(Effect.mapError((cause) => new GhDecodeError({ cause })));
107
+ args.push("--input", "-");
108
+ }
109
+
110
+ return { args, endpoint, options: { ...request.options, stdin } };
111
+ });
112
+
113
+ /** Returns stdout, stderr and the CLI exit code, including empty or non-JSON responses. Never retries. */
114
+ export const raw = Effect.fn("Api.raw")(function* (
115
+ request: Request,
116
+ ): Effect.fn.Return<GhOutput, GhError, Gh> {
117
+ const command = yield* prepare(request);
118
+ const gh = yield* Gh;
119
+
120
+ return yield* gh.execute(
121
+ [...command.args, "--", command.endpoint],
122
+ command.options,
123
+ );
124
+ });
125
+
126
+ /** Decodes one JSON response. Encoding and decoding failures are GhDecodeError; requests never retry. */
127
+ export const json = Effect.fn("Api.json")(function* <
128
+ S extends Schema.Constraint,
129
+ >(
130
+ request: Request,
131
+ schema: S,
132
+ ): Effect.fn.Return<S["Type"], GhError, Gh | S["DecodingServices"]> {
133
+ const command = yield* prepare(request);
134
+ const gh = yield* Gh;
135
+
136
+ return yield* gh.json(
137
+ [...command.args, "--", command.endpoint],
138
+ schema,
139
+ command.options,
140
+ );
141
+ });
142
+
143
+ /** Fetches REST GET pages with --paginate --slurp. The schema describes each page, preserving envelopes. Never retries. */
144
+ export const pages = Effect.fn("Api.pages")(function* <
145
+ S extends Schema.Constraint,
146
+ >(
147
+ request: PageRequest,
148
+ schema: S,
149
+ ): Effect.fn.Return<
150
+ ReadonlyArray<S["Type"]>,
151
+ GhError,
152
+ Gh | S["DecodingServices"]
153
+ > {
154
+ yield* Schema.decodeEffect(pageRequestSchema)(request).pipe(
155
+ Effect.mapError((cause) => new GhDecodeError({ cause })),
156
+ );
157
+ const command = yield* prepare(request);
158
+ const gh = yield* Gh;
159
+
160
+ return yield* gh.json(
161
+ [...command.args, "--paginate", "--slurp", "--", command.endpoint],
162
+ Schema.Array(schema),
163
+ command.options,
164
+ );
165
+ });
package/src/errors.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { Schema } from "effect";
2
+
3
+ export class GhCommandError extends Schema.TaggedError<GhCommandError>()(
4
+ "GhCommandError",
5
+ {
6
+ executable: Schema.String,
7
+ exitCode: Schema.Int,
8
+ stderr: Schema.String,
9
+ stderrTruncated: Schema.Boolean,
10
+ },
11
+ ) {}
12
+
13
+ export class GhPlatformError extends Schema.TaggedError<GhPlatformError>()(
14
+ "GhPlatformError",
15
+ { executable: Schema.String, cause: Schema.Defect() },
16
+ ) {}
17
+
18
+ export class GhTimeoutError extends Schema.TaggedError<GhTimeoutError>()(
19
+ "GhTimeoutError",
20
+ { executable: Schema.String, timeoutMs: Schema.Finite },
21
+ ) {}
22
+
23
+ export class GhDecodeError extends Schema.TaggedError<GhDecodeError>()(
24
+ "GhDecodeError",
25
+ { cause: Schema.Defect() },
26
+ ) {}
27
+
28
+ export type GhError =
29
+ GhCommandError | GhPlatformError | GhTimeoutError | GhDecodeError;
package/src/gh.ts ADDED
@@ -0,0 +1,236 @@
1
+ import {
2
+ Context,
3
+ Duration,
4
+ Effect,
5
+ Layer,
6
+ Match,
7
+ Predicate,
8
+ Schema,
9
+ Stream,
10
+ } from "effect";
11
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
12
+ import {
13
+ GhCommandError,
14
+ GhDecodeError,
15
+ GhPlatformError,
16
+ GhTimeoutError,
17
+ type GhError,
18
+ } from "./errors.js";
19
+
20
+ export interface GhOptions {
21
+ readonly executable?: string;
22
+ readonly cwd?: string;
23
+ readonly env?: Readonly<Record<string, string | undefined>>;
24
+ /** Explicit undefined clears stdin inherited from the layer. */
25
+ readonly stdin?: string | Uint8Array | undefined;
26
+ /** Total subprocess duration, including output draining. Null disables a layer timeout. */
27
+ readonly timeout?: Duration.Input | null;
28
+ }
29
+
30
+ export const GhOutput = Schema.Struct({
31
+ stdout: Schema.String,
32
+ stderr: Schema.String,
33
+ exitCode: Schema.Int,
34
+ });
35
+
36
+ export interface GhOutput extends Schema.Schema.Type<typeof GhOutput> {}
37
+
38
+ export const GhChunk = Schema.TaggedUnion({
39
+ Stdout: { text: Schema.String },
40
+ Stderr: { text: Schema.String },
41
+ });
42
+
43
+ export type GhChunk = typeof GhChunk.Type;
44
+
45
+ export interface Interface {
46
+ readonly execute: (
47
+ args: ReadonlyArray<string>,
48
+ options?: GhOptions,
49
+ ) => Effect.Effect<GhOutput, GhError>;
50
+ readonly json: <S extends Schema.Constraint>(
51
+ args: ReadonlyArray<string>,
52
+ schema: S,
53
+ options?: GhOptions,
54
+ ) => Effect.Effect<S["Type"], GhError, S["DecodingServices"]>;
55
+ readonly stream: (
56
+ args: ReadonlyArray<string>,
57
+ options?: GhOptions,
58
+ ) => Stream.Stream<GhChunk, GhError>;
59
+ }
60
+
61
+ export class Gh extends Context.Service<Gh, Interface>()(
62
+ "@timmo001/effect-gh/Gh",
63
+ ) {}
64
+
65
+ const stderrLimit = 65_536;
66
+
67
+ export const layer = (
68
+ defaults: GhOptions = {},
69
+ ): Layer.Layer<Gh, never, ChildProcessSpawner.ChildProcessSpawner> =>
70
+ Layer.effect(
71
+ Gh,
72
+ Effect.gen(function* () {
73
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
74
+
75
+ const open = Effect.fn("Gh.stream")(function* (
76
+ args: ReadonlyArray<string>,
77
+ options: GhOptions,
78
+ ) {
79
+ const executable = options.executable ?? "gh";
80
+
81
+ const handle = yield* spawner
82
+ .spawn(
83
+ ChildProcess.make(executable, args, {
84
+ cwd: options.cwd,
85
+ env: {
86
+ ...defaults.env,
87
+ ...options.env,
88
+ GH_PROMPT_DISABLED: "1",
89
+ GH_PAGER: "cat",
90
+ PAGER: "cat",
91
+ NO_COLOR: "1",
92
+ CLICOLOR: "0",
93
+ CLICOLOR_FORCE: "0",
94
+ GH_FORCE_TTY: undefined,
95
+ GH_SPINNER_DISABLED: "1",
96
+ },
97
+ extendEnv: true,
98
+ shell: false,
99
+ stdin: options.stdin === undefined ? "ignore" : "pipe",
100
+ stdout: "pipe",
101
+ stderr: "pipe",
102
+ forceKillAfter: "1 second",
103
+ }),
104
+ )
105
+ .pipe(
106
+ Effect.mapError(
107
+ (cause) => new GhPlatformError({ executable, cause }),
108
+ ),
109
+ );
110
+
111
+ let stderr = "";
112
+ let stderrTruncated = false;
113
+
114
+ const output = Stream.merge(
115
+ handle.stdout.pipe(
116
+ Stream.decodeText(),
117
+ Stream.map((text) => GhChunk.cases.Stdout.make({ text })),
118
+ ),
119
+ handle.stderr.pipe(
120
+ Stream.decodeText(),
121
+ Stream.map((text) => {
122
+ stderrTruncated ||= stderr.length + text.length > stderrLimit;
123
+ stderr = (stderr + text).slice(-stderrLimit);
124
+
125
+ return GhChunk.cases.Stderr.make({ text });
126
+ }),
127
+ ),
128
+ ).pipe(
129
+ Stream.mapError(
130
+ (cause) => new GhPlatformError({ executable, cause }),
131
+ ),
132
+ );
133
+
134
+ const completion = Effect.gen(function* () {
135
+ const exitCode = yield* handle.exitCode.pipe(
136
+ Effect.mapError(
137
+ (cause) => new GhPlatformError({ executable, cause }),
138
+ ),
139
+ );
140
+
141
+ if (exitCode !== 0) {
142
+ return yield* new GhCommandError({
143
+ executable,
144
+ exitCode,
145
+ stderr,
146
+ stderrTruncated,
147
+ });
148
+ }
149
+ });
150
+
151
+ const completed = output.pipe(
152
+ Stream.concat(Stream.fromEffect(completion).pipe(Stream.drain)),
153
+ );
154
+
155
+ if (options.stdin === undefined) return completed;
156
+
157
+ const input = Predicate.isString(options.stdin)
158
+ ? new TextEncoder().encode(options.stdin)
159
+ : options.stdin;
160
+
161
+ return completed.pipe(
162
+ Stream.mergeEffect(
163
+ Stream.run(Stream.succeed(input), handle.stdin).pipe(
164
+ Effect.mapError(
165
+ (cause) => new GhPlatformError({ executable, cause }),
166
+ ),
167
+ ),
168
+ ),
169
+ );
170
+ });
171
+
172
+ const stream: Interface["stream"] = (args, overrides) =>
173
+ Stream.suspend(() => {
174
+ const options = { ...defaults, ...overrides };
175
+ const output = Stream.unwrap(open(args, options));
176
+
177
+ if (options.timeout == null) return output;
178
+
179
+ if (!Duration.isFinite(Duration.fromInputUnsafe(options.timeout)))
180
+ return output;
181
+ const timeoutMs = Duration.toMillis(options.timeout);
182
+
183
+ return output.pipe(
184
+ Stream.mergeEffect(
185
+ Effect.sleep(options.timeout).pipe(
186
+ Effect.andThen(
187
+ Effect.fail(
188
+ new GhTimeoutError({
189
+ executable: options.executable ?? "gh",
190
+ timeoutMs,
191
+ }),
192
+ ),
193
+ ),
194
+ ),
195
+ ),
196
+ );
197
+ });
198
+
199
+ const execute = Effect.fn("Gh.execute")(function* (
200
+ args: ReadonlyArray<string>,
201
+ options?: GhOptions,
202
+ ) {
203
+ return yield* stream(args, options).pipe(
204
+ Stream.runFold(
205
+ () => ({ stdout: "", stderr: "", exitCode: 0 }) satisfies GhOutput,
206
+ (output, chunk) =>
207
+ Match.value(chunk).pipe(
208
+ Match.tag("Stdout", ({ text }) => ({
209
+ ...output,
210
+ stdout: output.stdout + text,
211
+ })),
212
+ Match.tag("Stderr", ({ text }) => ({
213
+ ...output,
214
+ stderr: output.stderr + text,
215
+ })),
216
+ Match.exhaustive,
217
+ ),
218
+ ),
219
+ );
220
+ });
221
+
222
+ const json = Effect.fn("Gh.json")(function* <S extends Schema.Constraint>(
223
+ args: ReadonlyArray<string>,
224
+ schema: S,
225
+ options?: GhOptions,
226
+ ) {
227
+ const output = yield* execute(args, options);
228
+
229
+ return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(
230
+ output.stdout,
231
+ ).pipe(Effect.mapError((cause) => new GhDecodeError({ cause })));
232
+ });
233
+
234
+ return Gh.of({ execute, json, stream });
235
+ }),
236
+ );
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ export {
2
+ Gh,
3
+ GhChunk,
4
+ GhOutput,
5
+ layer,
6
+ type GhOptions,
7
+ type Interface,
8
+ } from "./gh.js";
9
+
10
+ export {
11
+ GhCommandError,
12
+ GhDecodeError,
13
+ GhPlatformError,
14
+ GhTimeoutError,
15
+ type GhError,
16
+ } from "./errors.js";
17
+
18
+ export * as Api from "./api.js";
19
+
20
+ export * as Repository from "./repository.js";
21
+
22
+ export * as Issue from "./issue.js";
23
+
24
+ export * as PullRequest from "./pull-request.js";
25
+
26
+ export * as Workflow from "./workflow.js";