termcast 1.3.48 → 1.3.50

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 (255) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +12 -0
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.js +5 -40
  5. package/dist/cli.js.map +1 -1
  6. package/dist/colors.d.ts +7 -7
  7. package/dist/colors.js +7 -7
  8. package/dist/compile.d.ts +6 -1
  9. package/dist/compile.d.ts.map +1 -1
  10. package/dist/compile.js +45 -26
  11. package/dist/compile.js.map +1 -1
  12. package/dist/components/actions.js +1 -1
  13. package/dist/components/actions.js.map +1 -1
  14. package/dist/components/bar-chart.d.ts +38 -0
  15. package/dist/components/bar-chart.d.ts.map +1 -0
  16. package/dist/components/bar-chart.js +158 -0
  17. package/dist/components/bar-chart.js.map +1 -0
  18. package/dist/components/bar-graph.d.ts +41 -0
  19. package/dist/components/bar-graph.d.ts.map +1 -0
  20. package/dist/components/bar-graph.js +95 -0
  21. package/dist/components/bar-graph.js.map +1 -0
  22. package/dist/components/detail.d.ts.map +1 -1
  23. package/dist/components/detail.js +5 -7
  24. package/dist/components/detail.js.map +1 -1
  25. package/dist/components/footer.d.ts.map +1 -1
  26. package/dist/components/footer.js +8 -9
  27. package/dist/components/footer.js.map +1 -1
  28. package/dist/components/form/date-picker.d.ts.map +1 -1
  29. package/dist/components/form/date-picker.js +7 -1
  30. package/dist/components/form/date-picker.js.map +1 -1
  31. package/dist/components/form/dropdown.d.ts.map +1 -1
  32. package/dist/components/form/dropdown.js +10 -2
  33. package/dist/components/form/dropdown.js.map +1 -1
  34. package/dist/components/form/index.d.ts.map +1 -1
  35. package/dist/components/form/index.js +4 -5
  36. package/dist/components/form/index.js.map +1 -1
  37. package/dist/components/form/use-form-navigation.d.ts.map +1 -1
  38. package/dist/components/form/use-form-navigation.js +6 -0
  39. package/dist/components/form/use-form-navigation.js.map +1 -1
  40. package/dist/components/graph.d.ts +111 -0
  41. package/dist/components/graph.d.ts.map +1 -0
  42. package/dist/components/graph.js +392 -0
  43. package/dist/components/graph.js.map +1 -0
  44. package/dist/components/icon.js +5 -5
  45. package/dist/components/icon.js.map +1 -1
  46. package/dist/components/list.d.ts +53 -5
  47. package/dist/components/list.d.ts.map +1 -1
  48. package/dist/components/list.js +125 -71
  49. package/dist/components/list.js.map +1 -1
  50. package/dist/components/loading-bar.js +3 -3
  51. package/dist/components/loading-bar.js.map +1 -1
  52. package/dist/components/loading-text.d.ts +1 -1
  53. package/dist/components/loading-text.d.ts.map +1 -1
  54. package/dist/components/loading-text.js +3 -1
  55. package/dist/components/loading-text.js.map +1 -1
  56. package/dist/components/metadata.js +2 -2
  57. package/dist/components/metadata.js.map +1 -1
  58. package/dist/components/row.d.ts +10 -0
  59. package/dist/components/row.d.ts.map +1 -0
  60. package/dist/components/row.js +12 -0
  61. package/dist/components/row.js.map +1 -0
  62. package/dist/components/table.d.ts +57 -0
  63. package/dist/components/table.d.ts.map +1 -0
  64. package/dist/components/table.js +365 -0
  65. package/dist/components/table.js.map +1 -0
  66. package/dist/descendants.js +13 -13
  67. package/dist/descendants.js.map +1 -1
  68. package/dist/examples/bar-graph-weekly.d.ts +2 -0
  69. package/dist/examples/bar-graph-weekly.d.ts.map +1 -0
  70. package/dist/examples/bar-graph-weekly.js +95 -0
  71. package/dist/examples/bar-graph-weekly.js.map +1 -0
  72. package/dist/examples/components-weird-places.d.ts +2 -0
  73. package/dist/examples/components-weird-places.d.ts.map +1 -0
  74. package/dist/examples/components-weird-places.js +46 -0
  75. package/dist/examples/components-weird-places.js.map +1 -0
  76. package/dist/examples/graph-bar-chart.d.ts +2 -0
  77. package/dist/examples/graph-bar-chart.d.ts.map +1 -0
  78. package/dist/examples/graph-bar-chart.js +270 -0
  79. package/dist/examples/graph-bar-chart.js.map +1 -0
  80. package/dist/examples/graph-multi-series.d.ts +2 -0
  81. package/dist/examples/graph-multi-series.d.ts.map +1 -0
  82. package/dist/examples/graph-multi-series.js +23 -0
  83. package/dist/examples/graph-multi-series.js.map +1 -0
  84. package/dist/examples/graph-polymarket.d.ts +2 -0
  85. package/dist/examples/graph-polymarket.d.ts.map +1 -0
  86. package/dist/examples/graph-polymarket.js +109 -0
  87. package/dist/examples/graph-polymarket.js.map +1 -0
  88. package/dist/examples/graph-row.d.ts +2 -0
  89. package/dist/examples/graph-row.d.ts.map +1 -0
  90. package/dist/examples/graph-row.js +226 -0
  91. package/dist/examples/graph-row.js.map +1 -0
  92. package/dist/examples/graph-styles.d.ts +2 -0
  93. package/dist/examples/graph-styles.d.ts.map +1 -0
  94. package/dist/examples/graph-styles.js +316 -0
  95. package/dist/examples/graph-styles.js.map +1 -0
  96. package/dist/examples/list-accessory-table.d.ts +2 -0
  97. package/dist/examples/list-accessory-table.d.ts.map +1 -0
  98. package/dist/examples/list-accessory-table.js +46 -0
  99. package/dist/examples/list-accessory-table.js.map +1 -0
  100. package/dist/examples/list-item-accessories.d.ts +2 -0
  101. package/dist/examples/list-item-accessories.d.ts.map +1 -0
  102. package/dist/examples/list-item-accessories.js +27 -0
  103. package/dist/examples/list-item-accessories.js.map +1 -0
  104. package/dist/examples/list-no-actions.d.ts +2 -0
  105. package/dist/examples/list-no-actions.d.ts.map +1 -0
  106. package/dist/examples/list-no-actions.js +7 -0
  107. package/dist/examples/list-no-actions.js.map +1 -0
  108. package/dist/examples/simple-detail-table.d.ts +2 -0
  109. package/dist/examples/simple-detail-table.d.ts.map +1 -0
  110. package/dist/examples/simple-detail-table.js +45 -0
  111. package/dist/examples/simple-detail-table.js.map +1 -0
  112. package/dist/examples/simple-graph.d.ts +2 -0
  113. package/dist/examples/simple-graph.d.ts.map +1 -0
  114. package/dist/examples/simple-graph.js +32 -0
  115. package/dist/examples/simple-graph.js.map +1 -0
  116. package/dist/examples/simple-table-wrap.d.ts +2 -0
  117. package/dist/examples/simple-table-wrap.d.ts.map +1 -0
  118. package/dist/examples/simple-table-wrap.js +37 -0
  119. package/dist/examples/simple-table-wrap.js.map +1 -0
  120. package/dist/examples/table-edge-cases.d.ts +2 -0
  121. package/dist/examples/table-edge-cases.d.ts.map +1 -0
  122. package/dist/examples/table-edge-cases.js +70 -0
  123. package/dist/examples/table-edge-cases.js.map +1 -0
  124. package/dist/examples/table-flex-grow.d.ts +2 -0
  125. package/dist/examples/table-flex-grow.d.ts.map +1 -0
  126. package/dist/examples/table-flex-grow.js +18 -0
  127. package/dist/examples/table-flex-grow.js.map +1 -0
  128. package/dist/extensions/dev.d.ts.map +1 -1
  129. package/dist/extensions/dev.js +5 -1
  130. package/dist/extensions/dev.js.map +1 -1
  131. package/dist/globals.d.ts +1 -0
  132. package/dist/globals.d.ts.map +1 -1
  133. package/dist/globals.js +2 -0
  134. package/dist/globals.js.map +1 -1
  135. package/dist/index.d.ts +10 -0
  136. package/dist/index.d.ts.map +1 -1
  137. package/dist/index.js +10 -0
  138. package/dist/index.js.map +1 -1
  139. package/dist/internal/date-picker-widget.d.ts.map +1 -1
  140. package/dist/internal/date-picker-widget.js +4 -0
  141. package/dist/internal/date-picker-widget.js.map +1 -1
  142. package/dist/internal/providers.d.ts.map +1 -1
  143. package/dist/internal/providers.js +1 -3
  144. package/dist/internal/providers.js.map +1 -1
  145. package/dist/markdown-utils.d.ts +22 -1
  146. package/dist/markdown-utils.d.ts.map +1 -1
  147. package/dist/markdown-utils.js +66 -1
  148. package/dist/markdown-utils.js.map +1 -1
  149. package/dist/opentui.d.ts +4 -0
  150. package/dist/opentui.d.ts.map +1 -0
  151. package/dist/opentui.js +3 -0
  152. package/dist/opentui.js.map +1 -0
  153. package/dist/release.d.ts +2 -1
  154. package/dist/release.d.ts.map +1 -1
  155. package/dist/release.js +2 -1
  156. package/dist/release.js.map +1 -1
  157. package/dist/state.d.ts +1 -0
  158. package/dist/state.d.ts.map +1 -1
  159. package/dist/state.js +1 -1
  160. package/dist/state.js.map +1 -1
  161. package/dist/theme.d.ts +1 -0
  162. package/dist/theme.d.ts.map +1 -1
  163. package/dist/theme.js +13 -0
  164. package/dist/theme.js.map +1 -1
  165. package/dist/themes/nerv.json +227 -0
  166. package/dist/themes/termcast.json +72 -71
  167. package/dist/themes.d.ts +2 -1
  168. package/dist/themes.d.ts.map +1 -1
  169. package/dist/themes.js +7 -5
  170. package/dist/themes.js.map +1 -1
  171. package/dist/utils.d.ts.map +1 -1
  172. package/dist/utils.js +3 -0
  173. package/dist/utils.js.map +1 -1
  174. package/package.json +13 -5
  175. package/src/build.tsx +13 -0
  176. package/src/cli.tsx +5 -49
  177. package/src/colors.tsx +7 -7
  178. package/src/compile.tsx +52 -29
  179. package/src/components/actions.tsx +1 -1
  180. package/src/components/bar-chart.tsx +271 -0
  181. package/src/components/bar-graph.tsx +214 -0
  182. package/src/components/detail.tsx +7 -8
  183. package/src/components/footer.tsx +14 -15
  184. package/src/components/form/date-picker.tsx +9 -0
  185. package/src/components/form/dropdown.tsx +13 -3
  186. package/src/components/form/index.tsx +4 -6
  187. package/src/components/form/use-form-navigation.tsx +6 -0
  188. package/src/components/graph.tsx +506 -0
  189. package/src/components/icon.tsx +5 -5
  190. package/src/components/list.tsx +210 -102
  191. package/src/components/loading-bar.tsx +3 -3
  192. package/src/components/loading-text.tsx +4 -2
  193. package/src/components/metadata.tsx +2 -2
  194. package/src/components/row.tsx +31 -0
  195. package/src/components/table.tsx +511 -0
  196. package/src/descendants.tsx +13 -13
  197. package/src/examples/action-shortcut.vitest.tsx +1 -1
  198. package/src/examples/actions-context.vitest.tsx +1 -1
  199. package/src/examples/bar-graph-weekly.tsx +264 -0
  200. package/src/examples/bar-graph-weekly.vitest.tsx +275 -0
  201. package/src/examples/detail-metadata-showcase.vitest.tsx +8 -8
  202. package/src/examples/form-basic.vitest.tsx +239 -0
  203. package/src/examples/form-dropdown.vitest.tsx +29 -29
  204. package/src/examples/form-tagpicker.vitest.tsx +27 -27
  205. package/src/examples/github.vitest.tsx +4 -4
  206. package/src/examples/graph-bar-chart.tsx +408 -0
  207. package/src/examples/graph-bar-chart.vitest.tsx +283 -0
  208. package/src/examples/graph-multi-series.tsx +36 -0
  209. package/src/examples/graph-multi-series.vitest.tsx +89 -0
  210. package/src/examples/graph-polymarket.tsx +182 -0
  211. package/src/examples/graph-polymarket.vitest.tsx +130 -0
  212. package/src/examples/graph-row.tsx +347 -0
  213. package/src/examples/graph-row.vitest.tsx +295 -0
  214. package/src/examples/graph-styles.tsx +457 -0
  215. package/src/examples/graph-styles.vitest.tsx +322 -0
  216. package/src/examples/list-accessory-table.tsx +77 -0
  217. package/src/examples/list-detail-metadata.vitest.tsx +21 -21
  218. package/src/examples/list-dropdown-default.vitest.tsx +12 -12
  219. package/src/examples/list-item-accessories.tsx +106 -0
  220. package/src/examples/list-item-accessories.vitest.tsx +115 -0
  221. package/src/examples/list-no-actions.tsx +18 -0
  222. package/src/examples/list-no-actions.vitest.tsx +97 -0
  223. package/src/examples/list-spacing-mode.vitest.tsx +6 -6
  224. package/src/examples/list-with-detail.vitest.tsx +92 -92
  225. package/src/examples/list-with-dropdown.vitest.tsx +49 -6
  226. package/src/examples/list-with-sections.vitest.tsx +61 -56
  227. package/src/examples/simple-detail-markdown.vitest.tsx +21 -17
  228. package/src/examples/simple-detail-table.tsx +65 -0
  229. package/src/examples/simple-detail-table.vitest.tsx +200 -0
  230. package/src/examples/simple-graph.tsx +51 -0
  231. package/src/examples/simple-graph.vitest.tsx +124 -0
  232. package/src/examples/simple-grid.vitest.tsx +3 -3
  233. package/src/examples/simple-list-search.vitest.tsx +65 -0
  234. package/src/examples/simple-navigation.vitest.tsx +3 -3
  235. package/src/examples/simple-table-wrap.tsx +55 -0
  236. package/src/examples/simple-table-wrap.vitest.tsx +91 -0
  237. package/src/examples/store.vitest.tsx +1 -1
  238. package/src/examples/table-edge-cases.tsx +72 -0
  239. package/src/examples/table-edge-cases.vitest.tsx +307 -0
  240. package/src/examples/table-flex-grow.tsx +53 -0
  241. package/src/examples/table-flex-grow.vitest.tsx +124 -0
  242. package/src/extensions/dev.tsx +7 -1
  243. package/src/globals.ts +3 -0
  244. package/src/index.tsx +31 -0
  245. package/src/internal/date-picker-widget.tsx +4 -0
  246. package/src/internal/providers.tsx +1 -4
  247. package/src/markdown-utils.tsx +82 -1
  248. package/src/opentui.tsx +5 -0
  249. package/src/release.tsx +3 -0
  250. package/src/state.tsx +2 -1
  251. package/src/theme.tsx +14 -0
  252. package/src/themes/nerv.json +231 -0
  253. package/src/themes/termcast.json +75 -71
  254. package/src/themes.ts +8 -5
  255. package/src/utils.tsx +4 -0
package/src/cli.tsx CHANGED
@@ -23,54 +23,6 @@ import packageJson from '../package.json'
23
23
 
24
24
  const cli = goke('termcast')
25
25
 
26
- // Auto-update check
27
- async function checkForUpdates() {
28
- try {
29
- const currentVersion = packageJson.version
30
-
31
- // Fetch latest release info from GitHub
32
- const response = await fetch(
33
- 'https://api.github.com/repos/remorses/termcast/releases/latest',
34
- )
35
- if (!response.ok) {
36
- return
37
- }
38
-
39
- const latestRelease = (await response.json()) as { tag_name?: string }
40
- const latestVersion =
41
- latestRelease.tag_name?.replace('termcast@', '') ||
42
- latestRelease.tag_name?.replace('v', '')
43
-
44
- // Compare versions
45
- if (latestVersion && latestVersion !== currentVersion) {
46
- // Run the install script in background
47
- const updateProcess = spawn(
48
- 'bash',
49
- ['-c', 'curl -sf https://termcast.app/install | bash'],
50
- {
51
- detached: true,
52
- stdio: 'ignore',
53
- },
54
- )
55
-
56
- updateProcess.on('exit', async (code) => {
57
- if (code === 0) {
58
- // Show toast notification only on successful completion
59
- await showToast({
60
- title: 'Update available',
61
- message: `Restart to use the new version ${latestVersion}`,
62
- style: Toast.Style.Success,
63
- })
64
- }
65
- })
66
-
67
- // updateProcess.unref()
68
- }
69
- } catch (error) {
70
- // Silently fail - don't interrupt the user's workflow
71
- logger.log('Failed to check for updates:', error)
72
- }
73
- }
74
26
 
75
27
  // TODO: re-enable auto-update check once install script temp dir issue is fixed
76
28
  // checkForUpdates()
@@ -259,6 +211,7 @@ cli
259
211
  .command('compile [path]', 'Compile the extension to a standalone executable')
260
212
  .option('-o, --outfile <path>', 'Output file path for the executable')
261
213
  .option('--minify', 'Minify the output')
214
+ .option('--entry <file>', 'Custom entry file (instead of auto-generated one)')
262
215
  .action(async (extensionPath, options) => {
263
216
  extensionPath = path.resolve(extensionPath || process.cwd())
264
217
 
@@ -268,6 +221,7 @@ cli
268
221
  extensionPath,
269
222
  outfile: options.outfile,
270
223
  minify: options.minify,
224
+ entry: options.entry,
271
225
  })
272
226
 
273
227
  console.log(`\nExecutable created: ${result.outfile}`)
@@ -282,7 +236,8 @@ cli
282
236
  cli
283
237
  .command('release [path]', 'Build and publish extension to GitHub releases')
284
238
  .option('--single', 'Only compile for the current platform')
285
- .action(async (extensionPath: string, options: { single?: boolean }) => {
239
+ .option('--entry <file>', 'Custom entry file (instead of auto-generated one)')
240
+ .action(async (extensionPath: string, options: { single?: boolean; entry?: string }) => {
286
241
  extensionPath = path.resolve(extensionPath || process.cwd())
287
242
 
288
243
  console.log('Building and releasing extension...')
@@ -290,6 +245,7 @@ cli
290
245
  const result = await releaseExtension({
291
246
  extensionPath,
292
247
  single: options.single,
248
+ entry: options.entry,
293
249
  })
294
250
 
295
251
  console.log(`\nRelease complete: ${result.tag}`)
package/src/colors.tsx CHANGED
@@ -1,11 +1,11 @@
1
1
  export enum Color {
2
- Blue = '#0080FF',
3
- Green = '#00FF80',
4
- Magenta = '#FF00FF',
5
- Orange = '#FF8000',
6
- Purple = '#8000FF',
7
- Red = '#FF0000',
8
- Yellow = '#FFFF00',
2
+ Blue = '#5CB8FF',
3
+ Green = '#34EE7F',
4
+ Magenta = '#F07FFF',
5
+ Orange = '#FF9F43',
6
+ Purple = '#BF8FFF',
7
+ Red = '#FF7B7B',
8
+ Yellow = '#FFD534',
9
9
  PrimaryText = '#FFFFFF',
10
10
  SecondaryText = '#999999',
11
11
  }
package/src/compile.tsx CHANGED
@@ -5,29 +5,26 @@ import { logger } from './logger'
5
5
  import { getCommandsWithFiles } from './package-json'
6
6
  import { swiftLoaderPlugin } from './swift-loader'
7
7
 
8
+ // compile.tsx lives at termcast/src/compile.tsx, so __dirname is termcast/src/
9
+ const termcastRoot = path.resolve(__dirname, '..')
10
+
8
11
  const raycastAliasPlugin: BunPlugin = {
9
12
  name: 'raycast-to-termcast',
10
13
  setup(build) {
11
14
  build.onResolve({ filter: /@raycast\/api/ }, () => ({
12
- path: require.resolve('termcast/src/index'),
15
+ path: path.join(__dirname, 'index.tsx'),
13
16
  }))
14
-
15
17
  build.onResolve({ filter: /@raycast\/utils/ }, () => ({
16
18
  path: require.resolve('@termcast/utils'),
17
19
  }))
18
-
20
+ // termcast and termcast/* — resolve directly from the package source
19
21
  build.onResolve({ filter: /^termcast/ }, (args) => ({
20
- path: require.resolve(args.path),
22
+ path: args.path === 'termcast'
23
+ ? path.join(__dirname, 'index.tsx')
24
+ : require.resolve(args.path, { paths: [termcastRoot] }),
21
25
  }))
22
-
23
- build.onResolve({ filter: /^react$/ }, () => ({
24
- path: require.resolve('react'),
25
- }))
26
- build.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
27
- path: require.resolve('react/jsx-runtime'),
28
- }))
29
- build.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
30
- path: require.resolve('react/jsx-dev-runtime'),
26
+ build.onResolve({ filter: /^react(\/|$)/ }, (args) => ({
27
+ path: require.resolve(args.path),
31
28
  }))
32
29
  },
33
30
  }
@@ -153,6 +150,11 @@ export interface CompileOptions {
153
150
  minify?: boolean
154
151
  target?: CompileTarget
155
152
  version?: string
153
+ /** Custom entry file. When set, this file is used as the entrypoint instead of
154
+ * the auto-generated one. Useful for CLIs that have their own subcommands and
155
+ * only launch termcast for one of them (e.g. a "tui" subcommand). The same
156
+ * raycast alias plugin and swift loader plugin are still applied. */
157
+ entry?: string
156
158
  }
157
159
 
158
160
  export interface CompileResult {
@@ -166,6 +168,7 @@ export async function compileExtension({
166
168
  minify = false,
167
169
  target,
168
170
  version,
171
+ entry,
169
172
  }: CompileOptions): Promise<CompileResult> {
170
173
  const resolvedPath = path.resolve(extensionPath)
171
174
 
@@ -178,28 +181,35 @@ export async function compileExtension({
178
181
  throw new Error(`No package.json found at: ${packageJsonPath}`)
179
182
  }
180
183
 
184
+ // When using a custom entry, commands are optional — the user manages their own entry
181
185
  const { packageJson, commands } = getCommandsWithFiles({ packageJsonPath })
182
186
 
183
- const existingCommands = commands.filter((cmd) => cmd.exists)
184
- if (existingCommands.length === 0) {
185
- throw new Error('No command files found to build')
187
+ if (!entry) {
188
+ const existingCommands = commands.filter((cmd) => cmd.exists)
189
+ if (existingCommands.length === 0) {
190
+ throw new Error('No command files found to build')
191
+ }
192
+ logger.log(`Compiling ${existingCommands.length} commands...`)
193
+ } else {
194
+ logger.log(`Compiling with custom entry: ${entry}`)
186
195
  }
187
196
 
188
- logger.log(`Compiling ${existingCommands.length} commands...`)
189
-
190
197
  const bundleDir = path.join(resolvedPath, '.termcast-bundle')
191
198
  if (!fs.existsSync(bundleDir)) {
192
199
  fs.mkdirSync(bundleDir, { recursive: true })
193
200
  }
194
201
  fs.writeFileSync(path.join(bundleDir, '.gitignore'), '*\n')
195
202
 
196
- const entryCode = generateEntryCode({
197
- packageJson,
198
- commands: existingCommands.map((cmd) => ({
199
- name: cmd.name,
200
- bundledPath: cmd.filePath,
201
- })),
202
- })
203
+ const existingCommands = commands.filter((cmd) => cmd.exists)
204
+ const entryCode = entry
205
+ ? undefined
206
+ : generateEntryCode({
207
+ packageJson,
208
+ commands: existingCommands.map((cmd) => ({
209
+ name: cmd.name,
210
+ bundledPath: cmd.filePath,
211
+ })),
212
+ })
203
213
 
204
214
  // IMPORTANT: always compile with a concrete target (bun-linux-x64, bun-darwin-arm64, ...)
205
215
  // rather than the generic "bun" target. Using the generic target can cause Bun.build to
@@ -207,9 +217,22 @@ export async function compileExtension({
207
217
  // (e.g. @opentui/core-linux-musl-x64) even when compiling/running on glibc Linux.
208
218
  const resolvedTarget: CompileTarget = target || getCurrentTarget()
209
219
 
220
+ // When using a custom entry, resolve it relative to extensionPath and use directly.
221
+ // Otherwise generate a temp entry file with embedded packageJson and command loaders.
222
+ const resolvedEntry = entry ? path.resolve(resolvedPath, entry) : undefined
223
+ if (resolvedEntry && !fs.existsSync(resolvedEntry)) {
224
+ throw new Error(`Custom entry file does not exist: ${resolvedEntry}`)
225
+ }
226
+
210
227
  const targetSuffix = target ? targetToFileSuffix(target) : 'local'
211
- const tempEntryPath = path.join(bundleDir, `_entry-${targetSuffix}.tsx`)
212
- fs.writeFileSync(tempEntryPath, entryCode)
228
+ const tempEntryPath = resolvedEntry
229
+ ? undefined
230
+ : path.join(bundleDir, `_entry-${targetSuffix}.tsx`)
231
+ if (tempEntryPath && entryCode) {
232
+ fs.writeFileSync(tempEntryPath, entryCode)
233
+ }
234
+
235
+ const entrypoint = resolvedEntry || tempEntryPath!
213
236
 
214
237
  const bunTarget = targetToString(resolvedTarget)
215
238
  const distDir = path.join(resolvedPath, 'dist')
@@ -221,7 +244,7 @@ export async function compileExtension({
221
244
 
222
245
  try {
223
246
  const result = await Bun.build({
224
- entrypoints: [tempEntryPath],
247
+ entrypoints: [entrypoint],
225
248
  target: bunTarget as 'bun',
226
249
  format: 'esm',
227
250
  minify,
@@ -281,7 +304,7 @@ export async function compileExtension({
281
304
  outfile: defaultOutfile,
282
305
  }
283
306
  } finally {
284
- if (fs.existsSync(tempEntryPath)) {
307
+ if (tempEntryPath && fs.existsSync(tempEntryPath)) {
285
308
  fs.unlinkSync(tempEntryPath)
286
309
  }
287
310
  }
@@ -914,7 +914,7 @@ const ActionPanel: ActionPanelType = (props) => {
914
914
  }}
915
915
  />
916
916
  <Action
917
- title="See Console Logs"
917
+ title="Toggle Console Logs"
918
918
  onAction={() => {
919
919
  useStore.setState({ showActionsDialog: false })
920
920
  if (renderer) {
@@ -0,0 +1,271 @@
1
+ /**
2
+ * BarChart component for rendering horizontal stacked bar charts in the terminal.
3
+ *
4
+ * Uses opentui <box> elements with backgroundColor for each segment, sized
5
+ * proportionally via flexGrow. Labels are positioned above or below the bar
6
+ * with corner bracket connectors (┌/┐/└/┘).
7
+ *
8
+ * Segments too small to display (< 1 terminal column) are hidden.
9
+ * Labels are hidden when the segment is narrower than the label text.
10
+ *
11
+ * Color palette (assigned by value descending):
12
+ * primary, accent, info, success, warning, error, secondary (cycles with %)
13
+ */
14
+
15
+ import React, { ReactNode, useMemo } from 'react'
16
+ import { useTheme, getThemePalette } from 'termcast/src/theme'
17
+ import { Color, resolveColor } from 'termcast/src/colors'
18
+
19
+ // ── Types ────────────────────────────────────────────────────────────
20
+
21
+ export interface BarChartSegmentProps {
22
+ /** Numeric value for this segment (determines proportional width) */
23
+ value: number
24
+ /** Label text shown above/below the segment (e.g. "Spent") */
25
+ label?: string
26
+ /** Override the auto-assigned color */
27
+ color?: Color.ColorLike
28
+ }
29
+
30
+ export interface BarChartProps {
31
+ /** Height of the colored bar in terminal rows (default: 1) */
32
+ height?: number
33
+ /** Show label annotations above/below the bar (default: true) */
34
+ showLabels?: boolean
35
+ /** BarChart.Segment children */
36
+ children: ReactNode
37
+ }
38
+
39
+ interface BarChartType {
40
+ (props: BarChartProps): any
41
+ Segment: (props: BarChartSegmentProps) => any
42
+ }
43
+
44
+ // ── Internal: collected segment data ─────────────────────────────────
45
+
46
+ interface SegmentData {
47
+ value: number
48
+ label?: string
49
+ color?: string // resolved hex, or undefined for auto
50
+ /** Index in original children order */
51
+ originalIndex: number
52
+ }
53
+
54
+ // ── BarChart.Segment (data-only, renders null like Graph.Line) ───────
55
+
56
+ const BarChartSegment = (_props: BarChartSegmentProps): any => {
57
+ return null
58
+ }
59
+
60
+ // ── Label positioning ────────────────────────────────────────────────
61
+ // Labels use the same flexGrow as segments so they naturally align.
62
+ // "above" labels: fit within segment width. "below": overflow-hidden.
63
+ // The decision is based on estimated proportional width vs text length.
64
+
65
+ interface PositionedLabel {
66
+ text: string
67
+ segmentIndex: number
68
+ position: 'above' | 'below'
69
+ }
70
+
71
+ function formatValue(value: number, total: number): string {
72
+ const pct = (value / total) * 100
73
+ if (pct >= 1) {
74
+ return pct % 1 === 0 ? `${pct.toFixed(0)}%` : `${pct.toFixed(1)}%`
75
+ }
76
+ return `${pct.toFixed(1)}%`
77
+ }
78
+
79
+ function buildLabelText(label: string | undefined, value: number, total: number): string {
80
+ const formatted = formatValue(value, total)
81
+ if (label) {
82
+ return `${label}: ${formatted}`
83
+ }
84
+ return formatted
85
+ }
86
+
87
+ function computeLabels({
88
+ segments,
89
+ total,
90
+ }: {
91
+ segments: SegmentData[]
92
+ total: number
93
+ }): { above: PositionedLabel[]; below: PositionedLabel[] } {
94
+ const above: PositionedLabel[] = []
95
+ const below: PositionedLabel[] = []
96
+
97
+ for (let i = 0; i < segments.length; i++) {
98
+ const seg = segments[i]!
99
+ const text = buildLabelText(seg.label, seg.value, total)
100
+ const proportion = seg.value / total
101
+
102
+ // Skip labels for small segments - they'd be unreadable noise.
103
+ // 12% threshold ensures at least ~6 cols in a 50-col container,
104
+ // enough for a bracket + 4 chars + bracket.
105
+ if (proportion < 0.12) {
106
+ continue
107
+ }
108
+
109
+ const positioned: PositionedLabel = {
110
+ text,
111
+ segmentIndex: i,
112
+ position: 'above',
113
+ }
114
+
115
+ // Heuristic for "fits above": text + brackets (2 chars) must fit within
116
+ // estimated segment width. We use proportion * 60 as a conservative
117
+ // estimate (works for containers 50-100 cols wide).
118
+ const estimatedCols = proportion * 60
119
+ if (text.length + 2 <= estimatedCols) {
120
+ above.push(positioned)
121
+ } else if (estimatedCols >= 5) {
122
+ // Only show below if there's enough space for a readable truncation
123
+ // (bracket + at least 3 chars + bracket = 5 cols minimum)
124
+ positioned.position = 'below'
125
+ below.push(positioned)
126
+ }
127
+ // else: segment too narrow for readable label in either position, skip
128
+ }
129
+
130
+ return { above, below }
131
+ }
132
+
133
+ // ── Render a label row using flexbox ─────────────────────────────────
134
+ // Each segment gets a box with the same flexGrow so labels align with the bar.
135
+ // overflow="hidden" and wrapMode="none" ensure long labels clip rather than wrap.
136
+
137
+ function LabelRow({ segments, labelMap, position, color }: {
138
+ segments: Array<SegmentData & { resolvedColor: string }>
139
+ labelMap: Map<number, PositionedLabel>
140
+ position: 'above' | 'below'
141
+ color: string
142
+ }): any {
143
+ const hasAnyLabel = segments.some((_, i) => labelMap.has(i))
144
+ if (!hasAnyLabel) {
145
+ return null
146
+ }
147
+
148
+ const openBracket = position === 'above' ? '┌' : '└'
149
+ const closeBracket = position === 'above' ? '┐' : '┘'
150
+
151
+ return (
152
+ <box flexDirection="row" width="100%" height={1} flexShrink={0}>
153
+ {segments.map((seg, i) => {
154
+ const label = labelMap.get(i)
155
+ if (!label) {
156
+ // Empty spacer: uses flexGrow to maintain alignment but no height
157
+ return <box key={i} flexGrow={seg.value} flexShrink={1} flexBasis={0} />
158
+ }
159
+ return (
160
+ <box key={i} flexGrow={seg.value} flexShrink={1} flexBasis={0} overflow="hidden">
161
+ <text fg={color} wrapMode="none" flexShrink={0}>
162
+ {openBracket}{label.text}{closeBracket}
163
+ </text>
164
+ </box>
165
+ )
166
+ })}
167
+ </box>
168
+ )
169
+ }
170
+
171
+ // ── Main BarChart component ──────────────────────────────────────────
172
+
173
+ const BarChart: BarChartType = (props) => {
174
+ const theme = useTheme()
175
+ const { height = 1, showLabels = true, children } = props
176
+
177
+ // Collect segment data from BarChart.Segment children
178
+ const segments = useMemo<SegmentData[]>(() => {
179
+ const result: SegmentData[] = []
180
+ let idx = 0
181
+ React.Children.forEach(children, (child) => {
182
+ if (!React.isValidElement(child)) {
183
+ return
184
+ }
185
+ const childProps = child.props as BarChartSegmentProps
186
+ if (childProps.value === undefined) {
187
+ return
188
+ }
189
+ result.push({
190
+ value: childProps.value,
191
+ label: childProps.label,
192
+ color: resolveColor(childProps.color),
193
+ originalIndex: idx,
194
+ })
195
+ idx++
196
+ })
197
+ return result
198
+ }, [children])
199
+
200
+ // Sort by value descending for color assignment, then restore original order
201
+ const palette = getThemePalette(theme)
202
+ const coloredSegments = useMemo<Array<SegmentData & { resolvedColor: string }>>(() => {
203
+ // Create index mapping: sort by value desc, assign colors
204
+ const sortedIndices = segments
205
+ .map((_, i) => i)
206
+ .sort((a, b) => segments[b]!.value - segments[a]!.value)
207
+
208
+ const colorMap = new Map<number, string>()
209
+ sortedIndices.forEach((origIdx, rank) => {
210
+ const seg = segments[origIdx]!
211
+ const color = seg.color || palette[rank % palette.length]!
212
+ colorMap.set(origIdx, color)
213
+ })
214
+
215
+ return segments.map((seg, i) => ({
216
+ ...seg,
217
+ resolvedColor: colorMap.get(i) || palette[0]!,
218
+ }))
219
+ }, [segments, palette])
220
+
221
+ const total = useMemo(() => {
222
+ return coloredSegments.reduce((sum, s) => sum + s.value, 0)
223
+ }, [coloredSegments])
224
+
225
+ if (total === 0 || coloredSegments.length === 0) {
226
+ return null
227
+ }
228
+
229
+ // Filter out segments too small to render (proportion < 0.5%)
230
+ const visibleSegments = coloredSegments.filter((seg) => {
231
+ const proportion = seg.value / total
232
+ return proportion >= 0.005
233
+ })
234
+
235
+ // Compute labels
236
+ const { above, below } = showLabels
237
+ ? computeLabels({ segments: visibleSegments, total })
238
+ : { above: [], below: [] }
239
+
240
+ // Build lookup maps: segmentIndex -> label
241
+ const aboveMap = new Map(above.map((l) => [l.segmentIndex, l]))
242
+ const belowMap = new Map(below.map((l) => [l.segmentIndex, l]))
243
+
244
+ return (
245
+ <box flexDirection="column" width="100%" flexShrink={0}>
246
+ <LabelRow
247
+ segments={visibleSegments}
248
+ labelMap={aboveMap}
249
+ position="above"
250
+ color={theme.textMuted}
251
+ />
252
+ <box flexDirection="row" height={height} width="100%" flexShrink={0}>
253
+ {visibleSegments.map((seg, i) => {
254
+ return (
255
+ <box key={i} flexGrow={seg.value} flexShrink={0} backgroundColor={seg.resolvedColor} height={height} />
256
+ )
257
+ })}
258
+ </box>
259
+ <LabelRow
260
+ segments={visibleSegments}
261
+ labelMap={belowMap}
262
+ position="below"
263
+ color={theme.textMuted}
264
+ />
265
+ </box>
266
+ )
267
+ }
268
+
269
+ BarChart.Segment = BarChartSegment
270
+
271
+ export { BarChart }