kadai 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +105 -5
  2. package/dist/cli.js +104 -352
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -2,16 +2,116 @@
2
2
 
3
3
  # kadai
4
4
 
5
- To install dependencies:
5
+ A terminal UI for discovering and running project scripts. Drop scripts into `.kadai/actions/`, and kadai gives you a fuzzy-searchable menu to run them.
6
+
7
+ ## Getting Started
8
+
9
+ <img width="950" height="205" alt="image" src="https://github.com/user-attachments/assets/b694bfaa-146b-41c7-a44c-d197c7cea08e" />
6
10
 
7
11
  ```bash
8
- bun install
12
+ bunx kadai
13
+ # OR
14
+ npx kadai
15
+ ```
16
+
17
+ On first run, kadai creates a `.kadai/` directory with a sample action and config file. Run it again to open the interactive menu.
18
+
19
+ ### Directory Structure
20
+
21
+ ```
22
+ .kadai/
23
+ ├── config.ts # Optional configuration (env vars, actions dir)
24
+ └── actions/ # Your scripts live here
25
+ ├── hello.sh
26
+ ├── deploy.ts
27
+ └── database/ # Subdirectories become categories
28
+ ├── reset.sh
29
+ └── seed.py
30
+ ```
31
+
32
+ ## Features
33
+
34
+ ### Supported Runtimes
35
+
36
+ | Extension | Runtime |
37
+ |--------------------|----------|
38
+ | `.sh`, `.bash` | bash |
39
+ | `.ts`, `.js`, `.mjs` | bun |
40
+ | `.py` | python |
41
+
42
+ Shebangs are respected — if your script has `#!/usr/bin/env python3`, kadai uses that directly. Otherwise it finds the best available interpreter automatically (e.g. `uv run` before `python3` for `.py` files).
43
+
44
+ ### Frontmatter
45
+
46
+ Add metadata as comments in the first 20 lines of any script:
47
+
48
+ ```bash
49
+ #!/bin/bash
50
+ # kadai:name Deploy Staging
51
+ # kadai:emoji 🚀
52
+ # kadai:description Deploy the app to staging
53
+ # kadai:confirm true
54
+ ```
55
+
56
+ For JS/TS, use `//` comments:
57
+
58
+ ```typescript
59
+ // kadai:name Reset Database
60
+ // kadai:emoji 🗑️
61
+ // kadai:confirm true
9
62
  ```
10
63
 
11
- To run:
64
+ | Key | Type | Description |
65
+ |---------------|---------|--------------------------------------------|
66
+ | `name` | string | Display name (inferred from filename if omitted) |
67
+ | `emoji` | string | Emoji prefix in menus |
68
+ | `description` | string | Short description |
69
+ | `confirm` | boolean | Require confirmation before running |
70
+ | `hidden` | boolean | Hide from menu (still runnable via CLI) |
71
+ | `interactive` | boolean | Hand over the full terminal to the script |
72
+
73
+ ### Interactive Scripts
74
+
75
+ 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.).
76
+
77
+ ### Ink UI Actions (Planned)
78
+
79
+ `.tsx` files will be able to export an Ink component that renders directly inside kadai's UI, enabling rich interactive interfaces (forms, progress bars, tables) without spawning a subprocess.
80
+
81
+ ### Config
82
+
83
+ `.kadai/config.ts` lets you set environment variables injected into all actions:
84
+
85
+ ```typescript
86
+ export default {
87
+ env: {
88
+ DATABASE_URL: "postgres://localhost:5432/myapp",
89
+ APP_ENV: "development",
90
+ },
91
+ };
92
+ ```
93
+
94
+ ## CLI
12
95
 
13
96
  ```bash
14
- bun run index.ts
97
+ kadai # Interactive menu
98
+ kadai list --json # List actions as JSON
99
+ kadai list --json --all # Include hidden actions
100
+ kadai run <action-id> # Run an action directly
15
101
  ```
16
102
 
17
- This project was created using `bun init` in bun v1.3.2. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
103
+ ## AI
104
+
105
+ kadai is designed to work well with AI coding agents like Claude Code.
106
+
107
+ ### How It Works
108
+
109
+ - `kadai list --json` gives agents a machine-readable list of available project actions
110
+ - `kadai run <action-id>` runs actions non-interactively (confirmation prompts auto-skip in non-TTY)
111
+ - Agents can discover what's available, then run the right action — no hardcoded commands
112
+
113
+ ### Skill Installation
114
+
115
+ If your project uses Claude Code (has a `.claude/` directory or `CLAUDE.md`), kadai automatically creates a skill file at `.claude/skills/kadai/SKILL.md` on first run. This teaches Claude Code how to discover and run your project's actions.
116
+
117
+ The skill is non-user-invocable — Claude Code reads it automatically and uses kadai when relevant, without needing explicit prompts.
package/dist/cli.js CHANGED
@@ -86,9 +86,6 @@ function parseMetadataFromContent(content) {
86
86
  case "hidden":
87
87
  meta.hidden = value.trim() === "true";
88
88
  break;
89
- case "interactive":
90
- meta.interactive = value.trim() === "true";
91
- break;
92
89
  }
93
90
  }
94
91
  return meta;
@@ -108,15 +105,13 @@ async function extractMetadata(filePath) {
108
105
  emoji: frontmatter.emoji,
109
106
  description: frontmatter.description,
110
107
  confirm: frontmatter.confirm ?? false,
111
- hidden: frontmatter.hidden ?? false,
112
- interactive: frontmatter.interactive ?? false
108
+ hidden: frontmatter.hidden ?? false
113
109
  };
114
110
  }
115
111
  return {
116
112
  name: inferNameFromFilename(filename),
117
113
  confirm: false,
118
- hidden: false,
119
- interactive: false
114
+ hidden: false
120
115
  };
121
116
  }
122
117
  var META_PATTERN, MAX_SCAN_LINES = 20;
@@ -251,31 +246,9 @@ var init_loader = __esm(() => {
251
246
  // src/core/runner.ts
252
247
  var exports_runner = {};
253
248
  __export(exports_runner, {
254
- runAction: () => runAction,
255
249
  resolveCommand: () => resolveCommand,
256
250
  parseShebangCommand: () => parseShebangCommand
257
251
  });
258
- function runAction(action, options) {
259
- const { cwd, config } = options;
260
- const cmd = resolveCommand(action);
261
- const env = {
262
- ...process.env,
263
- ...config?.env ?? {}
264
- };
265
- const proc = Bun.spawn(cmd, {
266
- cwd,
267
- stdin: "pipe",
268
- stdout: "pipe",
269
- stderr: "pipe",
270
- env
271
- });
272
- return {
273
- proc,
274
- stdout: proc.stdout,
275
- stderr: proc.stderr,
276
- stdin: proc.stdin
277
- };
278
- }
279
252
  function cachedWhich(bin) {
280
253
  if (whichCache.has(bin))
281
254
  return whichCache.get(bin) ?? null;
@@ -20759,118 +20732,6 @@ var init_build3 = __esm(async () => {
20759
20732
  init_types();
20760
20733
  });
20761
20734
 
20762
- // src/hooks/useActionRunner.ts
20763
- function useActionRunner({
20764
- action,
20765
- cwd: cwd2,
20766
- config,
20767
- enabled = true,
20768
- onRunningChange
20769
- }) {
20770
- const [lines, setLines] = import_react60.useState([]);
20771
- const [exitCode, setExitCode] = import_react60.useState(null);
20772
- const [running, setRunning] = import_react60.useState(false);
20773
- const doneRef = import_react60.useRef(false);
20774
- const onRunningChangeRef = import_react60.useRef(onRunningChange);
20775
- onRunningChangeRef.current = onRunningChange;
20776
- const stdinRef = import_react60.useRef(null);
20777
- use_input_default((input, key) => {
20778
- const stdin = stdinRef.current;
20779
- if (!stdin)
20780
- return;
20781
- try {
20782
- if (key.return) {
20783
- stdin.write(`
20784
- `);
20785
- } else if (key.backspace || key.delete) {
20786
- stdin.write("\x7F");
20787
- } else if (key.tab) {
20788
- stdin.write("\t");
20789
- } else if (key.escape) {
20790
- stdin.write("\x1B");
20791
- } else if (key.ctrl && input) {
20792
- const code = input.toUpperCase().charCodeAt(0) - 64;
20793
- if (code > 0 && code < 32) {
20794
- stdin.write(String.fromCharCode(code));
20795
- }
20796
- } else if (input) {
20797
- stdin.write(input);
20798
- }
20799
- stdin.flush();
20800
- } catch {}
20801
- }, { isActive: running });
20802
- import_react60.useEffect(() => {
20803
- if (!action || !enabled)
20804
- return;
20805
- let aborted = false;
20806
- setLines([]);
20807
- setExitCode(null);
20808
- setRunning(true);
20809
- onRunningChangeRef.current?.(true);
20810
- doneRef.current = false;
20811
- let handle;
20812
- try {
20813
- handle = runAction(action, { cwd: cwd2, config });
20814
- } catch {
20815
- setRunning(false);
20816
- onRunningChangeRef.current?.(false);
20817
- setExitCode(-1);
20818
- doneRef.current = true;
20819
- return;
20820
- }
20821
- stdinRef.current = handle.stdin;
20822
- const readStream = async (stream) => {
20823
- const reader = stream.getReader();
20824
- const decoder = new TextDecoder;
20825
- try {
20826
- while (true) {
20827
- const { done, value } = await reader.read();
20828
- if (done || aborted)
20829
- break;
20830
- const text = decoder.decode(value);
20831
- const newLines = text.split(`
20832
- `).filter((l) => l.length > 0);
20833
- if (newLines.length > 0) {
20834
- setLines((prev) => [...prev, ...newLines]);
20835
- }
20836
- }
20837
- } catch {}
20838
- };
20839
- readStream(handle.stdout);
20840
- readStream(handle.stderr);
20841
- handle.proc.exited.then((code) => {
20842
- stdinRef.current = null;
20843
- try {
20844
- handle.stdin.end();
20845
- } catch {}
20846
- if (aborted)
20847
- return;
20848
- setExitCode(code);
20849
- setRunning(false);
20850
- onRunningChangeRef.current?.(false);
20851
- doneRef.current = true;
20852
- });
20853
- return () => {
20854
- aborted = true;
20855
- stdinRef.current = null;
20856
- try {
20857
- handle.stdin.end();
20858
- } catch {}
20859
- onRunningChangeRef.current?.(false);
20860
- try {
20861
- handle.proc.kill();
20862
- } catch {}
20863
- };
20864
- }, [action?.id, cwd2, config, enabled]);
20865
- return { lines, exitCode, running, doneRef };
20866
- }
20867
- var import_react60;
20868
- var init_useActionRunner = __esm(async () => {
20869
- init_runner();
20870
- await init_build2();
20871
- import_react60 = __toESM(require_react(), 1);
20872
- });
20873
-
20874
20735
  // node_modules/react/cjs/react-jsx-dev-runtime.development.js
20875
20736
  var require_react_jsx_dev_runtime_development = __commonJS((exports) => {
20876
20737
  var React29 = __toESM(require_react());
@@ -21094,106 +20955,50 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
21094
20955
  }
21095
20956
  });
21096
20957
 
21097
- // src/components/ActionOutput.tsx
21098
- function ActionOutput({
21099
- action,
21100
- cwd: cwd2,
21101
- config,
21102
- onRunningChange
21103
- }) {
21104
- const { lines, exitCode, running } = useActionRunner({
21105
- action,
21106
- cwd: cwd2,
21107
- config,
21108
- onRunningChange
21109
- });
21110
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21111
- flexDirection: "column",
21112
- children: [
21113
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21114
- marginBottom: 1,
21115
- children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21116
- bold: true,
21117
- children: [
21118
- action.meta.emoji ? `${action.meta.emoji} ` : "",
21119
- action.meta.name
21120
- ]
21121
- }, undefined, true, undefined, this)
21122
- }, undefined, false, undefined, this),
21123
- lines.map((line, i) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21124
- children: line
21125
- }, i, false, undefined, this)),
21126
- running && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21127
- dimColor: true,
21128
- children: "Running..."
21129
- }, undefined, false, undefined, this),
21130
- !running && exitCode !== null && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21131
- marginTop: 1,
21132
- children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21133
- color: exitCode === 0 ? "green" : "red",
21134
- children: [
21135
- exitCode === 0 ? "\u2713" : "\u2717",
21136
- " exit code ",
21137
- exitCode
21138
- ]
21139
- }, undefined, true, undefined, this)
21140
- }, undefined, false, undefined, this)
21141
- ]
21142
- }, undefined, true, undefined, this);
21143
- }
21144
- var jsx_dev_runtime;
21145
- var init_ActionOutput = __esm(async () => {
21146
- await __promiseAll([
21147
- init_build2(),
21148
- init_useActionRunner()
21149
- ]);
21150
- jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
21151
- });
21152
-
21153
20958
  // src/components/Breadcrumbs.tsx
21154
20959
  function Breadcrumbs({ path }) {
21155
20960
  const parts = ["kadai", ...path];
21156
- return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
20961
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21157
20962
  marginBottom: 1,
21158
- children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
20963
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21159
20964
  dimColor: true,
21160
20965
  children: parts.join(" > ")
21161
20966
  }, undefined, false, undefined, this)
21162
20967
  }, undefined, false, undefined, this);
21163
20968
  }
21164
- var jsx_dev_runtime2;
20969
+ var jsx_dev_runtime;
21165
20970
  var init_Breadcrumbs = __esm(async () => {
21166
20971
  await init_build2();
21167
- jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
20972
+ jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
21168
20973
  });
21169
20974
 
21170
20975
  // src/components/StatusBar.tsx
21171
20976
  function StatusBar() {
21172
20977
  const hints = "\u2191\u2193/j/k navigate / search esc back q quit";
21173
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
20978
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
21174
20979
  marginTop: 1,
21175
20980
  flexDirection: "column",
21176
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
20981
+ children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
21177
20982
  dimColor: true,
21178
20983
  children: hints
21179
20984
  }, undefined, false, undefined, this)
21180
20985
  }, undefined, false, undefined, this);
21181
20986
  }
21182
- var jsx_dev_runtime3;
20987
+ var jsx_dev_runtime2;
21183
20988
  var init_StatusBar = __esm(async () => {
21184
20989
  await init_build2();
21185
- jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
20990
+ jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
21186
20991
  });
21187
20992
 
21188
20993
  // src/hooks/useActions.ts
21189
20994
  import { join as join4 } from "path";
21190
20995
  function useActions({ kadaiDir }) {
21191
- const [actions, setActions] = import_react61.useState([]);
21192
- const [config, setConfig] = import_react61.useState({});
21193
- const [loading, setLoading] = import_react61.useState(true);
21194
- const actionsRef = import_react61.useRef(actions);
20996
+ const [actions, setActions] = import_react60.useState([]);
20997
+ const [config, setConfig] = import_react60.useState({});
20998
+ const [loading, setLoading] = import_react60.useState(true);
20999
+ const actionsRef = import_react60.useRef(actions);
21195
21000
  actionsRef.current = actions;
21196
- import_react61.useEffect(() => {
21001
+ import_react60.useEffect(() => {
21197
21002
  (async () => {
21198
21003
  const cfg = await loadConfig(kadaiDir);
21199
21004
  setConfig(cfg);
@@ -21205,11 +21010,11 @@ function useActions({ kadaiDir }) {
21205
21010
  }, [kadaiDir]);
21206
21011
  return { actions, actionsRef, config, loading };
21207
21012
  }
21208
- var import_react61;
21013
+ var import_react60;
21209
21014
  var init_useActions = __esm(() => {
21210
21015
  init_config();
21211
21016
  init_loader();
21212
- import_react61 = __toESM(require_react(), 1);
21017
+ import_react60 = __toESM(require_react(), 1);
21213
21018
  });
21214
21019
 
21215
21020
  // src/hooks/useKeyboard.ts
@@ -21242,11 +21047,6 @@ function useKeyboard({
21242
21047
  }) {
21243
21048
  use_input_default((input, key) => {
21244
21049
  const screen = stackRef.current.at(-1);
21245
- if (screen.type === "output") {
21246
- if (key.escape || key.return)
21247
- popScreen();
21248
- return;
21249
- }
21250
21050
  if (screen.type !== "menu")
21251
21051
  return;
21252
21052
  if (searchActiveRef.current) {
@@ -21309,7 +21109,7 @@ function useKeyboard({
21309
21109
  return;
21310
21110
  }
21311
21111
  if (key.return) {
21312
- selectCurrentItem(screen, actionsRef, searchQueryRef, selectedIndexRef, getMenuItems, computeFiltered, pushScreen);
21112
+ selectCurrentItem(screen, actionsRef, searchQueryRef, selectedIndexRef, getMenuItems, computeFiltered, pushScreen, onRunInteractive);
21313
21113
  return;
21314
21114
  }
21315
21115
  if (key.upArrow || input === "k") {
@@ -21341,10 +21141,8 @@ function selectCurrentItem(screen, actionsRef, searchQueryRef, selectedIndexRef,
21341
21141
  const action = actionsRef.current.find((a) => a.id === item.value);
21342
21142
  if (action?.meta.confirm) {
21343
21143
  pushScreen({ type: "confirm", actionId: item.value });
21344
- } else if (action?.meta.interactive && onRunInteractive) {
21144
+ } else if (action) {
21345
21145
  onRunInteractive(action);
21346
- } else {
21347
- pushScreen({ type: "output", actionId: item.value });
21348
21146
  }
21349
21147
  }
21350
21148
  }
@@ -21354,8 +21152,8 @@ var init_useKeyboard = __esm(async () => {
21354
21152
 
21355
21153
  // src/hooks/useNavigation.ts
21356
21154
  function useNavigation({ onExit, onNavigate }) {
21357
- const [stack, setStack] = import_react62.useState([{ type: "menu", path: [] }]);
21358
- const stackRef = import_react62.useRef(stack);
21155
+ const [stack, setStack] = import_react61.useState([{ type: "menu", path: [] }]);
21156
+ const stackRef = import_react61.useRef(stack);
21359
21157
  stackRef.current = stack;
21360
21158
  const currentScreen = stack.at(-1);
21361
21159
  const pushScreen = (screen) => {
@@ -21387,9 +21185,9 @@ function useNavigation({ onExit, onNavigate }) {
21387
21185
  stackRef
21388
21186
  };
21389
21187
  }
21390
- var import_react62;
21188
+ var import_react61;
21391
21189
  var init_useNavigation = __esm(() => {
21392
- import_react62 = __toESM(require_react(), 1);
21190
+ import_react61 = __toESM(require_react(), 1);
21393
21191
  });
21394
21192
 
21395
21193
  // node_modules/fuzzysort/fuzzysort.js
@@ -22124,17 +21922,17 @@ var require_fuzzysort = __commonJS((exports, module) => {
22124
21922
 
22125
21923
  // src/hooks/useRefState.ts
22126
21924
  function useRefState(initial) {
22127
- const [state, setState] = import_react63.useState(initial);
22128
- const ref = import_react63.useRef(initial);
21925
+ const [state, setState] = import_react62.useState(initial);
21926
+ const ref = import_react62.useRef(initial);
22129
21927
  const set = (value) => {
22130
21928
  ref.current = value;
22131
21929
  setState(value);
22132
21930
  };
22133
21931
  return [state, ref, set];
22134
21932
  }
22135
- var import_react63;
21933
+ var import_react62;
22136
21934
  var init_useRefState = __esm(() => {
22137
- import_react63 = __toESM(require_react(), 1);
21935
+ import_react62 = __toESM(require_react(), 1);
22138
21936
  });
22139
21937
 
22140
21938
  // src/hooks/useSearch.ts
@@ -22193,12 +21991,12 @@ function MenuList({
22193
21991
  selectedIndex
22194
21992
  }) {
22195
21993
  const hasAnyNew = items.some((item) => item.isNew);
22196
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(jsx_dev_runtime4.Fragment, {
21994
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(jsx_dev_runtime3.Fragment, {
22197
21995
  children: items.map((item, i) => {
22198
21996
  if (item.type === "separator") {
22199
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
21997
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
22200
21998
  marginTop: i > 0 ? 1 : 0,
22201
- children: /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
21999
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22202
22000
  dimColor: true,
22203
22001
  bold: true,
22204
22002
  children: item.label
@@ -22206,16 +22004,16 @@ function MenuList({
22206
22004
  }, `sep-${item.value}`, false, undefined, this);
22207
22005
  }
22208
22006
  const selected = i === selectedIndex;
22209
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22007
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
22210
22008
  children: [
22211
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22009
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22212
22010
  color: selected ? "cyan" : undefined,
22213
22011
  children: selected ? "\u276F " : " "
22214
22012
  }, undefined, false, undefined, this),
22215
- hasAnyNew && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22013
+ hasAnyNew && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22216
22014
  children: item.isNew ? "\u2728 " : " "
22217
22015
  }, undefined, false, undefined, this),
22218
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22016
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22219
22017
  color: selected ? "cyan" : undefined,
22220
22018
  children: [
22221
22019
  item.type === "category" ? "\uD83D\uDCC1 " : "",
@@ -22224,7 +22022,7 @@ function MenuList({
22224
22022
  item.type === "category" ? " \u25B8" : ""
22225
22023
  ]
22226
22024
  }, undefined, true, undefined, this),
22227
- item.description && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22025
+ item.description && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22228
22026
  dimColor: true,
22229
22027
  children: [
22230
22028
  " (",
@@ -22237,16 +22035,15 @@ function MenuList({
22237
22035
  })
22238
22036
  }, undefined, false, undefined, this);
22239
22037
  }
22240
- function App2({ cwd: cwd2, kadaiDir, onRunInteractive }) {
22038
+ function App2({ kadaiDir, onRunAction }) {
22241
22039
  const { exit } = use_app_default();
22242
- const [isProcessRunning, setIsProcessRunning] = import_react64.useState(false);
22243
- const handleRunInteractive = onRunInteractive ? (action) => {
22244
- onRunInteractive(action);
22040
+ const handleRunAction = (action) => {
22041
+ onRunAction(action);
22245
22042
  exit();
22246
- } : undefined;
22043
+ };
22247
22044
  const search = useSearch();
22248
22045
  const nav = useNavigation({ onExit: exit, onNavigate: search.resetSearch });
22249
- const { actions, actionsRef, config, loading } = useActions({
22046
+ const { actions, actionsRef, loading } = useActions({
22250
22047
  kadaiDir
22251
22048
  });
22252
22049
  useKeyboard({
@@ -22264,11 +22061,11 @@ function App2({ cwd: cwd2, kadaiDir, onRunInteractive }) {
22264
22061
  exit,
22265
22062
  getMenuItems: buildMenuItems,
22266
22063
  computeFiltered: search.computeFiltered,
22267
- isActive: nav.currentScreen.type !== "confirm" && !isProcessRunning,
22268
- onRunInteractive: handleRunInteractive
22064
+ isActive: nav.currentScreen.type !== "confirm",
22065
+ onRunInteractive: handleRunAction
22269
22066
  });
22270
22067
  if (loading) {
22271
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22068
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22272
22069
  dimColor: true,
22273
22070
  children: "Loading actions..."
22274
22071
  }, undefined, false, undefined, this);
@@ -22281,38 +22078,38 @@ function App2({ cwd: cwd2, kadaiDir, onRunInteractive }) {
22281
22078
  search.setSelectedIndex(1);
22282
22079
  search.selectedIndexRef.current = 1;
22283
22080
  }
22284
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22081
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
22285
22082
  flexDirection: "column",
22286
22083
  children: [
22287
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Breadcrumbs, {
22084
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Breadcrumbs, {
22288
22085
  path: menuPath
22289
22086
  }, undefined, false, undefined, this),
22290
- search.searchActive && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22087
+ search.searchActive && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
22291
22088
  marginBottom: 1,
22292
22089
  children: [
22293
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22090
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22294
22091
  children: [
22295
22092
  "/ ",
22296
22093
  search.searchQuery
22297
22094
  ]
22298
22095
  }, undefined, true, undefined, this),
22299
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22096
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22300
22097
  dimColor: true,
22301
22098
  children: "\u2588"
22302
22099
  }, undefined, false, undefined, this)
22303
22100
  ]
22304
22101
  }, undefined, true, undefined, this),
22305
- filteredItems.length === 0 && menuItems.length === 0 ? /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22102
+ filteredItems.length === 0 && menuItems.length === 0 ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22306
22103
  dimColor: true,
22307
22104
  children: "No actions found"
22308
- }, undefined, false, undefined, this) : filteredItems.length === 0 ? /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22105
+ }, undefined, false, undefined, this) : filteredItems.length === 0 ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22309
22106
  dimColor: true,
22310
22107
  children: "No matching items"
22311
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(MenuList, {
22108
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(MenuList, {
22312
22109
  items: filteredItems,
22313
22110
  selectedIndex: search.selectedIndex
22314
22111
  }, undefined, false, undefined, this),
22315
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(StatusBar, {}, undefined, false, undefined, this)
22112
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(StatusBar, {}, undefined, false, undefined, this)
22316
22113
  ]
22317
22114
  }, undefined, true, undefined, this);
22318
22115
  }
@@ -22320,29 +22117,21 @@ function App2({ cwd: cwd2, kadaiDir, onRunInteractive }) {
22320
22117
  const { actionId } = nav.currentScreen;
22321
22118
  const action = actions.find((a) => a.id === actionId);
22322
22119
  if (!action)
22323
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22120
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22324
22121
  color: "red",
22325
22122
  children: "Action not found"
22326
22123
  }, undefined, false, undefined, this);
22327
22124
  const handleConfirm = () => {
22328
- if (action.meta.interactive && handleRunInteractive) {
22329
- handleRunInteractive(action);
22330
- return;
22331
- }
22332
- nav.setStack((s) => {
22333
- const next = [...s.slice(0, -1), { type: "output", actionId }];
22334
- nav.stackRef.current = next;
22335
- return next;
22336
- });
22125
+ handleRunAction(action);
22337
22126
  };
22338
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22127
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
22339
22128
  flexDirection: "column",
22340
- children: /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22129
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
22341
22130
  children: [
22342
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22131
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22343
22132
  children: [
22344
22133
  "Run ",
22345
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22134
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
22346
22135
  bold: true,
22347
22136
  children: action.meta.name
22348
22137
  }, undefined, false, undefined, this),
@@ -22350,7 +22139,7 @@ function App2({ cwd: cwd2, kadaiDir, onRunInteractive }) {
22350
22139
  " "
22351
22140
  ]
22352
22141
  }, undefined, true, undefined, this),
22353
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(ConfirmInput, {
22142
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ConfirmInput, {
22354
22143
  onConfirm: handleConfirm,
22355
22144
  onCancel: () => nav.popScreen()
22356
22145
  }, undefined, false, undefined, this)
@@ -22358,33 +22147,6 @@ function App2({ cwd: cwd2, kadaiDir, onRunInteractive }) {
22358
22147
  }, undefined, true, undefined, this)
22359
22148
  }, undefined, false, undefined, this);
22360
22149
  }
22361
- if (nav.currentScreen.type === "output") {
22362
- const { actionId } = nav.currentScreen;
22363
- const action = actions.find((a) => a.id === actionId);
22364
- if (!action)
22365
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22366
- color: "red",
22367
- children: "Action not found"
22368
- }, undefined, false, undefined, this);
22369
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22370
- flexDirection: "column",
22371
- children: [
22372
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(ActionOutput, {
22373
- action,
22374
- cwd: cwd2,
22375
- config,
22376
- onRunningChange: setIsProcessRunning
22377
- }, undefined, false, undefined, this),
22378
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
22379
- marginTop: 1,
22380
- children: /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
22381
- dimColor: true,
22382
- children: "Press enter or esc to go back"
22383
- }, undefined, false, undefined, this)
22384
- }, undefined, false, undefined, this)
22385
- ]
22386
- }, undefined, true, undefined, this);
22387
- }
22388
22150
  return null;
22389
22151
  }
22390
22152
  function isRecentlyAdded(action) {
@@ -22458,7 +22220,7 @@ function buildMenuItems(actions, path) {
22458
22220
  });
22459
22221
  return items;
22460
22222
  }
22461
- var import_react64, jsx_dev_runtime4, SEVEN_DAYS_MS;
22223
+ var jsx_dev_runtime3, SEVEN_DAYS_MS;
22462
22224
  var init_app = __esm(async () => {
22463
22225
  init_useActions();
22464
22226
  init_useNavigation();
@@ -22466,13 +22228,11 @@ var init_app = __esm(async () => {
22466
22228
  await __promiseAll([
22467
22229
  init_build3(),
22468
22230
  init_build2(),
22469
- init_ActionOutput(),
22470
22231
  init_Breadcrumbs(),
22471
22232
  init_StatusBar(),
22472
22233
  init_useKeyboard()
22473
22234
  ]);
22474
- import_react64 = __toESM(require_react(), 1);
22475
- jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
22235
+ jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
22476
22236
  SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
22477
22237
  });
22478
22238
 
@@ -22819,55 +22579,47 @@ function createStdinStream() {
22819
22579
  });
22820
22580
  return charStream;
22821
22581
  }
22822
- var pendingInteractiveAction = null;
22823
- while (true) {
22824
- pendingInteractiveAction = null;
22825
- const stdinStream = createStdinStream();
22826
- const instance = render2(React29.createElement(App3, {
22827
- cwd: cwd2,
22828
- kadaiDir,
22829
- onRunInteractive: (action2) => {
22830
- pendingInteractiveAction = action2;
22831
- }
22832
- }), {
22833
- stdin: stdinStream,
22834
- stdout: process.stdout,
22835
- stderr: process.stderr
22836
- });
22837
- await instance.waitUntilExit();
22838
- if (!pendingInteractiveAction)
22839
- break;
22840
- const action = pendingInteractiveAction;
22841
- const config = await loadConfig2(kadaiDir);
22842
- const cmd = resolveCommand2(action);
22843
- const env3 = {
22844
- ...process.env,
22845
- ...config.env ?? {}
22846
- };
22847
- console.log(`${action.meta.emoji ? `${action.meta.emoji} ` : ""}${action.meta.name}
22582
+ var selectedAction = null;
22583
+ var stdinStream = createStdinStream();
22584
+ var instance = render2(React29.createElement(App3, {
22585
+ kadaiDir,
22586
+ onRunAction: (action) => {
22587
+ selectedAction = action;
22588
+ }
22589
+ }), {
22590
+ stdin: stdinStream,
22591
+ stdout: process.stdout,
22592
+ stderr: process.stderr
22593
+ });
22594
+ await instance.waitUntilExit();
22595
+ if (!selectedAction)
22596
+ process.exit(0);
22597
+ var action = selectedAction;
22598
+ var config = await loadConfig2(kadaiDir);
22599
+ var cmd = resolveCommand2(action);
22600
+ var env3 = {
22601
+ ...process.env,
22602
+ ...config.env ?? {}
22603
+ };
22604
+ console.log(`${action.meta.emoji ? `${action.meta.emoji} ` : ""}${action.meta.name}
22848
22605
  `);
22849
- process.stdin.pause();
22850
- const proc = Bun.spawn(cmd, {
22851
- cwd: cwd2,
22852
- stdout: "inherit",
22853
- stderr: "inherit",
22854
- stdin: "inherit",
22855
- env: env3
22856
- });
22857
- const exitCode = await proc.exited;
22858
- process.stdin.resume();
22859
- const color = exitCode === 0 ? "\x1B[32m" : "\x1B[31m";
22860
- const symbol = exitCode === 0 ? "\u2713" : "\u2717";
22861
- console.log(`
22862
- ${color}${symbol} exit code ${exitCode}\x1B[0m`);
22863
- console.log(`
22864
- Press enter to return to menu...`);
22865
- await new Promise((resolve) => {
22866
- const onData = () => {
22867
- process.stdin.removeListener("data", onData);
22868
- resolve();
22869
- };
22870
- process.stdin.on("data", onData);
22871
- });
22872
- }
22873
- process.exit(0);
22606
+ process.stdin.removeAllListeners("data");
22607
+ var proc = Bun.spawn(cmd, {
22608
+ cwd: cwd2,
22609
+ stdout: "inherit",
22610
+ stderr: "inherit",
22611
+ stdin: "pipe",
22612
+ env: env3
22613
+ });
22614
+ var forwardStdin = (data) => {
22615
+ try {
22616
+ const converted = Buffer.from(data.toString().replace(/\r/g, `
22617
+ `));
22618
+ proc.stdin.write(converted);
22619
+ proc.stdin.flush();
22620
+ } catch {}
22621
+ };
22622
+ process.stdin.on("data", forwardStdin);
22623
+ process.stdin.resume();
22624
+ var exitCode = await proc.exited;
22625
+ process.exit(exitCode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kadai",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "kadai": "./dist/cli.js"
@@ -11,7 +11,9 @@
11
11
  "lint": "biome check ./src ./test",
12
12
  "lint:fix": "biome check --write ./src ./test"
13
13
  },
14
- "files": ["dist/"],
14
+ "files": [
15
+ "dist/"
16
+ ],
15
17
  "devDependencies": {
16
18
  "@biomejs/biome": "^2.3.14",
17
19
  "@types/bun": "latest",