spindb 0.36.2 → 0.37.1

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 (38) hide show
  1. package/README.md +19 -8
  2. package/cli/commands/create.ts +7 -0
  3. package/cli/commands/databases.ts +17 -12
  4. package/cli/commands/delete.ts +3 -0
  5. package/cli/commands/engines.ts +59 -3
  6. package/cli/commands/info.ts +5 -0
  7. package/cli/commands/list.ts +2 -0
  8. package/cli/commands/menu/backup-handlers.ts +2 -0
  9. package/cli/commands/menu/settings-handlers.ts +3 -0
  10. package/cli/commands/menu/shell-handlers.ts +23 -0
  11. package/cli/commands/restore.ts +3 -0
  12. package/cli/commands/start.ts +3 -0
  13. package/cli/commands/url.ts +4 -0
  14. package/cli/constants.ts +4 -0
  15. package/cli/helpers.ts +93 -0
  16. package/config/backup-formats.ts +14 -0
  17. package/config/engine-defaults.ts +13 -0
  18. package/config/engines.json +17 -0
  19. package/core/config-manager.ts +5 -0
  20. package/core/dependency-manager.ts +2 -0
  21. package/core/docker-exporter.ts +17 -0
  22. package/core/library-env.ts +2 -4
  23. package/engines/base-engine.ts +8 -0
  24. package/engines/index.ts +4 -0
  25. package/engines/mariadb/index.ts +5 -4
  26. package/engines/redis/index.ts +15 -4
  27. package/engines/tigerbeetle/README.md +61 -0
  28. package/engines/tigerbeetle/backup.ts +49 -0
  29. package/engines/tigerbeetle/binary-manager.ts +95 -0
  30. package/engines/tigerbeetle/binary-urls.ts +62 -0
  31. package/engines/tigerbeetle/hostdb-releases.ts +26 -0
  32. package/engines/tigerbeetle/index.ts +746 -0
  33. package/engines/tigerbeetle/restore.ts +130 -0
  34. package/engines/tigerbeetle/version-maps.ts +68 -0
  35. package/engines/tigerbeetle/version-validator.ts +126 -0
  36. package/engines/valkey/index.ts +15 -4
  37. package/package.json +2 -1
  38. package/types/index.ts +9 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **One CLI for all your local databases.**
9
9
 
10
- SpinDB is a universal database management tool that combines a package manager, a unified API, and native client tooling for 19 different database engines—all from a single command-line interface. No Docker, no VMs, no platform-specific installers. Just databases, running natively on your machine.
10
+ SpinDB is a universal database management tool that combines a package manager, a unified API, and native client tooling for 20 different database engines—all from a single command-line interface. No Docker, no VMs, no platform-specific installers. Just databases, running natively on your machine.
11
11
 
12
12
  ```bash
13
13
  npm install -g spindb
@@ -28,7 +28,7 @@ spindb create cache --engine redis
28
28
 
29
29
  ## Supported Engines & Platforms
30
30
 
31
- SpinDB supports **19 database engines** across **5 platform architectures**—all with a consistent API.
31
+ SpinDB supports **20 database engines** across **5 platform architectures**—all with a consistent API.
32
32
 
33
33
  | Engine | Type | macOS ARM | macOS Intel | Linux x64 | Linux ARM | Windows |
34
34
  |--------|------|:---------:|:-----------:|:---------:|:---------:|:-------:|
@@ -51,8 +51,9 @@ SpinDB supports **19 database engines** across **5 platform architectures**—al
51
51
  | 🤖 **TypeDB** | Knowledge Graph | ✅ | ✅ | ✅ | ✅ | ✅ |
52
52
  | 📈 **InfluxDB** | Time-Series | ✅ | ✅ | ✅ | ✅ | ✅ |
53
53
  | 🔮 **Weaviate** | Vector Database | ✅ | ✅ | ✅ | ✅ | ✅ |
54
+ | 🐯 **TigerBeetle** | Financial Ledger | ✅ | ✅ | ✅ | ✅ | ✅ |
54
55
 
55
- **95 combinations. One CLI. Zero configuration.**
56
+ **100 combinations. One CLI. Zero configuration.**
56
57
 
57
58
  > ClickHouse is available on Windows via WSL. FerretDB v1 is natively supported on Windows (uses plain PostgreSQL backend); v2 requires macOS/Linux.
58
59
 
@@ -128,6 +129,15 @@ spindb connect vectors # Open web dashboard
128
129
 
129
130
  > Weaviate is an AI-native vector database. REST API on default port 8080, gRPC on port+1. Uses classes/collections.
130
131
 
132
+ ### TigerBeetle
133
+
134
+ ```bash
135
+ spindb create ledger --engine tigerbeetle --start
136
+ spindb connect ledger # Open REPL
137
+ ```
138
+
139
+ > TigerBeetle is a high-performance financial ledger database. Custom binary protocol on default port 3000. Uses REPL for interaction.
140
+
131
141
  ### Enhanced Shells & Visual Tools
132
142
 
133
143
  ```bash
@@ -140,7 +150,7 @@ spindb connect mydb --ui # Built-in Web UI (DuckDB)
140
150
  ### Any Engine
141
151
 
142
152
  ```bash
143
- spindb create mydb --engine [postgresql|mysql|mariadb|mongodb|ferretdb|redis|valkey|clickhouse|sqlite|duckdb|qdrant|meilisearch|couchdb|cockroachdb|surrealdb|questdb|typedb|influxdb|weaviate]
153
+ spindb create mydb --engine [postgresql|mysql|mariadb|mongodb|ferretdb|redis|valkey|clickhouse|sqlite|duckdb|qdrant|meilisearch|couchdb|cockroachdb|surrealdb|questdb|typedb|influxdb|weaviate|tigerbeetle]
144
154
  spindb start mydb
145
155
  spindb connect mydb
146
156
  spindb backup mydb
@@ -182,7 +192,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
182
192
 
183
193
  | Feature | SpinDB | DBngin | Postgres.app | Laragon |
184
194
  |---------|--------|--------|--------------|---------|
185
- | **Engines supported** | 19 | 3 (PG/MySQL/Redis) | 1 (PostgreSQL) | 4 (PG/MySQL/MariaDB/MongoDB) |
195
+ | **Engines supported** | 20 | 3 (PG/MySQL/Redis) | 1 (PostgreSQL) | 4 (PG/MySQL/MariaDB/MongoDB) |
186
196
  | CLI-first | ✅ | ❌ GUI-only | ❌ GUI-only | ⚠️ Limited CLI |
187
197
  | Multi-version support | ✅ | ✅ | ✅ | ✅ |
188
198
  | Built-in backup/restore | ✅ | ✅ | ❌ | ⚠️ Manual |
@@ -198,7 +208,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
198
208
 
199
209
  | Feature | SpinDB | Docker Desktop | Podman | OrbStack |
200
210
  |---------|--------|----------------|--------|----------|
201
- | **Engines supported** | 19 unified | Any (manual setup) | Any (manual setup) | Any (manual setup) |
211
+ | **Engines supported** | 20 unified | Any (manual setup) | Any (manual setup) | Any (manual setup) |
202
212
  | Daemon required | ❌ | ✅ | ❌ (rootless) | ✅ |
203
213
  | Resource overhead | Native | VM + containers | VM + containers | VM + containers |
204
214
  | Built-in backup/restore | ✅ | ❌ Manual | ❌ Manual | ❌ Manual |
@@ -214,7 +224,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
214
224
 
215
225
  | Feature | SpinDB | Homebrew | apt/winget | asdf-vm |
216
226
  |---------|--------|----------|------------|---------|
217
- | **Engines supported** | 19 unified | Many (separate formulas) | Many (separate packages) | Many (plugins) |
227
+ | **Engines supported** | 20 unified | Many (separate formulas) | Many (separate packages) | Many (plugins) |
218
228
  | Multi-version side-by-side | ✅ | ⚠️ Complex | ❌ | ✅ |
219
229
  | Isolated data directories | ✅ | ❌ System-wide | ❌ System-wide | ❌ |
220
230
  | Built-in backup/restore | ✅ | ❌ | ❌ | ❌ |
@@ -285,6 +295,7 @@ See [DEPLOY.md](DEPLOY.md) for comprehensive deployment documentation.
285
295
  - **ClickHouse Windows** - Not supported (hostdb doesn't build for Windows).
286
296
  - **FerretDB Windows** - v1 supported natively (plain PostgreSQL backend). v2 not supported (postgresql-documentdb has startup issues); use WSL for v2.
287
297
  - **Qdrant, Meilisearch, CouchDB, Weaviate** - Use REST API instead of CLI shell. Access via HTTP at the configured port.
298
+ - **TigerBeetle** - Custom binary protocol only. No SQL or REST API. Interact via REPL (`spindb connect`) or client libraries.
288
299
 
289
300
  ---
290
301
 
@@ -405,7 +416,7 @@ See [ENGINE_CHECKLIST.md](ENGINE_CHECKLIST.md) for adding new database engines.
405
416
 
406
417
  SpinDB is powered by:
407
418
 
408
- - **[hostdb](https://github.com/robertjbass/hostdb)** - Pre-compiled database binaries for 19 engines across all major platforms. Makes Docker-free multi-version database support possible.
419
+ - **[hostdb](https://github.com/robertjbass/hostdb)** - Pre-compiled database binaries for 20 engines across all major platforms. Makes Docker-free multi-version database support possible.
409
420
 
410
421
  ---
411
422
 
@@ -28,6 +28,7 @@ import {
28
28
  isV1 as isFerretDBv1,
29
29
  } from '../../engines/ferretdb/version-maps'
30
30
  import type { BaseEngine } from '../../engines/base-engine'
31
+ import { getEngineMetadata } from '../helpers'
31
32
 
32
33
  /**
33
34
  * Simplified SQLite container creation flow
@@ -160,6 +161,7 @@ async function createSqliteContainer(
160
161
 
161
162
  // Display success
162
163
  if (json) {
164
+ const metadata = await getEngineMetadata('sqlite')
163
165
  console.log(
164
166
  JSON.stringify({
165
167
  success: true,
@@ -170,6 +172,7 @@ async function createSqliteContainer(
170
172
  database: containerName,
171
173
  connectionString,
172
174
  restored: !!restoreLocation,
175
+ ...metadata,
173
176
  }),
174
177
  )
175
178
  } else {
@@ -329,6 +332,7 @@ async function createDuckDBContainer(
329
332
 
330
333
  // Display success
331
334
  if (json) {
335
+ const metadata = await getEngineMetadata('duckdb')
332
336
  console.log(
333
337
  JSON.stringify({
334
338
  success: true,
@@ -339,6 +343,7 @@ async function createDuckDBContainer(
339
343
  database: containerName,
340
344
  connectionString,
341
345
  restored: !!restoreLocation,
346
+ ...metadata,
342
347
  }),
343
348
  )
344
349
  } else {
@@ -1096,6 +1101,7 @@ export const createCommand = new Command('create')
1096
1101
  const connectionString = dbEngine.getConnectionString(finalConfig)
1097
1102
 
1098
1103
  if (options.json) {
1104
+ const metadata = await getEngineMetadata(finalConfig.engine)
1099
1105
  console.log(
1100
1106
  JSON.stringify({
1101
1107
  success: true,
@@ -1107,6 +1113,7 @@ export const createCommand = new Command('create')
1107
1113
  connectionString,
1108
1114
  status: finalConfig.status,
1109
1115
  restored: !!restoreLocation,
1116
+ ...metadata,
1110
1117
  }),
1111
1118
  )
1112
1119
  } else {
@@ -2,6 +2,7 @@ import { Command } from 'commander'
2
2
  import chalk from 'chalk'
3
3
  import { containerManager } from '../../core/container-manager'
4
4
  import { uiError, uiSuccess } from '../ui/theme'
5
+ import { getEngineMetadata } from '../helpers'
5
6
 
6
7
  /**
7
8
  * CLI command for managing database tracking within containers.
@@ -59,16 +60,20 @@ databasesCommand
59
60
  }
60
61
 
61
62
  if (options.json) {
62
- const result = containers.map((c) => {
63
- const rawDatabases = c.databases || []
64
- const databases = [...new Set([c.database, ...rawDatabases])]
65
- return {
66
- container: c.name,
67
- engine: c.engine,
68
- primary: c.database,
69
- databases,
70
- }
71
- })
63
+ const result = await Promise.all(
64
+ containers.map(async (c) => {
65
+ const rawDatabases = c.databases || []
66
+ const databases = [...new Set([c.database, ...rawDatabases])]
67
+ const metadata = await getEngineMetadata(c.engine)
68
+ return {
69
+ container: c.name,
70
+ engine: c.engine,
71
+ primary: c.database,
72
+ databases,
73
+ ...metadata,
74
+ }
75
+ }),
76
+ )
72
77
  console.log(JSON.stringify(result, null, 2))
73
78
  } else {
74
79
  console.log()
@@ -121,8 +126,8 @@ databasesCommand
121
126
  const databases = [...new Set([config.database, ...rawDatabases])]
122
127
 
123
128
  if (options.json) {
124
- // Return the full container config - it's already JSON and has all the info
125
- console.log(JSON.stringify(config, null, 2))
129
+ const metadata = await getEngineMetadata(config.engine)
130
+ console.log(JSON.stringify({ ...config, ...metadata }, null, 2))
126
131
  } else {
127
132
  console.log()
128
133
  console.log(chalk.bold(`Databases in "${container}":`))
@@ -6,6 +6,7 @@ import { getEngine } from '../../engines'
6
6
  import { promptContainerSelect, promptConfirm } from '../ui/prompts'
7
7
  import { createSpinner } from '../ui/spinner'
8
8
  import { uiWarning } from '../ui/theme'
9
+ import { getEngineMetadata } from '../helpers'
9
10
 
10
11
  export const deleteCommand = new Command('delete')
11
12
  .alias('rm')
@@ -113,12 +114,14 @@ export const deleteCommand = new Command('delete')
113
114
  deleteSpinner?.succeed(`Container "${containerName}" deleted`)
114
115
 
115
116
  if (options.json) {
117
+ const metadata = await getEngineMetadata(config.engine)
116
118
  console.log(
117
119
  JSON.stringify({
118
120
  success: true,
119
121
  deleted: containerName,
120
122
  container: containerName,
121
123
  engine: config.engine,
124
+ ...metadata,
122
125
  }),
123
126
  )
124
127
  }
@@ -30,7 +30,11 @@ import { promptConfirm } from '../ui/prompts'
30
30
  import { createSpinner } from '../ui/spinner'
31
31
  import { uiError, uiWarning, uiInfo, uiSuccess, formatBytes } from '../ui/theme'
32
32
  import { getEngineIcon } from '../constants'
33
- import { getInstalledEngines, getInstalledPostgresEngines } from '../helpers'
33
+ import {
34
+ getInstalledEngines,
35
+ getInstalledPostgresEngines,
36
+ getEngineMetadata,
37
+ } from '../helpers'
34
38
  import { Engine, Platform } from '../../types'
35
39
  import {
36
40
  loadEnginesJson,
@@ -55,6 +59,7 @@ import { questdbBinaryManager } from '../../engines/questdb/binary-manager'
55
59
  import { typedbBinaryManager } from '../../engines/typedb/binary-manager'
56
60
  import { influxdbBinaryManager } from '../../engines/influxdb/binary-manager'
57
61
  import { weaviateBinaryManager } from '../../engines/weaviate/binary-manager'
62
+ import { tigerbeetleBinaryManager } from '../../engines/tigerbeetle/binary-manager'
58
63
  import {
59
64
  DEFAULT_DOCUMENTDB_VERSION,
60
65
  DEFAULT_V1_POSTGRESQL_VERSION,
@@ -397,7 +402,13 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
397
402
  const engines = await getInstalledEngines()
398
403
 
399
404
  if (options.json) {
400
- console.log(JSON.stringify(engines, null, 2))
405
+ const enginesWithMetadata = await Promise.all(
406
+ engines.map(async (e) => ({
407
+ ...e,
408
+ ...(await getEngineMetadata(e.engine)),
409
+ })),
410
+ )
411
+ console.log(JSON.stringify(enginesWithMetadata, null, 2))
401
412
  return
402
413
  }
403
414
 
@@ -473,6 +484,7 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
473
484
  typedb: 'TypeDB',
474
485
  valkey: 'Valkey',
475
486
  weaviate: 'Weaviate',
487
+ tigerbeetle: 'TigerBeetle',
476
488
  }
477
489
 
478
490
  // Group engines by name for summary
@@ -1775,9 +1787,53 @@ enginesCommand
1775
1787
  return
1776
1788
  }
1777
1789
 
1790
+ if (['tigerbeetle', 'tb'].includes(normalizedEngine)) {
1791
+ if (!version) {
1792
+ console.error(uiError('TigerBeetle requires a version (e.g., 0.16)'))
1793
+ process.exit(1)
1794
+ }
1795
+
1796
+ const engine = getEngine(Engine.TigerBeetle)
1797
+
1798
+ const spinner = createSpinner(
1799
+ `Checking TigerBeetle ${version} binaries...`,
1800
+ )
1801
+ spinner.start()
1802
+
1803
+ let wasCached = false
1804
+ await engine.ensureBinaries(version, ({ stage, message }) => {
1805
+ if (stage === 'cached') {
1806
+ wasCached = true
1807
+ spinner.text = `TigerBeetle ${version} binaries ready (cached)`
1808
+ } else {
1809
+ spinner.text = message
1810
+ }
1811
+ })
1812
+
1813
+ if (wasCached) {
1814
+ spinner.succeed(`TigerBeetle ${version} binaries already installed`)
1815
+ } else {
1816
+ spinner.succeed(`TigerBeetle ${version} binaries downloaded`)
1817
+ }
1818
+
1819
+ // Show the path for reference
1820
+ const { platform: tbPlatform, arch: tbArch } =
1821
+ platformService.getPlatformInfo()
1822
+ const tbFullVersion = tigerbeetleBinaryManager.getFullVersion(version)
1823
+ const binPath = paths.getBinaryPath({
1824
+ engine: 'tigerbeetle',
1825
+ version: tbFullVersion,
1826
+ platform: tbPlatform,
1827
+ arch: tbArch,
1828
+ })
1829
+ console.log(chalk.gray(` Location: ${binPath}`))
1830
+
1831
+ return
1832
+ }
1833
+
1778
1834
  console.error(
1779
1835
  uiError(
1780
- `Unknown engine "${engineName}". Supported: postgresql, mysql, mariadb, sqlite, duckdb, mongodb, ferretdb, redis, valkey, clickhouse, qdrant, meilisearch, couchdb, cockroachdb, surrealdb, questdb, typedb, influxdb, weaviate`,
1836
+ `Unknown engine "${engineName}". Supported: postgresql, mysql, mariadb, sqlite, duckdb, mongodb, ferretdb, redis, valkey, clickhouse, qdrant, meilisearch, couchdb, cockroachdb, surrealdb, questdb, typedb, influxdb, weaviate, tigerbeetle`,
1781
1837
  ),
1782
1838
  )
1783
1839
  process.exit(1)
@@ -10,6 +10,7 @@ import { getEngine } from '../../engines'
10
10
  import { uiError, uiInfo, header } from '../ui/theme'
11
11
  import { getEngineIcon } from '../constants'
12
12
  import { isFileBasedEngine, type ContainerConfig } from '../../types'
13
+ import { getEngineMetadata } from '../helpers'
13
14
 
14
15
  function formatDate(dateString: string): string {
15
16
  const date = new Date(dateString)
@@ -43,6 +44,7 @@ async function displayContainerInfo(
43
44
  })
44
45
 
45
46
  if (options.json) {
47
+ const metadata = await getEngineMetadata(config.engine)
46
48
  console.log(
47
49
  JSON.stringify(
48
50
  {
@@ -50,6 +52,7 @@ async function displayContainerInfo(
50
52
  status: actualStatus,
51
53
  connectionString,
52
54
  dataDir,
55
+ ...metadata,
53
56
  },
54
57
  null,
55
58
  2,
@@ -147,11 +150,13 @@ async function displayAllContainersInfo(
147
150
  const dataDir = paths.getContainerDataPath(config.name, {
148
151
  engine: config.engine,
149
152
  })
153
+ const metadata = await getEngineMetadata(config.engine)
150
154
  return {
151
155
  ...config,
152
156
  status: actualStatus,
153
157
  connectionString,
154
158
  dataDir,
159
+ ...metadata,
155
160
  }
156
161
  }),
157
162
  )
@@ -14,6 +14,7 @@ import {
14
14
  getRegistryForEngine,
15
15
  type UnregisteredFile,
16
16
  } from '../../engines/file-based-utils'
17
+ import { getEngineMetadata } from '../helpers'
17
18
 
18
19
  type UnregisteredFileWithEngine = UnregisteredFile & { engine: Engine }
19
20
 
@@ -159,6 +160,7 @@ export const listCommand = new Command('list')
159
160
  const containersWithSize = await Promise.all(
160
161
  containers.map(async (container) => ({
161
162
  ...container,
163
+ ...(await getEngineMetadata(container.engine)),
162
164
  sizeBytes: await getContainerSize(container),
163
165
  })),
164
166
  )
@@ -228,6 +228,8 @@ function validateConnectionString(
228
228
  return 'Connection string must start with http:// or https://'
229
229
  }
230
230
  break
231
+ case Engine.TigerBeetle:
232
+ return 'TigerBeetle does not support remote dumps (custom binary protocol)'
231
233
  case Engine.SQLite:
232
234
  case Engine.DuckDB:
233
235
  return 'File-based engines do not support remote connection strings'
@@ -49,6 +49,7 @@ function generatePreviewLine(mode: IconMode): string {
49
49
  [Engine.TypeDB]: '[TB]',
50
50
  [Engine.InfluxDB]: '[IX]',
51
51
  [Engine.Weaviate]: '[WV]',
52
+ [Engine.TigerBeetle]: '[TT]',
52
53
  }
53
54
  const icons = PREVIEW_ENGINES.map((engine) => {
54
55
  const icon = ASCII_ICONS[engine] || '[??]'
@@ -79,6 +80,7 @@ function generatePreviewLine(mode: IconMode): string {
79
80
  [Engine.TypeDB]: '\ue706',
80
81
  [Engine.InfluxDB]: '\udb85\udf95',
81
82
  [Engine.Weaviate]: '\uf0e8',
83
+ [Engine.TigerBeetle]: '\uf0d6',
82
84
  }
83
85
  const icons = PREVIEW_ENGINES.map((engine) => {
84
86
  const icon = NERD_ICONS[engine] || '\ue706'
@@ -109,6 +111,7 @@ function generatePreviewLine(mode: IconMode): string {
109
111
  [Engine.TypeDB]: '\u{1F916}',
110
112
  [Engine.InfluxDB]: '\u{1F4C8}',
111
113
  [Engine.Weaviate]: '\u{1F52E}',
114
+ [Engine.TigerBeetle]: '\u{1F42F}',
112
115
  }
113
116
  const icons = PREVIEW_ENGINES.map((engine) => EMOJI_ICONS[engine] || '\u25A3')
114
117
  return icons.join(' ')
@@ -288,6 +288,13 @@ export async function handleOpenShell(
288
288
  engineSpecificInstalled = false
289
289
  engineSpecificValue = null
290
290
  engineSpecificInstallValue = null
291
+ } else if (config.engine === 'tigerbeetle') {
292
+ // TigerBeetle uses tigerbeetle repl command
293
+ defaultShellName = 'tigerbeetle repl'
294
+ engineSpecificCli = null
295
+ engineSpecificInstalled = false
296
+ engineSpecificValue = null
297
+ engineSpecificInstallValue = null
291
298
  } else {
292
299
  defaultShellName = 'psql'
293
300
  engineSpecificCli = 'pgcli'
@@ -1737,6 +1744,22 @@ async function launchShell(
1737
1744
  shellArgs = ['console', ...getConsoleBaseArgs(config.port)]
1738
1745
  }
1739
1746
  installHint = 'spindb engines download typedb'
1747
+ } else if (config.engine === 'tigerbeetle') {
1748
+ // TigerBeetle uses tigerbeetle repl command
1749
+ // Cluster ID 0 is the default for local single-node development.
1750
+ // TigerBeetle format/start also use cluster 0 (see engines/tigerbeetle/index.ts).
1751
+ const clusterId = 0
1752
+ const engine = getEngine(config.engine)
1753
+ const tigerbeetlePath = await engine
1754
+ .getTigerBeetlePath(config.version)
1755
+ .catch(() => null)
1756
+ shellCmd = tigerbeetlePath || 'tigerbeetle'
1757
+ shellArgs = [
1758
+ 'repl',
1759
+ `--cluster=${clusterId}`,
1760
+ `--addresses=127.0.0.1:${config.port}`,
1761
+ ]
1762
+ installHint = 'spindb engines download tigerbeetle'
1740
1763
  } else {
1741
1764
  // PostgreSQL default shell - look up downloaded binary path
1742
1765
  const psqlPath = await configManager.getBinaryPath('psql')
@@ -20,6 +20,7 @@ import { platformService } from '../../core/platform-service'
20
20
  import { TransactionManager } from '../../core/transaction-manager'
21
21
  import { isFileBasedEngine } from '../../types'
22
22
  import { logDebug } from '../../core/error-handler'
23
+ import { getEngineMetadata } from '../helpers'
23
24
 
24
25
  export const restoreCommand = new Command('restore')
25
26
  .description('Restore a backup to a container')
@@ -502,6 +503,7 @@ export const restoreCommand = new Command('restore')
502
503
  )
503
504
 
504
505
  if (options.json) {
506
+ const metadata = await getEngineMetadata(engineName)
505
507
  console.log(
506
508
  JSON.stringify({
507
509
  success: true,
@@ -512,6 +514,7 @@ export const restoreCommand = new Command('restore')
512
514
  sourceType: options.fromUrl ? 'remote' : 'file',
513
515
  connectionString,
514
516
  overwritten: databaseExists,
517
+ ...metadata,
515
518
  }),
516
519
  )
517
520
  } else {
@@ -11,6 +11,7 @@ import { createSpinner } from '../ui/spinner'
11
11
  import { uiWarning } from '../ui/theme'
12
12
  import { Engine, isFileBasedEngine } from '../../types'
13
13
  import { exitWithError, logDebug } from '../../core/error-handler'
14
+ import { getEngineMetadata } from '../helpers'
14
15
 
15
16
  export const startCommand = new Command('start')
16
17
  .description('Start a container')
@@ -189,6 +190,7 @@ export const startCommand = new Command('start')
189
190
  const connectionString = engine.getConnectionString(config)
190
191
 
191
192
  if (options.json) {
193
+ const metadata = await getEngineMetadata(config.engine)
192
194
  console.log(
193
195
  JSON.stringify({
194
196
  success: true,
@@ -197,6 +199,7 @@ export const startCommand = new Command('start')
197
199
  port: result.finalPort,
198
200
  connectionString,
199
201
  portChanged: result.retriesUsed > 0,
202
+ ...metadata,
200
203
  }),
201
204
  )
202
205
  } else {
@@ -4,6 +4,7 @@ import { platformService } from '../../core/platform-service'
4
4
  import { getEngine } from '../../engines'
5
5
  import { promptContainerSelect } from '../ui/prompts'
6
6
  import { uiError, uiWarning, uiSuccess } from '../ui/theme'
7
+ import { getEngineMetadata } from '../helpers'
7
8
 
8
9
  export const urlCommand = new Command('url')
9
10
  .alias('connection-string')
@@ -55,6 +56,7 @@ export const urlCommand = new Command('url')
55
56
  )
56
57
 
57
58
  if (options.json) {
59
+ const metadata = await getEngineMetadata(config.engine)
58
60
  const jsonOutput =
59
61
  config.engine === 'sqlite'
60
62
  ? {
@@ -62,6 +64,7 @@ export const urlCommand = new Command('url')
62
64
  path: databaseName,
63
65
  engine: config.engine,
64
66
  container: config.name,
67
+ ...metadata,
65
68
  }
66
69
  : {
67
70
  connectionString,
@@ -71,6 +74,7 @@ export const urlCommand = new Command('url')
71
74
  user: config.engine === 'postgresql' ? 'postgres' : 'root',
72
75
  engine: config.engine,
73
76
  container: config.name,
77
+ ...metadata,
74
78
  }
75
79
  console.log(JSON.stringify(jsonOutput, null, 2))
76
80
  return
package/cli/constants.ts CHANGED
@@ -78,6 +78,7 @@ export const ENGINE_BRAND_COLORS: Record<Engine, BrandColor> = {
78
78
  [Engine.TypeDB]: { foreground: '#FFFFFF', background: '#7B2D8E' }, // White on purple
79
79
  [Engine.InfluxDB]: { foreground: '#FFFFFF', background: '#9394FF' }, // White on indigo/purple
80
80
  [Engine.Weaviate]: { foreground: '#FFFFFF', background: '#00D1A8' }, // White on green
81
+ [Engine.TigerBeetle]: { foreground: '#FFFFFF', background: '#FF6600' }, // White on orange
81
82
  }
82
83
 
83
84
  // ASCII fallback icons - work in any terminal
@@ -101,6 +102,7 @@ const ASCII_ICONS: Record<Engine, string> = {
101
102
  [Engine.TypeDB]: '[TB]',
102
103
  [Engine.InfluxDB]: '[IX]',
103
104
  [Engine.Weaviate]: '[WV]',
105
+ [Engine.TigerBeetle]: '[TT]',
104
106
  }
105
107
 
106
108
  // Nerd Font icons - require a patched font
@@ -125,6 +127,7 @@ const NERD_ICONS: Record<Engine, string> = {
125
127
  [Engine.TypeDB]: '\ue706', // nf-dev-database (knowledge graph)
126
128
  [Engine.InfluxDB]: '\udb85\udf95', // nf-md-chart-line (time-series)
127
129
  [Engine.Weaviate]: '\uf0e8', // nf-fa-sitemap (vector graph)
130
+ [Engine.TigerBeetle]: '\uf0d6', // nf-fa-money (financial ledger)
128
131
  }
129
132
 
130
133
  // Emoji icons - original icons, inconsistent width across terminals
@@ -148,6 +151,7 @@ const EMOJI_ICONS: Record<Engine, string> = {
148
151
  [Engine.TypeDB]: '🤖',
149
152
  [Engine.InfluxDB]: '📈',
150
153
  [Engine.Weaviate]: '🔮',
154
+ [Engine.TigerBeetle]: '🐯',
151
155
  }
152
156
 
153
157
  const DEFAULT_ICONS: Record<IconMode, string> = {