kadai 0.5.0 → 0.7.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 CHANGED
@@ -7,12 +7,25 @@
7
7
  3. Share them with your team in the repo.
8
8
  4. Automatically make them discoverable by AI.
9
9
 
10
+ ## Prerequisites
11
+
12
+ kadai requires [Bun](https://bun.sh) as its runtime.
13
+
14
+ ```bash
15
+ # macOS / Linux
16
+ curl -fsSL https://bun.sh/install | bash
17
+
18
+ # Homebrew
19
+ brew install oven-sh/bun/bun
20
+
21
+ # Windows
22
+ powershell -c "irm bun.sh/install.ps1 | iex"
23
+ ```
24
+
10
25
  ## Getting Started
11
26
 
12
27
  ```bash
13
28
  bunx kadai
14
- # OR
15
- npx kadai
16
29
  ```
17
30
 
18
31
  On first run, kadai creates a `.kadai/` directory with a sample action and config file. Run it again to open the interactive menu.
package/dist/cli.js CHANGED
@@ -29,14 +29,67 @@ var __export = (target, all) => {
29
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
30
  var __require = import.meta.require;
31
31
 
32
+ // src/core/shared-deps.ts
33
+ import { existsSync, mkdirSync, symlinkSync, unlinkSync } from "fs";
34
+ import { dirname, join } from "path";
35
+ function registerSharedDeps() {
36
+ const escaped = SHARED_DEPS.map((d) => d.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
37
+ const filter = new RegExp(`^(${escaped.join("|")})(/.*)?$`);
38
+ Bun.plugin({
39
+ name: "kadai-shared-deps",
40
+ setup(build) {
41
+ build.onResolve({ filter }, (args) => {
42
+ try {
43
+ return { path: __require.resolve(args.path) };
44
+ } catch {
45
+ return;
46
+ }
47
+ });
48
+ }
49
+ });
50
+ }
51
+ function ensureKadaiResolvable(projectNodeModules) {
52
+ const link = join(projectNodeModules, "kadai");
53
+ if (existsSync(link))
54
+ return null;
55
+ const kadaiRoot = dirname(import.meta.dir);
56
+ if (!existsSync(join(kadaiRoot, "package.json")))
57
+ return null;
58
+ try {
59
+ if (!existsSync(projectNodeModules)) {
60
+ mkdirSync(projectNodeModules, { recursive: true });
61
+ }
62
+ symlinkSync(kadaiRoot, link);
63
+ } catch {
64
+ return null;
65
+ }
66
+ let cleaned = false;
67
+ const cleanup = () => {
68
+ if (cleaned)
69
+ return;
70
+ cleaned = true;
71
+ try {
72
+ unlinkSync(link);
73
+ } catch {}
74
+ };
75
+ process.on("exit", cleanup);
76
+ process.on("SIGTERM", cleanup);
77
+ process.on("SIGINT", cleanup);
78
+ return cleanup;
79
+ }
80
+ var SHARED_DEPS;
81
+ var init_shared_deps = __esm(() => {
82
+ SHARED_DEPS = ["ink", "react", "@inkjs/ui"];
83
+ });
84
+
32
85
  // src/core/config.ts
33
86
  var exports_config = {};
34
87
  __export(exports_config, {
35
88
  loadConfig: () => loadConfig
36
89
  });
37
- import { join } from "path";
90
+ import { join as join2 } from "path";
38
91
  async function loadConfig(kadaiDir) {
39
- const configPath = join(kadaiDir, "config.ts");
92
+ const configPath = join2(kadaiDir, "config.ts");
40
93
  const configFile = Bun.file(configPath);
41
94
  if (!await configFile.exists()) {
42
95
  return { ...DEFAULT_CONFIG };
@@ -150,7 +203,7 @@ var init_metadata = __esm(() => {
150
203
 
151
204
  // src/core/loader.ts
152
205
  import { readdir } from "fs/promises";
153
- import { join as join2 } from "path";
206
+ import { join as join3 } from "path";
154
207
  async function readShebang(filePath) {
155
208
  try {
156
209
  const head = await Bun.file(filePath).slice(0, 256).text();
@@ -192,7 +245,7 @@ async function getGitAddedDates(dir) {
192
245
  if (/^\d+$/.test(trimmed)) {
193
246
  currentTimestamp = Number.parseInt(trimmed, 10) * 1000;
194
247
  } else {
195
- const absPath = join2(repoRoot, trimmed);
248
+ const absPath = join3(repoRoot, trimmed);
196
249
  dates.set(absPath, currentTimestamp);
197
250
  }
198
251
  }
@@ -218,7 +271,7 @@ async function scanDirectory(baseDir, currentDir, category, actions, depth, gitD
218
271
  for (const entry of entries) {
219
272
  if (entry.name.startsWith("_") || entry.name.startsWith("."))
220
273
  continue;
221
- const fullPath = join2(currentDir, entry.name);
274
+ const fullPath = join3(currentDir, entry.name);
222
275
  if (entry.isDirectory()) {
223
276
  await scanDirectory(baseDir, fullPath, [...category, entry.name], actions, depth + 1, gitDates, origin);
224
277
  } else if (entry.isFile()) {
@@ -247,15 +300,15 @@ async function scanDirectory(baseDir, currentDir, category, actions, depth, gitD
247
300
  function findZcliDir(cwd) {
248
301
  let dir = cwd;
249
302
  while (true) {
250
- const candidate = join2(dir, ".kadai");
251
- if (Bun.file(join2(candidate, "actions")).name) {
303
+ const candidate = join3(dir, ".kadai");
304
+ if (Bun.file(join3(candidate, "actions")).name) {
252
305
  try {
253
306
  const stat = __require("fs").statSync(candidate);
254
307
  if (stat.isDirectory())
255
308
  return candidate;
256
309
  } catch {}
257
310
  }
258
- const parent = join2(dir, "..");
311
+ const parent = join3(dir, "..");
259
312
  if (parent === dir)
260
313
  break;
261
314
  dir = parent;
@@ -276,9 +329,44 @@ var init_loader = __esm(() => {
276
329
  ]);
277
330
  });
278
331
 
332
+ // src/core/last-action.ts
333
+ import { join as join4 } from "path";
334
+ async function saveLastAction(kadaiDir, actionId) {
335
+ await Bun.write(join4(kadaiDir, LAST_ACTION_FILE), actionId);
336
+ await ensureGitignore(kadaiDir);
337
+ }
338
+ async function ensureGitignore(kadaiDir) {
339
+ const gitignorePath = join4(kadaiDir, ".gitignore");
340
+ const file = Bun.file(gitignorePath);
341
+ if (await file.exists()) {
342
+ const content = await file.text();
343
+ const lines = content.split(`
344
+ `).map((l) => l.trim());
345
+ if (!lines.includes(LAST_ACTION_FILE)) {
346
+ const suffix = content.endsWith(`
347
+ `) ? "" : `
348
+ `;
349
+ await Bun.write(gitignorePath, `${content}${suffix}${LAST_ACTION_FILE}
350
+ `);
351
+ }
352
+ } else {
353
+ await Bun.write(gitignorePath, `${LAST_ACTION_FILE}
354
+ `);
355
+ }
356
+ }
357
+ async function loadLastAction(kadaiDir) {
358
+ const file = Bun.file(join4(kadaiDir, LAST_ACTION_FILE));
359
+ if (!await file.exists())
360
+ return null;
361
+ const content = (await file.text()).trim();
362
+ return content || null;
363
+ }
364
+ var LAST_ACTION_FILE = ".last-action";
365
+ var init_last_action = () => {};
366
+
279
367
  // src/core/fetchers/github.ts
280
368
  import { mkdir, rm } from "fs/promises";
281
- import { join as join3 } from "path";
369
+ import { join as join5 } from "path";
282
370
  async function fetchGithubPlugin(source, destDir) {
283
371
  const ref = source.ref ?? "main";
284
372
  const repoUrl = `https://github.com/${source.github}.git`;
@@ -296,7 +384,7 @@ async function fetchGithubPlugin(source, destDir) {
296
384
  });
297
385
  const sha = (await new Response(shaProc.stdout).text()).trim();
298
386
  await shaProc.exited;
299
- await rm(join3(destDir, ".git"), { recursive: true, force: true });
387
+ await rm(join5(destDir, ".git"), { recursive: true, force: true });
300
388
  return { resolvedVersion: sha };
301
389
  }
302
390
  async function checkGithubUpdate(source, currentSha) {
@@ -321,29 +409,25 @@ async function checkGithubUpdate(source, currentSha) {
321
409
  }
322
410
  var init_github = () => {};
323
411
 
324
- // src/core/fetchers/npm.ts
325
- import { mkdir as mkdir2, unlink } from "fs/promises";
326
- import { join as join4 } from "path";
327
- function parseSemver(v) {
328
- const withoutPrerelease = v.replace(/^v/, "").split("-")[0] ?? "";
329
- const clean = withoutPrerelease.split("+")[0] ?? "";
330
- const parts = clean.split(".");
331
- if (parts.length !== 3)
412
+ // src/core/semver.ts
413
+ function parseSemver(version) {
414
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
415
+ if (!match)
332
416
  return null;
333
- const nums = parts.map(Number);
334
- if (nums.some((n) => Number.isNaN(n)))
335
- return null;
336
- return nums;
417
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
337
418
  }
338
419
  function compareSemver(a, b) {
339
420
  for (let i = 0;i < 3; i++) {
340
- const av = a[i];
341
- const bv = b[i];
342
- if (av !== bv)
343
- return av - bv;
421
+ const diff = a[i] - b[i];
422
+ if (diff !== 0)
423
+ return diff;
344
424
  }
345
425
  return 0;
346
426
  }
427
+
428
+ // src/core/fetchers/npm.ts
429
+ import { mkdir as mkdir2, unlink } from "fs/promises";
430
+ import { join as join6 } from "path";
347
431
  function satisfies(version, range) {
348
432
  if (range === "*" || range === "x")
349
433
  return true;
@@ -417,7 +501,7 @@ async function fetchNpmPlugin(source, destDir) {
417
501
  }
418
502
  await mkdir2(destDir, { recursive: true });
419
503
  const tarball = await tarballRes.arrayBuffer();
420
- const tarballPath = join4(destDir, ".plugin.tgz");
504
+ const tarballPath = join6(destDir, ".plugin.tgz");
421
505
  await Bun.write(tarballPath, tarball);
422
506
  const proc = Bun.spawn(["tar", "xzf", tarballPath, "--strip-components=1"], {
423
507
  cwd: destDir,
@@ -457,9 +541,9 @@ var init_which = __esm(() => {
457
541
  });
458
542
 
459
543
  // src/core/pm.ts
460
- import { join as join5 } from "path";
544
+ import { join as join7 } from "path";
461
545
  async function resolvePM(dir) {
462
- const pkgJsonPath = join5(dir, "package.json");
546
+ const pkgJsonPath = join7(dir, "package.json");
463
547
  try {
464
548
  const file = Bun.file(pkgJsonPath);
465
549
  if (await file.exists()) {
@@ -489,13 +573,13 @@ var init_pm = __esm(() => {
489
573
  });
490
574
 
491
575
  // src/core/plugins.ts
492
- import { existsSync } from "fs";
576
+ import { existsSync as existsSync2 } from "fs";
493
577
  import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
494
- import { isAbsolute, join as join6, resolve } from "path";
578
+ import { isAbsolute, join as join8, resolve } from "path";
495
579
  async function ensurePluginCacheDir(kadaiDir) {
496
- const cacheDir = join6(kadaiDir, ".cache", "plugins");
580
+ const cacheDir = join8(kadaiDir, ".cache", "plugins");
497
581
  await mkdir3(cacheDir, { recursive: true });
498
- const gitignorePath = join6(kadaiDir, ".cache", ".gitignore");
582
+ const gitignorePath = join8(kadaiDir, ".cache", ".gitignore");
499
583
  const gitignoreFile = Bun.file(gitignorePath);
500
584
  if (!await gitignoreFile.exists()) {
501
585
  await Bun.write(gitignorePath, `*
@@ -522,7 +606,7 @@ function pluginDisplayName(source) {
522
606
  }
523
607
  async function readPluginMeta(cacheDir) {
524
608
  try {
525
- const file = Bun.file(join6(cacheDir, ".plugin-meta.json"));
609
+ const file = Bun.file(join8(cacheDir, ".plugin-meta.json"));
526
610
  if (!await file.exists())
527
611
  return null;
528
612
  return await file.json();
@@ -531,18 +615,18 @@ async function readPluginMeta(cacheDir) {
531
615
  }
532
616
  }
533
617
  async function writePluginMeta(cacheDir, meta) {
534
- await Bun.write(join6(cacheDir, ".plugin-meta.json"), JSON.stringify(meta, null, 2));
618
+ await Bun.write(join8(cacheDir, ".plugin-meta.json"), JSON.stringify(meta, null, 2));
535
619
  }
536
620
  async function loadCachedPlugins(kadaiDir, plugins) {
537
621
  const allActions = [];
538
- const cacheBase = join6(kadaiDir, ".cache", "plugins");
622
+ const cacheBase = join8(kadaiDir, ".cache", "plugins");
539
623
  for (const source of plugins) {
540
624
  if ("path" in source)
541
625
  continue;
542
626
  const key = cacheKeyFor(source);
543
- const pluginCacheDir = join6(cacheBase, key);
544
- const actionsDir = join6(pluginCacheDir, "actions");
545
- if (!existsSync(actionsDir))
627
+ const pluginCacheDir = join8(cacheBase, key);
628
+ const actionsDir = join8(pluginCacheDir, "actions");
629
+ if (!existsSync2(actionsDir))
546
630
  continue;
547
631
  const name = pluginDisplayName(source);
548
632
  const origin = { type: "plugin", pluginName: name };
@@ -556,8 +640,8 @@ async function loadCachedPlugins(kadaiDir, plugins) {
556
640
  return allActions;
557
641
  }
558
642
  async function installPluginDeps(pluginDir) {
559
- const pkgJsonPath = join6(pluginDir, "package.json");
560
- if (!existsSync(pkgJsonPath))
643
+ const pkgJsonPath = join8(pluginDir, "package.json");
644
+ if (!existsSync2(pkgJsonPath))
561
645
  return;
562
646
  const pm = await resolvePM(pluginDir);
563
647
  const proc = Bun.spawn(pm.install, {
@@ -574,7 +658,7 @@ async function installPluginDeps(pluginDir) {
574
658
  async function syncPlugin(kadaiDir, source) {
575
659
  const cacheBase = await ensurePluginCacheDir(kadaiDir);
576
660
  const key = cacheKeyFor(source);
577
- const pluginCacheDir = join6(cacheBase, key);
661
+ const pluginCacheDir = join8(cacheBase, key);
578
662
  const meta = await readPluginMeta(pluginCacheDir);
579
663
  if (meta) {
580
664
  let needsUpdate = false;
@@ -624,8 +708,8 @@ async function syncPlugins(kadaiDir, plugins, callbacks) {
624
708
  }
625
709
  async function loadPathPlugin(kadaiDir, source) {
626
710
  const pluginRoot = isAbsolute(source.path) ? source.path : resolve(kadaiDir, source.path);
627
- const actionsDir = join6(pluginRoot, "actions");
628
- if (!existsSync(actionsDir))
711
+ const actionsDir = join8(pluginRoot, "actions");
712
+ if (!existsSync2(actionsDir))
629
713
  return [];
630
714
  const name = source.path;
631
715
  const origin = { type: "plugin", pluginName: name };
@@ -640,8 +724,8 @@ async function loadUserGlobalActions() {
640
724
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
641
725
  if (!homeDir)
642
726
  return [];
643
- const actionsDir = join6(homeDir, ".kadai", "actions");
644
- if (!existsSync(actionsDir))
727
+ const actionsDir = join8(homeDir, ".kadai", "actions");
728
+ if (!existsSync2(actionsDir))
645
729
  return [];
646
730
  const origin = { type: "plugin", pluginName: "~" };
647
731
  const actions = await loadActions(actionsDir, origin);
@@ -729,13 +813,14 @@ var exports_commands = {};
729
813
  __export(exports_commands, {
730
814
  handleSync: () => handleSync,
731
815
  handleRun: () => handleRun,
816
+ handleRerun: () => handleRerun,
732
817
  handleList: () => handleList
733
818
  });
734
- import { join as join7 } from "path";
819
+ import { join as join9 } from "path";
735
820
  async function handleList(options) {
736
821
  const { kadaiDir, all } = options;
737
822
  const config = await loadConfig(kadaiDir);
738
- const actionsDir = join7(kadaiDir, config.actionsDir ?? "actions");
823
+ const actionsDir = join9(kadaiDir, config.actionsDir ?? "actions");
739
824
  let actions = await loadActions(actionsDir);
740
825
  const globalActions = await loadUserGlobalActions();
741
826
  actions = [...actions, ...globalActions];
@@ -768,7 +853,7 @@ async function handleList(options) {
768
853
  async function handleRun(options) {
769
854
  const { kadaiDir, actionId, cwd } = options;
770
855
  const config = await loadConfig(kadaiDir);
771
- const actionsDir = join7(kadaiDir, config.actionsDir ?? "actions");
856
+ const actionsDir = join9(kadaiDir, config.actionsDir ?? "actions");
772
857
  let actions = await loadActions(actionsDir);
773
858
  const globalActions = await loadUserGlobalActions();
774
859
  actions = [...actions, ...globalActions];
@@ -788,7 +873,9 @@ async function handleRun(options) {
788
873
  `);
789
874
  process.exit(1);
790
875
  }
876
+ await saveLastAction(kadaiDir, actionId);
791
877
  if (action.runtime === "ink") {
878
+ const cleanupKadai = ensureKadaiResolvable(join9(cwd, "node_modules"));
792
879
  const mod = await import(action.filePath);
793
880
  if (typeof mod.default !== "function") {
794
881
  process.stderr.write(`Error: "${action.filePath}" does not export a default function component
@@ -806,6 +893,7 @@ async function handleRun(options) {
806
893
  }));
807
894
  await instance.waitUntilExit();
808
895
  cleanupFullscreen?.();
896
+ cleanupKadai?.();
809
897
  process.exit(0);
810
898
  }
811
899
  const cmd = resolveCommand(action);
@@ -829,6 +917,16 @@ async function handleRun(options) {
829
917
  const exitCode = await proc.exited;
830
918
  process.exit(exitCode);
831
919
  }
920
+ async function handleRerun(options) {
921
+ const { kadaiDir, cwd } = options;
922
+ const actionId = await loadLastAction(kadaiDir);
923
+ if (!actionId) {
924
+ process.stderr.write(`No last action found. Run an action first before using --rerun.
925
+ `);
926
+ process.exit(1);
927
+ }
928
+ return handleRun({ kadaiDir, actionId, cwd });
929
+ }
832
930
  async function handleSync(options) {
833
931
  const { kadaiDir } = options;
834
932
  const config = await loadConfig(kadaiDir);
@@ -875,6 +973,8 @@ All plugins synced.
875
973
  var init_commands = __esm(() => {
876
974
  init_config();
877
975
  init_loader();
976
+ init_last_action();
977
+ init_shared_deps();
878
978
  init_plugins();
879
979
  init_runner();
880
980
  });
@@ -883,11 +983,20 @@ var init_commands = __esm(() => {
883
983
  var require_package = __commonJS((exports, module) => {
884
984
  module.exports = {
885
985
  name: "kadai",
886
- version: "0.5.0",
986
+ version: "0.7.1",
887
987
  type: "module",
888
988
  bin: {
889
989
  kadai: "./dist/cli.js"
890
990
  },
991
+ exports: {
992
+ ".": "./dist/cli.js",
993
+ "./types": "./dist/types.js",
994
+ "./ink": "./dist/exports/ink.js",
995
+ "./ui": "./dist/exports/ui.js",
996
+ "./react": "./dist/exports/react.js",
997
+ "./react/jsx-runtime": "./dist/exports/jsx-runtime.js",
998
+ "./react/jsx-dev-runtime": "./dist/exports/jsx-dev-runtime.js"
999
+ },
891
1000
  scripts: {
892
1001
  build: "bun build.ts",
893
1002
  check: "tsc --noEmit && biome check ./src ./test",
@@ -924,7 +1033,7 @@ __export(exports_mcp, {
924
1033
  ensureMcpConfig: () => ensureMcpConfig,
925
1034
  actionIdToToolName: () => actionIdToToolName
926
1035
  });
927
- import { join as join8, resolve as resolve2 } from "path";
1036
+ import { join as join10, resolve as resolve2 } from "path";
928
1037
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
929
1038
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
930
1039
  function resolveInvocationCommand() {
@@ -951,40 +1060,35 @@ function buildToolDescription(action) {
951
1060
  return parts.join(" ");
952
1061
  }
953
1062
  async function ensureMcpConfig(projectRoot) {
954
- const mcpJsonPath = join8(projectRoot, ".mcp.json");
1063
+ const mcpJsonPath = join10(projectRoot, ".mcp.json");
955
1064
  const mcpFile = Bun.file(mcpJsonPath);
956
1065
  const kadaiEntry = resolveInvocationCommand();
957
1066
  if (await mcpFile.exists()) {
958
1067
  const existing = await mcpFile.json();
959
1068
  if (existing.mcpServers?.kadai) {
960
- process.stderr.write(`kadai MCP server already configured in .mcp.json
961
- `);
962
- return;
1069
+ return false;
963
1070
  }
964
1071
  existing.mcpServers = existing.mcpServers ?? {};
965
1072
  existing.mcpServers.kadai = kadaiEntry;
966
1073
  await Bun.write(mcpJsonPath, `${JSON.stringify(existing, null, 2)}
967
1074
  `);
968
- process.stderr.write(`Added kadai entry to existing .mcp.json
969
- `);
970
- } else {
971
- const config = {
972
- mcpServers: {
973
- kadai: kadaiEntry
974
- }
975
- };
976
- await Bun.write(mcpJsonPath, `${JSON.stringify(config, null, 2)}
977
- `);
978
- process.stderr.write(`Created .mcp.json with kadai MCP server config
979
- `);
1075
+ return true;
980
1076
  }
1077
+ const config = {
1078
+ mcpServers: {
1079
+ kadai: kadaiEntry
1080
+ }
1081
+ };
1082
+ await Bun.write(mcpJsonPath, `${JSON.stringify(config, null, 2)}
1083
+ `);
1084
+ return true;
981
1085
  }
982
1086
  async function startMcpServer(kadaiDir, cwd) {
983
1087
  let visibleActions = [];
984
1088
  let config = {};
985
1089
  if (kadaiDir) {
986
1090
  config = await loadConfig(kadaiDir);
987
- const actionsDir = join8(kadaiDir, config.actionsDir ?? "actions");
1091
+ const actionsDir = join10(kadaiDir, config.actionsDir ?? "actions");
988
1092
  let allActions = await loadActions(actionsDir);
989
1093
  const globalActions = await loadUserGlobalActions();
990
1094
  allActions = [...allActions, ...globalActions];
@@ -1051,6 +1155,168 @@ var init_mcp = __esm(() => {
1051
1155
  init_runner();
1052
1156
  });
1053
1157
 
1158
+ // src/core/init-wizard.ts
1159
+ var exports_init_wizard = {};
1160
+ __export(exports_init_wizard, {
1161
+ writeInitFiles: () => writeInitFiles,
1162
+ generateConfigFile: () => generateConfigFile,
1163
+ ensureClaudeIntegration: () => ensureClaudeIntegration
1164
+ });
1165
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
1166
+ import { join as join11 } from "path";
1167
+ function generateConfigFile() {
1168
+ const lines = [' // actionsDir: "actions",', " // env: {},"];
1169
+ return `export default {
1170
+ ${lines.join(`
1171
+ `)}
1172
+ };
1173
+ `;
1174
+ }
1175
+ async function writeInitFiles(cwd) {
1176
+ const kadaiDir = join11(cwd, ".kadai");
1177
+ const actionsDir = join11(kadaiDir, "actions");
1178
+ mkdirSync2(actionsDir, { recursive: true });
1179
+ const sampleAction = join11(actionsDir, "hello.sh");
1180
+ const sampleFile = Bun.file(sampleAction);
1181
+ let sampleCreated = false;
1182
+ if (!await sampleFile.exists()) {
1183
+ await Bun.write(sampleAction, `#!/bin/bash
1184
+ # kadai:name Hello World
1185
+ # kadai:emoji \uD83D\uDC4B
1186
+ # kadai:description A sample action \u2014 edit or delete this file
1187
+
1188
+ echo "Hello from kadai!"
1189
+ echo "Add your own scripts to .kadai/actions/ to get started."
1190
+ `);
1191
+ sampleCreated = true;
1192
+ }
1193
+ const configContent = generateConfigFile();
1194
+ const configPath = join11(kadaiDir, "config.ts");
1195
+ await Bun.write(configPath, configContent);
1196
+ const integration = await ensureClaudeIntegration(cwd);
1197
+ return { sampleCreated, skillCreated: integration.skillCreated };
1198
+ }
1199
+ async function ensureClaudeIntegration(projectRoot) {
1200
+ const hasClaudeDir = existsSync3(join11(projectRoot, ".claude"));
1201
+ const hasClaudeMd = existsSync3(join11(projectRoot, "CLAUDE.md"));
1202
+ if (!hasClaudeDir && !hasClaudeMd) {
1203
+ return { skillCreated: false, mcpConfigured: false };
1204
+ }
1205
+ const skillCreated = await ensureSkillFile(projectRoot);
1206
+ const mcpConfigured = await ensureMcpJsonEntry(projectRoot);
1207
+ return { skillCreated, mcpConfigured };
1208
+ }
1209
+ async function ensureSkillFile(projectRoot) {
1210
+ const skillDir = join11(projectRoot, ".claude", "skills", "kadai");
1211
+ const skillPath = join11(skillDir, "SKILL.md");
1212
+ if (await Bun.file(skillPath).exists()) {
1213
+ return false;
1214
+ }
1215
+ mkdirSync2(skillDir, { recursive: true });
1216
+ await Bun.write(skillPath, generateSkillFile());
1217
+ return true;
1218
+ }
1219
+ async function ensureMcpJsonEntry(projectRoot) {
1220
+ const { ensureMcpConfig: ensureMcpConfig2 } = await Promise.resolve().then(() => (init_mcp(), exports_mcp));
1221
+ return await ensureMcpConfig2(projectRoot);
1222
+ }
1223
+ function generateSkillFile() {
1224
+ return `---
1225
+ name: kadai
1226
+ description: >-
1227
+ kadai is a script runner for this project. Discover available actions with
1228
+ kadai list --json, and run them with kadai run <action-id>.
1229
+ user-invocable: false
1230
+ ---
1231
+
1232
+ # kadai \u2014 Project Script Runner
1233
+
1234
+ kadai manages and runs project-specific shell scripts stored in \`.kadai/actions/\`.
1235
+
1236
+ ## Discovering Actions
1237
+
1238
+ \`\`\`bash
1239
+ kadai list --json
1240
+ \`\`\`
1241
+
1242
+ Returns a JSON array of available actions:
1243
+
1244
+ \`\`\`json
1245
+ [
1246
+ {
1247
+ "id": "database/reset",
1248
+ "name": "Reset Database",
1249
+ "emoji": "\uD83D\uDDD1\uFE0F",
1250
+ "description": "Drop and recreate the dev database",
1251
+ "category": ["database"],
1252
+ "runtime": "bash",
1253
+ "confirm": true
1254
+ }
1255
+ ]
1256
+ \`\`\`
1257
+
1258
+ Use \`--all\` to include hidden actions: \`kadai list --json --all\`
1259
+
1260
+ Always use \`kadai list --json\` for the current set of actions \u2014 do not hardcode action lists.
1261
+
1262
+ ## Running Actions
1263
+
1264
+ \`\`\`bash
1265
+ kadai run <action-id>
1266
+ \`\`\`
1267
+
1268
+ Runs the action and streams stdout/stderr directly. The process exits with the action's exit code.
1269
+ Confirmation prompts are automatically skipped in non-TTY environments.
1270
+
1271
+ ### Examples
1272
+
1273
+ \`\`\`bash
1274
+ kadai run hello
1275
+ kadai run database/reset
1276
+ \`\`\`
1277
+
1278
+ ## Creating Actions
1279
+
1280
+ Create a script file in \`.kadai/actions/\`. Supported extensions: \`.sh\`, \`.bash\`, \`.ts\`, \`.js\`, \`.mjs\`, \`.py\`, \`.tsx\`.
1281
+
1282
+ Add metadata as comments in the first 20 lines using \`# kadai:<key> <value>\` (for shell/python) or \`// kadai:<key> <value>\` (for JS/TS):
1283
+
1284
+ \`\`\`bash
1285
+ #!/bin/bash
1286
+ # kadai:name Deploy Staging
1287
+ # kadai:emoji \uD83D\uDE80
1288
+ # kadai:description Deploy the app to the staging environment
1289
+ # kadai:confirm true
1290
+
1291
+ echo "Deploying..."
1292
+ \`\`\`
1293
+
1294
+ Available metadata keys:
1295
+
1296
+ | Key | Description |
1297
+ |---------------|---------------------------------------------|
1298
+ | \`name\` | Display name in menus |
1299
+ | \`emoji\` | Emoji prefix |
1300
+ | \`description\` | Short description |
1301
+ | \`confirm\` | Require confirmation before running (true/false) |
1302
+ | \`hidden\` | Hide from default listing (true/false) |
1303
+ | \`fullscreen\` | Use alternate screen buffer for ink actions (true/false) |
1304
+
1305
+ If \`name\` is omitted, it is inferred from the filename (e.g. \`deploy-staging.sh\` \u2192 "Deploy Staging").
1306
+
1307
+ Organize actions into categories using subdirectories:
1308
+
1309
+ \`\`\`
1310
+ .kadai/actions/
1311
+ hello.sh \u2192 id: "hello"
1312
+ database/
1313
+ migrate.sh \u2192 id: "database/migrate"
1314
+ reset.ts \u2192 id: "database/reset"
1315
+ \`\`\`
1316
+ `;
1317
+ }
1318
+ var init_init_wizard = () => {};
1319
+
1054
1320
  // src/components/Breadcrumbs.tsx
1055
1321
  import { Box, Text } from "ink";
1056
1322
  import { jsxDEV } from "react/jsx-dev-runtime";
@@ -1087,6 +1353,7 @@ var init_FullscreenProvider = () => {};
1087
1353
 
1088
1354
  // src/components/InkActionRenderer.tsx
1089
1355
  import { Box as Box2, Text as Text2 } from "ink";
1356
+ import { join as join12 } from "path";
1090
1357
  import React, { useEffect, useState } from "react";
1091
1358
  import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
1092
1359
  function InkActionRenderer({
@@ -1100,6 +1367,7 @@ function InkActionRenderer({
1100
1367
  let cancelled = false;
1101
1368
  (async () => {
1102
1369
  try {
1370
+ ensureKadaiResolvable(join12(cwd, "node_modules"));
1103
1371
  const mod = await import(action.filePath);
1104
1372
  if (cancelled)
1105
1373
  return;
@@ -1162,6 +1430,7 @@ function InkActionRenderer({
1162
1430
  }
1163
1431
  var InkActionErrorBoundary;
1164
1432
  var init_InkActionRenderer = __esm(() => {
1433
+ init_shared_deps();
1165
1434
  InkActionErrorBoundary = class InkActionErrorBoundary extends React.Component {
1166
1435
  constructor(props) {
1167
1436
  super(props);
@@ -1212,7 +1481,7 @@ function StatusBar() {
1212
1481
  var init_StatusBar = () => {};
1213
1482
 
1214
1483
  // src/hooks/useActions.ts
1215
- import { join as join9 } from "path";
1484
+ import { join as join13 } from "path";
1216
1485
  import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
1217
1486
  function useActions({ kadaiDir }) {
1218
1487
  const [actions, setActions] = useState2([]);
@@ -1225,7 +1494,7 @@ function useActions({ kadaiDir }) {
1225
1494
  (async () => {
1226
1495
  const cfg = await loadConfig(kadaiDir);
1227
1496
  setConfig(cfg);
1228
- const actionsDir = join9(kadaiDir, cfg.actionsDir ?? "actions");
1497
+ const actionsDir = join13(kadaiDir, cfg.actionsDir ?? "actions");
1229
1498
  const localActions = await loadActions(actionsDir);
1230
1499
  let allActions = [...localActions];
1231
1500
  const globalActions = await loadUserGlobalActions();
@@ -2267,6 +2536,7 @@ function MenuList({
2267
2536
  }
2268
2537
  const selected = i === selectedIndex;
2269
2538
  return /* @__PURE__ */ jsxDEV5(Box4, {
2539
+ width: "100%",
2270
2540
  children: [
2271
2541
  /* @__PURE__ */ jsxDEV5(Text4, {
2272
2542
  color: selected ? "cyan" : undefined,
@@ -2288,14 +2558,13 @@ function MenuList({
2288
2558
  dimColor: true,
2289
2559
  children: " \u27F3"
2290
2560
  }, undefined, false, undefined, this),
2561
+ /* @__PURE__ */ jsxDEV5(Box4, {
2562
+ flexGrow: 1
2563
+ }, undefined, false, undefined, this),
2291
2564
  item.description && /* @__PURE__ */ jsxDEV5(Text4, {
2292
2565
  dimColor: true,
2293
- children: [
2294
- " (",
2295
- item.description,
2296
- ")"
2297
- ]
2298
- }, undefined, true, undefined, this)
2566
+ children: item.description
2567
+ }, undefined, false, undefined, this)
2299
2568
  ]
2300
2569
  }, `${i}-${item.value}`, true, undefined, this);
2301
2570
  })
@@ -2548,153 +2817,8 @@ var init_app = __esm(() => {
2548
2817
  SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
2549
2818
  });
2550
2819
 
2551
- // src/core/init-wizard.ts
2552
- var exports_init_wizard = {};
2553
- __export(exports_init_wizard, {
2554
- writeInitFiles: () => writeInitFiles,
2555
- generateConfigFile: () => generateConfigFile
2556
- });
2557
- import { existsSync as existsSync2, mkdirSync } from "fs";
2558
- import { join as join10 } from "path";
2559
- function generateConfigFile() {
2560
- const lines = [' // actionsDir: "actions",', " // env: {},"];
2561
- return `export default {
2562
- ${lines.join(`
2563
- `)}
2564
- };
2565
- `;
2566
- }
2567
- async function writeInitFiles(cwd) {
2568
- const kadaiDir = join10(cwd, ".kadai");
2569
- const actionsDir = join10(kadaiDir, "actions");
2570
- mkdirSync(actionsDir, { recursive: true });
2571
- const sampleAction = join10(actionsDir, "hello.sh");
2572
- const sampleFile = Bun.file(sampleAction);
2573
- let sampleCreated = false;
2574
- if (!await sampleFile.exists()) {
2575
- await Bun.write(sampleAction, `#!/bin/bash
2576
- # kadai:name Hello World
2577
- # kadai:emoji \uD83D\uDC4B
2578
- # kadai:description A sample action \u2014 edit or delete this file
2579
-
2580
- echo "Hello from kadai!"
2581
- echo "Add your own scripts to .kadai/actions/ to get started."
2582
- `);
2583
- sampleCreated = true;
2584
- }
2585
- const configContent = generateConfigFile();
2586
- const configPath = join10(kadaiDir, "config.ts");
2587
- await Bun.write(configPath, configContent);
2588
- let skillCreated = false;
2589
- const hasClaudeDir = existsSync2(join10(cwd, ".claude"));
2590
- const hasClaudeMd = existsSync2(join10(cwd, "CLAUDE.md"));
2591
- if (hasClaudeDir || hasClaudeMd) {
2592
- const skillDir = join10(cwd, ".claude", "skills", "kadai");
2593
- const skillPath = join10(skillDir, "SKILL.md");
2594
- if (!await Bun.file(skillPath).exists()) {
2595
- mkdirSync(skillDir, { recursive: true });
2596
- await Bun.write(skillPath, generateSkillFile());
2597
- skillCreated = true;
2598
- }
2599
- }
2600
- return { sampleCreated, skillCreated };
2601
- }
2602
- function generateSkillFile() {
2603
- return `---
2604
- name: kadai
2605
- description: >-
2606
- kadai is a script runner for this project. Discover available actions with
2607
- kadai list --json, and run them with kadai run <action-id>.
2608
- user-invocable: false
2609
- ---
2610
-
2611
- # kadai \u2014 Project Script Runner
2612
-
2613
- kadai manages and runs project-specific shell scripts stored in \`.kadai/actions/\`.
2614
-
2615
- ## Discovering Actions
2616
-
2617
- \`\`\`bash
2618
- kadai list --json
2619
- \`\`\`
2620
-
2621
- Returns a JSON array of available actions:
2622
-
2623
- \`\`\`json
2624
- [
2625
- {
2626
- "id": "database/reset",
2627
- "name": "Reset Database",
2628
- "emoji": "\uD83D\uDDD1\uFE0F",
2629
- "description": "Drop and recreate the dev database",
2630
- "category": ["database"],
2631
- "runtime": "bash",
2632
- "confirm": true
2633
- }
2634
- ]
2635
- \`\`\`
2636
-
2637
- Use \`--all\` to include hidden actions: \`kadai list --json --all\`
2638
-
2639
- Always use \`kadai list --json\` for the current set of actions \u2014 do not hardcode action lists.
2640
-
2641
- ## Running Actions
2642
-
2643
- \`\`\`bash
2644
- kadai run <action-id>
2645
- \`\`\`
2646
-
2647
- Runs the action and streams stdout/stderr directly. The process exits with the action's exit code.
2648
- Confirmation prompts are automatically skipped in non-TTY environments.
2649
-
2650
- ### Examples
2651
-
2652
- \`\`\`bash
2653
- kadai run hello
2654
- kadai run database/reset
2655
- \`\`\`
2656
-
2657
- ## Creating Actions
2658
-
2659
- Create a script file in \`.kadai/actions/\`. Supported extensions: \`.sh\`, \`.bash\`, \`.ts\`, \`.js\`, \`.mjs\`, \`.py\`, \`.tsx\`.
2660
-
2661
- Add metadata as comments in the first 20 lines using \`# kadai:<key> <value>\` (for shell/python) or \`// kadai:<key> <value>\` (for JS/TS):
2662
-
2663
- \`\`\`bash
2664
- #!/bin/bash
2665
- # kadai:name Deploy Staging
2666
- # kadai:emoji \uD83D\uDE80
2667
- # kadai:description Deploy the app to the staging environment
2668
- # kadai:confirm true
2669
-
2670
- echo "Deploying..."
2671
- \`\`\`
2672
-
2673
- Available metadata keys:
2674
-
2675
- | Key | Description |
2676
- |---------------|---------------------------------------------|
2677
- | \`name\` | Display name in menus |
2678
- | \`emoji\` | Emoji prefix |
2679
- | \`description\` | Short description |
2680
- | \`confirm\` | Require confirmation before running (true/false) |
2681
- | \`hidden\` | Hide from default listing (true/false) |
2682
- | \`fullscreen\` | Use alternate screen buffer for ink actions (true/false) |
2683
-
2684
- If \`name\` is omitted, it is inferred from the filename (e.g. \`deploy-staging.sh\` \u2192 "Deploy Staging").
2685
-
2686
- Organize actions into categories using subdirectories:
2687
-
2688
- \`\`\`
2689
- .kadai/actions/
2690
- hello.sh \u2192 id: "hello"
2691
- database/
2692
- migrate.sh \u2192 id: "database/migrate"
2693
- reset.ts \u2192 id: "database/reset"
2694
- \`\`\`
2695
- `;
2696
- }
2697
- var init_init_wizard = () => {};
2820
+ // src/cli.tsx
2821
+ init_shared_deps();
2698
2822
 
2699
2823
  // src/core/args.ts
2700
2824
  function parseArgs(argv) {
@@ -2705,6 +2829,9 @@ function parseArgs(argv) {
2705
2829
  if (command === "--version" || command === "-v") {
2706
2830
  return { type: "version" };
2707
2831
  }
2832
+ if (command === "--rerun" || command === "-r") {
2833
+ return { type: "rerun" };
2834
+ }
2708
2835
  switch (command) {
2709
2836
  case "list": {
2710
2837
  if (!argv.includes("--json")) {
@@ -2730,7 +2857,7 @@ function parseArgs(argv) {
2730
2857
  default:
2731
2858
  return {
2732
2859
  type: "error",
2733
- message: `Unknown command: ${command}. Available commands: list, run, sync, mcp, --version`
2860
+ message: `Unknown command: ${command}. Available commands: list, run, sync, mcp, --version, --rerun`
2734
2861
  };
2735
2862
  }
2736
2863
  }
@@ -2738,6 +2865,8 @@ function parseArgs(argv) {
2738
2865
  // src/cli.tsx
2739
2866
  init_commands();
2740
2867
  init_loader();
2868
+ init_last_action();
2869
+ registerSharedDeps();
2741
2870
  var cwd = process.cwd();
2742
2871
  var parsed = parseArgs(process.argv.slice(2));
2743
2872
  if (parsed.type === "error") {
@@ -2758,17 +2887,22 @@ if (parsed.type === "mcp") {
2758
2887
  await startMcpServer2(kadaiDir, cwd);
2759
2888
  await new Promise(() => {});
2760
2889
  }
2761
- if (parsed.type === "list" || parsed.type === "run" || parsed.type === "sync") {
2890
+ if (parsed.type === "list" || parsed.type === "run" || parsed.type === "sync" || parsed.type === "rerun") {
2762
2891
  const kadaiDir = findZcliDir(cwd);
2763
2892
  if (!kadaiDir) {
2764
2893
  process.stderr.write(`Error: No .kadai directory found. Run kadai to initialize.
2765
2894
  `);
2766
2895
  process.exit(1);
2767
2896
  }
2897
+ const { dirname: dirname2 } = await import("path");
2898
+ const { ensureClaudeIntegration: ensureClaudeIntegration2 } = await Promise.resolve().then(() => (init_init_wizard(), exports_init_wizard));
2899
+ await ensureClaudeIntegration2(dirname2(kadaiDir));
2768
2900
  if (parsed.type === "list") {
2769
2901
  await handleList({ kadaiDir, all: parsed.all });
2770
2902
  } else if (parsed.type === "run") {
2771
2903
  await handleRun({ kadaiDir, actionId: parsed.actionId, cwd });
2904
+ } else if (parsed.type === "rerun") {
2905
+ await handleRerun({ kadaiDir, cwd });
2772
2906
  } else {
2773
2907
  const { handleSync: handleSync2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
2774
2908
  await handleSync2({ kadaiDir });
@@ -2794,12 +2928,20 @@ if (!kadaiDir) {
2794
2928
  console.log(" Created .kadai/config.ts");
2795
2929
  if (result.sampleCreated)
2796
2930
  console.log(" Created .kadai/actions/hello.sh");
2797
- if (result.skillCreated)
2798
- console.log(" Created .claude/skills/kadai/SKILL.md");
2799
2931
  console.log(`
2800
2932
  Done! Run kadai again to get started.`);
2801
2933
  process.exit(0);
2802
2934
  }
2935
+ {
2936
+ const { dirname: dirname2 } = await import("path");
2937
+ const { ensureClaudeIntegration: ensureClaudeIntegration2 } = await Promise.resolve().then(() => (init_init_wizard(), exports_init_wizard));
2938
+ const projectRoot = dirname2(kadaiDir);
2939
+ const ensured = await ensureClaudeIntegration2(projectRoot);
2940
+ if (ensured.skillCreated)
2941
+ console.log("Created .claude/skills/kadai/SKILL.md");
2942
+ if (ensured.mcpConfigured)
2943
+ console.log("Configured kadai in .mcp.json");
2944
+ }
2803
2945
  function createStdinStream() {
2804
2946
  if (process.stdin.isTTY) {
2805
2947
  return process.stdin;
@@ -2881,6 +3023,7 @@ await instance.waitUntilExit();
2881
3023
  if (!selectedAction)
2882
3024
  process.exit(0);
2883
3025
  var action = selectedAction;
3026
+ await saveLastAction(kadaiDir, action.id);
2884
3027
  var config = await loadConfig2(kadaiDir);
2885
3028
  var cmd = resolveCommand2(action);
2886
3029
  var env = {
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ // src/exports/ink.ts
3
+ export * from "ink";
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ // src/exports/jsx-dev-runtime.ts
3
+ export * from "react/jsx-dev-runtime";
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ // src/exports/jsx-runtime.ts
3
+ export * from "react/jsx-runtime";
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ // src/exports/react.ts
3
+ export * from "react";
4
+ import { default as default2 } from "react";
5
+ export {
6
+ default2 as default
7
+ };
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ // src/exports/ui.ts
3
+ export * from "@inkjs/ui";
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ // @bun
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "kadai",
3
- "version": "0.5.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "kadai": "./dist/cli.js"
7
7
  },
8
+ "exports": {
9
+ ".": "./dist/cli.js",
10
+ "./types": "./dist/types.js",
11
+ "./ink": "./dist/exports/ink.js",
12
+ "./ui": "./dist/exports/ui.js",
13
+ "./react": "./dist/exports/react.js",
14
+ "./react/jsx-runtime": "./dist/exports/jsx-runtime.js",
15
+ "./react/jsx-dev-runtime": "./dist/exports/jsx-dev-runtime.js"
16
+ },
8
17
  "scripts": {
9
18
  "build": "bun build.ts",
10
19
  "check": "tsc --noEmit && biome check ./src ./test",