termcast 1.3.53 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/action-utils.d.ts.map +1 -1
  2. package/dist/action-utils.js +17 -132
  3. package/dist/action-utils.js.map +1 -1
  4. package/dist/apis/cache.d.ts +8 -30
  5. package/dist/apis/cache.d.ts.map +1 -1
  6. package/dist/apis/cache.js +9 -271
  7. package/dist/apis/cache.js.map +1 -1
  8. package/dist/apis/clipboard.d.ts +4 -2
  9. package/dist/apis/clipboard.d.ts.map +1 -1
  10. package/dist/apis/clipboard.js +18 -31
  11. package/dist/apis/clipboard.js.map +1 -1
  12. package/dist/apis/environment.d.ts.map +1 -1
  13. package/dist/apis/environment.js +14 -49
  14. package/dist/apis/environment.js.map +1 -1
  15. package/dist/apis/localstorage.d.ts +7 -12
  16. package/dist/apis/localstorage.d.ts.map +1 -1
  17. package/dist/apis/localstorage.js +7 -184
  18. package/dist/apis/localstorage.js.map +1 -1
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +46 -20
  21. package/dist/app.js.map +1 -1
  22. package/dist/cli.js +7 -6
  23. package/dist/cli.js.map +1 -1
  24. package/dist/components/actions.d.ts.map +1 -1
  25. package/dist/components/actions.js +13 -2
  26. package/dist/components/actions.js.map +1 -1
  27. package/dist/components/candle-chart.d.ts +110 -0
  28. package/dist/components/candle-chart.d.ts.map +1 -0
  29. package/dist/components/candle-chart.js +295 -0
  30. package/dist/components/candle-chart.js.map +1 -0
  31. package/dist/components/extension-preferences.d.ts.map +1 -1
  32. package/dist/components/extension-preferences.js +7 -8
  33. package/dist/components/extension-preferences.js.map +1 -1
  34. package/dist/components/form/file-autocomplete.js +2 -2
  35. package/dist/components/form/file-autocomplete.js.map +1 -1
  36. package/dist/components/list.d.ts.map +1 -1
  37. package/dist/components/list.js +242 -14
  38. package/dist/components/list.js.map +1 -1
  39. package/dist/components/table.d.ts +2 -0
  40. package/dist/components/table.d.ts.map +1 -1
  41. package/dist/components/table.js +41 -4
  42. package/dist/components/table.js.map +1 -1
  43. package/dist/e2e-node.d.ts.map +1 -1
  44. package/dist/e2e-node.js +5 -4
  45. package/dist/e2e-node.js.map +1 -1
  46. package/dist/examples/simple-candle-chart-data.d.ts +9064 -0
  47. package/dist/examples/simple-candle-chart-data.d.ts.map +1 -0
  48. package/dist/examples/simple-candle-chart-data.js +12683 -0
  49. package/dist/examples/simple-candle-chart-data.js.map +1 -0
  50. package/dist/examples/simple-candle-chart.d.ts +2 -0
  51. package/dist/examples/simple-candle-chart.d.ts.map +1 -0
  52. package/dist/examples/simple-candle-chart.js +125 -0
  53. package/dist/examples/simple-candle-chart.js.map +1 -0
  54. package/dist/extensions/dev.d.ts.map +1 -1
  55. package/dist/extensions/dev.js +5 -2
  56. package/dist/extensions/dev.js.map +1 -1
  57. package/dist/globals.d.ts.map +1 -1
  58. package/dist/globals.js +2 -1
  59. package/dist/globals.js.map +1 -1
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +2 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/internal/error-handler.d.ts.map +1 -1
  65. package/dist/internal/error-handler.js +21 -19
  66. package/dist/internal/error-handler.js.map +1 -1
  67. package/dist/internal/providers.d.ts.map +1 -1
  68. package/dist/internal/providers.js +41 -1
  69. package/dist/internal/providers.js.map +1 -1
  70. package/dist/logger.d.ts.map +1 -1
  71. package/dist/logger.js +31 -29
  72. package/dist/logger.js.map +1 -1
  73. package/dist/platform/browser/cache.d.ts +41 -0
  74. package/dist/platform/browser/cache.d.ts.map +1 -0
  75. package/dist/platform/browser/cache.js +262 -0
  76. package/dist/platform/browser/cache.js.map +1 -0
  77. package/dist/platform/browser/localstorage.d.ts +20 -0
  78. package/dist/platform/browser/localstorage.d.ts.map +1 -0
  79. package/dist/platform/browser/localstorage.js +102 -0
  80. package/dist/platform/browser/localstorage.js.map +1 -0
  81. package/dist/platform/browser/runtime.d.ts +51 -0
  82. package/dist/platform/browser/runtime.d.ts.map +1 -0
  83. package/dist/platform/browser/runtime.js +164 -0
  84. package/dist/platform/browser/runtime.js.map +1 -0
  85. package/dist/platform/bun/sqlite.d.ts +17 -0
  86. package/dist/platform/bun/sqlite.d.ts.map +1 -0
  87. package/dist/platform/bun/sqlite.js +6 -0
  88. package/dist/platform/bun/sqlite.js.map +1 -0
  89. package/dist/platform/node/cache.d.ts +35 -0
  90. package/dist/platform/node/cache.d.ts.map +1 -0
  91. package/dist/platform/node/cache.js +269 -0
  92. package/dist/platform/node/cache.js.map +1 -0
  93. package/dist/platform/node/localstorage.d.ts +17 -0
  94. package/dist/platform/node/localstorage.d.ts.map +1 -0
  95. package/dist/platform/node/localstorage.js +186 -0
  96. package/dist/platform/node/localstorage.js.map +1 -0
  97. package/dist/platform/node/runtime.d.ts +52 -0
  98. package/dist/platform/node/runtime.d.ts.map +1 -0
  99. package/dist/platform/node/runtime.js +230 -0
  100. package/dist/platform/node/runtime.js.map +1 -0
  101. package/dist/platform/node/sqlite.d.ts +27 -0
  102. package/dist/platform/node/sqlite.d.ts.map +1 -0
  103. package/dist/platform/node/sqlite.js +21 -0
  104. package/dist/platform/node/sqlite.js.map +1 -0
  105. package/dist/state.d.ts +5 -0
  106. package/dist/state.d.ts.map +1 -1
  107. package/dist/state.js +6 -28
  108. package/dist/state.js.map +1 -1
  109. package/dist/utils/file-system.d.ts.map +1 -1
  110. package/dist/utils/file-system.js +17 -22
  111. package/dist/utils/file-system.js.map +1 -1
  112. package/dist/utils.d.ts +1 -1
  113. package/dist/utils.d.ts.map +1 -1
  114. package/dist/utils.js +42 -47
  115. package/dist/utils.js.map +1 -1
  116. package/dist/vim-mode.d.ts +40 -0
  117. package/dist/vim-mode.d.ts.map +1 -0
  118. package/dist/vim-mode.js +135 -0
  119. package/dist/vim-mode.js.map +1 -0
  120. package/fonts/Inconsolata.otf +0 -0
  121. package/fonts/SIL Open Font License.txt +41 -0
  122. package/package.json +60 -8
  123. package/src/action-utils.tsx +27 -124
  124. package/src/apis/cache.test.ts +1 -1
  125. package/src/apis/cache.tsx +9 -373
  126. package/src/apis/clipboard.tsx +29 -38
  127. package/src/apis/environment.tsx +25 -52
  128. package/src/apis/localstorage.tsx +8 -214
  129. package/src/app.tsx +51 -20
  130. package/src/cli.tsx +14 -15
  131. package/src/compile.vitest.tsx +2 -2
  132. package/src/components/actions.tsx +19 -1
  133. package/src/components/candle-chart.tsx +410 -0
  134. package/src/components/extension-preferences.tsx +7 -8
  135. package/src/components/form/file-autocomplete.tsx +2 -2
  136. package/src/components/list.tsx +279 -14
  137. package/src/components/table.tsx +46 -4
  138. package/src/e2e-node.tsx +7 -7
  139. package/src/examples/action-shortcut.vitest.tsx +2 -2
  140. package/src/examples/actions-context.vitest.tsx +1 -1
  141. package/src/examples/bar-graph-weekly.vitest.tsx +10 -36
  142. package/src/examples/detail-metadata-showcase.vitest.tsx +36 -36
  143. package/src/examples/form-basic.vitest.tsx +21 -17
  144. package/src/examples/github.vitest.tsx +4 -4
  145. package/src/examples/graph-bar-chart.vitest.tsx +13 -11
  146. package/src/examples/graph-polymarket.vitest.tsx +2 -2
  147. package/src/examples/graph-row.vitest.tsx +66 -66
  148. package/src/examples/graph-styles.vitest.tsx +12 -12
  149. package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -48
  150. package/src/examples/list-detail-metadata.vitest.tsx +5 -5
  151. package/src/examples/list-fetch-data.vitest.tsx +3 -3
  152. package/src/examples/list-item-accessories.vitest.tsx +2 -2
  153. package/src/examples/list-loading-empty-view.vitest.tsx +1 -1
  154. package/src/examples/list-no-actions.vitest.tsx +2 -2
  155. package/src/examples/list-scrollbox.vitest.tsx +5 -5
  156. package/src/examples/list-spacing-mode.vitest.tsx +3 -3
  157. package/src/examples/list-with-detail.vitest.tsx +68 -68
  158. package/src/examples/list-with-dropdown.vitest.tsx +5 -5
  159. package/src/examples/list-with-sections.vitest.tsx +27 -27
  160. package/src/examples/simple-candle-chart-data.ts +12683 -0
  161. package/src/examples/simple-candle-chart.tsx +363 -0
  162. package/src/examples/simple-candle-chart.vitest.tsx +269 -0
  163. package/src/examples/simple-detail-markdown.vitest.tsx +8 -8
  164. package/src/examples/simple-detail-table.vitest.tsx +10 -10
  165. package/src/examples/simple-graph.vitest.tsx +3 -3
  166. package/src/examples/simple-grid.vitest.tsx +14 -14
  167. package/src/examples/simple-heatmap.vitest.tsx +1 -1
  168. package/src/examples/simple-navigation.vitest.tsx +17 -17
  169. package/src/examples/simple-progress-bar.vitest.tsx +1 -1
  170. package/src/examples/simple-table-wrap.vitest.tsx +19 -19
  171. package/src/examples/store.vitest.tsx +1 -1
  172. package/src/examples/swift-extension.vitest.tsx +2 -2
  173. package/src/examples/table-edge-cases.vitest.tsx +18 -18
  174. package/src/examples/table-flex-grow.vitest.tsx +8 -8
  175. package/src/examples/toast-action.vitest.tsx +2 -2
  176. package/src/extensions/dev.tsx +5 -2
  177. package/src/extensions/dev.vitest.tsx +3 -3
  178. package/src/globals.ts +2 -1
  179. package/src/index.tsx +7 -0
  180. package/src/internal/error-handler.tsx +19 -21
  181. package/src/internal/providers.tsx +39 -0
  182. package/src/logger.tsx +38 -41
  183. package/src/platform/browser/cache.ts +327 -0
  184. package/src/platform/browser/localstorage.ts +119 -0
  185. package/src/platform/browser/runtime.ts +209 -0
  186. package/src/platform/bun/sqlite.ts +19 -0
  187. package/src/platform/node/cache.ts +372 -0
  188. package/src/platform/node/localstorage.ts +214 -0
  189. package/src/platform/node/runtime.ts +264 -0
  190. package/src/platform/node/sqlite.ts +43 -0
  191. package/src/state.tsx +17 -28
  192. package/src/utils/file-system.ts +17 -22
  193. package/src/utils.test.tsx +1 -1
  194. package/src/utils.tsx +56 -47
  195. package/src/vim-mode.tsx +153 -0
  196. package/src/apis/sqlite.ts +0 -14
@@ -1,214 +1,8 @@
1
- import { Database } from './sqlite'
2
- import * as path from 'path'
3
- import * as fs from 'fs'
4
- import { logger } from '../logger'
5
- import { useStore } from '../state'
6
-
7
- let db: Database | null = null
8
- let currentDbPath: string | null = null
9
-
10
- function getCurrentDatabasePath(): string {
11
- const { extensionPath } = useStore.getState()
12
-
13
- if (!extensionPath) {
14
- throw new Error('Cannot access LocalStorage - extensionPath not set')
15
- }
16
-
17
- return path.join(extensionPath, '.termcast-bundle', 'data.db')
18
- }
19
-
20
- function getDatabase(): Database {
21
- const dbPath = getCurrentDatabasePath()
22
-
23
- // Check if we need to reconnect due to path change
24
- if (db && currentDbPath !== dbPath) {
25
- db.close()
26
- db = null
27
- currentDbPath = null
28
- }
29
-
30
- if (!db) {
31
- // Ensure parent directory exists
32
- const dbDir = path.dirname(dbPath)
33
- if (!fs.existsSync(dbDir)) {
34
- fs.mkdirSync(dbDir, { recursive: true })
35
- }
36
-
37
- db = new Database(dbPath, {
38
- create: true,
39
- readwrite: true,
40
- })
41
- currentDbPath = dbPath
42
-
43
- // Use WAL mode and optimize for single file usage
44
- db.exec('PRAGMA journal_mode = WAL')
45
- db.exec('PRAGMA wal_autocheckpoint = 1000')
46
- db.exec('PRAGMA synchronous = NORMAL')
47
-
48
- db.exec(`
49
- CREATE TABLE IF NOT EXISTS localstorage (
50
- key TEXT PRIMARY KEY,
51
- value TEXT NOT NULL,
52
- type TEXT NOT NULL
53
- )
54
- `)
55
- }
56
- return db
57
- }
58
-
59
-
60
-
61
- export namespace LocalStorage {
62
- export type Value = string | number | boolean
63
-
64
- export interface Values {
65
- [key: string]: any
66
- }
67
-
68
- export async function getItem<T extends Value = Value>(
69
- key: string,
70
- ): Promise<T | undefined> {
71
- return new Promise((resolve) => {
72
- try {
73
- const db = getDatabase()
74
- const row = db
75
- .prepare('SELECT value, type FROM localstorage WHERE key = ?')
76
- .get(key) as { value: string; type: string } | undefined
77
-
78
- if (!row) {
79
- resolve(undefined)
80
- return
81
- }
82
-
83
- let value: Value
84
- switch (row.type) {
85
- case 'number':
86
- value = parseFloat(row.value)
87
- break
88
- case 'boolean':
89
- value = row.value === 'true'
90
- break
91
- default:
92
- value = row.value
93
- }
94
-
95
- resolve(value as T)
96
- } catch (err) {
97
- logger.error('LocalStorage.getItem error:', err)
98
- resolve(undefined)
99
- }
100
- })
101
- }
102
-
103
- export function getItemSync<T extends Value = Value>(
104
- key: string,
105
- ): T | undefined {
106
- try {
107
- const db = getDatabase()
108
- const row = db
109
- .prepare('SELECT value, type FROM localstorage WHERE key = ?')
110
- .get(key) as { value: string; type: string } | undefined
111
-
112
- if (!row) {
113
- return undefined
114
- }
115
-
116
- let value: Value
117
- switch (row.type) {
118
- case 'number':
119
- value = parseFloat(row.value)
120
- break
121
- case 'boolean':
122
- value = row.value === 'true'
123
- break
124
- default:
125
- value = row.value
126
- }
127
-
128
- return value as T
129
- } catch (err) {
130
- logger.error('LocalStorage.getItemSync error:', err)
131
- return undefined
132
- }
133
- }
134
-
135
- export async function setItem(key: string, value: Value): Promise<void> {
136
- return new Promise((resolve, reject) => {
137
- try {
138
- const db = getDatabase()
139
- const type = typeof value
140
- const stringValue = String(value)
141
-
142
- db.prepare(
143
- 'INSERT OR REPLACE INTO localstorage (key, value, type) VALUES (?, ?, ?)',
144
- ).run(key, stringValue, type)
145
- resolve()
146
- } catch (err) {
147
- logger.error('LocalStorage.setItem error:', err)
148
- reject(err)
149
- }
150
- })
151
- }
152
-
153
- export async function removeItem(key: string): Promise<void> {
154
- return new Promise((resolve, reject) => {
155
- try {
156
- const db = getDatabase()
157
- db.prepare('DELETE FROM localstorage WHERE key = ?').run(key)
158
- resolve()
159
- } catch (err) {
160
- logger.error('LocalStorage.removeItem error:', err)
161
- reject(err)
162
- }
163
- })
164
- }
165
-
166
- export async function allItems<T extends Values = Values>(): Promise<T> {
167
- return new Promise((resolve, reject) => {
168
- try {
169
- const db = getDatabase()
170
- const rows = db
171
- .prepare('SELECT key, value, type FROM localstorage')
172
- .all() as Array<{
173
- key: string
174
- value: string
175
- type: string
176
- }>
177
-
178
- const result: Values = {}
179
- for (const row of rows) {
180
- let value: Value
181
- switch (row.type) {
182
- case 'number':
183
- value = parseFloat(row.value)
184
- break
185
- case 'boolean':
186
- value = row.value === 'true'
187
- break
188
- default:
189
- value = row.value
190
- }
191
- result[row.key] = value
192
- }
193
-
194
- resolve(result as T)
195
- } catch (err) {
196
- logger.error('LocalStorage.allItems error:', err)
197
- reject(err)
198
- }
199
- })
200
- }
201
-
202
- export async function clear(): Promise<void> {
203
- return new Promise((resolve, reject) => {
204
- try {
205
- const db = getDatabase()
206
- db.exec('DELETE FROM localstorage')
207
- resolve()
208
- } catch (err) {
209
- logger.error('LocalStorage.clear error:', err)
210
- reject(err)
211
- }
212
- })
213
- }
214
- }
1
+ /**
2
+ * LocalStorage API platform-agnostic facade.
3
+ *
4
+ * Re-exports the platform-specific LocalStorage from #platform/localstorage
5
+ * (SQLite on Node/Bun, window.localStorage on browser).
6
+ */
7
+
8
+ export { LocalStorage } from '#platform/localstorage'
package/src/app.tsx CHANGED
@@ -218,17 +218,47 @@ async function downloadWeztermLinux(): Promise<string> {
218
218
 
219
219
  console.log('Extracting wezterm-gui from tar.xz...')
220
220
  try {
221
- // Extract wezterm-gui using wildcard match on the nested path.
222
- // --strip-components=3 removes the top-level dir + usr/bin/ prefix.
221
+ // List archive entries first, then extract by exact path.
222
+ // This works on both GNU tar (Linux) and bsdtar (macOS).
223
+ const { stdout: entriesOutput } = await execFileAsync('tar', ['-tJf', tmpTar])
224
+ const archiveEntry = entriesOutput
225
+ .split('\n')
226
+ .map((entry) => {
227
+ return entry.trim()
228
+ })
229
+ .find((entry) => {
230
+ return entry.endsWith('/usr/bin/wezterm-gui')
231
+ })
232
+
233
+ if (!archiveEntry) {
234
+ const firstEntries = entriesOutput
235
+ .split('\n')
236
+ .map((entry) => {
237
+ return entry.trim()
238
+ })
239
+ .filter((entry) => {
240
+ return entry.length > 0
241
+ })
242
+ .slice(0, 20)
243
+ .join('\n ')
244
+ throw new Error(
245
+ `Could not find usr/bin/wezterm-gui in Linux archive. First entries:\n ${firstEntries}`,
246
+ )
247
+ }
248
+
249
+ const stripComponentsCount = archiveEntry.split('/').length - 1
250
+
251
+ // Removes the prefix directories (e.g. top-level dir + usr/bin/),
252
+ // yielding cacheDir/wezterm-gui directly.
223
253
  await execFileAsync('tar', [
224
254
  'xJf', tmpTar,
225
- '--strip-components=3',
226
- '--include=*/usr/bin/wezterm-gui',
255
+ `--strip-components=${stripComponentsCount}`,
227
256
  '-C', cacheDir,
257
+ archiveEntry,
228
258
  ])
229
259
  } catch (e) {
230
260
  throw new Error(
231
- `Failed to extract wezterm-gui from tar.xz. Ensure tar with xz support is available.`,
261
+ `Failed to extract wezterm-gui from tar.xz. Ensure tar with xz support is available and functional.`,
232
262
  { cause: e },
233
263
  )
234
264
  } finally {
@@ -464,11 +494,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
464
494
  if (dir[i] == L'\\\\') { dir[i + 1] = L'\\0'; break; }
465
495
  }
466
496
 
467
- /* Set TERMCAST_WEZTERM_CONFIG env var so the TUI can rewrite the config on theme change */
468
- WCHAR configPath[MAX_PATH * 2];
469
- wsprintfW(configPath, L"%sruntime\\\\config\\\\wezterm.lua", dir);
470
- SetEnvironmentVariableW(L"TERMCAST_WEZTERM_CONFIG", configPath);
471
-
472
497
  /* Set default theme name baked at build time */
473
498
  SetEnvironmentVariableW(L"TERMCAST_DEFAULT_THEME", L"${themeName}");
474
499
 
@@ -588,6 +613,8 @@ config.keys = {
588
613
  { key = 'RightArrow', mods = 'SUPER', action = wezterm.action.SendString('\\x1b[1;9C') },
589
614
  { key = 'UpArrow', mods = 'SUPER', action = wezterm.action.SendString('\\x1b[1;9A') },
590
615
  { key = 'DownArrow', mods = 'SUPER', action = wezterm.action.SendString('\\x1b[1;9B') },
616
+ -- Cmd+Backspace -> delete to line start (backspace codepoint = 127, super modifier = 9)
617
+ { key = 'Backspace', mods = 'SUPER', action = wezterm.action.SendString('\\x1b[127;9u') },
591
618
  }
592
619
  `
593
620
  : ''
@@ -607,9 +634,9 @@ config.window_decorations = '${platform === 'darwin' ? 'TITLE|RESIZE' : 'RESIZE'
607
634
  config.window_padding = { left = 0, right = 0, top = 0, bottom = 0 }
608
635
  config.window_close_confirmation = 'NeverPrompt'
609
636
 
610
- -- Background color matching the configured termcast theme.
611
- -- The TUI rewrites this file on theme change so WezTerm auto-reloads it,
612
- -- keeping the window edges/padding in sync with the active theme.
637
+ -- Background color: the TUI sets this dynamically via OSC 11 escape sequence
638
+ -- on startup and every theme change, so the window edges/padding always match.
639
+ -- This initial value comes from the theme configured at build time.
613
640
  config.colors = { background = '${backgroundColor}' }
614
641
 
615
642
  -- Default window size: 120x36 is comfortable for TUI apps (WezTerm default is 80x24)
@@ -651,10 +678,9 @@ function generateLaunchScript({ weztermBinaryName, themeName }: { weztermBinaryN
651
678
  return `\
652
679
  #!/bin/bash
653
680
  DIR="$(cd "$(dirname "$0")" && pwd)"
654
- export TERMCAST_WEZTERM_CONFIG="$DIR/../Resources/wezterm.lua"
655
681
  export TERMCAST_DEFAULT_THEME="${themeName}"
656
682
  export TERMCAST_APP_MODE=1
657
- exec "$DIR/${weztermBinaryName}" --config-file "$TERMCAST_WEZTERM_CONFIG"
683
+ exec "$DIR/${weztermBinaryName}" --config-file "$DIR/../Resources/wezterm.lua"
658
684
  `
659
685
  }
660
686
 
@@ -664,10 +690,9 @@ function generateLinuxLaunchScript({ themeName }: { themeName: string }): string
664
690
  return `\
665
691
  #!/bin/bash
666
692
  DIR="$(cd "$(dirname "$0")" && pwd)"
667
- export TERMCAST_WEZTERM_CONFIG="$DIR/runtime/config/wezterm.lua"
668
693
  export TERMCAST_DEFAULT_THEME="${themeName}"
669
694
  export TERMCAST_APP_MODE=1
670
- exec "$DIR/runtime/wezterm-gui" --config-file "$TERMCAST_WEZTERM_CONFIG"
695
+ exec "$DIR/runtime/wezterm-gui" --config-file "$DIR/runtime/config/wezterm.lua"
671
696
  `
672
697
  }
673
698
 
@@ -1091,8 +1116,9 @@ async function resolveBuildContext({
1091
1116
  // because PowerShell splits unquoted paths on whitespace.
1092
1117
  const safeName = appName.replace(/[/\\\s]+/g, '-').replace(/^-+|-+$/g, '')
1093
1118
 
1094
- // Resolve font directory: --font-dir flag, or fonts/ in extension root.
1095
- // --font-dir is resolved relative to the extension path (not cwd) for consistency.
1119
+ // Resolve font directory: --font-dir flag, fonts/ in extension root, or termcast's
1120
+ // built-in fonts/ as fallback. The built-in Inconsolata font ships with termcast so
1121
+ // every app has a guaranteed monospace font without depending on system fonts.
1096
1122
  const fontDirPath = (() => {
1097
1123
  if (fontDir) {
1098
1124
  const resolved = path.isAbsolute(fontDir)
@@ -1111,11 +1137,16 @@ async function resolveBuildContext({
1111
1137
  if (fs.existsSync(defaultFontDir) && fs.statSync(defaultFontDir).isDirectory()) {
1112
1138
  return defaultFontDir
1113
1139
  }
1140
+ // Fall back to termcast's built-in fonts/ directory (contains Inconsolata)
1141
+ const builtinFontDir = path.join(__dirname, '..', 'fonts')
1142
+ if (fs.existsSync(builtinFontDir) && fs.statSync(builtinFontDir).isDirectory()) {
1143
+ return builtinFontDir
1144
+ }
1114
1145
  return undefined
1115
1146
  })()
1116
1147
 
1117
1148
  const fontOptions: WeztermFontOptions = {
1118
- fontFamily,
1149
+ fontFamily: fontFamily ?? 'Inconsolata',
1119
1150
  fontSize,
1120
1151
  lineHeight,
1121
1152
  hasBundledFonts: !!fontDirPath,
package/src/cli.tsx CHANGED
@@ -239,13 +239,13 @@ cli
239
239
  .command('release [path]', 'Build and publish extension to GitHub releases')
240
240
  .option('--single', 'Only compile for the current platform')
241
241
  .option('--entry <file>', 'Custom entry file (instead of auto-generated one)')
242
- .action(async (extensionPath: string, options: { single?: boolean; entry?: string }) => {
243
- extensionPath = path.resolve(extensionPath || process.cwd())
242
+ .action(async (extensionPath: string | undefined, options: { single?: boolean; entry?: string }) => {
243
+ const resolvedPath = path.resolve(extensionPath || process.cwd())
244
244
 
245
245
  console.log('Building and releasing extension...')
246
246
  try {
247
247
  const result = await releaseExtension({
248
- extensionPath,
248
+ extensionPath: resolvedPath,
249
249
  single: options.single,
250
250
  entry: options.entry,
251
251
  })
@@ -279,7 +279,7 @@ cli
279
279
  .option('--theme <name>', 'Default theme name (default: nerv)')
280
280
  .action(
281
281
  async (
282
- extensionPath: string,
282
+ extensionPath: string | undefined,
283
283
  options: {
284
284
  name?: string
285
285
  icon?: string
@@ -294,6 +294,8 @@ cli
294
294
  fontSize?: string
295
295
  lineHeight?: string
296
296
  theme?: string
297
+ // `--no-installer` adds noInstaller to the inferred type
298
+ noInstaller?: boolean
297
299
  },
298
300
  ) => {
299
301
  extensionPath = path.resolve(extensionPath || process.cwd())
@@ -597,7 +599,7 @@ cli
597
599
  'Search for extensions in the Raycast store',
598
600
  )
599
601
  .option('-n, --limit [number]', 'Number of results to show (default: 10)')
600
- .action(async (query: string, options: { limit?: string }) => {
602
+ .action(async (query, options) => {
601
603
  try {
602
604
  const limit = parseInt(options.limit || '10', 10)
603
605
  const result = await searchStoreListings({ query, perPage: limit })
@@ -642,15 +644,12 @@ cli
642
644
  '--no-dir',
643
645
  'Put files directly in output directory instead of creating extension subdirectory',
644
646
  )
645
- .action(
646
- async (
647
- extensionName: string,
648
- options: { output?: string; dir: boolean },
649
- ) => {
647
+ .action(async (extensionName, options) => {
650
648
  try {
651
649
  const destPath = path.resolve(options.output || '.')
652
- // When --no-dir is passed, dir is false; put files directly in destPath
653
- const extensionDir = options.dir
650
+ // When --no-dir is passed, noDir is true; put files directly in destPath
651
+ const useSubdir = !options.noDir
652
+ const extensionDir = useSubdir
654
653
  ? path.join(destPath, extensionName)
655
654
  : destPath
656
655
  const tempCloneDir = path.join(
@@ -662,7 +661,7 @@ cli
662
661
  `Downloading extension '${extensionName}' from raycast/extensions...`,
663
662
  )
664
663
 
665
- if (options.dir && fs.existsSync(extensionDir)) {
664
+ if (useSubdir && fs.existsSync(extensionDir)) {
666
665
  console.log(`Removing existing directory: ${extensionDir}`)
667
666
  fs.rmSync(extensionDir, { recursive: true, force: true })
668
667
  }
@@ -724,7 +723,7 @@ cli
724
723
  }
725
724
 
726
725
  // Move files to final destination
727
- if (options.dir) {
726
+ if (useSubdir) {
728
727
  fs.mkdirSync(extensionDir, { recursive: true })
729
728
  }
730
729
  const filesToMove = fs.readdirSync(extensionPath)
@@ -755,7 +754,7 @@ cli
755
754
 
756
755
  cli
757
756
  .command('new [name]', 'Create a new termcast extension')
758
- .action(async (name: string) => {
757
+ .action(async (name: string | undefined) => {
759
758
  if (!name) {
760
759
  console.log('Usage: termcast new <extension-name>\n')
761
760
  console.log('Create a new termcast extension with the given name.\n')
@@ -96,7 +96,7 @@ test('compile extension and run executable', async () => {
96
96
  Show State Shows the current application state in view
97
97
 
98
98
 
99
- ↵ run command ↑↓ navigate ^k actions
99
+ ↵ run command ↑↓ navigate ^k actions :vim
100
100
  "
101
101
  `)
102
102
  }, 60000)
@@ -198,7 +198,7 @@ test('compiled executable can navigate back', async () => {
198
198
  ○ Fifth Item This is the fifth item
199
199
 
200
200
 
201
- ↵ copy item title ↑↓ navigate ^k actions
201
+ ↵ copy item title ↑↓ navigate ^k actions :vim
202
202
  "
203
203
  `)
204
204
  }, 60000)
@@ -1,3 +1,4 @@
1
+ import { platform } from '#platform/runtime'
1
2
  import React, {
2
3
  type ReactNode,
3
4
  type ReactElement,
@@ -22,6 +23,7 @@ import { Dropdown } from 'termcast/src/components/dropdown'
22
23
  import { ExtensionPreferences } from 'termcast/src/components/extension-preferences'
23
24
  import { ThemePicker } from 'termcast/src/components/theme-picker'
24
25
  import { useStore } from 'termcast/src/state'
26
+ import { toggleVimMode } from 'termcast/src/vim-mode'
25
27
  import { InFocus } from 'termcast/src/internal/focus-context'
26
28
  import { Onscreen, useIsOffscreen } from 'termcast/src/internal/offscreen'
27
29
  import { CommonProps } from 'termcast/src/utils'
@@ -587,7 +589,7 @@ Action.ToggleQuickLook = (props) => {
587
589
  return
588
590
  }
589
591
 
590
- if (process.platform !== 'darwin') {
592
+ if (platform !== 'darwin') {
591
593
  showToast({
592
594
  title: 'Quick Look',
593
595
  message: 'Quick Look is only supported on macOS',
@@ -743,6 +745,21 @@ export function matchesShortcut(
743
745
  return true
744
746
  }
745
747
 
748
+ // Separate component so the inputMode selector is reactive and the label
749
+ // updates when vim mode is toggled without reopening the action panel.
750
+ function VimModeAction(): any {
751
+ const inputMode = useStore((s) => s.inputMode)
752
+ return (
753
+ <Action
754
+ title={inputMode === 'vim' ? 'Disable Vim Mode' : 'Enable Vim Mode'}
755
+ onAction={() => {
756
+ useStore.setState({ showActionsDialog: false })
757
+ toggleVimMode()
758
+ }}
759
+ />
760
+ )
761
+ }
762
+
746
763
  /**
747
764
  * ActionPanel uses React portals to render its dialog content in the overlay
748
765
  * area while staying in the original React tree. This preserves all React
@@ -916,6 +933,7 @@ const ActionPanel: ActionPanelType = (props) => {
916
933
  dialog.push({ element: <ThemePicker /> })
917
934
  }}
918
935
  />
936
+ <VimModeAction />
919
937
  <Action
920
938
  title="Toggle Console Logs"
921
939
  onAction={() => {