cinchdb 0.1.2__tar.gz → 0.1.4__tar.gz

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 (55) hide show
  1. {cinchdb-0.1.2 → cinchdb-0.1.4}/.gitignore +3 -1
  2. {cinchdb-0.1.2 → cinchdb-0.1.4}/PKG-INFO +2 -2
  3. {cinchdb-0.1.2 → cinchdb-0.1.4}/README.md +1 -1
  4. {cinchdb-0.1.2 → cinchdb-0.1.4}/pyproject.toml +1 -2
  5. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/branch.py +22 -13
  6. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/column.py +27 -14
  7. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/database.py +2 -2
  8. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/query.py +19 -12
  9. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/remote.py +32 -28
  10. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/table.py +20 -16
  11. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/tenant.py +4 -4
  12. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/main.py +21 -11
  13. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/utils.py +8 -6
  14. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/config.py +18 -45
  15. cinchdb-0.1.4/src/cinchdb/core/__init__.py +6 -0
  16. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/core/database.py +9 -5
  17. cinchdb-0.1.4/src/cinchdb/core/initializer.py +214 -0
  18. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/branch.py +1 -3
  19. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/column.py +13 -9
  20. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/query.py +12 -6
  21. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/table.py +12 -9
  22. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/tenant.py +3 -3
  23. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/branch.py +1 -1
  24. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/database.py +1 -1
  25. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/table.py +5 -8
  26. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/tenant.py +1 -1
  27. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/utils/__init__.py +5 -5
  28. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/utils/name_validator.py +62 -31
  29. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/utils/sql_validator.py +91 -41
  30. cinchdb-0.1.2/src/cinchdb/core/__init__.py +0 -5
  31. {cinchdb-0.1.2 → cinchdb-0.1.4}/LICENSE +0 -0
  32. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/__init__.py +0 -0
  33. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/__main__.py +0 -0
  34. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/__init__.py +0 -0
  35. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/__init__.py +0 -0
  36. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/codegen.py +0 -0
  37. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/commands/view.py +0 -0
  38. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/handlers/__init__.py +0 -0
  39. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/cli/handlers/codegen_handler.py +0 -0
  40. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/core/connection.py +0 -0
  41. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/core/maintenance.py +0 -0
  42. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/core/path_utils.py +0 -0
  43. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/__init__.py +0 -0
  44. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/change_applier.py +0 -0
  45. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/change_comparator.py +0 -0
  46. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/change_tracker.py +0 -0
  47. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/codegen.py +0 -0
  48. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/data.py +0 -0
  49. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/merge_manager.py +0 -0
  50. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/managers/view.py +0 -0
  51. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/__init__.py +0 -0
  52. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/base.py +0 -0
  53. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/change.py +0 -0
  54. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/project.py +0 -0
  55. {cinchdb-0.1.2 → cinchdb-0.1.4}/src/cinchdb/models/view.py +0 -0
@@ -68,4 +68,6 @@ Thumbs.db
68
68
 
69
69
  site/
70
70
 
71
- saas/
71
+ saas/
72
+
73
+ test_cinch_init
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinchdb
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A Git-like SQLite database management system with branching and multi-tenancy
5
5
  Project-URL: Homepage, https://github.com/russellromney/cinchdb
6
6
  Project-URL: Documentation, https://russellromney.github.io/cinchdb
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  NOTE: CinchDB is in early alpha. This is project to test out an idea. Do not use this in production.
33
33
 
34
- CinchDB is for projects that need fast queries, data isolated data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4), and a branchable database.
34
+ CinchDB is for projects that need fast queries, data isolated data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4), and a branchable database that makes it easy to merge changes between branches.
35
35
 
36
36
  On a meta level, I made this because I wanted a database structure that I felt comfortable letting AI agents take full control over, safely, and I didn't want to run my own Postgres instance somewhere or pay for it on e.g. Neon - I don't need hyperscaling, I just need super fast queries.
37
37
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  NOTE: CinchDB is in early alpha. This is project to test out an idea. Do not use this in production.
6
6
 
7
- CinchDB is for projects that need fast queries, data isolated data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4), and a branchable database.
7
+ CinchDB is for projects that need fast queries, data isolated data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4), and a branchable database that makes it easy to merge changes between branches.
8
8
 
9
9
  On a meta level, I made this because I wanted a database structure that I felt comfortable letting AI agents take full control over, safely, and I didn't want to run my own Postgres instance somewhere or pay for it on e.g. Neon - I don't need hyperscaling, I just need super fast queries.
10
10
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinchdb"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "A Git-like SQLite database management system with branching and multi-tenancy"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -51,7 +51,6 @@ include = [
51
51
 
52
52
  # Explicitly exclude other directories
53
53
  exclude = [
54
- "frontend/",
55
54
  "docs/",
56
55
  "sdk/",
57
56
  "site/",
@@ -81,14 +81,14 @@ def create(
81
81
  ):
82
82
  """Create a new branch."""
83
83
  name = validate_required_arg(name, "name", ctx)
84
-
84
+
85
85
  # Validate branch name
86
86
  try:
87
87
  validate_name(name, "branch")
88
88
  except InvalidNameError as e:
89
89
  console.print(f"[red]❌ {e}[/red]")
90
90
  raise typer.Exit(1)
91
-
91
+
92
92
  config, config_data = get_config_with_data()
93
93
  db_name = config_data.active_database
94
94
  source_branch = source or config_data.active_branch
@@ -338,24 +338,25 @@ def changes(
338
338
 
339
339
  try:
340
340
  from cinchdb.managers.change_tracker import ChangeTracker
341
-
341
+
342
342
  tracker = ChangeTracker(config.project_dir, db_name, branch_name)
343
343
  changes = tracker.get_changes()
344
344
 
345
345
  if not changes:
346
- console.print(f"[yellow]No changes found in branch '{branch_name}'[/yellow]")
346
+ console.print(
347
+ f"[yellow]No changes found in branch '{branch_name}'[/yellow]"
348
+ )
347
349
  return
348
350
 
349
351
  if format == "json":
350
352
  # JSON output
351
353
  import json
352
- from datetime import datetime
353
-
354
+
354
355
  changes_data = []
355
356
  for change in changes:
356
- change_dict = change.model_dump(mode='json')
357
+ change_dict = change.model_dump(mode="json")
357
358
  changes_data.append(change_dict)
358
-
359
+
359
360
  console.print(json.dumps(changes_data, indent=2, default=str))
360
361
  else:
361
362
  # Table output
@@ -368,24 +369,32 @@ def changes(
368
369
  table.add_column("Created", style="dim")
369
370
 
370
371
  for change in changes:
371
- created_at = change.created_at.strftime("%Y-%m-%d %H:%M:%S") if change.created_at else "Unknown"
372
+ created_at = (
373
+ change.created_at.strftime("%Y-%m-%d %H:%M:%S")
374
+ if change.created_at
375
+ else "Unknown"
376
+ )
372
377
  applied_status = "✓" if change.applied else "✗"
373
378
  table.add_row(
374
379
  change.id[:8] if change.id else "Unknown",
375
- change.type.value if hasattr(change.type, 'value') else str(change.type),
380
+ change.type.value
381
+ if hasattr(change.type, "value")
382
+ else str(change.type),
376
383
  change.entity_name,
377
384
  change.entity_type,
378
385
  applied_status,
379
- created_at
386
+ created_at,
380
387
  )
381
388
 
382
389
  console.print(table)
383
-
390
+
384
391
  # Summary
385
392
  total = len(changes)
386
393
  applied = sum(1 for c in changes if c.applied)
387
394
  unapplied = total - applied
388
- console.print(f"\n[bold]Total:[/bold] {total} changes ({applied} applied, {unapplied} unapplied)")
395
+ console.print(
396
+ f"\n[bold]Total:[/bold] {total} changes ({applied} applied, {unapplied} unapplied)"
397
+ )
389
398
 
390
399
  except ValueError as e:
391
400
  console.print(f"[red]❌ {e}[/red]")
@@ -235,7 +235,10 @@ def alter_nullable(
235
235
  None, "--nullable/--not-nullable", help="Make column nullable or NOT NULL"
236
236
  ),
237
237
  fill_value: Optional[str] = typer.Option(
238
- None, "--fill-value", "-f", help="Value to use for NULL values when making NOT NULL"
238
+ None,
239
+ "--fill-value",
240
+ "-f",
241
+ help="Value to use for NULL values when making NOT NULL",
239
242
  ),
240
243
  apply: bool = typer.Option(
241
244
  True, "--apply/--no-apply", help="Apply changes to all tenants"
@@ -244,20 +247,22 @@ def alter_nullable(
244
247
  """Change the nullable constraint on a column."""
245
248
  table = validate_required_arg(table, "table", ctx)
246
249
  column = validate_required_arg(column, "column", ctx)
247
-
250
+
248
251
  # Validate nullable flag was provided
249
252
  if nullable is None:
250
253
  console.print(ctx.get_help())
251
- console.print("\n[red]❌ Error: Must specify either --nullable or --not-nullable[/red]")
254
+ console.print(
255
+ "\n[red]❌ Error: Must specify either --nullable or --not-nullable[/red]"
256
+ )
252
257
  raise typer.Exit(1)
253
-
258
+
254
259
  config, config_data = get_config_with_data()
255
260
  db_name = config_data.active_database
256
261
  branch_name = config_data.active_branch
257
262
 
258
263
  try:
259
264
  column_mgr = ColumnManager(config.project_dir, db_name, branch_name, "main")
260
-
265
+
261
266
  # Check if column has NULLs when making NOT NULL
262
267
  if not nullable and fill_value is None:
263
268
  # Get column info to check current state
@@ -266,18 +271,22 @@ def alter_nullable(
266
271
  # Check for NULL values
267
272
  from cinchdb.core.connection import DatabaseConnection
268
273
  from cinchdb.core.path_utils import get_tenant_db_path
269
-
270
- db_path = get_tenant_db_path(config.project_dir, db_name, branch_name, "main")
274
+
275
+ db_path = get_tenant_db_path(
276
+ config.project_dir, db_name, branch_name, "main"
277
+ )
271
278
  with DatabaseConnection(db_path) as conn:
272
279
  cursor = conn.execute(
273
280
  f"SELECT COUNT(*) FROM {table} WHERE {column} IS NULL"
274
281
  )
275
282
  null_count = cursor.fetchone()[0]
276
-
283
+
277
284
  if null_count > 0:
278
- console.print(f"[yellow]Column '{column}' has {null_count} NULL values.[/yellow]")
285
+ console.print(
286
+ f"[yellow]Column '{column}' has {null_count} NULL values.[/yellow]"
287
+ )
279
288
  fill_value = typer.prompt("Provide a fill value")
280
-
289
+
281
290
  # Convert fill_value to appropriate type
282
291
  if fill_value is not None:
283
292
  # Try to interpret the value
@@ -288,13 +297,17 @@ def alter_nullable(
288
297
  elif fill_value.replace(".", "", 1).isdigit():
289
298
  fill_value = float(fill_value)
290
299
  # Otherwise keep as string
291
-
300
+
292
301
  column_mgr.alter_column_nullable(table, column, nullable, fill_value)
293
-
302
+
294
303
  if nullable:
295
- console.print(f"[green]✅ Made column '{column}' nullable in table '{table}'[/green]")
304
+ console.print(
305
+ f"[green]✅ Made column '{column}' nullable in table '{table}'[/green]"
306
+ )
296
307
  else:
297
- console.print(f"[green]✅ Made column '{column}' NOT NULL in table '{table}'[/green]")
308
+ console.print(
309
+ f"[green]✅ Made column '{column}' NOT NULL in table '{table}'[/green]"
310
+ )
298
311
 
299
312
  if apply:
300
313
  # Apply to all tenants
@@ -65,14 +65,14 @@ def create(
65
65
  ):
66
66
  """Create a new database."""
67
67
  name = validate_required_arg(name, "name", ctx)
68
-
68
+
69
69
  # Validate database name
70
70
  try:
71
71
  validate_name(name, "database")
72
72
  except InvalidNameError as e:
73
73
  console.print(f"[red]❌ {e}[/red]")
74
74
  raise typer.Exit(1)
75
-
75
+
76
76
  config, config_data = get_config_with_data()
77
77
 
78
78
  # Create database directory structure
@@ -5,14 +5,20 @@ from typing import Optional
5
5
  from rich.console import Console
6
6
  from rich.table import Table as RichTable
7
7
 
8
- from cinchdb.cli.utils import get_config_with_data, get_cinchdb_instance
9
- from cinchdb.managers.query import QueryManager
8
+ from cinchdb.cli.utils import get_cinchdb_instance
10
9
 
11
10
  app = typer.Typer(help="Execute SQL queries", invoke_without_command=True)
12
11
  console = Console()
13
12
 
14
13
 
15
- def execute_query(sql: str, tenant: str, format: str, limit: Optional[int], force_local: bool = False, remote_alias: Optional[str] = None):
14
+ def execute_query(
15
+ sql: str,
16
+ tenant: str,
17
+ format: str,
18
+ limit: Optional[int],
19
+ force_local: bool = False,
20
+ remote_alias: Optional[str] = None,
21
+ ):
16
22
  """Execute a SQL query."""
17
23
  # Add LIMIT if specified
18
24
  query_sql = sql
@@ -20,7 +26,9 @@ def execute_query(sql: str, tenant: str, format: str, limit: Optional[int], forc
20
26
  query_sql = f"{sql} LIMIT {limit}"
21
27
 
22
28
  # Get CinchDB instance (handles local/remote automatically)
23
- db = get_cinchdb_instance(tenant=tenant, force_local=force_local, remote_alias=remote_alias)
29
+ db = get_cinchdb_instance(
30
+ tenant=tenant, force_local=force_local, remote_alias=remote_alias
31
+ )
24
32
 
25
33
  try:
26
34
  # Check if this is a SELECT query
@@ -74,6 +82,7 @@ def execute_query(sql: str, tenant: str, format: str, limit: Optional[int], forc
74
82
  if db.is_local:
75
83
  # For local connections, use the query manager directly
76
84
  from cinchdb.managers.query import QueryManager
85
+
77
86
  query_mgr = QueryManager(db.project_dir, db.database, db.branch, tenant)
78
87
  affected_rows = query_mgr.execute_non_query(query_sql)
79
88
  console.print(
@@ -84,10 +93,8 @@ def execute_query(sql: str, tenant: str, format: str, limit: Optional[int], forc
84
93
  # For remote connections, the API should handle all SQL types
85
94
  # This might need API support - for now, try using query
86
95
  try:
87
- result = db.query(query_sql)
88
- console.print(
89
- f"[green]✅ Query executed successfully[/green]"
90
- )
96
+ db.query(query_sql)
97
+ console.print("[green]✅ Query executed successfully[/green]")
91
98
  except Exception as e:
92
99
  # If remote doesn't support non-SELECT via query, show helpful message
93
100
  console.print(
@@ -112,9 +119,7 @@ def main(
112
119
  limit: Optional[int] = typer.Option(
113
120
  None, "--limit", "-l", help="Limit number of rows"
114
121
  ),
115
- local: bool = typer.Option(
116
- False, "--local", "-L", help="Force local connection"
117
- ),
122
+ local: bool = typer.Option(False, "--local", "-L", help="Force local connection"),
118
123
  remote: Optional[str] = typer.Option(
119
124
  None, "--remote", "-r", help="Use specific remote alias"
120
125
  ),
@@ -133,4 +138,6 @@ def main(
133
138
  if not sql:
134
139
  console.print(ctx.get_help())
135
140
  raise typer.Exit(0)
136
- execute_query(sql, tenant, format, limit, force_local=local, remote_alias=remote)
141
+ execute_query(
142
+ sql, tenant, format, limit, force_local=local, remote_alias=remote
143
+ )
@@ -21,25 +21,27 @@ def add_remote(
21
21
  ):
22
22
  """Add a remote CinchDB instance configuration."""
23
23
  alias = validate_required_arg(alias, "alias", ctx)
24
-
24
+
25
25
  if not url:
26
26
  console.print("[red]❌ Error: --url is required[/red]")
27
27
  raise typer.Exit(1)
28
-
28
+
29
29
  if not key:
30
30
  console.print("[red]❌ Error: --key is required[/red]")
31
31
  raise typer.Exit(1)
32
-
32
+
33
33
  config, config_data = get_config_with_data()
34
-
34
+
35
35
  # Check if alias already exists
36
36
  if alias in config_data.remotes:
37
- console.print(f"[yellow]⚠️ Remote '{alias}' already exists. Updating...[/yellow]")
38
-
37
+ console.print(
38
+ f"[yellow]⚠️ Remote '{alias}' already exists. Updating...[/yellow]"
39
+ )
40
+
39
41
  # Add or update the remote
40
42
  config_data.remotes[alias] = RemoteConfig(url=url.rstrip("/"), key=key)
41
43
  config.save(config_data)
42
-
44
+
43
45
  console.print(f"[green]✓ Remote '{alias}' configured successfully[/green]")
44
46
 
45
47
 
@@ -47,20 +49,20 @@ def add_remote(
47
49
  def list_remotes():
48
50
  """List all configured remote instances."""
49
51
  config, config_data = get_config_with_data()
50
-
52
+
51
53
  if not config_data.remotes:
52
54
  console.print("[yellow]No remotes configured[/yellow]")
53
55
  return
54
-
56
+
55
57
  table = Table(title="Configured Remotes")
56
58
  table.add_column("Alias", style="cyan")
57
59
  table.add_column("URL", style="green")
58
60
  table.add_column("Active", style="yellow")
59
-
61
+
60
62
  for alias, remote in config_data.remotes.items():
61
63
  is_active = "✓" if alias == config_data.active_remote else ""
62
64
  table.add_row(alias, remote.url, is_active)
63
-
65
+
64
66
  console.print(table)
65
67
 
66
68
 
@@ -71,20 +73,20 @@ def remove_remote(
71
73
  ):
72
74
  """Remove a remote configuration."""
73
75
  alias = validate_required_arg(alias, "alias", ctx)
74
-
76
+
75
77
  config, config_data = get_config_with_data()
76
-
78
+
77
79
  if alias not in config_data.remotes:
78
80
  console.print(f"[red]❌ Remote '{alias}' not found[/red]")
79
81
  raise typer.Exit(1)
80
-
82
+
81
83
  # Remove the remote
82
84
  del config_data.remotes[alias]
83
-
85
+
84
86
  # If this was the active remote, clear it
85
87
  if config_data.active_remote == alias:
86
88
  config_data.active_remote = None
87
-
89
+
88
90
  config.save(config_data)
89
91
  console.print(f"[green]✓ Remote '{alias}' removed[/green]")
90
92
 
@@ -96,16 +98,16 @@ def use_remote(
96
98
  ):
97
99
  """Set the active remote instance."""
98
100
  alias = validate_required_arg(alias, "alias", ctx)
99
-
101
+
100
102
  config, config_data = get_config_with_data()
101
-
103
+
102
104
  if alias not in config_data.remotes:
103
105
  console.print(f"[red]❌ Remote '{alias}' not found[/red]")
104
106
  raise typer.Exit(1)
105
-
107
+
106
108
  config_data.active_remote = alias
107
109
  config.save(config_data)
108
-
110
+
109
111
  console.print(f"[green]✓ Now using remote '{alias}'[/green]")
110
112
  console.print(f"[dim]URL: {config_data.remotes[alias].url}[/dim]")
111
113
 
@@ -114,14 +116,14 @@ def use_remote(
114
116
  def clear_remote():
115
117
  """Clear the active remote (switch back to local mode)."""
116
118
  config, config_data = get_config_with_data()
117
-
119
+
118
120
  if not config_data.active_remote:
119
121
  console.print("[yellow]No active remote set[/yellow]")
120
122
  return
121
-
123
+
122
124
  config_data.active_remote = None
123
125
  config.save(config_data)
124
-
126
+
125
127
  console.print("[green]✓ Cleared active remote. Now using local mode.[/green]")
126
128
 
127
129
 
@@ -129,16 +131,18 @@ def clear_remote():
129
131
  def show_remote():
130
132
  """Show the currently active remote."""
131
133
  config, config_data = get_config_with_data()
132
-
134
+
133
135
  if not config_data.active_remote:
134
136
  console.print("[yellow]No active remote. Using local mode.[/yellow]")
135
137
  return
136
-
138
+
137
139
  alias = config_data.active_remote
138
140
  if alias not in config_data.remotes:
139
- console.print(f"[red]❌ Active remote '{alias}' not found in configuration[/red]")
141
+ console.print(
142
+ f"[red]❌ Active remote '{alias}' not found in configuration[/red]"
143
+ )
140
144
  return
141
-
145
+
142
146
  remote = config_data.remotes[alias]
143
147
  console.print(f"[green]Active remote:[/green] {alias}")
144
- console.print(f"[dim]URL: {remote.url}[/dim]")
148
+ console.print(f"[dim]URL: {remote.url}[/dim]")
@@ -60,7 +60,8 @@ def create(
60
60
  ctx: typer.Context,
61
61
  name: Optional[str] = typer.Argument(None, help="Name of the table"),
62
62
  columns: Optional[List[str]] = typer.Argument(
63
- None, help="Column definitions (format: name:type[:nullable][:fk=table[.column][:action]])"
63
+ None,
64
+ help="Column definitions (format: name:type[:nullable][:fk=table[.column][:action]])",
64
65
  ),
65
66
  apply: bool = typer.Option(
66
67
  True, "--apply/--no-apply", help="Apply changes to all tenants"
@@ -92,14 +93,16 @@ def create(
92
93
  parts = col_def.split(":")
93
94
  if len(parts) < 2:
94
95
  console.print(f"[red]❌ Invalid column definition: '{col_def}'[/red]")
95
- console.print("[yellow]Format: name:type[:nullable][:fk=table[.column][:action]][/yellow]")
96
+ console.print(
97
+ "[yellow]Format: name:type[:nullable][:fk=table[.column][:action]][/yellow]"
98
+ )
96
99
  raise typer.Exit(1)
97
100
 
98
101
  col_name = parts[0]
99
102
  col_type = parts[1].upper()
100
103
  nullable = False
101
104
  foreign_key = None
102
-
105
+
103
106
  # Parse additional parts
104
107
  for i in range(2, len(parts)):
105
108
  part = parts[i]
@@ -108,7 +111,7 @@ def create(
108
111
  elif part.startswith("fk="):
109
112
  # Parse foreign key definition
110
113
  fk_def = part[3:] # Remove "fk=" prefix
111
-
114
+
112
115
  # Handle actions with spaces (e.g., "set null", "no action")
113
116
  # Check for known actions at the end
114
117
  fk_action = "RESTRICT" # Default
@@ -116,12 +119,12 @@ def create(
116
119
  if fk_def.lower().endswith("." + action):
117
120
  fk_action = action.upper()
118
121
  # Remove the action part from fk_def
119
- fk_def = fk_def[:-len("." + action)]
122
+ fk_def = fk_def[: -len("." + action)]
120
123
  break
121
-
124
+
122
125
  # Now split the remaining parts
123
126
  fk_parts = fk_def.split(".")
124
-
127
+
125
128
  if len(fk_parts) == 1:
126
129
  # Just table name, column defaults to "id"
127
130
  fk_table = fk_parts[0]
@@ -131,15 +134,17 @@ def create(
131
134
  fk_table = fk_parts[0]
132
135
  fk_column = fk_parts[1]
133
136
  else:
134
- console.print(f"[red]❌ Invalid foreign key format: '{fk_def}'[/red]")
137
+ console.print(
138
+ f"[red]❌ Invalid foreign key format: '{fk_def}'[/red]"
139
+ )
135
140
  console.print("[yellow]Format: fk=table[.column][:action][/yellow]")
136
141
  raise typer.Exit(1)
137
-
142
+
138
143
  foreign_key = ForeignKeyRef(
139
144
  table=fk_table,
140
145
  column=fk_column,
141
146
  on_delete=fk_action,
142
- on_update="RESTRICT" # Default to RESTRICT for updates
147
+ on_update="RESTRICT", # Default to RESTRICT for updates
143
148
  )
144
149
 
145
150
  if col_type not in ["TEXT", "INTEGER", "REAL", "BLOB", "NUMERIC"]:
@@ -149,12 +154,11 @@ def create(
149
154
  )
150
155
  raise typer.Exit(1)
151
156
 
152
- parsed_columns.append(Column(
153
- name=col_name,
154
- type=col_type,
155
- nullable=nullable,
156
- foreign_key=foreign_key
157
- ))
157
+ parsed_columns.append(
158
+ Column(
159
+ name=col_name, type=col_type, nullable=nullable, foreign_key=foreign_key
160
+ )
161
+ )
158
162
 
159
163
  try:
160
164
  table_mgr = TableManager(config.project_dir, db_name, branch_name, "main")
@@ -71,14 +71,14 @@ def create(
71
71
  ):
72
72
  """Create a new tenant."""
73
73
  name = validate_required_arg(name, "name", ctx)
74
-
74
+
75
75
  # Validate tenant name
76
76
  try:
77
77
  validate_name(name, "tenant")
78
78
  except InvalidNameError as e:
79
79
  console.print(f"[red]❌ {e}[/red]")
80
80
  raise typer.Exit(1)
81
-
81
+
82
82
  config, config_data = get_config_with_data()
83
83
  db_name = config_data.active_database
84
84
  branch_name = config_data.active_branch
@@ -164,14 +164,14 @@ def rename(
164
164
  """Rename a tenant."""
165
165
  old_name = validate_required_arg(old_name, "old_name", ctx)
166
166
  new_name = validate_required_arg(new_name, "new_name", ctx)
167
-
167
+
168
168
  # Validate new tenant name
169
169
  try:
170
170
  validate_name(new_name, "tenant")
171
171
  except InvalidNameError as e:
172
172
  console.print(f"[red]❌ {e}[/red]")
173
173
  raise typer.Exit(1)
174
-
174
+
175
175
  config, config_data = get_config_with_data()
176
176
  db_name = config_data.active_database
177
177
  branch_name = config_data.active_branch
@@ -73,18 +73,27 @@ def init(
73
73
  path: Optional[Path] = typer.Argument(
74
74
  None, help="Directory to initialize project in (default: current directory)"
75
75
  ),
76
+ database: Optional[str] = typer.Option(
77
+ "main", "--database", "-d", help="Initial database name"
78
+ ),
79
+ branch: Optional[str] = typer.Option(
80
+ "main", "--branch", "-b", help="Initial branch name"
81
+ ),
76
82
  ):
77
83
  """Initialize a new CinchDB project."""
78
- from cinchdb.config import Config
84
+ from cinchdb.core.initializer import init_project
79
85
 
80
86
  project_path = path or Path.cwd()
81
87
 
82
88
  try:
83
- config = Config(project_path)
84
- config.init_project()
89
+ # Use the core initializer directly
90
+ init_project(
91
+ project_dir=project_path, database_name=database, branch_name=branch
92
+ )
85
93
  typer.secho(
86
94
  f"✅ Initialized CinchDB project in {project_path}", fg=typer.colors.GREEN
87
95
  )
96
+ typer.secho(f" Database: {database}, Branch: {branch}", fg=typer.colors.CYAN)
88
97
  except FileExistsError:
89
98
  typer.secho(f"❌ Project already exists in {project_path}", fg=typer.colors.RED)
90
99
  raise typer.Exit(1)
@@ -103,31 +112,32 @@ def status():
103
112
  """Show CinchDB status including configuration and environment variables."""
104
113
  from cinchdb.cli.utils import get_config_with_data, show_env_config
105
114
  from rich.console import Console
106
- from rich.table import Table as RichTable
107
-
115
+
108
116
  console = Console()
109
-
117
+
110
118
  # Show project configuration
111
119
  try:
112
120
  config, config_data = get_config_with_data()
113
-
121
+
114
122
  console.print("\n[bold]CinchDB Status[/bold]")
115
123
  console.print(f"Project: {config.project_dir}")
116
124
  console.print(f"Active Database: {config_data.active_database}")
117
125
  console.print(f"Active Branch: {config_data.active_branch}")
118
-
126
+
119
127
  if config_data.active_remote:
120
128
  console.print(f"Active Remote: {config_data.active_remote}")
121
129
  if config_data.active_remote in config_data.remotes:
122
130
  remote = config_data.remotes[config_data.active_remote]
123
131
  console.print(f" URL: {remote.url}")
124
- console.print(f" Key: ***{remote.key[-8:] if len(remote.key) > 8 else '*' * len(remote.key)}")
132
+ console.print(
133
+ f" Key: ***{remote.key[-8:] if len(remote.key) > 8 else '*' * len(remote.key)}"
134
+ )
125
135
  else:
126
136
  console.print("Active Remote: [dim]None (local mode)[/dim]")
127
-
137
+
128
138
  # Show environment variables
129
139
  show_env_config()
130
-
140
+
131
141
  except Exception as e:
132
142
  console.print(f"[red]❌ Error: {e}[/red]")
133
143
  raise typer.Exit(1)