@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.
@@ -2,15 +2,6 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { execSync } = require('child_process');
4
4
  const { error, readJson, writeJson, EXIT } = require('./_common');
5
- const {
6
- encrypt,
7
- decrypt,
8
- deriveKey,
9
- machineFingerprint,
10
- isEncryptable,
11
- ENCRYPTED_FILES,
12
- } = require('./encrypt');
13
-
14
5
  const KDNA_DOMAIN_FILES = new Set([
15
6
  'KDNA_Core.json',
16
7
  'KDNA_Patterns.json',
@@ -469,7 +460,7 @@ zf.close()
469
460
  files.forEach((f) => console.log(` ${f}`));
470
461
  }
471
462
 
472
- // ─── Inspect .kdna file (ZIP container or legacy merged JSON) ────────────
463
+ // ─── Inspect .kdna file (ZIP container) ──────────────────────────────────
473
464
 
474
465
  function inspectKdnaFile(filePath, jsonMode = false) {
475
466
  const abs = path.resolve(filePath);
@@ -481,106 +472,47 @@ function inspectKdnaFile(filePath, jsonMode = false) {
481
472
  fs.readSync(fd, head, 0, 4, 0);
482
473
  fs.closeSync(fd);
483
474
  const isZip = head[0] === 0x50 && head[1] === 0x4b;
475
+ if (!isZip) error('Invalid .kdna asset: expected ZIP container');
484
476
 
485
- let core, patterns, manifest;
486
- const presentFiles = [];
487
-
488
- if (isZip) {
489
- // ZIP container — extract to temp, read files
490
- const os = require('os');
491
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-inspect-'));
492
- try {
493
- const tmpInspectPy = path.join(
494
- fs.existsSync('/tmp') ? '/tmp' : require('os').tmpdir(),
495
- `kdna-inspect-${Date.now()}.py`,
496
- );
497
- try {
498
- const script = `import zipfile, os
499
- zf = zipfile.ZipFile(${JSON.stringify(abs)}, 'r')
500
- zf.extractall(${JSON.stringify(tmpDir)})
501
- zf.close()
502
- `;
503
- fs.writeFileSync(tmpInspectPy, script);
504
- execSync(`python3 ${tmpInspectPy}`, { stdio: 'pipe' });
505
- } finally {
506
- try {
507
- fs.unlinkSync(tmpInspectPy);
508
- } catch {
509
- /* cleanup */
510
- }
511
- }
512
- } catch {
513
- try {
514
- execSync(`unzip -q -o "${abs}" -d "${tmpDir}"`, { stdio: 'pipe' });
515
- } catch {
516
- fs.rmSync(tmpDir, { recursive: true, force: true });
517
- error('Cannot read .kdna container. Install python3 or unzip.');
518
- }
519
- }
520
-
521
- core = readJson(path.join(tmpDir, 'KDNA_Core.json'));
522
- patterns = readJson(path.join(tmpDir, 'KDNA_Patterns.json'));
523
- manifest = readJson(path.join(tmpDir, 'kdna.json'));
524
-
525
- for (const f of fs.readdirSync(tmpDir)) {
526
- if (f.startsWith('KDNA_') && f.endsWith('.json')) {
527
- presentFiles.push(f);
528
- }
529
- if (f === 'README.md' || f === 'LICENSE') presentFiles.push(f);
530
- }
531
-
532
- fs.rmSync(tmpDir, { recursive: true, force: true });
533
- } else {
534
- // Legacy merged JSON/YAML format (deprecated)
535
- const raw = fs.readFileSync(abs, 'utf8');
536
- let data;
537
- try {
538
- data = JSON.parse(raw);
539
- } catch {
540
- data = parseSimpleYaml(raw);
541
- }
542
-
543
- if (!data || !data.meta) error(`Invalid .kdna file: missing meta section`);
544
-
545
- const m = data.meta || {};
546
- manifest = {
547
- name: m.name || m.domain,
548
- version: m.version || '?',
549
- status: data.status || '?',
550
- access: data.access || '?',
551
- language: data.language || '?',
552
- author: data.author || { name: '?' },
553
- license: data.license || { type: '?' },
554
- description: data.description || m.purpose || '?',
555
- spec_version: m.spec_version || data.kdna_spec || '?',
556
- };
557
- core = data.core || {};
558
- patterns = data.patterns || {};
559
- presentFiles.push('.kdna (legacy merged format)');
560
- if (data.scenarios) {
561
- presentFiles.push('scenarios (inline)');
562
- }
563
- if (data.cases) {
564
- presentFiles.push('cases (inline)');
565
- }
566
- if (data.reasoning) {
567
- presentFiles.push('reasoning (inline)');
568
- }
569
- if (data.evolution) {
570
- presentFiles.push('evolution (inline)');
571
- }
477
+ const { listContainerEntries, readContainerJson } = require('../package-store');
478
+ const { licenseDecryptOptionsForManifest } = require('./license');
479
+ const presentFiles = listContainerEntries(abs).filter(
480
+ (f) => (f.startsWith('KDNA_') && f.endsWith('.json')) || f === 'README.md' || f === 'LICENSE',
481
+ );
482
+ const manifest = readContainerJson(abs, 'kdna.json') || {};
483
+ const encryptedEntries = Array.isArray(manifest.encryption?.encrypted_entries)
484
+ ? manifest.encryption.encrypted_entries
485
+ : [];
486
+ let decryptOptions = {};
487
+ let decryptError = null;
488
+ if (encryptedEntries.length) {
489
+ const licensed = licenseDecryptOptionsForManifest(manifest);
490
+ if (licensed.ok) decryptOptions = { decryptEntry: licensed.decryptEntry };
491
+ else decryptError = licensed.error;
492
+ }
493
+
494
+ let core = null;
495
+ let patterns = null;
496
+ try {
497
+ core = decryptError ? null : readContainerJson(abs, 'KDNA_Core.json', decryptOptions);
498
+ patterns = decryptError ? null : readContainerJson(abs, 'KDNA_Patterns.json', decryptOptions);
499
+ } catch (e) {
500
+ if (!encryptedEntries.length) error(`Cannot inspect .kdna asset: ${e.message}`);
501
+ decryptError = e.message;
572
502
  }
573
503
 
574
- if (!core) error('KDNA_Core.json not found in container');
504
+ if (!core && !encryptedEntries.includes('KDNA_Core.json')) {
505
+ error('KDNA_Core.json not found in container');
506
+ }
575
507
 
576
508
  const m = manifest || {};
577
- const c = core;
509
+ const c = core || {};
578
510
  const p = patterns || {};
579
511
 
580
512
  if (jsonMode) {
581
513
  const result = {
582
514
  name: m.name || c.meta?.domain || path.basename(abs, '.kdna'),
583
- format: isZip ? 'kdna-zip' : 'legacy-merged',
515
+ format: 'kdna-zip',
584
516
  spec: m.spec_version || m.kdna_spec || null,
585
517
  version: m.version || null,
586
518
  status: m.status || 'experimental',
@@ -589,6 +521,9 @@ zf.close()
589
521
  license: m.license?.type || null,
590
522
  created: m.created || c.meta?.created || null,
591
523
  description: m.description || c.meta?.purpose || null,
524
+ protected: encryptedEntries.length > 0,
525
+ encrypted_entries: encryptedEntries,
526
+ license_required: !!decryptError,
592
527
  content: {
593
528
  axioms: (c.axioms || []).length,
594
529
  ontology: (c.ontology || []).length,
@@ -608,7 +543,7 @@ zf.close()
608
543
  console.log(` ${m.name || c.meta?.domain || path.basename(abs, '.kdna')} — KDNA Domain`);
609
544
  console.log('═'.repeat(50));
610
545
  console.log('');
611
- console.log(` Format: .kdna ${isZip ? '(ZIP container)' : '(legacy merged)'}`);
546
+ console.log(` Format: .kdna (ZIP container)`);
612
547
  console.log(` Spec: ${m.spec_version || m.kdna_spec || '0.4'}`);
613
548
  console.log(` Version: ${m.version || '?'}`);
614
549
  console.log(` Status: ${m.status || 'experimental'}`);
@@ -617,6 +552,10 @@ zf.close()
617
552
  console.log(` License: ${m.license?.type || '?'}`);
618
553
  console.log(` Created: ${m.created || c.meta?.created || '?'}`);
619
554
  console.log(` Description: ${m.description || c.meta?.purpose || '?'}`);
555
+ if (encryptedEntries.length) {
556
+ console.log(` Protected: ${encryptedEntries.join(', ')}`);
557
+ if (decryptError) console.log(` Activation: required (${decryptError})`);
558
+ }
620
559
  console.log('');
621
560
  console.log(' ── Content ──');
622
561
  console.log(` Axioms: ${(c.axioms || []).length}`);
@@ -633,57 +572,9 @@ zf.close()
633
572
  console.log('═'.repeat(50));
634
573
  }
635
574
 
636
- function parseSimpleYaml(raw) {
637
- // Parse a simple subset of YAML (no nesting beyond 1 level for sections)
638
- const result = {};
639
- let currentSection = null;
640
-
641
- const lines = raw.split('\n');
642
- for (const line of lines) {
643
- const trimmed = line.trim();
644
- if (!trimmed || trimmed.startsWith('#')) continue;
645
-
646
- // Section header: "core:" or " core:" etc
647
- if (/^[a-z_]+:$/.test(trimmed)) {
648
- currentSection = trimmed.slice(0, -1);
649
- if (!result[currentSection]) result[currentSection] = {};
650
- continue;
651
- }
652
-
653
- // Key: value
654
- const kv = trimmed.match(/^([a-z_]+):\s*(.*)/i);
655
- if (kv && !kv[1].startsWith('-')) {
656
- const key = kv[1];
657
- const val = kv[2].trim().replace(/^["']|["']$/g, '');
658
- if (currentSection) {
659
- if (key === 'version' && typeof result[currentSection] === 'object') {
660
- result[currentSection][key] = val;
661
- } else if (!result[currentSection][key]) {
662
- result[currentSection][key] = val;
663
- }
664
- } else {
665
- result[key] = val;
666
- }
667
- continue;
668
- }
669
-
670
- // Array item: "- value"
671
- if (trimmed.startsWith('- ') && currentSection) {
672
- // For counts only, we don't parse full arrays
673
- if (currentSection === 'axioms' || currentSection === 'stances') {
674
- if (!result.core) result.core = {};
675
- if (!result.core[currentSection]) result.core[currentSection] = [];
676
- result.core[currentSection].push({ _parsed: true });
677
- }
678
- }
679
- }
680
-
681
- return result;
682
- }
683
-
684
575
  // ─── Inspect ───────────────────────────────────────────────────────────
685
576
 
686
- function cmdInspect(dir, jsonMode = false, locale = null) {
577
+ function cmdInspect(dir, jsonMode = false, locale = null, options = {}) {
687
578
  const abs = path.resolve(dir);
688
579
  const stat = fs.existsSync(abs) ? fs.statSync(abs) : null;
689
580
  if (!stat) error(`Path not found: ${abs}`, EXIT.INPUT_ERROR);
@@ -694,7 +585,14 @@ function cmdInspect(dir, jsonMode = false, locale = null) {
694
585
  return;
695
586
  }
696
587
 
697
- // Directory existing logic
588
+ if (stat.isDirectory() && !options.allowDirectory) {
589
+ error(
590
+ 'Directory inspection is a dev-only operation. Use: kdna dev inspect <source-dir>',
591
+ EXIT.INPUT_ERROR,
592
+ );
593
+ }
594
+
595
+ // Dev source directory
698
596
  if (!stat.isDirectory()) error(`Not a KDNA domain: ${abs}`, EXIT.INPUT_ERROR);
699
597
 
700
598
  const core = readJson(path.join(abs, 'KDNA_Core.json'));
@@ -870,265 +768,46 @@ function cmdInspect(dir, jsonMode = false, locale = null) {
870
768
  console.log('═'.repeat(50));
871
769
  }
872
770
 
873
- // ─── Encrypted Container (.kdnae) ─────────────────────────────────────
771
+ // ─── KDNA Card (locale-aware) ────────────────────────────────────
874
772
 
875
- function cmdPackEncrypt(dir, args = []) {
876
- const abs = path.resolve(dir);
877
- if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
878
- error(`Not a directory: ${abs}`, EXIT.INPUT_ERROR);
879
- }
773
+ function readCardFromDirectory(abs, locale = null) {
774
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) return null;
775
+ let card = readJson(path.join(abs, 'KDNA_CARD.json'));
880
776
 
881
- const core = readJson(path.join(abs, 'KDNA_Core.json'));
882
- const pat = readJson(path.join(abs, 'KDNA_Patterns.json'));
883
- if (!core) error('KDNA_Core.json not found or invalid');
884
- if (!pat) error('KDNA_Patterns.json not found or invalid');
777
+ if (locale) {
778
+ const localeCard = readJson(path.join(abs, 'locales', locale, 'KDNA_CARD.json'));
779
+ if (localeCard) {
780
+ card = { ...card, ...localeCard };
781
+ }
782
+ }
885
783
 
886
- const domainName = core.meta?.domain || path.basename(abs);
784
+ return card;
785
+ }
887
786
 
888
- let manifest = readJson(path.join(abs, 'kdna.json'));
889
- if (!manifest) {
890
- const jsonCount = fs
891
- .readdirSync(abs)
892
- .filter((f) => f.endsWith('.json') && f !== 'kdna.json').length;
893
- manifest = {
894
- kdna_spec: '1.0-rc',
895
- name: domainName,
896
- version: core.meta?.version || '0.1.0',
897
- status: 'experimental',
898
- access: 'licensed',
899
- language: 'en',
900
- author: { name: '', id: '' },
901
- license: { type: 'KCL-1.0' },
902
- description: core.meta?.purpose || `${domainName} domain cognition`,
903
- file_count: jsonCount,
904
- created: new Date().toISOString().slice(0, 10),
905
- updated: new Date().toISOString().slice(0, 10),
906
- };
907
- writeJson(path.join(abs, 'kdna.json'), manifest);
908
- }
787
+ function cmdCard(dir, jsonMode = false, locale = null, options = {}) {
788
+ const abs = path.resolve(dir);
789
+ const stat = fs.existsSync(abs) ? fs.statSync(abs) : null;
790
+ if (!stat) error(`Path not found: ${abs}`, EXIT.INPUT_ERROR);
909
791
 
910
- // Get encryption key
911
- const licenseIdx = args.indexOf('--license');
912
- const keyIdx = args.indexOf('--key');
913
- let encKey;
914
-
915
- if (licenseIdx >= 0) {
916
- const licensePath = args[licenseIdx + 1];
917
- if (!licensePath || !fs.existsSync(licensePath))
918
- error('License file not found', EXIT.INPUT_ERROR);
919
- const license = JSON.parse(fs.readFileSync(licensePath, 'utf8'));
920
- const licenseKey = license.license_id;
921
- const fp = machineFingerprint();
922
- encKey = deriveKey(licenseKey, fp);
923
- } else if (keyIdx >= 0) {
924
- const rawKey = args[keyIdx + 1];
925
- if (!rawKey) error('--key requires a value', EXIT.INPUT_ERROR);
926
- encKey = deriveKey(rawKey, machineFingerprint());
927
- } else {
792
+ if (stat.isDirectory() && !options.allowDirectory) {
928
793
  error(
929
- 'Use --license <license.json> or --key <secret> to provide encryption key',
794
+ 'Directory card inspection is a dev-only operation. Use: kdna dev card <source-dir>',
930
795
  EXIT.INPUT_ERROR,
931
796
  );
932
797
  }
933
798
 
934
- const outputDir = args.includes('--output') ? args[args.indexOf('--output') + 1] : null;
935
- const outName = `${domainName}.kdnae`;
936
- const outPath = outputDir ? path.join(outputDir, outName) : path.join(process.cwd(), outName);
937
- if (outputDir && !fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
938
-
939
- // Build encrypted ZIP
940
- const zlib = require('zlib');
941
- const files = fs.readdirSync(abs).filter((f) => {
942
- if (f.startsWith('.')) return false;
943
- const ext = path.extname(f);
944
- return ext === '.json' || f === 'README.md' || f === 'LICENSE' || f === 'kdna.json';
945
- });
946
-
947
- const centralDir = [];
948
- const fileData = [];
949
- let offset = 0;
950
-
951
- for (const f of files.sort()) {
952
- let raw = fs.readFileSync(path.join(abs, f));
953
- let storedName = f;
954
-
955
- if (isEncryptable(f)) {
956
- try {
957
- const encrypted = encrypt(raw.toString('utf8'), encKey);
958
- raw = encrypted;
959
- storedName = f; // Keep original name, content is encrypted
960
- } catch (err) {
961
- error(`Failed to encrypt ${f}: ${err.message}`);
962
- }
799
+ let card = null;
800
+ if (stat.isFile() && abs.endsWith('.kdna')) {
801
+ const { readContainerJson } = require('../package-store');
802
+ card = readContainerJson(abs, 'KDNA_CARD.json') || null;
803
+ if (locale) {
804
+ const localeCard = readContainerJson(abs, `locales/${locale}/KDNA_CARD.json`) || null;
805
+ if (localeCard) card = { ...card, ...localeCard };
963
806
  }
964
-
965
- const crc = crc32(raw);
966
- const compressed = zlib.deflateRawSync(raw);
967
- const useStore = compressed.length >= raw.length;
968
- const stored = useStore ? raw : compressed;
969
-
970
- const nameBytes = Buffer.from(storedName, 'utf8');
971
- const localHeader = Buffer.alloc(30);
972
- localHeader.writeUInt32LE(0x04034b50, 0);
973
- localHeader.writeUInt16LE(20, 4);
974
- localHeader.writeUInt16LE(0x0800, 6);
975
- localHeader.writeUInt16LE(useStore ? 0 : 8, 8);
976
- localHeader.writeUInt32LE(crc, 14);
977
- localHeader.writeUInt32LE(useStore ? raw.length : compressed.length, 18);
978
- localHeader.writeUInt32LE(raw.length, 22);
979
- localHeader.writeUInt16LE(nameBytes.length, 26);
980
-
981
- fileData.push(Buffer.concat([localHeader, nameBytes, stored]));
982
- offset += localHeader.length + nameBytes.length + stored.length;
983
-
984
- const cdEntry = Buffer.alloc(46);
985
- cdEntry.writeUInt32LE(0x02014b50, 0);
986
- cdEntry.writeUInt16LE(20, 4);
987
- cdEntry.writeUInt16LE(20, 6);
988
- cdEntry.writeUInt16LE(0x0800, 8);
989
- cdEntry.writeUInt16LE(useStore ? 0 : 8, 10);
990
- cdEntry.writeUInt32LE(crc, 16);
991
- cdEntry.writeUInt32LE(useStore ? raw.length : compressed.length, 20);
992
- cdEntry.writeUInt32LE(raw.length, 24);
993
- cdEntry.writeUInt16LE(nameBytes.length, 28);
994
- cdEntry.writeUInt32LE(offset - stored.length - nameBytes.length - localHeader.length, 42);
995
- centralDir.push(Buffer.concat([cdEntry, nameBytes]));
996
- }
997
-
998
- const cdOffset = offset;
999
- const cdSize = centralDir.reduce((s, e) => s + e.length, 0);
1000
- const eocd = Buffer.alloc(22);
1001
- eocd.writeUInt32LE(0x06054b50, 0);
1002
- eocd.writeUInt16LE(0, 4);
1003
- eocd.writeUInt16LE(0, 6);
1004
- eocd.writeUInt16LE(files.length, 8);
1005
- eocd.writeUInt16LE(files.length, 10);
1006
- eocd.writeUInt32LE(cdSize, 12);
1007
- eocd.writeUInt32LE(cdOffset, 16);
1008
- eocd.writeUInt16LE(0, 20);
1009
-
1010
- const all = Buffer.concat([...fileData, ...centralDir, eocd]);
1011
- fs.writeFileSync(outPath, all);
1012
-
1013
- console.log(`✓ Encrypted pack: ${outPath}`);
1014
- console.log(` Domain: ${domainName} v${manifest.version}`);
1015
- console.log(
1016
- ` Files: ${files.length} (${files.filter(isEncryptable).length} encrypted, ${files.filter((f) => !isEncryptable(f)).length} plaintext)`,
1017
- );
1018
- console.log(` Container: AES-256-GCM .kdnae`);
1019
- }
1020
-
1021
- function cmdUnpackEncrypt(filePath, args = []) {
1022
- const abs = path.resolve(filePath);
1023
- if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
1024
- error(`Not a file: ${abs}`, EXIT.INPUT_ERROR);
1025
- }
1026
- if (!abs.endsWith('.kdnae')) {
1027
- error(`Not a .kdnae file: ${abs}`, EXIT.INPUT_ERROR);
1028
- }
1029
-
1030
- const licenseIdx = args.indexOf('--license');
1031
- const keyIdx = args.indexOf('--key');
1032
- let encKey;
1033
-
1034
- if (licenseIdx >= 0) {
1035
- const licensePath = args[licenseIdx + 1];
1036
- if (!licensePath || !fs.existsSync(licensePath))
1037
- error('License file not found', EXIT.INPUT_ERROR);
1038
- const license = JSON.parse(fs.readFileSync(licensePath, 'utf8'));
1039
- const licenseKey = license.license_id;
1040
- const fp = machineFingerprint();
1041
- encKey = deriveKey(licenseKey, fp);
1042
- } else if (keyIdx >= 0) {
1043
- const rawKey = args[keyIdx + 1];
1044
- if (!rawKey) error('--key requires a value', EXIT.INPUT_ERROR);
1045
- encKey = deriveKey(rawKey, machineFingerprint());
807
+ } else if (stat.isDirectory()) {
808
+ card = readCardFromDirectory(abs, locale);
1046
809
  } else {
1047
- error('Use --license <license.json> or --key <secret> to decrypt', EXIT.INPUT_ERROR);
1048
- }
1049
-
1050
- const domainName = path.basename(abs, '.kdnae');
1051
- const outDir = path.join(path.dirname(abs), domainName);
1052
- const force = args.includes('--force');
1053
-
1054
- if (fs.existsSync(outDir)) {
1055
- if (!force)
1056
- error(`Directory already exists: ${outDir}\nUse --force to overwrite.`, EXIT.INPUT_ERROR);
1057
- fs.rmSync(outDir, { recursive: true, force: true });
1058
- }
1059
- fs.mkdirSync(outDir, { recursive: true });
1060
-
1061
- // Extract ZIP first, then decrypt KDNA JSON files
1062
- const os = require('os');
1063
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdnae-unpack-'));
1064
-
1065
- try {
1066
- const tmpPy = path.join(os.tmpdir(), `kdnae-unpack-${Date.now()}.py`);
1067
- try {
1068
- const script = `import zipfile, os
1069
- zf = zipfile.ZipFile(${JSON.stringify(abs)}, 'r')
1070
- zf.extractall(${JSON.stringify(tmpDir)})
1071
- zf.close()
1072
- `;
1073
- fs.writeFileSync(tmpPy, script);
1074
- execSync(`python3 ${tmpPy}`, { stdio: 'pipe' });
1075
- } catch {
1076
- try {
1077
- execSync(`unzip -q -o "${abs}" -d "${tmpDir}"`, { stdio: 'pipe' });
1078
- } catch {
1079
- error('Cannot unpack .kdnae container');
1080
- }
1081
- } finally {
1082
- try {
1083
- fs.unlinkSync(tmpPy);
1084
- } catch {
1085
- /* cleanup */
1086
- }
1087
- }
1088
-
1089
- // Copy plaintext files, decrypt KDNA files
1090
- const extracted = fs.readdirSync(tmpDir);
1091
- for (const f of extracted) {
1092
- const src = path.join(tmpDir, f);
1093
- const dest = path.join(outDir, f);
1094
-
1095
- if (ENCRYPTED_FILES.includes(f)) {
1096
- try {
1097
- const encrypted = fs.readFileSync(src);
1098
- const decrypted = decrypt(encrypted, encKey);
1099
- fs.writeFileSync(dest, decrypted);
1100
- } catch (err) {
1101
- error(`Failed to decrypt ${f}: ${err.message}. Wrong license key?`);
1102
- }
1103
- } else {
1104
- fs.copyFileSync(src, dest);
1105
- }
1106
- }
1107
- } finally {
1108
- try {
1109
- fs.rmSync(tmpDir, { recursive: true, force: true });
1110
- } catch {
1111
- /* cleanup */
1112
- }
1113
- }
1114
-
1115
- console.log(`✓ Decrypted: ${outDir}`);
1116
- const files = fs.readdirSync(outDir);
1117
- console.log(` Files: ${files.length}`);
1118
- files.forEach((f) => console.log(` ${f}`));
1119
- }
1120
-
1121
- // ─── KDNA Card (locale-aware) ────────────────────────────────────
1122
-
1123
- function cmdCard(dir, jsonMode = false, locale = null) {
1124
- const abs = path.resolve(dir);
1125
- let card = readJson(path.join(abs, 'KDNA_CARD.json'));
1126
-
1127
- if (locale) {
1128
- const localeCard = readJson(path.join(abs, 'locales', locale, 'KDNA_CARD.json'));
1129
- if (localeCard) {
1130
- card = { ...card, ...localeCard };
1131
- }
810
+ error(`Not a .kdna asset: ${abs}`, EXIT.INPUT_ERROR);
1132
811
  }
1133
812
 
1134
813
  if (!card) {
@@ -1195,9 +874,7 @@ function cmdCard(dir, jsonMode = false, locale = null) {
1195
874
  module.exports = {
1196
875
  cmdValidate,
1197
876
  cmdPack,
1198
- cmdPackEncrypt,
1199
877
  cmdUnpack,
1200
- cmdUnpackEncrypt,
1201
878
  cmdInspect,
1202
879
  cmdCard,
1203
880
  };