@dboio/cli 0.9.8 → 0.11.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.
- package/README.md +172 -70
- package/bin/dbo.js +2 -0
- package/bin/postinstall.js +9 -1
- package/package.json +3 -3
- package/plugins/claude/dbo/commands/dbo.md +3 -3
- package/plugins/claude/dbo/skills/cli/SKILL.md +3 -3
- package/src/commands/add.js +50 -0
- package/src/commands/clone.js +720 -552
- package/src/commands/content.js +7 -3
- package/src/commands/deploy.js +22 -7
- package/src/commands/diff.js +41 -3
- package/src/commands/init.js +42 -79
- package/src/commands/input.js +5 -0
- package/src/commands/login.js +2 -2
- package/src/commands/mv.js +3 -0
- package/src/commands/output.js +8 -10
- package/src/commands/pull.js +268 -87
- package/src/commands/push.js +814 -94
- package/src/commands/rm.js +4 -1
- package/src/commands/status.js +12 -1
- package/src/commands/sync.js +71 -0
- package/src/lib/client.js +10 -0
- package/src/lib/config.js +80 -8
- package/src/lib/delta.js +178 -25
- package/src/lib/diff.js +150 -20
- package/src/lib/folder-icon.js +120 -0
- package/src/lib/ignore.js +2 -3
- package/src/lib/input-parser.js +37 -10
- package/src/lib/metadata-templates.js +21 -4
- package/src/lib/migrations.js +75 -0
- package/src/lib/save-to-disk.js +1 -1
- package/src/lib/scaffold.js +58 -3
- package/src/lib/structure.js +158 -21
- package/src/lib/toe-stepping.js +381 -0
- package/src/migrations/001-transaction-key-preset-scope.js +35 -0
- package/src/migrations/002-move-entity-dirs-to-lib.js +190 -0
- package/src/migrations/003-move-deploy-config.js +50 -0
- package/src/migrations/004-rename-output-files.js +101 -0
package/src/commands/content.js
CHANGED
|
@@ -9,6 +9,7 @@ import { checkModifyKey, isModifyKeyError, handleModifyKeyError } from '../lib/m
|
|
|
9
9
|
import { checkStoredTicket, applyStoredTicketToSubmission, clearGlobalTicket, getGlobalTicket, getRecordTicket } from '../lib/ticketing.js';
|
|
10
10
|
import { resolveTransactionKey } from '../lib/transaction-key.js';
|
|
11
11
|
import { log } from '../lib/logger.js';
|
|
12
|
+
import { runPendingMigrations } from '../lib/migrations.js';
|
|
12
13
|
|
|
13
14
|
function collect(value, previous) {
|
|
14
15
|
return previous.concat([value]);
|
|
@@ -18,7 +19,7 @@ export const contentCommand = new Command('content')
|
|
|
18
19
|
.description('Get, deploy, or pull content from DBO.io')
|
|
19
20
|
.argument('[uid]', 'Content UID')
|
|
20
21
|
.option('-o, --output <path>', 'Save content to local file')
|
|
21
|
-
.option('--
|
|
22
|
+
.option('--template <value>', 'Output template (e.g., json_raw, html, txt, or a content UID)')
|
|
22
23
|
.option('--no-minify', 'Disable minification')
|
|
23
24
|
.option('--json', 'Output raw JSON')
|
|
24
25
|
.option('-v, --verbose', 'Show HTTP request details')
|
|
@@ -37,8 +38,10 @@ const deployCmd = new Command('deploy')
|
|
|
37
38
|
.option('--json', 'Output raw JSON')
|
|
38
39
|
.option('-v, --verbose', 'Show HTTP request details')
|
|
39
40
|
.option('--domain <host>', 'Override domain')
|
|
41
|
+
.option('--no-migrate', 'Skip pending migrations for this invocation')
|
|
40
42
|
.action(async (uid, filepath, options) => {
|
|
41
43
|
try {
|
|
44
|
+
await runPendingMigrations(options);
|
|
42
45
|
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
43
46
|
|
|
44
47
|
// ModifyKey guard
|
|
@@ -130,6 +133,7 @@ const deployCmd = new Command('deploy')
|
|
|
130
133
|
result = await submit();
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
if (result.successful) await client.voidCache();
|
|
133
137
|
formatResponse(result, { json: options.json });
|
|
134
138
|
if (!result.successful) process.exit(1);
|
|
135
139
|
} catch (err) {
|
|
@@ -150,7 +154,7 @@ const pullCmd = new Command('pull')
|
|
|
150
154
|
.action(async (uid, options) => {
|
|
151
155
|
try {
|
|
152
156
|
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
153
|
-
const params = { '
|
|
157
|
+
const params = { '_template': 'json_raw' };
|
|
154
158
|
if (options.maxrows) params['_maxrows'] = options.maxrows;
|
|
155
159
|
|
|
156
160
|
for (const f of options.filter) {
|
|
@@ -216,7 +220,7 @@ contentCommand.action(async (uid, options) => {
|
|
|
216
220
|
}
|
|
217
221
|
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
218
222
|
const params = {};
|
|
219
|
-
if (options.
|
|
223
|
+
if (options.template) params['_template'] = options.template;
|
|
220
224
|
if (options.noMinify) params['_no_minify'] = 'true';
|
|
221
225
|
|
|
222
226
|
const result = await client.get(`/api/content/${uid}`, params);
|
package/src/commands/deploy.js
CHANGED
|
@@ -7,12 +7,14 @@ import { checkModifyKey, isModifyKeyError, handleModifyKeyError } from '../lib/m
|
|
|
7
7
|
import { checkStoredTicket, applyStoredTicketToSubmission, clearGlobalTicket, getGlobalTicket, getRecordTicket } from '../lib/ticketing.js';
|
|
8
8
|
import { resolveTransactionKey } from '../lib/transaction-key.js';
|
|
9
9
|
import { log } from '../lib/logger.js';
|
|
10
|
+
import { runPendingMigrations } from '../lib/migrations.js';
|
|
10
11
|
|
|
11
|
-
const MANIFEST_FILE = 'dbo.
|
|
12
|
+
const MANIFEST_FILE = '.dbo/deploy_config.json';
|
|
13
|
+
const LEGACY_MANIFEST_FILE = 'dbo.deploy.json';
|
|
12
14
|
|
|
13
15
|
export const deployCommand = new Command('deploy')
|
|
14
16
|
.description('Deploy files to DBO.io using a manifest or direct arguments')
|
|
15
|
-
.argument('[name]', 'Deployment name from dbo.
|
|
17
|
+
.argument('[name]', 'Deployment name from .dbo/deploy_config.json (e.g., css:colors)')
|
|
16
18
|
.option('--all', 'Deploy all entries in the manifest')
|
|
17
19
|
.option('-C, --confirm <value>', 'Commit: true (default) or false', 'true')
|
|
18
20
|
.option('--ticket <id>', 'Override ticket ID')
|
|
@@ -21,20 +23,32 @@ export const deployCommand = new Command('deploy')
|
|
|
21
23
|
.option('--json', 'Output raw JSON')
|
|
22
24
|
.option('-v, --verbose', 'Show HTTP request details')
|
|
23
25
|
.option('--domain <host>', 'Override domain')
|
|
26
|
+
.option('--no-migrate', 'Skip pending migrations for this invocation')
|
|
24
27
|
.action(async (name, options) => {
|
|
25
28
|
try {
|
|
29
|
+
await runPendingMigrations(options);
|
|
26
30
|
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
27
31
|
|
|
28
|
-
// Load manifest
|
|
32
|
+
// Load manifest — try .dbo/deploy_config.json first, fall back to legacy dbo.deploy.json
|
|
29
33
|
let manifest;
|
|
34
|
+
let manifestSource;
|
|
30
35
|
try {
|
|
31
36
|
const raw = await readFile(MANIFEST_FILE, 'utf8');
|
|
32
37
|
manifest = JSON.parse(raw);
|
|
38
|
+
manifestSource = MANIFEST_FILE;
|
|
33
39
|
} catch {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
// Fall back to legacy location
|
|
41
|
+
try {
|
|
42
|
+
const raw = await readFile(LEGACY_MANIFEST_FILE, 'utf8');
|
|
43
|
+
manifest = JSON.parse(raw);
|
|
44
|
+
manifestSource = LEGACY_MANIFEST_FILE;
|
|
45
|
+
log.dim(` Using legacy ${LEGACY_MANIFEST_FILE} — run \`dbo\` to migrate it to ${MANIFEST_FILE}`);
|
|
46
|
+
} catch {
|
|
47
|
+
if (!name) {
|
|
48
|
+
log.error(`No ${MANIFEST_FILE} found and no deployment name specified.`);
|
|
49
|
+
log.dim(` Create ${MANIFEST_FILE} or use: dbo content deploy <uid> <filepath>`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
38
52
|
}
|
|
39
53
|
}
|
|
40
54
|
|
|
@@ -175,6 +189,7 @@ export const deployCommand = new Command('deploy')
|
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
if (result.successful) {
|
|
192
|
+
await client.voidCache();
|
|
178
193
|
log.success(`${entryName} deployed`);
|
|
179
194
|
} else {
|
|
180
195
|
log.error(`${entryName} failed`);
|
package/src/commands/diff.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { stat } from 'fs/promises';
|
|
2
|
+
import { readFile, stat } from 'fs/promises';
|
|
3
3
|
import { join, dirname, basename, extname } from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { DboClient } from '../lib/client.js';
|
|
6
|
-
import { loadConfig } from '../lib/config.js';
|
|
6
|
+
import { loadConfig, loadAppConfig, loadAppJsonBaseline } from '../lib/config.js';
|
|
7
7
|
import { formatError } from '../lib/formatter.js';
|
|
8
8
|
import { log } from '../lib/logger.js';
|
|
9
9
|
import {
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
formatDiff,
|
|
13
13
|
applyServerChanges,
|
|
14
14
|
} from '../lib/diff.js';
|
|
15
|
+
import { fetchServerRecordsBatch } from '../lib/toe-stepping.js';
|
|
16
|
+
import { findBaselineEntry } from '../lib/delta.js';
|
|
17
|
+
import { runPendingMigrations } from '../lib/migrations.js';
|
|
15
18
|
|
|
16
19
|
export const diffCommand = new Command('diff')
|
|
17
20
|
.description('Compare local files with server versions and selectively merge changes')
|
|
@@ -20,8 +23,10 @@ export const diffCommand = new Command('diff')
|
|
|
20
23
|
.option('-y, --yes', 'Accept all server changes without prompting')
|
|
21
24
|
.option('-v, --verbose', 'Show HTTP request details')
|
|
22
25
|
.option('--domain <host>', 'Override domain')
|
|
26
|
+
.option('--no-migrate', 'Skip pending migrations for this invocation')
|
|
23
27
|
.action(async (targetPath, options) => {
|
|
24
28
|
try {
|
|
29
|
+
await runPendingMigrations(options);
|
|
25
30
|
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
26
31
|
const config = await loadConfig();
|
|
27
32
|
|
|
@@ -36,6 +41,39 @@ export const diffCommand = new Command('diff')
|
|
|
36
41
|
|
|
37
42
|
log.info(`Comparing ${metaFiles.length} record(s) against server...`);
|
|
38
43
|
|
|
44
|
+
// Batch-fetch server records via /api/app/object/ with UpdatedAfter
|
|
45
|
+
const ora = (await import('ora')).default;
|
|
46
|
+
let serverRecordsMap = new Map();
|
|
47
|
+
const appConfig = await loadAppConfig();
|
|
48
|
+
if (appConfig?.AppShortName) {
|
|
49
|
+
// Find oldest baseline _LastUpdated across all files to diff
|
|
50
|
+
const baseline = await loadAppJsonBaseline();
|
|
51
|
+
let oldestDate = null;
|
|
52
|
+
for (const metaPath of metaFiles) {
|
|
53
|
+
try {
|
|
54
|
+
const meta = JSON.parse(await readFile(metaPath, 'utf8'));
|
|
55
|
+
const uid = meta.UID || meta._id;
|
|
56
|
+
const entity = meta._entity;
|
|
57
|
+
if (!uid || !entity || !baseline) continue;
|
|
58
|
+
const entry = findBaselineEntry(baseline, entity, uid);
|
|
59
|
+
if (!entry?._LastUpdated) continue;
|
|
60
|
+
const d = new Date(entry._LastUpdated);
|
|
61
|
+
if (!isNaN(d) && (!oldestDate || d < oldestDate)) oldestDate = d;
|
|
62
|
+
} catch { /* skip unreadable */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (oldestDate) {
|
|
66
|
+
const updatedAfter = oldestDate.toISOString();
|
|
67
|
+
const spinner = ora('Fetching server state for comparison...').start();
|
|
68
|
+
serverRecordsMap = await fetchServerRecordsBatch(client, appConfig.AppShortName, updatedAfter);
|
|
69
|
+
if (serverRecordsMap.size > 0) {
|
|
70
|
+
spinner.succeed(`Fetched ${serverRecordsMap.size} record(s) from server`);
|
|
71
|
+
} else {
|
|
72
|
+
spinner.warn('No server records returned');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
let updated = 0;
|
|
40
78
|
let skipped = 0;
|
|
41
79
|
let unchanged = 0;
|
|
@@ -45,7 +83,7 @@ export const diffCommand = new Command('diff')
|
|
|
45
83
|
for (const metaPath of metaFiles) {
|
|
46
84
|
const metaBase = basename(metaPath, '.metadata.json');
|
|
47
85
|
|
|
48
|
-
const result = await compareRecord(metaPath,
|
|
86
|
+
const result = await compareRecord(metaPath, config, serverRecordsMap);
|
|
49
87
|
|
|
50
88
|
if (result.error) {
|
|
51
89
|
log.warn(`${metaBase}: ${result.error}`);
|
package/src/commands/init.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
2
|
+
import { mkdir } from 'fs/promises';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { isInitialized, hasLegacyConfig, readLegacyConfig, initConfig, saveCredentials, ensureGitignore, saveTransactionKeyPreset, saveTicketSuggestionOutput } from '../lib/config.js';
|
|
5
5
|
import { installOrUpdateClaudeCommands } from './install.js';
|
|
6
6
|
import { scaffoldProjectDirs, logScaffoldResult } from '../lib/scaffold.js';
|
|
7
|
-
import { createDboignore
|
|
7
|
+
import { createDboignore } from '../lib/ignore.js';
|
|
8
8
|
import { log } from '../lib/logger.js';
|
|
9
9
|
import { checkDomainChange, writeAppJsonDomain } from '../lib/domain-guard.js';
|
|
10
10
|
import { performLogin } from './login.js';
|
|
11
|
+
import { runPendingMigrations } from '../lib/migrations.js';
|
|
11
12
|
|
|
12
13
|
export const initCommand = new Command('init')
|
|
13
14
|
.description('Initialize DBO CLI configuration for the current directory')
|
|
@@ -23,10 +24,12 @@ export const initCommand = new Command('init')
|
|
|
23
24
|
.option('--scaffold', 'Create standard project directories (app_version, automation, bins, …)')
|
|
24
25
|
.option('--dboignore', 'Create or reset .dboignore to defaults (use with --force to overwrite)')
|
|
25
26
|
.option('--media-placement <placement>', 'Set media placement when cloning: fullpath or binpath (default: bin)')
|
|
27
|
+
.option('--no-migrate', 'Skip pending migrations for this invocation')
|
|
26
28
|
.action(async (options) => {
|
|
27
29
|
// Merge --yes into nonInteractive
|
|
28
30
|
if (options.yes) options.nonInteractive = true;
|
|
29
31
|
try {
|
|
32
|
+
await runPendingMigrations(options);
|
|
30
33
|
// --dboignore: standalone operation, works regardless of init state
|
|
31
34
|
if (options.dboignore) {
|
|
32
35
|
const created = await createDboignore(process.cwd(), { force: options.force });
|
|
@@ -99,67 +102,46 @@ export const initCommand = new Command('init')
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// Ensure sensitive files are gitignored
|
|
102
|
-
await ensureGitignore(['.dbo/credentials.json', '.dbo/cookies.txt', '.dbo/config.local.json', '.dbo/ticketing.local.json', 'trash/']);
|
|
105
|
+
await ensureGitignore(['.dbo/credentials.json', '.dbo/cookies.txt', '.dbo/config.local.json', '.dbo/ticketing.local.json', '.dbo/errors.log', 'trash/', 'Icon\\r']);
|
|
103
106
|
|
|
104
107
|
const createdIgnore = await createDboignore();
|
|
105
108
|
if (createdIgnore) log.dim(' Created .dboignore');
|
|
106
109
|
|
|
110
|
+
// Ensure .claude/ directory structure exists
|
|
111
|
+
const claudeSubDirs = ['1_prompts', '2_specs', '3_plans', 'commands'];
|
|
112
|
+
for (const sub of claudeSubDirs) {
|
|
113
|
+
await mkdir(join(process.cwd(), '.claude', sub), { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
log.dim(' Created .claude/ directory structure');
|
|
116
|
+
|
|
107
117
|
log.success(`Initialized .dbo/ for ${domain}`);
|
|
108
|
-
log.dim(' Run "dbo login" to authenticate.');
|
|
109
118
|
|
|
110
|
-
//
|
|
111
|
-
if (!options.nonInteractive) {
|
|
112
|
-
|
|
113
|
-
const { preset } = await inquirer.prompt([{
|
|
114
|
-
type: 'list',
|
|
115
|
-
name: 'preset',
|
|
116
|
-
message: 'Which row key should the CLI use when building input expressions?',
|
|
117
|
-
choices: [
|
|
118
|
-
{ name: 'RowUID (recommended — stable across domains)', value: 'RowUID' },
|
|
119
|
-
{ name: 'RowID (numeric IDs)', value: 'RowID' },
|
|
120
|
-
],
|
|
121
|
-
}]);
|
|
122
|
-
await saveTransactionKeyPreset(preset);
|
|
123
|
-
log.dim(` TransactionKeyPreset: ${preset}`);
|
|
124
|
-
} else {
|
|
125
|
-
await saveTransactionKeyPreset('RowUID');
|
|
119
|
+
// Authenticate early so the session is ready for subsequent operations
|
|
120
|
+
if (!options.nonInteractive && username) {
|
|
121
|
+
await performLogin(domain, username);
|
|
126
122
|
}
|
|
127
123
|
|
|
128
|
-
//
|
|
124
|
+
// TransactionKeyPreset — always RowUID (stable across domains)
|
|
125
|
+
await saveTransactionKeyPreset('RowUID');
|
|
126
|
+
log.dim(' TransactionKeyPreset: RowUID');
|
|
127
|
+
|
|
128
|
+
// TicketSuggestionOutput — auto-set default
|
|
129
129
|
const DEFAULT_TICKET_SUGGESTION_OUTPUT = 'ojaie9t3o0kfvliahnuuda';
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const { ticketSuggestionOutput } = await inquirer.prompt([{
|
|
133
|
-
type: 'input',
|
|
134
|
-
name: 'ticketSuggestionOutput',
|
|
135
|
-
message: 'Output UID for ticket suggestions (leave blank to skip):',
|
|
136
|
-
default: DEFAULT_TICKET_SUGGESTION_OUTPUT,
|
|
137
|
-
}]);
|
|
138
|
-
const tsVal = ticketSuggestionOutput.trim();
|
|
139
|
-
if (tsVal) {
|
|
140
|
-
await saveTicketSuggestionOutput(tsVal);
|
|
141
|
-
log.dim(` TicketSuggestionOutput: ${tsVal}`);
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
await saveTicketSuggestionOutput(DEFAULT_TICKET_SUGGESTION_OUTPUT);
|
|
145
|
-
}
|
|
130
|
+
await saveTicketSuggestionOutput(DEFAULT_TICKET_SUGGESTION_OUTPUT);
|
|
131
|
+
log.dim(` TicketSuggestionOutput: ${DEFAULT_TICKET_SUGGESTION_OUTPUT}`);
|
|
146
132
|
|
|
147
133
|
// ─── Scaffold ─────────────────────────────────────────────────────────────
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
message: 'Would you like to scaffold the standard project directory structure?',
|
|
160
|
-
default: isEmpty,
|
|
161
|
-
}]);
|
|
162
|
-
shouldScaffold = doScaffold;
|
|
134
|
+
// Auto-scaffold if standard directories don't exist yet
|
|
135
|
+
let shouldScaffold = true;
|
|
136
|
+
|
|
137
|
+
if (!options.scaffold && !options.nonInteractive) {
|
|
138
|
+
// Check if already scaffolded (bins/ exists = already done)
|
|
139
|
+
try {
|
|
140
|
+
const s = await (await import('fs/promises')).stat(join(process.cwd(), 'lib/bins'));
|
|
141
|
+
if (s.isDirectory()) shouldScaffold = false; // already scaffolded
|
|
142
|
+
} catch {
|
|
143
|
+
// bins/ doesn't exist — scaffold needed
|
|
144
|
+
}
|
|
163
145
|
}
|
|
164
146
|
|
|
165
147
|
if (shouldScaffold) {
|
|
@@ -167,7 +149,7 @@ export const initCommand = new Command('init')
|
|
|
167
149
|
logScaffoldResult(result);
|
|
168
150
|
}
|
|
169
151
|
|
|
170
|
-
// Clone if requested
|
|
152
|
+
// Clone if requested
|
|
171
153
|
if (options.clone || options.app) {
|
|
172
154
|
let appShortName = options.app;
|
|
173
155
|
if (!appShortName) {
|
|
@@ -180,38 +162,19 @@ export const initCommand = new Command('init')
|
|
|
180
162
|
appShortName = appName;
|
|
181
163
|
}
|
|
182
164
|
|
|
183
|
-
// Authenticate before fetching app data from the server
|
|
184
|
-
if (!options.nonInteractive) {
|
|
185
|
-
log.info('Login required to fetch app data from the server.');
|
|
186
|
-
await performLogin(domain, username);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
165
|
const { performClone } = await import('./clone.js');
|
|
190
166
|
await performClone(null, { app: appShortName, domain, mediaPlacement: options.mediaPlacement });
|
|
191
167
|
}
|
|
192
168
|
|
|
193
|
-
// Offer Claude Code
|
|
169
|
+
// Offer Claude Code plugin installation (skip in non-interactive mode)
|
|
194
170
|
if (!options.nonInteractive) {
|
|
195
|
-
const claudeDir = join(process.cwd(), '.claude');
|
|
196
171
|
const inquirer = (await import('inquirer')).default;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
message: 'Install DBO commands for Claude Code? (adds /dbo slash command)',
|
|
204
|
-
default: true,
|
|
205
|
-
}]);
|
|
206
|
-
if (installCmds) await installOrUpdateClaudeCommands(options);
|
|
207
|
-
} else {
|
|
208
|
-
const { setupClaude } = await inquirer.prompt([{
|
|
209
|
-
type: 'confirm', name: 'setupClaude',
|
|
210
|
-
message: 'Set up Claude Code integration? (creates .claude/commands/)',
|
|
211
|
-
default: false,
|
|
212
|
-
}]);
|
|
213
|
-
if (setupClaude) await installOrUpdateClaudeCommands(options);
|
|
214
|
-
}
|
|
172
|
+
const { installCmds } = await inquirer.prompt([{
|
|
173
|
+
type: 'confirm', name: 'installCmds',
|
|
174
|
+
message: 'Install DBO commands for Claude Code? (adds /dbo slash command)',
|
|
175
|
+
default: true,
|
|
176
|
+
}]);
|
|
177
|
+
if (installCmds) await installOrUpdateClaudeCommands(options);
|
|
215
178
|
}
|
|
216
179
|
} catch (err) {
|
|
217
180
|
log.error(err.message);
|
package/src/commands/input.js
CHANGED
|
@@ -6,6 +6,7 @@ import { loadAppConfig } from '../lib/config.js';
|
|
|
6
6
|
import { checkStoredTicket, clearGlobalTicket } from '../lib/ticketing.js';
|
|
7
7
|
import { checkModifyKey, isModifyKeyError, handleModifyKeyError } from '../lib/modify-key.js';
|
|
8
8
|
import { log } from '../lib/logger.js';
|
|
9
|
+
import { runPendingMigrations } from '../lib/migrations.js';
|
|
9
10
|
|
|
10
11
|
function collect(value, previous) {
|
|
11
12
|
return previous.concat([value]);
|
|
@@ -25,8 +26,10 @@ export const inputCommand = new Command('input')
|
|
|
25
26
|
.option('--jq <expr>', 'Filter JSON response with jq-like expression (implies --json)')
|
|
26
27
|
.option('-v, --verbose', 'Show HTTP request details')
|
|
27
28
|
.option('--domain <host>', 'Override domain')
|
|
29
|
+
.option('--no-migrate', 'Skip pending migrations for this invocation')
|
|
28
30
|
.action(async (options) => {
|
|
29
31
|
try {
|
|
32
|
+
await runPendingMigrations(options);
|
|
30
33
|
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
31
34
|
|
|
32
35
|
const extraParams = {};
|
|
@@ -124,6 +127,7 @@ export const inputCommand = new Command('input')
|
|
|
124
127
|
result = await client.postMultipart('/api/input/submit', fields, files);
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
if (result.successful) await client.voidCache();
|
|
127
131
|
formatResponse(result, { json: options.json, jq: options.jq, verbose: options.verbose });
|
|
128
132
|
if (!result.successful) process.exit(1);
|
|
129
133
|
} else {
|
|
@@ -153,6 +157,7 @@ export const inputCommand = new Command('input')
|
|
|
153
157
|
result = await client.postUrlEncoded('/api/input/submit', body);
|
|
154
158
|
}
|
|
155
159
|
|
|
160
|
+
if (result.successful) await client.voidCache();
|
|
156
161
|
formatResponse(result, { json: options.json, jq: options.jq, verbose: options.verbose });
|
|
157
162
|
if (!result.successful) process.exit(1);
|
|
158
163
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function performLogin(domain, knownUsername) {
|
|
|
42
42
|
|
|
43
43
|
// Fetch and store user info (non-critical)
|
|
44
44
|
try {
|
|
45
|
-
const userResult = await client.get('/api/output/31799e38f0854956a47f10', { '
|
|
45
|
+
const userResult = await client.get('/api/output/31799e38f0854956a47f10', { '_template': 'json_raw' });
|
|
46
46
|
const userData = userResult.payload || userResult.data;
|
|
47
47
|
const rows = Array.isArray(userData) ? userData : (userData?.Rows || userData?.rows || []);
|
|
48
48
|
if (rows.length > 0) {
|
|
@@ -121,7 +121,7 @@ export const loginCommand = new Command('login')
|
|
|
121
121
|
|
|
122
122
|
// Fetch current user info to store ID for future submissions
|
|
123
123
|
try {
|
|
124
|
-
const userResult = await client.get('/api/output/31799e38f0854956a47f10', { '
|
|
124
|
+
const userResult = await client.get('/api/output/31799e38f0854956a47f10', { '_template': 'json_raw' });
|
|
125
125
|
const userData = userResult.payload || userResult.data;
|
|
126
126
|
const rows = Array.isArray(userData) ? userData : (userData?.Rows || userData?.rows || []);
|
|
127
127
|
if (rows.length > 0) {
|
package/src/commands/mv.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
BINS_DIR
|
|
14
14
|
} from '../lib/structure.js';
|
|
15
15
|
import { findMetadataFiles } from '../lib/diff.js';
|
|
16
|
+
import { runPendingMigrations } from '../lib/migrations.js';
|
|
16
17
|
|
|
17
18
|
export const mvCommand = new Command('mv')
|
|
18
19
|
.description('Move files or bins to a new location and update metadata')
|
|
@@ -22,8 +23,10 @@ export const mvCommand = new Command('mv')
|
|
|
22
23
|
.option('-v, --verbose', 'Show detailed operations')
|
|
23
24
|
.option('--dry-run', 'Preview changes without executing')
|
|
24
25
|
.option('-C, --confirm <value>', 'Commit changes (true|false)', 'true')
|
|
26
|
+
.option('--no-migrate', 'Skip pending migrations for this invocation')
|
|
25
27
|
.action(async (source, destination, options) => {
|
|
26
28
|
try {
|
|
29
|
+
await runPendingMigrations(options);
|
|
27
30
|
// Only items inside Bins/ are moveable
|
|
28
31
|
const relSource = isAbsolute(source) ? relative(process.cwd(), source) : source;
|
|
29
32
|
const normalized = relSource.replace(/\/+$/, '');
|
package/src/commands/output.js
CHANGED
|
@@ -15,14 +15,13 @@ export const outputCommand = new Command('output')
|
|
|
15
15
|
.option('-e, --entity <uid>', 'Query by entity UID')
|
|
16
16
|
.option('--row <id>', 'Specific row ID')
|
|
17
17
|
.option('--filter <expr>', 'Filter expression (repeatable)', collect, [])
|
|
18
|
-
.option('--
|
|
18
|
+
.option('--template <value>', 'Output template: json_raw (default), json_indented, json, html, csv, xml, txt, pdf, or a content UID')
|
|
19
19
|
.option('--sort <expr>', 'Sort expression (repeatable)', collect, [])
|
|
20
20
|
.option('--search <expr>', 'Full-text search')
|
|
21
21
|
.option('--page <n>', 'Page number')
|
|
22
22
|
.option('--rows-per-page <n>', 'Rows per page')
|
|
23
23
|
.option('--maxrows <n>', 'Maximum rows')
|
|
24
24
|
.option('--rows <range>', 'Row range (e.g., 1-10)')
|
|
25
|
-
.option('--template <value>', 'Custom template')
|
|
26
25
|
.option('--debug', 'Include debug info')
|
|
27
26
|
.option('--debug-sql', 'Include SQL debug info')
|
|
28
27
|
.option('--debug-verbose', 'Verbose debug output')
|
|
@@ -30,7 +29,7 @@ export const outputCommand = new Command('output')
|
|
|
30
29
|
.option('--limit <n>', 'Maximum rows to return (_limit)')
|
|
31
30
|
.option('--rowcount <bool>', 'Include row count: true (default) or false for performance')
|
|
32
31
|
.option('--display <expr>', 'Show/hide template tags (repeatable), e.g., "sidebar=hide"', collect, [])
|
|
33
|
-
.option('--format-values', 'Enable value formatting
|
|
32
|
+
.option('--format-values', 'Enable value formatting with json_raw template')
|
|
34
33
|
.option('--empty-response-code <code>', 'HTTP status code when output returns no results')
|
|
35
34
|
.option('--fallback-content <expr>', 'Fallback content UID for error codes, e.g., "404=contentUID"')
|
|
36
35
|
.option('--escape-html <bool>', 'Control HTML escaping: true or false')
|
|
@@ -75,13 +74,12 @@ export const outputCommand = new Command('output')
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
// Build query params — default to json_raw for normalized data
|
|
78
|
-
const params = { '
|
|
77
|
+
const params = { '_template': options.template || 'json_raw' };
|
|
79
78
|
if (options.search) params['_search'] = options.search;
|
|
80
79
|
if (options.page) params['_page'] = options.page;
|
|
81
80
|
if (options.rowsPerPage) params['_RowsPerPage'] = options.rowsPerPage;
|
|
82
81
|
if (options.maxrows) params['_maxrows'] = options.maxrows;
|
|
83
82
|
if (options.rows) params['_rows'] = options.rows;
|
|
84
|
-
if (options.template) params['_template'] = options.template;
|
|
85
83
|
if (options.limit) params['_limit'] = options.limit;
|
|
86
84
|
if (options.rowcount !== undefined) params['_rowcount'] = options.rowcount;
|
|
87
85
|
if (options.debug) params['_debug'] = 'true';
|
|
@@ -142,9 +140,9 @@ export const outputCommand = new Command('output')
|
|
|
142
140
|
params['_sort'] = options.sort;
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
// For save mode or jq mode, force json_raw if user didn't set a custom
|
|
146
|
-
if ((options.save || options.saveFilename || options.jq) && !options.
|
|
147
|
-
params['
|
|
143
|
+
// For save mode or jq mode, force json_raw if user didn't set a custom template
|
|
144
|
+
if ((options.save || options.saveFilename || options.jq) && !options.template) {
|
|
145
|
+
params['_template'] = 'json_raw';
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
const result = await client.get(path, params);
|
|
@@ -171,8 +169,8 @@ export const outputCommand = new Command('output')
|
|
|
171
169
|
return;
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
// Default to colorized JSON when using json_raw (no explicit --
|
|
175
|
-
const jq = options.jq || (!options.
|
|
172
|
+
// Default to colorized JSON when using json_raw (no explicit --template, --json, or --jq)
|
|
173
|
+
const jq = options.jq || (!options.template && !options.json ? '.' : undefined);
|
|
176
174
|
formatResponse(result, { json: options.json, jq, columns: options.columns });
|
|
177
175
|
if (!result.successful && !result.ok) process.exit(1);
|
|
178
176
|
} catch (err) {
|