@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 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
+ }