@perrylink/dsh-github 0.4.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/LICENSE +201 -0
- package/README.es.md +256 -0
- package/README.hi.md +256 -0
- package/README.md +257 -0
- package/README.pt.md +256 -0
- package/README.zh-CN.md +254 -0
- package/cordis.patch.yml +9 -0
- package/lib/approval-gate.d.ts +25 -0
- package/lib/approval-gate.d.ts.map +1 -0
- package/lib/approval-gate.js +75 -0
- package/lib/approval-gate.js.map +1 -0
- package/lib/commands.d.ts +12 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +228 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +60 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +64 -0
- package/lib/config.js.map +1 -0
- package/lib/credential.d.ts +42 -0
- package/lib/credential.d.ts.map +1 -0
- package/lib/credential.js +80 -0
- package/lib/credential.js.map +1 -0
- package/lib/git.d.ts +52 -0
- package/lib/git.d.ts.map +1 -0
- package/lib/git.js +113 -0
- package/lib/git.js.map +1 -0
- package/lib/github.d.ts +66 -0
- package/lib/github.d.ts.map +1 -0
- package/lib/github.js +153 -0
- package/lib/github.js.map +1 -0
- package/lib/index.d.ts +55 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +44 -0
- package/lib/index.js.map +1 -0
- package/lib/jobs.d.ts +34 -0
- package/lib/jobs.d.ts.map +1 -0
- package/lib/jobs.js +255 -0
- package/lib/jobs.js.map +1 -0
- package/lib/present.d.ts +254 -0
- package/lib/present.d.ts.map +1 -0
- package/lib/present.js +149 -0
- package/lib/present.js.map +1 -0
- package/lib/review.d.ts +53 -0
- package/lib/review.d.ts.map +1 -0
- package/lib/review.js +158 -0
- package/lib/review.js.map +1 -0
- package/lib/state.d.ts +96 -0
- package/lib/state.d.ts.map +1 -0
- package/lib/state.js +86 -0
- package/lib/state.js.map +1 -0
- package/lib/tools.d.ts +21 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +937 -0
- package/lib/tools.js.map +1 -0
- package/lib/types.d.ts +147 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +78 -0
package/lib/git.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Runs one git command with stdout captured; injectable for tests. */
|
|
2
|
+
export type GitRunner = (args: string[], options: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
}) => Promise<{
|
|
6
|
+
stdout: string;
|
|
7
|
+
}>;
|
|
8
|
+
/** Read-only snapshot of the git state a PR draft needs. */
|
|
9
|
+
export interface GitState {
|
|
10
|
+
/** Current branch, or null outside a repository. */
|
|
11
|
+
branch: string | null;
|
|
12
|
+
/** Whether the working tree has uncommitted changes. */
|
|
13
|
+
hasChanges: boolean;
|
|
14
|
+
/** Porcelain status lines, capped. */
|
|
15
|
+
changedFiles: string[];
|
|
16
|
+
/** Oneline log of commits ahead of the upstream (or the latest commits). */
|
|
17
|
+
commitsAhead: string[];
|
|
18
|
+
/** `origin` remote URL, when configured. */
|
|
19
|
+
remote: string | null;
|
|
20
|
+
/** `owner/repo` parsed from the origin URL, when parseable. */
|
|
21
|
+
repoFromRemote: string | null;
|
|
22
|
+
/** First read failure that stopped collection; the rest stays partial. */
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Run the real git CLI with stdout captured (never logged). */
|
|
26
|
+
export declare function runGitCli(args: string[], options: {
|
|
27
|
+
cwd: string;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
stdout: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Parse `owner/repo` out of common origin URL forms (https, ssh, git), for any
|
|
34
|
+
* GitHub host. The host must match `apiHost` (the configured REST base's
|
|
35
|
+
* hostname; `github.com` by default), so a GitHub Enterprise checkout resolves
|
|
36
|
+
* against its own API without accepting an unrelated host.
|
|
37
|
+
* @param remote - raw `git remote get-url origin` output.
|
|
38
|
+
* @param apiHost - expected hostname (lowercased), e.g. `github.com` or `git.example.com`.
|
|
39
|
+
* @returns `owner/repo`, or null when the URL is unparseable or foreign.
|
|
40
|
+
*/
|
|
41
|
+
export declare function repoFromRemoteUrl(remote: string, apiHost?: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Collect the read-only git facts a PR draft needs, tolerating partial
|
|
44
|
+
* failures: each command that fails records `error` and stops collection.
|
|
45
|
+
* @param cwd - repository working directory.
|
|
46
|
+
* @param runGit - git CLI runner; the real one by default.
|
|
47
|
+
* @param signal - cancels collection.
|
|
48
|
+
* @param apiHost - expected origin host for `owner/repo` parsing (`github.com` by default).
|
|
49
|
+
* @returns the collected snapshot.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readGitState(cwd: string, runGit?: GitRunner, signal?: AbortSignal, apiHost?: string): Promise<GitState>;
|
|
52
|
+
//# sourceMappingURL=git.d.ts.map
|
package/lib/git.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AASA,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEvH,4DAA4D;AAC5D,MAAM,WAAW,QAAQ;IACvB,oDAAoD;IACpD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,wDAAwD;IACxD,UAAU,EAAE,OAAO,CAAA;IACnB,sCAAsC;IACtC,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAKD,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAOrH;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,SAAe,GAAG,MAAM,GAAG,IAAI,CAMvF;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,SAAqB,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,SAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsD9I"}
|
package/lib/git.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only git workspace inspection for `/pr create` drafts.
|
|
3
|
+
*
|
|
4
|
+
* The plugin never commits, pushes, or rewrites git identity — dsh-git-identity
|
|
5
|
+
* and the model's own bash calls own those writes. Everything here is a read.
|
|
6
|
+
* @module dsh-github/git
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
const MAX_CHANGED_FILES = 50;
|
|
10
|
+
const MAX_COMMITS = 20;
|
|
11
|
+
/** Run the real git CLI with stdout captured (never logged). */
|
|
12
|
+
export function runGitCli(args, options) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
execFile('git', args, { cwd: options.cwd, windowsHide: true, signal: options.signal, maxBuffer: 8 * 1024 * 1024 }, (error, stdout) => {
|
|
15
|
+
if (error)
|
|
16
|
+
reject(error);
|
|
17
|
+
else
|
|
18
|
+
resolve({ stdout });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse `owner/repo` out of common origin URL forms (https, ssh, git), for any
|
|
24
|
+
* GitHub host. The host must match `apiHost` (the configured REST base's
|
|
25
|
+
* hostname; `github.com` by default), so a GitHub Enterprise checkout resolves
|
|
26
|
+
* against its own API without accepting an unrelated host.
|
|
27
|
+
* @param remote - raw `git remote get-url origin` output.
|
|
28
|
+
* @param apiHost - expected hostname (lowercased), e.g. `github.com` or `git.example.com`.
|
|
29
|
+
* @returns `owner/repo`, or null when the URL is unparseable or foreign.
|
|
30
|
+
*/
|
|
31
|
+
export function repoFromRemoteUrl(remote, apiHost = 'github.com') {
|
|
32
|
+
const trimmed = remote.trim();
|
|
33
|
+
const match = /^(?:https?:\/\/|ssh:\/\/git@|git:\/\/|git@)?([^/:]+)(?::\d+)?[:/]([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/.exec(trimmed);
|
|
34
|
+
if (match === null)
|
|
35
|
+
return null;
|
|
36
|
+
const host = (match[1] ?? '').toLowerCase();
|
|
37
|
+
return host === apiHost.toLowerCase() ? (match[2] ?? null) : null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Collect the read-only git facts a PR draft needs, tolerating partial
|
|
41
|
+
* failures: each command that fails records `error` and stops collection.
|
|
42
|
+
* @param cwd - repository working directory.
|
|
43
|
+
* @param runGit - git CLI runner; the real one by default.
|
|
44
|
+
* @param signal - cancels collection.
|
|
45
|
+
* @param apiHost - expected origin host for `owner/repo` parsing (`github.com` by default).
|
|
46
|
+
* @returns the collected snapshot.
|
|
47
|
+
*/
|
|
48
|
+
export async function readGitState(cwd, runGit = runGitCli, signal, apiHost = 'github.com') {
|
|
49
|
+
const state = {
|
|
50
|
+
branch: null,
|
|
51
|
+
hasChanges: false,
|
|
52
|
+
changedFiles: [],
|
|
53
|
+
commitsAhead: [],
|
|
54
|
+
remote: null,
|
|
55
|
+
repoFromRemote: null,
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
const { stdout } = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, signal });
|
|
59
|
+
state.branch = stdout.trim() || null;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
state.error = describeError(error);
|
|
63
|
+
return state;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const { stdout } = await runGit(['status', '--porcelain=v1'], { cwd, signal });
|
|
67
|
+
const lines = stdout.split(/\r?\n/).filter(line => line.length > 0);
|
|
68
|
+
state.changedFiles = lines.slice(0, MAX_CHANGED_FILES);
|
|
69
|
+
state.hasChanges = lines.length > 0;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
state.error = describeError(error);
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const { stdout } = await runGit(['log', '--oneline', '-n', String(MAX_COMMITS), '@{upstream}..HEAD'], { cwd, signal });
|
|
77
|
+
state.commitsAhead = stdout.split(/\r?\n/).filter(line => line.length > 0);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// No upstream configured: list the latest local commits instead.
|
|
81
|
+
try {
|
|
82
|
+
const { stdout } = await runGit(['log', '--oneline', '-n', String(MAX_COMMITS), 'HEAD'], { cwd, signal });
|
|
83
|
+
state.commitsAhead = stdout.split(/\r?\n/).filter(line => line.length > 0);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
state.error = describeError(error);
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const { stdout } = await runGit(['remote', 'get-url', 'origin'], { cwd, signal });
|
|
92
|
+
const remote = stdout.trim();
|
|
93
|
+
if (remote.length > 0) {
|
|
94
|
+
state.remote = remote;
|
|
95
|
+
state.repoFromRemote = repoFromRemoteUrl(remote, apiHost);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// No origin remote: leave remote facts absent.
|
|
100
|
+
}
|
|
101
|
+
return state;
|
|
102
|
+
}
|
|
103
|
+
function describeError(error) {
|
|
104
|
+
if (error instanceof Error) {
|
|
105
|
+
if (error.message.includes('not a git repository'))
|
|
106
|
+
return 'not a git repository';
|
|
107
|
+
if (error.message.includes('ENOENT'))
|
|
108
|
+
return 'git CLI not available';
|
|
109
|
+
return error.message;
|
|
110
|
+
}
|
|
111
|
+
return 'git read failed';
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=git.js.map
|
package/lib/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
package/lib/github.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal GitHub REST client: JSON requests with 429 backoff-retry and
|
|
3
|
+
* rate-limit surfacing, plus the `application/vnd.github.diff` text endpoint.
|
|
4
|
+
*
|
|
5
|
+
* The token travels only in the Authorization header of outgoing requests;
|
|
6
|
+
* request bodies, error messages, and returned values never contain it.
|
|
7
|
+
* @module dsh-github/github
|
|
8
|
+
*/
|
|
9
|
+
import type { Config } from './config.ts';
|
|
10
|
+
/** GitHub rate-limit facts, surfaced to the model on every read result. */
|
|
11
|
+
export interface RateLimitInfo {
|
|
12
|
+
remaining: number | null;
|
|
13
|
+
/** Epoch ms when the window resets; null when the response omitted it. */
|
|
14
|
+
resetAt: number | null;
|
|
15
|
+
}
|
|
16
|
+
/** Domain failure of one GitHub API request, token-free by construction. */
|
|
17
|
+
export declare class GithubError extends Error {
|
|
18
|
+
readonly status: number;
|
|
19
|
+
readonly rateLimit: RateLimitInfo;
|
|
20
|
+
constructor(status: number, message: string, rateLimit: RateLimitInfo);
|
|
21
|
+
}
|
|
22
|
+
/** Successful JSON response with its rate-limit facts. */
|
|
23
|
+
export interface GithubJsonResponse<T> {
|
|
24
|
+
status: number;
|
|
25
|
+
data: T;
|
|
26
|
+
rateLimit: RateLimitInfo;
|
|
27
|
+
}
|
|
28
|
+
export interface GithubRequestOptions {
|
|
29
|
+
/** JSON body for writes. */
|
|
30
|
+
body?: unknown;
|
|
31
|
+
/** Override the Accept header (e.g. the diff media type). */
|
|
32
|
+
accept?: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
interface ClientOptions {
|
|
36
|
+
apiBaseUrl: string;
|
|
37
|
+
maxRetries: number;
|
|
38
|
+
retryBaseMs: number;
|
|
39
|
+
retryMaxWaitMs: number;
|
|
40
|
+
fetchImpl: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Authenticated GitHub REST client with 429 retry.
|
|
44
|
+
*
|
|
45
|
+
* Retries honor Retry-After / x-ratelimit-reset and the caller's signal;
|
|
46
|
+
* non-2xx responses surface as {@link GithubError} carrying the status and
|
|
47
|
+
* current rate-limit facts. Errors never contain the token.
|
|
48
|
+
*/
|
|
49
|
+
export declare class GithubClient {
|
|
50
|
+
private readonly token;
|
|
51
|
+
private readonly options;
|
|
52
|
+
constructor(token: string, options: ClientOptions);
|
|
53
|
+
/** One JSON request with retry; `GithubError` on any non-2xx status. */
|
|
54
|
+
requestJson<T>(method: string, path: string, options?: GithubRequestOptions): Promise<GithubJsonResponse<T>>;
|
|
55
|
+
/** One text request (the diff media type) with retry. */
|
|
56
|
+
requestText(method: string, path: string, options?: GithubRequestOptions): Promise<{
|
|
57
|
+
text: string;
|
|
58
|
+
rateLimit: RateLimitInfo;
|
|
59
|
+
}>;
|
|
60
|
+
/** Fetch with Authorization, 429 retry, and signal handling. */
|
|
61
|
+
private request;
|
|
62
|
+
}
|
|
63
|
+
/** Build a client from config; resolves the base URL once. */
|
|
64
|
+
export declare function clientOptionsFromConfig(config: Config, fetchImpl?: typeof fetch): ClientOptions;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,4EAA4E;AAC5E,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;gBAErB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa;CAMtE;AAED,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,CAAC,CAAA;IACP,SAAS,EAAE,aAAa,CAAA;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,4BAA4B;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,UAAU,aAAa;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,OAAO,KAAK,CAAA;CACxB;AAqDD;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;gBAE3B,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;IAKjD,wEAAwE;IAClE,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAiBtH,yDAAyD;IACnD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,aAAa,CAAA;KAAE,CAAC;IAgBxI,gEAAgE;YAClD,OAAO;CA0BtB;AAED,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAAG,aAAa,CAQ/F"}
|
package/lib/github.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Domain failure of one GitHub API request, token-free by construction. */
|
|
2
|
+
export class GithubError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
rateLimit;
|
|
5
|
+
constructor(status, message, rateLimit) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'GithubError';
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.rateLimit = rateLimit;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const JSON_ACCEPT = 'application/vnd.github+json';
|
|
13
|
+
const API_VERSION = '2022-11-28';
|
|
14
|
+
const USER_AGENT = 'dsh-github';
|
|
15
|
+
function rateLimitFromHeaders(headers) {
|
|
16
|
+
const remaining = headers.get('x-ratelimit-remaining');
|
|
17
|
+
const reset = headers.get('x-ratelimit-reset');
|
|
18
|
+
const remainingNum = remaining === null ? null : Number(remaining);
|
|
19
|
+
const resetNum = reset === null ? null : Number(reset);
|
|
20
|
+
return {
|
|
21
|
+
remaining: remainingNum !== null && Number.isFinite(remainingNum) ? remainingNum : null,
|
|
22
|
+
resetAt: resetNum !== null && Number.isFinite(resetNum) ? resetNum * 1000 : null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Retry delay for a 429 response: Retry-After seconds or reset-epoch, else backoff. */
|
|
26
|
+
function retryDelayMs(headers, attempt, options) {
|
|
27
|
+
const retryAfter = headers.get('retry-after');
|
|
28
|
+
if (retryAfter !== null) {
|
|
29
|
+
const seconds = Number(retryAfter);
|
|
30
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
31
|
+
return Math.min(seconds * 1000, options.retryMaxWaitMs);
|
|
32
|
+
}
|
|
33
|
+
const reset = headers.get('x-ratelimit-reset');
|
|
34
|
+
if (reset !== null) {
|
|
35
|
+
const until = Number(reset) * 1000 - Date.now();
|
|
36
|
+
if (Number.isFinite(until) && until > 0)
|
|
37
|
+
return Math.min(until, options.retryMaxWaitMs);
|
|
38
|
+
}
|
|
39
|
+
return Math.min(options.retryBaseMs * 2 ** attempt, options.retryMaxWaitMs);
|
|
40
|
+
}
|
|
41
|
+
/** Wait while still honoring the caller's abort signal. */
|
|
42
|
+
function sleep(ms, signal) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const aborter = signal ?? null;
|
|
45
|
+
if (aborter?.aborted === true) {
|
|
46
|
+
reject(aborter.reason instanceof Error ? aborter.reason : new Error('aborted'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const timer = setTimeout(done, ms);
|
|
50
|
+
aborter?.addEventListener('abort', onAbort, { once: true });
|
|
51
|
+
function done() {
|
|
52
|
+
aborter?.removeEventListener('abort', onAbort);
|
|
53
|
+
resolve();
|
|
54
|
+
}
|
|
55
|
+
function onAbort() {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
reject(aborter?.reason instanceof Error ? aborter.reason : new Error('aborted'));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Authenticated GitHub REST client with 429 retry.
|
|
63
|
+
*
|
|
64
|
+
* Retries honor Retry-After / x-ratelimit-reset and the caller's signal;
|
|
65
|
+
* non-2xx responses surface as {@link GithubError} carrying the status and
|
|
66
|
+
* current rate-limit facts. Errors never contain the token.
|
|
67
|
+
*/
|
|
68
|
+
export class GithubClient {
|
|
69
|
+
token;
|
|
70
|
+
options;
|
|
71
|
+
constructor(token, options) {
|
|
72
|
+
this.token = token;
|
|
73
|
+
this.options = options;
|
|
74
|
+
}
|
|
75
|
+
/** One JSON request with retry; `GithubError` on any non-2xx status. */
|
|
76
|
+
async requestJson(method, path, options = {}) {
|
|
77
|
+
const response = await this.request(method, path, { ...options, accept: options.accept ?? JSON_ACCEPT });
|
|
78
|
+
const rateLimit = rateLimitFromHeaders(response.headers);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
let message = `GitHub API ${response.status} for ${method} ${path}`;
|
|
81
|
+
try {
|
|
82
|
+
const parsed = await response.json();
|
|
83
|
+
if (typeof parsed.message === 'string' && parsed.message.length > 0)
|
|
84
|
+
message = parsed.message;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Non-JSON error body: keep the status-based message.
|
|
88
|
+
}
|
|
89
|
+
throw new GithubError(response.status, message, rateLimit);
|
|
90
|
+
}
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
return { status: response.status, data, rateLimit };
|
|
93
|
+
}
|
|
94
|
+
/** One text request (the diff media type) with retry. */
|
|
95
|
+
async requestText(method, path, options = {}) {
|
|
96
|
+
const response = await this.request(method, path, { ...options, accept: 'application/vnd.github.diff' });
|
|
97
|
+
const rateLimit = rateLimitFromHeaders(response.headers);
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
let message = `GitHub API ${response.status} for ${method} ${path}`;
|
|
100
|
+
try {
|
|
101
|
+
const parsed = await response.json();
|
|
102
|
+
if (typeof parsed.message === 'string' && parsed.message.length > 0)
|
|
103
|
+
message = parsed.message;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Non-JSON error body: keep the status-based message.
|
|
107
|
+
}
|
|
108
|
+
throw new GithubError(response.status, message, rateLimit);
|
|
109
|
+
}
|
|
110
|
+
return { text: await response.text(), rateLimit };
|
|
111
|
+
}
|
|
112
|
+
/** Fetch with Authorization, 429 retry, and signal handling. */
|
|
113
|
+
async request(method, path, options) {
|
|
114
|
+
let attempt = 0;
|
|
115
|
+
for (;;) {
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
response = await this.options.fetchImpl(new URL(path, this.options.apiBaseUrl), {
|
|
119
|
+
method,
|
|
120
|
+
headers: {
|
|
121
|
+
Authorization: `Bearer ${this.token}`,
|
|
122
|
+
Accept: options.accept ?? JSON_ACCEPT,
|
|
123
|
+
'X-GitHub-Api-Version': API_VERSION,
|
|
124
|
+
'User-Agent': USER_AGENT,
|
|
125
|
+
...options.body !== undefined ? { 'Content-Type': 'application/json' } : {},
|
|
126
|
+
},
|
|
127
|
+
...options.body !== undefined ? { body: JSON.stringify(options.body) } : {},
|
|
128
|
+
signal: options.signal,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (options.signal?.aborted)
|
|
133
|
+
throw options.signal.reason instanceof Error ? options.signal.reason : new Error('aborted');
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
if (response.status !== 429 || attempt >= this.options.maxRetries)
|
|
137
|
+
return response;
|
|
138
|
+
await sleep(retryDelayMs(response.headers, attempt, this.options), options.signal);
|
|
139
|
+
attempt += 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Build a client from config; resolves the base URL once. */
|
|
144
|
+
export function clientOptionsFromConfig(config, fetchImpl) {
|
|
145
|
+
return {
|
|
146
|
+
apiBaseUrl: config.apiBaseUrl.replace(/\/+$/, ''),
|
|
147
|
+
maxRetries: config.maxRetries,
|
|
148
|
+
retryBaseMs: config.retryBaseMs,
|
|
149
|
+
retryMaxWaitMs: config.retryMaxWaitMs,
|
|
150
|
+
fetchImpl: fetchImpl ?? globalThis.fetch.bind(globalThis),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-github: GitHub integration for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* Tools: pr_create (write), gh_review (read), review_post (write), gh_issue
|
|
5
|
+
* (read), issue_open / issue_comment / issue_close (writes), gh_search (read).
|
|
6
|
+
* Commands: /pr create, /review (start|stop|post), /issue open. Background
|
|
7
|
+
* review jobs run the deterministic analyzer by default; `reviewMode: "model"`
|
|
8
|
+
* delegates the capped diff to a one-shot subagent through the host's
|
|
9
|
+
* `subagents` seam. Every GitHub write is gated by the tools/pre-execute
|
|
10
|
+
* approval listener (default ask) and the allowedActions whitelist; the token
|
|
11
|
+
* travels only through the credentials seam / environment / gh CLI and never
|
|
12
|
+
* reaches model-visible text, session events, or logs.
|
|
13
|
+
*
|
|
14
|
+
* Model-visible ⇔ logged: the plugin appends NO custom session event types —
|
|
15
|
+
* the host refuses logs with unknown out-of-repo event types, so all content
|
|
16
|
+
* the model sees flows through the host's own logged surfaces: tool/result
|
|
17
|
+
* canonical values, agent-injected user/message notices, command/run +
|
|
18
|
+
* command/done lifecycle pairs, and the approval/asked + approval/decided
|
|
19
|
+
* audit pair. See README.md "Architecture".
|
|
20
|
+
* @module dsh-github
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
23
|
+
import { type GitRunner } from './git.ts';
|
|
24
|
+
import { type GhRunner } from './credential.ts';
|
|
25
|
+
import { Config, type Config as PluginConfig } from './config.ts';
|
|
26
|
+
export declare const name = "dsh-github";
|
|
27
|
+
/** Services this plugin requires; all exist in any profile built on dsh-base. */
|
|
28
|
+
export declare const inject: string[];
|
|
29
|
+
export { Config };
|
|
30
|
+
export type { Config as PluginConfig };
|
|
31
|
+
/** Environment-dependent runners, injectable for tests. */
|
|
32
|
+
export interface PluginDeps {
|
|
33
|
+
runGit?: GitRunner;
|
|
34
|
+
runGh?: GhRunner;
|
|
35
|
+
fetchImpl?: typeof fetch;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Apply the plugin with explicit environment runners.
|
|
39
|
+
*
|
|
40
|
+
* The production {@link apply} passes the real git/gh runners; tests inject
|
|
41
|
+
* fakes through {@link applyWithDeps} so no test touches the network or a
|
|
42
|
+
* real shell. Every registration is an effect — disposing the plugin fiber
|
|
43
|
+
* reverses all of them.
|
|
44
|
+
* @param ctx - plugin context; the injected services are ready at this point.
|
|
45
|
+
* @param config - validated Schemastery configuration (defaults applied).
|
|
46
|
+
* @param deps - optional git/gh/fetch runners.
|
|
47
|
+
*/
|
|
48
|
+
export declare function applyWithDeps(ctx: Context, config: PluginConfig, deps?: PluginDeps): void;
|
|
49
|
+
/**
|
|
50
|
+
* Apply the plugin: register tools, commands, and the write-approval gate.
|
|
51
|
+
* @param ctx - plugin context; the injected services are ready at this point.
|
|
52
|
+
* @param config - validated Schemastery configuration (defaults applied).
|
|
53
|
+
*/
|
|
54
|
+
export declare function apply(ctx: Context, config: PluginConfig): void;
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AACpD,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAQzD,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,IAAI,YAAY,EAAE,MAAM,aAAa,CAAA;AAEjE,eAAO,MAAM,IAAI,eAAe,CAAA;AAEhC,iFAAiF;AACjF,eAAO,MAAM,MAAM,UAA2D,CAAA;AAE9E,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,YAAY,EAAE,MAAM,IAAI,YAAY,EAAE,CAAA;AAEtC,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,UAAe,QActF;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,QAEvD"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createState } from "./state.js";
|
|
2
|
+
import { runGitCli } from "./git.js";
|
|
3
|
+
import { runGhCli } from "./credential.js";
|
|
4
|
+
import { prCreateTool, ghReviewTool, reviewPostTool, ghIssueTool, issueOpenTool, issueCommentTool, issueCloseTool, ghSearchTool, } from "./tools.js";
|
|
5
|
+
import { registerApprovalGate } from "./approval-gate.js";
|
|
6
|
+
import { registerCommands } from "./commands.js";
|
|
7
|
+
import { Config } from "./config.js";
|
|
8
|
+
export const name = 'dsh-github';
|
|
9
|
+
/** Services this plugin requires; all exist in any profile built on dsh-base. */
|
|
10
|
+
export const inject = ['tools', 'commands', 'jobs', 'approval', 'credentials'];
|
|
11
|
+
export { Config };
|
|
12
|
+
/**
|
|
13
|
+
* Apply the plugin with explicit environment runners.
|
|
14
|
+
*
|
|
15
|
+
* The production {@link apply} passes the real git/gh runners; tests inject
|
|
16
|
+
* fakes through {@link applyWithDeps} so no test touches the network or a
|
|
17
|
+
* real shell. Every registration is an effect — disposing the plugin fiber
|
|
18
|
+
* reverses all of them.
|
|
19
|
+
* @param ctx - plugin context; the injected services are ready at this point.
|
|
20
|
+
* @param config - validated Schemastery configuration (defaults applied).
|
|
21
|
+
* @param deps - optional git/gh/fetch runners.
|
|
22
|
+
*/
|
|
23
|
+
export function applyWithDeps(ctx, config, deps = {}) {
|
|
24
|
+
const state = createState({ credentials: ctx.credentials, subagents: ctx.get('subagents') }, config, deps.runGit ?? runGitCli, deps.runGh ?? runGhCli, deps.fetchImpl);
|
|
25
|
+
ctx.tools.register(prCreateTool(state));
|
|
26
|
+
ctx.tools.register(ghReviewTool(state));
|
|
27
|
+
ctx.tools.register(reviewPostTool(state));
|
|
28
|
+
ctx.tools.register(ghIssueTool(state));
|
|
29
|
+
ctx.tools.register(issueOpenTool(state));
|
|
30
|
+
ctx.tools.register(issueCommentTool(state));
|
|
31
|
+
ctx.tools.register(issueCloseTool(state));
|
|
32
|
+
ctx.tools.register(ghSearchTool(state));
|
|
33
|
+
ctx.effect(() => registerApprovalGate(ctx, state));
|
|
34
|
+
ctx.effect(() => registerCommands(ctx.commands, ctx.jobs, state));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Apply the plugin: register tools, commands, and the write-approval gate.
|
|
38
|
+
* @param ctx - plugin context; the injected services are ready at this point.
|
|
39
|
+
* @param config - validated Schemastery configuration (defaults applied).
|
|
40
|
+
*/
|
|
41
|
+
export function apply(ctx, config) {
|
|
42
|
+
applyWithDeps(ctx, config);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,SAAS,EAAkB,MAAM,UAAU,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAiB,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EACL,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EACtE,gBAAgB,EAAE,cAAc,EAAE,YAAY,GAC/C,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,MAAM,EAA+B,MAAM,aAAa,CAAA;AAEjE,MAAM,CAAC,MAAM,IAAI,GAAG,YAAY,CAAA;AAEhC,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,CAAC,CAAA;AAE9E,OAAO,EAAE,MAAM,EAAE,CAAA;AAUjB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY,EAAE,MAAoB,EAAE,OAAmB,EAAE;IACrF,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,WAAW,CAAiC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,IAAI,CAAC,KAAK,IAAI,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IAEtM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAA;IACzC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IACtC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;IACxC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAA;IAC3C,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAA;IACzC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;IAEvC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;IAClD,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAoB;IACtD,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AAC5B,CAAC"}
|
package/lib/jobs.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { GithubAgent, GithubJobId, JobRegistry } from './types.ts';
|
|
2
|
+
import type { GithubState } from './state.ts';
|
|
3
|
+
/** Producer kind — also the `<kind>-N` id prefix; the host treats it as opaque. */
|
|
4
|
+
export declare const REVIEW_JOB_KIND = "github-review";
|
|
5
|
+
export interface StartReviewJobInput {
|
|
6
|
+
repo: string;
|
|
7
|
+
pr: number;
|
|
8
|
+
label: string;
|
|
9
|
+
owner: GithubAgent;
|
|
10
|
+
/** Present in tests where no job registry exists; disables registration. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
/** Diff cap override for this job; defaults to the plugin config. */
|
|
13
|
+
maxDiffChars?: number;
|
|
14
|
+
/** Fetch and report CI check runs (default true). */
|
|
15
|
+
includeCi?: boolean;
|
|
16
|
+
/** Fetch and count existing review comments (default true). */
|
|
17
|
+
includeComments?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start one background review job and register its in-memory record.
|
|
21
|
+
*
|
|
22
|
+
* The token is resolved inside the run body (per-operation, per the
|
|
23
|
+
* credentials-seam contract); a missing token settles the job as `failed`
|
|
24
|
+
* with configuration guidance, never with a token value. `start` throws when
|
|
25
|
+
* no job controller serves the owner — callers surface that as a command
|
|
26
|
+
* error. With `reviewMode: "model"`, the subagent seam must be composed or
|
|
27
|
+
* this call fails loud before starting the job.
|
|
28
|
+
* @param registry - `ctx.jobs` of the hosting context.
|
|
29
|
+
* @param state - plugin state holding config, token resolution, and records.
|
|
30
|
+
* @param input - target PR, label, owning agent, and job options.
|
|
31
|
+
* @returns the registry-issued `<kind>-N` id.
|
|
32
|
+
*/
|
|
33
|
+
export declare function startReviewJob(registry: JobRegistry, state: GithubState, input: StartReviewJobInput): GithubJobId;
|
|
34
|
+
//# sourceMappingURL=jobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../src/jobs.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAc,WAAW,EAAgB,MAAM,YAAY,CAAA;AACjG,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,YAAY,CAAA;AAE9D,mFAAmF;AACnF,eAAO,MAAM,eAAe,kBAAkB,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,WAAW,CAAA;IAClB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAWD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,GAAG,WAAW,CAgCjH"}
|