@aikdna/kdna-cli 0.17.0 → 0.18.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 +42 -23
- package/package.json +5 -4
- package/skills/kdna-loader/SKILL.md +5 -6
- package/src/agent.js +145 -109
- package/src/cli.js +104 -60
- package/src/cmds/_common.js +32 -16
- package/src/cmds/badge.js +7 -7
- package/src/cmds/changelog.js +1 -1
- package/src/cmds/cluster.js +16 -48
- package/src/cmds/doctor.js +10 -27
- package/src/cmds/domain.js +77 -400
- package/src/cmds/explain.js +122 -0
- package/src/cmds/legacy.js +8 -8
- package/src/cmds/license.js +483 -26
- package/src/cmds/quality.js +2 -2
- package/src/cmds/registry.js +15 -67
- package/src/cmds/studio.js +4 -5
- package/src/cmds/test.js +4 -4
- package/src/cmds/trace.js +11 -7
- package/src/compare.js +28 -22
- package/src/diff.js +11 -13
- package/src/init.js +2 -2
- package/src/install.js +138 -460
- package/src/loader.js +10 -10
- package/src/package-store.js +229 -0
- package/src/paths.js +44 -0
- package/src/publish.js +73 -22
- package/src/registry.js +76 -9
- package/src/setup.js +19 -20
- package/src/verify.js +233 -124
- package/templates/standard-domain/kdna.json +2 -1
- package/src/cmds/encrypt.js +0 -199
package/src/cmds/encrypt.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KDNA Encryption — Encrypted container format (.kdnae) for licensed domains.
|
|
3
|
-
*
|
|
4
|
-
* .kdnae extends .kdna with AES-256-GCM encryption on KDNA JSON files.
|
|
5
|
-
* The kdna.json manifest and license.json stay in plaintext for discovery.
|
|
6
|
-
*
|
|
7
|
-
* Encryption key is derived via PBKDF2 from:
|
|
8
|
-
* license_key + machine_fingerprint
|
|
9
|
-
*
|
|
10
|
-
* This module provides pure-crypto functions (no filesystem I/O).
|
|
11
|
-
* File operations are in domain.js (pack/unpack) and install.js.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const crypto = require('crypto');
|
|
15
|
-
|
|
16
|
-
const ALGORITHM = 'aes-256-gcm';
|
|
17
|
-
const IV_LENGTH = 16; // 96-bit IV for GCM
|
|
18
|
-
const TAG_LENGTH = 16; // 128-bit auth tag
|
|
19
|
-
const SALT_LENGTH = 32;
|
|
20
|
-
const PBKDF2_ITERATIONS = 600000;
|
|
21
|
-
const KEY_LENGTH = 32; // AES-256
|
|
22
|
-
|
|
23
|
-
// Files in a .kdna container that get encrypted (JSON content only)
|
|
24
|
-
const ENCRYPTED_FILES = [
|
|
25
|
-
'KDNA_Core.json',
|
|
26
|
-
'KDNA_Patterns.json',
|
|
27
|
-
'KDNA_Scenarios.json',
|
|
28
|
-
'KDNA_Cases.json',
|
|
29
|
-
'KDNA_Reasoning.json',
|
|
30
|
-
'KDNA_Evolution.json',
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// Files that always stay in plaintext
|
|
34
|
-
const PLAINTEXT_FILES = ['kdna.json', 'license.json', 'README.md', 'LICENSE', 'signature.json'];
|
|
35
|
-
|
|
36
|
-
function isEncryptable(filename) {
|
|
37
|
-
return ENCRYPTED_FILES.includes(filename);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── Machine Fingerprint ────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
function machineFingerprint() {
|
|
43
|
-
const os = require('os');
|
|
44
|
-
const parts = [os.hostname(), os.userInfo().uid.toString(), os.platform(), os.arch()];
|
|
45
|
-
// Try to get hardware UUID on macOS
|
|
46
|
-
try {
|
|
47
|
-
const { execSync } = require('child_process');
|
|
48
|
-
if (os.platform() === 'darwin') {
|
|
49
|
-
const uuid = execSync('ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID', {
|
|
50
|
-
encoding: 'utf8',
|
|
51
|
-
timeout: 3000,
|
|
52
|
-
})
|
|
53
|
-
.match(/"[A-F0-9-]{36}"/)?.[0]
|
|
54
|
-
?.replace(/"/g, '');
|
|
55
|
-
if (uuid) parts.push(uuid);
|
|
56
|
-
}
|
|
57
|
-
if (os.platform() === 'linux') {
|
|
58
|
-
try {
|
|
59
|
-
const mid = require('fs').readFileSync('/etc/machine-id', 'utf8').trim();
|
|
60
|
-
if (mid) parts.push(mid);
|
|
61
|
-
} catch {
|
|
62
|
-
/* ignore */
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
/* non-critical */
|
|
67
|
-
}
|
|
68
|
-
return crypto.createHash('sha256').update(parts.join('|')).digest('hex');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ─── Key Derivation ─────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
function deriveKey(licenseKey, fingerprint) {
|
|
74
|
-
const salt = crypto
|
|
75
|
-
.createHash('sha256')
|
|
76
|
-
.update(fingerprint || machineFingerprint())
|
|
77
|
-
.digest();
|
|
78
|
-
return crypto.pbkdf2Sync(licenseKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─── Encrypt / Decrypt ──────────────────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
function encrypt(plaintext, key) {
|
|
84
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
85
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
86
|
-
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
87
|
-
const tag = cipher.getAuthTag();
|
|
88
|
-
// Format: iv (16) + tag (16) + ciphertext
|
|
89
|
-
return Buffer.concat([iv, tag, encrypted]);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function decrypt(encryptedData, key) {
|
|
93
|
-
if (encryptedData.length < IV_LENGTH + TAG_LENGTH) {
|
|
94
|
-
throw new Error('Invalid encrypted data: too short');
|
|
95
|
-
}
|
|
96
|
-
const iv = encryptedData.subarray(0, IV_LENGTH);
|
|
97
|
-
const tag = encryptedData.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
98
|
-
const ciphertext = encryptedData.subarray(IV_LENGTH + TAG_LENGTH);
|
|
99
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
100
|
-
decipher.setAuthTag(tag);
|
|
101
|
-
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Encrypted Container Detection ──────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
function isEncryptedContainer(filePath) {
|
|
107
|
-
return filePath.endsWith('.kdnae');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ─── License File ──────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
function createLicense(domain, options = {}) {
|
|
113
|
-
const {
|
|
114
|
-
issuedTo = 'licensee@example.com',
|
|
115
|
-
expiresAt = null, // ISO date or null for perpetual
|
|
116
|
-
maxAgents = 1,
|
|
117
|
-
requireMachineBinding = true,
|
|
118
|
-
requireOnlineCheck = false,
|
|
119
|
-
offlineGraceDays = 7,
|
|
120
|
-
} = options;
|
|
121
|
-
|
|
122
|
-
const license = {
|
|
123
|
-
version: '1.0',
|
|
124
|
-
license_id: `lic_${crypto.randomBytes(8).toString('hex')}`,
|
|
125
|
-
domain,
|
|
126
|
-
issued_to: issuedTo,
|
|
127
|
-
issued_at: new Date().toISOString(),
|
|
128
|
-
expires_at: expiresAt,
|
|
129
|
-
max_agents: maxAgents,
|
|
130
|
-
require_machine_binding: requireMachineBinding,
|
|
131
|
-
require_online_check: requireOnlineCheck,
|
|
132
|
-
offline_grace_days: offlineGraceDays,
|
|
133
|
-
allowed_agents: options.allowedAgents || ['claude_code', 'codex', 'opencode'],
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
return license;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function verifyLicense(license, scopePubkey, fingerprint) {
|
|
140
|
-
const issues = [];
|
|
141
|
-
|
|
142
|
-
// Check expiration
|
|
143
|
-
if (license.expires_at) {
|
|
144
|
-
if (new Date(license.expires_at) < new Date()) {
|
|
145
|
-
issues.push('License has expired');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check machine binding
|
|
150
|
-
if (license.require_machine_binding) {
|
|
151
|
-
if (license.machine_fingerprint && license.machine_fingerprint !== fingerprint) {
|
|
152
|
-
issues.push('Machine fingerprint mismatch');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
valid: issues.length === 0,
|
|
158
|
-
issues,
|
|
159
|
-
domain: license.domain,
|
|
160
|
-
license_id: license.license_id,
|
|
161
|
-
issued_to: license.issued_to,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function signLicense(license, privateKeyPem) {
|
|
166
|
-
const { signature: _, ...payload } = license;
|
|
167
|
-
const data = JSON.stringify(payload, Object.keys(payload).sort());
|
|
168
|
-
// Use crypto.sign() for Ed25519 (supported via PEM keys)
|
|
169
|
-
const sig = crypto.sign(null, Buffer.from(data), privateKeyPem);
|
|
170
|
-
license.signature = `ed25519:${sig.toString('hex')}`;
|
|
171
|
-
return license;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function verifyLicenseSignature(license, publicKeyPem) {
|
|
175
|
-
const signature = license.signature?.replace('ed25519:', '') || '';
|
|
176
|
-
if (!signature) return false;
|
|
177
|
-
const { signature: _, ...payload } = license;
|
|
178
|
-
const data = JSON.stringify(payload, Object.keys(payload).sort());
|
|
179
|
-
try {
|
|
180
|
-
return crypto.verify(null, Buffer.from(data), publicKeyPem, Buffer.from(signature, 'hex'));
|
|
181
|
-
} catch {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
module.exports = {
|
|
187
|
-
encrypt,
|
|
188
|
-
decrypt,
|
|
189
|
-
deriveKey,
|
|
190
|
-
machineFingerprint,
|
|
191
|
-
isEncryptable,
|
|
192
|
-
isEncryptedContainer,
|
|
193
|
-
createLicense,
|
|
194
|
-
verifyLicense,
|
|
195
|
-
signLicense,
|
|
196
|
-
verifyLicenseSignature,
|
|
197
|
-
ENCRYPTED_FILES,
|
|
198
|
-
PLAINTEXT_FILES,
|
|
199
|
-
};
|