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.
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +12 -0
- package/dist/build.js.map +1 -1
- package/dist/cli.js +5 -40
- package/dist/cli.js.map +1 -1
- package/dist/colors.d.ts +7 -7
- package/dist/colors.js +7 -7
- package/dist/compile.d.ts +6 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +45 -26
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.js +1 -1
- package/dist/components/actions.js.map +1 -1
- package/dist/components/bar-chart.d.ts +38 -0
- package/dist/components/bar-chart.d.ts.map +1 -0
- package/dist/components/bar-chart.js +158 -0
- package/dist/components/bar-chart.js.map +1 -0
- package/dist/components/bar-graph.d.ts +41 -0
- package/dist/components/bar-graph.d.ts.map +1 -0
- package/dist/components/bar-graph.js +95 -0
- package/dist/components/bar-graph.js.map +1 -0
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +5 -7
- package/dist/components/detail.js.map +1 -1
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +8 -9
- package/dist/components/footer.js.map +1 -1
- package/dist/components/form/date-picker.d.ts.map +1 -1
- package/dist/components/form/date-picker.js +7 -1
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +10 -2
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +4 -5
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/use-form-navigation.d.ts.map +1 -1
- package/dist/components/form/use-form-navigation.js +6 -0
- package/dist/components/form/use-form-navigation.js.map +1 -1
- package/dist/components/graph.d.ts +111 -0
- package/dist/components/graph.d.ts.map +1 -0
- package/dist/components/graph.js +392 -0
- package/dist/components/graph.js.map +1 -0
- package/dist/components/icon.js +5 -5
- package/dist/components/icon.js.map +1 -1
- package/dist/components/list.d.ts +53 -5
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +125 -71
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.js +3 -3
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts +1 -1
- package/dist/components/loading-text.d.ts.map +1 -1
- package/dist/components/loading-text.js +3 -1
- package/dist/components/loading-text.js.map +1 -1
- package/dist/components/metadata.js +2 -2
- package/dist/components/metadata.js.map +1 -1
- package/dist/components/row.d.ts +10 -0
- package/dist/components/row.d.ts.map +1 -0
- package/dist/components/row.js +12 -0
- package/dist/components/row.js.map +1 -0
- package/dist/components/table.d.ts +57 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.js +365 -0
- package/dist/components/table.js.map +1 -0
- package/dist/descendants.js +13 -13
- package/dist/descendants.js.map +1 -1
- package/dist/examples/bar-graph-weekly.d.ts +2 -0
- package/dist/examples/bar-graph-weekly.d.ts.map +1 -0
- package/dist/examples/bar-graph-weekly.js +95 -0
- package/dist/examples/bar-graph-weekly.js.map +1 -0
- package/dist/examples/components-weird-places.d.ts +2 -0
- package/dist/examples/components-weird-places.d.ts.map +1 -0
- package/dist/examples/components-weird-places.js +46 -0
- package/dist/examples/components-weird-places.js.map +1 -0
- package/dist/examples/graph-bar-chart.d.ts +2 -0
- package/dist/examples/graph-bar-chart.d.ts.map +1 -0
- package/dist/examples/graph-bar-chart.js +270 -0
- package/dist/examples/graph-bar-chart.js.map +1 -0
- package/dist/examples/graph-multi-series.d.ts +2 -0
- package/dist/examples/graph-multi-series.d.ts.map +1 -0
- package/dist/examples/graph-multi-series.js +23 -0
- package/dist/examples/graph-multi-series.js.map +1 -0
- package/dist/examples/graph-polymarket.d.ts +2 -0
- package/dist/examples/graph-polymarket.d.ts.map +1 -0
- package/dist/examples/graph-polymarket.js +109 -0
- package/dist/examples/graph-polymarket.js.map +1 -0
- package/dist/examples/graph-row.d.ts +2 -0
- package/dist/examples/graph-row.d.ts.map +1 -0
- package/dist/examples/graph-row.js +226 -0
- package/dist/examples/graph-row.js.map +1 -0
- package/dist/examples/graph-styles.d.ts +2 -0
- package/dist/examples/graph-styles.d.ts.map +1 -0
- package/dist/examples/graph-styles.js +316 -0
- package/dist/examples/graph-styles.js.map +1 -0
- package/dist/examples/list-accessory-table.d.ts +2 -0
- package/dist/examples/list-accessory-table.d.ts.map +1 -0
- package/dist/examples/list-accessory-table.js +46 -0
- package/dist/examples/list-accessory-table.js.map +1 -0
- package/dist/examples/list-item-accessories.d.ts +2 -0
- package/dist/examples/list-item-accessories.d.ts.map +1 -0
- package/dist/examples/list-item-accessories.js +27 -0
- package/dist/examples/list-item-accessories.js.map +1 -0
- package/dist/examples/list-no-actions.d.ts +2 -0
- package/dist/examples/list-no-actions.d.ts.map +1 -0
- package/dist/examples/list-no-actions.js +7 -0
- package/dist/examples/list-no-actions.js.map +1 -0
- package/dist/examples/simple-detail-table.d.ts +2 -0
- package/dist/examples/simple-detail-table.d.ts.map +1 -0
- package/dist/examples/simple-detail-table.js +45 -0
- package/dist/examples/simple-detail-table.js.map +1 -0
- package/dist/examples/simple-graph.d.ts +2 -0
- package/dist/examples/simple-graph.d.ts.map +1 -0
- package/dist/examples/simple-graph.js +32 -0
- package/dist/examples/simple-graph.js.map +1 -0
- package/dist/examples/simple-table-wrap.d.ts +2 -0
- package/dist/examples/simple-table-wrap.d.ts.map +1 -0
- package/dist/examples/simple-table-wrap.js +37 -0
- package/dist/examples/simple-table-wrap.js.map +1 -0
- package/dist/examples/table-edge-cases.d.ts +2 -0
- package/dist/examples/table-edge-cases.d.ts.map +1 -0
- package/dist/examples/table-edge-cases.js +70 -0
- package/dist/examples/table-edge-cases.js.map +1 -0
- package/dist/examples/table-flex-grow.d.ts +2 -0
- package/dist/examples/table-flex-grow.d.ts.map +1 -0
- package/dist/examples/table-flex-grow.js +18 -0
- package/dist/examples/table-flex-grow.js.map +1 -0
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +5 -1
- package/dist/extensions/dev.js.map +1 -1
- package/dist/globals.d.ts +1 -0
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +2 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/date-picker-widget.d.ts.map +1 -1
- package/dist/internal/date-picker-widget.js +4 -0
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +1 -3
- package/dist/internal/providers.js.map +1 -1
- package/dist/markdown-utils.d.ts +22 -1
- package/dist/markdown-utils.d.ts.map +1 -1
- package/dist/markdown-utils.js +66 -1
- package/dist/markdown-utils.js.map +1 -1
- package/dist/opentui.d.ts +4 -0
- package/dist/opentui.d.ts.map +1 -0
- package/dist/opentui.js +3 -0
- package/dist/opentui.js.map +1 -0
- package/dist/release.d.ts +2 -1
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +2 -1
- package/dist/release.js.map +1 -1
- package/dist/state.d.ts +1 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +1 -1
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +13 -0
- package/dist/theme.js.map +1 -1
- package/dist/themes/nerv.json +227 -0
- package/dist/themes/termcast.json +72 -71
- package/dist/themes.d.ts +2 -1
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +7 -5
- package/dist/themes.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -1
- package/package.json +13 -5
- package/src/build.tsx +13 -0
- package/src/cli.tsx +5 -49
- package/src/colors.tsx +7 -7
- package/src/compile.tsx +52 -29
- package/src/components/actions.tsx +1 -1
- package/src/components/bar-chart.tsx +271 -0
- package/src/components/bar-graph.tsx +214 -0
- package/src/components/detail.tsx +7 -8
- package/src/components/footer.tsx +14 -15
- package/src/components/form/date-picker.tsx +9 -0
- package/src/components/form/dropdown.tsx +13 -3
- package/src/components/form/index.tsx +4 -6
- package/src/components/form/use-form-navigation.tsx +6 -0
- package/src/components/graph.tsx +506 -0
- package/src/components/icon.tsx +5 -5
- package/src/components/list.tsx +210 -102
- package/src/components/loading-bar.tsx +3 -3
- package/src/components/loading-text.tsx +4 -2
- package/src/components/metadata.tsx +2 -2
- package/src/components/row.tsx +31 -0
- package/src/components/table.tsx +511 -0
- package/src/descendants.tsx +13 -13
- package/src/examples/action-shortcut.vitest.tsx +1 -1
- package/src/examples/actions-context.vitest.tsx +1 -1
- package/src/examples/bar-graph-weekly.tsx +264 -0
- package/src/examples/bar-graph-weekly.vitest.tsx +275 -0
- package/src/examples/detail-metadata-showcase.vitest.tsx +8 -8
- package/src/examples/form-basic.vitest.tsx +239 -0
- package/src/examples/form-dropdown.vitest.tsx +29 -29
- package/src/examples/form-tagpicker.vitest.tsx +27 -27
- package/src/examples/github.vitest.tsx +4 -4
- package/src/examples/graph-bar-chart.tsx +408 -0
- package/src/examples/graph-bar-chart.vitest.tsx +283 -0
- package/src/examples/graph-multi-series.tsx +36 -0
- package/src/examples/graph-multi-series.vitest.tsx +89 -0
- package/src/examples/graph-polymarket.tsx +182 -0
- package/src/examples/graph-polymarket.vitest.tsx +130 -0
- package/src/examples/graph-row.tsx +347 -0
- package/src/examples/graph-row.vitest.tsx +295 -0
- package/src/examples/graph-styles.tsx +457 -0
- package/src/examples/graph-styles.vitest.tsx +322 -0
- package/src/examples/list-accessory-table.tsx +77 -0
- package/src/examples/list-detail-metadata.vitest.tsx +21 -21
- package/src/examples/list-dropdown-default.vitest.tsx +12 -12
- package/src/examples/list-item-accessories.tsx +106 -0
- package/src/examples/list-item-accessories.vitest.tsx +115 -0
- package/src/examples/list-no-actions.tsx +18 -0
- package/src/examples/list-no-actions.vitest.tsx +97 -0
- package/src/examples/list-spacing-mode.vitest.tsx +6 -6
- package/src/examples/list-with-detail.vitest.tsx +92 -92
- package/src/examples/list-with-dropdown.vitest.tsx +49 -6
- package/src/examples/list-with-sections.vitest.tsx +61 -56
- package/src/examples/simple-detail-markdown.vitest.tsx +21 -17
- package/src/examples/simple-detail-table.tsx +65 -0
- package/src/examples/simple-detail-table.vitest.tsx +200 -0
- package/src/examples/simple-graph.tsx +51 -0
- package/src/examples/simple-graph.vitest.tsx +124 -0
- package/src/examples/simple-grid.vitest.tsx +3 -3
- package/src/examples/simple-list-search.vitest.tsx +65 -0
- package/src/examples/simple-navigation.vitest.tsx +3 -3
- package/src/examples/simple-table-wrap.tsx +55 -0
- package/src/examples/simple-table-wrap.vitest.tsx +91 -0
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/table-edge-cases.tsx +72 -0
- package/src/examples/table-edge-cases.vitest.tsx +307 -0
- package/src/examples/table-flex-grow.tsx +53 -0
- package/src/examples/table-flex-grow.vitest.tsx +124 -0
- package/src/extensions/dev.tsx +7 -1
- package/src/globals.ts +3 -0
- package/src/index.tsx +31 -0
- package/src/internal/date-picker-widget.tsx +4 -0
- package/src/internal/providers.tsx +1 -4
- package/src/markdown-utils.tsx +82 -1
- package/src/opentui.tsx +5 -0
- package/src/release.tsx +3 -0
- package/src/state.tsx +2 -1
- package/src/theme.tsx +14 -0
- package/src/themes/nerv.json +231 -0
- package/src/themes/termcast.json +75 -71
- package/src/themes.ts +8 -5
- 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
|
-
.
|
|
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 = '#
|
|
3
|
-
Green = '#
|
|
4
|
-
Magenta = '#
|
|
5
|
-
Orange = '#
|
|
6
|
-
Purple = '#
|
|
7
|
-
Red = '#
|
|
8
|
-
Yellow = '#
|
|
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:
|
|
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:
|
|
22
|
+
path: args.path === 'termcast'
|
|
23
|
+
? path.join(__dirname, 'index.tsx')
|
|
24
|
+
: require.resolve(args.path, { paths: [termcastRoot] }),
|
|
21
25
|
}))
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 =
|
|
212
|
-
|
|
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: [
|
|
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
|
}
|
|
@@ -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 }
|