kadai 0.6.0 → 0.8.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/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,24 @@ 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
+ }
337
+ async function loadLastAction(kadaiDir) {
338
+ const file = Bun.file(join4(kadaiDir, LAST_ACTION_FILE));
339
+ if (!await file.exists())
340
+ return null;
341
+ const content = (await file.text()).trim();
342
+ return content || null;
343
+ }
344
+ var LAST_ACTION_FILE = ".last-action";
345
+ var init_last_action = () => {};
346
+
279
347
  // src/core/fetchers/github.ts
280
348
  import { mkdir, rm } from "fs/promises";
281
- import { join as join3 } from "path";
349
+ import { join as join5 } from "path";
282
350
  async function fetchGithubPlugin(source, destDir) {
283
351
  const ref = source.ref ?? "main";
284
352
  const repoUrl = `https://github.com/${source.github}.git`;
@@ -296,7 +364,7 @@ async function fetchGithubPlugin(source, destDir) {
296
364
  });
297
365
  const sha = (await new Response(shaProc.stdout).text()).trim();
298
366
  await shaProc.exited;
299
- await rm(join3(destDir, ".git"), { recursive: true, force: true });
367
+ await rm(join5(destDir, ".git"), { recursive: true, force: true });
300
368
  return { resolvedVersion: sha };
301
369
  }
302
370
  async function checkGithubUpdate(source, currentSha) {
@@ -321,29 +389,25 @@ async function checkGithubUpdate(source, currentSha) {
321
389
  }
322
390
  var init_github = () => {};
323
391
 
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)
332
- return null;
333
- const nums = parts.map(Number);
334
- if (nums.some((n) => Number.isNaN(n)))
392
+ // src/core/semver.ts
393
+ function parseSemver(version) {
394
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
395
+ if (!match)
335
396
  return null;
336
- return nums;
397
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
337
398
  }
338
399
  function compareSemver(a, b) {
339
400
  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;
401
+ const diff = a[i] - b[i];
402
+ if (diff !== 0)
403
+ return diff;
344
404
  }
345
405
  return 0;
346
406
  }
407
+
408
+ // src/core/fetchers/npm.ts
409
+ import { mkdir as mkdir2, unlink } from "fs/promises";
410
+ import { join as join6 } from "path";
347
411
  function satisfies(version, range) {
348
412
  if (range === "*" || range === "x")
349
413
  return true;
@@ -417,7 +481,7 @@ async function fetchNpmPlugin(source, destDir) {
417
481
  }
418
482
  await mkdir2(destDir, { recursive: true });
419
483
  const tarball = await tarballRes.arrayBuffer();
420
- const tarballPath = join4(destDir, ".plugin.tgz");
484
+ const tarballPath = join6(destDir, ".plugin.tgz");
421
485
  await Bun.write(tarballPath, tarball);
422
486
  const proc = Bun.spawn(["tar", "xzf", tarballPath, "--strip-components=1"], {
423
487
  cwd: destDir,
@@ -457,9 +521,9 @@ var init_which = __esm(() => {
457
521
  });
458
522
 
459
523
  // src/core/pm.ts
460
- import { join as join5 } from "path";
524
+ import { join as join7 } from "path";
461
525
  async function resolvePM(dir) {
462
- const pkgJsonPath = join5(dir, "package.json");
526
+ const pkgJsonPath = join7(dir, "package.json");
463
527
  try {
464
528
  const file = Bun.file(pkgJsonPath);
465
529
  if (await file.exists()) {
@@ -489,13 +553,13 @@ var init_pm = __esm(() => {
489
553
  });
490
554
 
491
555
  // src/core/plugins.ts
492
- import { existsSync } from "fs";
556
+ import { existsSync as existsSync2 } from "fs";
493
557
  import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
494
- import { isAbsolute, join as join6, resolve } from "path";
558
+ import { isAbsolute, join as join8, resolve } from "path";
495
559
  async function ensurePluginCacheDir(kadaiDir) {
496
- const cacheDir = join6(kadaiDir, ".cache", "plugins");
560
+ const cacheDir = join8(kadaiDir, ".cache", "plugins");
497
561
  await mkdir3(cacheDir, { recursive: true });
498
- const gitignorePath = join6(kadaiDir, ".cache", ".gitignore");
562
+ const gitignorePath = join8(kadaiDir, ".cache", ".gitignore");
499
563
  const gitignoreFile = Bun.file(gitignorePath);
500
564
  if (!await gitignoreFile.exists()) {
501
565
  await Bun.write(gitignorePath, `*
@@ -522,7 +586,7 @@ function pluginDisplayName(source) {
522
586
  }
523
587
  async function readPluginMeta(cacheDir) {
524
588
  try {
525
- const file = Bun.file(join6(cacheDir, ".plugin-meta.json"));
589
+ const file = Bun.file(join8(cacheDir, ".plugin-meta.json"));
526
590
  if (!await file.exists())
527
591
  return null;
528
592
  return await file.json();
@@ -531,18 +595,18 @@ async function readPluginMeta(cacheDir) {
531
595
  }
532
596
  }
533
597
  async function writePluginMeta(cacheDir, meta) {
534
- await Bun.write(join6(cacheDir, ".plugin-meta.json"), JSON.stringify(meta, null, 2));
598
+ await Bun.write(join8(cacheDir, ".plugin-meta.json"), JSON.stringify(meta, null, 2));
535
599
  }
536
600
  async function loadCachedPlugins(kadaiDir, plugins) {
537
601
  const allActions = [];
538
- const cacheBase = join6(kadaiDir, ".cache", "plugins");
602
+ const cacheBase = join8(kadaiDir, ".cache", "plugins");
539
603
  for (const source of plugins) {
540
604
  if ("path" in source)
541
605
  continue;
542
606
  const key = cacheKeyFor(source);
543
- const pluginCacheDir = join6(cacheBase, key);
544
- const actionsDir = join6(pluginCacheDir, "actions");
545
- if (!existsSync(actionsDir))
607
+ const pluginCacheDir = join8(cacheBase, key);
608
+ const actionsDir = join8(pluginCacheDir, "actions");
609
+ if (!existsSync2(actionsDir))
546
610
  continue;
547
611
  const name = pluginDisplayName(source);
548
612
  const origin = { type: "plugin", pluginName: name };
@@ -556,8 +620,8 @@ async function loadCachedPlugins(kadaiDir, plugins) {
556
620
  return allActions;
557
621
  }
558
622
  async function installPluginDeps(pluginDir) {
559
- const pkgJsonPath = join6(pluginDir, "package.json");
560
- if (!existsSync(pkgJsonPath))
623
+ const pkgJsonPath = join8(pluginDir, "package.json");
624
+ if (!existsSync2(pkgJsonPath))
561
625
  return;
562
626
  const pm = await resolvePM(pluginDir);
563
627
  const proc = Bun.spawn(pm.install, {
@@ -574,7 +638,7 @@ async function installPluginDeps(pluginDir) {
574
638
  async function syncPlugin(kadaiDir, source) {
575
639
  const cacheBase = await ensurePluginCacheDir(kadaiDir);
576
640
  const key = cacheKeyFor(source);
577
- const pluginCacheDir = join6(cacheBase, key);
641
+ const pluginCacheDir = join8(cacheBase, key);
578
642
  const meta = await readPluginMeta(pluginCacheDir);
579
643
  if (meta) {
580
644
  let needsUpdate = false;
@@ -624,8 +688,8 @@ async function syncPlugins(kadaiDir, plugins, callbacks) {
624
688
  }
625
689
  async function loadPathPlugin(kadaiDir, source) {
626
690
  const pluginRoot = isAbsolute(source.path) ? source.path : resolve(kadaiDir, source.path);
627
- const actionsDir = join6(pluginRoot, "actions");
628
- if (!existsSync(actionsDir))
691
+ const actionsDir = join8(pluginRoot, "actions");
692
+ if (!existsSync2(actionsDir))
629
693
  return [];
630
694
  const name = source.path;
631
695
  const origin = { type: "plugin", pluginName: name };
@@ -640,8 +704,8 @@ async function loadUserGlobalActions() {
640
704
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
641
705
  if (!homeDir)
642
706
  return [];
643
- const actionsDir = join6(homeDir, ".kadai", "actions");
644
- if (!existsSync(actionsDir))
707
+ const actionsDir = join8(homeDir, ".kadai", "actions");
708
+ if (!existsSync2(actionsDir))
645
709
  return [];
646
710
  const origin = { type: "plugin", pluginName: "~" };
647
711
  const actions = await loadActions(actionsDir, origin);
@@ -729,13 +793,14 @@ var exports_commands = {};
729
793
  __export(exports_commands, {
730
794
  handleSync: () => handleSync,
731
795
  handleRun: () => handleRun,
796
+ handleRerun: () => handleRerun,
732
797
  handleList: () => handleList
733
798
  });
734
- import { join as join7 } from "path";
799
+ import { join as join9 } from "path";
735
800
  async function handleList(options) {
736
801
  const { kadaiDir, all } = options;
737
802
  const config = await loadConfig(kadaiDir);
738
- const actionsDir = join7(kadaiDir, config.actionsDir ?? "actions");
803
+ const actionsDir = join9(kadaiDir, config.actionsDir ?? "actions");
739
804
  let actions = await loadActions(actionsDir);
740
805
  const globalActions = await loadUserGlobalActions();
741
806
  actions = [...actions, ...globalActions];
@@ -768,7 +833,7 @@ async function handleList(options) {
768
833
  async function handleRun(options) {
769
834
  const { kadaiDir, actionId, cwd } = options;
770
835
  const config = await loadConfig(kadaiDir);
771
- const actionsDir = join7(kadaiDir, config.actionsDir ?? "actions");
836
+ const actionsDir = join9(kadaiDir, config.actionsDir ?? "actions");
772
837
  let actions = await loadActions(actionsDir);
773
838
  const globalActions = await loadUserGlobalActions();
774
839
  actions = [...actions, ...globalActions];
@@ -788,7 +853,9 @@ async function handleRun(options) {
788
853
  `);
789
854
  process.exit(1);
790
855
  }
856
+ await saveLastAction(kadaiDir, actionId);
791
857
  if (action.runtime === "ink") {
858
+ const cleanupKadai = ensureKadaiResolvable(join9(cwd, "node_modules"));
792
859
  const mod = await import(action.filePath);
793
860
  if (typeof mod.default !== "function") {
794
861
  process.stderr.write(`Error: "${action.filePath}" does not export a default function component
@@ -806,6 +873,7 @@ async function handleRun(options) {
806
873
  }));
807
874
  await instance.waitUntilExit();
808
875
  cleanupFullscreen?.();
876
+ cleanupKadai?.();
809
877
  process.exit(0);
810
878
  }
811
879
  const cmd = resolveCommand(action);
@@ -829,6 +897,16 @@ async function handleRun(options) {
829
897
  const exitCode = await proc.exited;
830
898
  process.exit(exitCode);
831
899
  }
900
+ async function handleRerun(options) {
901
+ const { kadaiDir, cwd } = options;
902
+ const actionId = await loadLastAction(kadaiDir);
903
+ if (!actionId) {
904
+ process.stderr.write(`No last action found. Run an action first before using --rerun.
905
+ `);
906
+ process.exit(1);
907
+ }
908
+ return handleRun({ kadaiDir, actionId, cwd });
909
+ }
832
910
  async function handleSync(options) {
833
911
  const { kadaiDir } = options;
834
912
  const config = await loadConfig(kadaiDir);
@@ -875,6 +953,8 @@ All plugins synced.
875
953
  var init_commands = __esm(() => {
876
954
  init_config();
877
955
  init_loader();
956
+ init_last_action();
957
+ init_shared_deps();
878
958
  init_plugins();
879
959
  init_runner();
880
960
  });
@@ -883,11 +963,20 @@ var init_commands = __esm(() => {
883
963
  var require_package = __commonJS((exports, module) => {
884
964
  module.exports = {
885
965
  name: "kadai",
886
- version: "0.6.0",
966
+ version: "0.8.0",
887
967
  type: "module",
888
968
  bin: {
889
969
  kadai: "./dist/cli.js"
890
970
  },
971
+ exports: {
972
+ ".": "./dist/cli.js",
973
+ "./types": "./dist/types.js",
974
+ "./ink": "./dist/exports/ink.js",
975
+ "./ui": "./dist/exports/ui.js",
976
+ "./react": "./dist/exports/react.js",
977
+ "./react/jsx-runtime": "./dist/exports/jsx-runtime.js",
978
+ "./react/jsx-dev-runtime": "./dist/exports/jsx-dev-runtime.js"
979
+ },
891
980
  scripts: {
892
981
  build: "bun build.ts",
893
982
  check: "tsc --noEmit && biome check ./src ./test",
@@ -924,7 +1013,7 @@ __export(exports_mcp, {
924
1013
  ensureMcpConfig: () => ensureMcpConfig,
925
1014
  actionIdToToolName: () => actionIdToToolName
926
1015
  });
927
- import { join as join8, resolve as resolve2 } from "path";
1016
+ import { join as join10, resolve as resolve2 } from "path";
928
1017
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
929
1018
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
930
1019
  function resolveInvocationCommand() {
@@ -951,40 +1040,35 @@ function buildToolDescription(action) {
951
1040
  return parts.join(" ");
952
1041
  }
953
1042
  async function ensureMcpConfig(projectRoot) {
954
- const mcpJsonPath = join8(projectRoot, ".mcp.json");
1043
+ const mcpJsonPath = join10(projectRoot, ".mcp.json");
955
1044
  const mcpFile = Bun.file(mcpJsonPath);
956
1045
  const kadaiEntry = resolveInvocationCommand();
957
1046
  if (await mcpFile.exists()) {
958
1047
  const existing = await mcpFile.json();
959
1048
  if (existing.mcpServers?.kadai) {
960
- process.stderr.write(`kadai MCP server already configured in .mcp.json
961
- `);
962
- return;
1049
+ return false;
963
1050
  }
964
1051
  existing.mcpServers = existing.mcpServers ?? {};
965
1052
  existing.mcpServers.kadai = kadaiEntry;
966
1053
  await Bun.write(mcpJsonPath, `${JSON.stringify(existing, null, 2)}
967
1054
  `);
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
- `);
1055
+ return true;
980
1056
  }
1057
+ const config = {
1058
+ mcpServers: {
1059
+ kadai: kadaiEntry
1060
+ }
1061
+ };
1062
+ await Bun.write(mcpJsonPath, `${JSON.stringify(config, null, 2)}
1063
+ `);
1064
+ return true;
981
1065
  }
982
1066
  async function startMcpServer(kadaiDir, cwd) {
983
1067
  let visibleActions = [];
984
1068
  let config = {};
985
1069
  if (kadaiDir) {
986
1070
  config = await loadConfig(kadaiDir);
987
- const actionsDir = join8(kadaiDir, config.actionsDir ?? "actions");
1071
+ const actionsDir = join10(kadaiDir, config.actionsDir ?? "actions");
988
1072
  let allActions = await loadActions(actionsDir);
989
1073
  const globalActions = await loadUserGlobalActions();
990
1074
  allActions = [...allActions, ...globalActions];
@@ -1051,6 +1135,173 @@ var init_mcp = __esm(() => {
1051
1135
  init_runner();
1052
1136
  });
1053
1137
 
1138
+ // src/core/init-wizard.ts
1139
+ var exports_init_wizard = {};
1140
+ __export(exports_init_wizard, {
1141
+ writeInitFiles: () => writeInitFiles,
1142
+ generateConfigFile: () => generateConfigFile,
1143
+ ensureClaudeIntegration: () => ensureClaudeIntegration
1144
+ });
1145
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
1146
+ import { join as join11 } from "path";
1147
+ function generateConfigFile() {
1148
+ const lines = [' // actionsDir: "actions",', " // env: {},"];
1149
+ return `export default {
1150
+ ${lines.join(`
1151
+ `)}
1152
+ };
1153
+ `;
1154
+ }
1155
+ async function writeInitFiles(cwd) {
1156
+ const kadaiDir = join11(cwd, ".kadai");
1157
+ const actionsDir = join11(kadaiDir, "actions");
1158
+ mkdirSync2(actionsDir, { recursive: true });
1159
+ const sampleAction = join11(actionsDir, "hello.sh");
1160
+ const sampleFile = Bun.file(sampleAction);
1161
+ let sampleCreated = false;
1162
+ if (!await sampleFile.exists()) {
1163
+ await Bun.write(sampleAction, `#!/bin/bash
1164
+ # kadai:name Hello World
1165
+ # kadai:emoji \uD83D\uDC4B
1166
+ # kadai:description A sample action \u2014 edit or delete this file
1167
+
1168
+ echo "Hello from kadai!"
1169
+ echo "Add your own scripts to .kadai/actions/ to get started."
1170
+ `);
1171
+ sampleCreated = true;
1172
+ }
1173
+ const gitignorePath = join11(kadaiDir, ".gitignore");
1174
+ if (!await Bun.file(gitignorePath).exists()) {
1175
+ await Bun.write(gitignorePath, `.last-action
1176
+ `);
1177
+ }
1178
+ const configContent = generateConfigFile();
1179
+ const configPath = join11(kadaiDir, "config.ts");
1180
+ await Bun.write(configPath, configContent);
1181
+ const integration = await ensureClaudeIntegration(cwd);
1182
+ return { sampleCreated, skillCreated: integration.skillCreated };
1183
+ }
1184
+ async function ensureClaudeIntegration(projectRoot) {
1185
+ const hasClaudeDir = existsSync3(join11(projectRoot, ".claude"));
1186
+ const hasClaudeMd = existsSync3(join11(projectRoot, "CLAUDE.md"));
1187
+ if (!hasClaudeDir && !hasClaudeMd) {
1188
+ return { skillCreated: false, mcpConfigured: false };
1189
+ }
1190
+ const skillCreated = await ensureSkillFile(projectRoot);
1191
+ const mcpConfigured = await ensureMcpJsonEntry(projectRoot);
1192
+ return { skillCreated, mcpConfigured };
1193
+ }
1194
+ async function ensureSkillFile(projectRoot) {
1195
+ const skillDir = join11(projectRoot, ".claude", "skills", "kadai");
1196
+ const skillPath = join11(skillDir, "SKILL.md");
1197
+ if (await Bun.file(skillPath).exists()) {
1198
+ return false;
1199
+ }
1200
+ mkdirSync2(skillDir, { recursive: true });
1201
+ await Bun.write(skillPath, generateSkillFile());
1202
+ return true;
1203
+ }
1204
+ async function ensureMcpJsonEntry(projectRoot) {
1205
+ const { ensureMcpConfig: ensureMcpConfig2 } = await Promise.resolve().then(() => (init_mcp(), exports_mcp));
1206
+ return await ensureMcpConfig2(projectRoot);
1207
+ }
1208
+ function generateSkillFile() {
1209
+ return `---
1210
+ name: kadai
1211
+ description: >-
1212
+ kadai is a script runner for this project. Discover available actions with
1213
+ kadai list --json, and run them with kadai run <action-id>.
1214
+ user-invocable: false
1215
+ ---
1216
+
1217
+ # kadai \u2014 Project Script Runner
1218
+
1219
+ kadai manages and runs project-specific shell scripts stored in \`.kadai/actions/\`.
1220
+
1221
+ ## Discovering Actions
1222
+
1223
+ \`\`\`bash
1224
+ kadai list --json
1225
+ \`\`\`
1226
+
1227
+ Returns a JSON array of available actions:
1228
+
1229
+ \`\`\`json
1230
+ [
1231
+ {
1232
+ "id": "database/reset",
1233
+ "name": "Reset Database",
1234
+ "emoji": "\uD83D\uDDD1\uFE0F",
1235
+ "description": "Drop and recreate the dev database",
1236
+ "category": ["database"],
1237
+ "runtime": "bash",
1238
+ "confirm": true
1239
+ }
1240
+ ]
1241
+ \`\`\`
1242
+
1243
+ Use \`--all\` to include hidden actions: \`kadai list --json --all\`
1244
+
1245
+ Always use \`kadai list --json\` for the current set of actions \u2014 do not hardcode action lists.
1246
+
1247
+ ## Running Actions
1248
+
1249
+ \`\`\`bash
1250
+ kadai run <action-id>
1251
+ \`\`\`
1252
+
1253
+ Runs the action and streams stdout/stderr directly. The process exits with the action's exit code.
1254
+ Confirmation prompts are automatically skipped in non-TTY environments.
1255
+
1256
+ ### Examples
1257
+
1258
+ \`\`\`bash
1259
+ kadai run hello
1260
+ kadai run database/reset
1261
+ \`\`\`
1262
+
1263
+ ## Creating Actions
1264
+
1265
+ Create a script file in \`.kadai/actions/\`. Supported extensions: \`.sh\`, \`.bash\`, \`.ts\`, \`.js\`, \`.mjs\`, \`.py\`, \`.tsx\`.
1266
+
1267
+ Add metadata as comments in the first 20 lines using \`# kadai:<key> <value>\` (for shell/python) or \`// kadai:<key> <value>\` (for JS/TS):
1268
+
1269
+ \`\`\`bash
1270
+ #!/bin/bash
1271
+ # kadai:name Deploy Staging
1272
+ # kadai:emoji \uD83D\uDE80
1273
+ # kadai:description Deploy the app to the staging environment
1274
+ # kadai:confirm true
1275
+
1276
+ echo "Deploying..."
1277
+ \`\`\`
1278
+
1279
+ Available metadata keys:
1280
+
1281
+ | Key | Description |
1282
+ |---------------|---------------------------------------------|
1283
+ | \`name\` | Display name in menus |
1284
+ | \`emoji\` | Emoji prefix |
1285
+ | \`description\` | Short description |
1286
+ | \`confirm\` | Require confirmation before running (true/false) |
1287
+ | \`hidden\` | Hide from default listing (true/false) |
1288
+ | \`fullscreen\` | Use alternate screen buffer for ink actions (true/false) |
1289
+
1290
+ If \`name\` is omitted, it is inferred from the filename (e.g. \`deploy-staging.sh\` \u2192 "Deploy Staging").
1291
+
1292
+ Organize actions into categories using subdirectories:
1293
+
1294
+ \`\`\`
1295
+ .kadai/actions/
1296
+ hello.sh \u2192 id: "hello"
1297
+ database/
1298
+ migrate.sh \u2192 id: "database/migrate"
1299
+ reset.ts \u2192 id: "database/reset"
1300
+ \`\`\`
1301
+ `;
1302
+ }
1303
+ var init_init_wizard = () => {};
1304
+
1054
1305
  // src/components/Breadcrumbs.tsx
1055
1306
  import { Box, Text } from "ink";
1056
1307
  import { jsxDEV } from "react/jsx-dev-runtime";
@@ -1087,6 +1338,7 @@ var init_FullscreenProvider = () => {};
1087
1338
 
1088
1339
  // src/components/InkActionRenderer.tsx
1089
1340
  import { Box as Box2, Text as Text2 } from "ink";
1341
+ import { join as join12 } from "path";
1090
1342
  import React, { useEffect, useState } from "react";
1091
1343
  import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
1092
1344
  function InkActionRenderer({
@@ -1100,6 +1352,7 @@ function InkActionRenderer({
1100
1352
  let cancelled = false;
1101
1353
  (async () => {
1102
1354
  try {
1355
+ ensureKadaiResolvable(join12(cwd, "node_modules"));
1103
1356
  const mod = await import(action.filePath);
1104
1357
  if (cancelled)
1105
1358
  return;
@@ -1162,6 +1415,7 @@ function InkActionRenderer({
1162
1415
  }
1163
1416
  var InkActionErrorBoundary;
1164
1417
  var init_InkActionRenderer = __esm(() => {
1418
+ init_shared_deps();
1165
1419
  InkActionErrorBoundary = class InkActionErrorBoundary extends React.Component {
1166
1420
  constructor(props) {
1167
1421
  super(props);
@@ -1212,7 +1466,7 @@ function StatusBar() {
1212
1466
  var init_StatusBar = () => {};
1213
1467
 
1214
1468
  // src/hooks/useActions.ts
1215
- import { join as join9 } from "path";
1469
+ import { join as join13 } from "path";
1216
1470
  import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
1217
1471
  function useActions({ kadaiDir }) {
1218
1472
  const [actions, setActions] = useState2([]);
@@ -1225,7 +1479,7 @@ function useActions({ kadaiDir }) {
1225
1479
  (async () => {
1226
1480
  const cfg = await loadConfig(kadaiDir);
1227
1481
  setConfig(cfg);
1228
- const actionsDir = join9(kadaiDir, cfg.actionsDir ?? "actions");
1482
+ const actionsDir = join13(kadaiDir, cfg.actionsDir ?? "actions");
1229
1483
  const localActions = await loadActions(actionsDir);
1230
1484
  let allActions = [...localActions];
1231
1485
  const globalActions = await loadUserGlobalActions();
@@ -2267,6 +2521,7 @@ function MenuList({
2267
2521
  }
2268
2522
  const selected = i === selectedIndex;
2269
2523
  return /* @__PURE__ */ jsxDEV5(Box4, {
2524
+ width: "100%",
2270
2525
  children: [
2271
2526
  /* @__PURE__ */ jsxDEV5(Text4, {
2272
2527
  color: selected ? "cyan" : undefined,
@@ -2288,14 +2543,13 @@ function MenuList({
2288
2543
  dimColor: true,
2289
2544
  children: " \u27F3"
2290
2545
  }, undefined, false, undefined, this),
2546
+ /* @__PURE__ */ jsxDEV5(Box4, {
2547
+ flexGrow: 1
2548
+ }, undefined, false, undefined, this),
2291
2549
  item.description && /* @__PURE__ */ jsxDEV5(Text4, {
2292
2550
  dimColor: true,
2293
- children: [
2294
- " (",
2295
- item.description,
2296
- ")"
2297
- ]
2298
- }, undefined, true, undefined, this)
2551
+ children: item.description
2552
+ }, undefined, false, undefined, this)
2299
2553
  ]
2300
2554
  }, `${i}-${item.value}`, true, undefined, this);
2301
2555
  })
@@ -2548,172 +2802,8 @@ var init_app = __esm(() => {
2548
2802
  SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
2549
2803
  });
2550
2804
 
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 = () => {};
2698
-
2699
- // src/core/shared-deps.ts
2700
- var SHARED_DEPS = ["ink", "react", "@inkjs/ui"];
2701
- function registerSharedDeps() {
2702
- const escaped = SHARED_DEPS.map((d) => d.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
2703
- const filter = new RegExp(`^(${escaped.join("|")})(/.*)?$`);
2704
- Bun.plugin({
2705
- name: "kadai-shared-deps",
2706
- setup(build) {
2707
- build.onResolve({ filter }, (args) => {
2708
- try {
2709
- return { path: __require.resolve(args.path) };
2710
- } catch {
2711
- return;
2712
- }
2713
- });
2714
- }
2715
- });
2716
- }
2805
+ // src/cli.tsx
2806
+ init_shared_deps();
2717
2807
 
2718
2808
  // src/core/args.ts
2719
2809
  function parseArgs(argv) {
@@ -2724,6 +2814,9 @@ function parseArgs(argv) {
2724
2814
  if (command === "--version" || command === "-v") {
2725
2815
  return { type: "version" };
2726
2816
  }
2817
+ if (command === "--rerun" || command === "-r") {
2818
+ return { type: "rerun" };
2819
+ }
2727
2820
  switch (command) {
2728
2821
  case "list": {
2729
2822
  if (!argv.includes("--json")) {
@@ -2749,7 +2842,7 @@ function parseArgs(argv) {
2749
2842
  default:
2750
2843
  return {
2751
2844
  type: "error",
2752
- message: `Unknown command: ${command}. Available commands: list, run, sync, mcp, --version`
2845
+ message: `Unknown command: ${command}. Available commands: list, run, sync, mcp, --version, --rerun`
2753
2846
  };
2754
2847
  }
2755
2848
  }
@@ -2757,6 +2850,7 @@ function parseArgs(argv) {
2757
2850
  // src/cli.tsx
2758
2851
  init_commands();
2759
2852
  init_loader();
2853
+ init_last_action();
2760
2854
  registerSharedDeps();
2761
2855
  var cwd = process.cwd();
2762
2856
  var parsed = parseArgs(process.argv.slice(2));
@@ -2778,17 +2872,22 @@ if (parsed.type === "mcp") {
2778
2872
  await startMcpServer2(kadaiDir, cwd);
2779
2873
  await new Promise(() => {});
2780
2874
  }
2781
- if (parsed.type === "list" || parsed.type === "run" || parsed.type === "sync") {
2875
+ if (parsed.type === "list" || parsed.type === "run" || parsed.type === "sync" || parsed.type === "rerun") {
2782
2876
  const kadaiDir = findZcliDir(cwd);
2783
2877
  if (!kadaiDir) {
2784
2878
  process.stderr.write(`Error: No .kadai directory found. Run kadai to initialize.
2785
2879
  `);
2786
2880
  process.exit(1);
2787
2881
  }
2882
+ const { dirname: dirname2 } = await import("path");
2883
+ const { ensureClaudeIntegration: ensureClaudeIntegration2 } = await Promise.resolve().then(() => (init_init_wizard(), exports_init_wizard));
2884
+ await ensureClaudeIntegration2(dirname2(kadaiDir));
2788
2885
  if (parsed.type === "list") {
2789
2886
  await handleList({ kadaiDir, all: parsed.all });
2790
2887
  } else if (parsed.type === "run") {
2791
2888
  await handleRun({ kadaiDir, actionId: parsed.actionId, cwd });
2889
+ } else if (parsed.type === "rerun") {
2890
+ await handleRerun({ kadaiDir, cwd });
2792
2891
  } else {
2793
2892
  const { handleSync: handleSync2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
2794
2893
  await handleSync2({ kadaiDir });
@@ -2814,12 +2913,20 @@ if (!kadaiDir) {
2814
2913
  console.log(" Created .kadai/config.ts");
2815
2914
  if (result.sampleCreated)
2816
2915
  console.log(" Created .kadai/actions/hello.sh");
2817
- if (result.skillCreated)
2818
- console.log(" Created .claude/skills/kadai/SKILL.md");
2819
2916
  console.log(`
2820
2917
  Done! Run kadai again to get started.`);
2821
2918
  process.exit(0);
2822
2919
  }
2920
+ {
2921
+ const { dirname: dirname2 } = await import("path");
2922
+ const { ensureClaudeIntegration: ensureClaudeIntegration2 } = await Promise.resolve().then(() => (init_init_wizard(), exports_init_wizard));
2923
+ const projectRoot = dirname2(kadaiDir);
2924
+ const ensured = await ensureClaudeIntegration2(projectRoot);
2925
+ if (ensured.skillCreated)
2926
+ console.log("Created .claude/skills/kadai/SKILL.md");
2927
+ if (ensured.mcpConfigured)
2928
+ console.log("Configured kadai in .mcp.json");
2929
+ }
2823
2930
  function createStdinStream() {
2824
2931
  if (process.stdin.isTTY) {
2825
2932
  return process.stdin;
@@ -2901,6 +3008,7 @@ await instance.waitUntilExit();
2901
3008
  if (!selectedAction)
2902
3009
  process.exit(0);
2903
3010
  var action = selectedAction;
3011
+ await saveLastAction(kadaiDir, action.id);
2904
3012
  var config = await loadConfig2(kadaiDir);
2905
3013
  var cmd = resolveCommand2(action);
2906
3014
  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.6.0",
3
+ "version": "0.8.0",
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",