@kitn.ai/cli 0.1.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 +58 -0
- package/bin/kai.js +160 -0
- package/bin/route.js +61 -0
- package/dist/assets/dev-C27Rnkul.js +668 -0
- package/dist/builder-page/assets/index-2uphF31h.js +72 -0
- package/dist/builder-page/assets/index-CMlatin-.css +1 -0
- package/dist/builder-page/index.html +20 -0
- package/dist/construct-cli.es.js +3128 -0
- package/dist/doctor.es.js +193 -0
- package/dist/theme-studio/assets/index-C4nWp3LE.js +538 -0
- package/dist/theme-studio/assets/index-CAlkHn6J.css +1 -0
- package/dist/theme-studio/index.html +17 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @kitn.ai/cli
|
|
2
|
+
|
|
3
|
+
The `kai` command line for [`@kitn.ai/ui`](https://www.npmjs.com/package/@kitn.ai/ui): scaffold a
|
|
4
|
+
project or add a block to one, diagnose its kit wiring, and run the construct tooling.
|
|
5
|
+
|
|
6
|
+
It is optional. `npm install @kitn.ai/ui` and importing components needs no command line at all;
|
|
7
|
+
this is the tool you install when you want the convenience.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @kitn.ai/cli # kai ... anywhere, like any other CLI
|
|
13
|
+
npm i -D @kitn.ai/cli # pinned per project: npx kai ...
|
|
14
|
+
npx -y @kitn.ai/cli add support-widget # no install at all
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Verbs
|
|
18
|
+
|
|
19
|
+
| verb | what it does |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `kai create [dir]` | the scaffolder wizard (the same one `npm create kai` runs) |
|
|
22
|
+
| `kai add <block>` | writes a block from the registry into an existing project |
|
|
23
|
+
| `kai add --list` | prints the blocks this release ships |
|
|
24
|
+
| `kai doctor` | diagnoses this project's kit wiring, versions and registration |
|
|
25
|
+
| `kai mcp` | runs the MCP server for an AI coding harness, if that package is installed |
|
|
26
|
+
| `kai dev <construct.json>` | live preview with reload-on-edit |
|
|
27
|
+
| `kai dev --builder` | the visual builder plus live preview |
|
|
28
|
+
| `kai compile <construct.json> [outDir]` | one self-registering `.js` |
|
|
29
|
+
| `kai eject <construct.json> <outDir>` | writes the generated Solid project out; the source is yours |
|
|
30
|
+
| `kai validate <construct.json>` | checks a construct and prints problems with paths |
|
|
31
|
+
|
|
32
|
+
## doctor
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
kai doctor # human-readable
|
|
36
|
+
kai doctor --json # the findings, for a CI job or an agent
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It reports the CLI version and the kit it was built against, the kit range this project declares
|
|
40
|
+
versus the version actually installed, whether `kai.json` is present, whether anything under `src/`
|
|
41
|
+
references the kit, whether a kit stylesheet is referenced, and whether the MCP package is
|
|
42
|
+
installed. It exits non-zero only for a real problem; "no `kai.json`" is information, because a
|
|
43
|
+
hand-built project is not broken.
|
|
44
|
+
|
|
45
|
+
## How the forwards work
|
|
46
|
+
|
|
47
|
+
`create` and `add` are implemented by [`create-kai`](https://www.npmjs.com/package/create-kai), the
|
|
48
|
+
package `npm create kai` resolves — so there is one implementation, and `kai add` and
|
|
49
|
+
`npx create-kai add` cannot drift. `kai mcp` forwards to
|
|
50
|
+
[`@kitn.ai/mcp`](https://www.npmjs.com/package/@kitn.ai/mcp) when it is installed, and otherwise
|
|
51
|
+
says so and names `npx -y @kitn.ai/mcp`. That split is deliberate: the MCP is the only piece that
|
|
52
|
+
needs the MCP SDK, and installing this CLI should not download it.
|
|
53
|
+
|
|
54
|
+
## Docs
|
|
55
|
+
|
|
56
|
+
- [Getting started](https://ui.kitn.ai/guides/getting-started/) — scaffold your first project
|
|
57
|
+
- [For AI agents](https://ui.kitn.ai/guides/for-ai-agents/) — wiring the MCP into a harness (Claude Code, Codex, VS Code, Copilot, Cursor, Windsurf, Cline, Zed, Gemini CLI, OpenCode, dsh, Hermes; and what to do on Pi, which has no MCP in its core)
|
|
58
|
+
- [Blocks](https://ui.kitn.ai/blocks/) — what `kai add` can write
|
package/bin/kai.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `kai` launcher: the one command for @kitn.ai/ui's dev tooling.
|
|
3
|
+
//
|
|
4
|
+
// THREE KINDS OF VERB, and the distinction is the design (see bin/route.js):
|
|
5
|
+
//
|
|
6
|
+
// local dev, compile, eject, validate (the construct engine) and doctor. Their
|
|
7
|
+
// bundles are in THIS package's dist/, built by config/vite/node.ts.
|
|
8
|
+
// forward create and add are `create-kai`'s, and mcp is `@kitn.ai/mcp`'s. Both are
|
|
9
|
+
// separate published programs reached by resolving their bin and spawning it
|
|
10
|
+
// with this process's stdio, so the prompt, the TTY and the exit code behave
|
|
11
|
+
// exactly as if the user had run that program directly.
|
|
12
|
+
// error anything else, loudly, with the valid command list.
|
|
13
|
+
//
|
|
14
|
+
// WHY THE FORWARDS ARE NOT BUNDLED HERE. Measured: `@modelcontextprotocol/sdk` is 5.9 MB
|
|
15
|
+
// installed with 17 direct dependencies, and `dist/construct-cli.es.js` does not import the
|
|
16
|
+
// kit or the SDK at runtime at all. Bundling the MCP into this package would put that tree
|
|
17
|
+
// into the install of everyone who only wants `kai add`. And create-kai is the package
|
|
18
|
+
// npm's own `create` convention resolves for `npm create kai`, so its implementation has to
|
|
19
|
+
// stay there; forwarding keeps one implementation rather than two.
|
|
20
|
+
//
|
|
21
|
+
// A missing forward target is a LOUD failure with the one-line fix, never a silent no-op:
|
|
22
|
+
// a harness pointed at `kai mcp` in a project where @kitn.ai/mcp is not installed must not
|
|
23
|
+
// look like a server that started and then said nothing.
|
|
24
|
+
import { spawn } from 'node:child_process';
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
import { createRequire } from 'node:module';
|
|
27
|
+
import { dirname, join } from 'node:path';
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
29
|
+
import { decideEntry, KNOWN_COMMANDS } from './route.js';
|
|
30
|
+
|
|
31
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const PKG = join(HERE, '..');
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
|
|
35
|
+
const HELP = `kai — the @kitn.ai/ui command line
|
|
36
|
+
|
|
37
|
+
Usage
|
|
38
|
+
kai create [dir] scaffold a project (the same wizard as \`npm create kai\`)
|
|
39
|
+
kai add <block> write a block from the registry into an existing project
|
|
40
|
+
kai add --list print the blocks this release ships
|
|
41
|
+
|
|
42
|
+
kai doctor diagnose this project's kit wiring, versions and registration
|
|
43
|
+
|
|
44
|
+
kai mcp run the MCP server for AI coding harnesses (@kitn.ai/mcp)
|
|
45
|
+
kai dev <construct.json> live preview with reload-on-edit
|
|
46
|
+
kai dev --builder visual builder + live preview
|
|
47
|
+
kai compile <c.json> [out] one self-registering .js
|
|
48
|
+
kai eject <c.json> <outDir> write the generated Solid project (it's yours)
|
|
49
|
+
kai validate <c.json> check a construct, print problems with paths
|
|
50
|
+
|
|
51
|
+
Options
|
|
52
|
+
-h, --help this
|
|
53
|
+
-v, --version print this CLI's version
|
|
54
|
+
|
|
55
|
+
Installing
|
|
56
|
+
npm create kai no install: npm resolves the create-kai package
|
|
57
|
+
npx -y @kitn.ai/mcp the MCP server alone, for an MCP client config
|
|
58
|
+
npm i -g @kitn.ai/cli kai on your PATH, for every project
|
|
59
|
+
npm i -D @kitn.ai/cli pinned per project (npx kai ...)
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
/** This package's own version, read from the manifest that ships beside this file. */
|
|
63
|
+
function cliVersion() {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(readFileSync(join(PKG, 'package.json'), 'utf8')).version ?? 'unknown';
|
|
66
|
+
} catch {
|
|
67
|
+
return 'unknown';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve another package's bin from its manifest and run it with this process's stdio.
|
|
73
|
+
*
|
|
74
|
+
* The bin is looked up through the PACKAGE (its `bin` field), not by path arithmetic into
|
|
75
|
+
* that package's internals: `bin` is the public contract, and a package that renames its
|
|
76
|
+
* dist file must not break this.
|
|
77
|
+
*/
|
|
78
|
+
function spawnBin(specifier, args, verb) {
|
|
79
|
+
let manifestPath;
|
|
80
|
+
try {
|
|
81
|
+
manifestPath = require.resolve(`${specifier}/package.json`);
|
|
82
|
+
} catch {
|
|
83
|
+
console.error(`[kai] ${verb}: ${specifier} is not installed, and it implements this verb.`);
|
|
84
|
+
console.error(`[kai] install it once: npm i -g ${specifier}`);
|
|
85
|
+
console.error(`[kai] or run it without installing: npx -y ${specifier}${verb === 'mcp' ? '' : ` ${verb}`}`);
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
89
|
+
const binField = manifest.bin;
|
|
90
|
+
const relative =
|
|
91
|
+
typeof binField === 'string' ? binField : Object.values(binField ?? {})[0];
|
|
92
|
+
if (!relative || typeof relative !== 'string') {
|
|
93
|
+
console.error(`[kai] ${verb}: ${specifier} declares no bin, so there is nothing to run.`);
|
|
94
|
+
process.exit(2);
|
|
95
|
+
}
|
|
96
|
+
const bin = join(dirname(manifestPath), relative);
|
|
97
|
+
const child = spawn(process.execPath, [bin, ...args], { stdio: 'inherit' });
|
|
98
|
+
child.on('error', (err) => {
|
|
99
|
+
console.error(`[kai] ${verb}: could not start ${specifier}:`, err);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
});
|
|
102
|
+
child.on('exit', (code, signal) => {
|
|
103
|
+
process.exit(signal ? 1 : (code ?? 0));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Import a bundle this package built. `doctor` is called with its flags; the construct CLI
|
|
108
|
+
* parses `process.argv` itself and sets `process.exitCode`, exactly as it did under its old bin. */
|
|
109
|
+
async function runLocal(verb, args) {
|
|
110
|
+
if (verb === 'doctor') {
|
|
111
|
+
const bundle = pathToFileURL(join(PKG, 'dist', 'doctor.es.js')).href;
|
|
112
|
+
try {
|
|
113
|
+
const mod = await import(bundle);
|
|
114
|
+
return (await mod.runDoctor(args)) ?? 0;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('[kai] doctor: could not load dist/doctor.es.js. If you are running from a checkout, build first: npm run build');
|
|
117
|
+
console.error(err);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const bundle = pathToFileURL(join(PKG, 'dist', 'construct-cli.es.js')).href;
|
|
122
|
+
try {
|
|
123
|
+
await import(bundle);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(`[kai] ${verb}: could not load dist/construct-cli.es.js. If you are running from a checkout, build first: npm run build`);
|
|
126
|
+
console.error(err);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const [, , command, ...rest] = process.argv;
|
|
133
|
+
const decision = decideEntry(command, rest);
|
|
134
|
+
|
|
135
|
+
switch (decision.kind) {
|
|
136
|
+
case 'help':
|
|
137
|
+
console.log(HELP);
|
|
138
|
+
break;
|
|
139
|
+
case 'version':
|
|
140
|
+
console.log(cliVersion());
|
|
141
|
+
break;
|
|
142
|
+
case 'error':
|
|
143
|
+
console.error(`[kai] ${decision.message}`);
|
|
144
|
+
console.error(`[kai] run \`kai --help\` for the full surface.`);
|
|
145
|
+
process.exitCode = 2;
|
|
146
|
+
break;
|
|
147
|
+
case 'forward':
|
|
148
|
+
spawnBin(decision.pkg, decision.args, command);
|
|
149
|
+
break;
|
|
150
|
+
case 'local': {
|
|
151
|
+
// `doctor`'s own flags start after the verb; the construct CLI parses argv itself, so it
|
|
152
|
+
// gets nothing from here.
|
|
153
|
+
const code = await runLocal(decision.verb, decision.verb === 'doctor' ? rest : []);
|
|
154
|
+
if (typeof code === 'number') process.exitCode = code;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
default:
|
|
158
|
+
console.error(`[kai] ${KNOWN_COMMANDS.length} verbs are defined but this one is not routed: ${decision.kind}`);
|
|
159
|
+
process.exitCode = 2;
|
|
160
|
+
}
|
package/bin/route.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Pure command routing for the `kai` bin, extracted so it can be tested without
|
|
2
|
+
// spawning a process or reading the filesystem. `bin/kai.js` does the actual work
|
|
3
|
+
// based on what this returns.
|
|
4
|
+
//
|
|
5
|
+
// THREE KINDS OF VERB, and the distinction is the whole design:
|
|
6
|
+
//
|
|
7
|
+
// 'local' -- a bundle inside THIS package. dev/compile/eject/validate are the
|
|
8
|
+
// construct engine; doctor is the wiring diagnosis.
|
|
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 wizard and block registry (the same implementation
|
|
12
|
+
// `npm create kai` runs), and mcp is `@kitn.ai/mcp`'s server. They are
|
|
13
|
+
// not bundled into this package because neither belongs to its install
|
|
14
|
+
// weight: create-kai is the scaffolder npm's own `create` convention
|
|
15
|
+
// reaches, and the MCP is the only thing carrying the 5.9 MB SDK.
|
|
16
|
+
// 'error' -- anything else. A typo must never fall through to a server or a verb
|
|
17
|
+
// that does the wrong thing; it names the valid commands instead.
|
|
18
|
+
//
|
|
19
|
+
// `mcp` stays a verb even though the server is its own package, so `kai --help` remains
|
|
20
|
+
// the complete surface and a harness config can still say `kai mcp` once both packages
|
|
21
|
+
// are installed. When @kitn.ai/mcp is absent the forwarder says so and names the one-line
|
|
22
|
+
// fix rather than failing obscurely.
|
|
23
|
+
|
|
24
|
+
export const CONSTRUCT_COMMANDS = ['dev', 'compile', 'eject', 'validate'];
|
|
25
|
+
|
|
26
|
+
/** Verbs a human types, whether or not the target package is installed. */
|
|
27
|
+
export const KNOWN_COMMANDS = [
|
|
28
|
+
'create',
|
|
29
|
+
'add',
|
|
30
|
+
'doctor',
|
|
31
|
+
'mcp',
|
|
32
|
+
...CONSTRUCT_COMMANDS,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string | undefined} command
|
|
37
|
+
* @returns {{ kind: 'local', verb: string }
|
|
38
|
+
* | { kind: 'forward', pkg: string, args: string[] }
|
|
39
|
+
* | { kind: 'help' }
|
|
40
|
+
* | { kind: 'version' }
|
|
41
|
+
* | { kind: 'error', message: string }}
|
|
42
|
+
*/
|
|
43
|
+
export function decideEntry(command, rest = []) {
|
|
44
|
+
if (command === undefined || command === 'help' || command === '--help' || command === '-h') {
|
|
45
|
+
return { kind: 'help' };
|
|
46
|
+
}
|
|
47
|
+
if (command === 'version' || command === '--version' || command === '-v') {
|
|
48
|
+
return { kind: 'version' };
|
|
49
|
+
}
|
|
50
|
+
// `kai create [dir]` and `kai add <block>` are create-kai's verbs, unchanged: the
|
|
51
|
+
// wizard is the from-scratch door, `add` the into-an-existing-project door.
|
|
52
|
+
if (command === 'create') return { kind: 'forward', pkg: 'create-kai', args: rest };
|
|
53
|
+
if (command === 'add') return { kind: 'forward', pkg: 'create-kai', args: ['add', ...rest] };
|
|
54
|
+
if (command === 'mcp') return { kind: 'forward', pkg: '@kitn.ai/mcp', args: [] };
|
|
55
|
+
if (command === 'doctor') return { kind: 'local', verb: 'doctor' };
|
|
56
|
+
if (CONSTRUCT_COMMANDS.includes(command)) return { kind: 'local', verb: 'construct' };
|
|
57
|
+
return {
|
|
58
|
+
kind: 'error',
|
|
59
|
+
message: `unknown command "${command}" — valid commands: ${KNOWN_COMMANDS.join(', ')}`,
|
|
60
|
+
};
|
|
61
|
+
}
|