@mmerterden/multi-agent-pipeline 20.17.0 → 20.17.2
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 +23 -0
- package/docs/facts.json +6 -6
- package/docs/features.md +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +85 -47
- 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 +35 -15
- package/pipeline/skills/.skills-index.json +66 -11
- package/pipeline/skills/shared/README.md +18 -13
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -2
- package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
- package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
- package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
- package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
- package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
- package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
- package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
- package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
- package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
- package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
- package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
- package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
- package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
- package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
- package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
- package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
- package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
- package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
- package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
- package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
- package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
- package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
- package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
- package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
- package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +170 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +50 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +145 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
- package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
- package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
- package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
- package/pipeline/skills/skills-index.md +16 -11
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +0 -786
|
@@ -18,15 +18,19 @@
|
|
|
18
18
|
* - It never registers when `usageLog.optOut` is true. That is permanent and
|
|
19
19
|
* checked before anything else, including any credential read (the
|
|
20
20
|
* credential store and gh).
|
|
21
|
-
* - It never registers a name it has not checked: the login sent is the
|
|
22
|
-
*
|
|
23
|
-
* (usage-identity.mjs
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* -
|
|
28
|
-
*
|
|
29
|
-
*
|
|
21
|
+
* - It never registers a name it has not checked: the login sent is the GitHub
|
|
22
|
+
* account on this machine that can read the pipeline repository
|
|
23
|
+
* (usage-identity.mjs: gh, the git credential helper, or an SSH key). No such
|
|
24
|
+
* account, no registration. The request proves the caller owns the login:
|
|
25
|
+
* a gh or credential-helper token as `Authorization: Bearer`, or for an SSH
|
|
26
|
+
* key a signature over a one-time server challenge
|
|
27
|
+
* (`ssh-keygen -Y sign`), sent with no Authorization header at all. The
|
|
28
|
+
* server pins the login to the token it issues.
|
|
29
|
+
* - It never sends a GitHub token over plain http to anything but loopback,
|
|
30
|
+
* never follows a redirect with it, never prints it, never stores it, and
|
|
31
|
+
* never puts it on an argv: gh or git prints it on its own stdout and it goes
|
|
32
|
+
* straight into the header. The credential-helper token is read again at
|
|
33
|
+
* registration rather than kept from the identity check.
|
|
30
34
|
* - It never fails a caller. Offline, endpoint down, ingest disabled by the
|
|
31
35
|
* admin, no credential store: one status line, exit 0.
|
|
32
36
|
* - It never reports run CONTENT. That is usage-report.mjs's contract, and it
|
|
@@ -35,22 +39,29 @@
|
|
|
35
39
|
* Usage:
|
|
36
40
|
* node usage-register.mjs [--prefs <path>] [--json] [--quiet] [--dry-run] [--feedback] [--refresh]
|
|
37
41
|
*
|
|
38
|
-
* `--refresh` re-checks which
|
|
39
|
-
* repository instead of using the cached `usageLog.login`.
|
|
42
|
+
* `--refresh` re-checks which GitHub account on this machine can read the
|
|
43
|
+
* pipeline repository instead of using the cached `usageLog.login`.
|
|
40
44
|
*
|
|
41
45
|
* Exit: always 0. `--json` says what happened; the shell does not need to care.
|
|
42
46
|
*
|
|
43
47
|
* @module pipeline/scripts/usage-register
|
|
44
48
|
*/
|
|
45
49
|
|
|
46
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
50
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
47
51
|
import { spawn, spawnSync } from "node:child_process";
|
|
48
52
|
import { join } from "node:path";
|
|
49
|
-
import { homedir, hostname } from "node:os";
|
|
53
|
+
import { homedir, hostname, tmpdir } from "node:os";
|
|
50
54
|
import { pathToFileURL } from "node:url";
|
|
51
55
|
import { hostCommand } from "../lib/user-facing.mjs";
|
|
52
56
|
import { canonicalEndpoint } from "../lib/usage-endpoint.mjs";
|
|
53
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
SSH_TIMEOUT_MS,
|
|
59
|
+
credentialToken,
|
|
60
|
+
displayPath,
|
|
61
|
+
ghTokenFor,
|
|
62
|
+
resolveReportingLogin,
|
|
63
|
+
sshKeygenBin,
|
|
64
|
+
} from "./usage-identity.mjs";
|
|
54
65
|
import { lockTimeoutFrom, updateJsonFileSync } from "../lib/json-file-lock.mjs";
|
|
55
66
|
|
|
56
67
|
const DEFAULT_ENDPOINT = "https://mmerterden.com/api/usage/ingest";
|
|
@@ -66,10 +77,10 @@ function readJson(file) {
|
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
/**
|
|
69
|
-
* The name this machine registers under: the
|
|
70
|
-
* read the pipeline repository (usage-identity.mjs),
|
|
71
|
-
*
|
|
72
|
-
* active.
|
|
80
|
+
* The name this machine registers under: the GitHub account on this machine
|
|
81
|
+
* that can read the pipeline repository (usage-identity.mjs), with the method
|
|
82
|
+
* it was found by, or login null when none can. Never the git identity, never
|
|
83
|
+
* the OS user, never the account gh merely has active.
|
|
73
84
|
*/
|
|
74
85
|
export function reportingUser(prefsPath, { env = process.env, refresh = false } = {}) {
|
|
75
86
|
return resolveReportingLogin({ prefsPath, env, refresh });
|
|
@@ -82,6 +93,12 @@ export function reportingUser(prefsPath, { env = process.env, refresh = false }
|
|
|
82
93
|
* them, and "you opted out" and "we could not reach the endpoint" are different
|
|
83
94
|
* facts about the same silence.
|
|
84
95
|
*/
|
|
96
|
+
function pinned(u) {
|
|
97
|
+
return (
|
|
98
|
+
typeof u.registeredAs === "string" && u.registeredAs !== "" && u.registeredFallback !== true
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
export function decideRegistration(
|
|
86
103
|
prefs,
|
|
87
104
|
{
|
|
@@ -90,7 +107,6 @@ export function decideRegistration(
|
|
|
90
107
|
forFeedback = false,
|
|
91
108
|
login = undefined,
|
|
92
109
|
loginReason = null,
|
|
93
|
-
fallback = false,
|
|
94
110
|
} = {},
|
|
95
111
|
) {
|
|
96
112
|
const g = prefs?.global || {};
|
|
@@ -103,14 +119,18 @@ export function decideRegistration(
|
|
|
103
119
|
if (env.MULTI_AGENT_USAGE_TOKEN) return { act: false, reason: "token in environment" };
|
|
104
120
|
if (typeof u.token === "string" && u.token.trim())
|
|
105
121
|
return { act: false, reason: "token in prefs" };
|
|
122
|
+
// A registered token stays pinned to its login whatever this machine holds
|
|
123
|
+
// later, so an account verified once keeps naming the runs after it is logged
|
|
124
|
+
// out. A registration marked `registeredFallback` (20.17.0) was made with an
|
|
125
|
+
// account that cannot read the repository, so it is not pinned.
|
|
126
|
+
if (hasStoredToken && login === null && pinned(u)) {
|
|
127
|
+
return { act: false, reason: "token already onboarded" };
|
|
128
|
+
}
|
|
106
129
|
if (login === null) {
|
|
107
|
-
return { act: false, reason: loginReason || "no
|
|
130
|
+
return { act: false, reason: loginReason || "no GitHub account on this machine has access" };
|
|
108
131
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// keeps naming the runs after it is logged out.
|
|
112
|
-
if (hasStoredToken && fallback && u.registeredAs) {
|
|
113
|
-
return { act: false, reason: "token already onboarded" };
|
|
132
|
+
if (hasStoredToken && login && u.registeredFallback === true) {
|
|
133
|
+
return { act: true, reason: `replacing a fallback registration with ${login}` };
|
|
114
134
|
}
|
|
115
135
|
// The server pins a token to the login it was issued for. A token issued to a
|
|
116
136
|
// different login, or before the login was verified, reports under the wrong
|
|
@@ -157,61 +177,148 @@ export function registerUrlAllowed(url, trustedHosts = []) {
|
|
|
157
177
|
return trusted.has(u.hostname.toLowerCase());
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
async function
|
|
180
|
+
async function postJson(url, body, bearer = null) {
|
|
161
181
|
const ctrl = new AbortController();
|
|
162
182
|
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
183
|
+
const headers = { "Content-Type": "application/json" };
|
|
184
|
+
if (bearer) headers.Authorization = `Bearer ${bearer}`;
|
|
163
185
|
try {
|
|
164
186
|
const res = await fetch(url, {
|
|
165
187
|
method: "POST",
|
|
166
|
-
headers
|
|
188
|
+
headers,
|
|
167
189
|
body: JSON.stringify(body),
|
|
168
190
|
redirect: "error",
|
|
169
191
|
signal: ctrl.signal,
|
|
170
192
|
});
|
|
171
193
|
const json = await res.json().catch(() => null);
|
|
172
|
-
|
|
173
|
-
const detail = typeof json?.error === "string" ? `: ${json.error.slice(0, 120)}` : "";
|
|
174
|
-
if (json?.code === "login_mismatch") {
|
|
175
|
-
return {
|
|
176
|
-
token: null,
|
|
177
|
-
why: `the gh token does not belong to ${body.u}; run \`gh auth status\` and retry with --refresh`,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
if (res.status === 401) {
|
|
181
|
-
return {
|
|
182
|
-
token: null,
|
|
183
|
-
why:
|
|
184
|
-
`endpoint answered ${res.status}${detail} - GitHub did not accept the token for ${body.u}; ` +
|
|
185
|
-
`check \`gh auth status\` lists that account, then retry with --refresh`,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (res.status === 403) {
|
|
189
|
-
return {
|
|
190
|
-
token: null,
|
|
191
|
-
why:
|
|
192
|
-
`endpoint answered 403${detail} - ${body.u} needs collaborator access to the pipeline repository; ` +
|
|
193
|
-
`add the account that has it with \`gh auth login\`, then rerun with --refresh`,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
return { token: null, why: `endpoint answered ${res.status}${detail}` };
|
|
197
|
-
}
|
|
198
|
-
if (json?.accepted === false)
|
|
199
|
-
return { token: null, declined: true, why: "the endpoint declined this login" };
|
|
200
|
-
const token = typeof json?.token === "string" ? json.token.trim() : "";
|
|
201
|
-
const pinned = typeof json?.u === "string" && json.u.trim() ? json.u.trim() : null;
|
|
202
|
-
return token
|
|
203
|
-
? { token, why: null, login: pinned }
|
|
204
|
-
: { token: null, why: "endpoint returned no token" };
|
|
194
|
+
return { res, json, failure: null };
|
|
205
195
|
} catch (e) {
|
|
206
196
|
return {
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
res: null,
|
|
198
|
+
json: null,
|
|
199
|
+
failure: e?.name === "AbortError" ? "endpoint timed out" : "endpoint unreachable",
|
|
209
200
|
};
|
|
210
201
|
} finally {
|
|
211
202
|
clearTimeout(timer);
|
|
212
203
|
}
|
|
213
204
|
}
|
|
214
205
|
|
|
206
|
+
const SSH_REFUSALS = {
|
|
207
|
+
ssh_signature_invalid: (u) =>
|
|
208
|
+
`the server could not verify the SSH signature for ${u}; retry with --refresh`,
|
|
209
|
+
ssh_key_not_on_account: (u) =>
|
|
210
|
+
`the SSH key is not on ${u}'s GitHub account; add its .pub under GitHub Settings > SSH keys, then retry with --refresh`,
|
|
211
|
+
ssh_key_unsupported: (u) =>
|
|
212
|
+
`the server does not accept this SSH key type for ${u}; put an ed25519, rsa or ecdsa-p256 key on that account, then retry with --refresh`,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
function refusal(status, json, body, method) {
|
|
216
|
+
const detail = typeof json?.error === "string" ? `: ${json.error.slice(0, 120)}` : "";
|
|
217
|
+
const code = typeof json?.code === "string" ? json.code : "";
|
|
218
|
+
if (SSH_REFUSALS[code]) return `endpoint answered ${status} - ${SSH_REFUSALS[code](body.u)}`;
|
|
219
|
+
if (code === "login_mismatch") {
|
|
220
|
+
return method === "gh"
|
|
221
|
+
? `the gh token does not belong to ${body.u}; run \`gh auth status\` and retry with --refresh`
|
|
222
|
+
: `the proof does not belong to ${body.u}; retry with --refresh`;
|
|
223
|
+
}
|
|
224
|
+
if (status === 401) {
|
|
225
|
+
const fix =
|
|
226
|
+
method === "gh"
|
|
227
|
+
? "check `gh auth status` lists that account, then retry with --refresh"
|
|
228
|
+
: "retry with --refresh";
|
|
229
|
+
return `endpoint answered ${status}${detail} - GitHub did not accept the proof for ${body.u}; ${fix}`;
|
|
230
|
+
}
|
|
231
|
+
if (status === 403) {
|
|
232
|
+
return (
|
|
233
|
+
`endpoint answered 403${detail} - ${body.u} needs collaborator access to the pipeline repository; ` +
|
|
234
|
+
"log in the account that has it (`gh auth login`, or add its SSH key), then rerun with --refresh"
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return `endpoint answered ${status}${detail}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function requestToken(url, body, proof, method) {
|
|
241
|
+
const { res, json, failure } = await postJson(url, body, proof);
|
|
242
|
+
if (failure) return { token: null, why: failure };
|
|
243
|
+
if (!res.ok) return { token: null, why: refusal(res.status, json, body, method) };
|
|
244
|
+
if (json?.accepted === false)
|
|
245
|
+
return { token: null, declined: true, why: "the endpoint declined this login" };
|
|
246
|
+
const token = typeof json?.token === "string" ? json.token.trim() : "";
|
|
247
|
+
const pinnedLogin = typeof json?.u === "string" && json.u.trim() ? json.u.trim() : null;
|
|
248
|
+
return token
|
|
249
|
+
? { token, why: null, login: pinnedLogin }
|
|
250
|
+
: { token: null, why: "endpoint returned no token" };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Sign `challenge` with the SSH key at `key` under `namespace`. The exact
|
|
255
|
+
* challenge bytes go into a 0600 file in a fresh temp dir that is removed
|
|
256
|
+
* afterwards. stdin is closed and askpass is disabled, so a key that needs a
|
|
257
|
+
* passphrase fails at once; the `.pub` is then tried, which signs through
|
|
258
|
+
* ssh-agent when the key is loaded there. Returns the armored signature or null.
|
|
259
|
+
*/
|
|
260
|
+
export function sshSign(key, namespace, challenge, env = process.env) {
|
|
261
|
+
let dir = null;
|
|
262
|
+
try {
|
|
263
|
+
dir = mkdtempSync(join(tmpdir(), "ma-usage-sign-"));
|
|
264
|
+
const file = join(dir, "challenge");
|
|
265
|
+
writeFileSync(file, challenge, { mode: 0o600 });
|
|
266
|
+
const tries = key.endsWith(".pub") ? [key] : [key, `${key}.pub`];
|
|
267
|
+
for (const f of tries) {
|
|
268
|
+
if (!existsSync(f)) continue;
|
|
269
|
+
rmSync(`${file}.sig`, { force: true });
|
|
270
|
+
const r = spawnSync(sshKeygenBin(env), ["-Y", "sign", "-f", f, "-n", namespace, file], {
|
|
271
|
+
encoding: "utf8",
|
|
272
|
+
timeout: SSH_TIMEOUT_MS,
|
|
273
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
274
|
+
env: { ...env, SSH_ASKPASS_REQUIRE: "never" },
|
|
275
|
+
});
|
|
276
|
+
if (r.status === 0 && existsSync(`${file}.sig`)) {
|
|
277
|
+
const sig = readFileSync(`${file}.sig`, "utf8");
|
|
278
|
+
if (sig.includes("BEGIN SSH SIGNATURE")) return sig;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
} finally {
|
|
285
|
+
if (dir) rmSync(dir, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** The challenge URL next to the register URL. */
|
|
290
|
+
export function challengeUrl(url) {
|
|
291
|
+
return `${url.replace(/\/+$/, "")}/challenge`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function requestTokenWithSsh(url, body, key) {
|
|
295
|
+
const ch = await postJson(challengeUrl(url), { u: body.u });
|
|
296
|
+
if (ch.failure) return { token: null, why: ch.failure };
|
|
297
|
+
if (!ch.res.ok) {
|
|
298
|
+
if (ch.res.status === 404 || ch.res.status === 405) {
|
|
299
|
+
return {
|
|
300
|
+
token: null,
|
|
301
|
+
why: `the endpoint does not accept an SSH key as proof yet (challenge answered ${ch.res.status})`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return { token: null, why: refusal(ch.res.status, ch.json, body, "ssh") };
|
|
305
|
+
}
|
|
306
|
+
const challenge = typeof ch.json?.challenge === "string" ? ch.json.challenge : "";
|
|
307
|
+
const namespace = typeof ch.json?.namespace === "string" ? ch.json.namespace : "";
|
|
308
|
+
if (!challenge || !/^[A-Za-z0-9@._-]{1,128}$/.test(namespace)) {
|
|
309
|
+
return { token: null, why: "the endpoint returned no usable SSH challenge" };
|
|
310
|
+
}
|
|
311
|
+
const signature = sshSign(key, namespace, challenge);
|
|
312
|
+
if (!signature) {
|
|
313
|
+
const shown = displayPath(key);
|
|
314
|
+
return {
|
|
315
|
+
token: null,
|
|
316
|
+
why: `could not sign with the SSH key ${shown} (it needs a passphrase and is not in ssh-agent); run \`ssh-add ${shown}\`, then retry with --refresh`,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return requestToken(url, { ...body, ssh: { challenge, signature } }, null, "ssh");
|
|
320
|
+
}
|
|
321
|
+
|
|
215
322
|
/**
|
|
216
323
|
* Write the switch, and the credential-store ENTRY NAME when there is one.
|
|
217
324
|
*
|
|
@@ -225,7 +332,7 @@ async function requestToken(url, body, proof) {
|
|
|
225
332
|
export function persistPrefs(
|
|
226
333
|
prefsPath,
|
|
227
334
|
entryName = null,
|
|
228
|
-
{ enableTelemetry = true, registeredAs = null
|
|
335
|
+
{ enableTelemetry = true, registeredAs = null } = {},
|
|
229
336
|
) {
|
|
230
337
|
try {
|
|
231
338
|
const written = updateJsonFileSync(
|
|
@@ -241,7 +348,7 @@ export function persistPrefs(
|
|
|
241
348
|
if (enableTelemetry) j.global.usageLog.enabled = true;
|
|
242
349
|
if (registeredAs) {
|
|
243
350
|
j.global.usageLog.registeredAs = registeredAs;
|
|
244
|
-
j.global.usageLog.registeredFallback
|
|
351
|
+
delete j.global.usageLog.registeredFallback;
|
|
245
352
|
j.global.usageLog.registeredAt = new Date().toISOString();
|
|
246
353
|
}
|
|
247
354
|
return j;
|
|
@@ -255,16 +362,25 @@ export function persistPrefs(
|
|
|
255
362
|
}
|
|
256
363
|
|
|
257
364
|
/**
|
|
258
|
-
* True
|
|
259
|
-
*
|
|
260
|
-
*
|
|
365
|
+
* True when this machine's runs are not pinned to a verified account AND no
|
|
366
|
+
* GitHub account on it can read the pipeline repository (no gh account, no
|
|
367
|
+
* credential-helper token and no SSH key with access). `login` is the identity
|
|
368
|
+
* check's answer when it ran; otherwise the cached `usageLog.login` stands in.
|
|
369
|
+
* Setup and update read it from `--json` and offer the fix once.
|
|
261
370
|
*/
|
|
262
|
-
export function personalAccountMissing(prefs) {
|
|
371
|
+
export function personalAccountMissing(prefs, login = undefined, env = process.env) {
|
|
263
372
|
const u = prefs?.global?.usageLog || {};
|
|
264
373
|
if (u.optOut === true) return false;
|
|
265
|
-
if (
|
|
374
|
+
if (env.MULTI_AGENT_USAGE_TOKEN) return false;
|
|
266
375
|
if (typeof u.token === "string" && u.token.trim()) return false;
|
|
267
|
-
|
|
376
|
+
if (pinned(u)) return false;
|
|
377
|
+
const found =
|
|
378
|
+
login !== undefined
|
|
379
|
+
? login
|
|
380
|
+
: u.loginFallback === true || typeof u.login !== "string"
|
|
381
|
+
? null
|
|
382
|
+
: u.login;
|
|
383
|
+
return !found;
|
|
268
384
|
}
|
|
269
385
|
|
|
270
386
|
/**
|
|
@@ -326,9 +442,10 @@ async function main(argv) {
|
|
|
326
442
|
const say = (line) => {
|
|
327
443
|
if (!opts.quiet) process.stdout.write(`${line}\n`);
|
|
328
444
|
};
|
|
445
|
+
const identity = { login: undefined };
|
|
329
446
|
const done = (status, detail) => {
|
|
330
447
|
if (opts.json) {
|
|
331
|
-
const needsPersonalAccount = personalAccountMissing(readJson(prefsPath));
|
|
448
|
+
const needsPersonalAccount = personalAccountMissing(readJson(prefsPath), identity.login);
|
|
332
449
|
process.stdout.write(`${JSON.stringify({ status, detail, needsPersonalAccount })}\n`);
|
|
333
450
|
}
|
|
334
451
|
if (opts.quiet && reportingStaysOff(status, detail)) {
|
|
@@ -358,13 +475,13 @@ async function main(argv) {
|
|
|
358
475
|
const who = identityMatters
|
|
359
476
|
? reportingUser(prefsPath, { refresh: opts.refresh })
|
|
360
477
|
: { login: undefined, reason: null };
|
|
478
|
+
identity.login = who.login;
|
|
361
479
|
const current = readJson(prefsPath) || prefs;
|
|
362
480
|
const decision = decideRegistration(current, {
|
|
363
481
|
hasStoredToken: has,
|
|
364
482
|
forFeedback: opts.feedback,
|
|
365
483
|
login: who.login,
|
|
366
484
|
loginReason: who.reason,
|
|
367
|
-
fallback: who.fallback === true,
|
|
368
485
|
});
|
|
369
486
|
if (!decision.act) {
|
|
370
487
|
const repairable = ["token in environment", "token in prefs", "token already onboarded"];
|
|
@@ -413,19 +530,22 @@ async function main(argv) {
|
|
|
413
530
|
return done("dry-run", url);
|
|
414
531
|
}
|
|
415
532
|
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
533
|
+
let result;
|
|
534
|
+
if (who.method === "ssh") {
|
|
535
|
+
result = await requestTokenWithSsh(url, { u: user, c: host }, who.key);
|
|
536
|
+
} else {
|
|
537
|
+
const proof = who.method === "credential" ? credentialToken() : ghTokenFor(user);
|
|
538
|
+
if (!proof) {
|
|
539
|
+
const why =
|
|
540
|
+
who.method === "credential"
|
|
541
|
+
? `the git credential helper no longer returns a GitHub token for ${user}; retry with --refresh`
|
|
542
|
+
: `gh has no token for ${user}; run \`gh auth login\` for that account`;
|
|
543
|
+
say(` -> operational reporting left off (${why})`);
|
|
544
|
+
return done("unavailable", why);
|
|
545
|
+
}
|
|
546
|
+
result = await requestToken(url, { u: user, c: host }, proof, who.method || "gh");
|
|
421
547
|
}
|
|
422
|
-
|
|
423
|
-
const {
|
|
424
|
-
token,
|
|
425
|
-
why,
|
|
426
|
-
declined,
|
|
427
|
-
login: pinnedLogin,
|
|
428
|
-
} = await requestToken(url, { u: user, c: host }, proof);
|
|
548
|
+
const { token, why, declined, login: pinnedLogin } = result;
|
|
429
549
|
if (declined) {
|
|
430
550
|
say(` -> operational reporting left off (${why})`);
|
|
431
551
|
return done("declined", why);
|
|
@@ -449,7 +569,6 @@ async function main(argv) {
|
|
|
449
569
|
persistPrefs(prefsPath, entry, {
|
|
450
570
|
enableTelemetry: !optedOut,
|
|
451
571
|
registeredAs: pinnedLogin || user,
|
|
452
|
-
registeredFallback: who.fallback === true,
|
|
453
572
|
});
|
|
454
573
|
if (optedOut) {
|
|
455
574
|
say(" -> feedback token registered; telemetry stays off (usageLog.optOut is set)");
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* usage-report.mjs - optional, opt-in per-run operational reporting.
|
|
3
3
|
*
|
|
4
4
|
* Off by default; a no-op unless prefs.global.usageLog.enabled is true, a
|
|
5
|
-
* token resolves, and a
|
|
6
|
-
*
|
|
5
|
+
* token resolves, and a GitHub account on this machine (gh, the git
|
|
6
|
+
* credential helper or an SSH key) can read the pipeline repository
|
|
7
|
+
* (usage-identity.mjs). Emits one compact run record keyed by run
|
|
7
8
|
* id (`taskId`), upserted at every phase transition and at completion, halt
|
|
8
9
|
* and park, fire-and-forget - never throws, never delays a run.
|
|
9
10
|
* Only coarse run metadata is included; never prompts, code, diffs, token
|
|
@@ -664,9 +665,9 @@ function appendErrorLedger(event) {
|
|
|
664
665
|
}
|
|
665
666
|
}
|
|
666
667
|
|
|
667
|
-
// Telemetry identity is the GitHub account that can read the
|
|
668
|
-
// repository (usage-identity.mjs), never the git identity.name and
|
|
669
|
-
// account gh merely has active. The server replaces it with the login pinned to
|
|
668
|
+
// Telemetry identity is the GitHub account on this machine that can read the
|
|
669
|
+
// pipeline repository (usage-identity.mjs), never the git identity.name and
|
|
670
|
+
// never the account gh merely has active. The server replaces it with the login pinned to
|
|
670
671
|
// the ingest token, so this value is informational.
|
|
671
672
|
function resolveUser(opts = {}) {
|
|
672
673
|
return resolveReportingLogin(opts).login;
|
|
@@ -843,6 +844,7 @@ async function reportInstalledVersion(endpoint, token, login, usageLog) {
|
|
|
843
844
|
* no gh call to name its runs, and keeps the name after that account logs out.
|
|
844
845
|
*/
|
|
845
846
|
function registeredLogin(prefs) {
|
|
847
|
+
if (prefs?.registeredFallback === true) return null;
|
|
846
848
|
const login = typeof prefs?.registeredAs === "string" ? prefs.registeredAs.trim() : "";
|
|
847
849
|
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,38}$/.test(login) ? { login, reason: "registered" } : null;
|
|
848
850
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
4
|
-
"skillCount":
|
|
3
|
+
"generatedAt": "2026-10-08T12:53:56Z",
|
|
4
|
+
"skillCount": 228,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
7
|
"path": "shared/core/apple-archive-compliance/SKILL.md",
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"path": "shared/core/multi-agent-setup/SKILL.md",
|
|
216
|
-
"sha256": "
|
|
216
|
+
"sha256": "427df811e5130da1ab6e23ff78202eb9134fb76bcd601924fba5668cbd630158"
|
|
217
217
|
},
|
|
218
218
|
{
|
|
219
219
|
"path": "shared/core/multi-agent-stack/SKILL.md",
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"path": "shared/core/multi-agent-update/SKILL.md",
|
|
268
|
-
"sha256": "
|
|
268
|
+
"sha256": "013317432263490a01d081b657928b977012afb78f75a64d278d94d2daa8e894"
|
|
269
269
|
},
|
|
270
270
|
{
|
|
271
271
|
"path": "shared/core/multi-agent/SKILL.md",
|
|
@@ -495,17 +495,21 @@
|
|
|
495
495
|
"path": "shared/external/healthkit/SKILL.md",
|
|
496
496
|
"sha256": "fece1986bc35925e34fed8d8cb13aa59a3577b8cedefdc02c736c0d4a5d38aff"
|
|
497
497
|
},
|
|
498
|
+
{
|
|
499
|
+
"path": "shared/external/hig-app-icons/SKILL.md",
|
|
500
|
+
"sha256": "70168a30347b1c51ff72ed30b54443add2e1ad63a6aae0aa79bb32bd87d00c4d"
|
|
501
|
+
},
|
|
498
502
|
{
|
|
499
503
|
"path": "shared/external/hig-components-content/SKILL.md",
|
|
500
504
|
"sha256": "910e4e24c47a3e103b3d398a1c49684f0990966423e2ea2d871111d6251d8a6c"
|
|
501
505
|
},
|
|
502
506
|
{
|
|
503
507
|
"path": "shared/external/hig-components-layout/SKILL.md",
|
|
504
|
-
"sha256": "
|
|
508
|
+
"sha256": "dde0a923736255aa2e5de791700fc51945d3c63f41fc7ec15e252953bd7cf168"
|
|
505
509
|
},
|
|
506
510
|
{
|
|
507
511
|
"path": "shared/external/hig-components-status/SKILL.md",
|
|
508
|
-
"sha256": "
|
|
512
|
+
"sha256": "8458f7de5e4f3407e162ffe5c4831ce7fb3e93ee4922bfb4d45444cfa31f59fb"
|
|
509
513
|
},
|
|
510
514
|
{
|
|
511
515
|
"path": "shared/external/hig-components-system/SKILL.md",
|
|
@@ -513,19 +517,19 @@
|
|
|
513
517
|
},
|
|
514
518
|
{
|
|
515
519
|
"path": "shared/external/hig-foundations/SKILL.md",
|
|
516
|
-
"sha256": "
|
|
520
|
+
"sha256": "e0a345ef1e25b9741dc848d9aac9c5bb045a69034b22348036d769adb8441f06"
|
|
517
521
|
},
|
|
518
522
|
{
|
|
519
523
|
"path": "shared/external/hig-inputs/SKILL.md",
|
|
520
|
-
"sha256": "
|
|
524
|
+
"sha256": "ec8ec26f5a4469baa72aaa59def64a44891b1655d1ef6a1936bd4d29d926b290"
|
|
521
525
|
},
|
|
522
526
|
{
|
|
523
527
|
"path": "shared/external/hig-patterns/SKILL.md",
|
|
524
|
-
"sha256": "
|
|
528
|
+
"sha256": "771a08a0ba8d6268ea4139fc2cde4a99750cb2c31a78b755a41fae78efd742c9"
|
|
525
529
|
},
|
|
526
530
|
{
|
|
527
531
|
"path": "shared/external/hig-platforms/SKILL.md",
|
|
528
|
-
"sha256": "
|
|
532
|
+
"sha256": "647da7ec54103b5b93198506e254d7468150fc24b880128a1bab7856390eaf25"
|
|
529
533
|
},
|
|
530
534
|
{
|
|
531
535
|
"path": "shared/external/hig-technologies/SKILL.md",
|
|
@@ -545,7 +549,7 @@
|
|
|
545
549
|
},
|
|
546
550
|
{
|
|
547
551
|
"path": "shared/external/ios-accessibility/SKILL.md",
|
|
548
|
-
"sha256": "
|
|
552
|
+
"sha256": "8dc07ddb1e18d0d0572967ee77b39ffe36342ffc781ffb9a3910c4a2d8ee41fe"
|
|
549
553
|
},
|
|
550
554
|
{
|
|
551
555
|
"path": "shared/external/ios-coding-standard/SKILL.md",
|
|
@@ -555,6 +559,18 @@
|
|
|
555
559
|
"path": "shared/external/ios-debugger-agent/SKILL.md",
|
|
556
560
|
"sha256": "2126e7211042209922a848d5e24589b893c4d3f5da658d81c166e5c1347f4b3f"
|
|
557
561
|
},
|
|
562
|
+
{
|
|
563
|
+
"path": "shared/external/ios-design-review/SKILL.md",
|
|
564
|
+
"sha256": "cdef9893d29fded21c87d39cc30075d7b21c79d07b6cca5386e6e1fca35deb2a"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
"path": "shared/external/ios-haptics/SKILL.md",
|
|
568
|
+
"sha256": "5edfc54f3d7733b6c48d1c9d30a3bbd30a0d5437be1dd6427e817b6e81f18150"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
"path": "shared/external/ios-iphone-duo/SKILL.md",
|
|
572
|
+
"sha256": "2da65582a8d86957ded84345e60095584a26942f9ded8f61e901f3d04fb66c37"
|
|
573
|
+
},
|
|
558
574
|
{
|
|
559
575
|
"path": "shared/external/ios-localization/SKILL.md",
|
|
560
576
|
"sha256": "3ee1a34bbf6c0b77e1e9f16cb7d8bc3d10f2ba7bf6d0b48e64126afcf9dbe2b6"
|
|
@@ -571,6 +587,10 @@
|
|
|
571
587
|
"path": "shared/external/ios-security/SKILL.md",
|
|
572
588
|
"sha256": "48924d4faea93fdf435851cbc77ba26cb33f3f8256954787af539f410f64d5c0"
|
|
573
589
|
},
|
|
590
|
+
{
|
|
591
|
+
"path": "shared/external/ios-sf-symbols/SKILL.md",
|
|
592
|
+
"sha256": "5fa9333a5d9699efba6389a9529aca88a38e2d8de873a129d4fead809143f1ed"
|
|
593
|
+
},
|
|
574
594
|
{
|
|
575
595
|
"path": "shared/external/ios-simulator/SKILL.md",
|
|
576
596
|
"sha256": "f7cb08384d448e3e73b0373e19f3ee1eca90b43eb7a3e3ac5d59a74035c0fc14"
|
|
@@ -773,11 +793,11 @@
|
|
|
773
793
|
},
|
|
774
794
|
{
|
|
775
795
|
"path": "shared/external/swiftui-animation/SKILL.md",
|
|
776
|
-
"sha256": "
|
|
796
|
+
"sha256": "d67e447c1b2cc002f007b46f68a15a1181a13d5ed16078b79333ebf48d31b329"
|
|
777
797
|
},
|
|
778
798
|
{
|
|
779
799
|
"path": "shared/external/swiftui-expert-skill/SKILL.md",
|
|
780
|
-
"sha256": "
|
|
800
|
+
"sha256": "c6d5ee097036edf12c6c1b5aed64b01bdafb2eb240169c82de2e951b33997cf6"
|
|
781
801
|
},
|
|
782
802
|
{
|
|
783
803
|
"path": "shared/external/swiftui-gestures/SKILL.md",
|
|
@@ -789,7 +809,7 @@
|
|
|
789
809
|
},
|
|
790
810
|
{
|
|
791
811
|
"path": "shared/external/swiftui-liquid-glass/SKILL.md",
|
|
792
|
-
"sha256": "
|
|
812
|
+
"sha256": "555aa9c8178f4f14484ff8adee57ea80621be48b6b0628fe9852277e37349048"
|
|
793
813
|
},
|
|
794
814
|
{
|
|
795
815
|
"path": "shared/external/swiftui-navigation/SKILL.md",
|
|
@@ -797,7 +817,7 @@
|
|
|
797
817
|
},
|
|
798
818
|
{
|
|
799
819
|
"path": "shared/external/swiftui-patterns/SKILL.md",
|
|
800
|
-
"sha256": "
|
|
820
|
+
"sha256": "0cea41552443a7ee86928b7666ca99c76447a56d1e8f54cca1292e063d966730"
|
|
801
821
|
},
|
|
802
822
|
{
|
|
803
823
|
"path": "shared/external/swiftui-performance-audit/SKILL.md",
|