@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.
- package/dist/commands/build.js +2 -2
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/check.js +3 -3
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/checkpoint-init.d.ts +1 -1
- package/dist/commands/checkpoint-init.d.ts.map +1 -1
- package/dist/commands/checkpoint-init.js +8 -41
- package/dist/commands/checkpoint-init.js.map +1 -1
- package/dist/commands/claim.js +2 -2
- package/dist/commands/claim.js.map +1 -1
- package/dist/commands/dco.d.ts +38 -1
- package/dist/commands/dco.d.ts.map +1 -1
- package/dist/commands/dco.js +180 -7
- package/dist/commands/dco.js.map +1 -1
- package/dist/commands/dev.js +1 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +18 -24
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.js +6 -6
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/identity.js +3 -3
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +103 -65
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/register.d.ts +1 -1
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +35 -7
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/rotate.js +4 -4
- package/dist/commands/rotate.js.map +1 -1
- package/dist/commands/start.js +3 -3
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/verify.js +6 -6
- package/dist/commands/verify.js.map +1 -1
- package/dist/dco.d.ts +4 -0
- package/dist/dco.d.ts.map +1 -0
- package/dist/dco.js +21 -0
- package/dist/dco.js.map +1 -0
- package/dist/index.js +8 -13
- package/dist/utils/checkpoint-api.d.ts +1 -1
- package/dist/utils/checkpoint-api.js +1 -1
- package/dist/utils/checkpoint-env.d.ts +1 -1
- package/dist/utils/checkpoint-env.js +2 -2
- package/dist/utils/checkpoint-env.js.map +1 -1
- package/dist/utils/checkpoint-scaffold.d.ts +1 -1
- package/dist/utils/checkpoint-scaffold.js +1 -1
- package/dist/utils/cli-kit/browser.d.ts +24 -0
- package/dist/utils/cli-kit/browser.d.ts.map +1 -0
- package/dist/utils/cli-kit/browser.js +58 -0
- package/dist/utils/cli-kit/browser.js.map +1 -0
- package/dist/utils/cli-kit/claim-polling.d.ts +18 -0
- package/dist/utils/cli-kit/claim-polling.d.ts.map +1 -0
- package/dist/utils/{claim-polling.js → cli-kit/claim-polling.js} +19 -26
- package/dist/utils/cli-kit/claim-polling.js.map +1 -0
- package/dist/utils/cli-kit/diagnostics.d.ts +9 -0
- package/dist/utils/cli-kit/diagnostics.d.ts.map +1 -0
- package/dist/utils/cli-kit/diagnostics.js +11 -0
- package/dist/utils/cli-kit/diagnostics.js.map +1 -0
- package/dist/utils/cli-kit/fs-safe.d.ts +14 -0
- package/dist/utils/cli-kit/fs-safe.d.ts.map +1 -0
- package/dist/utils/cli-kit/fs-safe.js +20 -0
- package/dist/utils/cli-kit/fs-safe.js.map +1 -0
- package/dist/utils/cli-kit/index.d.ts +15 -0
- package/dist/utils/cli-kit/index.d.ts.map +1 -0
- package/dist/utils/cli-kit/index.js +10 -0
- package/dist/utils/cli-kit/index.js.map +1 -0
- package/dist/utils/cli-kit/json-file.d.ts +21 -0
- package/dist/utils/cli-kit/json-file.d.ts.map +1 -0
- package/dist/utils/cli-kit/json-file.js +51 -0
- package/dist/utils/cli-kit/json-file.js.map +1 -0
- package/dist/utils/cli-kit/marker-block.d.ts +26 -0
- package/dist/utils/cli-kit/marker-block.d.ts.map +1 -0
- package/dist/utils/cli-kit/marker-block.js +61 -0
- package/dist/utils/cli-kit/marker-block.js.map +1 -0
- package/dist/utils/cli-kit/paths.d.ts +15 -0
- package/dist/utils/cli-kit/paths.d.ts.map +1 -0
- package/dist/utils/cli-kit/paths.js +21 -0
- package/dist/utils/cli-kit/paths.js.map +1 -0
- package/dist/utils/cli-kit/secure-storage.d.ts +14 -0
- package/dist/utils/cli-kit/secure-storage.d.ts.map +1 -0
- package/dist/utils/cli-kit/secure-storage.js +20 -0
- package/dist/utils/cli-kit/secure-storage.js.map +1 -0
- package/dist/utils/cli-kit/step-report.d.ts +16 -0
- package/dist/utils/cli-kit/step-report.d.ts.map +1 -0
- package/dist/utils/cli-kit/step-report.js +36 -0
- package/dist/utils/cli-kit/step-report.js.map +1 -0
- package/dist/utils/dco/agent-metadata.d.ts +1 -1
- package/dist/utils/dco/agent-metadata.js +1 -1
- package/dist/utils/dco/agent-metadata.js.map +1 -1
- package/dist/utils/dco/claude-config.d.ts +18 -14
- package/dist/utils/dco/claude-config.d.ts.map +1 -1
- package/dist/utils/dco/claude-config.js +31 -97
- package/dist/utils/dco/claude-config.js.map +1 -1
- package/dist/utils/dco/dispatch.d.ts +59 -0
- package/dist/utils/dco/dispatch.d.ts.map +1 -0
- package/dist/utils/dco/dispatch.js +332 -0
- package/dist/utils/dco/dispatch.js.map +1 -0
- package/dist/utils/dco/git-config.d.ts.map +1 -1
- package/dist/utils/dco/git-config.js +5 -3
- package/dist/utils/dco/git-config.js.map +1 -1
- package/dist/utils/dco/github.d.ts +8 -0
- package/dist/utils/dco/github.d.ts.map +1 -1
- package/dist/utils/dco/github.js +11 -0
- package/dist/utils/dco/github.js.map +1 -1
- package/dist/utils/dco/paths.d.ts +1 -8
- package/dist/utils/dco/paths.d.ts.map +1 -1
- package/dist/utils/dco/paths.js +3 -12
- package/dist/utils/dco/paths.js.map +1 -1
- package/dist/utils/dco/render.d.ts +1 -1
- package/dist/utils/dco/render.d.ts.map +1 -1
- package/dist/utils/dco/render.js +4 -21
- package/dist/utils/dco/render.js.map +1 -1
- package/dist/utils/dco/resolve-agent.d.ts +54 -0
- package/dist/utils/dco/resolve-agent.d.ts.map +1 -0
- package/dist/utils/dco/resolve-agent.js +164 -0
- package/dist/utils/dco/resolve-agent.js.map +1 -0
- package/dist/utils/dco/setup.d.ts +10 -7
- package/dist/utils/dco/setup.d.ts.map +1 -1
- package/dist/utils/dco/setup.js +304 -203
- package/dist/utils/dco/setup.js.map +1 -1
- package/dist/utils/dco/ssh-key.d.ts +13 -0
- package/dist/utils/dco/ssh-key.d.ts.map +1 -1
- package/dist/utils/dco/ssh-key.js +21 -0
- package/dist/utils/dco/ssh-key.js.map +1 -1
- package/dist/utils/dco/status.d.ts +1 -1
- package/dist/utils/dco/status.d.ts.map +1 -1
- package/dist/utils/dco/status.js +3 -3
- package/dist/utils/dco/status.js.map +1 -1
- package/dist/utils/dco/verify.d.ts +114 -0
- package/dist/utils/dco/verify.d.ts.map +1 -0
- package/dist/utils/dco/verify.js +383 -0
- package/dist/utils/dco/verify.js.map +1 -0
- package/dist/utils/gitignore.d.ts +17 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +40 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/identity-heal.d.ts +16 -0
- package/dist/utils/identity-heal.d.ts.map +1 -0
- package/dist/utils/identity-heal.js +37 -0
- package/dist/utils/identity-heal.js.map +1 -0
- package/dist/utils/poll.d.ts +1 -1
- package/dist/utils/poll.js +1 -1
- package/package.json +4 -3
- package/dist/utils/claim-polling.d.ts +0 -21
- package/dist/utils/claim-polling.d.ts.map +0 -1
- package/dist/utils/claim-polling.js.map +0 -1
- package/dist/utils/dco/fs-safe.d.ts +0 -14
- package/dist/utils/dco/fs-safe.d.ts.map +0 -1
- package/dist/utils/dco/fs-safe.js +0 -20
- 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"}
|
package/dist/utils/poll.d.ts
CHANGED
|
@@ -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 `
|
|
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/dist/utils/poll.js
CHANGED
|
@@ -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 `
|
|
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": "
|
|
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
|
-
"
|
|
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
|