@dboio/cli 0.6.6 → 0.6.8

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
@@ -128,11 +128,12 @@ All configuration is **directory-scoped**. Each project folder maintains its own
128
128
  |------|---------|-----|
129
129
  | `config.json` | Domain, app metadata, placement preferences | Committable (shared) |
130
130
  | `config.local.json` | Per-user settings: plugin scopes, future user prefs | Gitignored (per-user) |
131
+ | `ticketing.local.json` | Stored ticket IDs for submission error recovery | Gitignored (per-user) |
131
132
  | `credentials.json` | Username, user ID, UID, name, email (no password) | Gitignored (per-user) |
132
133
  | `cookies.txt` | Session cookie (Netscape format) | Gitignored (per-user) |
133
134
  | `structure.json` | Bin directory mapping (created by `dbo clone`) | Committable (shared) |
134
135
 
135
- `dbo init` automatically adds `.dbo/credentials.json`, `.dbo/cookies.txt`, and `.dbo/config.local.json` to `.gitignore` (creates the file if it doesn't exist).
136
+ `dbo init` automatically adds `.dbo/credentials.json`, `.dbo/cookies.txt`, `.dbo/config.local.json`, and `.dbo/ticketing.local.json` to `.gitignore` (creates the file if it doesn't exist).
136
137
 
137
138
  #### config.json reference
138
139
 
@@ -252,6 +253,34 @@ dbo init --domain my-domain.com --app myapp --clone
252
253
 
253
254
  When cloning an app that was already cloned locally, the CLI detects existing files and compares modification times against the server's `_LastUpdated`. You'll be prompted to overwrite, compare differences, or skip — same as `dbo pull`. Use `-y` to auto-accept all changes.
254
255
 
256
+ #### Collision detection
257
+
258
+ When multiple records would create files at the same path (e.g., a `content` record and a `media` record both named `colors.css`), the CLI detects the collision before writing any files and prompts you to choose which record to keep:
259
+
260
+ ```
261
+ ⚠ Collision: 2 records want to create "Bins/app/colors.css"
262
+ ? Which record should create this file?
263
+ ❯ [content] colors (UID: abc123)
264
+ [media] colors.css (UID: def456)
265
+ ```
266
+
267
+ The rejected record is automatically staged for deletion in `.dbo/synchronize.json`. Run `dbo push` to delete it from the server.
268
+
269
+ In non-interactive mode (`-y`), the first record is kept and others are auto-staged for deletion.
270
+
271
+ #### Stale media cleanup
272
+
273
+ During media downloads, files returning 404 (no longer exist on the server) are collected as "stale records". After all downloads complete, you'll be prompted to stage them for deletion:
274
+
275
+ ```
276
+ Found 3 stale media record(s) (404 - files no longer exist on server)
277
+ ? Stage these 3 stale media records for deletion? (y/N)
278
+ ```
279
+
280
+ This helps keep your app clean by removing database records for media files that have been deleted from the server.
281
+
282
+ In non-interactive mode (`-y`), stale cleanup is skipped (conservative default).
283
+
255
284
  #### Path resolution
256
285
 
257
286
  When a content record has both `Path` and `BinID`, the CLI prompts:
@@ -1090,7 +1119,35 @@ The `add` and `push` commands never submit these server-managed columns:
1090
1119
 
1091
1120
  The `input`, `push`, and `add` commands automatically detect recoverable server errors and prompt for missing values instead of failing immediately.
1092
1121
 
1093
- #### Ticket ID required
1122
+ #### Ticket error recovery
1123
+
1124
+ When the server returns a `ticket_error` (record update requires a Ticket ID), the CLI prompts with interactive recovery options:
1125
+
1126
+ ```
1127
+ ⚠ This record update requires a Ticket ID.
1128
+ ? Record update requires a Ticket ID:
1129
+ ❯ Apply a Ticket ID to this record and resubmit
1130
+ Apply a Ticket ID to all updates in this transaction, and update my current Ticket ID reference
1131
+ Skip this record update
1132
+ Skip all updates that require a Ticket ID
1133
+ ```
1134
+
1135
+ When the server returns a `repo_mismatch` (Ticket ID belongs to a different repository), the CLI prompts:
1136
+
1137
+ ```
1138
+ ⚠ Ticket "TICKET-123" is for another repository.
1139
+ ? The Ticket ID of "TICKET-123" is for another Repository:
1140
+ ❯ Commit anyway
1141
+ Submit with another Ticket ID
1142
+ Skip this record
1143
+ Commit all transactions with this ID anyway
1144
+ Commit all transactions with another Ticket ID, and update my current Ticket ID reference
1145
+ Skip all
1146
+ ```
1147
+
1148
+ Ticket selections are stored in `.dbo/ticketing.local.json` for reuse across submissions. Per-record tickets are cleaned up after successful submission; the global ticket persists until explicitly cleared. The `--ticket` flag always takes precedence over stored tickets.
1149
+
1150
+ #### Ticket ID required (legacy)
1094
1151
 
1095
1152
  When the server returns `ticket_lookup_required_error`, the CLI prompts:
1096
1153
 
@@ -1101,6 +1158,40 @@ When the server returns `ticket_lookup_required_error`, the CLI prompts:
1101
1158
 
1102
1159
  The submission is then retried with `_OverrideTicketID`. To skip the prompt, pass `--ticket <id>` upfront.
1103
1160
 
1161
+ #### Pre-submission ticket prompt
1162
+
1163
+ When a stored ticket exists in `.dbo/ticketing.local.json`, the CLI prompts before batch submissions:
1164
+
1165
+ ```
1166
+ ? Use stored Ticket ID "TICKET-123" for this submission?
1167
+ ❯ Yes, use "TICKET-123"
1168
+ No, clear stored ticket
1169
+ Cancel submission
1170
+ ```
1171
+
1172
+ #### `.dbo/ticketing.local.json`
1173
+
1174
+ Stores ticket IDs for automatic application during submissions:
1175
+
1176
+ ```json
1177
+ {
1178
+ "ticket_id": "TICKET-123",
1179
+ "records": [
1180
+ {
1181
+ "UID": "a2dxvg23rk6xsmnum7pdxa",
1182
+ "RowID": 16012,
1183
+ "entity": "content",
1184
+ "ticket_id": "TICKET-456",
1185
+ "expression": "RowID:16012;column:content._LastUpdatedTicketID=TICKET-456"
1186
+ }
1187
+ ]
1188
+ }
1189
+ ```
1190
+
1191
+ - `ticket_id` — Global ticket applied to all submissions until cleared
1192
+ - `records` — Per-record tickets (auto-cleared after successful submission)
1193
+ - `--ticket` flag always takes precedence over stored tickets
1194
+
1104
1195
  #### User identity required
1105
1196
 
1106
1197
  When the server returns an error mentioning `LoggedInUser_UID`, `LoggedInUserID`, `CurrentUserID`, or `UserID`, the CLI checks for a stored user identity from `dbo login`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dboio/cli",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "CLI for the DBO.io framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbo",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "DBO.io CLI integration for Claude Code",
5
5
  "author": {
6
6
  "name": "DBO.io"
@@ -7,6 +7,7 @@ import { formatResponse, formatError } from '../lib/formatter.js';
7
7
  import { log } from '../lib/logger.js';
8
8
  import { shouldSkipColumn } from '../lib/columns.js';
9
9
  import { loadAppConfig } from '../lib/config.js';
10
+ import { checkStoredTicket, clearGlobalTicket } from '../lib/ticketing.js';
10
11
 
11
12
  // Directories and patterns to skip when scanning with `dbo add .`
12
13
  const IGNORE_DIRS = new Set(['.dbo', '.git', 'node_modules', '.svn', '.hg']);
@@ -245,10 +246,21 @@ async function submitAdd(meta, metaPath, filePath, client, options) {
245
246
  let body = await buildInputBody(dataExprs, extraParams);
246
247
  let result = await client.postUrlEncoded('/api/input/submit', body);
247
248
 
248
- // Retry with prompted params if needed (ticket, user)
249
- const retryParams = await checkSubmitErrors(result);
250
- if (retryParams) {
251
- Object.assign(extraParams, retryParams);
249
+ // Retry with prompted params if needed (ticket, user, repo mismatch)
250
+ const retryResult = await checkSubmitErrors(result);
251
+ if (retryResult) {
252
+ if (retryResult.skipRecord) {
253
+ log.warn(' Skipping record');
254
+ return null;
255
+ }
256
+ if (retryResult.skipAll) {
257
+ throw new Error('SKIP_ALL');
258
+ }
259
+ if (retryResult.ticketExpressions?.length > 0) {
260
+ dataExprs.push(...retryResult.ticketExpressions);
261
+ }
262
+ const params = retryResult.retryParams || retryResult;
263
+ Object.assign(extraParams, params);
252
264
  body = await buildInputBody(dataExprs, extraParams);
253
265
  result = await client.postUrlEncoded('/api/input/submit', body);
254
266
  }
@@ -302,6 +314,19 @@ async function addDirectory(dirPath, client, options) {
302
314
  if (!proceed) return;
303
315
  }
304
316
 
317
+ // Pre-flight ticket validation
318
+ if (!options.ticket) {
319
+ const ticketCheck = await checkStoredTicket(options);
320
+ if (ticketCheck.cancel) {
321
+ log.info('Submission cancelled');
322
+ return;
323
+ }
324
+ if (ticketCheck.clearTicket) {
325
+ await clearGlobalTicket();
326
+ log.dim(' Cleared stored ticket');
327
+ }
328
+ }
329
+
305
330
  let succeeded = 0;
306
331
  let failed = 0;
307
332
  let batchDefaults = null;
@@ -314,6 +339,10 @@ async function addDirectory(dirPath, client, options) {
314
339
  succeeded++;
315
340
  }
316
341
  } catch (err) {
342
+ if (err.message === 'SKIP_ALL') {
343
+ log.info('Skipping remaining records');
344
+ break;
345
+ }
317
346
  log.error(`Failed: ${relative(process.cwd(), filePath)} — ${err.message}`);
318
347
  failed++;
319
348
  }