@pablozaiden/terminatui 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.
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  permissions:
17
17
  contents: read
18
- packages: write
18
+ id-token: write
19
19
  steps:
20
20
  - name: Determine release tag
21
21
  id: get_tag
@@ -52,6 +52,11 @@ jobs:
52
52
  curl -fsSL https://bun.com/install | bash
53
53
  echo "$HOME/.bun/bin" >> $GITHUB_PATH
54
54
 
55
+ - uses: actions/setup-node@v4
56
+ with:
57
+ node-version: '24'
58
+ registry-url: 'https://registry.npmjs.org'
59
+
55
60
  - name: Set package version
56
61
  run: |
57
62
  VERSION="${{ steps.get_tag.outputs.version }}"
@@ -73,6 +78,4 @@ jobs:
73
78
  run: bun run build
74
79
 
75
80
  - name: Publish Package
76
- run: bun publish --no-build
77
- env:
78
- BUN_PUBLISH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
81
+ run: npm publish --no-build
package/AGENTS.md ADDED
@@ -0,0 +1,31 @@
1
+ Default to using Bun instead of Node.js.
2
+
3
+ - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
4
+ - Use `bun test` instead of `jest` or `vitest`
5
+ - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
6
+ - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
7
+ - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
8
+ - Use `bunx <package> <command>` instead of `npx <package> <command>`
9
+ - Bun automatically loads .env, so don't use dotenv.
10
+
11
+ ## APIs
12
+
13
+ - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
14
+ - Bun.$`ls` instead of execa.
15
+
16
+ ## Testing
17
+
18
+ Always run `bun run build` before running tests, to make sure there are no build errors.
19
+ Use `bun run test` to run all the tests.
20
+
21
+ Always run `bun run test` when you think you are done making changes.
22
+
23
+ ```ts#index.test.ts
24
+ import { test, expect } from "bun:test";
25
+
26
+ test("hello world", () => {
27
+ expect(1).toBe(1);
28
+ });
29
+ ```
30
+
31
+ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
package/bun.lock ADDED
@@ -0,0 +1,236 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "@pablozaiden/terminatui",
7
+ "dependencies": {
8
+ "@opentui/react": "0.1.68",
9
+ "tslog": "^4.9.3",
10
+ },
11
+ "devDependencies": {
12
+ "@types/bun": "latest",
13
+ "@types/react": "^19",
14
+ "typescript": "5.9.3",
15
+ },
16
+ "peerDependencies": {
17
+ "typescript": "5.9.3",
18
+ },
19
+ },
20
+ },
21
+ "packages": {
22
+ "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],
23
+
24
+ "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
25
+
26
+ "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
27
+
28
+ "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="],
29
+
30
+ "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="],
31
+
32
+ "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="],
33
+
34
+ "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="],
35
+
36
+ "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="],
37
+
38
+ "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="],
39
+
40
+ "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="],
41
+
42
+ "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="],
43
+
44
+ "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="],
45
+
46
+ "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="],
47
+
48
+ "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="],
49
+
50
+ "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="],
51
+
52
+ "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="],
53
+
54
+ "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="],
55
+
56
+ "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="],
57
+
58
+ "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="],
59
+
60
+ "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="],
61
+
62
+ "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="],
63
+
64
+ "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="],
65
+
66
+ "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="],
67
+
68
+ "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="],
69
+
70
+ "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="],
71
+
72
+ "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="],
73
+
74
+ "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="],
75
+
76
+ "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="],
77
+
78
+ "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
79
+
80
+ "@opentui/core": ["@opentui/core@0.1.68", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.68", "@opentui/core-darwin-x64": "0.1.68", "@opentui/core-linux-arm64": "0.1.68", "@opentui/core-linux-x64": "0.1.68", "@opentui/core-win32-arm64": "0.1.68", "@opentui/core-win32-x64": "0.1.68", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-SZz5qNO+2lJ8jDEoTSieyXH23t49myu6NetLex+xzqOf67XsU6QKlDcw5oMmc3zrKvETXhgbBvlSnbyJNQoBMg=="],
81
+
82
+ "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.68", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ipPX2gavBLVtw3d8L4ZPJDLlEwIjIRNdlNlxu07rqSEGSfxD5s29yc+33wLAlYXbmnJDajOqm0Dx6HnlY1Y9Fg=="],
83
+
84
+ "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.68", "", { "os": "darwin", "cpu": "x64" }, "sha512-9dW0S9HINnuVjvC9QLj+S+329H7qEBQQtyJ9WHpykemokiJ5k4rnuDkfws5FxgTHIf/ddoYYTyPoGCS7WN5gsQ=="],
85
+
86
+ "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.68", "", { "os": "linux", "cpu": "arm64" }, "sha512-/el6TbSQriBUfPhIa6SBfCCc7tjU98Bnhf2+w0zKwQFBjf3F3kmnI42++YxedMGFmL7bRt3EUawGOkQRZZzFAg=="],
87
+
88
+ "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.68", "", { "os": "linux", "cpu": "x64" }, "sha512-9NzVI3GZzmICoIu3YhWBdkEt0KvY27m++tu/MqW+xb6fnvN74jZkRWzlgjTdM70obL4eUGQdvU08sDHgZjsIJw=="],
89
+
90
+ "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.68", "", { "os": "win32", "cpu": "arm64" }, "sha512-wrAeotyotOplUjQVBSxOGA8GCr9FWXSd6xCEo1PEGo/NjuAOtvHmKoENzyFEP0GzFsjvoUOyy2dZb987oFAn9A=="],
91
+
92
+ "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.68", "", { "os": "win32", "cpu": "x64" }, "sha512-w0yBjvzs/oMIwVdWICL4XlUrfsPoVXd4+RDqiuu+Xi/zD0UgANSTRY2asXca+gPe5zPHLsxvz1bAG0Z7uGtmyw=="],
93
+
94
+ "@opentui/react": ["@opentui/react@0.1.68", "", { "dependencies": { "@opentui/core": "0.1.68", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }, "sha512-dN2I60zINykyty31fFt0q8oZuod3O3LTygzdLPmXl0m4FLLYTiINqtvJqt12z4ikRnwMW+t2Ia4C17LnZn19Hw=="],
95
+
96
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
97
+
98
+ "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
99
+
100
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
101
+
102
+ "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
103
+
104
+ "@webgpu/types": ["@webgpu/types@0.1.68", "", {}, "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA=="],
105
+
106
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
107
+
108
+ "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
109
+
110
+ "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
111
+
112
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
113
+
114
+ "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
115
+
116
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
117
+
118
+ "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
119
+
120
+ "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
121
+
122
+ "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
123
+
124
+ "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eDgLN9teKTfmvrCqgwwmWNsNszxYs7IZdCqk0S1DCarvMhr4wcajoSBlA/nQA0/owwLduPTS8xxCnQp4/N/gDg=="],
125
+
126
+ "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-X+PjwJUWenUmdQBP8EtdItMyieQ6Nlpn+BH518oaouDiSnWj5+b0Y7DNDZJq7Ezom4EaxmqL/uGYZK3aCQ7CXg=="],
127
+
128
+ "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-zMLs2YIGB+/jxrYFXaFhVKX/GBt05UTF45lc9srcHc9JXGjEj+12CIo1CHLTAWatXMTqt0Jsu6ukWEoWVT/ayA=="],
129
+
130
+ "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA=="],
131
+
132
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
133
+
134
+ "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
135
+
136
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
137
+
138
+ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
139
+
140
+ "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
141
+
142
+ "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
143
+
144
+ "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
145
+
146
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
147
+
148
+ "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
149
+
150
+ "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
151
+
152
+ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
153
+
154
+ "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
155
+
156
+ "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
157
+
158
+ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
159
+
160
+ "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
161
+
162
+ "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="],
163
+
164
+ "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
165
+
166
+ "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
167
+
168
+ "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
169
+
170
+ "planck": ["planck@1.4.2", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew=="],
171
+
172
+ "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
173
+
174
+ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
175
+
176
+ "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
177
+
178
+ "react-devtools-core": ["react-devtools-core@7.0.1", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw=="],
179
+
180
+ "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
181
+
182
+ "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
183
+
184
+ "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
185
+
186
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
187
+
188
+ "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
189
+
190
+ "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
191
+
192
+ "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
193
+
194
+ "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="],
195
+
196
+ "stage-js": ["stage-js@1.0.0-alpha.17", "", {}, "sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw=="],
197
+
198
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
199
+
200
+ "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
201
+
202
+ "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
203
+
204
+ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
205
+
206
+ "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
207
+
208
+ "tslog": ["tslog@4.10.2", "", {}, "sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g=="],
209
+
210
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
211
+
212
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
213
+
214
+ "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
215
+
216
+ "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
217
+
218
+ "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
219
+
220
+ "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
221
+
222
+ "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
223
+
224
+ "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
225
+
226
+ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
227
+
228
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
229
+
230
+ "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
231
+
232
+ "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
233
+
234
+ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
235
+ }
236
+ }
@@ -0,0 +1,66 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type OptionSchema,
5
+ type OptionValues,
6
+ type CommandResult
7
+ } from "../../../../../src/index.ts";
8
+
9
+ const options = {
10
+ key: {
11
+ type: "string",
12
+ description: "Application configuration key to get",
13
+ required: true,
14
+ label: "Key",
15
+ order: 1,
16
+ group: "Required",
17
+ placeholder: "e.g., port, debug, logLevel",
18
+ },
19
+ } as const satisfies OptionSchema;
20
+
21
+ export class AppGetCommand extends Command<typeof options> {
22
+ readonly name = "get";
23
+ override displayName = "Get App Config";
24
+ readonly description = "Get an application configuration value";
25
+ readonly options = options;
26
+
27
+ override readonly actionLabel = "Get Value";
28
+
29
+ override readonly examples = [
30
+ { command: "config app get --key port", description: "Get app port" },
31
+ { command: "config app get --key logLevel", description: "Get log level" },
32
+ ];
33
+
34
+ override async execute(ctx: AppContext, opts: OptionValues<typeof options>): Promise<CommandResult> {
35
+ // Simulated app config store
36
+ const appConfig: Record<string, string | number | boolean> = {
37
+ port: 3000,
38
+ debug: true,
39
+ logLevel: "info",
40
+ maxConnections: 100,
41
+ timeout: 30000,
42
+ };
43
+
44
+ const value = appConfig[opts.key];
45
+
46
+ if (value === undefined) {
47
+ ctx.logger.warn(`Key "${opts.key}" not found in application configuration`);
48
+ return {
49
+ success: false,
50
+ message: `Key "${opts.key}" not found`,
51
+ };
52
+ }
53
+
54
+ ctx.logger.info(`Retrieved app.${opts.key} = ${value}`);
55
+ return {
56
+ success: true,
57
+ data: { key: opts.key, value },
58
+ };
59
+ }
60
+
61
+ override renderResult(result: CommandResult): string {
62
+ if (!result.success) return result.message || "Error";
63
+ const data = result.data as { key: string; value: string | number | boolean };
64
+ return `app.${data.key} = ${JSON.stringify(data.value)}`;
65
+ }
66
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type CommandResult
5
+ } from "../../../../../src/index.ts";
6
+ import { AppGetCommand } from "./get.ts";
7
+ import { AppSetCommand } from "./set.ts";
8
+
9
+ export class AppConfigCommand extends Command {
10
+ readonly name = "app";
11
+ override displayName = "App Settings";
12
+ readonly description = "Manage application configuration";
13
+ readonly options = {};
14
+
15
+ override readonly subCommands = [
16
+ new AppGetCommand(),
17
+ new AppSetCommand(),
18
+ ];
19
+
20
+ override execute(_ctx: AppContext): CommandResult {
21
+ console.log("Use 'config app <command>' for application configuration.");
22
+ console.log("Available: get, set");
23
+ return { success: true };
24
+ }
25
+ }
26
+
27
+ export { AppGetCommand, AppSetCommand };
@@ -0,0 +1,86 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type OptionSchema,
5
+ type OptionValues,
6
+ type CommandResult
7
+ } from "../../../../../src/index.ts";
8
+
9
+ const options = {
10
+ key: {
11
+ type: "string",
12
+ description: "Application configuration key to set",
13
+ required: true,
14
+ label: "Key",
15
+ order: 1,
16
+ group: "Required",
17
+ placeholder: "e.g., port, debug, logLevel",
18
+ },
19
+ value: {
20
+ type: "string",
21
+ description: "Value to set",
22
+ required: true,
23
+ label: "Value",
24
+ order: 2,
25
+ group: "Required",
26
+ placeholder: "Enter value...",
27
+ },
28
+ type: {
29
+ type: "string",
30
+ description: "Value type for parsing",
31
+ enum: ["string", "number", "boolean"] as const,
32
+ default: "string",
33
+ label: "Value Type",
34
+ order: 3,
35
+ group: "Options",
36
+ },
37
+ } as const satisfies OptionSchema;
38
+
39
+ export class AppSetCommand extends Command<typeof options> {
40
+ readonly name = "set";
41
+ override displayName = "Set App Config";
42
+ readonly description = "Set an application configuration value";
43
+ readonly options = options;
44
+
45
+ override readonly actionLabel = "Set Value";
46
+
47
+ override readonly examples = [
48
+ { command: "config app set --key port --value 8080 --type number", description: "Set app port" },
49
+ { command: "config app set --key debug --value false --type boolean", description: "Disable debug" },
50
+ ];
51
+
52
+ override async execute(ctx: AppContext, opts: OptionValues<typeof options>): Promise<CommandResult> {
53
+ let parsedValue: string | number | boolean = opts.value;
54
+
55
+ // Parse value based on type
56
+ if (opts.type === "number") {
57
+ parsedValue = Number(opts.value);
58
+ if (isNaN(parsedValue)) {
59
+ ctx.logger.error(`Invalid number value: "${opts.value}"`);
60
+ return {
61
+ success: false,
62
+ message: `Invalid number: "${opts.value}"`,
63
+ };
64
+ }
65
+ } else if (opts.type === "boolean") {
66
+ parsedValue = opts.value.toLowerCase() === "true";
67
+ }
68
+
69
+ ctx.logger.info(`Setting app.${opts.key} = ${JSON.stringify(parsedValue)}`);
70
+
71
+ // Simulate setting the value
72
+ await new Promise(resolve => setTimeout(resolve, 300));
73
+
74
+ ctx.logger.info(`Successfully updated application configuration`);
75
+ return {
76
+ success: true,
77
+ data: { key: opts.key, value: parsedValue, type: opts.type },
78
+ };
79
+ }
80
+
81
+ override renderResult(result: CommandResult): string {
82
+ if (!result.success) return result.message || "Error";
83
+ const data = result.data as { key: string; value: string | number | boolean; type: string };
84
+ return `✓ Set app.${data.key} = ${JSON.stringify(data.value)} (${data.type})`;
85
+ }
86
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type CommandResult
5
+ } from "../../../../src/index.ts";
6
+ import { UserConfigCommand } from "./user/index.ts";
7
+ import { AppConfigCommand } from "./app/index.ts";
8
+
9
+ export class ConfigCommand extends Command {
10
+ readonly name = "config";
11
+ override displayName = "Config";
12
+ readonly description = "Manage configuration (user and app settings)";
13
+ readonly options = {};
14
+
15
+ override readonly subCommands = [
16
+ new UserConfigCommand(),
17
+ new AppConfigCommand(),
18
+ ];
19
+
20
+ override readonly examples = [
21
+ { command: "config user get --key name", description: "Get user name" },
22
+ { command: "config user set --key theme --value dark", description: "Set user theme" },
23
+ { command: "config app get --key port", description: "Get app port" },
24
+ { command: "config app set --key debug --value true --type boolean", description: "Enable debug mode" },
25
+ ];
26
+
27
+ override execute(_ctx: AppContext): CommandResult {
28
+ console.log("Use 'config <category> <command>' to manage settings.");
29
+ console.log("Categories: user, app");
30
+ return { success: true };
31
+ }
32
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type OptionSchema,
5
+ type OptionValues,
6
+ type CommandResult
7
+ } from "../../../../../src/index.ts";
8
+
9
+ const options = {
10
+ key: {
11
+ type: "string",
12
+ description: "Configuration key to get",
13
+ required: true,
14
+ label: "Key",
15
+ order: 1,
16
+ group: "Required",
17
+ placeholder: "e.g., name, email, theme",
18
+ },
19
+ } as const satisfies OptionSchema;
20
+
21
+ export class UserGetCommand extends Command<typeof options> {
22
+ readonly name = "get";
23
+ override displayName = "Get User Config";
24
+ readonly description = "Get a user configuration value";
25
+ readonly options = options;
26
+
27
+ override readonly actionLabel = "Get Value";
28
+
29
+ override readonly examples = [
30
+ { command: "config user get --key name", description: "Get user name" },
31
+ { command: "config user get --key email", description: "Get user email" },
32
+ ];
33
+
34
+ override async execute(ctx: AppContext, opts: OptionValues<typeof options>): Promise<CommandResult> {
35
+ // Simulated user config store
36
+ const userConfig: Record<string, string> = {
37
+ name: "John Doe",
38
+ email: "john@example.com",
39
+ theme: "dark",
40
+ language: "en",
41
+ };
42
+
43
+ const value = userConfig[opts.key];
44
+
45
+ if (value === undefined) {
46
+ ctx.logger.warn(`Key "${opts.key}" not found in user configuration`);
47
+ return {
48
+ success: false,
49
+ message: `Key "${opts.key}" not found`,
50
+ };
51
+ }
52
+
53
+ ctx.logger.info(`Retrieved user.${opts.key} = ${value}`);
54
+ return {
55
+ success: true,
56
+ data: { key: opts.key, value },
57
+ };
58
+ }
59
+
60
+ override renderResult(result: CommandResult): string {
61
+ if (!result.success) return result.message || "Error";
62
+ const data = result.data as { key: string; value: string };
63
+ return `user.${data.key} = "${data.value}"`;
64
+ }
65
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type CommandResult
5
+ } from "../../../../../src/index.ts";
6
+ import { UserGetCommand } from "./get.ts";
7
+ import { UserSetCommand } from "./set.ts";
8
+
9
+ export class UserConfigCommand extends Command {
10
+ readonly name = "user";
11
+ override displayName = "User Settings";
12
+ readonly description = "Manage user configuration";
13
+ readonly options = {};
14
+
15
+ override readonly subCommands = [
16
+ new UserGetCommand(),
17
+ new UserSetCommand(),
18
+ ];
19
+
20
+ override execute(_ctx: AppContext): CommandResult {
21
+ console.log("Use 'config user <command>' for user configuration.");
22
+ console.log("Available: get, set");
23
+ return { success: true };
24
+ }
25
+ }
26
+
27
+ export { UserGetCommand, UserSetCommand };
@@ -0,0 +1,61 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type OptionSchema,
5
+ type OptionValues,
6
+ type CommandResult
7
+ } from "../../../../../src/index.ts";
8
+
9
+ const options = {
10
+ key: {
11
+ type: "string",
12
+ description: "Configuration key to set",
13
+ required: true,
14
+ label: "Key",
15
+ order: 1,
16
+ group: "Required",
17
+ placeholder: "e.g., name, email, theme",
18
+ },
19
+ value: {
20
+ type: "string",
21
+ description: "Value to set",
22
+ required: true,
23
+ label: "Value",
24
+ order: 2,
25
+ group: "Required",
26
+ placeholder: "Enter value...",
27
+ },
28
+ } as const satisfies OptionSchema;
29
+
30
+ export class UserSetCommand extends Command<typeof options> {
31
+ readonly name = "set";
32
+ override displayName = "Set User Config";
33
+ readonly description = "Set a user configuration value";
34
+ readonly options = options;
35
+
36
+ override readonly actionLabel = "Set Value";
37
+
38
+ override readonly examples = [
39
+ { command: "config user set --key name --value 'Jane Doe'", description: "Set user name" },
40
+ { command: "config user set --key theme --value light", description: "Set theme" },
41
+ ];
42
+
43
+ override async execute(ctx: AppContext, opts: OptionValues<typeof options>): Promise<CommandResult> {
44
+ ctx.logger.info(`Setting user.${opts.key} = "${opts.value}"`);
45
+
46
+ // Simulate setting the value
47
+ await new Promise(resolve => setTimeout(resolve, 300));
48
+
49
+ ctx.logger.info(`Successfully updated user configuration`);
50
+ return {
51
+ success: true,
52
+ data: { key: opts.key, value: opts.value },
53
+ };
54
+ }
55
+
56
+ override renderResult(result: CommandResult): string {
57
+ if (!result.success) return result.message || "Error";
58
+ const data = result.data as { key: string; value: string };
59
+ return `✓ Set user.${data.key} = "${data.value}"`;
60
+ }
61
+ }
@@ -37,6 +37,7 @@ const greetOptions = {
37
37
 
38
38
  export class GreetCommand extends Command<typeof greetOptions> {
39
39
  readonly name = "greet";
40
+ override displayName = "Greet";
40
41
  readonly description = "Greet someone with a friendly message";
41
42
  readonly options = greetOptions;
42
43
 
@@ -1,3 +1,4 @@
1
1
  export { GreetCommand } from "./greet.ts";
2
2
  export { MathCommand } from "./math.ts";
3
3
  export { StatusCommand } from "./status.ts";
4
+ export { ConfigCommand } from "./config/index.ts";
@@ -44,6 +44,7 @@ const mathOptions = {
44
44
 
45
45
  export class MathCommand extends Command<typeof mathOptions> {
46
46
  readonly name = "math";
47
+ override displayName = "Math Operations";
47
48
  readonly description = "Perform basic math operations";
48
49
  readonly options = mathOptions;
49
50
 
@@ -12,12 +12,14 @@ const statusOptions = {
12
12
  description: "Show detailed status",
13
13
  default: false,
14
14
  label: "Detailed",
15
+ alias: "d",
15
16
  order: 1,
16
17
  },
17
18
  } as const satisfies OptionSchema;
18
19
 
19
20
  export class StatusCommand extends Command<typeof statusOptions> {
20
21
  readonly name = "status";
22
+ override displayName = "Status";
21
23
  readonly description = "Show application status";
22
24
  readonly options = statusOptions;
23
25
 
@@ -39,7 +41,7 @@ export class StatusCommand extends Command<typeof statusOptions> {
39
41
  ` Uptime: ${data.uptime}`,
40
42
  ` Memory: ${data.memory}`,
41
43
  ` Platform: ${data.platform}`,
42
- ` Node: ${data.version}`,
44
+ ` Bun: ${data.version}`,
43
45
  ].join("\n");
44
46
  }
45
47
 
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { TuiApplication } from "../../src/index.ts";
16
- import { GreetCommand, MathCommand, StatusCommand } from "./commands/index.ts";
16
+ import { GreetCommand, MathCommand, StatusCommand, ConfigCommand } from "./commands/index.ts";
17
17
 
18
18
  class ExampleApp extends TuiApplication {
19
19
  constructor() {
@@ -24,6 +24,7 @@ class ExampleApp extends TuiApplication {
24
24
  new GreetCommand(),
25
25
  new MathCommand(),
26
26
  new StatusCommand(),
27
+ new ConfigCommand(),
27
28
  ],
28
29
  enableTui: true,
29
30
  });
@@ -246,58 +246,7 @@ export class DownloadCommand extends Command<typeof options, DownloadConfig> {
246
246
  }
247
247
  ```
248
248
 
249
- ## Step 4: Add Result Rendering
250
-
251
- ```typescript
252
- import React from "react";
253
- import { Text, Box } from "ink";
254
-
255
- export class DownloadCommand extends Command<typeof options, DownloadConfig> {
256
- // ... previous code ...
257
-
258
- /**
259
- * Custom TUI result display
260
- */
261
- override renderResult(result: CommandResult): React.ReactNode {
262
- if (!result.success && result.data?.cancelled) {
263
- return (
264
- <Box flexDirection="column">
265
- <Text color="yellow">⚠ Download Cancelled</Text>
266
- {result.data?.downloadedBytes > 0 && (
267
- <Text dimColor>
268
- Partial: {(result.data.downloadedBytes / 1024 / 1024).toFixed(2)} MB
269
- </Text>
270
- )}
271
- </Box>
272
- );
273
- }
274
-
275
- if (!result.success) {
276
- return <Text color="red">✗ {result.message}</Text>;
277
- }
278
-
279
- return (
280
- <Box flexDirection="column">
281
- <Text color="green">✓ Download Complete</Text>
282
- <Text>File: {result.data?.file}</Text>
283
- <Text>Size: {(result.data?.size / 1024 / 1024).toFixed(2)} MB</Text>
284
- </Box>
285
- );
286
- }
287
-
288
- /**
289
- * Copy file path to clipboard
290
- */
291
- override getClipboardContent(result: CommandResult): string | undefined {
292
- if (result.success && result.data?.file) {
293
- return result.data.file;
294
- }
295
- return undefined;
296
- }
297
- }
298
- ```
299
-
300
- ## Step 5: Create the Application
249
+ ## Step 4: Create the Application
301
250
 
302
251
  Create `src/index.ts`:
303
252
 
@@ -381,7 +330,6 @@ try {
381
330
  - Pass signal to `fetch` and other async APIs
382
331
  - Clean up resources on cancellation
383
332
  - Return meaningful results for cancelled operations
384
- - Render different UI for cancelled vs success vs error
385
333
 
386
334
  ## Next Steps
387
335
 
@@ -149,8 +149,6 @@ export class NotificationService {
149
149
  Create `src/commands/add.ts`:
150
150
 
151
151
  ```typescript
152
- import React from "react";
153
- import { Text, Box } from "ink";
154
152
  import {
155
153
  Command,
156
154
  ConfigValidationError,
@@ -231,49 +229,12 @@ export class AddCommand extends Command<typeof options, AddConfig> {
231
229
  message: `Created task: ${task.title} (${task.id})`,
232
230
  };
233
231
  }
234
-
235
- override renderResult(result: CommandResult): React.ReactNode {
236
- if (!result.success) {
237
- return <Text color="red">✗ {result.message}</Text>;
238
- }
239
-
240
- const task = result.data as Task;
241
- const priorityColor = {
242
- high: "red",
243
- medium: "yellow",
244
- low: "green",
245
- }[task.priority] as "red" | "yellow" | "green";
246
-
247
- return (
248
- <Box flexDirection="column" gap={1}>
249
- <Text color="green">✓ Task Created</Text>
250
- <Box>
251
- <Text dimColor>ID: </Text>
252
- <Text>{task.id}</Text>
253
- </Box>
254
- <Box>
255
- <Text dimColor>Title: </Text>
256
- <Text>{task.title}</Text>
257
- </Box>
258
- <Box>
259
- <Text dimColor>Priority: </Text>
260
- <Text color={priorityColor}>{task.priority}</Text>
261
- </Box>
262
- </Box>
263
- );
264
- }
265
-
266
- override getClipboardContent(result: CommandResult): string | undefined {
267
- return result.data?.id;
268
- }
269
232
  }
270
233
  ```
271
234
 
272
235
  Create `src/commands/list.ts`:
273
236
 
274
237
  ```typescript
275
- import React from "react";
276
- import { Text, Box } from "ink";
277
238
  import {
278
239
  Command,
279
240
  type AppContext,
@@ -344,45 +305,12 @@ export class ListCommand extends Command<typeof options, ListConfig> {
344
305
  message: `Found ${tasks.length} tasks`,
345
306
  };
346
307
  }
347
-
348
- override renderResult(result: CommandResult): React.ReactNode {
349
- const tasks = (result.data as Task[]) ?? [];
350
-
351
- if (tasks.length === 0) {
352
- return <Text dimColor>No tasks found</Text>;
353
- }
354
-
355
- const priorityColor = (p: string) =>
356
- ({ high: "red", medium: "yellow", low: "green" })[p] as "red" | "yellow" | "green";
357
-
358
- return (
359
- <Box flexDirection="column" gap={1}>
360
- <Text bold>Tasks ({tasks.length})</Text>
361
- {tasks.map((task) => (
362
- <Box key={task.id} gap={2}>
363
- <Text dimColor>[{task.id}]</Text>
364
- <Text color={task.status === "completed" ? "gray" : undefined}>
365
- {task.status === "completed" ? "✓" : "○"} {task.title}
366
- </Text>
367
- <Text color={priorityColor(task.priority)}>{task.priority}</Text>
368
- </Box>
369
- ))}
370
- </Box>
371
- );
372
- }
373
-
374
- override getClipboardContent(result: CommandResult): string | undefined {
375
- const tasks = result.data as Task[];
376
- return tasks?.map((t) => `${t.id}: ${t.title}`).join("\n");
377
- }
378
308
  }
379
309
  ```
380
310
 
381
311
  Create `src/commands/complete.ts`:
382
312
 
383
313
  ```typescript
384
- import React from "react";
385
- import { Text, Box } from "ink";
386
314
  import {
387
315
  Command,
388
316
  ConfigValidationError,
@@ -451,31 +379,12 @@ export class CompleteCommand extends Command<typeof options, CompleteConfig> {
451
379
  message: `Completed: ${task.title}`,
452
380
  };
453
381
  }
454
-
455
- override renderResult(result: CommandResult): React.ReactNode {
456
- if (!result.success) {
457
- return <Text color="red">✗ {result.message}</Text>;
458
- }
459
-
460
- const task = result.data as Task;
461
- return (
462
- <Box flexDirection="column">
463
- <Text color="green">✓ Task Completed</Text>
464
- <Text>{task.title}</Text>
465
- <Text dimColor>
466
- Completed at: {task.completedAt?.toLocaleString()}
467
- </Text>
468
- </Box>
469
- );
470
- }
471
382
  }
472
383
  ```
473
384
 
474
385
  Create `src/commands/stats.ts`:
475
386
 
476
387
  ```typescript
477
- import React from "react";
478
- import { Text, Box } from "ink";
479
388
  import {
480
389
  Command,
481
390
  type AppContext,
@@ -510,50 +419,6 @@ export class StatsCommand extends Command<typeof options> {
510
419
  message: `Total: ${stats.total}, Pending: ${stats.pending}, Completed: ${stats.completed}`,
511
420
  };
512
421
  }
513
-
514
- override renderResult(result: CommandResult): React.ReactNode {
515
- const stats = result.data as TaskStats;
516
-
517
- const completionRate = stats.total > 0
518
- ? Math.round((stats.completed / stats.total) * 100)
519
- : 0;
520
-
521
- return (
522
- <Box flexDirection="column" gap={1}>
523
- <Text bold>📊 Task Statistics</Text>
524
-
525
- <Box flexDirection="column">
526
- <Box gap={2}>
527
- <Text dimColor>Total:</Text>
528
- <Text>{stats.total}</Text>
529
- </Box>
530
- <Box gap={2}>
531
- <Text dimColor>Pending:</Text>
532
- <Text color="yellow">{stats.pending}</Text>
533
- </Box>
534
- <Box gap={2}>
535
- <Text dimColor>Completed:</Text>
536
- <Text color="green">{stats.completed}</Text>
537
- </Box>
538
- <Box gap={2}>
539
- <Text dimColor>Completion Rate:</Text>
540
- <Text color={completionRate >= 50 ? "green" : "yellow"}>
541
- {completionRate}%
542
- </Text>
543
- </Box>
544
- </Box>
545
-
546
- <Box flexDirection="column" marginTop={1}>
547
- <Text bold>By Priority:</Text>
548
- <Box gap={2}>
549
- <Text color="red">High: {stats.byPriority.high}</Text>
550
- <Text color="yellow">Medium: {stats.byPriority.medium}</Text>
551
- <Text color="green">Low: {stats.byPriority.low}</Text>
552
- </Box>
553
- </Box>
554
- </Box>
555
- );
556
- }
557
422
  }
558
423
  ```
559
424
 
@@ -647,7 +512,6 @@ bun start --verbose add "Debug task" --priority low
647
512
  | Display Names | `displayName = "Add Task"` |
648
513
  | Action Labels | `actionLabel = "Create Task"` |
649
514
  | Immediate Execution | `immediateExecution = true` |
650
- | Custom Results | `renderResult()` |
651
515
  | Clipboard Support | `getClipboardContent()` |
652
516
 
653
517
  ## What You Learned
@@ -655,7 +519,7 @@ bun start --verbose add "Debug task" --priority low
655
519
  - **Project Structure**: Organize code into commands, services, types
656
520
  - **Shared Services**: Database and notification services
657
521
  - **Command Groups**: Organize commands in TUI sidebar
658
- - **Full TUI Integration**: Custom rendering, clipboard, immediate execution
522
+ - **Full TUI Integration**: Clipboard, immediate execution
659
523
  - **Lifecycle Hooks**: `onBeforeRun` and `onAfterRun`
660
524
  - **Production Patterns**: Error handling, validation, logging
661
525
 
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@pablozaiden/terminatui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Terminal UI and Command Line Application Framework",
5
+ "repository": {
6
+ "url": "https://github.com/PabloZaiden/terminatui"
7
+ },
5
8
  "type": "module",
6
9
  "main": "src/index.ts",
7
10
  "types": "src/index.ts",
@@ -33,6 +33,13 @@ export class HelpCommand extends Command<OptionSchema> {
33
33
  this.appVersion = config.appVersion;
34
34
  }
35
35
 
36
+ /**
37
+ * Help command is CLI-only (auto-injected for CLI use, not shown in TUI).
38
+ */
39
+ override supportsTui(): boolean {
40
+ return false;
41
+ }
42
+
36
43
  override async execute(_ctx: AppContext): Promise<void> {
37
44
  let helpText: string;
38
45
 
@@ -91,6 +91,7 @@ function TuiAppContent({
91
91
  const [selectedCommand, setSelectedCommand] = useState<AnyCommand | null>(null);
92
92
  const [commandPath, setCommandPath] = useState<string[]>([]);
93
93
  const [commandSelectorIndex, setCommandSelectorIndex] = useState(0);
94
+ const [selectorIndexStack, setSelectorIndexStack] = useState<number[]>([]);
94
95
  const [selectedFieldIndex, setSelectedFieldIndex] = useState(0);
95
96
  const [editingField, setEditingField] = useState<string | null>(null);
96
97
  const [focusedSection, setFocusedSection] = useState<FocusedSection>(FocusedSection.Config);
@@ -133,9 +134,27 @@ function TuiAppContent({
133
134
  return buildCliCommand(name, commandPath, selectedCommand.options, configValues as OptionValues<OptionSchema>);
134
135
  }, [name, commandPath, selectedCommand, configValues]);
135
136
 
137
+ // Build breadcrumb with display names by traversing the command path
136
138
  const breadcrumb = useMemo(() => {
137
- return commandPath.length > 0 ? commandPath : undefined;
138
- }, [commandPath]);
139
+ if (commandPath.length === 0) return undefined;
140
+
141
+ const displayNames: string[] = [];
142
+ let current: AnyCommand[] = commands;
143
+
144
+ for (const pathPart of commandPath) {
145
+ const found = current.find((c) => c.name === pathPart);
146
+ if (found) {
147
+ displayNames.push(found.displayName ?? found.name);
148
+ if (found.subCommands) {
149
+ current = found.subCommands;
150
+ }
151
+ } else {
152
+ displayNames.push(pathPart);
153
+ }
154
+ }
155
+
156
+ return displayNames;
157
+ }, [commandPath, commands]);
139
158
 
140
159
  // Initialize config values when command changes
141
160
  const initializeConfigValues = useCallback((cmd: AnyCommand) => {
@@ -178,10 +197,22 @@ function TuiAppContent({
178
197
  setConfigValues(merged);
179
198
  }, [customFields, name]);
180
199
 
200
+ /**
201
+ * Check if a command has navigable subcommands (excluding commands that don't support TUI).
202
+ */
203
+ const hasNavigableSubCommands = useCallback((cmd: AnyCommand): boolean => {
204
+ if (!cmd.subCommands || cmd.subCommands.length === 0) return false;
205
+ // Filter out commands that don't support TUI
206
+ const navigable = cmd.subCommands.filter((sub) => sub.supportsTui());
207
+ return navigable.length > 0;
208
+ }, []);
209
+
181
210
  // Handlers
182
211
  const handleCommandSelect = useCallback((cmd: AnyCommand) => {
183
- // Check if command has subcommands to navigate into
184
- if (cmd.subCommands && cmd.subCommands.length > 0 && !cmd.supportsTui() && !cmd.supportsCli()) {
212
+ // Check if command has navigable subcommands (container commands)
213
+ if (hasNavigableSubCommands(cmd)) {
214
+ // Push current selection index to stack before navigating
215
+ setSelectorIndexStack((prev) => [...prev, commandSelectorIndex]);
185
216
  // Navigate into subcommands
186
217
  setCommandPath((prev) => [...prev, cmd.name]);
187
218
  setCommandSelectorIndex(0);
@@ -201,7 +232,7 @@ function TuiAppContent({
201
232
  } else {
202
233
  setMode(Mode.Config);
203
234
  }
204
- }, [initializeConfigValues]);
235
+ }, [initializeConfigValues, hasNavigableSubCommands, commandSelectorIndex]);
205
236
 
206
237
  const handleBack = useCallback(() => {
207
238
  if (mode === Mode.Running) {
@@ -242,12 +273,15 @@ function TuiAppContent({
242
273
  }
243
274
  resetExecutor();
244
275
  } else if (mode === Mode.CommandSelect && commandPath.length > 0) {
276
+ // Pop from selector index stack to restore previous selection
277
+ const previousIndex = selectorIndexStack[selectorIndexStack.length - 1] ?? 0;
278
+ setSelectorIndexStack((prev) => prev.slice(0, -1));
279
+ setCommandSelectorIndex(previousIndex);
245
280
  setCommandPath((prev) => prev.slice(0, -1));
246
- setCommandSelectorIndex(0);
247
281
  } else {
248
282
  onExit();
249
283
  }
250
- }, [mode, commandPath, selectedCommand, cancel, onExit, resetExecutor]);
284
+ }, [mode, commandPath, selectedCommand, selectorIndexStack, cancel, onExit, resetExecutor]);
251
285
 
252
286
  const handleRunCommand = useCallback(async (cmd?: AnyCommand) => {
253
287
  const cmdToRun = cmd ?? selectedCommand;
@@ -413,18 +447,21 @@ function TuiAppContent({
413
447
  { enabled: !editingField && !cliModalVisible }
414
448
  );
415
449
 
416
- // Get current commands for selector
450
+ // Get current commands for selector (excluding commands that don't support TUI)
417
451
  const currentCommands = useMemo(() => {
418
452
  if (commandPath.length === 0) {
419
- return commands;
453
+ return commands.filter((cmd) => cmd.supportsTui());
420
454
  }
421
455
 
422
- // Navigate to current path
456
+ // Navigate through the full path to find current level's subcommands
423
457
  let current: AnyCommand[] = commands;
424
- for (const pathPart of commandPath.slice(0, -1)) {
458
+ for (const pathPart of commandPath) {
425
459
  const found = current.find((c) => c.name === pathPart);
426
460
  if (found?.subCommands) {
427
- current = found.subCommands;
461
+ // Filter out commands that don't support TUI
462
+ current = found.subCommands.filter((sub) => sub.supportsTui());
463
+ } else {
464
+ break; // Path invalid or command has no subcommands
428
465
  }
429
466
  }
430
467
  return current;
@@ -477,7 +514,7 @@ function TuiAppContent({
477
514
  onSelectionChange={setCommandSelectorIndex}
478
515
  onSelect={handleCommandSelect}
479
516
  onExit={handleBack}
480
- breadcrumb={commandPath.length > 0 ? commandPath : undefined}
517
+ breadcrumb={breadcrumb}
481
518
  />
482
519
  );
483
520
 
@@ -486,7 +523,7 @@ function TuiAppContent({
486
523
  return (
487
524
  <box flexDirection="column" flexGrow={1}>
488
525
  <ConfigForm
489
- title={`Configure: ${selectedCommand.name}`}
526
+ title={`Configure: ${selectedCommand.displayName ?? selectedCommand.name}`}
490
527
  fieldConfigs={fieldConfigs}
491
528
  values={configValues}
492
529
  selectedIndex={selectedFieldIndex}
@@ -146,9 +146,16 @@ export function CommandSelector({
146
146
  }
147
147
 
148
148
  /**
149
- * Get mode indicator for a command (e.g., "[cli]", "[tui]", "[cli/tui]").
149
+ * Get mode indicator for a command (e.g., "[cli]", "[tui]", "" for subcommands).
150
150
  */
151
151
  function getModeIndicator(command: Command): string {
152
+ // Show navigation indicator for container commands with navigable subcommands
153
+ // (excluding commands that don't support TUI)
154
+ const navigableSubCommands = command.subCommands?.filter((sub) => sub.supportsTui()) ?? [];
155
+ if (navigableSubCommands.length > 0) {
156
+ return "→";
157
+ }
158
+
152
159
  const cli = command.supportsCli();
153
160
  const tui = command.supportsTui();
154
161