kadai 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 +18 -9
- package/dist/cli.js +143 -12
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<img width="
|
|
1
|
+
<img width="919" height="228" alt="image" src="https://github.com/user-attachments/assets/2c1fca9f-3484-409f-b3b4-214edc0387a4" />
|
|
2
2
|
|
|
3
3
|
# kadai
|
|
4
4
|
|
|
@@ -6,8 +6,6 @@ A terminal UI for discovering and running project scripts. Drop scripts into `.k
|
|
|
6
6
|
|
|
7
7
|
## Getting Started
|
|
8
8
|
|
|
9
|
-
<img width="950" height="205" alt="image" src="https://github.com/user-attachments/assets/b694bfaa-146b-41c7-a44c-d197c7cea08e" />
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
10
|
bunx kadai
|
|
13
11
|
# OR
|
|
@@ -70,11 +68,6 @@ For JS/TS, use `//` comments:
|
|
|
70
68
|
| `confirm` | boolean | Require confirmation before running |
|
|
71
69
|
| `hidden` | boolean | Hide from menu (still runnable via CLI) |
|
|
72
70
|
| `fullscreen` | boolean | Use alternate screen buffer (`.tsx` only) |
|
|
73
|
-
| `interactive` | boolean | Hand over the full terminal to the script |
|
|
74
|
-
|
|
75
|
-
### Interactive Scripts
|
|
76
|
-
|
|
77
|
-
Scripts marked `interactive` get full terminal control — kadai exits its UI, runs the script with inherited stdio, then returns to the menu. Use this for scripts that need user input (readline prompts, password entry, etc.).
|
|
78
71
|
|
|
79
72
|
### Ink TUI Actions
|
|
80
73
|
|
|
@@ -148,13 +141,29 @@ kadai # Interactive menu
|
|
|
148
141
|
kadai list --json # List actions as JSON
|
|
149
142
|
kadai list --json --all # Include hidden actions
|
|
150
143
|
kadai run <action-id> # Run an action directly
|
|
144
|
+
kadai mcp # Start MCP server (creates .mcp.json)
|
|
151
145
|
```
|
|
152
146
|
|
|
153
147
|
## AI
|
|
154
148
|
|
|
155
149
|
kadai is designed to work well with AI coding agents like Claude Code.
|
|
156
150
|
|
|
157
|
-
###
|
|
151
|
+
### MCP Server
|
|
152
|
+
|
|
153
|
+
kadai includes a built-in [MCP](https://modelcontextprotocol.io/) server that exposes your actions as tools. Any MCP-compatible client (Claude Code, Claude Desktop, etc.) can auto-discover and run your project's actions.
|
|
154
|
+
|
|
155
|
+
kadai will automatically configure a `.mcp.json` file in your project root so Claude can automatically discover any `kadai` actions you define.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
kadai mcp
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This creates the `.mcp.json` in your project root if it doesn't already exist (so `claude` will autodiscover it.)
|
|
162
|
+
It then starts the `mcp` server (this is the command `claude` uses to invoke `kadai` MCP.)
|
|
163
|
+
|
|
164
|
+
Each action becomes an MCP tool. Nested action IDs use `--` as a separator (e.g. `database/reset` becomes the tool `database--reset`) since MCP tool names don't allow slashes.
|
|
165
|
+
|
|
166
|
+
### JSON API
|
|
158
167
|
|
|
159
168
|
- `kadai list --json` gives agents a machine-readable list of available project actions
|
|
160
169
|
- `kadai run <action-id>` runs actions non-interactively (confirmation prompts auto-skip in non-TTY)
|
package/dist/cli.js
CHANGED
|
@@ -347,6 +347,127 @@ var init_runner = __esm(() => {
|
|
|
347
347
|
};
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
+
// src/core/mcp.ts
|
|
351
|
+
var exports_mcp = {};
|
|
352
|
+
__export(exports_mcp, {
|
|
353
|
+
toolNameToActionId: () => toolNameToActionId,
|
|
354
|
+
startMcpServer: () => startMcpServer,
|
|
355
|
+
ensureMcpConfig: () => ensureMcpConfig,
|
|
356
|
+
actionIdToToolName: () => actionIdToToolName
|
|
357
|
+
});
|
|
358
|
+
import { join as join4, resolve } from "path";
|
|
359
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
360
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
361
|
+
function resolveInvocationCommand() {
|
|
362
|
+
const script = process.argv[1] ?? "";
|
|
363
|
+
if (script.endsWith(".ts") || script.endsWith(".tsx")) {
|
|
364
|
+
return { command: "bun", args: [resolve(script), "mcp"] };
|
|
365
|
+
}
|
|
366
|
+
return { command: "bunx", args: ["kadai", "mcp"] };
|
|
367
|
+
}
|
|
368
|
+
function actionIdToToolName(id) {
|
|
369
|
+
return id.replace(/\//g, "--");
|
|
370
|
+
}
|
|
371
|
+
function toolNameToActionId(name) {
|
|
372
|
+
return name.replace(/--/g, "/");
|
|
373
|
+
}
|
|
374
|
+
function buildToolDescription(action) {
|
|
375
|
+
const parts = [];
|
|
376
|
+
if (action.meta.emoji)
|
|
377
|
+
parts.push(action.meta.emoji);
|
|
378
|
+
parts.push(action.meta.name);
|
|
379
|
+
if (action.meta.description) {
|
|
380
|
+
parts.push(`\u2014 ${action.meta.description}`);
|
|
381
|
+
}
|
|
382
|
+
return parts.join(" ");
|
|
383
|
+
}
|
|
384
|
+
async function ensureMcpConfig(projectRoot) {
|
|
385
|
+
const mcpJsonPath = join4(projectRoot, ".mcp.json");
|
|
386
|
+
const mcpFile = Bun.file(mcpJsonPath);
|
|
387
|
+
const kadaiEntry = resolveInvocationCommand();
|
|
388
|
+
if (await mcpFile.exists()) {
|
|
389
|
+
const existing = await mcpFile.json();
|
|
390
|
+
if (existing.mcpServers?.kadai) {
|
|
391
|
+
process.stderr.write(`kadai MCP server already configured in .mcp.json
|
|
392
|
+
`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
existing.mcpServers = existing.mcpServers ?? {};
|
|
396
|
+
existing.mcpServers.kadai = kadaiEntry;
|
|
397
|
+
await Bun.write(mcpJsonPath, `${JSON.stringify(existing, null, 2)}
|
|
398
|
+
`);
|
|
399
|
+
process.stderr.write(`Added kadai entry to existing .mcp.json
|
|
400
|
+
`);
|
|
401
|
+
} else {
|
|
402
|
+
const config = {
|
|
403
|
+
mcpServers: {
|
|
404
|
+
kadai: kadaiEntry
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
await Bun.write(mcpJsonPath, `${JSON.stringify(config, null, 2)}
|
|
408
|
+
`);
|
|
409
|
+
process.stderr.write(`Created .mcp.json with kadai MCP server config
|
|
410
|
+
`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function startMcpServer(kadaiDir, cwd) {
|
|
414
|
+
let visibleActions = [];
|
|
415
|
+
let config = {};
|
|
416
|
+
if (kadaiDir) {
|
|
417
|
+
config = await loadConfig(kadaiDir);
|
|
418
|
+
const actionsDir = join4(kadaiDir, config.actionsDir ?? "actions");
|
|
419
|
+
visibleActions = (await loadActions(actionsDir)).filter((a) => !a.meta.hidden);
|
|
420
|
+
}
|
|
421
|
+
const server = new McpServer({ name: "kadai", version: "0.3.0" });
|
|
422
|
+
const env = {
|
|
423
|
+
...process.env,
|
|
424
|
+
...config.env ?? {}
|
|
425
|
+
};
|
|
426
|
+
for (const action of visibleActions) {
|
|
427
|
+
const toolName = actionIdToToolName(action.id);
|
|
428
|
+
const description = buildToolDescription(action);
|
|
429
|
+
server.registerTool(toolName, { description }, async () => {
|
|
430
|
+
const cmd = resolveCommand(action);
|
|
431
|
+
const proc = Bun.spawn(cmd, {
|
|
432
|
+
cwd,
|
|
433
|
+
stdout: "pipe",
|
|
434
|
+
stderr: "pipe",
|
|
435
|
+
stdin: "ignore",
|
|
436
|
+
env
|
|
437
|
+
});
|
|
438
|
+
const [stdout, stderr] = await Promise.all([
|
|
439
|
+
new Response(proc.stdout).text(),
|
|
440
|
+
new Response(proc.stderr).text()
|
|
441
|
+
]);
|
|
442
|
+
const exitCode = await proc.exited;
|
|
443
|
+
const parts = [];
|
|
444
|
+
if (stdout)
|
|
445
|
+
parts.push(stdout);
|
|
446
|
+
if (stderr)
|
|
447
|
+
parts.push(`[stderr]
|
|
448
|
+
${stderr}`);
|
|
449
|
+
if (exitCode !== 0)
|
|
450
|
+
parts.push(`[exit code: ${exitCode}]`);
|
|
451
|
+
return {
|
|
452
|
+
content: [
|
|
453
|
+
{ type: "text", text: parts.join(`
|
|
454
|
+
`) || "(no output)" }
|
|
455
|
+
],
|
|
456
|
+
isError: exitCode !== 0
|
|
457
|
+
};
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
const transport = new StdioServerTransport;
|
|
461
|
+
await server.connect(transport);
|
|
462
|
+
process.stderr.write(`kadai MCP server running (${visibleActions.length} tools)
|
|
463
|
+
`);
|
|
464
|
+
}
|
|
465
|
+
var init_mcp = __esm(() => {
|
|
466
|
+
init_config();
|
|
467
|
+
init_loader();
|
|
468
|
+
init_runner();
|
|
469
|
+
});
|
|
470
|
+
|
|
350
471
|
// src/components/Breadcrumbs.tsx
|
|
351
472
|
import { Box, Text } from "ink";
|
|
352
473
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
@@ -508,7 +629,7 @@ function StatusBar() {
|
|
|
508
629
|
var init_StatusBar = () => {};
|
|
509
630
|
|
|
510
631
|
// src/hooks/useActions.ts
|
|
511
|
-
import { join as
|
|
632
|
+
import { join as join5 } from "path";
|
|
512
633
|
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
513
634
|
function useActions({ kadaiDir }) {
|
|
514
635
|
const [actions, setActions] = useState2([]);
|
|
@@ -520,7 +641,7 @@ function useActions({ kadaiDir }) {
|
|
|
520
641
|
(async () => {
|
|
521
642
|
const cfg = await loadConfig(kadaiDir);
|
|
522
643
|
setConfig(cfg);
|
|
523
|
-
const actionsDir =
|
|
644
|
+
const actionsDir = join5(kadaiDir, cfg.actionsDir ?? "actions");
|
|
524
645
|
const localActions = await loadActions(actionsDir);
|
|
525
646
|
setActions(localActions);
|
|
526
647
|
setLoading(false);
|
|
@@ -1785,7 +1906,7 @@ __export(exports_init_wizard, {
|
|
|
1785
1906
|
generateConfigFile: () => generateConfigFile
|
|
1786
1907
|
});
|
|
1787
1908
|
import { existsSync, mkdirSync } from "fs";
|
|
1788
|
-
import { join as
|
|
1909
|
+
import { join as join6 } from "path";
|
|
1789
1910
|
function generateConfigFile() {
|
|
1790
1911
|
const lines = [' // actionsDir: "actions",', " // env: {},"];
|
|
1791
1912
|
return `export default {
|
|
@@ -1795,10 +1916,10 @@ ${lines.join(`
|
|
|
1795
1916
|
`;
|
|
1796
1917
|
}
|
|
1797
1918
|
async function writeInitFiles(cwd) {
|
|
1798
|
-
const kadaiDir =
|
|
1799
|
-
const actionsDir =
|
|
1919
|
+
const kadaiDir = join6(cwd, ".kadai");
|
|
1920
|
+
const actionsDir = join6(kadaiDir, "actions");
|
|
1800
1921
|
mkdirSync(actionsDir, { recursive: true });
|
|
1801
|
-
const sampleAction =
|
|
1922
|
+
const sampleAction = join6(actionsDir, "hello.sh");
|
|
1802
1923
|
const sampleFile = Bun.file(sampleAction);
|
|
1803
1924
|
let sampleCreated = false;
|
|
1804
1925
|
if (!await sampleFile.exists()) {
|
|
@@ -1813,14 +1934,14 @@ echo "Add your own scripts to .kadai/actions/ to get started."
|
|
|
1813
1934
|
sampleCreated = true;
|
|
1814
1935
|
}
|
|
1815
1936
|
const configContent = generateConfigFile();
|
|
1816
|
-
const configPath =
|
|
1937
|
+
const configPath = join6(kadaiDir, "config.ts");
|
|
1817
1938
|
await Bun.write(configPath, configContent);
|
|
1818
1939
|
let skillCreated = false;
|
|
1819
|
-
const hasClaudeDir = existsSync(
|
|
1820
|
-
const hasClaudeMd = existsSync(
|
|
1940
|
+
const hasClaudeDir = existsSync(join6(cwd, ".claude"));
|
|
1941
|
+
const hasClaudeMd = existsSync(join6(cwd, "CLAUDE.md"));
|
|
1821
1942
|
if (hasClaudeDir || hasClaudeMd) {
|
|
1822
|
-
const skillDir =
|
|
1823
|
-
const skillPath =
|
|
1943
|
+
const skillDir = join6(cwd, ".claude", "skills", "kadai");
|
|
1944
|
+
const skillPath = join6(skillDir, "SKILL.md");
|
|
1824
1945
|
if (!await Bun.file(skillPath).exists()) {
|
|
1825
1946
|
mkdirSync(skillDir, { recursive: true });
|
|
1826
1947
|
await Bun.write(skillPath, generateSkillFile());
|
|
@@ -1950,10 +2071,12 @@ function parseArgs(argv) {
|
|
|
1950
2071
|
}
|
|
1951
2072
|
return { type: "run", actionId };
|
|
1952
2073
|
}
|
|
2074
|
+
case "mcp":
|
|
2075
|
+
return { type: "mcp" };
|
|
1953
2076
|
default:
|
|
1954
2077
|
return {
|
|
1955
2078
|
type: "error",
|
|
1956
|
-
message: `Unknown command: ${command}. Available commands: list, run`
|
|
2079
|
+
message: `Unknown command: ${command}. Available commands: list, run, mcp`
|
|
1957
2080
|
};
|
|
1958
2081
|
}
|
|
1959
2082
|
}
|
|
@@ -2039,6 +2162,14 @@ if (parsed.type === "error") {
|
|
|
2039
2162
|
`);
|
|
2040
2163
|
process.exit(1);
|
|
2041
2164
|
}
|
|
2165
|
+
if (parsed.type === "mcp") {
|
|
2166
|
+
const { ensureMcpConfig: ensureMcpConfig2, startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), exports_mcp));
|
|
2167
|
+
const kadaiDir = findZcliDir(cwd);
|
|
2168
|
+
const projectRoot = kadaiDir ? (await import("path")).dirname(kadaiDir) : cwd;
|
|
2169
|
+
await ensureMcpConfig2(projectRoot);
|
|
2170
|
+
await startMcpServer2(kadaiDir, cwd);
|
|
2171
|
+
await new Promise(() => {});
|
|
2172
|
+
}
|
|
2042
2173
|
if (parsed.type === "list" || parsed.type === "run") {
|
|
2043
2174
|
const kadaiDir = findZcliDir(cwd);
|
|
2044
2175
|
if (!kadaiDir) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kadai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kadai": "./dist/cli.js"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@inkjs/ui": "^2.0.0",
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
27
28
|
"@types/react": "^19.2.14",
|
|
28
29
|
"fuzzysort": "^3.1.0",
|
|
29
30
|
"ink": "^6.7.0",
|