@aikdna/kdna-cli 0.17.0 → 0.19.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/README.md +120 -101
- package/SECURITY.md +1 -1
- package/package.json +6 -4
- package/skills/kdna-loader/SKILL.md +23 -22
- package/src/agent.js +290 -159
- package/src/cli.js +117 -67
- package/src/cmds/_common.js +40 -18
- package/src/cmds/badge.js +14 -9
- package/src/cmds/changelog.js +32 -12
- package/src/cmds/cluster.js +80 -85
- package/src/cmds/doctor.js +10 -27
- package/src/cmds/domain.js +114 -427
- package/src/cmds/explain.js +119 -0
- package/src/cmds/governance.js +111 -42
- package/src/cmds/legacy.js +8 -9
- package/src/cmds/license.js +491 -26
- package/src/cmds/quality.js +10 -3
- package/src/cmds/registry.js +15 -67
- package/src/cmds/studio.js +99 -47
- package/src/cmds/test.js +9 -6
- package/src/cmds/trace.js +11 -7
- package/src/compare.js +41 -22
- package/src/diff.js +38 -24
- package/src/identity.js +9 -7
- package/src/init.js +2 -2
- package/src/install.js +147 -459
- package/src/loader.js +10 -10
- package/src/package-store.js +232 -0
- package/src/paths.js +44 -0
- package/src/publish.js +150 -51
- package/src/registry.js +81 -9
- package/src/setup.js +19 -20
- package/src/verify.js +293 -140
- package/src/version.js +15 -7
- package/templates/minimal-domain/kdna.json +7 -7
- package/templates/standard-domain/README.md +10 -10
- package/templates/standard-domain/kdna.json +7 -3
- package/validators/kdna-lint.js +45 -11
- package/src/cmds/encrypt.js +0 -199
package/src/registry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RegistryResolver — KDNA
|
|
2
|
+
* RegistryResolver — KDNA asset-first registry client.
|
|
3
3
|
*
|
|
4
4
|
* Responsibilities:
|
|
5
5
|
* 1. Resolve names: bare → @aikdna/bare, validate @scope/name format
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Cache registry metadata locally
|
|
8
8
|
* 4. Surface scope trust info to install/publish
|
|
9
9
|
*
|
|
10
|
-
* Schema
|
|
10
|
+
* Schema v3.0 — see kdna-registry/SCHEMA.md
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
@@ -22,6 +22,7 @@ const DEFAULT_OFFICIAL_SCOPE = '@aikdna';
|
|
|
22
22
|
const CANONICAL_REGISTRY_URL =
|
|
23
23
|
process.env.KDNA_REGISTRY_URL ||
|
|
24
24
|
'https://raw.githubusercontent.com/aikdna/kdna-registry/main/domains.json';
|
|
25
|
+
const REQUIRED_SCHEMA_VERSION = '3.0';
|
|
25
26
|
|
|
26
27
|
const NAME_RE = /^@([a-z][a-z0-9-]*)\/([a-z][a-z0-9_]*)$/;
|
|
27
28
|
const BARE_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
@@ -39,6 +40,63 @@ function writeJson(file, data) {
|
|
|
39
40
|
fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function parseDate(value) {
|
|
44
|
+
const date = value ? new Date(value) : null;
|
|
45
|
+
return date && !Number.isNaN(date.getTime()) ? date : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registryTrustIssues(registry, { now = new Date() } = {}) {
|
|
49
|
+
const issues = [];
|
|
50
|
+
const trust = registry?.trust || {};
|
|
51
|
+
|
|
52
|
+
if (!registry || registry.schema_version !== REQUIRED_SCHEMA_VERSION) {
|
|
53
|
+
issues.push(
|
|
54
|
+
`Registry schema_version must be ${REQUIRED_SCHEMA_VERSION}, got ${JSON.stringify(registry?.schema_version)}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!trust.model) issues.push('registry.trust.model is required');
|
|
59
|
+
if (!trust.snapshot) issues.push('registry.trust.snapshot is required');
|
|
60
|
+
if (!trust.timestamp) issues.push('registry.trust.timestamp is required');
|
|
61
|
+
|
|
62
|
+
const snapshotVersion = trust.snapshot?.registry_version;
|
|
63
|
+
if (snapshotVersion && snapshotVersion !== registry.registry_version) {
|
|
64
|
+
issues.push(
|
|
65
|
+
`registry.trust.snapshot.registry_version ${snapshotVersion} does not match registry_version ${registry.registry_version}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const snapshotExpires = parseDate(trust.snapshot?.expires_at);
|
|
70
|
+
const timestampExpires = parseDate(trust.timestamp?.expires_at);
|
|
71
|
+
if (!snapshotExpires) issues.push('registry.trust.snapshot.expires_at must be an ISO timestamp');
|
|
72
|
+
if (!timestampExpires)
|
|
73
|
+
issues.push('registry.trust.timestamp.expires_at must be an ISO timestamp');
|
|
74
|
+
if (snapshotExpires && snapshotExpires <= now) {
|
|
75
|
+
issues.push(`registry snapshot expired at ${trust.snapshot.expires_at}`);
|
|
76
|
+
}
|
|
77
|
+
if (timestampExpires && timestampExpires <= now) {
|
|
78
|
+
issues.push(`registry timestamp expired at ${trust.timestamp.expires_at}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return issues;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function registryRevocations(registry) {
|
|
85
|
+
return registry?.trust?.revocations || [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isEntryRevoked(registry, entry) {
|
|
89
|
+
const revocations = registryRevocations(registry);
|
|
90
|
+
return (
|
|
91
|
+
revocations.find((rev) => {
|
|
92
|
+
if (rev.name && rev.name !== entry.name) return false;
|
|
93
|
+
if (rev.version && rev.version !== entry.version) return false;
|
|
94
|
+
if (rev.asset_digest && rev.asset_digest !== entry.asset_digest) return false;
|
|
95
|
+
return rev.name || rev.asset_digest;
|
|
96
|
+
}) || null
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
42
100
|
// ─── Name parsing ───────────────────────────────────────────────────────
|
|
43
101
|
|
|
44
102
|
/**
|
|
@@ -151,7 +209,17 @@ class RegistryResolver {
|
|
|
151
209
|
_loadRegistryForScope(scopeName) {
|
|
152
210
|
if (this._registries.has(scopeName)) return this._registries.get(scopeName);
|
|
153
211
|
const source = this._sourceForScope(scopeName);
|
|
154
|
-
|
|
212
|
+
let data = source.load({ allowNetwork: this.allowNetwork, refresh: this.refresh });
|
|
213
|
+
let trustIssues = data ? registryTrustIssues(data) : [];
|
|
214
|
+
if (trustIssues.length && this.allowNetwork && !this.refresh) {
|
|
215
|
+
data = source.load({ allowNetwork: true, refresh: true });
|
|
216
|
+
trustIssues = data ? registryTrustIssues(data) : [];
|
|
217
|
+
}
|
|
218
|
+
if (trustIssues.length) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Registry trust check failed:\n${trustIssues.map((i) => `- ${i}`).join('\n')}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
155
223
|
this._registries.set(scopeName, data);
|
|
156
224
|
return data;
|
|
157
225
|
}
|
|
@@ -186,12 +254,6 @@ class RegistryResolver {
|
|
|
186
254
|
);
|
|
187
255
|
}
|
|
188
256
|
|
|
189
|
-
if (registry.schema_version && registry.schema_version !== '2.0') {
|
|
190
|
-
throw new Error(
|
|
191
|
-
`Registry schema_version ${registry.schema_version} not supported. This CLI requires 2.0.`,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
257
|
const scope = registry.scopes?.[parsed.scope];
|
|
196
258
|
if (!scope) {
|
|
197
259
|
throw new Error(`Scope ${parsed.scope} not registered in registry.`);
|
|
@@ -215,6 +277,13 @@ class RegistryResolver {
|
|
|
215
277
|
throw new Error(`${entry.name}@${entry.version} has been yanked${when}.${reason}${replace}`);
|
|
216
278
|
}
|
|
217
279
|
|
|
280
|
+
const revocation = isEntryRevoked(registry, entry);
|
|
281
|
+
if (revocation) {
|
|
282
|
+
const reason = revocation.reason ? `\nReason: ${revocation.reason}` : '';
|
|
283
|
+
const when = revocation.revoked_at ? ` (revoked ${revocation.revoked_at.slice(0, 10)})` : '';
|
|
284
|
+
throw new Error(`${entry.name}@${entry.version} has been revoked${when}.${reason}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
218
287
|
return { parsed, scope, entry, registry };
|
|
219
288
|
}
|
|
220
289
|
|
|
@@ -250,6 +319,9 @@ function fetchRegistry() {
|
|
|
250
319
|
module.exports = {
|
|
251
320
|
RegistryResolver,
|
|
252
321
|
parseName,
|
|
322
|
+
REQUIRED_SCHEMA_VERSION,
|
|
323
|
+
registryTrustIssues,
|
|
324
|
+
isEntryRevoked,
|
|
253
325
|
loadRegistry,
|
|
254
326
|
fetchRegistry,
|
|
255
327
|
CANONICAL_REGISTRY_URL,
|
package/src/setup.js
CHANGED
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
18
|
+
const PATHS = require('./paths');
|
|
19
|
+
|
|
20
|
+
const USER_KDNA_DIR = PATHS.root;
|
|
21
|
+
const DOMAINS_DIR = PATHS.domains.root;
|
|
22
|
+
const CLUSTERS_DIR = PATHS.clusters;
|
|
21
23
|
const SKILLS_REPO = 'https://raw.githubusercontent.com/aikdna/kdna-skills/main';
|
|
22
24
|
|
|
23
25
|
const AGENTS = [
|
|
@@ -119,15 +121,23 @@ async function cmdSetup() {
|
|
|
119
121
|
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
120
122
|
log(`KDNA CLI v${pkg.version}`);
|
|
121
123
|
|
|
122
|
-
// 2. KDNA data root
|
|
123
|
-
ensureDir(
|
|
124
|
+
// 2. KDNA data root — .kdna asset store
|
|
125
|
+
ensureDir(PATHS.root);
|
|
126
|
+
ensureDir(PATHS.packages);
|
|
124
127
|
ensureDir(CLUSTERS_DIR);
|
|
128
|
+
ensureDir(PATHS.registry);
|
|
129
|
+
ensureDir(PATHS.traces);
|
|
130
|
+
ensureDir(PATHS.feedback);
|
|
131
|
+
ensureDir(PATHS.evals);
|
|
132
|
+
ensureDir(PATHS.cache);
|
|
133
|
+
ensureDir(PATHS.identity);
|
|
134
|
+
ensureDir(PATHS.licenses);
|
|
125
135
|
log(`Data root: ${USER_KDNA_DIR}/`);
|
|
126
136
|
|
|
127
|
-
// 2b.
|
|
137
|
+
// 2b. Directory installs are not part of the runtime model.
|
|
128
138
|
if (fs.existsSync(DOMAINS_DIR)) {
|
|
129
139
|
const legacy = fs.readdirSync(DOMAINS_DIR).filter((e) => {
|
|
130
|
-
if (e.startsWith('
|
|
140
|
+
if (e.startsWith('.')) return false;
|
|
131
141
|
try {
|
|
132
142
|
return fs.statSync(path.join(DOMAINS_DIR, e)).isDirectory();
|
|
133
143
|
} catch {
|
|
@@ -136,19 +146,8 @@ async function cmdSetup() {
|
|
|
136
146
|
});
|
|
137
147
|
if (legacy.length) {
|
|
138
148
|
console.log('');
|
|
139
|
-
warn(
|
|
140
|
-
|
|
141
|
-
);
|
|
142
|
-
for (const d of legacy) {
|
|
143
|
-
const dPath = path.join(DOMAINS_DIR, d);
|
|
144
|
-
try {
|
|
145
|
-
fs.rmSync(dPath, { recursive: true, force: true });
|
|
146
|
-
log(` removed ~/.kdna/domains/${d}/`);
|
|
147
|
-
} catch (e) {
|
|
148
|
-
warn(` could not remove ~/.kdna/domains/${d}/ — ${e.message}`);
|
|
149
|
-
console.log(` To remove manually: rm -rf ~/.kdna/domains/${d}/`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
149
|
+
warn(`Ignoring ${legacy.length} legacy domain director${legacy.length > 1 ? 'ies' : 'y'}.`);
|
|
150
|
+
console.log(' Runtime assets now live under ~/.kdna/packages/ as .kdna files.');
|
|
152
151
|
console.log(' Re-install with: kdna install @aikdna/<name>');
|
|
153
152
|
console.log('');
|
|
154
153
|
}
|