bod-cli 0.7.2 → 0.8.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/dist/cli.js CHANGED
@@ -20151,23 +20151,47 @@ function directTarget(port, token, label) {
20151
20151
  }
20152
20152
  };
20153
20153
  }
20154
- async function resolveDbTarget(appArg) {
20155
- const info = readServeInfo();
20156
- if (info)
20157
- return directTarget(info.dbPort, info.dbAdminPassword, "local serve");
20158
- const envPw = readDotEnv("BODDB_ADMIN_PASSWORD");
20159
- if (envPw)
20160
- return directTarget(readDbPortFromYaml(), envPw, "direct via .env");
20154
+ function isInAppFolder() {
20155
+ return existsSync6(join6(process.cwd(), "bodify.yaml"));
20156
+ }
20157
+ async function resolveDbTarget(appArg, wantLocal) {
20158
+ const inApp = isInAppFolder();
20159
+ const scopeLabel = inApp ? "APP" : "BODIFY-PLATFORM";
20160
+ if (wantLocal) {
20161
+ if (!inApp) {
20162
+ console.error(source_default.red("--local (-l) only works inside a Bodify app folder (needs bodify.yaml)."));
20163
+ process.exit(1);
20164
+ }
20165
+ const info = readServeInfo();
20166
+ if (info)
20167
+ return directTarget(info.dbPort, info.dbAdminPassword, `LOCAL ${scopeLabel} bod serve`);
20168
+ const envPw = readDotEnv("BODDB_ADMIN_PASSWORD");
20169
+ if (envPw)
20170
+ return directTarget(readDbPortFromYaml(), envPw, `LOCAL ${scopeLabel} via .env`);
20171
+ console.error(source_default.red("--local (-l) requires a running `bod serve` in this directory."));
20172
+ console.error(source_default.dim(" Expected `.bodify/serve.info.json` or BODDB_ADMIN_PASSWORD in .env — neither found."));
20173
+ process.exit(1);
20174
+ }
20161
20175
  const { url, apiKey } = getResolvedInstance(loadConfig());
20162
20176
  const client = new BodClient(url, apiKey);
20163
- const appId = await resolveAppId(client, resolveAppName(appArg));
20177
+ const effectiveAppArg = inApp ? appArg : appArg ?? "bodify";
20178
+ const appId = await resolveAppId(client, resolveAppName(effectiveAppArg));
20179
+ const instanceLabel = process.env._BOD_INSTANCE_OVERRIDE || process.env.BOD_INSTANCE || "default";
20164
20180
  return {
20165
- label: `agent ${url} → app ${appId}`,
20181
+ label: `REMOTE ${scopeLabel} ${instanceLabel} (${url}) → app ${appId}`,
20166
20182
  request(method, sub, body) {
20167
20183
  return client.request(method, `/apps/${appId}${sub}`, body);
20168
20184
  }
20169
20185
  };
20170
20186
  }
20187
+ function printTargetBanner(target) {
20188
+ const isLocal = target.label.startsWith("LOCAL");
20189
+ const color = isLocal ? source_default.cyan : source_default.yellow;
20190
+ console.error(color(`→ DB target: ${target.label}${isInAppFolder() ? "" : " [no bodify.yaml in cwd]"}`));
20191
+ }
20192
+ function readLocalFlag() {
20193
+ return process.argv.slice(2).some((a2) => a2 === "--local" || a2 === "-l");
20194
+ }
20171
20195
  async function readBody(positional, file) {
20172
20196
  let raw;
20173
20197
  if (positional)
@@ -20248,7 +20272,8 @@ var getCmd = defineCommand2({
20248
20272
  },
20249
20273
  async run({ args }) {
20250
20274
  strictArgs(["path", "app", "a", "deep", "d", "limit", "n", "offset", "output", "o"]);
20251
- const target = await resolveDbTarget(args.app);
20275
+ const target = await resolveDbTarget(args.app, readLocalFlag());
20276
+ printTargetBanner(target);
20252
20277
  const path = normalizePath(args.path);
20253
20278
  const qs = [];
20254
20279
  if (!args.deep) {
@@ -20277,7 +20302,8 @@ var setCmd2 = defineCommand2({
20277
20302
  async run({ args }) {
20278
20303
  strictArgs(["path", "value", "app", "a", "file", "f"]);
20279
20304
  const body = await readBody(args.value, args.file);
20280
- const target = await resolveDbTarget(args.app);
20305
+ const target = await resolveDbTarget(args.app, readLocalFlag());
20306
+ printTargetBanner(target);
20281
20307
  await target.request("PUT", `/db/${normalizePath(args.path)}`, body);
20282
20308
  console.log(source_default.green(`✓ Set ${args.path}`));
20283
20309
  }
@@ -20293,7 +20319,8 @@ var updateCmd = defineCommand2({
20293
20319
  async run({ args }) {
20294
20320
  strictArgs(["path", "value", "app", "a", "file", "f"]);
20295
20321
  const body = await readBody(args.value, args.file);
20296
- const target = await resolveDbTarget(args.app);
20322
+ const target = await resolveDbTarget(args.app, readLocalFlag());
20323
+ printTargetBanner(target);
20297
20324
  await target.request("PATCH", `/db/${normalizePath(args.path)}`, body);
20298
20325
  console.log(source_default.green(`✓ Updated ${args.path}`));
20299
20326
  }
@@ -20309,7 +20336,8 @@ var pushCmd = defineCommand2({
20309
20336
  async run({ args }) {
20310
20337
  strictArgs(["path", "value", "app", "a", "file", "f"]);
20311
20338
  const body = await readBody(args.value, args.file);
20312
- const target = await resolveDbTarget(args.app);
20339
+ const target = await resolveDbTarget(args.app, readLocalFlag());
20340
+ printTargetBanner(target);
20313
20341
  const res = await target.request("POST", `/db/${normalizePath(args.path)}`, body);
20314
20342
  console.log(source_default.green(`✓ Pushed → ${args.path}/${res.key}`));
20315
20343
  }
@@ -20327,7 +20355,8 @@ var deleteCmd = defineCommand2({
20327
20355
  console.error(source_default.yellow(`Refusing to delete "${args.path}" without --confirm (-y).`));
20328
20356
  process.exit(1);
20329
20357
  }
20330
- const target = await resolveDbTarget(args.app);
20358
+ const target = await resolveDbTarget(args.app, readLocalFlag());
20359
+ printTargetBanner(target);
20331
20360
  await target.request("DELETE", `/db/${normalizePath(args.path)}`);
20332
20361
  console.log(source_default.green(`✓ Deleted ${args.path}`));
20333
20362
  }
@@ -20394,7 +20423,8 @@ var queryCmd = defineCommand2({
20394
20423
  limit: args.limit ? Number(args.limit) : undefined,
20395
20424
  offset: args.offset ? Number(args.offset) : undefined
20396
20425
  };
20397
- const target = await resolveDbTarget(args.app);
20426
+ const target = await resolveDbTarget(args.app, readLocalFlag());
20427
+ printTargetBanner(target);
20398
20428
  const res = await target.request("POST", `/query/${normalizePath(args.path)}`, body);
20399
20429
  let rows = res.data;
20400
20430
  if (!args.deep && Array.isArray(rows)) {
@@ -20419,7 +20449,18 @@ var db_default = defineCommand2({
20419
20449
  });
20420
20450
 
20421
20451
  // src/cli.ts
20422
- var instanceFlag = process.argv.find((a2) => a2.startsWith("--instance="))?.split("=").slice(1).join("=");
20452
+ function parseInstanceFlag() {
20453
+ const argv2 = process.argv;
20454
+ for (let i2 = 0;i2 < argv2.length; i2++) {
20455
+ const a2 = argv2[i2];
20456
+ if (a2.startsWith("--instance="))
20457
+ return a2.slice("--instance=".length);
20458
+ if (a2 === "--instance" && i2 + 1 < argv2.length)
20459
+ return argv2[i2 + 1];
20460
+ }
20461
+ return;
20462
+ }
20463
+ var instanceFlag = parseInstanceFlag();
20423
20464
  var isLocal = process.argv.includes("--local") || process.argv.includes("-l");
20424
20465
  if (instanceFlag)
20425
20466
  setInstanceOverride(instanceFlag);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bod-cli",
3
- "version": "0.7.2",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "bod": "./dist/cli.js"
package/src/cli.ts CHANGED
@@ -17,8 +17,18 @@ import sshCmd from './commands/ssh'
17
17
  import publishCmd from './commands/publish'
18
18
  import dbCmd from './commands/db'
19
19
 
20
- // Parse --instance / --local / -l early so it's set before citty dispatches subcommands
21
- const instanceFlag = process.argv.find(a => a.startsWith('--instance='))?.split('=').slice(1).join('=')
20
+ // Parse --instance / --local / -l early so it's set before citty dispatches subcommands.
21
+ // Supports both `--instance=NAME` and `--instance NAME` forms.
22
+ function parseInstanceFlag(): string | undefined {
23
+ const argv = process.argv
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i]
26
+ if (a.startsWith('--instance=')) return a.slice('--instance='.length)
27
+ if (a === '--instance' && i + 1 < argv.length) return argv[i + 1]
28
+ }
29
+ return undefined
30
+ }
31
+ const instanceFlag = parseInstanceFlag()
22
32
  const isLocal = process.argv.includes('--local') || process.argv.includes('-l')
23
33
  if (instanceFlag) setInstanceOverride(instanceFlag)
24
34
  else if (isLocal) setInstanceOverride('local')
@@ -79,31 +79,76 @@ function directTarget(port: number, token: string, label: string): DbTarget {
79
79
  }
80
80
  }
81
81
 
82
+ /** True if cwd has a `bodify.yaml` (i.e. we're inside a Bodify app project). */
83
+ function isInAppFolder(): boolean {
84
+ return existsSync(join(process.cwd(), 'bodify.yaml'))
85
+ }
86
+
82
87
  /**
83
- * Resolve a target for DB commands. Direct-to-BodDB takes precedence when we
84
- * can discover the port + admin password locally — this covers `bod serve`
85
- * running in the same dir (via `.bodify/serve.info.json` or `.env`). Falls
86
- * back to the Bodify agent proxy for deployed apps.
88
+ * Resolve a target for DB commands.
89
+ *
90
+ * Explicit, predictable rules — no silent auto-detection:
91
+ * • `-l` / `--local`: REQUIRE a running `bod serve` in cwd. Connects
92
+ * directly to its BodDB (port from `.bodify/serve.info.json`, or
93
+ * `.env BODDB_ADMIN_PASSWORD` + `bodify.yaml database.port`). Fails
94
+ * loudly if no local serve is discoverable — we never silently fall
95
+ * through to prod when `-l` was requested.
96
+ * • No `-l` (default): route through the Bodify agent proxy (remote prod
97
+ * by default, or the instance named by `--instance` / `BOD_INSTANCE`).
98
+ * Even when `bod serve` is running in the same directory, we stay on
99
+ * prod unless `-l` was passed — this is the opposite of the old
100
+ * behavior and prevents "which DB did I just write to?" confusion.
101
+ *
102
+ * Every resolved target carries a human label that the command layer prints
103
+ * as a banner before running, so the user can always see where writes land.
87
104
  */
88
- async function resolveDbTarget(appArg: string | undefined): Promise<DbTarget> {
89
- // 1. Live `bod serve` info file — authoritative (correct port even if scanned).
90
- const info = readServeInfo()
91
- if (info) return directTarget(info.dbPort, info.dbAdminPassword, 'local serve')
92
- // 2. .env BODDB_ADMIN_PASSWORD + bodify.yaml database.port — works without restarting serve.
93
- const envPw = readDotEnv('BODDB_ADMIN_PASSWORD')
94
- if (envPw) return directTarget(readDbPortFromYaml(), envPw, 'direct via .env')
95
- // 3. Bodify agent proxy (deployed apps).
105
+ async function resolveDbTarget(appArg: string | undefined, wantLocal: boolean): Promise<DbTarget> {
106
+ const inApp = isInAppFolder()
107
+ const scopeLabel = inApp ? 'APP' : 'BODIFY-PLATFORM'
108
+
109
+ if (wantLocal) {
110
+ // `-l` only makes sense inside an app folder — the Bodify platform DB
111
+ // isn't something you run locally via `bod serve`.
112
+ if (!inApp) {
113
+ console.error(chalk.red('--local (-l) only works inside a Bodify app folder (needs bodify.yaml).'))
114
+ process.exit(1)
115
+ }
116
+ const info = readServeInfo()
117
+ if (info) return directTarget(info.dbPort, info.dbAdminPassword, `LOCAL ${scopeLabel} bod serve`)
118
+ const envPw = readDotEnv('BODDB_ADMIN_PASSWORD')
119
+ if (envPw) return directTarget(readDbPortFromYaml(), envPw, `LOCAL ${scopeLabel} via .env`)
120
+ console.error(chalk.red('--local (-l) requires a running `bod serve` in this directory.'))
121
+ console.error(chalk.dim(' Expected `.bodify/serve.info.json` or BODDB_ADMIN_PASSWORD in .env — neither found.'))
122
+ process.exit(1)
123
+ }
124
+
125
+ // Remote: Bodify agent proxy. Inside an app folder → that app's DB;
126
+ // outside → the Bodify platform app's own DB.
96
127
  const { url, apiKey } = getResolvedInstance(loadConfig())
97
128
  const client = new BodClient(url, apiKey)
98
- const appId = await resolveAppId(client, resolveAppName(appArg))
129
+ const effectiveAppArg = inApp ? appArg : (appArg ?? 'bodify')
130
+ const appId = await resolveAppId(client, resolveAppName(effectiveAppArg))
131
+ const instanceLabel = process.env._BOD_INSTANCE_OVERRIDE || process.env.BOD_INSTANCE || 'default'
99
132
  return {
100
- label: `agent ${url} → app ${appId}`,
133
+ label: `REMOTE ${scopeLabel} ${instanceLabel} (${url}) → app ${appId}`,
101
134
  request<T>(method: string, sub: string, body?: unknown): Promise<T> {
102
135
  return client.request<T>(method, `/apps/${appId}${sub}`, body)
103
136
  },
104
137
  }
105
138
  }
106
139
 
140
+ /** Print a clear banner showing the resolved DB target. Always on stderr. */
141
+ function printTargetBanner(target: DbTarget): void {
142
+ const isLocal = target.label.startsWith('LOCAL')
143
+ const color = isLocal ? chalk.cyan : chalk.yellow
144
+ console.error(color(`→ DB target: ${target.label}${isInAppFolder() ? '' : ' [no bodify.yaml in cwd]'}`))
145
+ }
146
+
147
+ /** Read --local / -l from argv before citty parses, so all subcommands see it. */
148
+ function readLocalFlag(): boolean {
149
+ return process.argv.slice(2).some(a => a === '--local' || a === '-l')
150
+ }
151
+
107
152
  /** Read body from positional arg, --file, or stdin (in that order). */
108
153
  async function readBody(positional: string | undefined, file: string | undefined): Promise<unknown> {
109
154
  let raw: string | undefined
@@ -190,7 +235,8 @@ const getCmd = defineCommand({
190
235
  },
191
236
  async run({ args }) {
192
237
  strictArgs(['path', 'app', 'a', 'deep', 'd', 'limit', 'n', 'offset', 'output', 'o'])
193
- const target = await resolveDbTarget(args.app)
238
+ const target = await resolveDbTarget(args.app, readLocalFlag())
239
+ printTargetBanner(target)
194
240
  const path = normalizePath(args.path)
195
241
  const qs: string[] = []
196
242
  if (!args.deep) {
@@ -218,7 +264,8 @@ const setCmd = defineCommand({
218
264
  async run({ args }) {
219
265
  strictArgs(['path', 'value', 'app', 'a', 'file', 'f'])
220
266
  const body = await readBody(args.value, args.file)
221
- const target = await resolveDbTarget(args.app)
267
+ const target = await resolveDbTarget(args.app, readLocalFlag())
268
+ printTargetBanner(target)
222
269
  await target.request('PUT', `/db/${normalizePath(args.path)}`, body)
223
270
  console.log(chalk.green(`✓ Set ${args.path}`))
224
271
  },
@@ -235,7 +282,8 @@ const updateCmd = defineCommand({
235
282
  async run({ args }) {
236
283
  strictArgs(['path', 'value', 'app', 'a', 'file', 'f'])
237
284
  const body = await readBody(args.value, args.file)
238
- const target = await resolveDbTarget(args.app)
285
+ const target = await resolveDbTarget(args.app, readLocalFlag())
286
+ printTargetBanner(target)
239
287
  await target.request('PATCH', `/db/${normalizePath(args.path)}`, body)
240
288
  console.log(chalk.green(`✓ Updated ${args.path}`))
241
289
  },
@@ -252,7 +300,8 @@ const pushCmd = defineCommand({
252
300
  async run({ args }) {
253
301
  strictArgs(['path', 'value', 'app', 'a', 'file', 'f'])
254
302
  const body = await readBody(args.value, args.file)
255
- const target = await resolveDbTarget(args.app)
303
+ const target = await resolveDbTarget(args.app, readLocalFlag())
304
+ printTargetBanner(target)
256
305
  const res = await target.request<DbResponse>('POST', `/db/${normalizePath(args.path)}`, body)
257
306
  console.log(chalk.green(`✓ Pushed → ${args.path}/${res.key}`))
258
307
  },
@@ -271,7 +320,8 @@ const deleteCmd = defineCommand({
271
320
  console.error(chalk.yellow(`Refusing to delete "${args.path}" without --confirm (-y).`))
272
321
  process.exit(1)
273
322
  }
274
- const target = await resolveDbTarget(args.app)
323
+ const target = await resolveDbTarget(args.app, readLocalFlag())
324
+ printTargetBanner(target)
275
325
  await target.request('DELETE', `/db/${normalizePath(args.path)}`)
276
326
  console.log(chalk.green(`✓ Deleted ${args.path}`))
277
327
  },
@@ -339,7 +389,8 @@ const queryCmd = defineCommand({
339
389
  limit: args.limit ? Number(args.limit) : undefined,
340
390
  offset: args.offset ? Number(args.offset) : undefined,
341
391
  }
342
- const target = await resolveDbTarget(args.app)
392
+ const target = await resolveDbTarget(args.app, readLocalFlag())
393
+ printTargetBanner(target)
343
394
  const res = await target.request<DbResponse>('POST', `/query/${normalizePath(args.path)}`, body)
344
395
  let rows = res.data as Array<Record<string, unknown>> | null
345
396
  if (!args.deep && Array.isArray(rows)) {