@aikdna/kdna-cli 0.16.1 → 0.16.3
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/package.json +1 -1
- package/skills/kdna-loader/SKILL.md +45 -0
- package/src/agent.js +65 -18
- package/src/cmds/doctor.js +34 -8
- package/src/cmds/domain.js +27 -1
- package/src/cmds/encrypt.js +14 -5
- package/src/cmds/license.js +49 -19
- package/src/cmds/trace.js +38 -15
- package/src/compare.js +80 -21
- package/src/install.js +72 -38
package/package.json
CHANGED
|
@@ -215,6 +215,51 @@ KDNA does not override:
|
|
|
215
215
|
|
|
216
216
|
---
|
|
217
217
|
|
|
218
|
+
## Safety & Governance
|
|
219
|
+
|
|
220
|
+
KDNA domains influence agent judgment. The loader MUST apply safety rules before loading any domain.
|
|
221
|
+
|
|
222
|
+
### Loading Priority
|
|
223
|
+
|
|
224
|
+
When KDNA is loaded, the agent MUST respect this priority order:
|
|
225
|
+
1. System safety policy (highest — cannot be overridden)
|
|
226
|
+
2. Legal and compliance requirements
|
|
227
|
+
3. User's explicit intent
|
|
228
|
+
4. KDNA domain judgment
|
|
229
|
+
5. Tool/skill instructions (lowest)
|
|
230
|
+
|
|
231
|
+
KDNA MUST NOT override system safety policies, legal requirements, or the user's explicit refusal to apply domain judgment.
|
|
232
|
+
|
|
233
|
+
### Risk-Level Checks
|
|
234
|
+
|
|
235
|
+
Before loading a KDNA domain, check its risk level in `kdna.json` or `KDNA_CARD.json`:
|
|
236
|
+
|
|
237
|
+
| Risk Level | Loading Behavior |
|
|
238
|
+
|-----------|-----------------|
|
|
239
|
+
| **R0** (Low) | Load silently |
|
|
240
|
+
| **R1** (Medium) | Load silently; log |
|
|
241
|
+
| **R2** (High) | Warn user before loading; require confirmation |
|
|
242
|
+
| **R3** (Restricted) | Reject loading unless explicitly authorized |
|
|
243
|
+
|
|
244
|
+
### Signature & Trust Checks
|
|
245
|
+
|
|
246
|
+
- Yanked domains: **REJECT** loading (domain has been withdrawn for safety)
|
|
247
|
+
- Deprecated domains: warn; suggest replacement if `replaced_by` is set
|
|
248
|
+
- Unsigned domains: warn; load only if user confirms
|
|
249
|
+
- Unknown scope domains: warn; load only if user confirms
|
|
250
|
+
|
|
251
|
+
### Runtime Logging
|
|
252
|
+
|
|
253
|
+
Every KDNA load MUST be logged with:
|
|
254
|
+
- Domain name and version
|
|
255
|
+
- Risk level
|
|
256
|
+
- Signature status
|
|
257
|
+
- Which axioms were triggered
|
|
258
|
+
- Which misunderstandings were avoided
|
|
259
|
+
- Self-check pass rate
|
|
260
|
+
|
|
261
|
+
This enables audit and accountability.
|
|
262
|
+
|
|
218
263
|
## Failure handling
|
|
219
264
|
|
|
220
265
|
| Situation | What to do |
|
package/src/agent.js
CHANGED
|
@@ -371,7 +371,12 @@ function cmdLoad(input, args = []) {
|
|
|
371
371
|
// JSON format
|
|
372
372
|
if (format === 'json') {
|
|
373
373
|
process.stdout.write(JSON.stringify({ manifest, core, patterns: pat }, null, 2) + '\n');
|
|
374
|
-
recordTrace({
|
|
374
|
+
recordTrace({
|
|
375
|
+
timestamp: new Date().toISOString(),
|
|
376
|
+
agent: detectAgent(),
|
|
377
|
+
domain: parsed.full,
|
|
378
|
+
format: 'json',
|
|
379
|
+
});
|
|
375
380
|
return;
|
|
376
381
|
}
|
|
377
382
|
|
|
@@ -384,20 +389,35 @@ function cmdLoad(input, args = []) {
|
|
|
384
389
|
process.stdout.write(fs.readFileSync(p, 'utf8'));
|
|
385
390
|
}
|
|
386
391
|
}
|
|
387
|
-
recordTrace({
|
|
392
|
+
recordTrace({
|
|
393
|
+
timestamp: new Date().toISOString(),
|
|
394
|
+
agent: detectAgent(),
|
|
395
|
+
domain: parsed.full,
|
|
396
|
+
format: 'raw',
|
|
397
|
+
});
|
|
388
398
|
return;
|
|
389
399
|
}
|
|
390
400
|
|
|
391
401
|
// Load profiles
|
|
392
402
|
if (profile) {
|
|
393
403
|
emitProfile(parsed, manifest, core, pat, profile, profileInput);
|
|
394
|
-
recordTrace({
|
|
404
|
+
recordTrace({
|
|
405
|
+
timestamp: new Date().toISOString(),
|
|
406
|
+
agent: detectAgent(),
|
|
407
|
+
domain: parsed.full,
|
|
408
|
+
format: `profile:${profile}`,
|
|
409
|
+
});
|
|
395
410
|
return;
|
|
396
411
|
}
|
|
397
412
|
|
|
398
413
|
// Default: --as=prompt — compact text optimized for system-prompt injection.
|
|
399
414
|
emitCompact(parsed, manifest, core, pat);
|
|
400
|
-
recordTrace({
|
|
415
|
+
recordTrace({
|
|
416
|
+
timestamp: new Date().toISOString(),
|
|
417
|
+
agent: detectAgent(),
|
|
418
|
+
domain: parsed.full,
|
|
419
|
+
format: 'prompt',
|
|
420
|
+
});
|
|
401
421
|
}
|
|
402
422
|
|
|
403
423
|
// ─── Load profiles ─────────────────────────────────────────────────────
|
|
@@ -420,7 +440,8 @@ function emitProfile(parsed, manifest, core, pat, profile, input) {
|
|
|
420
440
|
for (const a of axioms) {
|
|
421
441
|
lines.push(`- ${a.one_sentence}`);
|
|
422
442
|
if (a.applies_when?.length) lines.push(` APPLIES: ${a.applies_when.join('; ')}`);
|
|
423
|
-
if (a.does_not_apply_when?.length)
|
|
443
|
+
if (a.does_not_apply_when?.length)
|
|
444
|
+
lines.push(` NOT: ${a.does_not_apply_when.join('; ')}`);
|
|
424
445
|
}
|
|
425
446
|
lines.push('');
|
|
426
447
|
}
|
|
@@ -434,7 +455,11 @@ function emitProfile(parsed, manifest, core, pat, profile, input) {
|
|
|
434
455
|
const taskTokens = tokenize(input || '');
|
|
435
456
|
const relevant = axioms.filter((a) => {
|
|
436
457
|
if (!a.applies_when?.length) return false;
|
|
437
|
-
const combinedText = [
|
|
458
|
+
const combinedText = [
|
|
459
|
+
...a.applies_when,
|
|
460
|
+
a.one_sentence || '',
|
|
461
|
+
a.full_statement || '',
|
|
462
|
+
].join(' ');
|
|
438
463
|
const score = overlapScore(taskTokens, combinedText);
|
|
439
464
|
return score.hits >= 1 || score.coverage >= 0.1;
|
|
440
465
|
});
|
|
@@ -603,7 +628,9 @@ function cmdSelect(args = []) {
|
|
|
603
628
|
|
|
604
629
|
if (!input) {
|
|
605
630
|
if (wantJson) {
|
|
606
|
-
console.log(
|
|
631
|
+
console.log(
|
|
632
|
+
JSON.stringify({ error: 'Usage: kdna select --input "<task>" [--max-domains=N] [--json]' }),
|
|
633
|
+
);
|
|
607
634
|
process.exit(2);
|
|
608
635
|
}
|
|
609
636
|
console.error('Usage: kdna select --input "<task>" [--max-domains=N] [--json]');
|
|
@@ -664,12 +691,18 @@ function cmdSelect(args = []) {
|
|
|
664
691
|
const selected = scores.slice(0, maxDomains);
|
|
665
692
|
|
|
666
693
|
if (wantJson) {
|
|
667
|
-
console.log(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
694
|
+
console.log(
|
|
695
|
+
JSON.stringify(
|
|
696
|
+
{
|
|
697
|
+
input: input.slice(0, 200),
|
|
698
|
+
selected,
|
|
699
|
+
max_domains: maxDomains,
|
|
700
|
+
total_candidates: scores.length,
|
|
701
|
+
},
|
|
702
|
+
null,
|
|
703
|
+
2,
|
|
704
|
+
),
|
|
705
|
+
);
|
|
673
706
|
return;
|
|
674
707
|
}
|
|
675
708
|
|
|
@@ -775,7 +808,9 @@ function cmdPostvalidate(args = []) {
|
|
|
775
808
|
for (const sc of selfChecks) {
|
|
776
809
|
const text = typeof sc === 'string' ? sc : sc.question;
|
|
777
810
|
if (text) {
|
|
778
|
-
const keywords = tokenize(text)
|
|
811
|
+
const keywords = tokenize(text)
|
|
812
|
+
.filter((t) => t.length > 3)
|
|
813
|
+
.slice(0, 3);
|
|
779
814
|
const found = keywords.some((k) => agentOutput.toLowerCase().includes(k));
|
|
780
815
|
if (found) foundChecks++;
|
|
781
816
|
}
|
|
@@ -794,7 +829,9 @@ function cmdPostvalidate(args = []) {
|
|
|
794
829
|
for (const notApply of ax.does_not_apply_when || []) {
|
|
795
830
|
if (overlapScore(tokenize(agentOutput), notApply).hits >= 2) {
|
|
796
831
|
boundaryViolations++;
|
|
797
|
-
results.violations.push(
|
|
832
|
+
results.violations.push(
|
|
833
|
+
`boundary violation: ${ax.id} (should not apply when "${notApply.slice(0, 80)}")`,
|
|
834
|
+
);
|
|
798
835
|
break;
|
|
799
836
|
}
|
|
800
837
|
}
|
|
@@ -828,14 +865,20 @@ function cmdPostvalidate(args = []) {
|
|
|
828
865
|
agent: detectAgent(),
|
|
829
866
|
domain: parsed.full,
|
|
830
867
|
type: 'postvalidate',
|
|
831
|
-
postvalidate: {
|
|
868
|
+
postvalidate: {
|
|
869
|
+
result: results.violations.length ? 'fail' : 'pass',
|
|
870
|
+
violations: results.violations.length,
|
|
871
|
+
passed: results.passed.length,
|
|
872
|
+
},
|
|
832
873
|
});
|
|
833
874
|
process.exit(results.violations.length ? 1 : 0);
|
|
834
875
|
}
|
|
835
876
|
|
|
836
877
|
console.log(`Post-validation: ${parsed.full}`);
|
|
837
878
|
console.log('');
|
|
838
|
-
console.log(
|
|
879
|
+
console.log(
|
|
880
|
+
` Violations: ${results.violations.length} Warnings: ${results.warnings.length} Passed: ${results.passed.length}`,
|
|
881
|
+
);
|
|
839
882
|
console.log('');
|
|
840
883
|
|
|
841
884
|
if (results.violations.length) {
|
|
@@ -858,7 +901,11 @@ function cmdPostvalidate(args = []) {
|
|
|
858
901
|
agent: detectAgent(),
|
|
859
902
|
domain: parsed.full,
|
|
860
903
|
type: 'postvalidate',
|
|
861
|
-
postvalidate: {
|
|
904
|
+
postvalidate: {
|
|
905
|
+
result: results.violations.length ? 'fail' : 'pass',
|
|
906
|
+
violations: results.violations.length,
|
|
907
|
+
passed: results.passed.length,
|
|
908
|
+
},
|
|
862
909
|
});
|
|
863
910
|
process.exit(results.violations.length ? 1 : 0);
|
|
864
911
|
}
|
package/src/cmds/doctor.js
CHANGED
|
@@ -9,7 +9,11 @@ const AGENTS = [
|
|
|
9
9
|
{ name: 'Codex', dir: path.join(process.env.HOME || '', '.codex'), skillsDir: 'skills' },
|
|
10
10
|
{ name: 'Claude Code', dir: path.join(process.env.HOME || '', '.claude'), skillsDir: 'skills' },
|
|
11
11
|
{ name: 'Cursor', dir: path.join(process.env.HOME || '', '.cursor'), skillsDir: 'skills' },
|
|
12
|
-
{
|
|
12
|
+
{
|
|
13
|
+
name: 'Gemini Antigravity',
|
|
14
|
+
dir: path.join(process.env.HOME || '', '.gemini', 'antigravity'),
|
|
15
|
+
skillsDir: 'skills',
|
|
16
|
+
},
|
|
13
17
|
];
|
|
14
18
|
|
|
15
19
|
const V2_1_MARKER = 'kdna available';
|
|
@@ -91,7 +95,11 @@ function cmdDoctor(args) {
|
|
|
91
95
|
detail: `${domains} domain${domains !== 1 ? 's' : ''} installed`,
|
|
92
96
|
});
|
|
93
97
|
} else {
|
|
94
|
-
checks.push({
|
|
98
|
+
checks.push({
|
|
99
|
+
name: 'Domains directory',
|
|
100
|
+
status: 'warn',
|
|
101
|
+
detail: '~/.kdna/domains/ not found',
|
|
102
|
+
});
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
|
|
@@ -100,7 +108,9 @@ function cmdDoctor(args) {
|
|
|
100
108
|
const detected = detectAgents();
|
|
101
109
|
for (const agent of AGENTS) {
|
|
102
110
|
const agentDirExists = fs.existsSync(agent.dir);
|
|
103
|
-
const skill = agentDirExists
|
|
111
|
+
const skill = agentDirExists
|
|
112
|
+
? checkAgentSkill(agent)
|
|
113
|
+
: { installed: false, version: null, path: null };
|
|
104
114
|
|
|
105
115
|
let status, detail;
|
|
106
116
|
if (!agentDirExists) {
|
|
@@ -159,7 +169,11 @@ function cmdDoctor(args) {
|
|
|
159
169
|
checks.push({ name: 'Registry cache', status: 'warn', detail: 'cannot read cache' });
|
|
160
170
|
}
|
|
161
171
|
} else {
|
|
162
|
-
checks.push({
|
|
172
|
+
checks.push({
|
|
173
|
+
name: 'Registry cache',
|
|
174
|
+
status: 'warn',
|
|
175
|
+
detail: 'not cached (run: kdna registry refresh)',
|
|
176
|
+
});
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
// 8. Schema files available
|
|
@@ -184,7 +198,11 @@ function cmdDoctor(args) {
|
|
|
184
198
|
if (fs.existsSync(projectConfig)) {
|
|
185
199
|
checks.push({ name: 'Project config', status: 'ok', detail: projectConfig });
|
|
186
200
|
} else {
|
|
187
|
-
checks.push({
|
|
201
|
+
checks.push({
|
|
202
|
+
name: 'Project config',
|
|
203
|
+
status: 'warn',
|
|
204
|
+
detail: 'No .kdna/config.json in current project',
|
|
205
|
+
});
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
|
|
@@ -196,7 +214,11 @@ function cmdDoctor(args) {
|
|
|
196
214
|
name: c.name,
|
|
197
215
|
status: c.status,
|
|
198
216
|
detail: c.detail,
|
|
199
|
-
...(c.agent && {
|
|
217
|
+
...(c.agent && {
|
|
218
|
+
agent: c.agent,
|
|
219
|
+
skillInstalled: c.skillInstalled,
|
|
220
|
+
skillVersion: c.skillVersion,
|
|
221
|
+
}),
|
|
200
222
|
})),
|
|
201
223
|
ok: checks.filter((c) => c.status === 'ok').length,
|
|
202
224
|
warnings: checks.filter((c) => c.status === 'warn').length,
|
|
@@ -218,9 +240,13 @@ function cmdDoctor(args) {
|
|
|
218
240
|
const fails = checks.filter((c) => c.status === 'fail').length;
|
|
219
241
|
console.log('');
|
|
220
242
|
if (fails > 0) {
|
|
221
|
-
console.log(
|
|
243
|
+
console.log(
|
|
244
|
+
`${ok}/${checks.length} checks passed (${fails} failure${fails !== 1 ? 's' : ''}, ${warns} warning${warns !== 1 ? 's' : ''})`,
|
|
245
|
+
);
|
|
222
246
|
} else if (warns > 0) {
|
|
223
|
-
console.log(
|
|
247
|
+
console.log(
|
|
248
|
+
`${ok}/${checks.length} checks passed (${warns} warning${warns !== 1 ? 's' : ''})`,
|
|
249
|
+
);
|
|
224
250
|
} else {
|
|
225
251
|
console.log(`${ok}/${checks.length} checks passed`);
|
|
226
252
|
}
|
package/src/cmds/domain.js
CHANGED
|
@@ -656,6 +656,19 @@ function cmdInspect(dir, jsonMode = false) {
|
|
|
656
656
|
},
|
|
657
657
|
axioms: (c.axioms || []).map((a) => a.one_sentence || null).filter(Boolean),
|
|
658
658
|
};
|
|
659
|
+
|
|
660
|
+
// Governance metadata (KDNA_CARD.json)
|
|
661
|
+
const card = readJson(path.join(abs, 'KDNA_CARD.json'));
|
|
662
|
+
if (card) {
|
|
663
|
+
result.governance = {
|
|
664
|
+
risk_level: card.risk_level || null,
|
|
665
|
+
review_status: card.review_status || null,
|
|
666
|
+
intended_use: card.intended_use || [],
|
|
667
|
+
out_of_scope: card.out_of_scope || [],
|
|
668
|
+
known_limitations: card.known_limitations || [],
|
|
669
|
+
requires_expert_review: card.requires_expert_review || false,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
659
672
|
console.log(JSON.stringify(result, null, 2));
|
|
660
673
|
return;
|
|
661
674
|
}
|
|
@@ -703,7 +716,20 @@ function cmdInspect(dir, jsonMode = false) {
|
|
|
703
716
|
|
|
704
717
|
if (rea) console.log(` Reasoning chains: ${(rea.reasoning_chains || []).length}`);
|
|
705
718
|
|
|
706
|
-
if (evo)
|
|
719
|
+
if (evo) console.log(` Evolution stages: ${(evo.stages || []).length}`);
|
|
720
|
+
|
|
721
|
+
// Governance metadata
|
|
722
|
+
const kdnaCard = readJson(path.join(abs, 'KDNA_CARD.json'));
|
|
723
|
+
if (kdnaCard) {
|
|
724
|
+
console.log('');
|
|
725
|
+
console.log(' ── Governance ──');
|
|
726
|
+
console.log(` Risk level: ${kdnaCard.risk_level || '?'}`);
|
|
727
|
+
console.log(` Review status: ${kdnaCard.review_status || '?'}`);
|
|
728
|
+
if (kdnaCard.intended_use?.length) console.log(` Intended use: ${kdnaCard.intended_use[0]}${kdnaCard.intended_use.length > 1 ? ` (+${kdnaCard.intended_use.length - 1} more)` : ''}`);
|
|
729
|
+
if (kdnaCard.out_of_scope?.length) console.log(` Out of scope: ${kdnaCard.out_of_scope[0]}${kdnaCard.out_of_scope.length > 1 ? ` (+${kdnaCard.out_of_scope.length - 1} more)` : ''}`);
|
|
730
|
+
if (kdnaCard.known_limitations?.length) console.log(` Limitations: ${kdnaCard.known_limitations[0]}${kdnaCard.known_limitations.length > 1 ? ` (+${kdnaCard.known_limitations.length - 1} more)` : ''}`);
|
|
731
|
+
if (kdnaCard.requires_expert_review) console.log(` ⚠ Expert review required`);
|
|
732
|
+
}
|
|
707
733
|
|
|
708
734
|
console.log('');
|
|
709
735
|
console.log(' ── Axioms ──');
|
package/src/cmds/encrypt.js
CHANGED
|
@@ -49,23 +49,32 @@ function machineFingerprint() {
|
|
|
49
49
|
const uuid = execSync('ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID', {
|
|
50
50
|
encoding: 'utf8',
|
|
51
51
|
timeout: 3000,
|
|
52
|
-
})
|
|
52
|
+
})
|
|
53
|
+
.match(/"[A-F0-9-]{36}"/)?.[0]
|
|
54
|
+
?.replace(/"/g, '');
|
|
53
55
|
if (uuid) parts.push(uuid);
|
|
54
56
|
}
|
|
55
57
|
if (os.platform() === 'linux') {
|
|
56
58
|
try {
|
|
57
59
|
const mid = require('fs').readFileSync('/etc/machine-id', 'utf8').trim();
|
|
58
60
|
if (mid) parts.push(mid);
|
|
59
|
-
} catch {
|
|
61
|
+
} catch {
|
|
62
|
+
/* ignore */
|
|
63
|
+
}
|
|
60
64
|
}
|
|
61
|
-
} catch {
|
|
65
|
+
} catch {
|
|
66
|
+
/* non-critical */
|
|
67
|
+
}
|
|
62
68
|
return crypto.createHash('sha256').update(parts.join('|')).digest('hex');
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
// ─── Key Derivation ─────────────────────────────────────────────────────
|
|
66
72
|
|
|
67
73
|
function deriveKey(licenseKey, fingerprint) {
|
|
68
|
-
const salt = crypto
|
|
74
|
+
const salt = crypto
|
|
75
|
+
.createHash('sha256')
|
|
76
|
+
.update(fingerprint || machineFingerprint())
|
|
77
|
+
.digest();
|
|
69
78
|
return crypto.pbkdf2Sync(licenseKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
|
|
70
79
|
}
|
|
71
80
|
|
|
@@ -103,7 +112,7 @@ function isEncryptedContainer(filePath) {
|
|
|
103
112
|
function createLicense(domain, options = {}) {
|
|
104
113
|
const {
|
|
105
114
|
issuedTo = 'licensee@example.com',
|
|
106
|
-
expiresAt = null,
|
|
115
|
+
expiresAt = null, // ISO date or null for perpetual
|
|
107
116
|
maxAgents = 1,
|
|
108
117
|
requireMachineBinding = true,
|
|
109
118
|
requireOnlineCheck = false,
|
package/src/cmds/license.js
CHANGED
|
@@ -11,9 +11,19 @@ const fs = require('fs');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const crypto = require('crypto');
|
|
13
13
|
const { EXIT, error } = require('./_common');
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const {
|
|
15
|
+
createLicense,
|
|
16
|
+
signLicense,
|
|
17
|
+
verifyLicense,
|
|
18
|
+
verifyLicenseSignature,
|
|
19
|
+
machineFingerprint,
|
|
20
|
+
} = require('./encrypt');
|
|
21
|
+
|
|
22
|
+
const IDENTITY_DIR = path.join(
|
|
23
|
+
process.env.HOME || process.env.USERPROFILE || '.',
|
|
24
|
+
'.kdna',
|
|
25
|
+
'identity',
|
|
26
|
+
);
|
|
17
27
|
const PRIVATE_KEY_PATH = path.join(IDENTITY_DIR, 'kdna.key');
|
|
18
28
|
const PUBLIC_KEY_PATH = path.join(IDENTITY_DIR, 'kdna.pub');
|
|
19
29
|
|
|
@@ -29,7 +39,11 @@ function readIdentity() {
|
|
|
29
39
|
|
|
30
40
|
function cmdLicenseGenerate(args) {
|
|
31
41
|
const domain = args[0];
|
|
32
|
-
if (!domain)
|
|
42
|
+
if (!domain)
|
|
43
|
+
error(
|
|
44
|
+
'Usage: kdna license generate <domain> --to <email> [--expires <date>] [--max-agents <n>]',
|
|
45
|
+
EXIT.INPUT_ERROR,
|
|
46
|
+
);
|
|
33
47
|
|
|
34
48
|
const emailIdx = args.indexOf('--to');
|
|
35
49
|
const email = emailIdx >= 0 ? args[emailIdx + 1] : null;
|
|
@@ -82,7 +96,7 @@ function cmdLicenseGenerate(args) {
|
|
|
82
96
|
|
|
83
97
|
function cmdLicenseVerify(args) {
|
|
84
98
|
const jsonMode = args.includes('--json');
|
|
85
|
-
const filtered = args.filter(a => !a.startsWith('--'));
|
|
99
|
+
const filtered = args.filter((a) => !a.startsWith('--'));
|
|
86
100
|
const licensePath = filtered[0];
|
|
87
101
|
if (!licensePath) error('Usage: kdna license verify <license.json>', EXIT.INPUT_ERROR);
|
|
88
102
|
|
|
@@ -99,15 +113,21 @@ function cmdLicenseVerify(args) {
|
|
|
99
113
|
const result = verifyLicense(license, publicKey, fp);
|
|
100
114
|
|
|
101
115
|
if (jsonMode) {
|
|
102
|
-
console.log(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
console.log(
|
|
117
|
+
JSON.stringify(
|
|
118
|
+
{
|
|
119
|
+
domain: license.domain,
|
|
120
|
+
license_id: license.license_id,
|
|
121
|
+
issued_to: license.issued_to,
|
|
122
|
+
signature_valid: signatureValid,
|
|
123
|
+
valid: result.valid,
|
|
124
|
+
issues: result.issues,
|
|
125
|
+
fingerprint: license.require_machine_binding ? fp : 'not required',
|
|
126
|
+
},
|
|
127
|
+
null,
|
|
128
|
+
2,
|
|
129
|
+
),
|
|
130
|
+
);
|
|
111
131
|
} else {
|
|
112
132
|
console.log(`License: ${license.license_id}`);
|
|
113
133
|
console.log(`Domain: ${license.domain}`);
|
|
@@ -124,7 +144,7 @@ function cmdLicenseVerify(args) {
|
|
|
124
144
|
if (result.issues.length) {
|
|
125
145
|
console.log('');
|
|
126
146
|
console.log('Issues:');
|
|
127
|
-
result.issues.forEach(i => console.log(` ✗ ${i}`));
|
|
147
|
+
result.issues.forEach((i) => console.log(` ✗ ${i}`));
|
|
128
148
|
} else {
|
|
129
149
|
console.log('');
|
|
130
150
|
console.log('✓ License valid');
|
|
@@ -135,7 +155,7 @@ function cmdLicenseVerify(args) {
|
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
function cmdLicenseBind(args) {
|
|
138
|
-
const filtered = args.filter(a => !a.startsWith('--'));
|
|
158
|
+
const filtered = args.filter((a) => !a.startsWith('--'));
|
|
139
159
|
const licensePath = filtered[0];
|
|
140
160
|
if (!licensePath) error('Usage: kdna license bind <license.json>', EXIT.INPUT_ERROR);
|
|
141
161
|
|
|
@@ -166,7 +186,7 @@ function cmdLicenseBind(args) {
|
|
|
166
186
|
}
|
|
167
187
|
|
|
168
188
|
function cmdLicenseShow(args) {
|
|
169
|
-
const filtered = args.filter(a => !a.startsWith('--'));
|
|
189
|
+
const filtered = args.filter((a) => !a.startsWith('--'));
|
|
170
190
|
const licensePath = filtered[0];
|
|
171
191
|
if (!licensePath) {
|
|
172
192
|
const local = path.join(process.cwd(), 'license.json');
|
|
@@ -189,7 +209,11 @@ function cmdLicenseInstall(args) {
|
|
|
189
209
|
|
|
190
210
|
if (!license.domain) error('License missing domain field', EXIT.INPUT_ERROR);
|
|
191
211
|
|
|
192
|
-
const licenseDir = path.join(
|
|
212
|
+
const licenseDir = path.join(
|
|
213
|
+
process.env.HOME || process.env.USERPROFILE || '.',
|
|
214
|
+
'.kdna',
|
|
215
|
+
'licenses',
|
|
216
|
+
);
|
|
193
217
|
fs.mkdirSync(licenseDir, { recursive: true });
|
|
194
218
|
|
|
195
219
|
const safeName = license.domain.replace(/^@/, '').replace('/', '-');
|
|
@@ -204,4 +228,10 @@ function cmdLicenseInstall(args) {
|
|
|
204
228
|
console.log(`Now install the domain: kdna install ${license.domain}`);
|
|
205
229
|
}
|
|
206
230
|
|
|
207
|
-
module.exports = {
|
|
231
|
+
module.exports = {
|
|
232
|
+
cmdLicenseGenerate,
|
|
233
|
+
cmdLicenseVerify,
|
|
234
|
+
cmdLicenseBind,
|
|
235
|
+
cmdLicenseShow,
|
|
236
|
+
cmdLicenseInstall,
|
|
237
|
+
};
|
package/src/cmds/trace.js
CHANGED
|
@@ -20,7 +20,10 @@ function todayFile() {
|
|
|
20
20
|
|
|
21
21
|
function traceFiles(sinceDate) {
|
|
22
22
|
ensureTracesDir();
|
|
23
|
-
let files = fs
|
|
23
|
+
let files = fs
|
|
24
|
+
.readdirSync(TRACES_DIR)
|
|
25
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
26
|
+
.sort();
|
|
24
27
|
if (sinceDate) {
|
|
25
28
|
const since = sinceDate instanceof Date ? sinceDate : new Date(sinceDate);
|
|
26
29
|
files = files.filter((f) => {
|
|
@@ -45,9 +48,13 @@ function readAllTraces(opts = {}) {
|
|
|
45
48
|
if (agent && entry.agent !== agent) continue;
|
|
46
49
|
if (domain && entry.domain !== domain) continue;
|
|
47
50
|
entries.push(entry);
|
|
48
|
-
} catch {
|
|
51
|
+
} catch {
|
|
52
|
+
/* skip malformed lines */
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
|
-
} catch {
|
|
55
|
+
} catch {
|
|
56
|
+
/* skip unreadable files */
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
59
|
return entries;
|
|
53
60
|
}
|
|
@@ -129,14 +136,18 @@ function cmdTrace(args) {
|
|
|
129
136
|
console.log(`${'Timestamp'.padEnd(20)} ${'Agent'.padEnd(15)} ${'Domain'.padEnd(25)} ${'Result'}`);
|
|
130
137
|
console.log('-'.repeat(75));
|
|
131
138
|
for (const e of entries.slice(-50).reverse()) {
|
|
132
|
-
const ts = e.timestamp
|
|
139
|
+
const ts = e.timestamp
|
|
140
|
+
? new Date(e.timestamp).toISOString().replace('T', ' ').slice(0, 19)
|
|
141
|
+
: 'unknown';
|
|
133
142
|
const agent = (e.agent || 'unknown').padEnd(15);
|
|
134
143
|
const domain = (e.domain || '(none)').padEnd(25);
|
|
135
144
|
const result = e.postvalidate?.result || 'loaded';
|
|
136
145
|
console.log(`${ts} ${agent} ${domain} ${result}`);
|
|
137
146
|
}
|
|
138
147
|
console.log('');
|
|
139
|
-
console.log(
|
|
148
|
+
console.log(
|
|
149
|
+
`${entries.length} entries total. --export <file> for audit export. --clear to reset.`,
|
|
150
|
+
);
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
function cmdHistory(args) {
|
|
@@ -166,13 +177,19 @@ function cmdHistory(args) {
|
|
|
166
177
|
}
|
|
167
178
|
|
|
168
179
|
if (json) {
|
|
169
|
-
console.log(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
console.log(
|
|
181
|
+
JSON.stringify(
|
|
182
|
+
{
|
|
183
|
+
total,
|
|
184
|
+
skipped,
|
|
185
|
+
domainCounts,
|
|
186
|
+
agentCounts,
|
|
187
|
+
skipRate: total > 0 ? Math.round((skipped / total) * 100) : 0,
|
|
188
|
+
},
|
|
189
|
+
null,
|
|
190
|
+
2,
|
|
191
|
+
),
|
|
192
|
+
);
|
|
176
193
|
} else {
|
|
177
194
|
console.log(`Total KDNA loads: ${total}`);
|
|
178
195
|
console.log(`Skipped (no domain): ${skipped}`);
|
|
@@ -208,10 +225,14 @@ function cmdHistory(args) {
|
|
|
208
225
|
process.exit(EXIT.OK);
|
|
209
226
|
}
|
|
210
227
|
|
|
211
|
-
console.log(
|
|
228
|
+
console.log(
|
|
229
|
+
`${'Timestamp'.padEnd(20)} ${'Agent'.padEnd(15)} ${'Domain'.padEnd(28)} ${'Result'.padEnd(10)} ${'Score'}`,
|
|
230
|
+
);
|
|
212
231
|
console.log('-'.repeat(85));
|
|
213
232
|
for (const e of recent) {
|
|
214
|
-
const ts = e.timestamp
|
|
233
|
+
const ts = e.timestamp
|
|
234
|
+
? new Date(e.timestamp).toISOString().replace('T', ' ').slice(0, 19)
|
|
235
|
+
: 'unknown';
|
|
215
236
|
const agent = (e.agent || 'unknown').padEnd(15);
|
|
216
237
|
const domain = (e.domain || '(none)').padEnd(28);
|
|
217
238
|
const result = (e.postvalidate?.result || 'loaded').padEnd(10);
|
|
@@ -219,7 +240,9 @@ function cmdHistory(args) {
|
|
|
219
240
|
console.log(`${ts} ${agent} ${domain} ${result} ${score}`);
|
|
220
241
|
}
|
|
221
242
|
console.log('');
|
|
222
|
-
console.log(
|
|
243
|
+
console.log(
|
|
244
|
+
`Showing ${recent.length} of ${entries.length} total entries. --stats for summary. --domain <name> to filter.`,
|
|
245
|
+
);
|
|
223
246
|
}
|
|
224
247
|
|
|
225
248
|
module.exports = { cmdTrace, cmdHistory, recordTrace, readAllTraces };
|
package/src/compare.js
CHANGED
|
@@ -299,17 +299,21 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
|
|
|
299
299
|
const domainScore = scoreDiff(axes);
|
|
300
300
|
const axioms = core.axioms || [];
|
|
301
301
|
const selfChecks = pat.self_check || [];
|
|
302
|
-
const bannedTerms = (pat.terminology?.banned_terms || []).map(t =>
|
|
302
|
+
const bannedTerms = (pat.terminology?.banned_terms || []).map((t) =>
|
|
303
|
+
typeof t === 'string' ? t : t.term,
|
|
304
|
+
);
|
|
303
305
|
const misunderstandings = pat.misunderstandings || [];
|
|
304
306
|
|
|
305
307
|
const lines = [];
|
|
306
308
|
lines.push('# KDNA Judgment Comparison Report');
|
|
307
309
|
lines.push('');
|
|
308
310
|
lines.push(`**Domain:** ${parsed.full} (v${manifest.version || '?'})`);
|
|
309
|
-
lines.push(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
lines.push(
|
|
312
|
+
`**Input:** "${((args) => {
|
|
313
|
+
const i = args.indexOf('--input');
|
|
314
|
+
return i >= 0 ? args[i + 1].slice(0, 120) : '?';
|
|
315
|
+
})(process.argv.slice(2))}"`,
|
|
316
|
+
);
|
|
313
317
|
lines.push(`**Model:** ${llm.provider} / ${llm.model}`);
|
|
314
318
|
lines.push(`**Date:** ${new Date().toISOString()}`);
|
|
315
319
|
lines.push('');
|
|
@@ -318,7 +322,14 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
|
|
|
318
322
|
lines.push('## Without KDNA');
|
|
319
323
|
lines.push('');
|
|
320
324
|
lines.push('### Judgment Path');
|
|
321
|
-
lines.push(
|
|
325
|
+
lines.push(
|
|
326
|
+
responseA
|
|
327
|
+
.split('\n')
|
|
328
|
+
.filter((l) => l.trim())
|
|
329
|
+
.slice(0, 3)
|
|
330
|
+
.map((l) => `- ${l}`)
|
|
331
|
+
.join('\n'),
|
|
332
|
+
);
|
|
322
333
|
lines.push('');
|
|
323
334
|
lines.push('### Key Deficiencies');
|
|
324
335
|
lines.push('- No domain-specific diagnosis applied');
|
|
@@ -331,12 +342,21 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
|
|
|
331
342
|
lines.push(`### Domain Loaded`);
|
|
332
343
|
lines.push(`- Name: ${parsed.full}`);
|
|
333
344
|
lines.push(`- Axioms applied: ${axioms.length} total`);
|
|
334
|
-
lines.push(
|
|
345
|
+
lines.push(
|
|
346
|
+
`- Frameworks: ${(core.frameworks || []).map((f) => f.id).join(', ') || 'none declared'}`,
|
|
347
|
+
);
|
|
335
348
|
lines.push(`- Self-checks: ${selfChecks.length} items`);
|
|
336
349
|
lines.push(`- Banned terms: ${bannedTerms.length}`);
|
|
337
350
|
lines.push('');
|
|
338
351
|
lines.push('### Judgment Path');
|
|
339
|
-
lines.push(
|
|
352
|
+
lines.push(
|
|
353
|
+
responseB
|
|
354
|
+
.split('\n')
|
|
355
|
+
.filter((l) => l.trim())
|
|
356
|
+
.slice(0, 3)
|
|
357
|
+
.map((l) => `- ${l}`)
|
|
358
|
+
.join('\n'),
|
|
359
|
+
);
|
|
340
360
|
lines.push('');
|
|
341
361
|
lines.push('---');
|
|
342
362
|
lines.push('');
|
|
@@ -354,9 +374,13 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
|
|
|
354
374
|
for (const d of dims) {
|
|
355
375
|
const v = axes[d.axis];
|
|
356
376
|
const changed = v && v.toUpperCase() !== 'SAME';
|
|
357
|
-
lines.push(
|
|
377
|
+
lines.push(
|
|
378
|
+
`| **${d.name}** | Generic | Domain-specific | **${changed ? 'Improved' : 'Same'}** |`,
|
|
379
|
+
);
|
|
358
380
|
}
|
|
359
|
-
lines.push(
|
|
381
|
+
lines.push(
|
|
382
|
+
`| **Self-check rate** | N/A | ${selfChecks.length > 0 ? 'Domain applied' : 'N/A'} | **Improved** |`,
|
|
383
|
+
);
|
|
360
384
|
lines.push('');
|
|
361
385
|
lines.push(`**Verdict:** ${verdict.replace(/_/g, ' ')}`);
|
|
362
386
|
lines.push('');
|
|
@@ -366,27 +390,47 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
|
|
|
366
390
|
lines.push('');
|
|
367
391
|
lines.push(`| D# | Dimension | Score (0-10) |`);
|
|
368
392
|
lines.push('|----|-----------|:-----------:|');
|
|
369
|
-
lines.push(
|
|
370
|
-
|
|
393
|
+
lines.push(
|
|
394
|
+
`| D1 | Diagnostic depth | ${domainScore.changed.includes('diagnosis') ? '8' : '5'} |`,
|
|
395
|
+
);
|
|
396
|
+
lines.push(
|
|
397
|
+
`| D2 | Terminology precision | ${domainScore.changed.includes('terminology') ? '8' : '5'} |`,
|
|
398
|
+
);
|
|
371
399
|
lines.push(`| D3 | Misunderstanding detection | 5 |`);
|
|
372
400
|
lines.push(`| D4 | Axiom alignment | ${domainScore.score} |`);
|
|
373
401
|
lines.push(`| D5 | Self-check pass rate | ${selfChecks.length > 0 ? '100%' : 'N/A'} |`);
|
|
374
|
-
lines.push(
|
|
402
|
+
lines.push(
|
|
403
|
+
`| D6 | Boundary respect | ${domainScore.changed.includes('boundary') ? 'Pass' : 'N/A'} |`,
|
|
404
|
+
);
|
|
375
405
|
lines.push(`| D7 | Risk avoidance | ${axes.failure ? 'Pass' : 'N/A'} |`);
|
|
376
406
|
lines.push('');
|
|
377
407
|
lines.push('---');
|
|
378
408
|
lines.push('');
|
|
379
409
|
lines.push('## Summary');
|
|
380
410
|
lines.push('');
|
|
381
|
-
const changedDims = domainScore.changed.map(c => `**${c}**`).join(', ');
|
|
382
|
-
lines.push(
|
|
411
|
+
const changedDims = domainScore.changed.map((c) => `**${c}**`).join(', ');
|
|
412
|
+
lines.push(
|
|
413
|
+
`Loading \`${parsed.full}\` changed the agent's response across ${domainScore.changed.length} dimensions: ${changedDims || 'no significant change'}. ${verdict.includes('changed') ? 'The reasoning trajectory shifted from generic to domain-specific judgment.' : 'The domain did not significantly alter the judgment trajectory for this input.'}`,
|
|
414
|
+
);
|
|
383
415
|
lines.push('');
|
|
384
|
-
lines.push(
|
|
416
|
+
lines.push(
|
|
417
|
+
'*Generated by kdna compare. Copy-pasteable as a GitHub comment, Slack message, or tweet.*',
|
|
418
|
+
);
|
|
385
419
|
|
|
386
420
|
return lines.join('\n');
|
|
387
421
|
}
|
|
388
422
|
|
|
389
|
-
function emitJsonReport(
|
|
423
|
+
function emitJsonReport(
|
|
424
|
+
parsed,
|
|
425
|
+
manifest,
|
|
426
|
+
core,
|
|
427
|
+
pat,
|
|
428
|
+
responseA,
|
|
429
|
+
responseB,
|
|
430
|
+
diffText,
|
|
431
|
+
llm,
|
|
432
|
+
userInput,
|
|
433
|
+
) {
|
|
390
434
|
const { axes, verdict } = parseDiffText(diffText);
|
|
391
435
|
const domainScore = scoreDiff(axes);
|
|
392
436
|
const axioms = core.axioms || [];
|
|
@@ -442,7 +486,10 @@ async function cmdCompare(input, args = []) {
|
|
|
442
486
|
const outputFile = args.includes('--output') ? args[args.indexOf('--output') + 1] : null;
|
|
443
487
|
const idxInput = args.indexOf('--input');
|
|
444
488
|
if (idxInput < 0 || !args[idxInput + 1]) {
|
|
445
|
-
error(
|
|
489
|
+
error(
|
|
490
|
+
'Usage: kdna compare <name> --input "<text>" [--report-md|--report-json] [--output <file>]',
|
|
491
|
+
EXIT.INPUT_ERROR,
|
|
492
|
+
);
|
|
446
493
|
}
|
|
447
494
|
const userInput = args[idxInput + 1];
|
|
448
495
|
|
|
@@ -477,11 +524,13 @@ async function cmdCompare(input, args = []) {
|
|
|
477
524
|
|
|
478
525
|
if (!jsonMode && !reportMd && !reportJson) console.log('[1/3] Running baseline (no KDNA)...');
|
|
479
526
|
const responseA = await callLlm(llm, BASELINE_SYSTEM, userInput);
|
|
480
|
-
if (!jsonMode && !reportMd && !reportJson)
|
|
527
|
+
if (!jsonMode && !reportMd && !reportJson)
|
|
528
|
+
console.log(` ${responseA.length} chars returned`);
|
|
481
529
|
|
|
482
530
|
if (!jsonMode && !reportMd && !reportJson) console.log('[2/3] Running with KDNA loaded...');
|
|
483
531
|
const responseB = await callLlm(llm, TREATMENT_SYSTEM, userInput);
|
|
484
|
-
if (!jsonMode && !reportMd && !reportJson)
|
|
532
|
+
if (!jsonMode && !reportMd && !reportJson)
|
|
533
|
+
console.log(` ${responseB.length} chars returned`);
|
|
485
534
|
|
|
486
535
|
if (!jsonMode && !reportMd && !reportJson) console.log('[3/3] Diffing reasoning trajectories...');
|
|
487
536
|
const diffPrompt = makeDiffPrompt(userInput, responseA, responseB);
|
|
@@ -508,7 +557,17 @@ async function cmdCompare(input, args = []) {
|
|
|
508
557
|
}
|
|
509
558
|
|
|
510
559
|
if (reportJson) {
|
|
511
|
-
const report = emitJsonReport(
|
|
560
|
+
const report = emitJsonReport(
|
|
561
|
+
parsed,
|
|
562
|
+
manifest,
|
|
563
|
+
core,
|
|
564
|
+
pat,
|
|
565
|
+
responseA,
|
|
566
|
+
responseB,
|
|
567
|
+
diff,
|
|
568
|
+
llm,
|
|
569
|
+
userInput,
|
|
570
|
+
);
|
|
512
571
|
if (outputFile) {
|
|
513
572
|
fs.writeFileSync(outputFile, JSON.stringify(report, null, 2) + '\n');
|
|
514
573
|
console.log(`Report saved to ${outputFile}`);
|
package/src/install.js
CHANGED
|
@@ -20,7 +20,13 @@ const crypto = require('crypto');
|
|
|
20
20
|
const { execSync, execFileSync } = require('child_process');
|
|
21
21
|
const { RegistryResolver, parseName } = require('./registry');
|
|
22
22
|
const { EXIT, error } = require('./cmds/_common');
|
|
23
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
decrypt,
|
|
25
|
+
deriveKey,
|
|
26
|
+
machineFingerprint,
|
|
27
|
+
isEncryptedContainer,
|
|
28
|
+
ENCRYPTED_FILES,
|
|
29
|
+
} = require('./cmds/encrypt');
|
|
24
30
|
|
|
25
31
|
const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
|
|
26
32
|
const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
|
|
@@ -306,11 +312,16 @@ function extractAndDecrypt(kdnaPath, destDir, licenseKey) {
|
|
|
306
312
|
|
|
307
313
|
function findLicense(domainName) {
|
|
308
314
|
const licenseDir = path.join(USER_KDNA_DIR, 'licenses');
|
|
309
|
-
const licensePath = path.join(
|
|
315
|
+
const licensePath = path.join(
|
|
316
|
+
licenseDir,
|
|
317
|
+
`${domainName.replace(/^@/, '').replace('/', '-')}.json`,
|
|
318
|
+
);
|
|
310
319
|
if (fs.existsSync(licensePath)) {
|
|
311
320
|
try {
|
|
312
321
|
return JSON.parse(fs.readFileSync(licensePath, 'utf8'));
|
|
313
|
-
} catch {
|
|
322
|
+
} catch {
|
|
323
|
+
/* invalid license */
|
|
324
|
+
}
|
|
314
325
|
}
|
|
315
326
|
return null;
|
|
316
327
|
}
|
|
@@ -325,7 +336,11 @@ function findLicenseForDomain(domainFull) {
|
|
|
325
336
|
for (const c of candidates) {
|
|
326
337
|
const p = path.join(licenseDir, `${c}.json`);
|
|
327
338
|
if (fs.existsSync(p)) {
|
|
328
|
-
try {
|
|
339
|
+
try {
|
|
340
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
341
|
+
} catch {
|
|
342
|
+
/* skip */
|
|
343
|
+
}
|
|
329
344
|
}
|
|
330
345
|
}
|
|
331
346
|
return null;
|
|
@@ -362,7 +377,10 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
|
|
|
362
377
|
|
|
363
378
|
// Author pubkey fingerprint must match scope trust_pubkey
|
|
364
379
|
if (manifest.author?.pubkey !== trustKey) {
|
|
365
|
-
error(
|
|
380
|
+
error(
|
|
381
|
+
`${entry.name}: author.pubkey does not match scope trust key. Refusing to install.`,
|
|
382
|
+
EXIT.TRUST_FAILED,
|
|
383
|
+
);
|
|
366
384
|
}
|
|
367
385
|
|
|
368
386
|
// Full Ed25519 verify (requires public_key_pem embedded in the package)
|
|
@@ -414,7 +432,10 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
|
|
|
414
432
|
const publicKey = crypto.createPublicKey(pem);
|
|
415
433
|
const ok = crypto.verify(null, Buffer.from(payload), publicKey, Buffer.from(sigHex, 'hex'));
|
|
416
434
|
if (!ok) {
|
|
417
|
-
error(
|
|
435
|
+
error(
|
|
436
|
+
`${entry.name}: Ed25519 signature INVALID. Package may be tampered. Refusing.`,
|
|
437
|
+
EXIT.TRUST_FAILED,
|
|
438
|
+
);
|
|
418
439
|
}
|
|
419
440
|
console.log(' ✓ Signature OK (Ed25519 verified)');
|
|
420
441
|
} catch (e) {
|
|
@@ -510,7 +531,10 @@ function installFromRegistry(parsed, yes, jsonMode = false) {
|
|
|
510
531
|
}
|
|
511
532
|
}
|
|
512
533
|
if (entry.access && entry.access !== 'open') {
|
|
513
|
-
error(
|
|
534
|
+
error(
|
|
535
|
+
`${entry.name} requires "${entry.access}" access. Not installable via CLI yet.`,
|
|
536
|
+
EXIT.POLICY_VIOLATION,
|
|
537
|
+
);
|
|
514
538
|
}
|
|
515
539
|
|
|
516
540
|
if (entry.type === 'cluster') {
|
|
@@ -587,13 +611,15 @@ function installSingleFromUrl({ entry, scope }, jsonMode = false) {
|
|
|
587
611
|
fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
588
612
|
|
|
589
613
|
if (jsonMode) {
|
|
590
|
-
console.log(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
614
|
+
console.log(
|
|
615
|
+
JSON.stringify({
|
|
616
|
+
name: entry.name,
|
|
617
|
+
version: entry.version,
|
|
618
|
+
installed: true,
|
|
619
|
+
path: dest,
|
|
620
|
+
type: entry.type || 'domain',
|
|
621
|
+
}),
|
|
622
|
+
);
|
|
597
623
|
} else {
|
|
598
624
|
console.log(`✓ Installed ${entry.name}@${entry.version}`);
|
|
599
625
|
console.log(` Location: ${dest}`);
|
|
@@ -643,14 +669,16 @@ function installCluster(clusterEntry, resolver, _yes, jsonMode = false) {
|
|
|
643
669
|
);
|
|
644
670
|
|
|
645
671
|
if (jsonMode) {
|
|
646
|
-
console.log(
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
672
|
+
console.log(
|
|
673
|
+
JSON.stringify({
|
|
674
|
+
name: clusterEntry.name,
|
|
675
|
+
version: clusterEntry.version,
|
|
676
|
+
type: 'cluster',
|
|
677
|
+
installed: true,
|
|
678
|
+
path: clusterDest,
|
|
679
|
+
subdomains: subdomains.length,
|
|
680
|
+
}),
|
|
681
|
+
);
|
|
654
682
|
} else {
|
|
655
683
|
console.log('');
|
|
656
684
|
console.log(`✓ Cluster ${clusterEntry.name} installed`);
|
|
@@ -677,7 +705,9 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
|
|
|
677
705
|
try {
|
|
678
706
|
const lic = JSON.parse(fs.readFileSync(licenseFromArgs, 'utf8'));
|
|
679
707
|
licenseKey = lic.license_id;
|
|
680
|
-
} catch {
|
|
708
|
+
} catch {
|
|
709
|
+
/* invalid license file */
|
|
710
|
+
}
|
|
681
711
|
}
|
|
682
712
|
|
|
683
713
|
if (!licenseKey) {
|
|
@@ -734,14 +764,16 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
|
|
|
734
764
|
fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
|
|
735
765
|
|
|
736
766
|
if (jsonMode) {
|
|
737
|
-
console.log(
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
767
|
+
console.log(
|
|
768
|
+
JSON.stringify({
|
|
769
|
+
name: declared,
|
|
770
|
+
installed: true,
|
|
771
|
+
path: dest,
|
|
772
|
+
source: 'local-file',
|
|
773
|
+
source_path: abs,
|
|
774
|
+
encrypted: isEncrypted,
|
|
775
|
+
}),
|
|
776
|
+
);
|
|
745
777
|
} else {
|
|
746
778
|
console.log(`✓ Installed ${declared} from ${isEncrypted ? 'encrypted' : 'local'} file`);
|
|
747
779
|
console.log(` Location: ${dest}`);
|
|
@@ -770,13 +802,15 @@ function installFromLocalDir(dirPath, _yes, jsonMode = false) {
|
|
|
770
802
|
fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
|
|
771
803
|
|
|
772
804
|
if (jsonMode) {
|
|
773
|
-
console.log(
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
805
|
+
console.log(
|
|
806
|
+
JSON.stringify({
|
|
807
|
+
name: declared,
|
|
808
|
+
installed: true,
|
|
809
|
+
path: dest,
|
|
810
|
+
source: 'local-dir',
|
|
811
|
+
source_path: abs,
|
|
812
|
+
}),
|
|
813
|
+
);
|
|
780
814
|
} else {
|
|
781
815
|
console.log(`✓ Installed ${declared} from local directory (dev mode)`);
|
|
782
816
|
console.log(` Location: ${dest}`);
|