@dboio/cli 0.6.5 → 0.6.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dboio/cli",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
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.5",
3
+ "version": "0.6.6",
4
4
  "description": "DBO.io CLI integration for Claude Code",
5
5
  "author": {
6
6
  "name": "DBO.io"
@@ -0,0 +1,274 @@
1
+ # DBO CLI Command
2
+
3
+ The dbo CLI interacts with DBO.io — a database-driven application framework.
4
+
5
+ **STEP 1: Check if `$ARGUMENTS` is empty or blank.**
6
+
7
+ If `$ARGUMENTS` is empty — meaning the user typed just `/dbo` with nothing after it — then you MUST NOT run any bash command. Do NOT run `dbo`, `dbo --help`, or anything else. Instead, respond ONLY with this text message:
8
+
9
+ ---
10
+
11
+ Hi there! I can help you with running dbo commands. What would you like to do?
12
+
13
+ Here are the available commands:
14
+
15
+ | Command | Description |
16
+ |-----------|--------------------------------------------------|
17
+ | init | Initialize .dbo/ configuration |
18
+ | login | Authenticate with a DBO.io instance |
19
+ | logout | Clear session |
20
+ | status | Show config, domain, and session info |
21
+ | input | Submit CRUD operations (add/edit/delete records) |
22
+ | output | Query data from outputs or entities |
23
+ | content | Get or deploy content |
24
+ | media | Get media files |
25
+ | upload | Upload a file |
26
+ | message | Send messages (email, SMS, chatbot) |
27
+ | pull | Pull records to local files |
28
+ | push | Push local files back to DBO.io |
29
+ | add | Add a new file to DBO.io |
30
+ | clone | Clone an app to local project structure |
31
+ | diff | Compare local files with server versions |
32
+ | rm | Remove a file and stage server deletion |
33
+ | deploy | Deploy via manifest |
34
+ | cache | Manage cache |
35
+ | install | Install or upgrade CLI, plugins, Claude commands (shorthand: `i`) |
36
+
37
+ Just tell me what you'd like to do and I'll help you build the right command!
38
+
39
+ ---
40
+
41
+ Then STOP. Wait for the user to respond. Guide them step by step — asking about entities, UIDs, file paths, flags, etc. to construct the correct `dbo` command. Run `dbo status` proactively to check if the CLI is initialized and authenticated before suggesting data commands.
42
+
43
+ **STEP 2: If `$ARGUMENTS` is NOT empty, run the command:**
44
+
45
+ ```bash
46
+ dbo $ARGUMENTS
47
+ ```
48
+
49
+ ## Command Reference
50
+
51
+ Available subcommands:
52
+ - `init` — Initialize .dbo/ configuration for the current directory
53
+ - `login` — Authenticate with a DBO.io instance
54
+ - `logout` — Clear session
55
+ - `status` — Show config, domain, and session info
56
+ - `input -d '<expr>'` — CRUD operations (add/edit/delete records)
57
+ - `output -e <entity>` — Query data from entities
58
+ - `output <uid>` — Query custom outputs
59
+ - `content <uid>` — Get content, `content deploy <uid> <file>` to deploy
60
+ - `pull [uid]` — Pull records to local files (default: content entity)
61
+ - `pull -e <entity> [uid]` — Pull from any entity
62
+ - `push <path>` — Push local files back to DBO using metadata
63
+ - `add <path>` — Add a new file to DBO (creates record on server)
64
+ - `media <uid>` — Get media files
65
+ - `upload <file>` — Upload binary files
66
+ - `message <uid>` — Send messages (email, SMS, chatbot)
67
+ - `cache list|refresh` — Manage cache
68
+ - `clone [source]` — Clone an app to local project (from file or server)
69
+ - `clone --app <name>` — Clone by app short name from server
70
+ - `diff [path]` — Compare local files against server and selectively merge changes
71
+ - `diff -y` — Accept all server changes without prompting
72
+ - `diff --no-interactive` — Show diffs without prompting to accept
73
+ - `rm <file>` — Remove a file locally and stage server deletion for next push
74
+ - `rm <directory>` — Remove a directory, all files, and sub-directories recursively
75
+ - `rm -f <path>` — Remove without confirmation prompts
76
+ - `rm --keep-local <path>` — Stage server deletions without deleting local files/directories
77
+ - `deploy [name]` — Deploy via dbo.deploy.json manifest
78
+ - `install` (alias: `i`) — Install or upgrade CLI, plugins, or Claude commands
79
+ - `i dbo` or `i dbo@latest` — Install/upgrade the CLI from npm
80
+ - `i dbo@0.4.1` — Install a specific CLI version
81
+ - `install /path/to/src` — Install CLI from local source
82
+ - `install plugins` — Install/upgrade Claude command plugins
83
+ - `install plugins --global` — Install plugins to `~/.claude/commands/` (shared across projects)
84
+ - `install plugins --local` — Install plugins to `.claude/commands/` (project only)
85
+ - `install claudecommands` — Install/upgrade Claude Code commands
86
+ - `install claudecode` — Install Claude Code CLI + commands
87
+ - `install --claudecommand dbo --global` — Install a specific command globally
88
+
89
+ ## Change Detection (pull, clone, diff)
90
+
91
+ When pulling or cloning records that already exist locally, the CLI compares file modification times against the server's `_LastUpdated` timestamp. If the server has newer data, you'll be prompted with options:
92
+
93
+ 1. **Overwrite** — Replace local files with server version
94
+ 2. **Compare** — Show a line-by-line diff and selectively merge
95
+ 3. **Skip** — Keep local files unchanged
96
+ 4. **Overwrite all** — Accept all remaining server changes
97
+ 5. **Skip all** — Skip all remaining files
98
+
99
+ Use `dbo diff [path]` to compare without pulling. Use `-y` to auto-accept all changes.
100
+
101
+ ## Smart Command Building
102
+
103
+ When helping the user build a command interactively:
104
+
105
+ 1. **Check readiness first**: Run `dbo status` to see if initialized and authenticated. If not, guide them through `dbo init` and `dbo login` first.
106
+ 2. **Understand intent**: Ask what they want to do (query data, deploy a file, add a record, etc.)
107
+ 3. **Gather parameters**: Ask for the specific values needed (entity name, UID, file path, filters, etc.)
108
+ 4. **Build the command**: Construct the full `dbo` command with proper flags and syntax
109
+ 5. **Execute**: Run it and explain the results
110
+
111
+ ### Common workflows to suggest:
112
+
113
+ - **"I want to query data"** → Guide toward `dbo output -e <entity>` with filters
114
+ - **"I want to deploy/update a file"** → Check if `.metadata.json` exists → `dbo push` or `dbo content deploy`
115
+ - **"I want to add a new file"** → `dbo add <path>` (will create metadata interactively)
116
+ - **"I want to pull files from the server"** → `dbo pull` or `dbo pull -e <entity>`
117
+ - **"I want to delete/remove a file"** → `dbo rm <file>` (stages deletion for next `dbo push`)
118
+ - **"I want to delete a directory"** → `dbo rm <directory>` (removes all files + sub-dirs, stages bin deletions)
119
+ - **"I want to see what's on the server"** → `dbo output -e <entity> --format json`
120
+ - **"I need to set up this project"** → `dbo init` → `dbo login` → `dbo status`
121
+ - **"I want to clone an app"** → `dbo clone --app <name>` or `dbo clone <local.json>`
122
+ - **"I want to set up and clone"** → `dbo init --domain <host> --app <name> --clone`
123
+
124
+ ## Add Command Details
125
+
126
+ `dbo add <path>` registers a new local file with the DBO server by creating an insert record.
127
+
128
+ ```bash
129
+ # Add a single file (interactive metadata wizard if no .metadata.json exists)
130
+ dbo add assets/css/colors.css
131
+
132
+ # Add with auto-accept and ticket
133
+ dbo add assets/css/colors.css -y --ticket abc123
134
+
135
+ # Scan current directory for all un-added files
136
+ dbo add .
137
+
138
+ # Scan a specific directory
139
+ dbo add assets/
140
+ ```
141
+
142
+ Flags: `-C/--confirm <true|false>`, `--ticket <id>`, `-y/--yes`, `--json`, `--jq <expr>`, `-v/--verbose`, `--domain <host>`
143
+
144
+ ### How add works
145
+
146
+ 1. Checks for a companion `<basename>.metadata.json` next to the file
147
+ 2. If metadata exists with `_CreatedOn` → already on server, skips (use `push` instead)
148
+ 3. If metadata exists without `_CreatedOn` → uses it to insert the record
149
+ 4. If no metadata → interactive wizard prompts for: entity, content column, AppID, BinID, SiteID, Path
150
+ 5. Creates `.metadata.json`, submits insert to `/api/input/submit`
151
+ 6. Writes returned UID back to metadata file
152
+ 7. Suggests running `dbo pull -e <entity> <uid>` to populate all server columns
153
+
154
+ ### Non-interactive add (for scripting)
155
+
156
+ To add without prompts, create the `.metadata.json` first, then run `dbo add <file> -y`:
157
+
158
+ ```json
159
+ {
160
+ "Name": "colors",
161
+ "Path": "assets/css/colors.css",
162
+ "Content": "@colors.css",
163
+ "_entity": "content",
164
+ "_contentColumns": ["Content"]
165
+ }
166
+ ```
167
+
168
+ Optional fields like `AppID`, `BinID`, `SiteID` are only included if the user provides values — they have no defaults and are omitted when left blank.
169
+
170
+ The `@colors.css` value means "read content from colors.css in the same directory".
171
+
172
+ ### Directory scan (`dbo add .`)
173
+
174
+ Finds files that have no `.metadata.json` or whose metadata lacks `_CreatedOn`. Skips `.dbo/`, `.git/`, `node_modules/`, dotfiles, and `.metadata.json` files. When adding multiple files, defaults from the first file are reused.
175
+
176
+ ## Push Command Details
177
+
178
+ `dbo push <path>` pushes local file changes back to existing DBO records using their `.metadata.json`.
179
+
180
+ ```bash
181
+ dbo push assets/css/colors.css # single file
182
+ dbo push assets/ # all records in directory
183
+ dbo push assets/ --content-only # only file content, skip metadata
184
+ dbo push assets/ --meta-only # only metadata columns, skip files
185
+ dbo push assets/ -y --ticket abc123 # auto-accept + ticket
186
+ ```
187
+
188
+ Flags: `-C/--confirm`, `--ticket <id>`, `--meta-only`, `--content-only`, `-y/--yes`, `--json`, `--jq`, `-v/--verbose`, `--domain`
189
+
190
+ ## Column Filtering (add & push)
191
+
192
+ These columns are **never submitted** in add or push payloads:
193
+ - `_CreatedOn`, `_LastUpdated` — server-managed timestamps
194
+ - `_LastUpdatedUserID`, `_LastUpdatedTicketID` — session-provided values
195
+ - `UID` — server-assigned on insert; used as identifier on push (not as column value)
196
+ - `_id`, `_entity`, `_contentColumns`, `_mediaFile` — internal/metadata fields
197
+
198
+ ## Pull → Edit → Push/Add Workflow
199
+
200
+ ```bash
201
+ # Pull existing records
202
+ dbo pull -e content --filter 'AppID=10100'
203
+
204
+ # Edit files locally, then push changes back
205
+ dbo push assets/css/colors.css
206
+
207
+ # Or add a brand new file
208
+ dbo add assets/css/newstyle.css
209
+ ```
210
+
211
+ ## Clone Command Details
212
+
213
+ `dbo clone` scaffolds a local project from a DBO.io app export JSON, creating directories, files, metadata, and config.
214
+
215
+ ```bash
216
+ # Clone from a local JSON export file
217
+ dbo clone /path/to/app_export.json
218
+
219
+ # Clone from server by app short name (requires login)
220
+ dbo clone --app myapp
221
+
222
+ # Clone using AppShortName already in config
223
+ dbo clone
224
+
225
+ # Init + clone in one step
226
+ dbo init --domain my-domain.com --app myapp --clone
227
+ ```
228
+
229
+ Flags: `--app <name>`, `--domain <host>`, `-y/--yes`, `-v/--verbose`
230
+
231
+ ### What clone does
232
+
233
+ 1. Loads app JSON (local file, server API, or prompt)
234
+ 2. Updates `.dbo/config.json` with `AppID`, `AppUID`, `AppName`, `AppShortName`
235
+ 3. Updates `package.json` with `name`, `productName`, `description`, `homepage`, and `deploy` script
236
+ 4. Creates directory structure from `children.bin` hierarchy → saves `.dbo/structure.json`
237
+ 5. Writes content files (decodes base64) with `*.metadata.json` into bin directories
238
+ 6. Downloads media files from server via `/api/media/{uid}` with `*.metadata.json`
239
+ 7. Processes entity-dir records (`extension`, `app_version`, `data_source`, `site`, `group`, `integration`, `automation`) into project directories (`Extensions/`, `Data Sources/`, etc.) as `.metadata.json` files with optional companion content files
240
+ 8. Processes remaining entities with BinID into corresponding bin directories
241
+ 9. Saves `app.json` to project root with `@path/to/*.metadata.json` references
242
+
243
+ ### Placement preferences
244
+
245
+ When a record has both `Path`/`FullPath` and `BinID`, clone prompts the user to choose placement. Preferences are saved to `.dbo/config.json` and reused on future clones:
246
+
247
+ - `ContentPlacement`: `bin` | `path` | `ask` — for content and other entities
248
+ - `MediaPlacement`: `bin` | `fullpath` | `ask` — for media files
249
+
250
+ Pre-set these in config.json to skip prompts. `.dbo/config.json` and `.dbo/structure.json` are shared via git; `.dbo/credentials.json` and `.dbo/cookies.txt` are gitignored (per-user).
251
+
252
+ ### AppID awareness (add & input)
253
+
254
+ After cloning, the config has an `AppID`. When running `dbo add` or `dbo input` without an AppID in the data, the CLI prompts:
255
+ 1. Yes, use AppID from config
256
+ 2. No
257
+ 3. Enter custom AppID
258
+
259
+ ### Init flags for clone
260
+
261
+ `dbo init` now supports `--app <shortName>` and `--clone` flags to combine initialization with app cloning.
262
+
263
+ ## Error Handling
264
+
265
+ - If the command fails with a session/authentication error, suggest: `dbo login`
266
+ - If it fails with "No domain configured", suggest: `dbo init`
267
+ - If a command is not found, suggest: `dbo --help`
268
+
269
+ ## Output
270
+
271
+ When showing results:
272
+ - Format JSON output readably
273
+ - For pull/push/add operations, list the files created or modified
274
+ - For query operations, summarize the row count and key fields
@@ -116,6 +116,41 @@ async function unregisterPlugin(pluginName) {
116
116
  }
117
117
  }
118
118
 
119
+ /**
120
+ * Extract commands/*.md from a directory plugin to ~/.claude/commands/ or .claude/commands/.
121
+ * This provides the /commandName shorthand alongside the /plugin:skill format.
122
+ * @param {string} pluginSourcePath - Path to the plugin source directory
123
+ * @param {'project' | 'global'} scope - Installation scope
124
+ * @returns {Promise<number>} Number of commands extracted
125
+ */
126
+ async function extractPluginCommands(pluginSourcePath, scope) {
127
+ const commandsSourceDir = join(pluginSourcePath, 'commands');
128
+ if (!existsSync(commandsSourceDir)) return 0;
129
+
130
+ const commandsTargetDir = getCommandsDir(scope);
131
+ await mkdir(commandsTargetDir, { recursive: true });
132
+
133
+ let extracted = 0;
134
+ const files = await readdir(commandsSourceDir);
135
+ for (const file of files) {
136
+ if (!file.endsWith('.md')) continue;
137
+ const srcPath = join(commandsSourceDir, file);
138
+ const destPath = join(commandsTargetDir, file);
139
+ const srcContent = await readFile(srcPath, 'utf8');
140
+
141
+ if (await fileExists(destPath)) {
142
+ const destContent = await readFile(destPath, 'utf8');
143
+ if (fileHash(srcContent) === fileHash(destContent)) continue;
144
+ }
145
+
146
+ await copyFile(srcPath, destPath);
147
+ const label = scope === 'global' ? '~/.claude/commands/' : '.claude/commands/';
148
+ log.dim(` Extracted command: ${label}${file}`);
149
+ extracted++;
150
+ }
151
+ return extracted;
152
+ }
153
+
119
154
  /**
120
155
  * Check if a plugin source is a directory-based plugin (has .claude-plugin/).
121
156
  * @param {string} pluginPath - Path to the plugin directory
@@ -680,6 +715,9 @@ export async function installOrUpdateClaudeCommands(options = {}) {
680
715
  }
681
716
  }
682
717
 
718
+ // Extract commands/*.md to ~/.claude/commands/ or .claude/commands/
719
+ await extractPluginCommands(plugin.path, targetScope);
720
+
683
721
  // Persist scope + metadata to config.local.json
684
722
  if (hasProject && (options.global || options.local || !await getPluginScope(plugin.name))) {
685
723
  await setPluginScope(plugin.name, {
@@ -691,19 +729,22 @@ export async function installOrUpdateClaudeCommands(options = {}) {
691
729
  log.warn(`Cannot persist scope preference (no .dbo/ directory). Run "dbo init" first.`);
692
730
  }
693
731
 
694
- // Clean up legacy file if it exists
695
- const legacyProjectPath = join(getCommandsDir('project'), legacyFileName);
696
- const legacyGlobalPath = join(getCommandsDir('global'), legacyFileName);
697
- if (await fileExists(legacyProjectPath)) {
698
- const { unlink } = await import('fs/promises');
699
- await unlink(legacyProjectPath);
700
- await removeFromGitignore(`.claude/commands/${legacyFileName}`);
701
- log.dim(` Removed legacy command: .claude/commands/${legacyFileName}`);
702
- }
703
- if (await fileExists(legacyGlobalPath)) {
704
- const { unlink } = await import('fs/promises');
705
- await unlink(legacyGlobalPath);
706
- log.dim(` Removed legacy command: ~/.claude/commands/${legacyFileName}`);
732
+ // Clean up legacy command files (but not ones we just extracted from commands/)
733
+ const hasPluginCommand = existsSync(join(plugin.path, 'commands', legacyFileName));
734
+ if (!hasPluginCommand) {
735
+ const legacyProjectPath = join(getCommandsDir('project'), legacyFileName);
736
+ const legacyGlobalPath = join(getCommandsDir('global'), legacyFileName);
737
+ if (await fileExists(legacyProjectPath)) {
738
+ const { unlink } = await import('fs/promises');
739
+ await unlink(legacyProjectPath);
740
+ await removeFromGitignore(`.claude/commands/${legacyFileName}`);
741
+ log.dim(` Removed legacy command: .claude/commands/${legacyFileName}`);
742
+ }
743
+ if (await fileExists(legacyGlobalPath)) {
744
+ const { unlink } = await import('fs/promises');
745
+ await unlink(legacyGlobalPath);
746
+ log.dim(` Removed legacy command: ~/.claude/commands/${legacyFileName}`);
747
+ }
707
748
  }
708
749
 
709
750
  if (targetScope === 'global' && locations.project) {
@@ -766,7 +807,7 @@ export async function installOrUpdateClaudeCommands(options = {}) {
766
807
  if (upToDate > 0) log.dim(`${upToDate} plugin(s) already up to date.`);
767
808
  if (skipped > 0) log.dim(`${skipped} plugin(s) skipped.`);
768
809
  if (installed > 0 || updated > 0) {
769
- log.info('Use /dbo:cli in Claude Code.');
810
+ log.info('Use /dbo in Claude Code.');
770
811
  log.warn('Note: Plugins will be available in new Claude Code sessions (restart any active session).');
771
812
  }
772
813
  }
@@ -872,6 +913,9 @@ async function installOrUpdateSpecificCommand(name, options = {}) {
872
913
  }
873
914
  }
874
915
 
916
+ // Extract commands/*.md to ~/.claude/commands/ or .claude/commands/
917
+ await extractPluginCommands(plugin.path, targetScope);
918
+
875
919
  // Persist scope + metadata to config.local.json
876
920
  if (hasProject && (options.global || options.local || !await getPluginScope(pluginName))) {
877
921
  await setPluginScope(pluginName, {
@@ -883,19 +927,22 @@ async function installOrUpdateSpecificCommand(name, options = {}) {
883
927
  log.warn(`Cannot persist scope preference (no .dbo/ directory). Run "dbo init" first.`);
884
928
  }
885
929
 
886
- // Clean up legacy files
887
- const legacyProjectPath = join(getCommandsDir('project'), legacyFileName);
888
- const legacyGlobalPath = join(getCommandsDir('global'), legacyFileName);
889
- if (await fileExists(legacyProjectPath)) {
890
- const { unlink } = await import('fs/promises');
891
- await unlink(legacyProjectPath);
892
- await removeFromGitignore(`.claude/commands/${legacyFileName}`);
893
- log.dim(` Removed legacy command: .claude/commands/${legacyFileName}`);
894
- }
895
- if (await fileExists(legacyGlobalPath)) {
896
- const { unlink } = await import('fs/promises');
897
- await unlink(legacyGlobalPath);
898
- log.dim(` Removed legacy command: ~/.claude/commands/${legacyFileName}`);
930
+ // Clean up legacy command files (but not ones we just extracted from commands/)
931
+ const hasPluginCommand = existsSync(join(plugin.path, 'commands', legacyFileName));
932
+ if (!hasPluginCommand) {
933
+ const legacyProjectPath = join(getCommandsDir('project'), legacyFileName);
934
+ const legacyGlobalPath = join(getCommandsDir('global'), legacyFileName);
935
+ if (await fileExists(legacyProjectPath)) {
936
+ const { unlink } = await import('fs/promises');
937
+ await unlink(legacyProjectPath);
938
+ await removeFromGitignore(`.claude/commands/${legacyFileName}`);
939
+ log.dim(` Removed legacy command: .claude/commands/${legacyFileName}`);
940
+ }
941
+ if (await fileExists(legacyGlobalPath)) {
942
+ const { unlink } = await import('fs/promises');
943
+ await unlink(legacyGlobalPath);
944
+ log.dim(` Removed legacy command: ~/.claude/commands/${legacyFileName}`);
945
+ }
899
946
  }
900
947
 
901
948
  if (targetScope === 'global' && locations.project) {