@kya-os/cli 1.8.1 → 2.0.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.
Files changed (154) hide show
  1. package/dist/commands/build.js +2 -2
  2. package/dist/commands/build.js.map +1 -1
  3. package/dist/commands/check.js +3 -3
  4. package/dist/commands/check.js.map +1 -1
  5. package/dist/commands/checkpoint-init.d.ts +1 -1
  6. package/dist/commands/checkpoint-init.d.ts.map +1 -1
  7. package/dist/commands/checkpoint-init.js +8 -41
  8. package/dist/commands/checkpoint-init.js.map +1 -1
  9. package/dist/commands/claim.js +2 -2
  10. package/dist/commands/claim.js.map +1 -1
  11. package/dist/commands/dco.d.ts +38 -1
  12. package/dist/commands/dco.d.ts.map +1 -1
  13. package/dist/commands/dco.js +180 -7
  14. package/dist/commands/dco.js.map +1 -1
  15. package/dist/commands/dev.js +1 -1
  16. package/dist/commands/dev.js.map +1 -1
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +18 -24
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/env.js +6 -6
  21. package/dist/commands/env.js.map +1 -1
  22. package/dist/commands/identity.js +3 -3
  23. package/dist/commands/identity.js.map +1 -1
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +103 -65
  26. package/dist/commands/init.js.map +1 -1
  27. package/dist/commands/register.d.ts +1 -1
  28. package/dist/commands/register.d.ts.map +1 -1
  29. package/dist/commands/register.js +35 -7
  30. package/dist/commands/register.js.map +1 -1
  31. package/dist/commands/rotate.js +4 -4
  32. package/dist/commands/rotate.js.map +1 -1
  33. package/dist/commands/start.js +3 -3
  34. package/dist/commands/start.js.map +1 -1
  35. package/dist/commands/status.js +1 -1
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/commands/verify.js +6 -6
  38. package/dist/commands/verify.js.map +1 -1
  39. package/dist/dco.d.ts +4 -0
  40. package/dist/dco.d.ts.map +1 -0
  41. package/dist/dco.js +21 -0
  42. package/dist/dco.js.map +1 -0
  43. package/dist/index.js +8 -13
  44. package/dist/utils/checkpoint-api.d.ts +1 -1
  45. package/dist/utils/checkpoint-api.js +1 -1
  46. package/dist/utils/checkpoint-env.d.ts +1 -1
  47. package/dist/utils/checkpoint-env.js +2 -2
  48. package/dist/utils/checkpoint-env.js.map +1 -1
  49. package/dist/utils/checkpoint-scaffold.d.ts +1 -1
  50. package/dist/utils/checkpoint-scaffold.js +1 -1
  51. package/dist/utils/cli-kit/browser.d.ts +24 -0
  52. package/dist/utils/cli-kit/browser.d.ts.map +1 -0
  53. package/dist/utils/cli-kit/browser.js +58 -0
  54. package/dist/utils/cli-kit/browser.js.map +1 -0
  55. package/dist/utils/cli-kit/claim-polling.d.ts +18 -0
  56. package/dist/utils/cli-kit/claim-polling.d.ts.map +1 -0
  57. package/dist/utils/{claim-polling.js → cli-kit/claim-polling.js} +19 -26
  58. package/dist/utils/cli-kit/claim-polling.js.map +1 -0
  59. package/dist/utils/cli-kit/diagnostics.d.ts +9 -0
  60. package/dist/utils/cli-kit/diagnostics.d.ts.map +1 -0
  61. package/dist/utils/cli-kit/diagnostics.js +11 -0
  62. package/dist/utils/cli-kit/diagnostics.js.map +1 -0
  63. package/dist/utils/cli-kit/fs-safe.d.ts +14 -0
  64. package/dist/utils/cli-kit/fs-safe.d.ts.map +1 -0
  65. package/dist/utils/cli-kit/fs-safe.js +20 -0
  66. package/dist/utils/cli-kit/fs-safe.js.map +1 -0
  67. package/dist/utils/cli-kit/index.d.ts +15 -0
  68. package/dist/utils/cli-kit/index.d.ts.map +1 -0
  69. package/dist/utils/cli-kit/index.js +10 -0
  70. package/dist/utils/cli-kit/index.js.map +1 -0
  71. package/dist/utils/cli-kit/json-file.d.ts +21 -0
  72. package/dist/utils/cli-kit/json-file.d.ts.map +1 -0
  73. package/dist/utils/cli-kit/json-file.js +51 -0
  74. package/dist/utils/cli-kit/json-file.js.map +1 -0
  75. package/dist/utils/cli-kit/marker-block.d.ts +26 -0
  76. package/dist/utils/cli-kit/marker-block.d.ts.map +1 -0
  77. package/dist/utils/cli-kit/marker-block.js +61 -0
  78. package/dist/utils/cli-kit/marker-block.js.map +1 -0
  79. package/dist/utils/cli-kit/paths.d.ts +15 -0
  80. package/dist/utils/cli-kit/paths.d.ts.map +1 -0
  81. package/dist/utils/cli-kit/paths.js +21 -0
  82. package/dist/utils/cli-kit/paths.js.map +1 -0
  83. package/dist/utils/cli-kit/secure-storage.d.ts +14 -0
  84. package/dist/utils/cli-kit/secure-storage.d.ts.map +1 -0
  85. package/dist/utils/cli-kit/secure-storage.js +20 -0
  86. package/dist/utils/cli-kit/secure-storage.js.map +1 -0
  87. package/dist/utils/cli-kit/step-report.d.ts +16 -0
  88. package/dist/utils/cli-kit/step-report.d.ts.map +1 -0
  89. package/dist/utils/cli-kit/step-report.js +36 -0
  90. package/dist/utils/cli-kit/step-report.js.map +1 -0
  91. package/dist/utils/dco/agent-metadata.d.ts +1 -1
  92. package/dist/utils/dco/agent-metadata.js +1 -1
  93. package/dist/utils/dco/agent-metadata.js.map +1 -1
  94. package/dist/utils/dco/claude-config.d.ts +18 -14
  95. package/dist/utils/dco/claude-config.d.ts.map +1 -1
  96. package/dist/utils/dco/claude-config.js +31 -97
  97. package/dist/utils/dco/claude-config.js.map +1 -1
  98. package/dist/utils/dco/dispatch.d.ts +59 -0
  99. package/dist/utils/dco/dispatch.d.ts.map +1 -0
  100. package/dist/utils/dco/dispatch.js +332 -0
  101. package/dist/utils/dco/dispatch.js.map +1 -0
  102. package/dist/utils/dco/git-config.d.ts.map +1 -1
  103. package/dist/utils/dco/git-config.js +5 -3
  104. package/dist/utils/dco/git-config.js.map +1 -1
  105. package/dist/utils/dco/github.d.ts +8 -0
  106. package/dist/utils/dco/github.d.ts.map +1 -1
  107. package/dist/utils/dco/github.js +11 -0
  108. package/dist/utils/dco/github.js.map +1 -1
  109. package/dist/utils/dco/paths.d.ts +1 -8
  110. package/dist/utils/dco/paths.d.ts.map +1 -1
  111. package/dist/utils/dco/paths.js +3 -12
  112. package/dist/utils/dco/paths.js.map +1 -1
  113. package/dist/utils/dco/render.d.ts +1 -1
  114. package/dist/utils/dco/render.d.ts.map +1 -1
  115. package/dist/utils/dco/render.js +4 -21
  116. package/dist/utils/dco/render.js.map +1 -1
  117. package/dist/utils/dco/resolve-agent.d.ts +54 -0
  118. package/dist/utils/dco/resolve-agent.d.ts.map +1 -0
  119. package/dist/utils/dco/resolve-agent.js +164 -0
  120. package/dist/utils/dco/resolve-agent.js.map +1 -0
  121. package/dist/utils/dco/setup.d.ts +10 -7
  122. package/dist/utils/dco/setup.d.ts.map +1 -1
  123. package/dist/utils/dco/setup.js +304 -203
  124. package/dist/utils/dco/setup.js.map +1 -1
  125. package/dist/utils/dco/ssh-key.d.ts +13 -0
  126. package/dist/utils/dco/ssh-key.d.ts.map +1 -1
  127. package/dist/utils/dco/ssh-key.js +21 -0
  128. package/dist/utils/dco/ssh-key.js.map +1 -1
  129. package/dist/utils/dco/status.d.ts +1 -1
  130. package/dist/utils/dco/status.d.ts.map +1 -1
  131. package/dist/utils/dco/status.js +3 -3
  132. package/dist/utils/dco/status.js.map +1 -1
  133. package/dist/utils/dco/verify.d.ts +114 -0
  134. package/dist/utils/dco/verify.d.ts.map +1 -0
  135. package/dist/utils/dco/verify.js +383 -0
  136. package/dist/utils/dco/verify.js.map +1 -0
  137. package/dist/utils/gitignore.d.ts +17 -0
  138. package/dist/utils/gitignore.d.ts.map +1 -0
  139. package/dist/utils/gitignore.js +40 -0
  140. package/dist/utils/gitignore.js.map +1 -0
  141. package/dist/utils/identity-heal.d.ts +16 -0
  142. package/dist/utils/identity-heal.d.ts.map +1 -0
  143. package/dist/utils/identity-heal.js +37 -0
  144. package/dist/utils/identity-heal.js.map +1 -0
  145. package/dist/utils/poll.d.ts +1 -1
  146. package/dist/utils/poll.js +1 -1
  147. package/package.json +4 -3
  148. package/dist/utils/claim-polling.d.ts +0 -21
  149. package/dist/utils/claim-polling.d.ts.map +0 -1
  150. package/dist/utils/claim-polling.js.map +0 -1
  151. package/dist/utils/dco/fs-safe.d.ts +0 -14
  152. package/dist/utils/dco/fs-safe.d.ts.map +0 -1
  153. package/dist/utils/dco/fs-safe.js +0 -20
  154. package/dist/utils/dco/fs-safe.js.map +0 -1
@@ -0,0 +1,383 @@
1
+ import { lookup as dnsLookup } from "node:dns/promises";
2
+ import { isValidDid } from "./claude-config.js";
3
+ const defaultLookup = async (hostname) => {
4
+ const records = await dnsLookup(hostname, { all: true });
5
+ return records.map((record) => record.address);
6
+ };
7
+ /**
8
+ * The document host is chosen by the DID, which comes from a PR body we do
9
+ * not control. Without a deadline a hostile or merely unreachable host hangs
10
+ * the command for as long as it cares to hold the socket open — tolerable
11
+ * when a human can interrupt it, not when this runs unattended in CI.
12
+ */
13
+ export const RESOLVE_TIMEOUT_MS = 10000;
14
+ /**
15
+ * Captures the whole remainder of the line, not the first whitespace-free
16
+ * run. Truncating at whitespace would silently reduce a malformed value to
17
+ * whichever prefix happens to parse: `Agent DID: did:web:knowthat.ai junk`
18
+ * would become `did:web:knowthat.ai`, resolve the registry's own root
19
+ * document, match its own `id`, and report a forged line as verified.
20
+ */
21
+ const AGENT_DID_LINE = /Agent DID:[^\S\n\r]*([^\n\r]*)/g;
22
+ /**
23
+ * Pull the claimed DID out of a PR body.
24
+ *
25
+ * Returns null when the body claims two *different* DIDs. Taking the first
26
+ * match would mean an appended line could never change the verdict, but it
27
+ * would also mean a forged line prepended above the real one silently wins.
28
+ * Ambiguous attribution is a failure, not a tie to be broken.
29
+ */
30
+ export function extractAgentDid(prBody) {
31
+ const claimed = new Set();
32
+ for (const match of prBody.matchAll(AGENT_DID_LINE)) {
33
+ // The managed block wraps the line in a code span, so a trailing
34
+ // backtick is expected punctuation rather than part of the identifier.
35
+ const candidate = match[1].replace(/`+\s*$/, "").trim();
36
+ if (isValidDid(candidate)) {
37
+ claimed.add(candidate);
38
+ }
39
+ }
40
+ if (claimed.size !== 1)
41
+ return null;
42
+ return [...claimed][0];
43
+ }
44
+ /**
45
+ * Whether a piece of text already carries an `Agent DID:` attribution line
46
+ * that `extractAgentDid` would pick up. Uses the exact same line pattern and
47
+ * backtick/whitespace handling, so a caller (notably `dco dispatch`, which
48
+ * appends its own authoritative line) can reject a description that would
49
+ * otherwise put a second, conflicting DID into the PR body. Matches the
50
+ * managed CLAUDE.md block's backticked, mid-line form, not just a line-start
51
+ * one.
52
+ */
53
+ export function hasAgentDidClaim(text) {
54
+ for (const match of text.matchAll(AGENT_DID_LINE)) {
55
+ const candidate = match[1].replace(/`+\s*$/, "").trim();
56
+ if (isValidDid(candidate)) {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+ /**
63
+ * Map a `did:web` to the HTTPS URL its document must be served from, per the
64
+ * did:web method spec: the identifier's colon-separated segments *are* the
65
+ * path, and a bare domain resolves under `/.well-known/`.
66
+ *
67
+ * Segments are validated AFTER percent-decoding. `DID_PATTERN` permits `%`,
68
+ * so a crafted `%2F` would otherwise smuggle a path separator through and
69
+ * point resolution at an attacker-chosen URL on the same host.
70
+ */
71
+ export function didWebToHttpsUrl(did) {
72
+ const prefix = "did:web:";
73
+ if (!did.startsWith(prefix))
74
+ return null;
75
+ const segments = did.slice(prefix.length).split(":");
76
+ if (segments.length === 0 || segments.some((segment) => segment === "")) {
77
+ return null;
78
+ }
79
+ const decoded = [];
80
+ for (const segment of segments) {
81
+ try {
82
+ decoded.push(decodeURIComponent(segment));
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ const [host, ...path] = decoded;
89
+ // Host may carry a port (encoded as %3A in the DID) but nothing else.
90
+ if (!/^[A-Za-z0-9.-]+(:\d+)?$/.test(host))
91
+ return null;
92
+ if (path.some((segment) => segment === "" ||
93
+ segment === "." ||
94
+ segment === ".." ||
95
+ /[/\\?#]/.test(segment))) {
96
+ return null;
97
+ }
98
+ const suffix = path.length
99
+ ? `${path.join("/")}/did.json`
100
+ : ".well-known/did.json";
101
+ return `https://${host}/${suffix}`;
102
+ }
103
+ /**
104
+ * IPv4 ranges that must never be treated as a resolvable did:web host:
105
+ * loopback, all three RFC1918 private blocks, link-local (which is also
106
+ * where every major cloud's instance-metadata service lives, at
107
+ * 169.254.169.254), and the remaining reserved/special-use blocks that have
108
+ * no business serving a public DID document.
109
+ */
110
+ const BLOCKED_IPV4_RANGES = [
111
+ ["0.0.0.0", 8], // "this network"
112
+ ["10.0.0.0", 8], // RFC1918 private
113
+ ["100.64.0.0", 10], // shared address space (CGNAT)
114
+ ["127.0.0.0", 8], // loopback
115
+ ["169.254.0.0", 16], // link-local, incl. cloud metadata (169.254.169.254)
116
+ ["172.16.0.0", 12], // RFC1918 private
117
+ ["192.0.0.0", 24], // IETF protocol assignments
118
+ ["192.0.2.0", 24], // documentation (TEST-NET-1)
119
+ ["192.168.0.0", 16], // RFC1918 private
120
+ ["198.18.0.0", 15], // benchmarking
121
+ ["198.51.100.0", 24], // documentation (TEST-NET-2)
122
+ ["203.0.113.0", 24], // documentation (TEST-NET-3)
123
+ ["224.0.0.0", 4], // multicast
124
+ ["240.0.0.0", 4], // reserved, incl. the 255.255.255.255 broadcast address
125
+ ];
126
+ function ipv4ToInt(ip) {
127
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ip);
128
+ if (!match)
129
+ return null;
130
+ const octets = match.slice(1, 5).map(Number);
131
+ if (octets.some((octet) => octet > 255))
132
+ return null;
133
+ return (((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>>
134
+ 0);
135
+ }
136
+ function isBlockedIpv4(ip) {
137
+ const value = ipv4ToInt(ip);
138
+ if (value === null)
139
+ return false;
140
+ return BLOCKED_IPV4_RANGES.some(([base, prefix]) => {
141
+ const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
142
+ return (value & mask) === (ipv4ToInt(base) & mask);
143
+ });
144
+ }
145
+ /**
146
+ * The IPv6 counterparts of the ranges above: loopback, unspecified,
147
+ * link-local (`fe80::/10`), and unique-local (`fc00::/7`, the IPv6 analogue
148
+ * of RFC1918). A resolver can hand back either family, so an address that
149
+ * only checked IPv4 would wave an AAAA-only loopback/private record through.
150
+ */
151
+ function isBlockedIpv6(ip) {
152
+ const lower = ip.toLowerCase();
153
+ // A v4-mapped/v4-compatible address ends in a dotted IPv4 literal (e.g.
154
+ // "::ffff:127.0.0.1") — defer to the IPv4 check on that embedded address.
155
+ const tail = lower.slice(lower.lastIndexOf(":") + 1);
156
+ if (tail.includes(".") && isBlockedIpv4(tail))
157
+ return true;
158
+ if (lower === "::1" || lower === "::")
159
+ return true;
160
+ const firstGroup = lower.split(":")[0];
161
+ const firstHextet = firstGroup === "" ? NaN : parseInt(firstGroup, 16);
162
+ if (Number.isNaN(firstHextet))
163
+ return false;
164
+ if (firstHextet >= 0xfe80 && firstHextet <= 0xfebf)
165
+ return true; // link-local
166
+ if (firstHextet >= 0xfc00 && firstHextet <= 0xfdff)
167
+ return true; // unique-local
168
+ return false;
169
+ }
170
+ function isBlockedAddress(ip) {
171
+ return ip.includes(":") ? isBlockedIpv6(ip) : isBlockedIpv4(ip);
172
+ }
173
+ /**
174
+ * Cheap, synchronous first line of defense against the exact literals an
175
+ * attacker would put directly in a did:web host: `localhost`, or an IPv4
176
+ * literal in a private, loopback, link-local, or otherwise reserved range.
177
+ *
178
+ * Takes the hostname `new URL()` produces, not the raw did:web segment.
179
+ * `URL` canonicalizes decimal/hex/octal IPv4 shorthand (`2130706433` and
180
+ * `0x7f000001` both become `127.0.0.1`) before `fetch` ever sees it, so
181
+ * checking the pre-parse segment would miss exactly the encodings an
182
+ * attacker would reach for to slip past a naive dotted-quad check. The
183
+ * trailing dot is stripped first for the same reason: `new URL()` preserves
184
+ * a root-zone dot (`"localhost."` stays `"localhost."`), and DNS treats it
185
+ * as identical to `"localhost"` — an unstripped compare would let that
186
+ * single character walk straight past an exact-match or `endsWith` check.
187
+ *
188
+ * This is a fast-path, not the actual guarantee: it only catches literals,
189
+ * so it cannot see a normal-looking hostname whose A/AAAA record happens to
190
+ * point at an internal address. `hostResolvesToBlockedAddress` below is
191
+ * what actually closes that gap, by checking where the name resolves to
192
+ * rather than how it's spelled.
193
+ */
194
+ export function isBlockedResolutionHost(hostname) {
195
+ const lower = hostname.toLowerCase().replace(/\.$/, "");
196
+ if (lower === "localhost" || lower.endsWith(".localhost"))
197
+ return true;
198
+ return isBlockedAddress(lower);
199
+ }
200
+ /**
201
+ * The actual guarantee `isBlockedResolutionHost` can't provide on its own:
202
+ * resolve the hostname and check every address it answers to, not just
203
+ * whether the hostname string itself looks like one of the blocked
204
+ * literals. A domain an attacker controls can point an A/AAAA record at
205
+ * 169.254.169.254 (or loopback, or an RFC1918 address) just as easily as a
206
+ * did:web host can spell the address out directly — DNS is the attacker's
207
+ * to shape here just as much as the PR body text is, since the DID names
208
+ * whatever host they choose.
209
+ *
210
+ * Deliberately does not catch a lookup failure: that is "no evidence
211
+ * either way", the same as a failed fetch, and the caller already has a
212
+ * reason for that (`unresolvable`) that this must not collide with —
213
+ * mislabeling a plain NXDOMAIN as "blocked-host" would tell the CLI's user
214
+ * they hit a private address when they actually just mistyped a domain.
215
+ */
216
+ export async function hostResolvesToBlockedAddress(hostname, lookupImpl = defaultLookup) {
217
+ const addresses = await lookupImpl(hostname);
218
+ return addresses.some(isBlockedAddress);
219
+ }
220
+ /**
221
+ * Everything that has to be true about `documentUrl`'s host before it is
222
+ * worth spending a request on: it must parse, it must not be one of the
223
+ * literals `isBlockedResolutionHost` recognizes, and it must not resolve to
224
+ * one either. Pulled out on its own so `verifyPrAttribution` reads as one
225
+ * linear pipeline instead of three nested guard clauses doing the same kind
226
+ * of check.
227
+ */
228
+ async function checkDocumentHostIsSafe(documentUrl, lookupImpl) {
229
+ let hostname;
230
+ try {
231
+ hostname = new URL(documentUrl).hostname;
232
+ }
233
+ catch {
234
+ return {
235
+ safe: false,
236
+ reason: "unresolvable",
237
+ detail: "The resolved document URL was invalid.",
238
+ };
239
+ }
240
+ if (isBlockedResolutionHost(hostname)) {
241
+ return {
242
+ safe: false,
243
+ reason: "blocked-host",
244
+ detail: "Refusing to resolve a private, loopback, link-local, or metadata address.",
245
+ };
246
+ }
247
+ let blocked;
248
+ try {
249
+ blocked = await hostResolvesToBlockedAddress(hostname, lookupImpl);
250
+ }
251
+ catch (error) {
252
+ // Same reasoning as a failed fetch: no evidence either way, so no pass.
253
+ return {
254
+ safe: false,
255
+ reason: "unresolvable",
256
+ detail: error instanceof Error ? error.message : String(error),
257
+ };
258
+ }
259
+ if (blocked) {
260
+ return {
261
+ safe: false,
262
+ reason: "blocked-host",
263
+ detail: "The claimed DID resolves to a private, loopback, link-local, or metadata address.",
264
+ };
265
+ }
266
+ return { safe: true };
267
+ }
268
+ function readServiceEndpoint(document) {
269
+ if (typeof document !== "object" || document === null)
270
+ return undefined;
271
+ const service = document.service;
272
+ if (!Array.isArray(service))
273
+ return undefined;
274
+ for (const entry of service) {
275
+ if (typeof entry !== "object" || entry === null)
276
+ continue;
277
+ const endpoint = entry.serviceEndpoint;
278
+ if (typeof endpoint === "string" && endpoint.length > 0)
279
+ return endpoint;
280
+ }
281
+ return undefined;
282
+ }
283
+ /**
284
+ * Resolve the DID claimed by a PR body and confirm the document describes
285
+ * that same DID.
286
+ */
287
+ export async function verifyPrAttribution(prBody, fetchImpl = (url, init) => fetch(url, init), lookupImpl = defaultLookup) {
288
+ const did = extractAgentDid(prBody);
289
+ if (!did)
290
+ return { ok: false, reason: "no-did" };
291
+ const documentUrl = didWebToHttpsUrl(did);
292
+ if (!documentUrl) {
293
+ return {
294
+ ok: false,
295
+ did,
296
+ reason: "unsupported-method",
297
+ detail: "Only did:web identities can be resolved over HTTPS.",
298
+ };
299
+ }
300
+ // The document host came from a PR body we do not control, so it is
301
+ // checked — both how it's spelled and where it actually resolves — before
302
+ // we ever make a request to it. Skipping this would let an IP literal, or
303
+ // a normal-looking domain with an A/AAAA record pointing at one, reach a
304
+ // loopback, private, link-local, or cloud-metadata address — an SSRF that
305
+ // a hostile PR description can trigger unattended in CI.
306
+ const hostCheck = await checkDocumentHostIsSafe(documentUrl, lookupImpl);
307
+ if (!hostCheck.safe) {
308
+ return {
309
+ ok: false,
310
+ did,
311
+ documentUrl,
312
+ reason: hostCheck.reason,
313
+ detail: hostCheck.detail,
314
+ };
315
+ }
316
+ let response;
317
+ try {
318
+ response = await fetchImpl(documentUrl, {
319
+ signal: AbortSignal.timeout(RESOLVE_TIMEOUT_MS),
320
+ // A redirect target is unverified: it could bounce to an internal
321
+ // address the check above never saw. Refusing to follow it, rather
322
+ // than trusting `fetch`'s default of following automatically, keeps
323
+ // this host check from being bypassable by a 3xx response.
324
+ redirect: "manual",
325
+ });
326
+ }
327
+ catch (error) {
328
+ // A transport failure is indistinguishable from a missing document as far
329
+ // as trust goes: neither produced evidence, so neither may pass.
330
+ return {
331
+ ok: false,
332
+ did,
333
+ documentUrl,
334
+ reason: "unresolvable",
335
+ detail: error instanceof Error ? error.message : String(error),
336
+ };
337
+ }
338
+ if (!response.ok) {
339
+ return {
340
+ ok: false,
341
+ did,
342
+ documentUrl,
343
+ reason: "unresolvable",
344
+ detail: response.type === "opaqueredirect"
345
+ ? "Server returned a redirect, which is not followed."
346
+ : `HTTP ${response.status}`,
347
+ };
348
+ }
349
+ let document;
350
+ try {
351
+ document = await response.json();
352
+ }
353
+ catch {
354
+ return {
355
+ ok: false,
356
+ did,
357
+ documentUrl,
358
+ reason: "unresolvable",
359
+ detail: "Response was not valid JSON.",
360
+ };
361
+ }
362
+ const resolvedId = typeof document === "object" && document !== null
363
+ ? document.id
364
+ : undefined;
365
+ // A document that resolves but names a different subject proves nothing
366
+ // about the claimed identity.
367
+ if (resolvedId !== did) {
368
+ return {
369
+ ok: false,
370
+ did,
371
+ documentUrl,
372
+ reason: "did-mismatch",
373
+ detail: `Document describes ${typeof resolvedId === "string" ? resolvedId : "no DID"}.`,
374
+ };
375
+ }
376
+ return {
377
+ ok: true,
378
+ did,
379
+ documentUrl,
380
+ agentUrl: readServiceEndpoint(document),
381
+ };
382
+ }
383
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/utils/dco/verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Adds `entry` to .gitignore if no existing pattern already covers it,
3
+ * including a trailing-`*` wildcard pattern (e.g. `.env*`, create-next-app's
4
+ * own default), the same glob EnvManager.checkGitignore() already honors, so
5
+ * this doesn't append a redundant, already-covered line.
6
+ *
7
+ * Shared by every command that writes a credential-bearing path into a
8
+ * project directory it doesn't fully control (`.env.local`, `.checkpoint/`,
9
+ * `.mcpi/`). Neither write is safe to leave ungitignored: a repo whose
10
+ * `.gitignore` doesn't already cover it (hand-rolled apps, a pruned/custom
11
+ * .gitignore, a repo that was never scaffolded by this CLI) would otherwise
12
+ * let `git add .` stage a real credential straight into history. Callers
13
+ * should call this BEFORE writing the secret, not after, so there's no
14
+ * window where the file exists ungitignored.
15
+ */
16
+ export declare function ensureGitignored(projectRoot: string, entry: string, label: string): void;
17
+ //# sourceMappingURL=gitignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,IAAI,CAwBN"}
@@ -0,0 +1,40 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ /**
4
+ * Adds `entry` to .gitignore if no existing pattern already covers it,
5
+ * including a trailing-`*` wildcard pattern (e.g. `.env*`, create-next-app's
6
+ * own default), the same glob EnvManager.checkGitignore() already honors, so
7
+ * this doesn't append a redundant, already-covered line.
8
+ *
9
+ * Shared by every command that writes a credential-bearing path into a
10
+ * project directory it doesn't fully control (`.env.local`, `.checkpoint/`,
11
+ * `.mcpi/`). Neither write is safe to leave ungitignored: a repo whose
12
+ * `.gitignore` doesn't already cover it (hand-rolled apps, a pruned/custom
13
+ * .gitignore, a repo that was never scaffolded by this CLI) would otherwise
14
+ * let `git add .` stage a real credential straight into history. Callers
15
+ * should call this BEFORE writing the secret, not after, so there's no
16
+ * window where the file exists ungitignored.
17
+ */
18
+ export function ensureGitignored(projectRoot, entry, label) {
19
+ const gitignorePath = join(projectRoot, ".gitignore");
20
+ const bare = entry.replace(/\/$/, "");
21
+ let content = existsSync(gitignorePath)
22
+ ? readFileSync(gitignorePath, "utf-8")
23
+ : "";
24
+ const alreadyIgnored = content
25
+ .split("\n")
26
+ .map((line) => line.trim())
27
+ .filter((line) => line && !line.startsWith("#"))
28
+ .some((line) => line === entry ||
29
+ line === bare ||
30
+ line === `/${bare}` ||
31
+ line === `/${entry}` ||
32
+ (line.endsWith("*") && bare.startsWith(line.slice(0, -1))));
33
+ if (alreadyIgnored)
34
+ return;
35
+ if (content && !content.endsWith("\n"))
36
+ content += "\n";
37
+ content += `${content ? "\n" : ""}# ${label}\n${entry}\n`;
38
+ writeFileSync(gitignorePath, content, "utf-8");
39
+ }
40
+ //# sourceMappingURL=gitignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAmB,EACnB,KAAa,EACb,KAAa;IAEb,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC;QACrC,CAAC,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC;QACtC,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,cAAc,GAAG,OAAO;SAC3B,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;SAC/C,IAAI,CACH,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,KAAK,KAAK;QACd,IAAI,KAAK,IAAI;QACb,IAAI,KAAK,IAAI,IAAI,EAAE;QACnB,IAAI,KAAK,IAAI,KAAK,EAAE;QACpB,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAC7D,CAAC;IACJ,IAAI,cAAc;QAAE,OAAO;IAE3B,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,IAAI,CAAC;IACxD,OAAO,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC;IAC1D,aAAa,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Patches only the `did` field of an on-disk `.mcpi/identity.json`,
3
+ * preserving every other field (privateKey, publicKey, kid, createdAt, ...).
4
+ *
5
+ * Both `init` and `register` mint or accept a locally-generated placeholder
6
+ * did before the registry assigns the real did:web record; once the
7
+ * canonical did is known, this heals identity.json in place so it never
8
+ * drifts from agent.json (dco setup.ts throws "for a different agent" the
9
+ * moment they disagree).
10
+ *
11
+ * Returns false, without writing anything, when the file does not exist or
12
+ * already has the target did. Read/parse/write errors are left to THROW:
13
+ * callers each keep their own catch-and-warn behavior around this call.
14
+ */
15
+ export declare function patchIdentityDidInPlace(identityFilePath: string, newDid: string): boolean;
16
+ //# sourceMappingURL=identity-heal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-heal.d.ts","sourceRoot":"","sources":["../../src/utils/identity-heal.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAsBT"}
@@ -0,0 +1,37 @@
1
+ import { existsSync, readFileSync, chmodSync } from "fs";
2
+ import { writeFileAtomicSafe } from "./cli-kit/index.js";
3
+ /**
4
+ * Patches only the `did` field of an on-disk `.mcpi/identity.json`,
5
+ * preserving every other field (privateKey, publicKey, kid, createdAt, ...).
6
+ *
7
+ * Both `init` and `register` mint or accept a locally-generated placeholder
8
+ * did before the registry assigns the real did:web record; once the
9
+ * canonical did is known, this heals identity.json in place so it never
10
+ * drifts from agent.json (dco setup.ts throws "for a different agent" the
11
+ * moment they disagree).
12
+ *
13
+ * Returns false, without writing anything, when the file does not exist or
14
+ * already has the target did. Read/parse/write errors are left to THROW:
15
+ * callers each keep their own catch-and-warn behavior around this call.
16
+ */
17
+ export function patchIdentityDidInPlace(identityFilePath, newDid) {
18
+ if (!existsSync(identityFilePath)) {
19
+ return false;
20
+ }
21
+ const existing = JSON.parse(readFileSync(identityFilePath, "utf-8"));
22
+ if (!existing || typeof existing !== "object" || existing.did === newDid) {
23
+ return false;
24
+ }
25
+ // Atomic + symlink-safe: identity.json holds a private key, so a crash
26
+ // mid-write must never truncate it, and a planted symlink at the write's
27
+ // temp sibling must never be followed into an arbitrary file.
28
+ writeFileAtomicSafe(identityFilePath, JSON.stringify({ ...existing, did: newDid }, null, 2), 0o600);
29
+ try {
30
+ chmodSync(identityFilePath, 0o600);
31
+ }
32
+ catch {
33
+ // Best-effort; e.g. unsupported on Windows.
34
+ }
35
+ return true;
36
+ }
37
+ //# sourceMappingURL=identity-heal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-heal.js","sourceRoot":"","sources":["../../src/utils/identity-heal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CACrC,gBAAwB,EACxB,MAAc;IAEd,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,uEAAuE;IACvE,yEAAyE;IACzE,8DAA8D;IAC9D,mBAAmB,CACjB,gBAAgB,EAChB,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EACrD,KAAK,CACN,CAAC;IACF,IAAI,CAAC;QACH,SAAS,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,4CAA4C;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -9,7 +9,7 @@
9
9
  * this is exactly the drift that let checkpoint-init.ts's own poll loop
10
10
  * ship without the SIGINT guard claim-polling.ts already had (PR #658
11
11
  * deep review, finding B7). Left unmigrated here: this change's ground
12
- * rules explicitly scope out the existing top-level `mcpi init` (MCP-I
12
+ * rules explicitly scope out the existing top-level `kya-os init` (MCP-I
13
13
  * agent flow) beyond the parseAsync entrypoint change, so claim-polling.ts
14
14
  * keeps its own copy for now; folding it onto this shared helper is a
15
15
  * follow-up once that flow is in scope.
@@ -9,7 +9,7 @@
9
9
  * this is exactly the drift that let checkpoint-init.ts's own poll loop
10
10
  * ship without the SIGINT guard claim-polling.ts already had (PR #658
11
11
  * deep review, finding B7). Left unmigrated here: this change's ground
12
- * rules explicitly scope out the existing top-level `mcpi init` (MCP-I
12
+ * rules explicitly scope out the existing top-level `kya-os init` (MCP-I
13
13
  * agent flow) beyond the parseAsync entrypoint change, so claim-polling.ts
14
14
  * keeps its own copy for now; folding it onto this shared helper is a
15
15
  * follow-up once that flow is in scope.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/cli",
3
- "version": "1.8.1",
3
+ "version": "2.0.0",
4
4
  "description": "CLI for MCP-I setup and management",
5
5
  "keywords": [
6
6
  "cli",
@@ -19,14 +19,15 @@
19
19
  "main": "dist/index.js",
20
20
  "type": "module",
21
21
  "bin": {
22
- "mcpi": "./dist/index.js"
22
+ "kya-os": "./dist/index.js",
23
+ "dco": "./dist/dco.js"
23
24
  },
24
25
  "files": [
25
26
  "dist",
26
27
  "templates"
27
28
  ],
28
29
  "scripts": {
29
- "build": "rm -f tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json && mv dist/index.prod.js dist/index.js && mv dist/index.prod.d.ts dist/index.d.ts && rm -f dist/index.prod.* && node scripts/fix-esm-imports.js && chmod +x dist/index.js",
30
+ "build": "rm -f tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json && mv dist/index.prod.js dist/index.js && mv dist/index.prod.d.ts dist/index.d.ts && rm -f dist/index.prod.* && node scripts/fix-esm-imports.js && chmod +x dist/index.js dist/dco.js",
30
31
  "dev": "tsx src/index.ts",
31
32
  "clean": "rm -rf dist",
32
33
  "prepublishOnly": "npm run clean && npm run build && node ../create-mcpi-app/scripts/validate-dependencies.js",
@@ -1,21 +0,0 @@
1
- /**
2
- * Options for claim polling
3
- */
4
- export interface ClaimPollingOptions {
5
- verbose?: boolean;
6
- maxAttempts?: number;
7
- pollInterval?: number;
8
- }
9
- /**
10
- * Poll for agent claim status
11
- *
12
- * Polls the KTA API to check if an agent has been claimed.
13
- * Returns true when claimed, false if timeout or interrupted.
14
- *
15
- * @param agentName - The agent's name (slug) to check
16
- * @param did - The agent's DID
17
- * @param endpoint - API endpoint URL (default: https://knowthat.ai)
18
- * @param options - Polling options
19
- */
20
- export declare function pollForClaimStatus(agentName: string, did: string, endpoint?: string, options?: ClaimPollingOptions): Promise<boolean>;
21
- //# sourceMappingURL=claim-polling.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claim-polling.d.ts","sourceRoot":"","sources":["../../src/utils/claim-polling.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAA8B,EACxC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CA4GlB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"claim-polling.js","sourceRoot":"","sources":["../../src/utils/claim-polling.ts"],"names":[],"mappings":"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"}
@@ -1,14 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,20 +0,0 @@
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