spindb 0.31.4 → 0.33.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 (64) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +107 -826
  3. package/cli/commands/create.ts +5 -1
  4. package/cli/commands/engines.ts +256 -1
  5. package/cli/commands/menu/backup-handlers.ts +16 -0
  6. package/cli/commands/menu/container-handlers.ts +170 -17
  7. package/cli/commands/menu/engine-handlers.ts +6 -0
  8. package/cli/commands/menu/settings-handlers.ts +6 -0
  9. package/cli/commands/menu/shell-handlers.ts +74 -14
  10. package/cli/commands/menu/sql-handlers.ts +8 -50
  11. package/cli/commands/menu/validators.ts +8 -0
  12. package/cli/commands/users.ts +264 -0
  13. package/cli/constants.ts +8 -0
  14. package/cli/helpers.ts +140 -0
  15. package/cli/index.ts +2 -0
  16. package/cli/ui/prompts.ts +24 -20
  17. package/config/backup-formats.ts +28 -0
  18. package/config/engine-defaults.ts +26 -0
  19. package/config/engines-registry.ts +1 -0
  20. package/config/engines.json +50 -0
  21. package/config/engines.schema.json +6 -1
  22. package/core/base-binary-manager.ts +6 -1
  23. package/core/config-manager.ts +20 -0
  24. package/core/credential-manager.ts +257 -0
  25. package/core/dependency-manager.ts +5 -0
  26. package/core/docker-exporter.ts +30 -0
  27. package/core/error-handler.ts +19 -0
  28. package/engines/base-engine.ts +32 -1
  29. package/engines/clickhouse/index.ts +99 -3
  30. package/engines/cockroachdb/index.ts +69 -2
  31. package/engines/couchdb/index.ts +149 -1
  32. package/engines/ferretdb/README.md +4 -0
  33. package/engines/ferretdb/index.ts +342 -13
  34. package/engines/index.ts +8 -0
  35. package/engines/influxdb/README.md +180 -0
  36. package/engines/influxdb/api-client.ts +64 -0
  37. package/engines/influxdb/backup.ts +160 -0
  38. package/engines/influxdb/binary-manager.ts +110 -0
  39. package/engines/influxdb/binary-urls.ts +69 -0
  40. package/engines/influxdb/hostdb-releases.ts +23 -0
  41. package/engines/influxdb/index.ts +1227 -0
  42. package/engines/influxdb/restore.ts +417 -0
  43. package/engines/influxdb/version-maps.ts +75 -0
  44. package/engines/influxdb/version-validator.ts +128 -0
  45. package/engines/mariadb/index.ts +96 -1
  46. package/engines/meilisearch/index.ts +97 -1
  47. package/engines/mongodb/index.ts +82 -0
  48. package/engines/mysql/index.ts +105 -1
  49. package/engines/postgresql/index.ts +92 -0
  50. package/engines/qdrant/index.ts +107 -2
  51. package/engines/redis/index.ts +106 -12
  52. package/engines/surrealdb/index.ts +102 -2
  53. package/engines/typedb/backup.ts +167 -0
  54. package/engines/typedb/binary-manager.ts +200 -0
  55. package/engines/typedb/binary-urls.ts +38 -0
  56. package/engines/typedb/cli-utils.ts +210 -0
  57. package/engines/typedb/hostdb-releases.ts +118 -0
  58. package/engines/typedb/index.ts +1275 -0
  59. package/engines/typedb/restore.ts +377 -0
  60. package/engines/typedb/version-maps.ts +48 -0
  61. package/engines/typedb/version-validator.ts +127 -0
  62. package/engines/valkey/index.ts +70 -2
  63. package/package.json +4 -1
  64. package/types/index.ts +37 -0
@@ -32,6 +32,8 @@ import { createSpinner } from '../../ui/spinner'
32
32
  import { uiError, uiWarning, uiInfo, uiSuccess } from '../../ui/theme'
33
33
  import { pressEnterToContinue } from './shared'
34
34
  import { paths } from '../../../config/paths'
35
+ import { getEngineConfig } from '../../../config/engines-registry'
36
+ import { getConsoleBaseArgs } from '../../../engines/typedb/cli-utils'
35
37
 
36
38
  /**
37
39
  * Open a URL in the system's default browser
@@ -216,6 +218,13 @@ export async function handleOpenShell(
216
218
  engineSpecificInstalled = false
217
219
  engineSpecificValue = null
218
220
  engineSpecificInstallValue = null
221
+ } else if (config.engine === 'influxdb') {
222
+ // InfluxDB uses REST API, no interactive shell
223
+ defaultShellName = 'Web Dashboard'
224
+ engineSpecificCli = null
225
+ engineSpecificInstalled = false
226
+ engineSpecificValue = null
227
+ engineSpecificInstallValue = null
219
228
  } else if (config.engine === 'couchdb') {
220
229
  // CouchDB uses REST API, open Fauxton dashboard in browser
221
230
  defaultShellName = 'Fauxton Dashboard'
@@ -246,6 +255,13 @@ export async function handleOpenShell(
246
255
  engineSpecificInstalled = false
247
256
  engineSpecificValue = null
248
257
  engineSpecificInstallValue = null
258
+ } else if (config.engine === 'typedb') {
259
+ // TypeDB uses typedb console
260
+ defaultShellName = 'typedb console'
261
+ engineSpecificCli = null
262
+ engineSpecificInstalled = false
263
+ engineSpecificValue = null
264
+ engineSpecificInstallValue = null
249
265
  } else {
250
266
  defaultShellName = 'psql'
251
267
  engineSpecificCli = 'pgcli'
@@ -299,6 +315,12 @@ export async function handleOpenShell(
299
315
  name: `ℹ Show API info`,
300
316
  value: 'api-info',
301
317
  })
318
+ } else if (config.engine === 'influxdb') {
319
+ // InfluxDB: REST API only, no web dashboard or interactive shell
320
+ choices.push({
321
+ name: `ℹ Show API info`,
322
+ value: 'api-info',
323
+ })
302
324
  } else if (config.engine === 'couchdb') {
303
325
  // CouchDB: Fauxton dashboard is built-in at /_utils
304
326
  choices.push({
@@ -311,7 +333,7 @@ export async function handleOpenShell(
311
333
  value: 'api-info',
312
334
  })
313
335
  } else {
314
- // Non-Qdrant/Meilisearch/CouchDB engines: show default shell option
336
+ // Non-REST-API engines: show default shell option
315
337
  choices.push({
316
338
  name: `>_ Use default shell (${defaultShellName})`,
317
339
  value: 'default',
@@ -342,17 +364,9 @@ export async function handleOpenShell(
342
364
  }
343
365
  }
344
366
 
345
- // usql supports SQL databases (PostgreSQL, MySQL, SQLite) - skip for Redis, Valkey, MongoDB, FerretDB, Qdrant, Meilisearch, CouchDB, and SurrealDB
346
- const isNonSqlEngine =
347
- config.engine === 'redis' ||
348
- config.engine === 'valkey' ||
349
- config.engine === 'mongodb' ||
350
- config.engine === 'ferretdb' ||
351
- config.engine === 'qdrant' ||
352
- config.engine === 'meilisearch' ||
353
- config.engine === 'couchdb' ||
354
- config.engine === 'surrealdb'
355
- if (!isNonSqlEngine) {
367
+ // usql supports SQL databases - skip for non-SQL engines
368
+ const engineConfig = await getEngineConfig(config.engine)
369
+ if (engineConfig.queryLanguage === 'sql') {
356
370
  if (usqlInstalled) {
357
371
  choices.push({
358
372
  name: '⚡ Use usql (universal SQL client)',
@@ -423,6 +437,17 @@ export async function handleOpenShell(
423
437
  console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/indexes`))
424
438
  console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/health`))
425
439
  console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/stats`))
440
+ } else if (config.engine === 'influxdb') {
441
+ console.log(chalk.cyan('InfluxDB REST API:'))
442
+ console.log(chalk.white(` HTTP: http://127.0.0.1:${config.port}`))
443
+ console.log()
444
+ console.log(chalk.gray('Example curl commands:'))
445
+ console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/health`))
446
+ console.log(
447
+ chalk.gray(
448
+ ` curl -H "Content-Type: application/json" http://127.0.0.1:${config.port}/api/v3/query_sql -d '{"db":"mydb","q":"SELECT 1"}'`,
449
+ ),
450
+ )
426
451
  } else if (config.engine === 'couchdb') {
427
452
  console.log(chalk.cyan('CouchDB REST API:'))
428
453
  console.log(chalk.white(` HTTP: http://127.0.0.1:${config.port}`))
@@ -918,6 +943,24 @@ async function launchShell(
918
943
  openInBrowser(dashboardUrl)
919
944
  await pressEnterToContinue()
920
945
  return
946
+ } else if (config.engine === 'influxdb') {
947
+ // InfluxDB: REST API only, no web dashboard
948
+ // This branch shouldn't be reached since we removed the 'default' choice,
949
+ // but handle gracefully just in case
950
+ console.log()
951
+ console.log(chalk.cyan('InfluxDB REST API:'))
952
+ console.log(chalk.white(` HTTP: http://127.0.0.1:${config.port}`))
953
+ console.log()
954
+ console.log(chalk.gray('Example curl commands:'))
955
+ console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/health`))
956
+ console.log(
957
+ chalk.gray(
958
+ ` curl -H "Content-Type: application/json" http://127.0.0.1:${config.port}/api/v3/query_sql -d '{"db":"mydb","q":"SELECT 1"}'`,
959
+ ),
960
+ )
961
+ console.log()
962
+ await pressEnterToContinue()
963
+ return
921
964
  } else if (config.engine === 'couchdb') {
922
965
  // CouchDB: Open Fauxton dashboard in browser (served at /_utils)
923
966
  const dashboardUrl = `http://127.0.0.1:${config.port}/_utils`
@@ -990,10 +1033,27 @@ async function launchShell(
990
1033
  const questDbConnStr = `postgresql://admin:quest@127.0.0.1:${config.port}/${db}`
991
1034
  shellArgs = [questDbConnStr]
992
1035
  installHint = 'brew install libpq && brew link --force libpq'
1036
+ } else if (config.engine === 'typedb') {
1037
+ // TypeDB uses typedb console with address and tls-disabled flags
1038
+ const engine = getEngine(config.engine)
1039
+ const consolePath = await engine
1040
+ .getTypeDBConsolePath(config.version)
1041
+ .catch(() => null)
1042
+ if (consolePath) {
1043
+ shellCmd = consolePath
1044
+ shellArgs = getConsoleBaseArgs(config.port)
1045
+ } else {
1046
+ // Fallback: use the typedb launcher with 'console' subcommand
1047
+ shellCmd = 'typedb'
1048
+ shellArgs = ['console', ...getConsoleBaseArgs(config.port)]
1049
+ }
1050
+ installHint = 'spindb engines download typedb'
993
1051
  } else {
994
- shellCmd = 'psql'
1052
+ // PostgreSQL default shell - look up downloaded binary path
1053
+ const psqlPath = await configManager.getBinaryPath('psql')
1054
+ shellCmd = psqlPath || 'psql'
995
1055
  shellArgs = [connectionString]
996
- installHint = 'brew install libpq && brew link --force libpq'
1056
+ installHint = 'spindb engines download postgresql'
997
1057
  }
998
1058
 
999
1059
  const shellProcess = spawn(shellCmd, shellArgs, {
@@ -10,7 +10,7 @@ import { promptInstallDependencies, escapeablePrompt } from '../../ui/prompts'
10
10
  import { uiError, uiWarning, uiInfo, uiSuccess } from '../../ui/theme'
11
11
  import { pressEnterToContinue } from './shared'
12
12
  import { followFile, getLastNLines } from '../../utils/file-follower'
13
- import { Engine, assertExhaustive } from '../../../types'
13
+ import { getEngineConfig } from '../../../config/engines-registry'
14
14
 
15
15
  export async function handleRunSql(
16
16
  containerName: string,
@@ -56,52 +56,12 @@ export async function handleRunSql(
56
56
  // Strip quotes that terminals add when drag-and-dropping files
57
57
  const stripQuotes = (path: string) => path.replace(/^['"]|['"]$/g, '').trim()
58
58
 
59
- // Get script type terminology based on engine
60
- // IMPORTANT: When adding a new engine, update this function and ENGINE_CHECKLIST.md
61
- // - SQL: PostgreSQL, MySQL, MariaDB, SQLite, DuckDB, ClickHouse, CockroachDB
62
- // - SurrealQL: SurrealDB (SQL-like but distinct language)
63
- // - Script: MongoDB, FerretDB (JavaScript via mongosh), Qdrant, Meilisearch (REST API)
64
- // - Command: Redis, Valkey (Redis commands)
65
- const getScriptType = (engine: Engine): { type: string; lower: string } => {
66
- switch (engine) {
67
- // Redis-like engines use "Command" terminology
68
- case Engine.Redis:
69
- case Engine.Valkey:
70
- return { type: 'Command', lower: 'command' }
71
-
72
- // Document/search engines use "Script" terminology
73
- // MongoDB and FerretDB use JavaScript via mongosh
74
- // Qdrant, Meilisearch, and CouchDB use REST API (JSON)
75
- case Engine.MongoDB:
76
- case Engine.FerretDB:
77
- case Engine.Qdrant:
78
- case Engine.Meilisearch:
79
- case Engine.CouchDB:
80
- return { type: 'Script', lower: 'script' }
81
-
82
- // SurrealDB uses SurrealQL (distinct from SQL)
83
- case Engine.SurrealDB:
84
- return { type: 'SurrealQL', lower: 'SurrealQL' }
85
-
86
- // SQL engines use "SQL" terminology
87
- case Engine.PostgreSQL:
88
- case Engine.MySQL:
89
- case Engine.MariaDB:
90
- case Engine.SQLite:
91
- case Engine.DuckDB:
92
- case Engine.ClickHouse:
93
- case Engine.CockroachDB:
94
- case Engine.QuestDB:
95
- return { type: 'SQL', lower: 'sql' }
96
-
97
- default:
98
- assertExhaustive(engine)
99
- }
100
- }
101
-
102
- const { type: scriptType, lower: scriptTypeLower } = getScriptType(
103
- config.engine,
104
- )
59
+ // Script type terminology derived from engines.json scriptFileLabel
60
+ // e.g., "Run SQL file" type: "SQL", "Run TypeQL file" type: "TypeQL"
61
+ const engineConfig = await getEngineConfig(config.engine)
62
+ const scriptType = (engineConfig.scriptFileLabel ?? 'Script file')
63
+ .replace(/^Run\s+/, '')
64
+ .replace(/\s+file$/, '')
105
65
 
106
66
  // Prompt for file path (empty input = go back)
107
67
  console.log(
@@ -135,9 +95,7 @@ export async function handleRunSql(
135
95
  const databaseName = database || config.database
136
96
 
137
97
  console.log()
138
- console.log(
139
- uiInfo(`Running ${scriptTypeLower} file against "${databaseName}"...`),
140
- )
98
+ console.log(uiInfo(`Running ${scriptType} file against "${databaseName}"...`))
141
99
  console.log()
142
100
 
143
101
  try {
@@ -0,0 +1,8 @@
1
+ export function validateTypedbConnectionString(input: string): string | null {
2
+ const hostPortPattern = /^(?:[\w.-]+|\[[\da-fA-F:]+\]):\d+(?:\/.*)?$/
3
+ const schemeHostPattern = /^(?:typedb|typedb-core|https?):\/\/[^/]+/
4
+ if (!hostPortPattern.test(input) && !schemeHostPattern.test(input)) {
5
+ return 'Connection string must be host:port, [IPv6]:port, typedb://, typedb-core://, or http(s):// with a host'
6
+ }
7
+ return null
8
+ }
@@ -0,0 +1,264 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+ import { containerManager } from '../../core/container-manager'
4
+ import { processManager } from '../../core/process-manager'
5
+ import { getEngine } from '../../engines'
6
+ import { generatePassword } from '../../core/credential-generator'
7
+ import {
8
+ saveCredentials,
9
+ listCredentials,
10
+ credentialsExist,
11
+ getDefaultUsername,
12
+ } from '../../core/credential-manager'
13
+ import {
14
+ assertValidUsername,
15
+ UnsupportedOperationError,
16
+ } from '../../core/error-handler'
17
+ import { platformService } from '../../core/platform-service'
18
+ import { isFileBasedEngine } from '../../types'
19
+ import { uiError, uiSuccess, uiWarning } from '../ui/theme'
20
+
21
+ function exitWithError(message: string, json?: boolean): never {
22
+ if (json) {
23
+ console.log(JSON.stringify({ error: message }))
24
+ } else {
25
+ console.error(uiError(message))
26
+ }
27
+ process.exit(1)
28
+ }
29
+
30
+ export const usersCommand = new Command('users').description(
31
+ 'Manage database users and credentials',
32
+ )
33
+
34
+ usersCommand
35
+ .command('create')
36
+ .description('Create a database user')
37
+ .argument('<container>', 'Container name')
38
+ .argument('[username]', 'Username to create')
39
+ .option('-p, --password <password>', 'Use specific password')
40
+ .option('-d, --database <database>', 'Target database for grants')
41
+ .option('-c, --copy', 'Copy connection string to clipboard')
42
+ .option('-j, --json', 'Output as JSON')
43
+ .option('--no-save', 'Do not save credentials to disk')
44
+ .option('--force', 'Overwrite existing credential file')
45
+ .action(
46
+ async (
47
+ containerName: string,
48
+ username: string | undefined,
49
+ options: {
50
+ password?: string
51
+ database?: string
52
+ copy?: boolean
53
+ json?: boolean
54
+ save: boolean
55
+ force?: boolean
56
+ },
57
+ ) => {
58
+ try {
59
+ const config = await containerManager.getConfig(containerName)
60
+ if (!config) {
61
+ exitWithError(
62
+ `Container "${containerName}" not found. Run "spindb list" to see available containers.`,
63
+ options.json,
64
+ )
65
+ }
66
+
67
+ const engineName = config.engine
68
+
69
+ // Check container is running (skip for file-based engines)
70
+ if (!isFileBasedEngine(engineName)) {
71
+ const running = await processManager.isRunning(containerName, {
72
+ engine: engineName,
73
+ })
74
+ if (!running) {
75
+ exitWithError(
76
+ `Container "${containerName}" is not running. Start it with: spindb start ${containerName}`,
77
+ options.json,
78
+ )
79
+ }
80
+ }
81
+
82
+ // Use default username if none provided
83
+ const resolvedUsername = username || getDefaultUsername(config.engine)
84
+
85
+ assertValidUsername(resolvedUsername)
86
+
87
+ const engine = getEngine(engineName)
88
+
89
+ // Generate or use provided password
90
+ const password =
91
+ options.password ||
92
+ generatePassword({ length: 20, alphanumericOnly: true })
93
+
94
+ const database = options.database || config.database
95
+
96
+ // Check if credential file already exists
97
+ if (
98
+ options.save &&
99
+ !options.force &&
100
+ credentialsExist(containerName, engineName, resolvedUsername)
101
+ ) {
102
+ exitWithError(
103
+ `Credential file already exists for "${resolvedUsername}" in "${containerName}". Use --force to overwrite.`,
104
+ options.json,
105
+ )
106
+ }
107
+
108
+ // Create the user in the database
109
+ const credentials = await engine.createUser(config, {
110
+ username: resolvedUsername,
111
+ password,
112
+ database,
113
+ })
114
+
115
+ // Save credentials to disk (non-fatal — credentials are already created)
116
+ let credentialFile: string | undefined
117
+ if (options.save) {
118
+ try {
119
+ credentialFile = await saveCredentials(
120
+ containerName,
121
+ engineName,
122
+ credentials,
123
+ )
124
+ } catch (error) {
125
+ if (!options.json) {
126
+ console.error(
127
+ uiWarning(
128
+ `Could not save credentials to disk: ${(error as Error).message}`,
129
+ ),
130
+ )
131
+ }
132
+ process.exitCode = 1
133
+ }
134
+ }
135
+
136
+ // Copy to clipboard before output so JSON includes the status
137
+ let clipboardCopied: boolean | undefined
138
+ if (options.copy) {
139
+ const textToCopy = credentials.apiKey || credentials.connectionString
140
+ if (textToCopy) {
141
+ clipboardCopied = await platformService.copyToClipboard(textToCopy)
142
+ }
143
+ }
144
+
145
+ // Output results
146
+ if (options.json) {
147
+ const result: Record<string, unknown> = {
148
+ username: credentials.username,
149
+ password: credentials.password,
150
+ ...(credentials.database != null && {
151
+ database: credentials.database,
152
+ }),
153
+ connectionString: credentials.connectionString,
154
+ ...(credentials.apiKey != null && { apiKey: credentials.apiKey }),
155
+ ...(credentialFile != null && {
156
+ credentialFile,
157
+ }),
158
+ ...(clipboardCopied !== undefined && { clipboardCopied }),
159
+ }
160
+ console.log(JSON.stringify(result, null, 2))
161
+ } else {
162
+ console.log()
163
+ console.log(uiSuccess(`Created user "${resolvedUsername}"`))
164
+ console.log()
165
+ if (credentials.apiKey) {
166
+ console.log(` ${chalk.gray('Key name:')} ${credentials.username}`)
167
+ console.log(` ${chalk.gray('API key:')} ${credentials.apiKey}`)
168
+ console.log(
169
+ ` ${chalk.gray('API URL:')} ${credentials.connectionString}`,
170
+ )
171
+ } else {
172
+ console.log(` ${chalk.gray('Username:')} ${credentials.username}`)
173
+ console.log(` ${chalk.gray('Password:')} ${credentials.password}`)
174
+ if (credentials.database) {
175
+ console.log(
176
+ ` ${chalk.gray('Database:')} ${credentials.database}`,
177
+ )
178
+ }
179
+ console.log(
180
+ ` ${chalk.gray('URL:')} ${credentials.connectionString}`,
181
+ )
182
+ }
183
+ if (credentialFile) {
184
+ console.log()
185
+ console.log(` ${chalk.gray('Saved to:')} ${credentialFile}`)
186
+ }
187
+ console.log()
188
+
189
+ // Show clipboard status in human-readable output
190
+ if (clipboardCopied !== undefined) {
191
+ if (clipboardCopied) {
192
+ console.log(
193
+ uiSuccess(
194
+ credentials.apiKey
195
+ ? 'API key copied to clipboard'
196
+ : 'Connection string copied to clipboard',
197
+ ),
198
+ )
199
+ } else {
200
+ console.log(uiWarning('Could not copy to clipboard'))
201
+ }
202
+ }
203
+ }
204
+ } catch (error) {
205
+ if (error instanceof UnsupportedOperationError) {
206
+ exitWithError(
207
+ 'User management is not supported for this engine',
208
+ options.json,
209
+ )
210
+ }
211
+ exitWithError((error as Error).message, options.json)
212
+ }
213
+ },
214
+ )
215
+
216
+ usersCommand
217
+ .command('list')
218
+ .description('List saved credentials for a container')
219
+ .argument('<container>', 'Container name')
220
+ .option('-j, --json', 'Output as JSON')
221
+ .action(async (containerName: string, options: { json?: boolean }) => {
222
+ try {
223
+ const config = await containerManager.getConfig(containerName)
224
+ if (!config) {
225
+ exitWithError(`Container "${containerName}" not found`, options.json)
226
+ }
227
+
228
+ const usernames = await listCredentials(containerName, config.engine)
229
+
230
+ if (options.json) {
231
+ console.log(
232
+ JSON.stringify(
233
+ {
234
+ container: containerName,
235
+ engine: config.engine,
236
+ users: usernames,
237
+ },
238
+ null,
239
+ 2,
240
+ ),
241
+ )
242
+ } else {
243
+ if (usernames.length === 0) {
244
+ console.log()
245
+ console.log(chalk.gray(`No saved credentials for "${containerName}"`))
246
+ console.log(
247
+ chalk.gray(
248
+ ` Create one with: spindb users create ${containerName} <username>`,
249
+ ),
250
+ )
251
+ console.log()
252
+ } else {
253
+ console.log()
254
+ console.log(chalk.bold(`Saved credentials for "${containerName}":`))
255
+ for (const user of usernames) {
256
+ console.log(` ${chalk.cyan(user)}`)
257
+ }
258
+ console.log()
259
+ }
260
+ }
261
+ } catch (error) {
262
+ exitWithError((error as Error).message, options.json)
263
+ }
264
+ })
package/cli/constants.ts CHANGED
@@ -75,6 +75,8 @@ export const ENGINE_BRAND_COLORS: Record<Engine, BrandColor> = {
75
75
  [Engine.CockroachDB]: { foreground: '#FFFFFF', background: '#6933FF' }, // White on purple
76
76
  [Engine.SurrealDB]: { foreground: '#FFFFFF', background: '#FF00A0' }, // White on pink
77
77
  [Engine.QuestDB]: { foreground: '#000000', background: '#02FC04' }, // Black on green
78
+ [Engine.TypeDB]: { foreground: '#FFFFFF', background: '#7B2D8E' }, // White on purple
79
+ [Engine.InfluxDB]: { foreground: '#FFFFFF', background: '#9394FF' }, // White on indigo/purple
78
80
  }
79
81
 
80
82
  // ASCII fallback icons - work in any terminal
@@ -95,6 +97,8 @@ const ASCII_ICONS: Record<Engine, string> = {
95
97
  [Engine.CockroachDB]: '[CR]',
96
98
  [Engine.SurrealDB]: '[SR]',
97
99
  [Engine.QuestDB]: '[QS]',
100
+ [Engine.TypeDB]: '[TB]',
101
+ [Engine.InfluxDB]: '[IX]',
98
102
  }
99
103
 
100
104
  // Nerd Font icons - require a patched font
@@ -116,6 +120,8 @@ const NERD_ICONS: Record<Engine, string> = {
116
120
  [Engine.CockroachDB]: '\ue269', // nf-fae-cockroach
117
121
  [Engine.SurrealDB]: '\uedfe', // nf-fa-infinity (multi-model)
118
122
  [Engine.QuestDB]: '\ued2f', // nf-fa-gauge-high (time-series performance)
123
+ [Engine.TypeDB]: '\ue706', // nf-dev-database (knowledge graph)
124
+ [Engine.InfluxDB]: '\udb85\udf95', // nf-md-chart-line (time-series)
119
125
  }
120
126
 
121
127
  // Emoji icons - original icons, inconsistent width across terminals
@@ -136,6 +142,8 @@ const EMOJI_ICONS: Record<Engine, string> = {
136
142
  [Engine.CockroachDB]: '🪳',
137
143
  [Engine.SurrealDB]: '🌀',
138
144
  [Engine.QuestDB]: '⏱',
145
+ [Engine.TypeDB]: '🤖',
146
+ [Engine.InfluxDB]: '📈',
139
147
  }
140
148
 
141
149
  const DEFAULT_ICONS: Record<IconMode, string> = {