@aikdna/kdna-cli 0.14.0 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "KDNA CLI — create, validate, install, and manage domain cognition packages for AI agents.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -24,7 +24,7 @@ const { cmdIdentity } = require('./cmds/identity');
24
24
  const { cmdSetup } = require('./cmds/setup');
25
25
  const { cmdDoctor } = require('./cmds/doctor');
26
26
  const { cmdTrace, cmdHistory } = require('./cmds/trace');
27
- const { cmdLicenseGenerate, cmdLicenseVerify, cmdLicenseBind, cmdLicenseShow } = require('./cmds/license');
27
+ const { cmdLicenseGenerate, cmdLicenseVerify, cmdLicenseBind, cmdLicenseShow, cmdLicenseInstall } = require('./cmds/license');
28
28
  const { cmdPreview, cmdProject, cmdEval, cmdExport, cmdDemo } = require('./cmds/legacy');
29
29
  const { cmdStudioScaffold, cmdCardsValidate, cmdLockVerify, cmdStudioCompile, cmdStudioReadiness } = require('./cmds/studio');
30
30
  const { cmdTestRun, cmdTestImport } = require('./cmds/test');
@@ -138,6 +138,7 @@ Trace & Diagnostics:
138
138
 
139
139
  License & Authorization:
140
140
  license generate <domain> --to <email> Generate signed license
141
+ license install <license.json> Register license for auto-decrypt
141
142
  license verify <license.json> Verify license signature
142
143
  license bind <license.json> Bind license to this machine
143
144
  license show <license.json> Display license details
@@ -436,10 +437,13 @@ switch (cmd) {
436
437
  cmdLicenseBind(rest);
437
438
  } else if (sub === 'show') {
438
439
  cmdLicenseShow(rest);
440
+ } else if (sub === 'install') {
441
+ cmdLicenseInstall(rest);
439
442
  } else {
440
443
  error(
441
444
  'Usage:\n' +
442
445
  ' kdna license generate <domain> --to <email> [--expires <date>]\n' +
446
+ ' kdna license install <license.json>\n' +
443
447
  ' kdna license verify <license.json>\n' +
444
448
  ' kdna license bind <license.json>\n' +
445
449
  ' kdna license show <license.json>',
@@ -167,7 +167,6 @@ function cmdLicenseBind(args) {
167
167
  function cmdLicenseShow(args) {
168
168
  const licensePath = args[0];
169
169
  if (!licensePath) {
170
- // Try to find license in current directory
171
170
  const local = path.join(process.cwd(), 'license.json');
172
171
  if (fs.existsSync(local)) return cmdLicenseVerify([local, ...args.slice(1)]);
173
172
  error('Usage: kdna license show <license.json>', EXIT.INPUT_ERROR);
@@ -175,4 +174,32 @@ function cmdLicenseShow(args) {
175
174
  cmdLicenseVerify(args);
176
175
  }
177
176
 
178
- module.exports = { cmdLicenseGenerate, cmdLicenseVerify, cmdLicenseBind, cmdLicenseShow };
177
+ function cmdLicenseInstall(args) {
178
+ const licensePath = args[0];
179
+ if (!licensePath) error('Usage: kdna license install <license.json>', EXIT.INPUT_ERROR);
180
+
181
+ let license;
182
+ try {
183
+ license = JSON.parse(fs.readFileSync(licensePath, 'utf8'));
184
+ } catch {
185
+ error(`Cannot read license file: ${licensePath}`, EXIT.INPUT_ERROR);
186
+ }
187
+
188
+ if (!license.domain) error('License missing domain field', EXIT.INPUT_ERROR);
189
+
190
+ const licenseDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna', 'licenses');
191
+ fs.mkdirSync(licenseDir, { recursive: true });
192
+
193
+ const safeName = license.domain.replace(/^@/, '').replace('/', '-');
194
+ const dest = path.join(licenseDir, `${safeName}.json`);
195
+
196
+ fs.writeFileSync(dest, JSON.stringify(license, null, 2) + '\n');
197
+
198
+ console.log(`License installed for ${license.domain}`);
199
+ console.log(` License ID: ${license.license_id || 'unknown'}`);
200
+ console.log(` Saved to: ${dest}`);
201
+ console.log('');
202
+ console.log(`Now install the domain: kdna install ${license.domain}`);
203
+ }
204
+
205
+ module.exports = { cmdLicenseGenerate, cmdLicenseVerify, cmdLicenseBind, cmdLicenseShow, cmdLicenseInstall };
package/src/install.js CHANGED
@@ -20,6 +20,7 @@ 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 { decrypt, deriveKey, machineFingerprint, isEncryptedContainer, ENCRYPTED_FILES } = require('./cmds/encrypt');
23
24
 
24
25
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
25
26
  const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
@@ -199,9 +200,9 @@ function warnLegacy() {
199
200
  // ─── Source parsing ─────────────────────────────────────────────────────
200
201
 
201
202
  function parseSource(input) {
202
- // Local file
203
+ // Local file (.kdna or .kdnae)
203
204
  if (
204
- input.endsWith('.kdna') &&
205
+ (input.endsWith('.kdna') || input.endsWith('.kdnae')) &&
205
206
  (input.startsWith('./') || input.startsWith('/') || input.startsWith('~/'))
206
207
  ) {
207
208
  const resolved = path.resolve(input.replace(/^~/, process.env.HOME || ''));
@@ -283,6 +284,53 @@ print('ok')
283
284
  }
284
285
  }
285
286
 
287
+ function extractAndDecrypt(kdnaPath, destDir, licenseKey) {
288
+ extractKdna(kdnaPath, destDir);
289
+ const fp = machineFingerprint();
290
+ const key = deriveKey(licenseKey, fp);
291
+
292
+ for (const f of fs.readdirSync(destDir)) {
293
+ if (ENCRYPTED_FILES.includes(f)) {
294
+ try {
295
+ const fullPath = path.join(destDir, f);
296
+ const encrypted = fs.readFileSync(fullPath);
297
+ const decrypted = decrypt(encrypted, key);
298
+ fs.writeFileSync(fullPath, decrypted);
299
+ } catch (err) {
300
+ fs.rmSync(destDir, { recursive: true, force: true });
301
+ error(`Failed to decrypt ${f}: ${err.message}. Wrong license key?`);
302
+ }
303
+ }
304
+ }
305
+ }
306
+
307
+ function findLicense(domainName) {
308
+ const licenseDir = path.join(USER_KDNA_DIR, 'licenses');
309
+ const licensePath = path.join(licenseDir, `${domainName.replace(/^@/, '').replace('/', '-')}.json`);
310
+ if (fs.existsSync(licensePath)) {
311
+ try {
312
+ return JSON.parse(fs.readFileSync(licensePath, 'utf8'));
313
+ } catch { /* invalid license */ }
314
+ }
315
+ return null;
316
+ }
317
+
318
+ function findLicenseForDomain(domainFull) {
319
+ const licenseDir = path.join(USER_KDNA_DIR, 'licenses');
320
+ if (!fs.existsSync(licenseDir)) return null;
321
+ // Try exact match: @aikdna/writing → @aikdna-writing.json
322
+ const candidates = [domainFull.replace(/^@/, '').replace('/', '-')];
323
+ // Also try just the domain name
324
+ candidates.push(domainFull.split('/').pop());
325
+ for (const c of candidates) {
326
+ const p = path.join(licenseDir, `${c}.json`);
327
+ if (fs.existsSync(p)) {
328
+ try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { /* skip */ }
329
+ }
330
+ }
331
+ return null;
332
+ }
333
+
286
334
  // ─── Signature verification ────────────────────────────────────────────
287
335
 
288
336
  function verifySignature({ destDir, scope, entry, lenient = true }) {
@@ -613,9 +661,53 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
613
661
  const abs = path.resolve(filePath);
614
662
  if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) error(`Not a file: ${abs}`);
615
663
 
664
+ const isEncrypted = isEncryptedContainer(abs);
616
665
  const tmpDir = path.join(INSTALL_DIR, '.local-tmp-' + Date.now());
617
666
  ensureDir(tmpDir);
618
- extractKdna(abs, tmpDir);
667
+
668
+ if (isEncrypted) {
669
+ // Find license for this .kdnae file
670
+ // First check the license directory, then ask for --license flag from args
671
+ const licenseFromArgs = process.argv.includes('--license')
672
+ ? process.argv[process.argv.indexOf('--license') + 1]
673
+ : null;
674
+ let licenseKey = null;
675
+
676
+ if (licenseFromArgs && fs.existsSync(licenseFromArgs)) {
677
+ try {
678
+ const lic = JSON.parse(fs.readFileSync(licenseFromArgs, 'utf8'));
679
+ licenseKey = lic.license_id;
680
+ } catch { /* invalid license file */ }
681
+ }
682
+
683
+ if (!licenseKey) {
684
+ // Try to auto-discover from ~/.kdna/licenses/
685
+ const manifest = readJson(path.join(tmpDir, 'kdna.json'));
686
+ // We need to extract just the manifest first to get the domain name
687
+ extractKdna(abs, tmpDir);
688
+ const mf = readJson(path.join(tmpDir, 'kdna.json'));
689
+ if (mf?.name) {
690
+ const lic = findLicenseForDomain(mf.name);
691
+ if (lic) licenseKey = lic.license_id;
692
+ }
693
+ if (!licenseKey) {
694
+ fs.rmSync(tmpDir, { recursive: true, force: true });
695
+ error(
696
+ `Cannot install encrypted .kdnae without a license.\n` +
697
+ `Save the license to ~/.kdna/licenses/ or use --license <file>.`,
698
+ EXIT.TRUST_FAILED,
699
+ );
700
+ }
701
+ // Re-extract for decryption
702
+ fs.rmSync(tmpDir, { recursive: true, force: true });
703
+ ensureDir(tmpDir);
704
+ }
705
+
706
+ console.log(' Decrypting .kdnae container...');
707
+ extractAndDecrypt(abs, tmpDir, licenseKey);
708
+ } else {
709
+ extractKdna(abs, tmpDir);
710
+ }
619
711
 
620
712
  const manifest = readJson(path.join(tmpDir, 'kdna.json'));
621
713
  const declared = manifest?.name;
@@ -636,6 +728,7 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
636
728
  destManifest._source = {
637
729
  type: 'local-file',
638
730
  path: abs,
731
+ encrypted: isEncrypted,
639
732
  installed_at: new Date().toISOString(),
640
733
  };
641
734
  fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
@@ -647,9 +740,10 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
647
740
  path: dest,
648
741
  source: 'local-file',
649
742
  source_path: abs,
743
+ encrypted: isEncrypted,
650
744
  }));
651
745
  } else {
652
- console.log(`✓ Installed ${declared} from local file`);
746
+ console.log(`✓ Installed ${declared} from ${isEncrypted ? 'encrypted' : 'local'} file`);
653
747
  console.log(` Location: ${dest}`);
654
748
  }
655
749
  }