@aikdna/kdna-cli 0.26.1 → 0.26.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/README.md +13 -13
- package/fixtures/v1-minimal/checksums.json +1 -1
- package/package.json +4 -3
- package/skills/kdna-loader/SKILL.md +21 -21
- package/src/agent.js +1 -3
- package/src/cli.js +109 -10
- package/src/cmds/anti-monolithic.js +6 -2
- package/src/cmds/demo.js +1 -3
- package/src/cmds/domain.js +4 -18
- package/src/package-store.js +27 -2
- package/src/publish.js +13 -9
- package/src/verify.js +9 -55
- package/src/cmds/protect.js.bak +0 -245
package/README.md
CHANGED
|
@@ -51,19 +51,19 @@ Successful validation returns:
|
|
|
51
51
|
|
|
52
52
|
## Core Commands
|
|
53
53
|
|
|
54
|
-
| Command
|
|
55
|
-
|
|
56
|
-
| `kdna demo minimal <dir>`
|
|
57
|
-
| `kdna inspect <path>`
|
|
58
|
-
| `kdna validate <path>`
|
|
59
|
-
| `kdna plan-load <path> --json`
|
|
60
|
-
| `kdna plan-load <path> --json --has-password`
|
|
61
|
-
| `kdna plan-load <path> --json --entitlement-status active` | Diagnose receipt/entitlement load state
|
|
62
|
-
| `kdna pack <source-dir> <output.kdna>`
|
|
63
|
-
| `kdna unpack <input.kdna> <output-dir>`
|
|
64
|
-
| `kdna load <path> --profile=<index|compact|scenario|full> --as=<json|prompt>` | Render judgment context for agents or tools |
|
|
65
|
-
| `kdna setup`
|
|
66
|
-
| `kdna doctor --agents`
|
|
54
|
+
| Command | Purpose |
|
|
55
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------- | -------- | ---------------- | -------- | ------------------------------------------- |
|
|
56
|
+
| `kdna demo minimal <dir>` | Create a minimal v1 source directory |
|
|
57
|
+
| `kdna inspect <path>` | Inspect a v1 source dir or `.kdna` container |
|
|
58
|
+
| `kdna validate <path>` | Validate format, schema, payload, checksums, and load contract |
|
|
59
|
+
| `kdna plan-load <path> --json` | Return the Core LoadPlan before runtime load |
|
|
60
|
+
| `kdna plan-load <path> --json --has-password` | Diagnose password-authorized load state |
|
|
61
|
+
| `kdna plan-load <path> --json --entitlement-status active` | Diagnose receipt/entitlement load state |
|
|
62
|
+
| `kdna pack <source-dir> <output.kdna>` | Pack a v1 source directory |
|
|
63
|
+
| `kdna unpack <input.kdna> <output-dir>` | Unpack a v1 container |
|
|
64
|
+
| `kdna load <path> --profile=<index | compact | scenario | full> --as=<json | prompt>` | Render judgment context for agents or tools |
|
|
65
|
+
| `kdna setup` | Install the `kdna-loader` skill for supported agents |
|
|
66
|
+
| `kdna doctor --agents` | Check agent loader installation |
|
|
67
67
|
|
|
68
68
|
## Producer Path
|
|
69
69
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-cli",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.3",
|
|
4
4
|
"description": "KDNA CLI — runtime control plane for verifying, installing, loading, comparing, publishing, and auditing existing .kdna assets.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"test:v1": "node --test tests/v1-global-cli.test.js tests/v1-demo-minimal.test.js",
|
|
32
32
|
"test:smoke": "npm run test:core-smoke",
|
|
33
33
|
"test:legacy": "node --test tests/v07-commands.test.js tests/v012-commands.test.js tests/asset-store.test.js",
|
|
34
|
-
"test:demo": "node --test tests/v1-demo-minimal.test.js"
|
|
34
|
+
"test:demo": "node --test tests/v1-demo-minimal.test.js",
|
|
35
|
+
"prepublishOnly": "npm test"
|
|
35
36
|
},
|
|
36
37
|
"keywords": [
|
|
37
38
|
"kdna",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"node": ">=18"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@aikdna/kdna-core": "^0.12.
|
|
59
|
+
"@aikdna/kdna-core": "^0.12.1"
|
|
59
60
|
},
|
|
60
61
|
"optionalDependencies": {
|
|
61
62
|
"ajv": "^8.20.0",
|
|
@@ -29,11 +29,11 @@ incorrectly, assigns wrong priorities, applies wrong risk models, and
|
|
|
29
29
|
offers wrong recommendations — all with the false confidence of having
|
|
30
30
|
"loaded expert judgment."
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
_No KDNA_: the agent uses model capability, tools, MCP, project files,
|
|
33
33
|
and normal prompts. It may lack domain-specific judgment, but it is
|
|
34
34
|
not polluted by incorrect judgment.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
_Wrong KDNA_: the agent applies a mismatched framework — e.g., diagnosing
|
|
37
37
|
a website design task through a team management lens, or treating a
|
|
38
38
|
price question as an editing issue. The output is worse than baseline.
|
|
39
39
|
|
|
@@ -152,15 +152,15 @@ what you'd produce if you loaded the domain? If yes, skip it.
|
|
|
152
152
|
After evaluating against `applies_when`, `does_not_apply_when`, and
|
|
153
153
|
`failure_risks`, classify into one of 7 states:
|
|
154
154
|
|
|
155
|
-
| State
|
|
156
|
-
|
|
157
|
-
| **SKIP_NO_JUDGMENT_NEEDED** | Task is mechanical: format, translate, lookup, execute
|
|
158
|
-
| **SKIP_NO_LOCAL_DOMAIN**
|
|
159
|
-
| **SKIP_WEAK_FIT**
|
|
160
|
-
| **REJECT_NEGATIVE_MATCH**
|
|
161
|
-
| **ASK_AMBIGUOUS_DOMAIN**
|
|
162
|
-
| **LOAD_STRONG_FIT**
|
|
163
|
-
| **BLOCK_INTEGRITY_FAILED**
|
|
155
|
+
| State | Condition | Action |
|
|
156
|
+
| --------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
157
|
+
| **SKIP_NO_JUDGMENT_NEEDED** | Task is mechanical: format, translate, lookup, execute | Answer normally. Do not mention KDNA. |
|
|
158
|
+
| **SKIP_NO_LOCAL_DOMAIN** | Task may need judgment, but no installed domain covers it | Answer normally. Only mention KDNA if user explicitly asks. |
|
|
159
|
+
| **SKIP_WEAK_FIT** | A domain is weakly related but insufficiently matches | Answer normally. Trace notes "weak match, skipped." |
|
|
160
|
+
| **REJECT_NEGATIVE_MATCH** | A domain's `does_not_apply_when` explicitly excludes this task | Block loading. Respect the author's boundary. |
|
|
161
|
+
| **ASK_AMBIGUOUS_DOMAIN** | 2+ domains could apply but with different judgment frameworks | Ask user to choose. Do **not** silently blend. |
|
|
162
|
+
| **LOAD_STRONG_FIT** | One local domain strongly matches and validates | Load it. |
|
|
163
|
+
| **BLOCK_INTEGRITY_FAILED** | Domain matches but validation, checksum, parsing, or runtime loading fails | Block loading. Notify if appropriate. |
|
|
164
164
|
|
|
165
165
|
**Rule: Negative Match First.** Check `does_not_apply_when` before
|
|
166
166
|
checking `applies_when`. A domain that says "not for visual design"
|
|
@@ -223,8 +223,8 @@ stages.
|
|
|
223
223
|
You have now internalized the domain's judgment surface. From this
|
|
224
224
|
point on:
|
|
225
225
|
|
|
226
|
-
1. **Adopt the axioms as your reasoning frame** — reason
|
|
227
|
-
them, not
|
|
226
|
+
1. **Adopt the axioms as your reasoning frame** — reason _from_
|
|
227
|
+
them, not _around_ them.
|
|
228
228
|
2. **Honour the boundaries** — for each axiom you'd apply, confirm
|
|
229
229
|
the task is in `applies_when` AND not in `does_not_apply_when`.
|
|
230
230
|
3. **Pre-check failure_risk** — before producing output, ask:
|
|
@@ -259,14 +259,14 @@ KDNA does not override:
|
|
|
259
259
|
|
|
260
260
|
## Failure handling
|
|
261
261
|
|
|
262
|
-
| Situation
|
|
263
|
-
|
|
264
|
-
| `kdna` CLI not installed
|
|
265
|
-
| No local v1 assets are found
|
|
266
|
-
| `kdna plan-load <asset>` returns `can_load_now=false`
|
|
267
|
-
| `kdna load <name>` exits non-zero
|
|
268
|
-
| User explicitly asks for a domain that isn't installed | Tell them, suggest `kdna install <name>`. Do not fabricate the domain.
|
|
269
|
-
| Two domains' stances directly conflict on the task
|
|
262
|
+
| Situation | What to do |
|
|
263
|
+
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
264
|
+
| `kdna` CLI not installed | Skip KDNA. Answer normally. Mention installation only if user asks about KDNA itself. |
|
|
265
|
+
| No local v1 assets are found | No domains installed. Skip KDNA. |
|
|
266
|
+
| `kdna plan-load <asset>` returns `can_load_now=false` | Do not load. Follow `required_action` and `issues[].code`. |
|
|
267
|
+
| `kdna load <name>` exits non-zero | That domain is broken (validation, authorization, parse, or runtime loading failure). Try next candidate or skip KDNA. The error message tells you why. |
|
|
268
|
+
| User explicitly asks for a domain that isn't installed | Tell them, suggest `kdna install <name>`. Do not fabricate the domain. |
|
|
269
|
+
| Two domains' stances directly conflict on the task | Surface to user. Do not blend. |
|
|
270
270
|
|
|
271
271
|
---
|
|
272
272
|
|
package/src/agent.js
CHANGED
package/src/cli.js
CHANGED
|
@@ -89,6 +89,7 @@ Start here:
|
|
|
89
89
|
Core v1:
|
|
90
90
|
inspect <path> Inspect v1 source dir or .kdna container
|
|
91
91
|
validate <path> Validate v1 source dir or .kdna container
|
|
92
|
+
validate <path> --runtime Validate and require LoadPlan readiness
|
|
92
93
|
plan-load <path> Return a LoadPlan before runtime load
|
|
93
94
|
Add --has-password or --entitlement-status for diagnostics
|
|
94
95
|
pack <src> <out> Deterministic pack into .kdna container
|
|
@@ -97,6 +98,11 @@ Core v1:
|
|
|
97
98
|
More:
|
|
98
99
|
kdna help advanced Agent runtime, setup, loading, comparison
|
|
99
100
|
kdna help legacy Pre-v1 compatibility commands
|
|
101
|
+
Advanced index: doctor, trace, history, license, verify, compare, diff, search
|
|
102
|
+
verify <name|file> Legacy asset verification
|
|
103
|
+
compare <name|file> --input "..." Legacy comparison report
|
|
104
|
+
diff <left> <right> Legacy asset diff
|
|
105
|
+
search <query> Search installed/available legacy entries
|
|
100
106
|
|
|
101
107
|
Flags:
|
|
102
108
|
--json Structured JSON output
|
|
@@ -111,6 +117,7 @@ function showHelpAdvanced() {
|
|
|
111
117
|
Core v1:
|
|
112
118
|
inspect <path> Inspect v1 source dir or .kdna container
|
|
113
119
|
validate <path> Validate v1 source dir or .kdna container
|
|
120
|
+
validate <path> --runtime Validate and require LoadPlan readiness
|
|
114
121
|
plan-load <path> [--has-password] [--entitlement-status <status>]
|
|
115
122
|
Return a LoadPlan before runtime load
|
|
116
123
|
pack <src> <out> Deterministic pack into .kdna container
|
|
@@ -261,12 +268,56 @@ switch (cmd) {
|
|
|
261
268
|
case 'validate': {
|
|
262
269
|
const v1Target = args.filter((a) => !a.startsWith('--'))[1];
|
|
263
270
|
if (v1Target) {
|
|
264
|
-
const {
|
|
271
|
+
const {
|
|
272
|
+
isV1SourceDir,
|
|
273
|
+
detectContainerFormat,
|
|
274
|
+
validate,
|
|
275
|
+
pack,
|
|
276
|
+
unpack,
|
|
277
|
+
inspect,
|
|
278
|
+
} = require('@aikdna/kdna-core');
|
|
265
279
|
const abs = require('node:path').resolve(v1Target);
|
|
266
280
|
if (isV1SourceDir(abs) || detectContainerFormat(abs) === 'v1') {
|
|
281
|
+
const runtimeMode = args.includes('--runtime');
|
|
267
282
|
const result = validate(v1Target);
|
|
283
|
+
if (runtimeMode) {
|
|
284
|
+
const entitlementStatusIndex = args.indexOf('--entitlement-status');
|
|
285
|
+
const entitlementStatus =
|
|
286
|
+
entitlementStatusIndex >= 0 ? args[entitlementStatusIndex + 1] : null;
|
|
287
|
+
const allowedEntitlementStatuses = new Set([
|
|
288
|
+
'active',
|
|
289
|
+
'expired',
|
|
290
|
+
'revoked',
|
|
291
|
+
'offline_grace',
|
|
292
|
+
]);
|
|
293
|
+
if (entitlementStatusIndex >= 0 && !allowedEntitlementStatuses.has(entitlementStatus)) {
|
|
294
|
+
error(
|
|
295
|
+
'Invalid --entitlement-status. Use active, expired, revoked, or offline_grace.',
|
|
296
|
+
EXIT.INPUT_ERROR,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const core = require('@aikdna/kdna-core');
|
|
300
|
+
if (typeof core.planLoad !== 'function') {
|
|
301
|
+
error(
|
|
302
|
+
'kdna validate --runtime requires @aikdna/kdna-core with the LoadPlan v1 API. Update @aikdna/kdna-core before enabling runtime authorization diagnostics.',
|
|
303
|
+
EXIT.PROVIDER_ERROR,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
result.runtime_load_plan = core.planLoad(v1Target, {
|
|
307
|
+
hasPassword: args.includes('--has-password'),
|
|
308
|
+
entitlement: entitlementStatus ? { status: entitlementStatus } : undefined,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
268
311
|
console.log(JSON.stringify(result, null, 2));
|
|
269
|
-
|
|
312
|
+
if (!result.overall_valid) process.exit(1);
|
|
313
|
+
if (
|
|
314
|
+
runtimeMode &&
|
|
315
|
+
result.runtime_load_plan &&
|
|
316
|
+
result.runtime_load_plan.can_load_now !== true
|
|
317
|
+
) {
|
|
318
|
+
process.exit(result.runtime_load_plan.state === 'invalid' ? 1 : 3);
|
|
319
|
+
}
|
|
320
|
+
process.exit(0);
|
|
270
321
|
}
|
|
271
322
|
}
|
|
272
323
|
error(
|
|
@@ -277,7 +328,11 @@ switch (cmd) {
|
|
|
277
328
|
}
|
|
278
329
|
case 'plan-load': {
|
|
279
330
|
const v1Target = args.filter((a) => !a.startsWith('--'))[1];
|
|
280
|
-
if (!v1Target)
|
|
331
|
+
if (!v1Target)
|
|
332
|
+
error(
|
|
333
|
+
'Usage: kdna plan-load <path> [--json] [--has-password] [--entitlement-status <status>]',
|
|
334
|
+
EXIT.INPUT_ERROR,
|
|
335
|
+
);
|
|
281
336
|
const core = require('@aikdna/kdna-core');
|
|
282
337
|
const abs = require('node:path').resolve(v1Target);
|
|
283
338
|
if (!(core.isV1SourceDir(abs) || core.detectContainerFormat(abs) === 'v1')) {
|
|
@@ -293,19 +348,30 @@ switch (cmd) {
|
|
|
293
348
|
const entitlementStatus = entitlementStatusIndex >= 0 ? args[entitlementStatusIndex + 1] : null;
|
|
294
349
|
const allowedEntitlementStatuses = new Set(['active', 'expired', 'revoked', 'offline_grace']);
|
|
295
350
|
if (entitlementStatusIndex >= 0 && !allowedEntitlementStatuses.has(entitlementStatus)) {
|
|
296
|
-
error(
|
|
351
|
+
error(
|
|
352
|
+
'Invalid --entitlement-status. Use active, expired, revoked, or offline_grace.',
|
|
353
|
+
EXIT.INPUT_ERROR,
|
|
354
|
+
);
|
|
297
355
|
}
|
|
298
356
|
const plan = core.planLoad(v1Target, {
|
|
299
357
|
hasPassword: args.includes('--has-password'),
|
|
300
358
|
entitlement: entitlementStatus ? { status: entitlementStatus } : undefined,
|
|
301
359
|
});
|
|
302
360
|
console.log(JSON.stringify(plan, null, 2));
|
|
303
|
-
process.exit(plan.state === 'invalid' ? 1 : 0);
|
|
361
|
+
process.exit(plan.state === 'invalid' ? 1 : plan.can_load_now === true ? 0 : 3);
|
|
362
|
+
break;
|
|
304
363
|
}
|
|
305
364
|
case 'pack': {
|
|
306
365
|
const v1Target = args.filter((a) => !a.startsWith('--'))[1];
|
|
307
366
|
if (v1Target) {
|
|
308
|
-
const {
|
|
367
|
+
const {
|
|
368
|
+
isV1SourceDir,
|
|
369
|
+
detectContainerFormat,
|
|
370
|
+
validate,
|
|
371
|
+
pack,
|
|
372
|
+
unpack,
|
|
373
|
+
inspect,
|
|
374
|
+
} = require('@aikdna/kdna-core');
|
|
309
375
|
const abs = require('node:path').resolve(v1Target);
|
|
310
376
|
if (isV1SourceDir(abs)) {
|
|
311
377
|
const out = args.filter((a) => !a.startsWith('--'))[2];
|
|
@@ -329,7 +395,14 @@ switch (cmd) {
|
|
|
329
395
|
case 'unpack': {
|
|
330
396
|
const v1Target = args.filter((a) => !a.startsWith('--'))[1];
|
|
331
397
|
if (v1Target) {
|
|
332
|
-
const {
|
|
398
|
+
const {
|
|
399
|
+
isV1SourceDir,
|
|
400
|
+
detectContainerFormat,
|
|
401
|
+
validate,
|
|
402
|
+
pack,
|
|
403
|
+
unpack,
|
|
404
|
+
inspect,
|
|
405
|
+
} = require('@aikdna/kdna-core');
|
|
333
406
|
const abs = require('node:path').resolve(v1Target);
|
|
334
407
|
if (detectContainerFormat(abs) === 'v1') {
|
|
335
408
|
const out = args.filter((a) => !a.startsWith('--'))[2];
|
|
@@ -408,7 +481,14 @@ switch (cmd) {
|
|
|
408
481
|
case 'inspect': {
|
|
409
482
|
const target = args.filter((a) => !a.startsWith('--'))[1];
|
|
410
483
|
if (!target) error('Usage: kdna inspect <path> [--json] [--locale zh-CN]');
|
|
411
|
-
const {
|
|
484
|
+
const {
|
|
485
|
+
isV1SourceDir,
|
|
486
|
+
detectContainerFormat,
|
|
487
|
+
validate,
|
|
488
|
+
pack,
|
|
489
|
+
unpack,
|
|
490
|
+
inspect,
|
|
491
|
+
} = require('@aikdna/kdna-core');
|
|
412
492
|
const abs = require('node:path').resolve(target);
|
|
413
493
|
if (isV1SourceDir(abs) || detectContainerFormat(abs) === 'v1') {
|
|
414
494
|
const out = inspect(target);
|
|
@@ -466,7 +546,8 @@ switch (cmd) {
|
|
|
466
546
|
case 'load': {
|
|
467
547
|
const target = args.filter((a) => !a.startsWith('--'))[1];
|
|
468
548
|
if (target) {
|
|
469
|
-
const
|
|
549
|
+
const core = require('@aikdna/kdna-core');
|
|
550
|
+
const { isV1SourceDir, detectContainerFormat } = core;
|
|
470
551
|
const abs = require('node:path').resolve(target);
|
|
471
552
|
if (isV1SourceDir(abs) || detectContainerFormat(abs) === 'v1') {
|
|
472
553
|
const getFlag = (name) => {
|
|
@@ -478,7 +559,25 @@ switch (cmd) {
|
|
|
478
559
|
const profile = getFlag('--profile') || 'compact';
|
|
479
560
|
const as = getFlag('--as') || 'json';
|
|
480
561
|
try {
|
|
481
|
-
const
|
|
562
|
+
const loadV1Authorized =
|
|
563
|
+
core.loadAuthorized ||
|
|
564
|
+
((input, options) => {
|
|
565
|
+
if (typeof core.planLoad !== 'function') {
|
|
566
|
+
throw new Error('LoadPlan API is required before loading KDNA Core v1 assets');
|
|
567
|
+
}
|
|
568
|
+
const plan = core.planLoad(input, options);
|
|
569
|
+
if (plan.can_load_now !== true) {
|
|
570
|
+
const err = new Error(
|
|
571
|
+
`LoadPlan denied loading: state=${plan.state || 'invalid'} required_action=${plan.required_action || 'block'}`,
|
|
572
|
+
);
|
|
573
|
+
err.code =
|
|
574
|
+
(plan.issues && plan.issues[0] && plan.issues[0].code) ||
|
|
575
|
+
'KDNA_LOAD_NOT_AUTHORIZED';
|
|
576
|
+
throw err;
|
|
577
|
+
}
|
|
578
|
+
return core.loadV1(input, options);
|
|
579
|
+
});
|
|
580
|
+
const r = loadV1Authorized(target, { profile, as });
|
|
482
581
|
if (as === 'prompt') {
|
|
483
582
|
process.stdout.write(r.text + '\n');
|
|
484
583
|
} else {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
const fs = require('fs');
|
|
39
39
|
const path = require('path');
|
|
40
40
|
|
|
41
|
-
const AXIOM_THRESHOLD = 6;
|
|
41
|
+
const AXIOM_THRESHOLD = 6; // SPEC §5.2 says "between 2 and 6 axioms"
|
|
42
42
|
const FRAMEWORK_THRESHOLD = 3; // RFC-0013 §4 companion rule
|
|
43
43
|
const RATIONALE_MIN_LENGTH = 30;
|
|
44
44
|
|
|
@@ -103,7 +103,11 @@ function runAntiMonolithicCheck(dir, opts = {}) {
|
|
|
103
103
|
hasRationale = typeof rationale === 'string' && rationale.trim().length >= RATIONALE_MIN_LENGTH;
|
|
104
104
|
result.summary.has_decomposition_rationale = hasRationale;
|
|
105
105
|
|
|
106
|
-
if (
|
|
106
|
+
if (
|
|
107
|
+
rationale &&
|
|
108
|
+
rationale.trim().length > 0 &&
|
|
109
|
+
rationale.trim().length < RATIONALE_MIN_LENGTH
|
|
110
|
+
) {
|
|
107
111
|
result.warnings.push(
|
|
108
112
|
`module_manifest.json: decomposition_rationale is only ${rationale.trim().length} chars; ` +
|
|
109
113
|
`minimum is ${RATIONALE_MIN_LENGTH} chars to count as a real sign-off.`,
|
package/src/cmds/demo.js
CHANGED
|
@@ -26,9 +26,7 @@ function cmdDemo(args) {
|
|
|
26
26
|
if (fs.existsSync(outDir)) {
|
|
27
27
|
const existing = fs.readdirSync(outDir).filter((f) => f !== '.DS_Store');
|
|
28
28
|
if (existing.length > 0 && !force) {
|
|
29
|
-
console.error(
|
|
30
|
-
`Target already exists and is not empty: ${outDir}`,
|
|
31
|
-
);
|
|
29
|
+
console.error(`Target already exists and is not empty: ${outDir}`);
|
|
32
30
|
console.error('Use --force to overwrite.');
|
|
33
31
|
process.exit(2);
|
|
34
32
|
}
|
package/src/cmds/domain.js
CHANGED
|
@@ -238,9 +238,7 @@ function cmdValidateAntiMonolithic(dir, opts = {}) {
|
|
|
238
238
|
const amResults = [];
|
|
239
239
|
if (schemaResult.cluster && Array.isArray(schemaResult.domains)) {
|
|
240
240
|
for (const d of schemaResult.domains) {
|
|
241
|
-
amResults.push(
|
|
242
|
-
runAntiMonolithicCheckOnCore(d.path || abs, { strict }),
|
|
243
|
-
);
|
|
241
|
+
amResults.push(runAntiMonolithicCheckOnCore(d.path || abs, { strict }));
|
|
244
242
|
}
|
|
245
243
|
} else {
|
|
246
244
|
amResults.push(runAntiMonolithicCheckOnCore(abs, { strict }));
|
|
@@ -316,21 +314,9 @@ function cmdPack(dir, outputDir) {
|
|
|
316
314
|
if (!core) error('KDNA_Core.json not found or invalid');
|
|
317
315
|
if (!pat) error('KDNA_Patterns.json not found or invalid');
|
|
318
316
|
|
|
319
|
-
console.warn('Warning: kdna dev pack creates a dev-only
|
|
320
|
-
console.warn('Use KDNA Studio compile/export
|
|
321
|
-
|
|
322
|
-
// Human Lock Gate — check judgment-class cards before packing
|
|
323
|
-
const { checkHumanLock } = require('../publish');
|
|
324
|
-
const hl = checkHumanLock(abs);
|
|
325
|
-
if (!hl.passed) {
|
|
326
|
-
console.error('Human Lock Gate: BLOCKED');
|
|
327
|
-
for (const issue of hl.issues) {
|
|
328
|
-
console.error(` ✗ ${issue}`);
|
|
329
|
-
}
|
|
330
|
-
console.error('Judgment-class cards must be locked with valid Human Lock before packing.');
|
|
331
|
-
console.error('Use kdna publish --check for details.');
|
|
332
|
-
process.exit(EXIT.HUMAN_LOCK_REQUIRED);
|
|
333
|
-
}
|
|
317
|
+
console.warn('Warning: kdna dev pack creates a dev-only .kdna bundle.');
|
|
318
|
+
console.warn('Use KDNA Studio compile/export for release-grade authoring evidence.');
|
|
319
|
+
console.warn('Human Lock is optional provenance and is not required for format validity.');
|
|
334
320
|
|
|
335
321
|
const domainName = core.meta?.domain || path.basename(abs);
|
|
336
322
|
|
package/src/package-store.js
CHANGED
|
@@ -68,7 +68,7 @@ function readContainerJson(kdnaPath, fileName, options = {}) {
|
|
|
68
68
|
|
|
69
69
|
function readContainerDataMap(kdnaPath, options = {}) {
|
|
70
70
|
const asset = assetReader.openSync(kdnaPath);
|
|
71
|
-
const dataMap =
|
|
71
|
+
const dataMap = readDataMapCompatSync(asset, options);
|
|
72
72
|
if (asset.entries.has('kdna.json')) {
|
|
73
73
|
dataMap['kdna.json'] = assetReader.readJsonSync(asset, 'kdna.json', options);
|
|
74
74
|
}
|
|
@@ -87,7 +87,7 @@ function listContainerEntries(kdnaPath) {
|
|
|
87
87
|
|
|
88
88
|
function readContainer(kdnaPath, options = {}) {
|
|
89
89
|
const asset = assetReader.openSync(kdnaPath);
|
|
90
|
-
const dataMap =
|
|
90
|
+
const dataMap = readDataMapCompatSync(asset, options);
|
|
91
91
|
return {
|
|
92
92
|
manifest: dataMap['kdna.json'] || {},
|
|
93
93
|
core: dataMap['KDNA_Core.json'] || {},
|
|
@@ -100,6 +100,31 @@ function readContainer(kdnaPath, options = {}) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function readDataMapCompatSync(asset, options = {}) {
|
|
104
|
+
try {
|
|
105
|
+
return assetReader.readDataMapSync(asset, undefined, options);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (!String(e?.message || '').includes('missing payload.kdnab')) {
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const dataMap = {};
|
|
113
|
+
for (const entry of [
|
|
114
|
+
'KDNA_Core.json',
|
|
115
|
+
'KDNA_Patterns.json',
|
|
116
|
+
'KDNA_Scenarios.json',
|
|
117
|
+
'KDNA_Cases.json',
|
|
118
|
+
'KDNA_Reasoning.json',
|
|
119
|
+
'KDNA_Evolution.json',
|
|
120
|
+
]) {
|
|
121
|
+
if (asset.entries.has(entry)) {
|
|
122
|
+
dataMap[entry] = assetReader.readJsonSync(asset, entry, options);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return dataMap;
|
|
126
|
+
}
|
|
127
|
+
|
|
103
128
|
function verifyAsset(kdnaPath, options = {}) {
|
|
104
129
|
const asset = assetReader.openSync(kdnaPath);
|
|
105
130
|
return assetReader.verifySync(asset, options);
|
package/src/publish.js
CHANGED
|
@@ -689,7 +689,7 @@ function cmdPublish(assetPath, args = []) {
|
|
|
689
689
|
|
|
690
690
|
console.log('');
|
|
691
691
|
console.log('─'.repeat(60));
|
|
692
|
-
console.log('Legacy
|
|
692
|
+
console.log('Legacy Registry patch (historical compatibility only):');
|
|
693
693
|
console.log('─'.repeat(60));
|
|
694
694
|
console.log(JSON.stringify(patch, null, 2));
|
|
695
695
|
console.log('');
|
|
@@ -710,12 +710,6 @@ function validateAuthoringProvenance(manifest) {
|
|
|
710
710
|
const badge = manifest.quality_badge || 'untested';
|
|
711
711
|
const highTrust = (badgeRank[badge] || 0) >= badgeRank.tested;
|
|
712
712
|
const authoring = manifest.authoring;
|
|
713
|
-
const studioCompatible = new Set([
|
|
714
|
-
'kdna-studio',
|
|
715
|
-
'kdna-studio-cli',
|
|
716
|
-
'kdna-studio-sdk',
|
|
717
|
-
'third-party-studio-compatible',
|
|
718
|
-
]);
|
|
719
713
|
|
|
720
714
|
if (!authoring) {
|
|
721
715
|
if (highTrust) issues.push(`quality_badge "${badge}" requires authoring provenance`);
|
|
@@ -724,8 +718,18 @@ function validateAuthoringProvenance(manifest) {
|
|
|
724
718
|
if (authoring.created_by === 'manual-dev-source' && highTrust) {
|
|
725
719
|
issues.push('manual-dev-source assets cannot claim tested or higher quality');
|
|
726
720
|
}
|
|
727
|
-
|
|
728
|
-
|
|
721
|
+
// Conformance-based check: any tool that passes the official validator is compatible.
|
|
722
|
+
// The authoring.conformance block records validator identity and pass status.
|
|
723
|
+
if (highTrust) {
|
|
724
|
+
const conformance = authoring.conformance;
|
|
725
|
+
if (!conformance || !conformance.passed) {
|
|
726
|
+
issues.push(
|
|
727
|
+
`quality_badge "${badge}" requires conformance validation (authoring.conformance.passed = true)`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
if (!conformance || !conformance.spec_version) {
|
|
731
|
+
issues.push('trusted assets require authoring.conformance.spec_version');
|
|
732
|
+
}
|
|
729
733
|
}
|
|
730
734
|
if (highTrust && !authoring.compiler) issues.push('trusted assets require authoring.compiler');
|
|
731
735
|
if (highTrust && !authoring.compiler_version) {
|
package/src/verify.js
CHANGED
|
@@ -20,6 +20,7 @@ const path = require('path');
|
|
|
20
20
|
const { RegistryResolver, parseName, registryTrustIssues, isEntryRevoked } = require('./registry');
|
|
21
21
|
const { EXIT, isYesNoSelfCheck } = require('./cmds/_common');
|
|
22
22
|
const { licenseDecryptOptionsForManifest } = require('./cmds/license');
|
|
23
|
+
const { validateAuthoringProvenance } = require('./publish');
|
|
23
24
|
|
|
24
25
|
const {
|
|
25
26
|
getInstalled,
|
|
@@ -515,71 +516,24 @@ function checkJudgment(input, options = {}) {
|
|
|
515
516
|
};
|
|
516
517
|
const badge = manifest?.quality_badge || 'untested';
|
|
517
518
|
const highTrust = (badgeRank[badge] || 0) >= badgeRank.tested;
|
|
518
|
-
const authoring = manifest?.authoring;
|
|
519
|
-
const studioCompatible = new Set([
|
|
520
|
-
'kdna-studio',
|
|
521
|
-
'kdna-studio-cli',
|
|
522
|
-
'kdna-studio-sdk',
|
|
523
|
-
'third-party-studio-compatible',
|
|
524
|
-
]);
|
|
525
519
|
if (highTrust) {
|
|
526
|
-
|
|
527
|
-
|
|
520
|
+
const provenanceIssues = validateAuthoringProvenance(manifest || {});
|
|
521
|
+
score.max += 1;
|
|
522
|
+
if (provenanceIssues.length) {
|
|
528
523
|
issues.push({
|
|
529
524
|
severity: 'error',
|
|
530
|
-
msg: `quality_badge ${badge}
|
|
525
|
+
msg: `quality_badge ${badge} authoring provenance gate failed: ${provenanceIssues.join('; ')}`,
|
|
531
526
|
});
|
|
532
527
|
} else {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!okSource) {
|
|
536
|
-
issues.push({
|
|
537
|
-
severity: 'error',
|
|
538
|
-
msg: 'trusted quality requires Studio-compatible authoring.created_by',
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
const hasCompiler = !!(
|
|
542
|
-
authoring.compiler &&
|
|
543
|
-
authoring.compiler_version &&
|
|
544
|
-
authoring.compiled_at
|
|
545
|
-
);
|
|
546
|
-
bump(1, hasCompiler ? 1 : 0, 'authoring compiler metadata present');
|
|
547
|
-
if (!hasCompiler) {
|
|
548
|
-
issues.push({
|
|
549
|
-
severity: 'error',
|
|
550
|
-
msg: 'trusted quality requires compiler, compiler_version, and compiled_at',
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
const hasIdentity = [
|
|
554
|
-
'asset_uid',
|
|
555
|
-
'project_uid',
|
|
556
|
-
'build_id',
|
|
557
|
-
'domain_id',
|
|
558
|
-
'content_digest',
|
|
559
|
-
].every((field) => !!(authoring[field] || manifest[field]));
|
|
560
|
-
bump(1, hasIdentity ? 1 : 0, 'authoring asset identity present');
|
|
561
|
-
if (!hasIdentity) {
|
|
562
|
-
issues.push({
|
|
563
|
-
severity: 'error',
|
|
564
|
-
msg: 'trusted quality requires asset_uid, project_uid, build_id, domain_id, and content_digest',
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
const humanConfirmed =
|
|
568
|
-
authoring.human_confirmed === true && Number(authoring.human_lock_count) > 0;
|
|
569
|
-
bump(1, humanConfirmed ? 1 : 0, `Human Lock provenance (${authoring.human_lock_count || 0})`);
|
|
570
|
-
if (!humanConfirmed) {
|
|
571
|
-
issues.push({
|
|
572
|
-
severity: 'error',
|
|
573
|
-
msg: 'trusted quality requires human_confirmed=true and human_lock_count > 0',
|
|
574
|
-
});
|
|
575
|
-
}
|
|
528
|
+
score.total += 1;
|
|
529
|
+
passed.push('✓ authoring provenance satisfies trusted quality gate');
|
|
576
530
|
}
|
|
577
|
-
} else if (!authoring) {
|
|
531
|
+
} else if (!manifest?.authoring) {
|
|
578
532
|
issues.push({
|
|
579
533
|
severity: 'warn',
|
|
580
534
|
msg: 'authoring provenance missing; asset cannot be promoted above untested',
|
|
581
535
|
});
|
|
582
|
-
} else if (authoring.created_by === 'manual-dev-source') {
|
|
536
|
+
} else if (manifest.authoring.created_by === 'manual-dev-source') {
|
|
583
537
|
passed.push('authoring provenance: manual-dev-source (untested ceiling)');
|
|
584
538
|
}
|
|
585
539
|
|
package/src/cmds/protect.js.bak
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KDNA Protected Asset Commands (RFC-0009)
|
|
3
|
-
*
|
|
4
|
-
* Commands:
|
|
5
|
-
* kdna protect <file.kdna> --out <file.kdna> [--entries <list>]
|
|
6
|
-
* kdna unlock <file.kdna> [--profile compact|index|full]
|
|
7
|
-
* kdna recover <file.kdna> --out <file.kdna> [--code-stdin]
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { EXIT, error, promptPassword } = require('./_common');
|
|
13
|
-
const {
|
|
14
|
-
createKdnaAssetReader,
|
|
15
|
-
createPasswordDecryptEntry,
|
|
16
|
-
createRecoveryDecryptEntry,
|
|
17
|
-
encryptProtectedEntry,
|
|
18
|
-
generateRecoveryCode,
|
|
19
|
-
} = require('@aikdna/kdna-core');
|
|
20
|
-
|
|
21
|
-
function parseEntriesFlag(flag) {
|
|
22
|
-
if (!flag) return ['KDNA_Core.json'];
|
|
23
|
-
return flag.split(',').map((s) => s.trim());
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function cmdProtect(args) {
|
|
27
|
-
const file = args[0];
|
|
28
|
-
if (!file) error('Usage: kdna protect <file.kdna> --out <file.kdna> [--entries KDNA_Core.json,KDNA_Patterns.json]', EXIT.INPUT_ERROR);
|
|
29
|
-
|
|
30
|
-
const outIdx = args.indexOf('--out');
|
|
31
|
-
const outPath = outIdx >= 0 ? args[outIdx + 1] : null;
|
|
32
|
-
if (!outPath) error('Missing --out', EXIT.INPUT_ERROR);
|
|
33
|
-
|
|
34
|
-
const entriesIdx = args.indexOf('--entries');
|
|
35
|
-
const entriesToEncrypt = parseEntriesFlag(entriesIdx >= 0 ? args[entriesIdx + 1] : null);
|
|
36
|
-
|
|
37
|
-
if (!fs.existsSync(file)) error(`File not found: ${file}`, EXIT.INPUT_ERROR);
|
|
38
|
-
|
|
39
|
-
const password = promptPassword('Password: ');
|
|
40
|
-
if (!password) error('Password is required.', EXIT.INPUT_ERROR);
|
|
41
|
-
|
|
42
|
-
const reader = createKdnaAssetReader();
|
|
43
|
-
const asset = reader.openSync(file);
|
|
44
|
-
const manifest = reader.readManifestSync(asset);
|
|
45
|
-
|
|
46
|
-
if (manifest.access === 'protected') {
|
|
47
|
-
error('Asset is already protected. Use recover to change password.', EXIT.INPUT_ERROR);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Update manifest
|
|
51
|
-
const newManifest = { ...manifest, access: 'protected', encryption: { profile: 'kdna-password-protected-v1', encrypted_entries: entriesToEncrypt } };
|
|
52
|
-
|
|
53
|
-
// Build new ZIP with encrypted entries
|
|
54
|
-
const allEntries = reader.listEntriesSync(asset);
|
|
55
|
-
const zipEntries = {};
|
|
56
|
-
const recoveryCode = generateRecoveryCode();
|
|
57
|
-
|
|
58
|
-
for (const entryName of allEntries) {
|
|
59
|
-
if (entryName === 'kdna.json') {
|
|
60
|
-
zipEntries[entryName] = JSON.stringify(newManifest);
|
|
61
|
-
} else if (entriesToEncrypt.includes(entryName)) {
|
|
62
|
-
const plaintext = reader.readEntrySync(asset, entryName);
|
|
63
|
-
const encrypted = encryptProtectedEntry(plaintext, {
|
|
64
|
-
entryName,
|
|
65
|
-
manifest: newManifest,
|
|
66
|
-
password,
|
|
67
|
-
recoveryCode,
|
|
68
|
-
});
|
|
69
|
-
zipEntries[entryName] = JSON.stringify(encrypted);
|
|
70
|
-
} else {
|
|
71
|
-
zipEntries[entryName] = reader.readEntrySync(asset, entryName);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Add mimetype if missing
|
|
76
|
-
if (!zipEntries.mimetype) {
|
|
77
|
-
zipEntries.mimetype = 'application/vnd.aikdna.kdna+zip';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Write new asset using core's internal ZIP builder or a simple approach
|
|
81
|
-
// For the CLI, we use the asset reader's internal helper if available, otherwise manual ZIP
|
|
82
|
-
const zipBuffer = buildZip(zipEntries);
|
|
83
|
-
fs.writeFileSync(outPath, zipBuffer);
|
|
84
|
-
|
|
85
|
-
console.log(`Protected asset written to: ${outPath}`);
|
|
86
|
-
console.log(`Encrypted entries: ${entriesToEncrypt.join(', ')}`);
|
|
87
|
-
console.log('Recovery code: (displayed once — save it)');
|
|
88
|
-
console.log(` ${recoveryCode}`);
|
|
89
|
-
console.log(' Use `kdna recover` if you forget the password.');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function cmdUnlock(args) {
|
|
93
|
-
const file = args[0];
|
|
94
|
-
if (!file) error('Usage: kdna unlock <file.kdna> [--profile compact|index|full]', EXIT.INPUT_ERROR);
|
|
95
|
-
|
|
96
|
-
const profileIdx = args.indexOf('--profile');
|
|
97
|
-
const profile = profileIdx >= 0 ? args[profileIdx + 1] : 'compact';
|
|
98
|
-
|
|
99
|
-
if (!fs.existsSync(file)) error(`File not found: ${file}`, EXIT.INPUT_ERROR);
|
|
100
|
-
|
|
101
|
-
const password = promptPassword('Password: ');
|
|
102
|
-
if (!password) error('Password is required.', EXIT.INPUT_ERROR);
|
|
103
|
-
|
|
104
|
-
const reader = createKdnaAssetReader();
|
|
105
|
-
const asset = reader.openSync(file);
|
|
106
|
-
const manifest = reader.readManifestSync(asset);
|
|
107
|
-
|
|
108
|
-
if (manifest.access !== 'protected') {
|
|
109
|
-
error(`Asset access is "${manifest.access}", expected "protected"`, EXIT.INPUT_ERROR);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const decryptEntry = createPasswordDecryptEntry({ password });
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const loaded = reader.loadProfileSync(asset, profile, { decryptEntry });
|
|
116
|
-
console.log(JSON.stringify(loaded, null, 2));
|
|
117
|
-
} catch (e) {
|
|
118
|
-
error(`Unlock failed: ${e.message}`, EXIT.TRUST_FAILED);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function cmdRecover(args) {
|
|
123
|
-
const file = args[0];
|
|
124
|
-
if (!file) error('Usage: kdna recover <file.kdna> --out <file.kdna> [--code-stdin]', EXIT.INPUT_ERROR);
|
|
125
|
-
|
|
126
|
-
const outIdx = args.indexOf('--out');
|
|
127
|
-
const outPath = outIdx >= 0 ? args[outIdx + 1] : null;
|
|
128
|
-
if (!outPath) error('Missing --out', EXIT.INPUT_ERROR);
|
|
129
|
-
|
|
130
|
-
if (!fs.existsSync(file)) error(`File not found: ${file}`, EXIT.INPUT_ERROR);
|
|
131
|
-
|
|
132
|
-
let recoveryCode;
|
|
133
|
-
if (args.includes('--code-stdin')) {
|
|
134
|
-
const stdinData = fs.readFileSync(0, 'utf8').trim();
|
|
135
|
-
if (!stdinData) error('No recovery code provided on stdin.', EXIT.INPUT_ERROR);
|
|
136
|
-
recoveryCode = stdinData;
|
|
137
|
-
} else {
|
|
138
|
-
recoveryCode = promptPassword('Recovery code: ');
|
|
139
|
-
if (!recoveryCode) error('Recovery code is required.', EXIT.INPUT_ERROR);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const newPassword = promptPassword('New password: ');
|
|
143
|
-
if (!newPassword) error('New password is required.', EXIT.INPUT_ERROR);
|
|
144
|
-
|
|
145
|
-
const reader = createKdnaAssetReader();
|
|
146
|
-
const asset = reader.openSync(file);
|
|
147
|
-
const manifest = reader.readManifestSync(asset);
|
|
148
|
-
|
|
149
|
-
if (manifest.access !== 'protected') {
|
|
150
|
-
error(`Asset access is "${manifest.access}", expected "protected"`, EXIT.INPUT_ERROR);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const decryptEntry = createRecoveryDecryptEntry({ recoveryCode });
|
|
154
|
-
|
|
155
|
-
// Decrypt all encrypted entries with recovery code, then re-encrypt with new password
|
|
156
|
-
const entriesToEncrypt = manifest.encryption?.encrypted_entries || ['KDNA_Core.json'];
|
|
157
|
-
const allEntries = reader.listEntriesSync(asset);
|
|
158
|
-
const zipEntries = {};
|
|
159
|
-
const newRecoveryCode = generateRecoveryCode();
|
|
160
|
-
|
|
161
|
-
for (const entryName of allEntries) {
|
|
162
|
-
if (entriesToEncrypt.includes(entryName)) {
|
|
163
|
-
// Decrypt with recovery code
|
|
164
|
-
const encryptedData = reader.readEntrySync(asset, entryName);
|
|
165
|
-
const plaintext = decryptEntry({ entryName, ciphertext: encryptedData, manifest });
|
|
166
|
-
|
|
167
|
-
// Re-encrypt with new password and new recovery code
|
|
168
|
-
const encrypted = encryptProtectedEntry(plaintext, {
|
|
169
|
-
entryName,
|
|
170
|
-
manifest: { ...manifest, encryption: { ...manifest.encryption, encrypted_entries: entriesToEncrypt } },
|
|
171
|
-
password: newPassword,
|
|
172
|
-
recoveryCode: newRecoveryCode,
|
|
173
|
-
});
|
|
174
|
-
zipEntries[entryName] = JSON.stringify(encrypted);
|
|
175
|
-
} else {
|
|
176
|
-
zipEntries[entryName] = reader.readEntrySync(asset, entryName);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!zipEntries.mimetype) {
|
|
181
|
-
zipEntries.mimetype = 'application/vnd.aikdna.kdna+zip';
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const zipBuffer = buildZip(zipEntries);
|
|
185
|
-
fs.writeFileSync(outPath, zipBuffer);
|
|
186
|
-
|
|
187
|
-
console.log(`Recovered asset written to: ${outPath}`);
|
|
188
|
-
console.log('Password has been reset.');
|
|
189
|
-
console.log('New recovery code: (displayed once — save it)');
|
|
190
|
-
console.log(` ${newRecoveryCode}`);
|
|
191
|
-
console.log(' The old recovery code is no longer valid.');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Simple ZIP builder for CLI usage
|
|
195
|
-
function u16(n) {
|
|
196
|
-
const b = Buffer.alloc(2);
|
|
197
|
-
b.writeUInt16LE(n);
|
|
198
|
-
return b;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function u32(n) {
|
|
202
|
-
const b = Buffer.alloc(4);
|
|
203
|
-
b.writeUInt32LE(n);
|
|
204
|
-
return b;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function buildZip(entries) {
|
|
208
|
-
const localParts = [];
|
|
209
|
-
const centralParts = [];
|
|
210
|
-
let offset = 0;
|
|
211
|
-
|
|
212
|
-
for (const [name, value] of Object.entries(entries)) {
|
|
213
|
-
const nameBuf = Buffer.from(name);
|
|
214
|
-
const data = Buffer.from(value);
|
|
215
|
-
const local = Buffer.concat([
|
|
216
|
-
u32(0x04034b50), u16(20), u16(0), u16(0), u16(0), u16(0),
|
|
217
|
-
u32(0), u32(data.length), u32(data.length), u16(nameBuf.length), u16(0),
|
|
218
|
-
nameBuf, data,
|
|
219
|
-
]);
|
|
220
|
-
localParts.push(local);
|
|
221
|
-
|
|
222
|
-
centralParts.push(
|
|
223
|
-
Buffer.concat([
|
|
224
|
-
u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(0), u16(0),
|
|
225
|
-
u32(0), u32(data.length), u32(data.length), u16(nameBuf.length), u16(0),
|
|
226
|
-
u16(0), u16(0), u16(0), u32(0), u32(offset), nameBuf,
|
|
227
|
-
]),
|
|
228
|
-
);
|
|
229
|
-
offset += local.length;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const central = Buffer.concat(centralParts);
|
|
233
|
-
const local = Buffer.concat(localParts);
|
|
234
|
-
const eocd = Buffer.concat([
|
|
235
|
-
u32(0x06054b50), u16(0), u16(0), u16(centralParts.length), u16(centralParts.length),
|
|
236
|
-
u32(central.length), u32(local.length), u16(0),
|
|
237
|
-
]);
|
|
238
|
-
return Buffer.concat([local, central, eocd]);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
module.exports = {
|
|
242
|
-
cmdProtect,
|
|
243
|
-
cmdUnlock,
|
|
244
|
-
cmdRecover,
|
|
245
|
-
};
|