@kya-os/cli 1.5.9 → 1.6.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/dist/commands/dco.d.ts +18 -0
- package/dist/commands/dco.d.ts.map +1 -0
- package/dist/commands/dco.js +107 -0
- package/dist/commands/dco.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +76 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/register.d.ts +2 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +50 -0
- package/dist/commands/register.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/utils/dco/agent-metadata.d.ts +24 -0
- package/dist/utils/dco/agent-metadata.d.ts.map +1 -0
- package/dist/utils/dco/agent-metadata.js +46 -0
- package/dist/utils/dco/agent-metadata.js.map +1 -0
- package/dist/utils/dco/claude-config.d.ts +45 -0
- package/dist/utils/dco/claude-config.d.ts.map +1 -0
- package/dist/utils/dco/claude-config.js +112 -0
- package/dist/utils/dco/claude-config.js.map +1 -0
- package/dist/utils/dco/fs-safe.d.ts +14 -0
- package/dist/utils/dco/fs-safe.d.ts.map +1 -0
- package/dist/utils/dco/fs-safe.js +20 -0
- package/dist/utils/dco/fs-safe.js.map +1 -0
- package/dist/utils/dco/git-config.d.ts +47 -0
- package/dist/utils/dco/git-config.d.ts.map +1 -0
- package/dist/utils/dco/git-config.js +159 -0
- package/dist/utils/dco/git-config.js.map +1 -0
- package/dist/utils/dco/github.d.ts +49 -0
- package/dist/utils/dco/github.d.ts.map +1 -0
- package/dist/utils/dco/github.js +154 -0
- package/dist/utils/dco/github.js.map +1 -0
- package/dist/utils/dco/paths.d.ts +16 -0
- package/dist/utils/dco/paths.d.ts.map +1 -0
- package/dist/utils/dco/paths.js +27 -0
- package/dist/utils/dco/paths.js.map +1 -0
- package/dist/utils/dco/render.d.ts +4 -0
- package/dist/utils/dco/render.d.ts.map +1 -0
- package/dist/utils/dco/render.js +52 -0
- package/dist/utils/dco/render.js.map +1 -0
- package/dist/utils/dco/setup.d.ts +47 -0
- package/dist/utils/dco/setup.d.ts.map +1 -0
- package/dist/utils/dco/setup.js +740 -0
- package/dist/utils/dco/setup.js.map +1 -0
- package/dist/utils/dco/ssh-key.d.ts +43 -0
- package/dist/utils/dco/ssh-key.d.ts.map +1 -0
- package/dist/utils/dco/ssh-key.js +176 -0
- package/dist/utils/dco/ssh-key.js.map +1 -0
- package/dist/utils/dco/status.d.ts +12 -0
- package/dist/utils/dco/status.d.ts.map +1 -0
- package/dist/utils/dco/status.js +238 -0
- package/dist/utils/dco/status.js.map +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code integration: a managed block in CLAUDE.md instructs the agent
|
|
3
|
+
* harness to attribute PRs to the registered agent, and .claude/settings.json
|
|
4
|
+
* disables the stock "Generated with Claude Code" attribution.
|
|
5
|
+
*
|
|
6
|
+
* The PR byline can only be influenced through instructions because the PR
|
|
7
|
+
* body is composed by the model at PR time; commit trailers are enforced
|
|
8
|
+
* mechanically by the git hook instead (see git-config.ts).
|
|
9
|
+
*/
|
|
10
|
+
export declare const CLAUDE_MD_START = "<!-- mcpi:dco:start -->";
|
|
11
|
+
export declare const CLAUDE_MD_END = "<!-- mcpi:dco:end -->";
|
|
12
|
+
export interface AttributionBlockParams {
|
|
13
|
+
agentName: string;
|
|
14
|
+
agentUrl: string;
|
|
15
|
+
/**
|
|
16
|
+
* When the git hook could not be installed (foreign hook, unsafe
|
|
17
|
+
* core.hooksPath, or install failure), trailer duty falls back to
|
|
18
|
+
* instructions.
|
|
19
|
+
*/
|
|
20
|
+
includeTrailerInstructions?: boolean;
|
|
21
|
+
humanName?: string;
|
|
22
|
+
humanEmail?: string;
|
|
23
|
+
agentEmail?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildAttributionBlock(params: AttributionBlockParams): string;
|
|
26
|
+
export type UpsertResult = "created" | "updated" | "unchanged" | "would-change";
|
|
27
|
+
export declare function upsertClaudeMdBlock(cwd: string, block: string, options?: {
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
}): {
|
|
30
|
+
result: UpsertResult;
|
|
31
|
+
path: string;
|
|
32
|
+
};
|
|
33
|
+
export type SettingsResult = "created" | "updated" | "unchanged" | "skipped-invalid" | "would-change";
|
|
34
|
+
/**
|
|
35
|
+
* Set includeCoAuthoredBy: false in .claude/settings.json, preserving every
|
|
36
|
+
* other key. A file we cannot parse is left untouched: clobbering a user's
|
|
37
|
+
* settings is worse than leaving the stock byline on.
|
|
38
|
+
*/
|
|
39
|
+
export declare function updateClaudeSettings(cwd: string, options?: {
|
|
40
|
+
dryRun?: boolean;
|
|
41
|
+
}): {
|
|
42
|
+
result: SettingsResult;
|
|
43
|
+
path: string;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=claude-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-config.d.ts","sourceRoot":"","sources":["../../../src/utils/dco/claude-config.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,eAAO,MAAM,eAAe,4BAA4B,CAAC;AACzD,eAAO,MAAM,aAAa,0BAA0B,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAaD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAoB5E;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,CAAC;AAEhF,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAiCxC;AAED,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,SAAS,GACT,WAAW,GACX,iBAAiB,GACjB,cAAc,CAAC;AAEnB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqC1C"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { writeFileAtomicSafe } from "./fs-safe.js";
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code integration: a managed block in CLAUDE.md instructs the agent
|
|
6
|
+
* harness to attribute PRs to the registered agent, and .claude/settings.json
|
|
7
|
+
* disables the stock "Generated with Claude Code" attribution.
|
|
8
|
+
*
|
|
9
|
+
* The PR byline can only be influenced through instructions because the PR
|
|
10
|
+
* body is composed by the model at PR time; commit trailers are enforced
|
|
11
|
+
* mechanically by the git hook instead (see git-config.ts).
|
|
12
|
+
*/
|
|
13
|
+
export const CLAUDE_MD_START = "<!-- mcpi:dco:start -->";
|
|
14
|
+
export const CLAUDE_MD_END = "<!-- mcpi:dco:end -->";
|
|
15
|
+
/**
|
|
16
|
+
* Values interpolated into the managed block come from .mcpi/agent.json and
|
|
17
|
+
* git config, both attacker-influenceable in a cloned repo. Clamp everything
|
|
18
|
+
* to a single line with no control characters so crafted metadata cannot
|
|
19
|
+
* inject extra instructions into a block the harness treats as trusted.
|
|
20
|
+
*/
|
|
21
|
+
function singleLine(value) {
|
|
22
|
+
// eslint-disable-next-line no-control-regex
|
|
23
|
+
return value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
|
|
24
|
+
}
|
|
25
|
+
export function buildAttributionBlock(params) {
|
|
26
|
+
const agentName = singleLine(params.agentName);
|
|
27
|
+
const agentUrl = singleLine(params.agentUrl);
|
|
28
|
+
const lines = [
|
|
29
|
+
CLAUDE_MD_START,
|
|
30
|
+
"## Agent attribution (managed by `mcpi dco setup`)",
|
|
31
|
+
"",
|
|
32
|
+
"- When creating pull requests, end the PR description with exactly this byline:",
|
|
33
|
+
` \`Generated with [${agentName}](${agentUrl})\``,
|
|
34
|
+
'- Never add "Generated with Claude Code" or any other tool byline to PR descriptions or commit messages.',
|
|
35
|
+
];
|
|
36
|
+
if (params.includeTrailerInstructions) {
|
|
37
|
+
lines.push("- End every commit message with these trailers:", ` \`Signed-off-by: ${singleLine(params.humanName ?? "")} <${singleLine(params.humanEmail ?? "")}>\``, ` \`Co-authored-by: ${agentName} <${singleLine(params.agentEmail ?? "")}>\``);
|
|
38
|
+
}
|
|
39
|
+
lines.push(CLAUDE_MD_END);
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
42
|
+
export function upsertClaudeMdBlock(cwd, block, options = {}) {
|
|
43
|
+
const path = join(cwd, "CLAUDE.md");
|
|
44
|
+
if (!existsSync(path)) {
|
|
45
|
+
if (options.dryRun) {
|
|
46
|
+
return { result: "would-change", path };
|
|
47
|
+
}
|
|
48
|
+
writeFileAtomicSafe(path, block + "\n");
|
|
49
|
+
return { result: "created", path };
|
|
50
|
+
}
|
|
51
|
+
const existing = readFileSync(path, "utf-8");
|
|
52
|
+
const startIdx = existing.indexOf(CLAUDE_MD_START);
|
|
53
|
+
const endIdx = existing.indexOf(CLAUDE_MD_END);
|
|
54
|
+
let next;
|
|
55
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
56
|
+
const before = existing.slice(0, startIdx);
|
|
57
|
+
const after = existing.slice(endIdx + CLAUDE_MD_END.length);
|
|
58
|
+
next = before + block + after;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
62
|
+
next = existing + separator + block + "\n";
|
|
63
|
+
}
|
|
64
|
+
if (next === existing) {
|
|
65
|
+
return { result: "unchanged", path };
|
|
66
|
+
}
|
|
67
|
+
if (options.dryRun) {
|
|
68
|
+
return { result: "would-change", path };
|
|
69
|
+
}
|
|
70
|
+
writeFileAtomicSafe(path, next);
|
|
71
|
+
return { result: "updated", path };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Set includeCoAuthoredBy: false in .claude/settings.json, preserving every
|
|
75
|
+
* other key. A file we cannot parse is left untouched: clobbering a user's
|
|
76
|
+
* settings is worse than leaving the stock byline on.
|
|
77
|
+
*/
|
|
78
|
+
export function updateClaudeSettings(cwd, options = {}) {
|
|
79
|
+
const dir = join(cwd, ".claude");
|
|
80
|
+
const path = join(dir, "settings.json");
|
|
81
|
+
let settings = {};
|
|
82
|
+
if (existsSync(path)) {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
85
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
86
|
+
return { result: "skipped-invalid", path };
|
|
87
|
+
}
|
|
88
|
+
settings = parsed;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return { result: "skipped-invalid", path };
|
|
92
|
+
}
|
|
93
|
+
if (settings.includeCoAuthoredBy === false) {
|
|
94
|
+
return { result: "unchanged", path };
|
|
95
|
+
}
|
|
96
|
+
if (options.dryRun) {
|
|
97
|
+
return { result: "would-change", path };
|
|
98
|
+
}
|
|
99
|
+
settings.includeCoAuthoredBy = false;
|
|
100
|
+
writeFileAtomicSafe(path, JSON.stringify(settings, null, 2) + "\n");
|
|
101
|
+
return { result: "updated", path };
|
|
102
|
+
}
|
|
103
|
+
if (options.dryRun) {
|
|
104
|
+
return { result: "would-change", path };
|
|
105
|
+
}
|
|
106
|
+
if (!existsSync(dir)) {
|
|
107
|
+
mkdirSync(dir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
writeFileAtomicSafe(path, JSON.stringify({ includeCoAuthoredBy: false }, null, 2) + "\n");
|
|
110
|
+
return { result: "created", path };
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=claude-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-config.js","sourceRoot":"","sources":["../../../src/utils/dco/claude-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic, symlink-safe file replace.
|
|
3
|
+
*
|
|
4
|
+
* For `mcpi dco`, the destination and its temp sibling live in the current
|
|
5
|
+
* working tree, which may be an untrusted cloned repo. A tracked symlink
|
|
6
|
+
* planted at the predictable `<path>.mcpi-tmp` sibling would otherwise make
|
|
7
|
+
* a plain writeFileSync follow it and clobber an arbitrary file the user can
|
|
8
|
+
* write. So: unlink any pre-existing temp (rmSync removes the link itself,
|
|
9
|
+
* never its target), then create it with O_EXCL (`wx`) so a planted or
|
|
10
|
+
* racing symlink makes the write fail closed instead of being followed.
|
|
11
|
+
* renameSync replaces the destination without following a symlink there.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeFileAtomicSafe(path: string, content: string, mode?: number): void;
|
|
14
|
+
//# sourceMappingURL=fs-safe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-safe.d.ts","sourceRoot":"","sources":["../../../src/utils/dco/fs-safe.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,SAAQ,GACX,IAAI,CAKN"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { renameSync, rmSync, writeFileSync } from "fs";
|
|
2
|
+
/**
|
|
3
|
+
* Atomic, symlink-safe file replace.
|
|
4
|
+
*
|
|
5
|
+
* For `mcpi dco`, the destination and its temp sibling live in the current
|
|
6
|
+
* working tree, which may be an untrusted cloned repo. A tracked symlink
|
|
7
|
+
* planted at the predictable `<path>.mcpi-tmp` sibling would otherwise make
|
|
8
|
+
* a plain writeFileSync follow it and clobber an arbitrary file the user can
|
|
9
|
+
* write. So: unlink any pre-existing temp (rmSync removes the link itself,
|
|
10
|
+
* never its target), then create it with O_EXCL (`wx`) so a planted or
|
|
11
|
+
* racing symlink makes the write fail closed instead of being followed.
|
|
12
|
+
* renameSync replaces the destination without following a symlink there.
|
|
13
|
+
*/
|
|
14
|
+
export function writeFileAtomicSafe(path, content, mode = 0o644) {
|
|
15
|
+
const tempPath = `${path}.mcpi-tmp`;
|
|
16
|
+
rmSync(tempPath, { force: true });
|
|
17
|
+
writeFileSync(tempPath, content, { flag: "wx", mode });
|
|
18
|
+
renameSync(tempPath, path);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=fs-safe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-safe.js","sourceRoot":"","sources":["../../../src/utils/dco/fs-safe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,OAAe,EACf,IAAI,GAAG,KAAK;IAEZ,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,CAAC;IACpC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const HOOK_MARKER = "# mcpi-dco-hook";
|
|
2
|
+
export interface GitIdentity {
|
|
3
|
+
name: string;
|
|
4
|
+
email: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function isGitRepo(cwd: string): Promise<boolean>;
|
|
7
|
+
export declare function getGitConfigValue(key: string, cwd: string, scope?: "local" | "global"): Promise<string | null>;
|
|
8
|
+
export declare function setGitConfig(entries: Record<string, string>, options: {
|
|
9
|
+
cwd: string;
|
|
10
|
+
global?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
export interface SigningConflict {
|
|
13
|
+
key: string;
|
|
14
|
+
current: string;
|
|
15
|
+
desired: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Detect pre-existing signing configuration that our setup would silently
|
|
19
|
+
* override, e.g. a user who already GPG-signs commits. Only differing values
|
|
20
|
+
* count; matching values mean a previous run and are fine to reapply.
|
|
21
|
+
*
|
|
22
|
+
* `scope` selects which config layer to read: the default (undefined) reads
|
|
23
|
+
* the effective value; "local" reads only repo-local entries, which is how a
|
|
24
|
+
* `--global` write detects repo-local values that would still shadow it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectSigningConflicts(cwd: string, desired: Record<string, string>, scope?: "local" | "global"): Promise<SigningConflict[]>;
|
|
27
|
+
export interface HooksDirInfo {
|
|
28
|
+
dir: string;
|
|
29
|
+
source: "core.hooksPath" | "default";
|
|
30
|
+
}
|
|
31
|
+
export declare function getHooksDir(cwd: string): Promise<HooksDirInfo>;
|
|
32
|
+
export declare function buildTrailerHook(human: GitIdentity, agent: GitIdentity): string;
|
|
33
|
+
export type HookInstallResult = "installed" | "updated" | "unchanged" | "foreign-hook" | "unsafe-hooks-path" | "would-change";
|
|
34
|
+
/**
|
|
35
|
+
* Install (or refresh) the prepare-commit-msg trailer hook. A hook we did not
|
|
36
|
+
* write is never modified, and a core.hooksPath pointing outside the repo is
|
|
37
|
+
* never written to (a crafted repo config must not make us create files at
|
|
38
|
+
* arbitrary locations); the caller falls back to instruction-based trailers
|
|
39
|
+
* in both cases.
|
|
40
|
+
*/
|
|
41
|
+
export declare function installTrailerHook(cwd: string, human: GitIdentity, agent: GitIdentity, options?: {
|
|
42
|
+
dryRun?: boolean;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
result: HookInstallResult;
|
|
45
|
+
path: string;
|
|
46
|
+
}>;
|
|
47
|
+
//# sourceMappingURL=git-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-config.d.ts","sourceRoot":"","sources":["../../../src/utils/dco/git-config.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAUD,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM7D;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWxB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACzC,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GACzB,OAAO,CAAC,eAAe,EAAE,CAAC,CAS5B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACtC;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBpE;AAMD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,WAAW,GACjB,MAAM,CAwBR;AAED,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,SAAS,GACT,WAAW,GACX,cAAc,GACd,mBAAmB,GACnB,cAAc,CAAC;AAEnB;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA8CtD"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync } from "fs";
|
|
4
|
+
import { isAbsolute, join, resolve, sep } from "path";
|
|
5
|
+
import { writeFileAtomicSafe } from "./fs-safe.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
/** Resolve symlinks when the path exists; fall back to the input otherwise. */
|
|
8
|
+
function realpathIfExists(path) {
|
|
9
|
+
try {
|
|
10
|
+
return realpathSync(path);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return resolve(path);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export const HOOK_MARKER = "# mcpi-dco-hook";
|
|
17
|
+
async function git(args, cwd) {
|
|
18
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
19
|
+
cwd,
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
});
|
|
22
|
+
return stdout.trim();
|
|
23
|
+
}
|
|
24
|
+
export async function isGitRepo(cwd) {
|
|
25
|
+
try {
|
|
26
|
+
return (await git(["rev-parse", "--is-inside-work-tree"], cwd)) === "true";
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function getGitConfigValue(key, cwd, scope) {
|
|
33
|
+
const args = ["config"];
|
|
34
|
+
if (scope) {
|
|
35
|
+
args.push(`--${scope}`);
|
|
36
|
+
}
|
|
37
|
+
args.push("--get", key);
|
|
38
|
+
try {
|
|
39
|
+
return await git(args, cwd);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function setGitConfig(entries, options) {
|
|
46
|
+
const scope = options.global ? "--global" : "--local";
|
|
47
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
48
|
+
await git(["config", scope, key, value], options.cwd);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Detect pre-existing signing configuration that our setup would silently
|
|
53
|
+
* override, e.g. a user who already GPG-signs commits. Only differing values
|
|
54
|
+
* count; matching values mean a previous run and are fine to reapply.
|
|
55
|
+
*
|
|
56
|
+
* `scope` selects which config layer to read: the default (undefined) reads
|
|
57
|
+
* the effective value; "local" reads only repo-local entries, which is how a
|
|
58
|
+
* `--global` write detects repo-local values that would still shadow it.
|
|
59
|
+
*/
|
|
60
|
+
export async function detectSigningConflicts(cwd, desired, scope) {
|
|
61
|
+
const conflicts = [];
|
|
62
|
+
for (const [key, desiredValue] of Object.entries(desired)) {
|
|
63
|
+
const current = await getGitConfigValue(key, cwd, scope);
|
|
64
|
+
if (current !== null && current !== desiredValue) {
|
|
65
|
+
conflicts.push({ key, current, desired: desiredValue });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return conflicts;
|
|
69
|
+
}
|
|
70
|
+
export async function getHooksDir(cwd) {
|
|
71
|
+
const hooksPath = await getGitConfigValue("core.hooksPath", cwd);
|
|
72
|
+
if (hooksPath) {
|
|
73
|
+
// core.hooksPath is relative to the working-tree root, not cwd.
|
|
74
|
+
const topLevel = await git(["rev-parse", "--show-toplevel"], cwd);
|
|
75
|
+
const dir = isAbsolute(hooksPath) ? hooksPath : resolve(topLevel, hooksPath);
|
|
76
|
+
return { dir, source: "core.hooksPath" };
|
|
77
|
+
}
|
|
78
|
+
// --git-path resolves "hooks" against the COMMON git dir (where git actually
|
|
79
|
+
// runs hooks from, which matters in linked worktrees where the per-worktree
|
|
80
|
+
// git dir differs), and --path-format=absolute removes the cwd-relative
|
|
81
|
+
// ambiguity that made a bare --git-path point at the wrong directory from a
|
|
82
|
+
// subdirectory. Requires git >= 2.31, well below the >= 2.34 that SSH commit
|
|
83
|
+
// signing (the point of this feature) already needs.
|
|
84
|
+
const hooksDir = await git(["rev-parse", "--path-format=absolute", "--git-path", "hooks"], cwd);
|
|
85
|
+
return { dir: hooksDir, source: "default" };
|
|
86
|
+
}
|
|
87
|
+
function shQuote(value) {
|
|
88
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
89
|
+
}
|
|
90
|
+
export function buildTrailerHook(human, agent) {
|
|
91
|
+
const signedOffBy = shQuote(`Signed-off-by: ${human.name} <${human.email}>`);
|
|
92
|
+
const coAuthoredBy = shQuote(`Co-authored-by: ${agent.name} <${agent.email}>`);
|
|
93
|
+
return `#!/bin/sh
|
|
94
|
+
${HOOK_MARKER} v1
|
|
95
|
+
# Managed by \`mcpi dco setup\`. Appends the DCO sign-off and agent
|
|
96
|
+
# attribution trailers to commit messages when they are missing.
|
|
97
|
+
|
|
98
|
+
COMMIT_MSG_FILE="$1"
|
|
99
|
+
COMMIT_SOURCE="$2"
|
|
100
|
+
|
|
101
|
+
case "$COMMIT_SOURCE" in
|
|
102
|
+
merge|squash)
|
|
103
|
+
exit 0
|
|
104
|
+
;;
|
|
105
|
+
esac
|
|
106
|
+
|
|
107
|
+
git interpret-trailers --in-place --if-exists addIfDifferent \\
|
|
108
|
+
--trailer ${signedOffBy} \\
|
|
109
|
+
--trailer ${coAuthoredBy} \\
|
|
110
|
+
"$COMMIT_MSG_FILE"
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Install (or refresh) the prepare-commit-msg trailer hook. A hook we did not
|
|
115
|
+
* write is never modified, and a core.hooksPath pointing outside the repo is
|
|
116
|
+
* never written to (a crafted repo config must not make us create files at
|
|
117
|
+
* arbitrary locations); the caller falls back to instruction-based trailers
|
|
118
|
+
* in both cases.
|
|
119
|
+
*/
|
|
120
|
+
export async function installTrailerHook(cwd, human, agent, options = {}) {
|
|
121
|
+
const { dir, source } = await getHooksDir(cwd);
|
|
122
|
+
const hookPath = join(dir, "prepare-commit-msg");
|
|
123
|
+
if (source === "core.hooksPath") {
|
|
124
|
+
const topLevel = realpathIfExists(await git(["rev-parse", "--show-toplevel"], cwd));
|
|
125
|
+
const gitDir = realpathIfExists(await git(["rev-parse", "--absolute-git-dir"], cwd));
|
|
126
|
+
const resolvedDir = realpathIfExists(dir);
|
|
127
|
+
const inside = (parent) => resolvedDir === parent || resolvedDir.startsWith(parent + sep);
|
|
128
|
+
if (!inside(topLevel) && !inside(gitDir)) {
|
|
129
|
+
return { result: "unsafe-hooks-path", path: hookPath };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const content = buildTrailerHook(human, agent);
|
|
133
|
+
if (existsSync(hookPath)) {
|
|
134
|
+
const existing = readFileSync(hookPath, "utf-8");
|
|
135
|
+
if (!existing.includes(HOOK_MARKER)) {
|
|
136
|
+
return { result: "foreign-hook", path: hookPath };
|
|
137
|
+
}
|
|
138
|
+
if (existing === content) {
|
|
139
|
+
return { result: "unchanged", path: hookPath };
|
|
140
|
+
}
|
|
141
|
+
if (options.dryRun) {
|
|
142
|
+
return { result: "would-change", path: hookPath };
|
|
143
|
+
}
|
|
144
|
+
// Symlink-safe: hookPath lives under a possibly-untrusted repo, and a
|
|
145
|
+
// planted symlink there would otherwise be followed. 0755 so git can
|
|
146
|
+
// execute the hook.
|
|
147
|
+
writeFileAtomicSafe(hookPath, content, 0o755);
|
|
148
|
+
return { result: "updated", path: hookPath };
|
|
149
|
+
}
|
|
150
|
+
if (options.dryRun) {
|
|
151
|
+
return { result: "would-change", path: hookPath };
|
|
152
|
+
}
|
|
153
|
+
if (!existsSync(dir)) {
|
|
154
|
+
mkdirSync(dir, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
writeFileAtomicSafe(hookPath, content, 0o755);
|
|
157
|
+
return { result: "installed", path: hookPath };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=git-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-config.js","sourceRoot":"","sources":["../../../src/utils/dco/git-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around the GitHub CLI (`gh`). We deliberately shell out to gh
|
|
3
|
+
* instead of handling OAuth tokens ourselves: the user's existing gh auth is
|
|
4
|
+
* the credential, and it never passes through this process beyond gh's own
|
|
5
|
+
* environment.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SIGNING_KEY_SCOPE = "admin:ssh_signing_key";
|
|
8
|
+
export declare const SCOPE_REFRESH_COMMAND = "gh auth refresh -h github.com -s admin:ssh_signing_key";
|
|
9
|
+
export declare class GhNotInstalledError extends Error {
|
|
10
|
+
constructor();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The default gh token does not include the admin:ssh_signing_key scope, so
|
|
14
|
+
* both listing and uploading signing keys fail until the user refreshes.
|
|
15
|
+
* GitHub reports the missing scope as HTTP 404 on these endpoints.
|
|
16
|
+
*/
|
|
17
|
+
export declare class GhScopeError extends Error {
|
|
18
|
+
readonly refreshCommand = "gh auth refresh -h github.com -s admin:ssh_signing_key";
|
|
19
|
+
constructor();
|
|
20
|
+
}
|
|
21
|
+
export interface GhUser {
|
|
22
|
+
login: string;
|
|
23
|
+
id: number;
|
|
24
|
+
name?: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface SigningKeyEntry {
|
|
27
|
+
id: number;
|
|
28
|
+
key: string;
|
|
29
|
+
title: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function isGhInstalled(): Promise<boolean>;
|
|
32
|
+
export declare function isGhAuthenticated(): Promise<boolean>;
|
|
33
|
+
export declare function getGhUser(): Promise<GhUser>;
|
|
34
|
+
/** GitHub's commit-attribution noreply address for the authenticated user. */
|
|
35
|
+
export declare function githubNoreplyEmail(user: GhUser): string;
|
|
36
|
+
export declare function listSigningKeys(): Promise<SigningKeyEntry[]>;
|
|
37
|
+
export declare function uploadSigningKey(publicKeyLine: string, title: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Compare the base64 body of an authorized_keys style line against uploaded
|
|
40
|
+
* keys. GitHub returns keys without comments, so comparing the full line
|
|
41
|
+
* would always miss.
|
|
42
|
+
*/
|
|
43
|
+
export declare function signingKeyAlreadyUploaded(keys: SigningKeyEntry[], publicKeyLine: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Run `gh auth refresh` interactively so the user can grant the signing key
|
|
46
|
+
* scope without leaving the flow. Requires a TTY; the caller must check.
|
|
47
|
+
*/
|
|
48
|
+
export declare function refreshAuthScope(): Promise<boolean>;
|
|
49
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/utils/dco/github.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AACzD,eAAO,MAAM,qBAAqB,2DAA0D,CAAC;AAE7F,qBAAa,mBAAoB,SAAQ,KAAK;;CAO7C;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,cAAc,4DAAyB;;CAOjD;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AA+BD,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAOtD;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU1D;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAYjD;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAalE;AAED,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EAAE,EACvB,aAAa,EAAE,MAAM,GACpB,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAWzD"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
/**
|
|
5
|
+
* Thin wrapper around the GitHub CLI (`gh`). We deliberately shell out to gh
|
|
6
|
+
* instead of handling OAuth tokens ourselves: the user's existing gh auth is
|
|
7
|
+
* the credential, and it never passes through this process beyond gh's own
|
|
8
|
+
* environment.
|
|
9
|
+
*/
|
|
10
|
+
export const SIGNING_KEY_SCOPE = "admin:ssh_signing_key";
|
|
11
|
+
export const SCOPE_REFRESH_COMMAND = `gh auth refresh -h github.com -s ${SIGNING_KEY_SCOPE}`;
|
|
12
|
+
export class GhNotInstalledError extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("GitHub CLI (gh) is not installed. Install it from https://cli.github.com and run `gh auth login`.");
|
|
15
|
+
this.name = "GhNotInstalledError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The default gh token does not include the admin:ssh_signing_key scope, so
|
|
20
|
+
* both listing and uploading signing keys fail until the user refreshes.
|
|
21
|
+
* GitHub reports the missing scope as HTTP 404 on these endpoints.
|
|
22
|
+
*/
|
|
23
|
+
export class GhScopeError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(`Your GitHub token is missing the ${SIGNING_KEY_SCOPE} scope. Run: ${SCOPE_REFRESH_COMMAND}`);
|
|
26
|
+
this.refreshCommand = SCOPE_REFRESH_COMMAND;
|
|
27
|
+
this.name = "GhScopeError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function gh(args) {
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await execFileAsync("gh", args, {
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
35
|
+
});
|
|
36
|
+
return stdout;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error?.code === "ENOENT") {
|
|
40
|
+
throw new GhNotInstalledError();
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isScopeError(error) {
|
|
46
|
+
const err = error;
|
|
47
|
+
const message = `${err?.stderr ?? ""} ${err?.message ?? ""}`;
|
|
48
|
+
// Deliberately narrow: never key off the bare 403 status, which GitHub
|
|
49
|
+
// also uses for rate limits and SSO enforcement where scope-refresh advice
|
|
50
|
+
// would be wrong. Only the explicit scope name, HTTP 404, or a "Resource
|
|
51
|
+
// not accessible" body count as scope problems.
|
|
52
|
+
return (message.includes(SIGNING_KEY_SCOPE) ||
|
|
53
|
+
/HTTP 404/.test(message) ||
|
|
54
|
+
/Resource not accessible/i.test(message));
|
|
55
|
+
}
|
|
56
|
+
export async function isGhInstalled() {
|
|
57
|
+
try {
|
|
58
|
+
await gh(["--version"]);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function isGhAuthenticated() {
|
|
66
|
+
try {
|
|
67
|
+
await gh(["auth", "status", "--hostname", "github.com"]);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof GhNotInstalledError) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export async function getGhUser() {
|
|
78
|
+
const stdout = await gh([
|
|
79
|
+
"api",
|
|
80
|
+
"user",
|
|
81
|
+
"--jq",
|
|
82
|
+
"{login: .login, id: .id, name: .name}",
|
|
83
|
+
]);
|
|
84
|
+
const parsed = JSON.parse(stdout);
|
|
85
|
+
if (!parsed?.login || typeof parsed.id !== "number") {
|
|
86
|
+
throw new Error("Unexpected response from `gh api user`");
|
|
87
|
+
}
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
/** GitHub's commit-attribution noreply address for the authenticated user. */
|
|
91
|
+
export function githubNoreplyEmail(user) {
|
|
92
|
+
return `${user.id}+${user.login}@users.noreply.github.com`;
|
|
93
|
+
}
|
|
94
|
+
export async function listSigningKeys() {
|
|
95
|
+
try {
|
|
96
|
+
const stdout = await gh(["api", "user/ssh_signing_keys", "--paginate"]);
|
|
97
|
+
return JSON.parse(stdout);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error instanceof GhNotInstalledError) {
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
if (isScopeError(error)) {
|
|
104
|
+
throw new GhScopeError();
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function uploadSigningKey(publicKeyLine, title) {
|
|
110
|
+
try {
|
|
111
|
+
await gh([
|
|
112
|
+
"api",
|
|
113
|
+
"user/ssh_signing_keys",
|
|
114
|
+
"-f",
|
|
115
|
+
`key=${publicKeyLine}`,
|
|
116
|
+
"-f",
|
|
117
|
+
`title=${title}`,
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof GhNotInstalledError) {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
if (isScopeError(error)) {
|
|
125
|
+
throw new GhScopeError();
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Compare the base64 body of an authorized_keys style line against uploaded
|
|
132
|
+
* keys. GitHub returns keys without comments, so comparing the full line
|
|
133
|
+
* would always miss.
|
|
134
|
+
*/
|
|
135
|
+
export function signingKeyAlreadyUploaded(keys, publicKeyLine) {
|
|
136
|
+
const body = publicKeyLine.split(/\s+/)[1];
|
|
137
|
+
if (!body) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return keys.some((entry) => entry.key.split(/\s+/)[1] === body);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Run `gh auth refresh` interactively so the user can grant the signing key
|
|
144
|
+
* scope without leaving the flow. Requires a TTY; the caller must check.
|
|
145
|
+
*/
|
|
146
|
+
export async function refreshAuthScope() {
|
|
147
|
+
const { spawn } = await import("child_process");
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const child = spawn("gh", ["auth", "refresh", "-h", "github.com", "-s", SIGNING_KEY_SCOPE], { stdio: "inherit" });
|
|
150
|
+
child.on("error", () => resolve(false));
|
|
151
|
+
child.on("close", (code) => resolve(code === 0));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../../src/utils/dco/github.ts"],"names":[],"mappings":"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"}
|