mythik-cli 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # mythik-cli
2
2
 
3
- Command-line tooling for Mythik specs.
3
+ The AI-first surface to Mythik. The `mythik` command and its
4
+ programmatic sibling (`mythik-cli/api`) are how an AI agent — or a
5
+ human — reads, validates, patches, versions, and promotes Mythik
6
+ specs.
7
+
8
+ > See [the framework README on GitHub](https://github.com/mldixdev/mythik#readme)
9
+ > for the full Mythik architecture and design philosophy. This file
10
+ > documents what `mythik-cli` gives you and how to use it.
11
+
12
+ ---
13
+
14
+ ## What Mythik is, briefly
15
+
16
+ Mythik is an **AI-first, spec-driven framework**. Every UI screen and
17
+ API endpoint lives as a JSON spec in a database — not a `.tsx` file
18
+ in a repo. Apps grow by adding rows, not files. The framework
19
+ validates every change atomically, versions everything automatically,
20
+ and promotes between environments (`dev`, `staging`, `production`) by
21
+ atomic pointer move. Your favorite AI agent drives the full workflow
22
+ through this CLI — no GUI in the loop.
4
23
 
5
24
  ## Install
6
25
 
@@ -14,58 +33,197 @@ The package exposes the `mythik` binary:
14
33
  npx mythik --help
15
34
  ```
16
35
 
17
- ## Common Commands
36
+ ## The canonical workflow
37
+
38
+ The Mythik loop is four steps, all served by this package:
18
39
 
19
- ```bash
20
- npx mythik docs path
21
- npx mythik docs copy ./mythik-docs
22
- npx mythik manifest home
23
- npx mythik elements home root,submit-button
24
- npx mythik validate home
25
- npx mythik patch home --from-file patch.json
26
- npx mythik push home --from-file home.json
27
- npx mythik pull home
28
- npx mythik lint --from-file home.json
29
- npx mythik contract --app app-demo --api api-demo
30
40
  ```
41
+ ┌──────────────────────────────────────────────────┐
42
+ │ │
43
+ ▼ │
44
+ manifest ──▶ elements ──▶ patch ──▶ observe
45
+ (read (read just (RFC 6902 (the
46
+ structure) what you diff; next
47
+ need) atomic manifest)
48
+ validation
49
+ on push)
50
+ ```
51
+
52
+ Each step is one command:
53
+
54
+ ```bash
55
+ # 1. Read structure
56
+ $ npx mythik manifest task-manager
57
+
58
+ # 2. Read just the elements you need (use --toon for token-efficient output)
59
+ $ npx mythik elements task-manager task-row,new-task-form --json
60
+
61
+ # 3. Apply a patch (validates atomically; rejected if it breaks the contract)
62
+ $ npx mythik patch task-manager --from-file add-due-date.json --author claude
63
+ ✓ Patch applied. v3 → v4. 4 elements modified.
64
+
65
+ # 4. Observe — read the new structure
66
+ $ npx mythik manifest task-manager
67
+ ```
68
+
69
+ When `patch` fails (the candidate breaks the contract), the gate
70
+ returns structured diagnostics: rule IDs, paths, suggestion text. The
71
+ agent reads the error, refines the patch, and retries. No half-baked
72
+ write ever lands.
73
+
74
+ ## Moving across environments
75
+
76
+ `dev`, `staging`, `production` are not deploy targets — they are
77
+ pointers to specific versions of a spec. Move them with `envs`;
78
+ promote across them atomically with `promote`:
79
+
80
+ ```bash
81
+ # Move the dev pointer to the new version
82
+ $ npx mythik envs task-manager --set dev=4 --author claude
83
+
84
+ # Promote dev → production (validates atomically; rejected if inconsistent)
85
+ $ npx mythik promote task-manager --from dev --to production --confirm --author mldix
86
+ ✓ Validated against contract.
87
+ ✓ Cross-screen consistency check passed.
88
+ ✓ Pointer moved: production v3 → v4.
89
+
90
+ # List current environment pointers
91
+ $ npx mythik envs task-manager --json
92
+ ```
93
+
94
+ ## Read-only previews
31
95
 
32
- Use `--json` for machine-readable output and `--toon` where supported for token-efficient agent workflows.
96
+ For drafts and audits *outside* the canonical loop:
97
+
98
+ ```bash
99
+ # Validate a stored spec (read-only check; no write)
100
+ $ npx mythik validate task-manager
101
+
102
+ # Lint a local file (draft not yet pushed) or a directory
103
+ $ npx mythik lint --from-file draft.json
104
+ $ npx mythik lint --from-dir ./specs
105
+
106
+ # Cross-validate a frontend spec against an ApiSpec (catches drift before deploy)
107
+ $ npx mythik contract --app app-demo --api api-demo
108
+ ```
109
+
110
+ All three run the same validators that `patch` and `promote` enforce
111
+ atomically — useful when constructing complex multi-step changes
112
+ locally before committing to a push.
33
113
 
34
- ## AI Documentation
114
+ ## Store backends
35
115
 
36
- `mythik` bundles the AI documentation corpus inside the core package. Use:
116
+ The CLI can operate against memory, file, Supabase, SQL Server,
117
+ PostgreSQL, MySQL, and SQLite stores. SQL stores share the same edit
118
+ loop: inspect with `manifest`, read exact nodes with `elements`, write
119
+ with `patch --from-file`, then verify.
37
120
 
38
121
  ```bash
39
- npx mythik docs path
40
- ```
122
+ # SQLite: local development, demos, tests
123
+ npx mythik init-store --dialect sqlite --target ./mythik.db
124
+ npx mythik patch floor-editor --from-file patch.json --store sqlite --filename ./mythik.db --author ai-agent
41
125
 
42
- Point the AI agent at the printed directory before asking it to create or modify Mythik specs. To copy the docs into a
43
- project-local folder:
126
+ # PostgreSQL
127
+ npx mythik init-store --dialect postgres --dry-run
128
+ npx mythik patch floor-editor --from-file patch.json --store postgres --url "$DATABASE_URL" --author ai-agent
44
129
 
45
- ```bash
46
- npx mythik docs copy ./mythik-docs
130
+ # MySQL
131
+ npx mythik init-store --dialect mysql --dry-run
132
+ npx mythik patch floor-editor --from-file patch.json --store mysql --url "$DATABASE_URL" --author ai-agent
133
+
134
+ # SQL Server
135
+ npx mythik init-store --dialect sqlserver --server localhost --database Mythik --user "$DB_USER" --password "$DB_PASSWORD" --encrypt false --trust-server-certificate
136
+ npx mythik patch floor-editor --from-file patch.json --store sqlserver --server localhost --database Mythik --user "$DB_USER" --password "$DB_PASSWORD" --author ai-agent
47
137
  ```
138
+
139
+ `init-store --dry-run` prints the idempotent DDL for review or for a
140
+ team-managed migration pipeline. Runtime reads and writes do not
141
+ silently create missing store tables. MySQL generated upsert SQL
142
+ targets MySQL 8.0.19+.
143
+
144
+ ## History and rollback
145
+
146
+ ```bash
147
+ # Show version history with structural diffs
148
+ $ npx mythik history task-manager
149
+
150
+ # Compare two versions or two environments
151
+ $ npx mythik diff task-manager 3 4
152
+ $ npx mythik diff task-manager dev production
153
+
154
+ # Always-forward rollback (creates a new version with old content)
155
+ $ npx mythik rollback task-manager --to 3 --author mldix
156
+ ```
157
+
158
+ Rollback never rewrites history. The new version `v5` will hold the
159
+ content of `v3`. `v4` stays in the history with its own author and
160
+ timestamp.
161
+
162
+ ## Output formats
163
+
164
+ | Flag | When to use |
165
+ |---|---|
166
+ | `--json` | Machine-readable output for scripts and IDE integrations |
167
+ | `--toon` (where supported) | Token-efficient agent reads — same shape, fewer tokens, useful for paginating large manifests or element catalogs |
48
168
 
49
169
  ## Programmatic API
50
170
 
51
- Use `mythik-cli/api` when a script, IDE integration, or AI agent should avoid shell quoting issues:
171
+ When a script, IDE integration, or AI agent should avoid shell
172
+ quoting, use `mythik-cli/api`:
52
173
 
53
174
  ```ts
54
- import { runPatch, runPush, runLint } from 'mythik-cli/api';
175
+ import { runPatch, runValidate, runPromote } from 'mythik-cli/api';
55
176
 
56
- await runPatch({
57
- screen: 'home',
58
- fromFile: 'patch.json',
177
+ const result = await runPatch({
178
+ screen: 'task-manager',
179
+ fromFile: 'add-due-date.json',
180
+ author: 'claude',
59
181
  json: true,
60
182
  });
183
+
184
+ if (!result.ok) {
185
+ console.error(result.diagnostics);
186
+ }
61
187
  ```
62
188
 
63
- The programmatic API uses the same implementation path as the CLI.
189
+ The programmatic API uses the same implementation path as the binary —
190
+ nothing is special-cased. Whatever the CLI does, the API does
191
+ identically.
64
192
 
65
- ## License
193
+ ## Bundled AI documentation
66
194
 
67
- Apache-2.0.
195
+ The `mythik` package (peer dependency) ships canonical AI docs at
196
+ `node_modules/mythik/docs/`. Locate or copy them:
197
+
198
+ ```bash
199
+ $ npx mythik docs path
200
+ /your-project/node_modules/mythik/docs
201
+
202
+ $ npx mythik docs copy ./mythik-docs
203
+ ```
204
+
205
+ Point your AI agent at the printed path before asking it to author or
206
+ modify specs. Inside that directory:
207
+
208
+ - `consumer/ai-context.md` — spec-generation primer (rules and traps)
209
+ - `consumer/ai-context-primitives.md` — every primitive's props
210
+ - `consumer/reference-doc.md` — full rule catalog
211
+ - `wiki/compiled/` — atomic per-concept articles (one page per action,
212
+ primitive, rule, expression) optimized for AI lookup
213
+ - `llms.txt` — index for AI tools that read it
214
+
215
+ ## Related packages
216
+
217
+ - [`mythik`](https://github.com/mldixdev/mythik/tree/main/packages/core#readme) — the runtime this CLI manipulates
218
+ - [`mythik-react`](https://github.com/mldixdev/mythik/tree/main/packages/react#readme) — render the specs as a React app
219
+ - [`mythik-server`](https://github.com/mldixdev/mythik/tree/main/packages/server#readme) — declarative REST server from an `ApiSpec`
68
220
 
69
221
  ## Status
70
222
 
71
- v0.1.1 public release. The binary name is `mythik`; the npm package name is `mythik-cli`.
223
+ Public release line. The binary name is `mythik`; the npm package name
224
+ is `mythik-cli`. APIs are documented for real-world feedback as the
225
+ framework evolves.
226
+
227
+ ## License
228
+
229
+ Apache-2.0.
package/dist/api.d.ts CHANGED
@@ -15,6 +15,8 @@ export { runPatch, parsePatchInput } from './commands/patch.js';
15
15
  export type { PatchOptions } from './commands/patch.js';
16
16
  export { runDocsCommand, resolveBundledDocsRoot } from './commands/docs.js';
17
17
  export type { DocsCommandOptions, DocsCommandResult } from './commands/docs.js';
18
+ export { runInitStore } from './commands/init-store.js';
19
+ export type { InitStoreOptions } from './commands/init-store.js';
18
20
  export type { CommandResult } from './commands/manifest.js';
19
21
  export type { SpecStore, JsonPatch, ValidationError, PatchResult } from 'mythik';
20
22
  export { runLint } from './lint/orchestrator.js';
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG5D,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAGjF,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG5D,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAGjF,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/api.js CHANGED
@@ -12,6 +12,7 @@
12
12
  export { runPush } from './commands/push.js';
13
13
  export { runPatch, parsePatchInput } from './commands/patch.js';
14
14
  export { runDocsCommand, resolveBundledDocsRoot } from './commands/docs.js';
15
+ export { runInitStore } from './commands/init-store.js';
15
16
  // Lint API — runLint + types (v49 Item I)
16
17
  export { runLint } from './lint/orchestrator.js';
17
18
  //# sourceMappingURL=api.js.map
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAQ5E,0CAA0C;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAQxD,0CAA0C;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { loadConfig } from './config.js';
3
+ import { loadConfig, withStoreTableOverride } from './config.js';
4
4
  import { resolveStore } from './stores/resolver.js';
5
5
  import { runManifest } from './commands/manifest.js';
6
6
  import { runElements } from './commands/elements.js';
7
7
  import { runPatch, parsePatchInput } from './commands/patch.js';
8
8
  import { runValidate } from './commands/validate.js';
9
9
  import { runInit } from './commands/init.js';
10
+ import { runInitStore } from './commands/init-store.js';
10
11
  import { runPull } from './commands/pull.js';
11
12
  import { runPush } from './commands/push.js';
12
13
  import { runDelete } from './commands/delete.js';
@@ -23,19 +24,26 @@ import { runDocsCommand } from './commands/docs.js';
23
24
  import { resolveInput } from './input-resolver.js';
24
25
  import { runPushBulk } from './commands/push-bulk.js';
25
26
  const program = new Command();
27
+ const STORE_HELP = 'Store type (supabase, sqlserver, postgres, mysql, sqlite, file, memory)';
26
28
  program
27
29
  .name('mythik')
28
30
  .description('Mythik CLI — manage specs via SpecEngine')
29
- .version('0.1.1');
31
+ .version('0.1.3');
30
32
  program
31
33
  .command('manifest <screen>')
32
34
  .description('Show structural tree of a screen spec')
33
35
  .option('--json', 'Output as JSON', false)
34
- .option('--store <type>', 'Store type (supabase, file, memory)')
36
+ .option('--store <type>', STORE_HELP)
35
37
  .option('--table <name>', 'Table name override (e.g., api_specs)')
36
38
  .option('--url <url>', 'Supabase URL')
37
39
  .option('--key <key>', 'Supabase API key')
40
+ .option('--server <host>', 'SQL Server host')
41
+ .option('--database <name>', 'SQL Server database name')
42
+ .option('--user <user>', 'SQL database user')
43
+ .option('--password <password>', 'SQL database password')
44
+ .option('--port <port>', 'SQL database port')
38
45
  .option('--dir <dir>', 'File store directory')
46
+ .option('--filename <file>', 'SQLite database file')
39
47
  .action(async (screen, opts) => {
40
48
  try {
41
49
  const config = loadConfig({ flags: opts });
@@ -54,11 +62,17 @@ program
54
62
  .description('Show details of specific elements by ID (comma-separated)')
55
63
  .option('--json', 'Output as JSON', false)
56
64
  .option('--toon', 'Output in TOON format (token-efficient)', false)
57
- .option('--store <type>', 'Store type (supabase, file, memory)')
65
+ .option('--store <type>', STORE_HELP)
58
66
  .option('--table <name>', 'Table name override (e.g., api_specs)')
59
67
  .option('--url <url>', 'Supabase URL')
60
68
  .option('--key <key>', 'Supabase API key')
69
+ .option('--server <host>', 'SQL Server host')
70
+ .option('--database <name>', 'SQL Server database name')
71
+ .option('--user <user>', 'SQL database user')
72
+ .option('--password <password>', 'SQL database password')
73
+ .option('--port <port>', 'SQL database port')
61
74
  .option('--dir <dir>', 'File store directory')
75
+ .option('--filename <file>', 'SQLite database file')
62
76
  .action(async (screen, ids, opts) => {
63
77
  try {
64
78
  const config = loadConfig({ flags: opts });
@@ -81,11 +95,17 @@ program
81
95
  .option('--author <name>', 'Author name for version history')
82
96
  .option('--description <text>', 'Description for version history')
83
97
  .option('--from-file <path>', 'Read patches from file (use "-" for stdin)')
84
- .option('--store <type>', 'Store type (supabase, file, memory)')
98
+ .option('--store <type>', STORE_HELP)
85
99
  .option('--table <name>', 'Table name override (e.g., api_specs)')
86
100
  .option('--url <url>', 'Supabase URL')
87
101
  .option('--key <key>', 'Supabase API key')
102
+ .option('--server <host>', 'SQL Server host')
103
+ .option('--database <name>', 'SQL Server database name')
104
+ .option('--user <user>', 'SQL database user')
105
+ .option('--password <password>', 'SQL database password')
106
+ .option('--port <port>', 'SQL database port')
88
107
  .option('--dir <dir>', 'File store directory')
108
+ .option('--filename <file>', 'SQLite database file')
89
109
  .action(async (screen, patchesArg, opts) => {
90
110
  try {
91
111
  const config = loadConfig({ flags: opts });
@@ -122,11 +142,17 @@ program
122
142
  .command('validate <screen>')
123
143
  .description('Validate a screen spec for errors')
124
144
  .option('--json', 'Output as JSON', false)
125
- .option('--store <type>', 'Store type (supabase, file, memory)')
145
+ .option('--store <type>', STORE_HELP)
126
146
  .option('--table <name>', 'Table name override (e.g., api_specs)')
127
147
  .option('--url <url>', 'Supabase URL')
128
148
  .option('--key <key>', 'Supabase API key')
149
+ .option('--server <host>', 'SQL Server host')
150
+ .option('--database <name>', 'SQL Server database name')
151
+ .option('--user <user>', 'SQL database user')
152
+ .option('--password <password>', 'SQL database password')
153
+ .option('--port <port>', 'SQL database port')
129
154
  .option('--dir <dir>', 'File store directory')
155
+ .option('--filename <file>', 'SQLite database file')
130
156
  .action(async (screen, opts) => {
131
157
  try {
132
158
  const config = loadConfig({ flags: opts });
@@ -143,11 +169,17 @@ program
143
169
  program
144
170
  .command('init')
145
171
  .description('Initialize Mythik CLI configuration')
146
- .option('--store <type>', 'Store type (supabase, file, memory)')
172
+ .option('--store <type>', STORE_HELP)
147
173
  .option('--table <name>', 'Table name override (e.g., api_specs)')
148
174
  .option('--url <url>', 'Supabase URL')
149
175
  .option('--key <key>', 'Supabase API key')
176
+ .option('--server <host>', 'SQL Server host')
177
+ .option('--database <name>', 'SQL Server database name')
178
+ .option('--user <user>', 'SQL database user')
179
+ .option('--password <password>', 'SQL database password')
180
+ .option('--port <port>', 'SQL database port')
150
181
  .option('--dir <dir>', 'File store directory')
182
+ .option('--filename <file>', 'SQLite database file')
151
183
  .action(async (opts) => {
152
184
  try {
153
185
  const result = await runInit(opts, process.cwd());
@@ -161,6 +193,43 @@ program
161
193
  process.exit(1);
162
194
  }
163
195
  });
196
+ program
197
+ .command('init-store')
198
+ .description('Initialize Mythik-managed SQL store tables')
199
+ .requiredOption('--dialect <dialect>', 'SQL dialect (sqlserver, postgres, mysql, sqlite)')
200
+ .option('--target <file>', 'SQLite database file target')
201
+ .option('--url <url>', 'Database URL for PostgreSQL, MySQL, or SQL Server')
202
+ .option('--server <host>', 'SQL Server host')
203
+ .option('--database <name>', 'SQL Server database name')
204
+ .option('--user <user>', 'SQL Server user')
205
+ .option('--password <password>', 'SQL Server password')
206
+ .option('--port <port>', 'SQL Server port')
207
+ .option('--encrypt <value>', 'SQL Server encrypt option (true or false)')
208
+ .option('--trust-server-certificate', 'Trust SQL Server self-signed certificates')
209
+ .option('--dry-run', 'Print canonical DDL without connecting', false)
210
+ .action(async (opts) => {
211
+ try {
212
+ const result = await runInitStore({
213
+ dialect: opts.dialect,
214
+ target: opts.target,
215
+ url: opts.url,
216
+ server: opts.server,
217
+ database: opts.database,
218
+ user: opts.user,
219
+ password: opts.password,
220
+ port: opts.port,
221
+ encrypt: opts.encrypt,
222
+ trustServerCertificate: opts.trustServerCertificate === true,
223
+ dryRun: opts.dryRun === true,
224
+ });
225
+ process.stdout.write(result.output + '\n');
226
+ process.exit(result.exitCode);
227
+ }
228
+ catch (err) {
229
+ console.error(err.message);
230
+ process.exit(1);
231
+ }
232
+ });
164
233
  program
165
234
  .command('push [screen]')
166
235
  .description('Create or replace screen spec(s) from stdin, --from-file, or --from-dir')
@@ -170,11 +239,17 @@ program
170
239
  .option('--description <text>', 'Description for version history')
171
240
  .option('--from-file <path>', 'Read spec from file (use "-" for stdin)')
172
241
  .option('--from-dir <folder>', 'Push every *.json file in <folder> (filename stem = spec id)')
173
- .option('--store <type>', 'Store type (supabase, file, memory, sqlserver)')
242
+ .option('--store <type>', STORE_HELP)
174
243
  .option('--table <name>', 'Table name override (e.g., api_specs)')
175
244
  .option('--url <url>', 'Supabase URL')
176
245
  .option('--key <key>', 'Supabase API key')
246
+ .option('--server <host>', 'SQL Server host')
247
+ .option('--database <name>', 'SQL Server database name')
248
+ .option('--user <user>', 'SQL database user')
249
+ .option('--password <password>', 'SQL database password')
250
+ .option('--port <port>', 'SQL database port')
177
251
  .option('--dir <dir>', 'File store directory')
252
+ .option('--filename <file>', 'SQLite database file')
178
253
  .action(async (screen, opts) => {
179
254
  try {
180
255
  const config = loadConfig({ flags: opts });
@@ -232,11 +307,17 @@ program
232
307
  .description('Export a full screen spec to stdout')
233
308
  .option('--json', 'Wrap output in JSON object', false)
234
309
  .option('--toon', 'Output in TOON format', false)
235
- .option('--store <type>', 'Store type (supabase, file, memory, sqlserver)')
310
+ .option('--store <type>', STORE_HELP)
236
311
  .option('--table <name>', 'Table name override (e.g., api_specs)')
237
312
  .option('--url <url>', 'Supabase URL')
238
313
  .option('--key <key>', 'Supabase API key')
314
+ .option('--server <host>', 'SQL Server host')
315
+ .option('--database <name>', 'SQL Server database name')
316
+ .option('--user <user>', 'SQL database user')
317
+ .option('--password <password>', 'SQL database password')
318
+ .option('--port <port>', 'SQL database port')
239
319
  .option('--dir <dir>', 'File store directory')
320
+ .option('--filename <file>', 'SQLite database file')
240
321
  .action(async (screen, opts) => {
241
322
  try {
242
323
  const config = loadConfig({ flags: opts });
@@ -255,11 +336,17 @@ program
255
336
  .description('Delete a screen spec from the store')
256
337
  .option('--confirm', 'Actually delete (without this flag, preview only)', false)
257
338
  .option('--json', 'Output as JSON', false)
258
- .option('--store <type>', 'Store type (supabase, file, memory, sqlserver)')
339
+ .option('--store <type>', STORE_HELP)
259
340
  .option('--table <name>', 'Table name override (e.g., api_specs)')
260
341
  .option('--url <url>', 'Supabase URL')
261
342
  .option('--key <key>', 'Supabase API key')
343
+ .option('--server <host>', 'SQL Server host')
344
+ .option('--database <name>', 'SQL Server database name')
345
+ .option('--user <user>', 'SQL database user')
346
+ .option('--password <password>', 'SQL database password')
347
+ .option('--port <port>', 'SQL database port')
262
348
  .option('--dir <dir>', 'File store directory')
349
+ .option('--filename <file>', 'SQLite database file')
263
350
  .action(async (screen, opts) => {
264
351
  try {
265
352
  const config = loadConfig({ flags: opts });
@@ -284,11 +371,17 @@ program
284
371
  .option('--base-url <url>', 'Base URL to strip from fetch URLs')
285
372
  .option('--api-table <table>', 'Table name for api-specs (when stored separately)')
286
373
  .option('--json', 'Output as JSON', false)
287
- .option('--store <type>', 'Store type (supabase, file, memory, sqlserver)')
374
+ .option('--store <type>', STORE_HELP)
288
375
  .option('--table <name>', 'Table name override (e.g., api_specs)')
289
376
  .option('--url <url>', 'Supabase URL')
290
377
  .option('--key <key>', 'Supabase API key')
378
+ .option('--server <host>', 'SQL Server host')
379
+ .option('--database <name>', 'SQL Server database name')
380
+ .option('--user <user>', 'SQL database user')
381
+ .option('--password <password>', 'SQL database password')
382
+ .option('--port <port>', 'SQL database port')
291
383
  .option('--dir <dir>', 'File store directory')
384
+ .option('--filename <file>', 'SQLite database file')
292
385
  .action(async (opts) => {
293
386
  try {
294
387
  const config = loadConfig({ flags: opts });
@@ -297,12 +390,7 @@ program
297
390
  // If --api-table is provided, create a separate store for api-specs
298
391
  let apiStore;
299
392
  if (opts.apiTable) {
300
- const apiConfig = { ...config };
301
- if (apiConfig.sqlserver)
302
- apiConfig.sqlserver = { ...apiConfig.sqlserver, table: opts.apiTable };
303
- if (apiConfig.supabase)
304
- apiConfig.supabase = { ...apiConfig.supabase, table: opts.apiTable };
305
- apiStore = resolveStore(apiConfig);
393
+ apiStore = resolveStore(withStoreTableOverride(config, opts.apiTable));
306
394
  }
307
395
  const result = await runContractCommand({
308
396
  store,
@@ -326,8 +414,18 @@ program
326
414
  .description('Show version history for a spec')
327
415
  .option('--limit <n>', 'Limit number of versions shown', parseInt)
328
416
  .option('--json', 'Output as JSON', false)
329
- .option('--store <type>', 'Store type')
417
+ .option('--store <type>', STORE_HELP)
330
418
  .option('--table <name>', 'Table name override (e.g., api_specs)')
419
+ .option('--versions-table <name>', 'SQL version history table name')
420
+ .option('--environments-table <name>', 'SQL environment pointer table name')
421
+ .option('--snapshot-interval <n>', 'SQL version snapshot interval')
422
+ .option('--url <url>', 'Database URL')
423
+ .option('--server <host>', 'SQL Server host')
424
+ .option('--database <name>', 'SQL Server database name')
425
+ .option('--user <user>', 'SQL database user')
426
+ .option('--password <password>', 'SQL database password')
427
+ .option('--port <port>', 'SQL database port')
428
+ .option('--filename <file>', 'SQLite database file')
331
429
  .action(async (specId, opts) => {
332
430
  try {
333
431
  const config = loadConfig({ flags: opts });
@@ -345,8 +443,18 @@ program
345
443
  .command('diff <specId> <from> [to]')
346
444
  .description('Show structural diff between two versions or environments')
347
445
  .option('--json', 'Output as JSON', false)
348
- .option('--store <type>', 'Store type')
446
+ .option('--store <type>', STORE_HELP)
349
447
  .option('--table <name>', 'Table name override (e.g., api_specs)')
448
+ .option('--versions-table <name>', 'SQL version history table name')
449
+ .option('--environments-table <name>', 'SQL environment pointer table name')
450
+ .option('--snapshot-interval <n>', 'SQL version snapshot interval')
451
+ .option('--url <url>', 'Database URL')
452
+ .option('--server <host>', 'SQL Server host')
453
+ .option('--database <name>', 'SQL Server database name')
454
+ .option('--user <user>', 'SQL database user')
455
+ .option('--password <password>', 'SQL database password')
456
+ .option('--port <port>', 'SQL database port')
457
+ .option('--filename <file>', 'SQLite database file')
350
458
  .action(async (specId, from, to, opts) => {
351
459
  try {
352
460
  const config = loadConfig({ flags: opts });
@@ -366,8 +474,18 @@ program
366
474
  .option('--set <env=version>', 'Set environment pointer (e.g., --set dev=12)')
367
475
  .option('--author <name>', 'Author for set operation')
368
476
  .option('--json', 'Output as JSON', false)
369
- .option('--store <type>', 'Store type')
477
+ .option('--store <type>', STORE_HELP)
370
478
  .option('--table <name>', 'Table name override (e.g., api_specs)')
479
+ .option('--versions-table <name>', 'SQL version history table name')
480
+ .option('--environments-table <name>', 'SQL environment pointer table name')
481
+ .option('--snapshot-interval <n>', 'SQL version snapshot interval')
482
+ .option('--url <url>', 'Database URL')
483
+ .option('--server <host>', 'SQL Server host')
484
+ .option('--database <name>', 'SQL Server database name')
485
+ .option('--user <user>', 'SQL database user')
486
+ .option('--password <password>', 'SQL database password')
487
+ .option('--port <port>', 'SQL database port')
488
+ .option('--filename <file>', 'SQLite database file')
371
489
  .action(async (specId, opts) => {
372
490
  try {
373
491
  const config = loadConfig({ flags: opts });
@@ -389,8 +507,18 @@ program
389
507
  .option('--author <name>', 'Author name', 'system')
390
508
  .option('--description <text>', 'Description for the rollback version')
391
509
  .option('--json', 'Output as JSON', false)
392
- .option('--store <type>', 'Store type')
510
+ .option('--store <type>', STORE_HELP)
393
511
  .option('--table <name>', 'Table name override (e.g., api_specs)')
512
+ .option('--versions-table <name>', 'SQL version history table name')
513
+ .option('--environments-table <name>', 'SQL environment pointer table name')
514
+ .option('--snapshot-interval <n>', 'SQL version snapshot interval')
515
+ .option('--url <url>', 'Database URL')
516
+ .option('--server <host>', 'SQL Server host')
517
+ .option('--database <name>', 'SQL Server database name')
518
+ .option('--user <user>', 'SQL database user')
519
+ .option('--password <password>', 'SQL database password')
520
+ .option('--port <port>', 'SQL database port')
521
+ .option('--filename <file>', 'SQLite database file')
394
522
  .action(async (specId, opts) => {
395
523
  try {
396
524
  const config = loadConfig({ flags: opts });
@@ -416,8 +544,18 @@ program
416
544
  .option('--confirm', 'Execute promotion (without this, preview only)', false)
417
545
  .option('--author <name>', 'Author name', 'system')
418
546
  .option('--json', 'Output as JSON', false)
419
- .option('--store <type>', 'Store type')
547
+ .option('--store <type>', STORE_HELP)
420
548
  .option('--table <name>', 'Table name override (e.g., api_specs)')
549
+ .option('--versions-table <name>', 'SQL version history table name')
550
+ .option('--environments-table <name>', 'SQL environment pointer table name')
551
+ .option('--snapshot-interval <n>', 'SQL version snapshot interval')
552
+ .option('--url <url>', 'Database URL')
553
+ .option('--server <host>', 'SQL Server host')
554
+ .option('--database <name>', 'SQL Server database name')
555
+ .option('--user <user>', 'SQL database user')
556
+ .option('--password <password>', 'SQL database password')
557
+ .option('--port <port>', 'SQL database port')
558
+ .option('--filename <file>', 'SQLite database file')
421
559
  .action(async (specIds, opts) => {
422
560
  try {
423
561
  const config = loadConfig({ flags: opts });