@upend/cli 0.1.1 → 0.1.2

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/bin/cli.ts CHANGED
@@ -1,7 +1,18 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import { existsSync } from "fs";
4
+
5
+ // auto-load encrypted .env if present (skip for init — no .env yet)
3
6
  const args = process.argv.slice(2);
4
7
  const command = args[0];
8
+ if (command !== "init" && existsSync(".env")) {
9
+ try {
10
+ const { config } = await import("@dotenvx/dotenvx");
11
+ config({ quiet: true });
12
+ } catch {
13
+ // dotenvx not available, env vars must be set manually
14
+ }
15
+ }
5
16
 
6
17
  const commands: Record<string, () => Promise<void>> = {
7
18
  init: () => import("../src/commands/init").then((m) => m.default(args.slice(1))),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upend/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Anti-SaaS stack. Deploy live apps with Claude, Postgres, and rsync.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "url": "https://github.com/cif/upend"
20
20
  },
21
21
  "dependencies": {
22
+ "@dotenvx/dotenvx": "^1.55.1",
22
23
  "hono": "^4.12.8",
23
24
  "jose": "^6.2.1",
24
25
  "postgres": "^3.4.8"
@@ -23,7 +23,7 @@ export default async function dev(args: string[]) {
23
23
 
24
24
  // start API service
25
25
  log.info(`starting api → :${apiPort}`);
26
- Bun.spawn(["bunx", "@dotenvx/dotenvx", "run", "--", "bun", "--watch", `${cliRoot}/src/services/gateway/index.ts`], {
26
+ Bun.spawn(["bun", "--watch", `${cliRoot}/src/services/gateway/index.ts`], {
27
27
  cwd: projectDir,
28
28
  env: { ...process.env, API_PORT: apiPort, UPEND_PROJECT: projectDir },
29
29
  stdout: "inherit",
@@ -32,7 +32,7 @@ export default async function dev(args: string[]) {
32
32
 
33
33
  // start Claude service
34
34
  log.info(`starting claude → :${claudePort}`);
35
- Bun.spawn(["bunx", "@dotenvx/dotenvx", "run", "--", "bun", "--watch", `${cliRoot}/src/services/claude/index.ts`], {
35
+ Bun.spawn(["bun", "--watch", `${cliRoot}/src/services/claude/index.ts`], {
36
36
  cwd: projectDir,
37
37
  env: { ...process.env, CLAUDE_PORT: claudePort, UPEND_PROJECT: projectDir },
38
38
  stdout: "inherit",
@@ -340,10 +340,10 @@ All requests need \`Authorization: Bearer <jwt>\` header.
340
340
  log.blank();
341
341
  log.info(`cd ${name}`);
342
342
  if (!process.env.ANTHROPIC_API_KEY) {
343
- log.info("upend env:set ANTHROPIC_API_KEY <your-key>");
343
+ log.info("bunx upend env:set ANTHROPIC_API_KEY <your-key>");
344
344
  }
345
- log.info("upend migrate");
346
- log.info("upend dev");
345
+ log.info("bunx upend migrate");
346
+ log.info("bunx upend dev");
347
347
  log.blank();
348
348
  if (databaseUrl) {
349
349
  log.dim(`database: ${neonProjectId}`);
@@ -190,6 +190,25 @@
190
190
  </div>
191
191
  </div>
192
192
 
193
+ <!-- new app modal -->
194
+ <div x-show="showNewAppModal" x-transition class="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
195
+ <form @submit.prevent="submitNewApp()"
196
+ @keydown.escape="showNewAppModal = false"
197
+ class="bg-surface border border-border rounded-xl p-6 w-96 flex flex-col gap-4">
198
+ <h3 class="text-accent font-bold text-sm">new app</h3>
199
+ <input x-model="newAppName" x-ref="newAppNameInput" type="text" placeholder="app name (lowercase, no spaces)"
200
+ class="bg-bg border border-border rounded-md px-3 py-2 text-sm text-gray-200 font-mono outline-none focus:border-accent">
201
+ <textarea x-model="newAppDesc" rows="3" placeholder="what should this app do?"
202
+ class="bg-bg border border-border rounded-md px-3 py-2 text-sm text-gray-200 font-mono outline-none focus:border-accent resize-none"></textarea>
203
+ <div class="flex gap-2 justify-end">
204
+ <button type="button" @click="showNewAppModal = false"
205
+ class="text-muted text-xs px-3 py-1.5 rounded border border-border cursor-pointer hover:text-gray-200 font-mono">cancel</button>
206
+ <button type="submit" :disabled="!newAppName.trim() || !newAppDesc.trim()"
207
+ class="bg-accent text-black text-xs px-4 py-1.5 rounded font-bold cursor-pointer disabled:opacity-40 font-mono">create</button>
208
+ </div>
209
+ </form>
210
+ </div>
211
+
193
212
  <!-- main panels -->
194
213
  <div x-show="token" class="flex-1 flex min-h-0">
195
214
  <!-- left: chat (native DOM) -->
@@ -385,6 +404,9 @@ function dashboard() {
385
404
  showSessionModal: false,
386
405
  showCloseModal: false,
387
406
  showPublishModal: false,
407
+ showNewAppModal: false,
408
+ newAppName: '',
409
+ newAppDesc: '',
388
410
  sessionTitle: '',
389
411
  _pendingPrompt: '',
390
412
 
@@ -826,10 +848,17 @@ function dashboard() {
826
848
 
827
849
  createNewApp() {
828
850
  this.appsOpen = false;
829
- const name = window.prompt('app name (lowercase, no spaces):');
830
- if (!name) return;
831
- const desc = window.prompt('what should this app do?');
832
- if (!desc) return;
851
+ this.newAppName = '';
852
+ this.newAppDesc = '';
853
+ this.showNewAppModal = true;
854
+ this.$nextTick(() => this.$refs.newAppNameInput?.focus());
855
+ },
856
+
857
+ submitNewApp() {
858
+ const name = this.newAppName.trim().toLowerCase().replace(/\s+/g, '-');
859
+ const desc = this.newAppDesc.trim();
860
+ if (!name || !desc) return;
861
+ this.showNewAppModal = false;
833
862
  this.prompt = `create an app called "${name}" in apps/${name}/. ${desc}`;
834
863
  this.sendPrompt();
835
864
  },