@mmerterden/multi-agent-pipeline 20.17.0 → 20.17.1
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/CHANGELOG.md +12 -0
- package/docs/facts.json +1 -1
- package/docs/features.md +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +18 -18
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/update/SKILL.md +2 -2
- package/pipeline/multi-agent-refs/features/usage-reporting.md +106 -67
- package/pipeline/schemas/prefs.schema.json +23 -2
- package/pipeline/scripts/usage-identity.mjs +312 -53
- package/pipeline/scripts/usage-register.mjs +204 -85
- package/pipeline/scripts/usage-report.mjs +7 -5
- package/pipeline/skills/.skill-manifest.json +3 -3
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -2
|
@@ -2,30 +2,44 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* usage-identity.mjs - the GitHub account this machine reports usage under.
|
|
4
4
|
*
|
|
5
|
-
* The reporting identity is the
|
|
6
|
-
* pipeline repository (`usageLog.accessRepo`, default
|
|
5
|
+
* The reporting identity is the GitHub account on this machine that can read
|
|
6
|
+
* the pipeline repository (`usageLog.accessRepo`, default
|
|
7
7
|
* mmerterden/multi-agent-pipeline). Access to that repository is what grants
|
|
8
8
|
* use of the pipeline, so it is also what decides whose name a run carries.
|
|
9
9
|
* The account `gh` happens to have active is not used as such: a machine with
|
|
10
|
-
* a work account and a personal account
|
|
11
|
-
*
|
|
10
|
+
* a work account and a personal account reports under whichever of the two
|
|
11
|
+
* actually holds access.
|
|
12
12
|
*
|
|
13
|
-
* Resolution
|
|
14
|
-
* in order `usageLog.reportAs` (when it is one of them), the active account,
|
|
15
|
-
* then the rest. Each check runs `gh api repos/<accessRepo>` with that
|
|
16
|
-
* account's token in the child's environment (GH_TOKEN), never on argv and
|
|
17
|
-
* never written anywhere. The first account that can read the repository wins.
|
|
13
|
+
* Resolution stops at the first account that can read the repository:
|
|
18
14
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* 1. gh: every github.com account `gh auth status` lists, in order
|
|
16
|
+
* `usageLog.reportAs` (when it is one of them), the active account, then
|
|
17
|
+
* the rest. Each check runs `gh api repos/<accessRepo>` with that
|
|
18
|
+
* account's token in the child's environment (GH_TOKEN).
|
|
19
|
+
* 2. credential: the token git's credential helper holds for
|
|
20
|
+
* https://github.com (`git credential fill`, prompts disabled). Its login
|
|
21
|
+
* comes from `GET /user`, its access from `GET /repos/<accessRepo>`, both
|
|
22
|
+
* through gh with GH_TOKEN, or straight to the API when gh is missing.
|
|
23
|
+
* 3. ssh: each private key `ssh -G github.com` names, plus ~/.ssh/id_ed25519,
|
|
24
|
+
* id_ecdsa and id_rsa, when its `.pub` sits next to it. `ssh -T` with only
|
|
25
|
+
* that key names the login ("Hi <login>!"), and
|
|
26
|
+
* `git ls-remote git@github.com:<accessRepo>.git HEAD` with the same key
|
|
27
|
+
* proves access. Batch mode throughout, so a key that needs a passphrase
|
|
28
|
+
* and is not in ssh-agent is skipped rather than prompted for.
|
|
25
29
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* No token is ever put on an argv, printed or written anywhere. A missing gh,
|
|
31
|
+
* git or ssh binary, or any failed check, only means "no candidate".
|
|
32
|
+
*
|
|
33
|
+
* When no account qualifies the login is null and `reason` says what was tried
|
|
34
|
+
* in one line; nothing is reported under another account.
|
|
35
|
+
*
|
|
36
|
+
* The result is cached in prefs as `usageLog.login`, `usageLog.loginMethod`
|
|
37
|
+
* (gh | credential | ssh), `usageLog.loginKey` (the key path, ssh only),
|
|
38
|
+
* `usageLog.loginRepo`, `usageLog.loginCheckedAt` and `usageLog.loginReason`
|
|
39
|
+
* (why none qualified), re-checked after LOGIN_TTL_MS, after NO_LOGIN_TTL_MS
|
|
40
|
+
* when no account had access, when the cached key file is gone, or on
|
|
41
|
+
* `--refresh`. A cache entry marked `usageLog.loginFallback` (written by
|
|
42
|
+
* 20.17.0) is never served.
|
|
29
43
|
*
|
|
30
44
|
* Usage:
|
|
31
45
|
* node usage-identity.mjs [--refresh] [--prefs <path>] [--json]
|
|
@@ -37,7 +51,7 @@
|
|
|
37
51
|
|
|
38
52
|
import { existsSync, readFileSync } from "node:fs";
|
|
39
53
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
40
|
-
import { homedir } from "node:os";
|
|
54
|
+
import { homedir, userInfo } from "node:os";
|
|
41
55
|
import { join } from "node:path";
|
|
42
56
|
import { pathToFileURL } from "node:url";
|
|
43
57
|
import { lockTimeoutFrom, updateJsonFileSync } from "../lib/json-file-lock.mjs";
|
|
@@ -46,7 +60,13 @@ export const ACCESS_REPO_DEFAULT = "mmerterden/multi-agent-pipeline";
|
|
|
46
60
|
export const LOGIN_TTL_MS = 7 * 24 * 3_600_000;
|
|
47
61
|
export const NO_LOGIN_TTL_MS = 24 * 3_600_000;
|
|
48
62
|
const GH_TIMEOUT_MS = 6000;
|
|
63
|
+
export const SSH_TIMEOUT_MS = 12000;
|
|
64
|
+
const SSH_CONNECT_TIMEOUT_S = 8;
|
|
65
|
+
const MAX_SSH_KEYS = 8;
|
|
49
66
|
const HOST = "github.com";
|
|
67
|
+
const API_DEFAULT = "https://api.github.com";
|
|
68
|
+
const DEFAULT_KEYS = ["id_ed25519", "id_ecdsa", "id_rsa"];
|
|
69
|
+
export const LOGIN_METHODS = ["gh", "credential", "ssh"];
|
|
50
70
|
const LOGIN_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,38}$/;
|
|
51
71
|
const REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
52
72
|
|
|
@@ -66,6 +86,28 @@ function ghBin(env) {
|
|
|
66
86
|
return env.MA_GH_BIN || "gh";
|
|
67
87
|
}
|
|
68
88
|
|
|
89
|
+
function gitBin(env) {
|
|
90
|
+
return env.MA_GIT_BIN || "git";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sshBin(env) {
|
|
94
|
+
return env.MA_SSH_BIN || "ssh";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function sshKeygenBin(env = process.env) {
|
|
98
|
+
return env.MA_SSH_KEYGEN_BIN || "ssh-keygen";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function homeOf(env) {
|
|
102
|
+
return env.HOME || homedir();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** `~/...` for a path under the home directory, for status lines. */
|
|
106
|
+
export function displayPath(path, env = process.env) {
|
|
107
|
+
const home = homeOf(env);
|
|
108
|
+
return path && home && path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
|
|
109
|
+
}
|
|
110
|
+
|
|
69
111
|
export function accessRepoOf(prefs) {
|
|
70
112
|
const r = prefs?.global?.usageLog?.accessRepo;
|
|
71
113
|
return typeof r === "string" && REPO_RE.test(r.trim()) ? r.trim() : ACCESS_REPO_DEFAULT;
|
|
@@ -164,9 +206,182 @@ function orderCandidates(accounts, preferred) {
|
|
|
164
206
|
}
|
|
165
207
|
|
|
166
208
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
209
|
+
* The token git's credential helper holds for https://github.com, or "".
|
|
210
|
+
* Prompts are disabled (terminal, askpass and helper UI), so a
|
|
211
|
+
* machine without a stored credential answers at once. The token is read from
|
|
212
|
+
* the helper's stdout and only ever passed on in a child's environment.
|
|
213
|
+
*/
|
|
214
|
+
export function credentialToken(env = process.env) {
|
|
215
|
+
const r = spawnSync(gitBin(env), ["credential", "fill"], {
|
|
216
|
+
input: `protocol=https\nhost=${HOST}\n\n`,
|
|
217
|
+
encoding: "utf8",
|
|
218
|
+
timeout: GH_TIMEOUT_MS,
|
|
219
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
220
|
+
env: {
|
|
221
|
+
...env,
|
|
222
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
223
|
+
GCM_INTERACTIVE: "never",
|
|
224
|
+
GIT_ASKPASS: "",
|
|
225
|
+
SSH_ASKPASS: "",
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
if (r.status !== 0 || typeof r.stdout !== "string") return "";
|
|
229
|
+
const line = r.stdout.split("\n").find((l) => l.startsWith("password="));
|
|
230
|
+
return line ? line.slice("password=".length).trim() : "";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function apiBase(env) {
|
|
234
|
+
const v = String(env.MA_GITHUB_API_URL || "");
|
|
235
|
+
return /^http:\/\/(127\.0\.0\.1|localhost)(:\d+)?$/.test(v) ? v : API_DEFAULT;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const API_CHECK = `
|
|
239
|
+
const base = process.env.MA_CHECK_API;
|
|
240
|
+
const repo = process.env.MA_CHECK_REPO;
|
|
241
|
+
const headers = {
|
|
242
|
+
Authorization: "Bearer " + process.env.MA_CHECK_TOKEN,
|
|
243
|
+
Accept: "application/vnd.github+json",
|
|
244
|
+
"User-Agent": "multi-agent-pipeline",
|
|
245
|
+
};
|
|
246
|
+
const get = (path) =>
|
|
247
|
+
fetch(base + path, { headers, redirect: "error", signal: AbortSignal.timeout(${GH_TIMEOUT_MS}) });
|
|
248
|
+
(async () => {
|
|
249
|
+
const u = await get("/user");
|
|
250
|
+
if (!u.ok) process.exit(1);
|
|
251
|
+
const login = (await u.json())?.login;
|
|
252
|
+
const r = await get("/repos/" + repo);
|
|
253
|
+
if (!r.ok) process.exit(1);
|
|
254
|
+
process.stdout.write(String(login || ""));
|
|
255
|
+
})().catch(() => process.exit(1));
|
|
256
|
+
`;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The login a token belongs to, when that login can read `repo`; else null.
|
|
260
|
+
* Through gh when it is installed, otherwise a direct API call from a child
|
|
261
|
+
* node process. Either way the token travels in the child's environment.
|
|
262
|
+
*/
|
|
263
|
+
function tokenLoginWithAccess(token, repo, env) {
|
|
264
|
+
const who = spawnSync(ghBin(env), ["api", "user", "--jq", ".login"], {
|
|
265
|
+
encoding: "utf8",
|
|
266
|
+
timeout: GH_TIMEOUT_MS,
|
|
267
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
268
|
+
env: { ...env, GH_TOKEN: token, GH_HOST: HOST, GITHUB_TOKEN: "" },
|
|
269
|
+
});
|
|
270
|
+
if (who.error?.code === "ENOENT") {
|
|
271
|
+
const r = spawnSync(process.execPath, ["-e", API_CHECK], {
|
|
272
|
+
encoding: "utf8",
|
|
273
|
+
timeout: GH_TIMEOUT_MS * 2,
|
|
274
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
275
|
+
env: { ...env, MA_CHECK_TOKEN: token, MA_CHECK_REPO: repo, MA_CHECK_API: apiBase(env) },
|
|
276
|
+
});
|
|
277
|
+
const login = (r.stdout || "").trim();
|
|
278
|
+
return r.status === 0 && LOGIN_RE.test(login) ? login : null;
|
|
279
|
+
}
|
|
280
|
+
const login = (who.stdout || "").trim();
|
|
281
|
+
if (who.status !== 0 || !LOGIN_RE.test(login)) return null;
|
|
282
|
+
return canRead(repo, token, env) ? login : null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function shQuote(s) {
|
|
286
|
+
return `'${String(s).replace(/'/g, "'\\''")}'`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const SSH_FLAGS = [
|
|
290
|
+
"-o",
|
|
291
|
+
"IdentitiesOnly=yes",
|
|
292
|
+
"-o",
|
|
293
|
+
"BatchMode=yes",
|
|
294
|
+
"-o",
|
|
295
|
+
`ConnectTimeout=${SSH_CONNECT_TIMEOUT_S}`,
|
|
296
|
+
"-o",
|
|
297
|
+
"StrictHostKeyChecking=accept-new",
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
function sshEnv(env) {
|
|
301
|
+
return { ...env, SSH_ASKPASS_REQUIRE: "never", GIT_TERMINAL_PROMPT: "0" };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function expandIdentity(raw, env) {
|
|
305
|
+
const home = homeOf(env);
|
|
306
|
+
let user = env.USER || "";
|
|
307
|
+
if (!user) {
|
|
308
|
+
try {
|
|
309
|
+
user = userInfo().username;
|
|
310
|
+
} catch {
|
|
311
|
+
user = "";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const parts = raw
|
|
315
|
+
.trim()
|
|
316
|
+
.replace(/^"(.*)"$/, "$1")
|
|
317
|
+
.replace(/^~(?=\/|$)/, home)
|
|
318
|
+
.split("%%")
|
|
319
|
+
.map((part) => part.replace(/%d/g, home).replace(/%u/g, user).replace(/%h/g, HOST));
|
|
320
|
+
return parts.some((part) => part.includes("%")) ? null : parts.join("%");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function usableKey(path) {
|
|
324
|
+
if (!path) return false;
|
|
325
|
+
if (path.endsWith(".pub")) return existsSync(path);
|
|
326
|
+
return existsSync(path) && existsSync(`${path}.pub`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Private-key paths worth trying for github.com: what `ssh -G github.com`
|
|
331
|
+
* names, then the default key names under ~/.ssh, each kept only when its
|
|
332
|
+
* `.pub` exists (a `.pub` named directly stands for a key held by an agent).
|
|
333
|
+
* Empty when ssh is not installed.
|
|
334
|
+
*/
|
|
335
|
+
export function sshKeyCandidates(env = process.env) {
|
|
336
|
+
const r = spawnSync(sshBin(env), ["-G", HOST], {
|
|
337
|
+
encoding: "utf8",
|
|
338
|
+
timeout: GH_TIMEOUT_MS,
|
|
339
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
340
|
+
env,
|
|
341
|
+
});
|
|
342
|
+
if (r.error) return [];
|
|
343
|
+
const listed = [];
|
|
344
|
+
for (const line of (r.stdout || "").split("\n")) {
|
|
345
|
+
const m = line.match(/^identityfile\s+(.+)$/i);
|
|
346
|
+
if (m) listed.push(expandIdentity(m[1], env));
|
|
347
|
+
}
|
|
348
|
+
const defaults = DEFAULT_KEYS.map((n) => join(homeOf(env), ".ssh", n));
|
|
349
|
+
const out = [];
|
|
350
|
+
for (const p of [...listed, ...defaults]) {
|
|
351
|
+
if (p && !out.includes(p) && usableKey(p)) out.push(p);
|
|
352
|
+
}
|
|
353
|
+
return out.slice(0, MAX_SSH_KEYS);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** The GitHub login an SSH key authenticates as, from `ssh -T`; else null. */
|
|
357
|
+
export function sshLogin(key, env = process.env) {
|
|
358
|
+
const r = spawnSync(sshBin(env), ["-i", key, ...SSH_FLAGS, "-T", `git@${HOST}`], {
|
|
359
|
+
encoding: "utf8",
|
|
360
|
+
timeout: SSH_TIMEOUT_MS,
|
|
361
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
362
|
+
env: sshEnv(env),
|
|
363
|
+
});
|
|
364
|
+
if (r.error) return null;
|
|
365
|
+
const m = `${r.stderr || ""}\n${r.stdout || ""}`.match(/Hi ([^\s!]+)!/);
|
|
366
|
+
return m && LOGIN_RE.test(m[1]) ? m[1] : null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function sshCanRead(key, repo, env) {
|
|
370
|
+
const command = [shQuote(sshBin(env)), "-i", shQuote(key), ...SSH_FLAGS].join(" ");
|
|
371
|
+
const r = spawnSync(gitBin(env), ["ls-remote", `git@${HOST}:${repo}.git`, "HEAD"], {
|
|
372
|
+
encoding: "utf8",
|
|
373
|
+
timeout: SSH_TIMEOUT_MS,
|
|
374
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
375
|
+
env: { ...sshEnv(env), GIT_SSH_COMMAND: command },
|
|
376
|
+
});
|
|
377
|
+
return r.status === 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check the accounts on this machine against the access repository, live, in
|
|
382
|
+
* the order gh, git credential helper, SSH keys. Returns
|
|
383
|
+
* { login, method, key, reason }: the first account with access, else login
|
|
384
|
+
* null with a one-line reason naming what was tried.
|
|
170
385
|
*/
|
|
171
386
|
export function checkAccess({
|
|
172
387
|
repo = ACCESS_REPO_DEFAULT,
|
|
@@ -174,33 +389,71 @@ export function checkAccess({
|
|
|
174
389
|
env = process.env,
|
|
175
390
|
} = {}) {
|
|
176
391
|
const accounts = listAccounts({ env });
|
|
177
|
-
|
|
392
|
+
for (const a of orderCandidates(accounts, preferred)) {
|
|
393
|
+
const token = tokenFor(a.login, env);
|
|
394
|
+
if (token && canRead(repo, token, env)) {
|
|
395
|
+
return { login: a.login, method: "gh", key: null, reason: `can read ${repo}` };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const cred = credentialToken(env);
|
|
400
|
+
const credLogin = cred ? tokenLoginWithAccess(cred, repo, env) : null;
|
|
401
|
+
if (credLogin) {
|
|
178
402
|
return {
|
|
179
|
-
login:
|
|
180
|
-
|
|
181
|
-
|
|
403
|
+
login: credLogin,
|
|
404
|
+
method: "credential",
|
|
405
|
+
key: null,
|
|
406
|
+
reason: `can read ${repo}`,
|
|
182
407
|
};
|
|
183
408
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
409
|
+
|
|
410
|
+
const keys = sshKeyCandidates(env);
|
|
411
|
+
const sshLogins = [];
|
|
412
|
+
for (const key of keys) {
|
|
413
|
+
const login = sshLogin(key, env);
|
|
414
|
+
if (!login) continue;
|
|
415
|
+
sshLogins.push(login);
|
|
416
|
+
if (sshCanRead(key, repo, env)) {
|
|
417
|
+
return {
|
|
418
|
+
login,
|
|
419
|
+
method: "ssh",
|
|
420
|
+
key,
|
|
421
|
+
reason: `can read ${repo}`,
|
|
422
|
+
};
|
|
189
423
|
}
|
|
190
424
|
}
|
|
191
|
-
|
|
425
|
+
|
|
426
|
+
const ghPart = accounts.length ? accounts.map((a) => a.login).join(", ") : "none";
|
|
427
|
+
const credPart = cred ? "a token without access" : "none";
|
|
428
|
+
const sshPart = keys.length
|
|
429
|
+
? `${keys.length} tried${sshLogins.length ? ` (${[...new Set(sshLogins)].join(", ")})` : ""}`
|
|
430
|
+
: "none";
|
|
192
431
|
return {
|
|
193
|
-
login:
|
|
194
|
-
|
|
195
|
-
|
|
432
|
+
login: null,
|
|
433
|
+
method: null,
|
|
434
|
+
key: null,
|
|
435
|
+
reason: `no GitHub account on this machine can read ${repo} (gh: ${ghPart}; git credential: ${credPart}; SSH keys: ${sshPart})`,
|
|
196
436
|
};
|
|
197
437
|
}
|
|
198
438
|
|
|
439
|
+
function cachedMethod(u) {
|
|
440
|
+
if (u.loginMethod === undefined || u.loginMethod === null) return "gh";
|
|
441
|
+
return LOGIN_METHODS.includes(u.loginMethod) ? u.loginMethod : null;
|
|
442
|
+
}
|
|
443
|
+
|
|
199
444
|
function cacheFresh(u, repo, now) {
|
|
200
445
|
const at = Date.parse(u?.loginCheckedAt || "");
|
|
201
446
|
if (Number.isNaN(at)) return false;
|
|
202
447
|
if ((u.loginRepo || ACCESS_REPO_DEFAULT) !== repo) return false;
|
|
203
|
-
|
|
448
|
+
if (u.loginFallback === true) return false;
|
|
449
|
+
if (u.login) {
|
|
450
|
+
const method = cachedMethod(u);
|
|
451
|
+
if (!method) return false;
|
|
452
|
+
if (method === "ssh" && !(typeof u.loginKey === "string" && usableKey(u.loginKey))) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const ttl = u.login ? LOGIN_TTL_MS : NO_LOGIN_TTL_MS;
|
|
204
457
|
return now - at < ttl && now >= at;
|
|
205
458
|
}
|
|
206
459
|
|
|
@@ -238,20 +491,20 @@ export function resolveReportingLogin({
|
|
|
238
491
|
const repo = accessRepoOf(prefs);
|
|
239
492
|
if (!refresh && cacheFresh(u, repo, now)) {
|
|
240
493
|
if (typeof u.login === "string" && LOGIN_RE.test(u.login)) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
return { login: u.login, fallback: false, reason: `cached: can read ${repo}`, cached: true };
|
|
494
|
+
const method = cachedMethod(u);
|
|
495
|
+
return {
|
|
496
|
+
login: u.login,
|
|
497
|
+
method,
|
|
498
|
+
key: method === "ssh" ? u.loginKey : null,
|
|
499
|
+
reason: `cached: can read ${repo}`,
|
|
500
|
+
cached: true,
|
|
501
|
+
};
|
|
250
502
|
}
|
|
251
503
|
return {
|
|
252
504
|
login: null,
|
|
253
|
-
|
|
254
|
-
|
|
505
|
+
method: null,
|
|
506
|
+
key: null,
|
|
507
|
+
reason: u.loginReason || `cached: no GitHub account on this machine can read ${repo}`,
|
|
255
508
|
cached: true,
|
|
256
509
|
};
|
|
257
510
|
}
|
|
@@ -262,10 +515,12 @@ export function resolveReportingLogin({
|
|
|
262
515
|
prefsPath,
|
|
263
516
|
{
|
|
264
517
|
login: result.login,
|
|
265
|
-
|
|
518
|
+
loginMethod: result.method,
|
|
519
|
+
loginKey: result.method === "ssh" ? result.key : null,
|
|
520
|
+
loginFallback: undefined,
|
|
266
521
|
loginRepo: repo,
|
|
267
522
|
loginCheckedAt: new Date(now).toISOString(),
|
|
268
|
-
loginReason: result.login
|
|
523
|
+
loginReason: result.login ? null : result.reason,
|
|
269
524
|
},
|
|
270
525
|
env,
|
|
271
526
|
);
|
|
@@ -287,12 +542,16 @@ function main(argv) {
|
|
|
287
542
|
});
|
|
288
543
|
if (opts.json) {
|
|
289
544
|
process.stdout.write(
|
|
290
|
-
`${JSON.stringify({ login: r.login,
|
|
545
|
+
`${JSON.stringify({ login: r.login, method: r.method, key: r.key, reason: r.reason, cached: r.cached })}\n`,
|
|
291
546
|
);
|
|
292
|
-
} else if (r.login && r.fallback) {
|
|
293
|
-
process.stdout.write(`usage identity: ${r.login} (fallback - ${r.reason})\n`);
|
|
294
547
|
} else if (r.login) {
|
|
295
|
-
|
|
548
|
+
const via =
|
|
549
|
+
r.method === "ssh"
|
|
550
|
+
? `SSH key ${displayPath(r.key)}`
|
|
551
|
+
: r.method === "credential"
|
|
552
|
+
? "git credential helper"
|
|
553
|
+
: "gh";
|
|
554
|
+
process.stdout.write(`usage identity: ${r.login} (${via}; ${r.reason})\n`);
|
|
296
555
|
} else {
|
|
297
556
|
process.stdout.write(`usage identity: none - ${r.reason}; nothing will be reported\n`);
|
|
298
557
|
}
|