kadai 0.3.0 → 0.4.1
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 +22 -10
- package/dist/cli.js +148 -23
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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
|
|
|
5
|
-
|
|
5
|
+
1. Drop scripts into `.kadai/actions/`.
|
|
6
|
+
2. Run with `bunx kadai`.
|
|
7
|
+
3. Share them with your team in the repo.
|
|
8
|
+
4. Automatically make them discoverable by AI.
|
|
6
9
|
|
|
7
10
|
## Getting Started
|
|
8
11
|
|
|
9
|
-
<img width="950" height="205" alt="image" src="https://github.com/user-attachments/assets/b694bfaa-146b-41c7-a44c-d197c7cea08e" />
|
|
10
|
-
|
|
11
12
|
```bash
|
|
12
13
|
bunx kadai
|
|
13
14
|
# OR
|
|
@@ -70,11 +71,6 @@ For JS/TS, use `//` comments:
|
|
|
70
71
|
| `confirm` | boolean | Require confirmation before running |
|
|
71
72
|
| `hidden` | boolean | Hide from menu (still runnable via CLI) |
|
|
72
73
|
| `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
74
|
|
|
79
75
|
### Ink TUI Actions
|
|
80
76
|
|
|
@@ -148,13 +144,29 @@ kadai # Interactive menu
|
|
|
148
144
|
kadai list --json # List actions as JSON
|
|
149
145
|
kadai list --json --all # Include hidden actions
|
|
150
146
|
kadai run <action-id> # Run an action directly
|
|
147
|
+
kadai mcp # Start MCP server (creates .mcp.json)
|
|
151
148
|
```
|
|
152
149
|
|
|
153
150
|
## AI
|
|
154
151
|
|
|
155
152
|
kadai is designed to work well with AI coding agents like Claude Code.
|
|
156
153
|
|
|
157
|
-
###
|
|
154
|
+
### MCP Server
|
|
155
|
+
|
|
156
|
+
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.
|
|
157
|
+
|
|
158
|
+
kadai will automatically configure a `.mcp.json` file in your project root so Claude can automatically discover any `kadai` actions you define.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
kadai mcp
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This creates the `.mcp.json` in your project root if it doesn't already exist (so `claude` will autodiscover it.)
|
|
165
|
+
It then starts the `mcp` server (this is the command `claude` uses to invoke `kadai` MCP.)
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
### JSON API
|
|
158
170
|
|
|
159
171
|
- `kadai list --json` gives agents a machine-readable list of available project actions
|
|
160
172
|
- `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) {
|
|
@@ -2168,22 +2299,16 @@ var env = {
|
|
|
2168
2299
|
console.log(`${action.meta.emoji ? `${action.meta.emoji} ` : ""}${action.meta.name}
|
|
2169
2300
|
`);
|
|
2170
2301
|
process.stdin.removeAllListeners("data");
|
|
2302
|
+
if (process.stdin.isTTY) {
|
|
2303
|
+
process.stdin.setRawMode(false);
|
|
2304
|
+
}
|
|
2305
|
+
process.stdin.resume();
|
|
2171
2306
|
var proc = Bun.spawn(cmd, {
|
|
2172
2307
|
cwd,
|
|
2173
2308
|
stdout: "inherit",
|
|
2174
2309
|
stderr: "inherit",
|
|
2175
|
-
stdin: "
|
|
2310
|
+
stdin: "inherit",
|
|
2176
2311
|
env
|
|
2177
2312
|
});
|
|
2178
|
-
var forwardStdin = (data) => {
|
|
2179
|
-
try {
|
|
2180
|
-
const converted = Buffer.from(data.toString().replace(/\r/g, `
|
|
2181
|
-
`));
|
|
2182
|
-
proc.stdin.write(converted);
|
|
2183
|
-
proc.stdin.flush();
|
|
2184
|
-
} catch {}
|
|
2185
|
-
};
|
|
2186
|
-
process.stdin.on("data", forwardStdin);
|
|
2187
|
-
process.stdin.resume();
|
|
2188
2313
|
var exitCode = await proc.exited;
|
|
2189
2314
|
process.exit(exitCode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kadai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|