@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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const ACTION_BY_TOOL = {
|
|
2
|
+
pr_create: 'pr.create',
|
|
3
|
+
review_post: 'review.post',
|
|
4
|
+
issue_open: 'issue.create',
|
|
5
|
+
issue_comment: 'issue.comment',
|
|
6
|
+
issue_close: 'issue.close',
|
|
7
|
+
};
|
|
8
|
+
/** Tools the gate intercepts; everything else delegates via next(). */
|
|
9
|
+
const WRITE_TOOLS = new Set(Object.keys(ACTION_BY_TOOL));
|
|
10
|
+
/** Read the frozen tool arguments as a plain object, tolerant of bad input. */
|
|
11
|
+
function argumentsAsRecord(exec) {
|
|
12
|
+
return typeof exec.arguments === 'object' && exec.arguments !== null ? exec.arguments : {};
|
|
13
|
+
}
|
|
14
|
+
/** `"title" (body N chars)`-style preview of a titled write. */
|
|
15
|
+
function titledPreview(args) {
|
|
16
|
+
const title = typeof args.title === 'string' ? args.title : '(untitled)';
|
|
17
|
+
const body = typeof args.body === 'string' ? args.body.trim() : '';
|
|
18
|
+
return body.length === 0 ? `"${title}"` : `"${title}" (body ${body.length} chars)`;
|
|
19
|
+
}
|
|
20
|
+
/** First line of a string, elided to `max` characters. */
|
|
21
|
+
function firstLine(value, max) {
|
|
22
|
+
const line = value.split('\n')[0] ?? '';
|
|
23
|
+
return line.length > max ? `${line.slice(0, max)}…` : line;
|
|
24
|
+
}
|
|
25
|
+
/** Human-readable reason for the approval prompt of one write call. */
|
|
26
|
+
function askReason(toolName, args, state) {
|
|
27
|
+
if (toolName === 'pr_create') {
|
|
28
|
+
return `create GitHub pull request ${titledPreview(args)}`;
|
|
29
|
+
}
|
|
30
|
+
if (toolName === 'review_post') {
|
|
31
|
+
if (typeof args.jobId !== 'string')
|
|
32
|
+
return 'post GitHub review comments';
|
|
33
|
+
const record = state.records.get(args.jobId);
|
|
34
|
+
const target = record === undefined || record.report === null
|
|
35
|
+
? `job ${args.jobId}`
|
|
36
|
+
: `PR #${record.pr} (${record.report.findings.length} finding(s))`;
|
|
37
|
+
const mode = args.mode === 'inline' ? 'inline' : 'summary';
|
|
38
|
+
const override = typeof args.body === 'string' && args.body.trim().length > 0
|
|
39
|
+
? `; body override: ${firstLine(args.body.trim(), 80)}`
|
|
40
|
+
: '';
|
|
41
|
+
return `post GitHub review comments for ${target} (${mode})${override}`;
|
|
42
|
+
}
|
|
43
|
+
if (toolName === 'issue_open') {
|
|
44
|
+
return `create GitHub issue ${titledPreview(args)}`;
|
|
45
|
+
}
|
|
46
|
+
if (toolName === 'issue_comment') {
|
|
47
|
+
const number = typeof args.issueNumber === 'number' ? ` #${args.issueNumber}` : '';
|
|
48
|
+
const body = typeof args.body === 'string' ? args.body.trim() : '';
|
|
49
|
+
return `comment on GitHub issue${number}${body.length === 0 ? '' : ` (body ${body.length} chars)`}`;
|
|
50
|
+
}
|
|
51
|
+
if (toolName === 'issue_close') {
|
|
52
|
+
const number = typeof args.issueNumber === 'number' ? ` #${args.issueNumber}` : '';
|
|
53
|
+
return `close GitHub issue${number}`;
|
|
54
|
+
}
|
|
55
|
+
return 'perform a GitHub write action';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Register the approval gate. Registration is an effect: disposing the plugin
|
|
59
|
+
* fiber removes the listener.
|
|
60
|
+
* @param ctx - plugin context; the listener lives on the shared tools pipeline.
|
|
61
|
+
* @param state - plugin state used to enrich approval reasons.
|
|
62
|
+
* @returns the effect disposer.
|
|
63
|
+
*/
|
|
64
|
+
export function registerApprovalGate(ctx, state) {
|
|
65
|
+
return ctx.on('tools/pre-execute', async (exec, next) => {
|
|
66
|
+
if (!WRITE_TOOLS.has(exec.name))
|
|
67
|
+
return next();
|
|
68
|
+
const action = ACTION_BY_TOOL[exec.name];
|
|
69
|
+
if (action === undefined || !state.config.allowedActions.includes(action)) {
|
|
70
|
+
return { kind: 'deny', reason: `dsh-github: action "${action ?? exec.name}" is not in allowedActions` };
|
|
71
|
+
}
|
|
72
|
+
return { kind: 'ask', reason: `dsh-github: ${askReason(exec.name, argumentsAsRecord(exec), state)}` };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=approval-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-gate.js","sourceRoot":"","sources":["../src/approval-gate.ts"],"names":[],"mappings":"AAmBA,MAAM,cAAc,GAAiC;IACnD,SAAS,EAAE,WAAW;IACtB,WAAW,EAAE,aAAa;IAC1B,UAAU,EAAE,cAAc;IAC1B,aAAa,EAAE,eAAe;IAC9B,WAAW,EAAE,aAAa;CAC3B,CAAA;AAED,uEAAuE;AACvE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAA;AAExD,+EAA+E;AAC/E,SAAS,iBAAiB,CAAC,IAAmB;IAC5C,OAAO,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAoC,CAAC,CAAC,CAAC,EAAE,CAAA;AACvH,CAAC;AAED,gEAAgE;AAChE,SAAS,aAAa,CAAC,IAA6B;IAClD,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAA;IACxE,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAClE,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,MAAM,SAAS,CAAA;AACpF,CAAC;AAED,0DAA0D;AAC1D,SAAS,SAAS,CAAC,KAAa,EAAE,GAAW;IAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACvC,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5D,CAAC;AAED,uEAAuE;AACvE,SAAS,SAAS,CAAC,QAAgB,EAAE,IAA6B,EAAE,KAAkB;IACpF,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC7B,OAAO,8BAA8B,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IAC5D,CAAC;IACD,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;QAC/B,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,6BAA6B,CAAA;QACxE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5C,MAAM,MAAM,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;YAC3D,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE;YACrB,CAAC,CAAC,OAAO,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,cAAc,CAAA;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;QAC1D,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAC3E,CAAC,CAAC,oBAAoB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE;YACvD,CAAC,CAAC,EAAE,CAAA;QACN,OAAO,mCAAmC,MAAM,KAAK,IAAI,IAAI,QAAQ,EAAE,CAAA;IACzE,CAAC;IACD,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;QAC9B,OAAO,uBAAuB,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IACrD,CAAC;IACD,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAClF,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAClE,OAAO,0BAA0B,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,MAAM,SAAS,EAAE,CAAA;IACrG,CAAC;IACD,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAClF,OAAO,qBAAqB,MAAM,EAAE,CAAA;IACtC,CAAC;IACD,OAAO,+BAA+B,CAAA;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,KAAkB;IACnE,OAAO,GAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAA4B,EAAE;QAChF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,EAAE,CAAA;QAC9C,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxC,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,MAAM,IAAI,IAAI,CAAC,IAAI,4BAA4B,EAAE,CAAA;QACzG,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAA;IACvG,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CommandDefinition, CommandsService, JobRegistry } from './types.ts';
|
|
2
|
+
import type { GithubState } from './state.ts';
|
|
3
|
+
/** Register the /pr command family. */
|
|
4
|
+
export declare function registerPrCommand(commands: CommandsService, state: GithubState): () => void;
|
|
5
|
+
/** Register the /review command family. */
|
|
6
|
+
export declare function registerReviewCommand(commands: CommandsService, jobs: JobRegistry, state: GithubState): () => void;
|
|
7
|
+
/** Register the /issue command family. */
|
|
8
|
+
export declare function registerIssueCommand(commands: CommandsService, state: GithubState): () => void;
|
|
9
|
+
/** Register all three command families; returns the combined disposer. */
|
|
10
|
+
export declare function registerCommands(commands: CommandsService, jobs: JobRegistry, state: GithubState): () => void;
|
|
11
|
+
export type { CommandDefinition };
|
|
12
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,iBAAiB,EAAoC,eAAe,EAAe,WAAW,EAAE,MAAM,YAAY,CAAA;AAChI,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAiB7C,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAY3F;AAoCD,2CAA2C;AAC3C,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAoClH;AAiED,0CAA0C;AAC1C,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAoB9F;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAS7G;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human slash commands: /pr, /review, /issue.
|
|
3
|
+
*
|
|
4
|
+
* Commands never perform GitHub writes themselves: command handlers run with
|
|
5
|
+
* no open turn, so the approval seam is structurally closed to them. Instead
|
|
6
|
+
* a write command gathers read-only context (git state, review records) and
|
|
7
|
+
* queues a model instruction — via followup for an idle agent, inject for a
|
|
8
|
+
* busy one — so the model calls the corresponding write tool inside a turn,
|
|
9
|
+
* where the tools/pre-execute approval gate asks the human. Read-only command
|
|
10
|
+
* work (starting or stopping a review job) runs directly in the handler.
|
|
11
|
+
* @module dsh-github/commands
|
|
12
|
+
*/
|
|
13
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm/message';
|
|
14
|
+
import { readGitState } from "./git.js";
|
|
15
|
+
import { startReviewJob } from "./jobs.js";
|
|
16
|
+
const USAGE_PR = 'Usage: /pr create [title]';
|
|
17
|
+
const USAGE_REVIEW = 'Usage: /review <pr> [--max-diff <n>] [--no-ci] [--no-comments] | /review stop <jobId> | /review post <jobId>';
|
|
18
|
+
const USAGE_ISSUE = 'Usage: /issue open <title>';
|
|
19
|
+
/** Queue a model instruction: wake an idle driver, inject into a busy one. */
|
|
20
|
+
function notify(agent, text) {
|
|
21
|
+
const summary = text.split('\n')[0] ?? '';
|
|
22
|
+
const message = createUserMessage({
|
|
23
|
+
content: [{ type: 'text', text }],
|
|
24
|
+
source: { kind: 'plugin', plugin: 'dsh-github', form: 'notice', summary },
|
|
25
|
+
});
|
|
26
|
+
if (agent.status === 'idle')
|
|
27
|
+
agent.followup(message);
|
|
28
|
+
else
|
|
29
|
+
agent.inject(message);
|
|
30
|
+
}
|
|
31
|
+
/** Register the /pr command family. */
|
|
32
|
+
export function registerPrCommand(commands, state) {
|
|
33
|
+
return commands.register({
|
|
34
|
+
name: 'pr',
|
|
35
|
+
description: 'Create a GitHub pull request (approval required)',
|
|
36
|
+
input: { hint: 'create [title]' },
|
|
37
|
+
handler: async (invocation) => {
|
|
38
|
+
const [sub, ...rest] = invocation.rawInput.trim().split(/\s+/);
|
|
39
|
+
if (sub === undefined || sub.length === 0)
|
|
40
|
+
return { kind: 'success', text: USAGE_PR };
|
|
41
|
+
if (sub !== 'create')
|
|
42
|
+
return { kind: 'error', text: `unknown /pr subcommand "${sub}". ${USAGE_PR}` };
|
|
43
|
+
return createPrDraft(invocation, rest.join(' '), state);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Gather git state and queue a pr_create instruction for the model. */
|
|
48
|
+
async function createPrDraft(invocation, title, state) {
|
|
49
|
+
const git = await readGitState(state.workspaceDir, state.runGit, invocation.signal, state.apiHost);
|
|
50
|
+
if (git.error !== undefined) {
|
|
51
|
+
return { kind: 'error', text: `could not read git state: ${git.error}. Run /pr create inside a git checkout.` };
|
|
52
|
+
}
|
|
53
|
+
if (git.branch === null)
|
|
54
|
+
return { kind: 'error', text: 'no current branch found. Run /pr create inside a git checkout.' };
|
|
55
|
+
const repo = git.repoFromRemote ?? state.config.defaultOwnerRepo;
|
|
56
|
+
if (repo === undefined) {
|
|
57
|
+
return { kind: 'error', text: 'could not determine the target repository. Set defaultOwnerRepo in cordis.yml or add a GitHub origin remote.' };
|
|
58
|
+
}
|
|
59
|
+
const lines = [
|
|
60
|
+
`The user ran /pr create${title.length > 0 ? ` "${title}"` : ''}. Create a GitHub pull request by calling the pr_create tool.`,
|
|
61
|
+
`- ownerRepo: ${repo}`,
|
|
62
|
+
`- head: ${git.branch}`,
|
|
63
|
+
'- base: omit (repository default branch)',
|
|
64
|
+
`- title: ${title.length > 0 ? JSON.stringify(title) : 'derive one from the changes'}`,
|
|
65
|
+
];
|
|
66
|
+
if (git.hasChanges) {
|
|
67
|
+
lines.push(`- the working tree has uncommitted changes: ${git.changedFiles.slice(0, 10).join(', ')}${git.changedFiles.length > 10 ? ' …' : ''}`);
|
|
68
|
+
}
|
|
69
|
+
if (git.commitsAhead.length > 0) {
|
|
70
|
+
lines.push(`- unpushed commits ahead of upstream:\n${git.commitsAhead.slice(0, 20).join('\n')}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push(state.config.autoCommit
|
|
73
|
+
? '- autoCommit is enabled: commit the uncommitted changes and push them to the remote via bash first (each bash write needs approval), then call pr_create.'
|
|
74
|
+
: '- do NOT commit or push anything; create the PR against the pushed head as-is.');
|
|
75
|
+
lines.push('You may polish the PR body before calling pr_create. Creating the PR asks the human for approval.');
|
|
76
|
+
notify(invocation.agent, lines.join('\n'));
|
|
77
|
+
return { kind: 'success', text: `queued a pr_create draft for ${repo} (${git.branch}). The model prepares the PR; creation requires your approval.` };
|
|
78
|
+
}
|
|
79
|
+
/** Register the /review command family. */
|
|
80
|
+
export function registerReviewCommand(commands, jobs, state) {
|
|
81
|
+
return commands.register({
|
|
82
|
+
name: 'review',
|
|
83
|
+
description: 'Review a GitHub pull request in a background job',
|
|
84
|
+
input: { hint: '<pr> [--max-diff <n>] [--no-ci] [--no-comments] | stop <jobId> | post <jobId>' },
|
|
85
|
+
handler: async (invocation) => {
|
|
86
|
+
const input = invocation.rawInput.trim();
|
|
87
|
+
if (input.length === 0)
|
|
88
|
+
return { kind: 'success', text: USAGE_REVIEW };
|
|
89
|
+
if (input.startsWith('stop ')) {
|
|
90
|
+
const jobId = input.slice('stop '.length).trim();
|
|
91
|
+
if (jobId.length === 0)
|
|
92
|
+
return { kind: 'error', text: USAGE_REVIEW };
|
|
93
|
+
if (!state.records.has(jobId)) {
|
|
94
|
+
return { kind: 'error', text: `no review job "${jobId}". List jobs with job_list or check the /review output.` };
|
|
95
|
+
}
|
|
96
|
+
const outcome = jobs.kill(jobId, invocation.agent, 'user requested stop via /review stop');
|
|
97
|
+
return { kind: 'success', text: outcome === 'requested' ? `requested stop of job ${jobId}` : `job ${jobId} had already finished` };
|
|
98
|
+
}
|
|
99
|
+
if (input.startsWith('post ')) {
|
|
100
|
+
const jobId = input.slice('post '.length).trim();
|
|
101
|
+
if (jobId.length === 0)
|
|
102
|
+
return { kind: 'error', text: USAGE_REVIEW };
|
|
103
|
+
const record = state.records.get(jobId);
|
|
104
|
+
if (record === undefined) {
|
|
105
|
+
return { kind: 'error', text: `no review job "${jobId}". List jobs with job_list or check the /review output.` };
|
|
106
|
+
}
|
|
107
|
+
if (record.status !== 'completed' || record.report === null) {
|
|
108
|
+
return { kind: 'error', text: `review job "${jobId}" is ${record.status}; wait for completion before posting.` };
|
|
109
|
+
}
|
|
110
|
+
notify(invocation.agent, `The user ran /review post ${jobId}. Call the review_post tool with { "jobId": "${jobId}" } to publish the review `
|
|
111
|
+
+ `for PR #${record.pr} in ${record.repo} (${record.report.findings.length} finding(s)). Use mode "inline" for line-anchored `
|
|
112
|
+
+ 'comments or "summary" for one aggregated comment. Posting asks the human for approval.');
|
|
113
|
+
return { kind: 'success', text: `queued review_post for job ${jobId} (PR #${record.pr}, ${record.report.findings.length} finding(s)). Posting requires your approval.` };
|
|
114
|
+
}
|
|
115
|
+
return startReview(invocation, input, jobs, state);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** Parse `/review <pr>` options: --max-diff <n>, --no-ci, --no-comments. */
|
|
120
|
+
function parseReviewOptions(input) {
|
|
121
|
+
const parts = input.split(/\s+/);
|
|
122
|
+
const options = { includeCi: true, includeComments: true };
|
|
123
|
+
let pr = '';
|
|
124
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
125
|
+
const part = parts[index] ?? '';
|
|
126
|
+
if (part === '--max-diff') {
|
|
127
|
+
const value = parts[index + 1];
|
|
128
|
+
if (value === undefined || !/^\d+$/.test(value))
|
|
129
|
+
throw new Error(`--max-diff needs a positive number. ${USAGE_REVIEW}`);
|
|
130
|
+
options.maxDiffChars = Number(value);
|
|
131
|
+
index += 1;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (part === '--no-ci') {
|
|
135
|
+
options.includeCi = false;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (part === '--no-comments') {
|
|
139
|
+
options.includeComments = false;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (pr.length > 0)
|
|
143
|
+
throw new Error(`unexpected argument "${part}". ${USAGE_REVIEW}`);
|
|
144
|
+
pr = part;
|
|
145
|
+
}
|
|
146
|
+
if (pr.length === 0)
|
|
147
|
+
throw new Error(USAGE_REVIEW);
|
|
148
|
+
return { pr, options };
|
|
149
|
+
}
|
|
150
|
+
/** Start a background review job for one PR reference. */
|
|
151
|
+
async function startReview(invocation, input, jobs, state) {
|
|
152
|
+
let parsed;
|
|
153
|
+
try {
|
|
154
|
+
parsed = parseReviewOptions(input);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return { kind: 'error', text: error instanceof Error ? error.message : USAGE_REVIEW };
|
|
158
|
+
}
|
|
159
|
+
const ref = state.parsePrRef(parsed.pr);
|
|
160
|
+
if (ref === null) {
|
|
161
|
+
return { kind: 'error', text: `"${parsed.pr}" is not a PR reference. Use a number, "#number", "owner/repo#number", or a pull URL.` };
|
|
162
|
+
}
|
|
163
|
+
const repoResult = ref.repo !== undefined ? { ok: true, repo: ref.repo } : await state.resolveRepo(undefined, invocation.signal);
|
|
164
|
+
if (!repoResult.ok)
|
|
165
|
+
return { kind: 'error', text: `${repoResult.message}. ${repoResult.guidance}` };
|
|
166
|
+
let jobId;
|
|
167
|
+
try {
|
|
168
|
+
jobId = startReviewJob(jobs, state, {
|
|
169
|
+
repo: repoResult.repo,
|
|
170
|
+
pr: ref.number,
|
|
171
|
+
label: `review PR #${ref.number} (${repoResult.repo})`,
|
|
172
|
+
owner: invocation.agent,
|
|
173
|
+
...parsed.options,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
178
|
+
return {
|
|
179
|
+
kind: 'error',
|
|
180
|
+
text: `could not start the review job: ${detail}.\n`
|
|
181
|
+
+ 'Load @deepseek-ai/dsh-tool-jobs in the agent composition (it serves the job registry and owns completion notices), then retry.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
kind: 'success',
|
|
186
|
+
text: `started review job ${jobId} for PR #${ref.number} in ${repoResult.repo}.\n`
|
|
187
|
+
+ `You will be notified on completion; the model reads it via job_output ${jobId}.\n`
|
|
188
|
+
+ `/review post ${jobId} publishes the comment (requires your approval) · /review stop ${jobId} cancels.`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Register the /issue command family. */
|
|
192
|
+
export function registerIssueCommand(commands, state) {
|
|
193
|
+
return commands.register({
|
|
194
|
+
name: 'issue',
|
|
195
|
+
description: 'Open a GitHub issue (approval required)',
|
|
196
|
+
input: { hint: 'open <title>' },
|
|
197
|
+
handler: async (invocation) => {
|
|
198
|
+
const input = invocation.rawInput.trim();
|
|
199
|
+
if (input.length === 0)
|
|
200
|
+
return { kind: 'success', text: USAGE_ISSUE };
|
|
201
|
+
if (!input.startsWith('open '))
|
|
202
|
+
return { kind: 'error', text: `unknown /issue subcommand. ${USAGE_ISSUE}` };
|
|
203
|
+
const title = input.slice('open '.length).trim();
|
|
204
|
+
if (title.length === 0)
|
|
205
|
+
return { kind: 'error', text: USAGE_ISSUE };
|
|
206
|
+
const repoResult = await state.resolveRepo(undefined, invocation.signal);
|
|
207
|
+
if (!repoResult.ok)
|
|
208
|
+
return { kind: 'error', text: `${repoResult.message}. ${repoResult.guidance}` };
|
|
209
|
+
notify(invocation.agent, `The user ran /issue open "${title}". Create the issue by calling the issue_open tool with `
|
|
210
|
+
+ `{ "title": ${JSON.stringify(title)}, "ownerRepo": "${repoResult.repo}" } and a concise body you write. `
|
|
211
|
+
+ 'Creating the issue asks the human for approval.');
|
|
212
|
+
return { kind: 'success', text: `queued issue_open for ${repoResult.repo}. Creating the issue requires your approval.` };
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Register all three command families; returns the combined disposer. */
|
|
217
|
+
export function registerCommands(commands, jobs, state) {
|
|
218
|
+
const disposers = [
|
|
219
|
+
registerPrCommand(commands, state),
|
|
220
|
+
registerReviewCommand(commands, jobs, state),
|
|
221
|
+
registerIssueCommand(commands, state),
|
|
222
|
+
];
|
|
223
|
+
return () => {
|
|
224
|
+
for (const dispose of disposers)
|
|
225
|
+
dispose();
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schemastery configuration for dsh-github.
|
|
3
|
+
*
|
|
4
|
+
* Every tunable is changeable from cordis.yml; the schema validates at load
|
|
5
|
+
* time and fails loud on invalid values. Secrets are NOT config fields: the
|
|
6
|
+
* token always travels through the credentials seam (a reference name here,
|
|
7
|
+
* never a value).
|
|
8
|
+
* @module dsh-github/config
|
|
9
|
+
*/
|
|
10
|
+
import z from '@deepseek-ai/schemastery';
|
|
11
|
+
/** GitHub write actions the plugin may perform (each still requires approval). */
|
|
12
|
+
export type GithubAction = 'pr.create' | 'review.post' | 'issue.create' | 'issue.comment' | 'issue.close';
|
|
13
|
+
/** Where a GitHub token is looked up. `auto` tries them in order. */
|
|
14
|
+
export type TokenSource = 'auto' | 'credentials' | 'env' | 'gh';
|
|
15
|
+
export interface Config {
|
|
16
|
+
/**
|
|
17
|
+
* Token lookup order: `auto` resolves credentials-seam reference → `env`
|
|
18
|
+
* variable → `gh` CLI login, in that order. Explicit values restrict to one
|
|
19
|
+
* source and report a structured error when it is unavailable.
|
|
20
|
+
*/
|
|
21
|
+
tokenSource: TokenSource;
|
|
22
|
+
/** Credentials-seam reference / environment-variable name for the token. */
|
|
23
|
+
tokenRef: string;
|
|
24
|
+
/** `owner/repo` used when a call does not name one and git has no origin. */
|
|
25
|
+
defaultOwnerRepo?: string;
|
|
26
|
+
/** Whether `/pr create` may instruct the model to commit and push first. */
|
|
27
|
+
autoCommit: boolean;
|
|
28
|
+
/** Character cap for PR diffs read into a review (diff or review job). */
|
|
29
|
+
maxDiffChars: number;
|
|
30
|
+
/** Character cap for the diff excerpt rendered into tool output. */
|
|
31
|
+
renderExcerptChars: number;
|
|
32
|
+
/** Cap for PR comments listed by gh_review. */
|
|
33
|
+
maxComments: number;
|
|
34
|
+
/** Deadline for one background review job; exceeded jobs fail with detail. */
|
|
35
|
+
reviewJobTimeoutMs: number;
|
|
36
|
+
/** Cap for in-memory review-job records; oldest settled records evict first. */
|
|
37
|
+
maxReviewRecords: number;
|
|
38
|
+
/**
|
|
39
|
+
* Review engine: `static` runs the deterministic analyzer; `model` delegates
|
|
40
|
+
* the capped diff to a one-shot subagent through the host's `subagents` seam
|
|
41
|
+
* (the owner agent is the parent). Fails loud when the seam is absent.
|
|
42
|
+
*/
|
|
43
|
+
reviewMode: 'static' | 'model';
|
|
44
|
+
/** Subagent provider name for `reviewMode: "model"`; defaults to the first registered. */
|
|
45
|
+
modelReviewProvider?: string;
|
|
46
|
+
/** Maximum 429 retry attempts per GitHub API request. */
|
|
47
|
+
maxRetries: number;
|
|
48
|
+
/** Base backoff for 429 retries (doubles per attempt). */
|
|
49
|
+
retryBaseMs: number;
|
|
50
|
+
/** Ceiling for 429 retry backoff. */
|
|
51
|
+
retryMaxWaitMs: number;
|
|
52
|
+
/** GitHub REST base URL; change for GitHub Enterprise. */
|
|
53
|
+
apiBaseUrl: string;
|
|
54
|
+
/** Whitelist of write actions; everything else is denied before approval. */
|
|
55
|
+
allowedActions: GithubAction[];
|
|
56
|
+
/** Working directory for read-only git inspection; defaults to the process cwd. */
|
|
57
|
+
workspaceDir?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare const Config: z<Config>;
|
|
60
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,kFAAkF;AAClF,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAA;AAEzG,qEAAqE;AACrE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,aAAa,GAAG,KAAK,GAAG,IAAI,CAAA;AAE/D,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,WAAW,EAAE,WAAW,CAAA;IACxB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,4EAA4E;IAC5E,UAAU,EAAE,OAAO,CAAA;IACnB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAA;IAC1B,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,kBAAkB,EAAE,MAAM,CAAA;IAC1B,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,UAAU,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC9B,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAA;IAClB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAA;IACnB,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAA;IACtB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,cAAc,EAAE,YAAY,EAAE,CAAA;IAC9B,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAID,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAmD3B,CAAA"}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schemastery configuration for dsh-github.
|
|
3
|
+
*
|
|
4
|
+
* Every tunable is changeable from cordis.yml; the schema validates at load
|
|
5
|
+
* time and fails loud on invalid values. Secrets are NOT config fields: the
|
|
6
|
+
* token always travels through the credentials seam (a reference name here,
|
|
7
|
+
* never a value).
|
|
8
|
+
* @module dsh-github/config
|
|
9
|
+
*/
|
|
10
|
+
import z from '@deepseek-ai/schemastery';
|
|
11
|
+
const OWNER_REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
12
|
+
export const Config = z.object({
|
|
13
|
+
tokenSource: z.union(['auto', 'credentials', 'env', 'gh'])
|
|
14
|
+
.default('auto')
|
|
15
|
+
.description('Token lookup source: auto tries credentials → env → gh in order.'),
|
|
16
|
+
tokenRef: z.string()
|
|
17
|
+
.pattern(/^[A-Za-z_][A-Za-z0-9_]*$/)
|
|
18
|
+
.default('GITHUB_TOKEN')
|
|
19
|
+
.description('Credential reference / environment variable name holding the token.'),
|
|
20
|
+
defaultOwnerRepo: z.string()
|
|
21
|
+
.pattern(OWNER_REPO_PATTERN)
|
|
22
|
+
.description('Fallback owner/repo when neither the call nor git names one.'),
|
|
23
|
+
autoCommit: z.boolean()
|
|
24
|
+
.default(false)
|
|
25
|
+
.description('Whether /pr create may instruct the model to commit and push first.'),
|
|
26
|
+
maxDiffChars: z.number().step(1).min(1)
|
|
27
|
+
.default(8000)
|
|
28
|
+
.description('Character cap for PR diffs read into a review.'),
|
|
29
|
+
renderExcerptChars: z.number().step(1).min(1)
|
|
30
|
+
.default(2000)
|
|
31
|
+
.description('Character cap for the diff excerpt rendered into tool output.'),
|
|
32
|
+
maxComments: z.number().step(1).min(1)
|
|
33
|
+
.default(20)
|
|
34
|
+
.description('Cap for PR comments listed by gh_review.'),
|
|
35
|
+
reviewJobTimeoutMs: z.number().step(1).min(1000)
|
|
36
|
+
.default(600_000)
|
|
37
|
+
.description('Deadline for one background review job.'),
|
|
38
|
+
maxReviewRecords: z.number().step(1).min(1)
|
|
39
|
+
.default(50)
|
|
40
|
+
.description('Cap for in-memory review-job records; oldest settled records evict first.'),
|
|
41
|
+
reviewMode: z.union(['static', 'model'])
|
|
42
|
+
.default('static')
|
|
43
|
+
.description('Review engine: static (deterministic analyzer) or model (one-shot subagent through the subagents seam).'),
|
|
44
|
+
modelReviewProvider: z.string()
|
|
45
|
+
.description('Subagent provider name for model review; defaults to the first registered provider.'),
|
|
46
|
+
maxRetries: z.number().step(1).min(0)
|
|
47
|
+
.default(3)
|
|
48
|
+
.description('Maximum 429 retry attempts per GitHub API request.'),
|
|
49
|
+
retryBaseMs: z.number().step(1).min(1)
|
|
50
|
+
.default(500)
|
|
51
|
+
.description('Base backoff for 429 retries (doubles per attempt).'),
|
|
52
|
+
retryMaxWaitMs: z.number().step(1).min(1)
|
|
53
|
+
.default(60_000)
|
|
54
|
+
.description('Ceiling for 429 retry backoff.'),
|
|
55
|
+
apiBaseUrl: z.string()
|
|
56
|
+
.default('https://api.github.com')
|
|
57
|
+
.description('GitHub REST base URL (change for GitHub Enterprise).'),
|
|
58
|
+
allowedActions: z.array(z.union(['pr.create', 'review.post', 'issue.create', 'issue.comment', 'issue.close']))
|
|
59
|
+
.default(['pr.create', 'review.post', 'issue.create', 'issue.comment', 'issue.close'])
|
|
60
|
+
.description('Write actions this plugin may perform; each still requires approval.'),
|
|
61
|
+
workspaceDir: z.string()
|
|
62
|
+
.description('Working directory for read-only git inspection (defaults to process cwd).'),
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
|
|
2
|
+
import type { TokenSource } from './config.ts';
|
|
3
|
+
/** A resolved non-empty token and the source layer that supplied it. */
|
|
4
|
+
export interface ResolvedToken {
|
|
5
|
+
value: string;
|
|
6
|
+
source: Exclude<TokenSource, 'auto'>;
|
|
7
|
+
}
|
|
8
|
+
/** Structured, token-free explanation of a failed resolution. */
|
|
9
|
+
export interface TokenResolutionError {
|
|
10
|
+
code: 'no-token';
|
|
11
|
+
message: string;
|
|
12
|
+
guidance: string;
|
|
13
|
+
}
|
|
14
|
+
export type TokenResolution = {
|
|
15
|
+
ok: true;
|
|
16
|
+
token: ResolvedToken;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
error: TokenResolutionError;
|
|
20
|
+
};
|
|
21
|
+
/** Runs one `gh` CLI invocation; injectable for tests. */
|
|
22
|
+
export type GhRunner = (args: string[], signal?: AbortSignal) => Promise<{
|
|
23
|
+
stdout: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Run the real `gh` CLI with stdout captured (never logged). */
|
|
26
|
+
export declare function runGhCli(args: string[], signal?: AbortSignal): Promise<{
|
|
27
|
+
stdout: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the GitHub token for one operation.
|
|
31
|
+
*
|
|
32
|
+
* `auto` order: credentials seam → `GITHUB_TOKEN`-style environment variable →
|
|
33
|
+
* `gh` CLI logged-in token. An explicit source restricts to that one source.
|
|
34
|
+
* @param credentials - the credentials seam (`ctx.credentials`).
|
|
35
|
+
* @param tokenSource - configured lookup policy.
|
|
36
|
+
* @param tokenRef - reference / environment-variable name.
|
|
37
|
+
* @param runGh - `gh` CLI runner; the real one by default.
|
|
38
|
+
* @param signal - aborts the `gh` lookup.
|
|
39
|
+
* @returns the token with its source, or a structured error with guidance.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveToken(credentials: CredentialProvider, tokenSource: TokenSource, tokenRef: string, runGh?: GhRunner, signal?: AbortSignal): Promise<TokenResolution>;
|
|
42
|
+
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;CACrC;AAED,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,CAAA;AAE7G,0DAA0D;AAC1D,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAO5F,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAO1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,kBAAkB,EAC/B,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,QAAmB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,eAAe,CAAC,CAsC1B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub token resolution across the three supported sources.
|
|
3
|
+
*
|
|
4
|
+
* The token value never leaves this module except through a {@link ResolvedToken}
|
|
5
|
+
* handed to the REST client: it is never written into model-visible text,
|
|
6
|
+
* session events, logs, or error messages. Every resolution is per operation —
|
|
7
|
+
* nothing is cached across operations, so a rotated credential reaches the
|
|
8
|
+
* very next call (credentials-seam contract).
|
|
9
|
+
* @module dsh-github/credential
|
|
10
|
+
*/
|
|
11
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
const NO_TOKEN_GUIDANCE = 'Configure one of: 1) a dsh credential for this reference (recommended; ' +
|
|
14
|
+
'$DSH_HOME/.credentials.yaml), 2) the same-named environment variable, or ' +
|
|
15
|
+
'3) `gh auth login` for the gh CLI. Set `tokenSource` in cordis.yml to pin one source.';
|
|
16
|
+
/** Run the real `gh` CLI with stdout captured (never logged). */
|
|
17
|
+
export function runGhCli(args, signal) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
execFile('gh', args, { windowsHide: true, signal, maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
20
|
+
if (error)
|
|
21
|
+
reject(error);
|
|
22
|
+
else
|
|
23
|
+
resolve({ stdout });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the GitHub token for one operation.
|
|
29
|
+
*
|
|
30
|
+
* `auto` order: credentials seam → `GITHUB_TOKEN`-style environment variable →
|
|
31
|
+
* `gh` CLI logged-in token. An explicit source restricts to that one source.
|
|
32
|
+
* @param credentials - the credentials seam (`ctx.credentials`).
|
|
33
|
+
* @param tokenSource - configured lookup policy.
|
|
34
|
+
* @param tokenRef - reference / environment-variable name.
|
|
35
|
+
* @param runGh - `gh` CLI runner; the real one by default.
|
|
36
|
+
* @param signal - aborts the `gh` lookup.
|
|
37
|
+
* @returns the token with its source, or a structured error with guidance.
|
|
38
|
+
*/
|
|
39
|
+
export async function resolveToken(credentials, tokenSource, tokenRef, runGh = runGhCli, signal) {
|
|
40
|
+
const order = tokenSource === 'auto' ? ['credentials', 'env', 'gh'] : [tokenSource];
|
|
41
|
+
for (const source of order) {
|
|
42
|
+
if (source === 'credentials') {
|
|
43
|
+
let ref;
|
|
44
|
+
try {
|
|
45
|
+
ref = credentialRef(tokenRef);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { ok: false, error: { code: 'no-token', message: `credential ref "${tokenRef}" is not a valid identifier`, guidance: NO_TOKEN_GUIDANCE } };
|
|
49
|
+
}
|
|
50
|
+
const resolved = await credentials.resolve(ref);
|
|
51
|
+
if (resolved)
|
|
52
|
+
return { ok: true, token: { value: resolved.value, source: 'credentials' } };
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (source === 'env') {
|
|
56
|
+
const value = process.env[tokenRef];
|
|
57
|
+
if (value && value.length > 0)
|
|
58
|
+
return { ok: true, token: { value, source: 'env' } };
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const { stdout } = await runGh(['auth', 'token'], signal);
|
|
63
|
+
const value = stdout.trim();
|
|
64
|
+
if (value.length > 0)
|
|
65
|
+
return { ok: true, token: { value, source: 'gh' } };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// gh missing or not logged in: fall through to the structured error.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: {
|
|
74
|
+
code: 'no-token',
|
|
75
|
+
message: `no GitHub token found (source: ${tokenSource === 'auto' ? 'credentials/env/gh' : tokenSource})`,
|
|
76
|
+
guidance: NO_TOKEN_GUIDANCE,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAqB7C,MAAM,iBAAiB,GACrB,yEAAyE;IACzE,2EAA2E;IAC3E,uFAAuF,CAAA;AAEzF,iEAAiE;AACjE,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,MAAoB;IAC3D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC5F,IAAI,KAAK;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAA;;gBACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,WAA+B,EAC/B,WAAwB,EACxB,QAAgB,EAChB,QAAkB,QAAQ,EAC1B,MAAoB;IAEpB,MAAM,KAAK,GACT,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAEvE,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;QAC3B,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;YAC7B,IAAI,GAAqC,CAAA;YACzC,IAAI,CAAC;gBACH,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,QAAQ,6BAA6B,EAAE,QAAQ,EAAE,iBAAiB,EAAE,EAAE,CAAA;YACnJ,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC/C,IAAI,QAAQ;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAA;YAC1F,SAAQ;QACV,CAAC;QACD,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACnC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAA;YACnF,SAAQ;QACV,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAA;YACzD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAA;QAC3E,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE;YACL,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,kCAAkC,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,WAAW,GAAG;YACzG,QAAQ,EAAE,iBAAiB;SAC5B;KACF,CAAA;AACH,CAAC"}
|