@kitn.ai/cli 0.3.0 → 0.4.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 +16 -0
- package/bin/kai.js +2 -0
- package/bin/route.js +10 -3
- package/dist/doctor.es.js +50 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ npx -y @kitn.ai/cli add support-widget # no install at all
|
|
|
21
21
|
| `kai create [dir]` | the scaffolder wizard (the same one `npm create kai` runs) |
|
|
22
22
|
| `kai add <block>` | writes a block from the registry into an existing project |
|
|
23
23
|
| `kai add --list` | prints the blocks this release ships |
|
|
24
|
+
| `kai init [--form <id>]` | makes an EXISTING project kai-aware: adds the kit at this CLI's pin and prints the wiring that framework needs |
|
|
25
|
+
| `kai upgrade [--write]` | brings a SCAFFOLDED project up to the template this CLI emits: it replaces the files you never touched and reports the ones you edited. `--strict` exits non-zero on drift |
|
|
24
26
|
| `kai doctor` | diagnoses this project's kit wiring, versions and registration |
|
|
25
27
|
| `kai mcp` | runs the MCP server for an AI coding harness, if that package is installed |
|
|
26
28
|
| `kai dev <construct.json>` | live preview with reload-on-edit |
|
|
@@ -29,6 +31,20 @@ npx -y @kitn.ai/cli add support-widget # no install at all
|
|
|
29
31
|
| `kai eject <construct.json> <outDir>` | writes the generated Solid project out; the source is yours |
|
|
30
32
|
| `kai validate <construct.json>` | checks a construct and prints problems with paths |
|
|
31
33
|
|
|
34
|
+
## upgrade
|
|
35
|
+
|
|
36
|
+
A project made with `npm create kai` is a copy of a template, and the templates move. `kai upgrade` brings that copy up to what the current CLI emits and **never overwrites something you wrote**:
|
|
37
|
+
|
|
38
|
+
| | verdict | `--write` |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `^` | outdated: untouched since you scaffolded it, so the template moved | replaces it |
|
|
41
|
+
| `+` | missing: the template emits it and you do not have it | adds it |
|
|
42
|
+
| `!` | edited: you changed it | nothing, ever |
|
|
43
|
+
| `?` | unknown: it differs, and there is no baseline to say whose change it is | nothing |
|
|
44
|
+
| `=` | same: already current | nothing |
|
|
45
|
+
|
|
46
|
+
`kai.json` records a sha256 of every file the scaffolder wrote, which is what makes that distinction possible. A project scaffolded before that was recorded has no baseline, so `upgrade` reports the drift and refuses to write. It renders into a temp directory with the same code the scaffolder runs, it deletes nothing, and `--strict` makes drift exit non-zero for CI. `doctor` reads the same recorded hashes and reports how far your copy has moved, without rendering anything.
|
|
47
|
+
|
|
32
48
|
## doctor
|
|
33
49
|
|
|
34
50
|
```bash
|
package/bin/kai.js
CHANGED
|
@@ -38,6 +38,8 @@ Usage
|
|
|
38
38
|
kai create [dir] scaffold a project (the same wizard as \`npm create kai\`)
|
|
39
39
|
kai add <block> write a block from the registry into an existing project
|
|
40
40
|
kai add --list print the blocks this release ships
|
|
41
|
+
kai init [--form <id>] make an EXISTING project kai-aware: add the kit and print the wiring
|
|
42
|
+
kai upgrade [--write] bring a SCAFFOLDED project up to this CLI's template (never your edits)
|
|
41
43
|
|
|
42
44
|
kai doctor diagnose this project's kit wiring, versions and registration
|
|
43
45
|
kai doctor --strict the same, but warnings fail the run (for CI)
|
package/bin/route.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
// 'local' -- a bundle inside THIS package. dev/compile/eject/validate are the
|
|
8
8
|
// construct engine; doctor is the wiring diagnosis.
|
|
9
9
|
// 'forward' -- a SEPARATE published program, launched by resolving that package's
|
|
10
|
-
// bin and spawning it with this process's stdio. create/add are
|
|
11
|
-
// `create-kai`'s
|
|
12
|
-
//
|
|
10
|
+
// bin and spawning it with this process's stdio. create/add/init are
|
|
11
|
+
// `create-kai`'s (the same implementation `npm create kai` runs), and
|
|
12
|
+
// mcp is `@kitn.ai/mcp`'s server. They are
|
|
13
13
|
// not bundled into this package because neither belongs to its install
|
|
14
14
|
// weight: create-kai is the scaffolder npm's own `create` convention
|
|
15
15
|
// reaches, and the MCP is the only thing carrying the 5.9 MB SDK.
|
|
@@ -27,6 +27,8 @@ export const CONSTRUCT_COMMANDS = ['dev', 'compile', 'eject', 'validate'];
|
|
|
27
27
|
export const KNOWN_COMMANDS = [
|
|
28
28
|
'create',
|
|
29
29
|
'add',
|
|
30
|
+
'init',
|
|
31
|
+
'upgrade',
|
|
30
32
|
'doctor',
|
|
31
33
|
'mcp',
|
|
32
34
|
...CONSTRUCT_COMMANDS,
|
|
@@ -51,6 +53,11 @@ export function decideEntry(command, rest = []) {
|
|
|
51
53
|
// wizard is the from-scratch door, `add` the into-an-existing-project door.
|
|
52
54
|
if (command === 'create') return { kind: 'forward', pkg: 'create-kai', args: rest };
|
|
53
55
|
if (command === 'add') return { kind: 'forward', pkg: 'create-kai', args: ['add', ...rest] };
|
|
56
|
+
// `init` makes an EXISTING project kai-aware: it merges the dependency and prints the wiring.
|
|
57
|
+
if (command === 'init') return { kind: 'forward', pkg: 'create-kai', args: ['init', ...rest] };
|
|
58
|
+
// `upgrade` re-diffs a scaffolded project against the template this CLI emits; it replaces the
|
|
59
|
+
// files the user never touched and reports the ones they did.
|
|
60
|
+
if (command === 'upgrade') return { kind: 'forward', pkg: 'create-kai', args: ['upgrade', ...rest] };
|
|
54
61
|
if (command === 'mcp') return { kind: 'forward', pkg: '@kitn.ai/mcp', args: [] };
|
|
55
62
|
if (command === 'doctor') return { kind: 'local', verb: 'doctor' };
|
|
56
63
|
if (CONSTRUCT_COMMANDS.includes(command)) return { kind: 'local', verb: 'construct' };
|
package/dist/doctor.es.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync, statSync, readdirSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { join, relative } from "node:path";
|
|
3
4
|
const RULES = [
|
|
4
5
|
{
|
|
@@ -239,6 +240,23 @@ function sourceFiles(dir, limit = 400) {
|
|
|
239
240
|
if (statSync(dir, { throwIfNoEntry: false })?.isDirectory()) walk(dir);
|
|
240
241
|
return out;
|
|
241
242
|
}
|
|
243
|
+
function baselineDrift(cwd, files) {
|
|
244
|
+
const changed = [];
|
|
245
|
+
const gone = [];
|
|
246
|
+
let same = 0;
|
|
247
|
+
for (const [file, recorded] of Object.entries(files)) {
|
|
248
|
+
let text;
|
|
249
|
+
try {
|
|
250
|
+
text = readFileSync(join(cwd, file), "utf8");
|
|
251
|
+
} catch {
|
|
252
|
+
gone.push(file);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (createHash("sha256").update(text, "utf8").digest("hex") === recorded) same += 1;
|
|
256
|
+
else changed.push(file);
|
|
257
|
+
}
|
|
258
|
+
return { changed: changed.sort(), gone: gone.sort(), same };
|
|
259
|
+
}
|
|
242
260
|
const readAll = (files) => files.flatMap((file) => {
|
|
243
261
|
try {
|
|
244
262
|
return [{ file, text: readFileSync(file, "utf8") }];
|
|
@@ -305,7 +323,38 @@ function diagnose(input) {
|
|
|
305
323
|
const framework = kaiJson.framework ?? "?";
|
|
306
324
|
const built = kaiJson.kitBuiltAgainst ?? "?";
|
|
307
325
|
const features = Array.isArray(kaiJson.features) ? kaiJson.features.join(", ") : "?";
|
|
308
|
-
findings.push({
|
|
326
|
+
findings.push({
|
|
327
|
+
severity: "ok",
|
|
328
|
+
title: `${KAI_JSON}: framework ${framework}, features ${features}`,
|
|
329
|
+
detail: `scaffolded against kit ${built}`
|
|
330
|
+
});
|
|
331
|
+
const baseline = kaiJson.files;
|
|
332
|
+
if (baseline !== null && typeof baseline === "object" && Object.keys(baseline).length > 0) {
|
|
333
|
+
const files = baseline;
|
|
334
|
+
const { changed, gone, same } = baselineDrift(input.cwd, files);
|
|
335
|
+
if (changed.length === 0 && gone.length === 0) {
|
|
336
|
+
findings.push({
|
|
337
|
+
severity: "ok",
|
|
338
|
+
title: `${KAI_JSON}'s baseline: all ${same} scaffolded file(s) are exactly as written`
|
|
339
|
+
});
|
|
340
|
+
} else {
|
|
341
|
+
const names = [...changed, ...gone];
|
|
342
|
+
const shown = names.slice(0, 3).join(", ");
|
|
343
|
+
const more = names.length > 3 ? ` (and ${names.length - 3} more)` : "";
|
|
344
|
+
findings.push({
|
|
345
|
+
severity: "info",
|
|
346
|
+
title: `${KAI_JSON}'s baseline: ${same} of ${Object.keys(files).length} scaffolded file(s) are as written, ${changed.length} changed, ${gone.length} gone`,
|
|
347
|
+
detail: `${shown}${more}
|
|
348
|
+
Run \`kai upgrade\` to see what the template this CLI emits would change (it replaces only the files you never touched), or \`kai upgrade --strict\` in CI.`
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
findings.push({
|
|
353
|
+
severity: "info",
|
|
354
|
+
title: `${KAI_JSON} has no baseline`,
|
|
355
|
+
detail: "it predates the recorded hashes, so this cannot tell your edits from a template change. `kai upgrade` still diffs the project against the template this CLI emits, and will not write without a baseline."
|
|
356
|
+
});
|
|
357
|
+
}
|
|
309
358
|
} else {
|
|
310
359
|
findings.push({
|
|
311
360
|
severity: "info",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitn.ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The kai command line for @kitn.ai/ui: scaffold a project or add a block to one, diagnose its wiring, and run the construct dev/eject/compile tooling.",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"lint:cli-invocations": "node scripts/lint-cli-invocations.mjs --self-test && node scripts/lint-cli-invocations.mjs"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"create-kai": "^0.
|
|
59
|
+
"create-kai": "^0.8.0",
|
|
60
60
|
"zod": "^4.4.3"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|