@usenaive-sdk/cli 0.15.1 → 1.0.0
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 +43 -402
- package/dist/index.js +51775 -502
- package/package.json +22 -18
- package/dist/client.d.ts +0 -108
- package/dist/client.js +0 -458
- package/dist/client.js.map +0 -1
- package/dist/commands/account-kits.d.ts +0 -2
- package/dist/commands/account-kits.js +0 -71
- package/dist/commands/account-kits.js.map +0 -1
- package/dist/commands/aeo.d.ts +0 -2
- package/dist/commands/aeo.js +0 -451
- package/dist/commands/aeo.js.map +0 -1
- package/dist/commands/agent-profiles.d.ts +0 -11
- package/dist/commands/agent-profiles.js +0 -120
- package/dist/commands/agent-profiles.js.map +0 -1
- package/dist/commands/agents.d.ts +0 -2
- package/dist/commands/agents.js +0 -916
- package/dist/commands/agents.js.map +0 -1
- package/dist/commands/app-data.d.ts +0 -2
- package/dist/commands/app-data.js +0 -445
- package/dist/commands/app-data.js.map +0 -1
- package/dist/commands/approvals.d.ts +0 -2
- package/dist/commands/approvals.js +0 -242
- package/dist/commands/approvals.js.map +0 -1
- package/dist/commands/apps.d.ts +0 -9
- package/dist/commands/apps.js +0 -844
- package/dist/commands/apps.js.map +0 -1
- package/dist/commands/audio.d.ts +0 -9
- package/dist/commands/audio.js +0 -391
- package/dist/commands/audio.js.map +0 -1
- package/dist/commands/auth.d.ts +0 -31
- package/dist/commands/auth.js +0 -428
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/billing.d.ts +0 -2
- package/dist/commands/billing.js +0 -250
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/brain.d.ts +0 -2
- package/dist/commands/brain.js +0 -1300
- package/dist/commands/brain.js.map +0 -1
- package/dist/commands/browser.d.ts +0 -2
- package/dist/commands/browser.js +0 -378
- package/dist/commands/browser.js.map +0 -1
- package/dist/commands/business.d.ts +0 -2
- package/dist/commands/business.js +0 -566
- package/dist/commands/business.js.map +0 -1
- package/dist/commands/cards.d.ts +0 -2
- package/dist/commands/cards.js +0 -419
- package/dist/commands/cards.js.map +0 -1
- package/dist/commands/ceo.d.ts +0 -2
- package/dist/commands/ceo.js +0 -369
- package/dist/commands/ceo.js.map +0 -1
- package/dist/commands/clone.d.ts +0 -2
- package/dist/commands/clone.js +0 -203
- package/dist/commands/clone.js.map +0 -1
- package/dist/commands/companies.d.ts +0 -10
- package/dist/commands/companies.js +0 -283
- package/dist/commands/companies.js.map +0 -1
- package/dist/commands/company-data.d.ts +0 -2
- package/dist/commands/company-data.js +0 -168
- package/dist/commands/company-data.js.map +0 -1
- package/dist/commands/compute.d.ts +0 -2
- package/dist/commands/compute.js +0 -498
- package/dist/commands/compute.js.map +0 -1
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -36
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/connections.d.ts +0 -8
- package/dist/commands/connections.js +0 -137
- package/dist/commands/connections.js.map +0 -1
- package/dist/commands/cron-jobs.d.ts +0 -2
- package/dist/commands/cron-jobs.js +0 -245
- package/dist/commands/cron-jobs.js.map +0 -1
- package/dist/commands/domains.d.ts +0 -2
- package/dist/commands/domains.js +0 -474
- package/dist/commands/domains.js.map +0 -1
- package/dist/commands/ecommerce.d.ts +0 -2
- package/dist/commands/ecommerce.js +0 -277
- package/dist/commands/ecommerce.js.map +0 -1
- package/dist/commands/email.d.ts +0 -2
- package/dist/commands/email.js +0 -336
- package/dist/commands/email.js.map +0 -1
- package/dist/commands/employees.d.ts +0 -2
- package/dist/commands/employees.js +0 -190
- package/dist/commands/employees.js.map +0 -1
- package/dist/commands/env.d.ts +0 -7
- package/dist/commands/env.js +0 -112
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/formation.d.ts +0 -2
- package/dist/commands/formation.js +0 -273
- package/dist/commands/formation.js.map +0 -1
- package/dist/commands/identity.d.ts +0 -2
- package/dist/commands/identity.js +0 -110
- package/dist/commands/identity.js.map +0 -1
- package/dist/commands/images.d.ts +0 -2
- package/dist/commands/images.js +0 -371
- package/dist/commands/images.js.map +0 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.js +0 -43
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/jobs.d.ts +0 -2
- package/dist/commands/jobs.js +0 -144
- package/dist/commands/jobs.js.map +0 -1
- package/dist/commands/keys.d.ts +0 -2
- package/dist/commands/keys.js +0 -156
- package/dist/commands/keys.js.map +0 -1
- package/dist/commands/legacy-auth.d.ts +0 -27
- package/dist/commands/legacy-auth.js +0 -64
- package/dist/commands/legacy-auth.js.map +0 -1
- package/dist/commands/llm.d.ts +0 -2
- package/dist/commands/llm.js +0 -125
- package/dist/commands/llm.js.map +0 -1
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.js +0 -65
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -7
- package/dist/commands/logout.js +0 -55
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/logs.d.ts +0 -2
- package/dist/commands/logs.js +0 -42
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/loops.d.ts +0 -2
- package/dist/commands/loops.js +0 -260
- package/dist/commands/loops.js.map +0 -1
- package/dist/commands/media.d.ts +0 -2
- package/dist/commands/media.js +0 -183
- package/dist/commands/media.js.map +0 -1
- package/dist/commands/memory.d.ts +0 -2
- package/dist/commands/memory.js +0 -151
- package/dist/commands/memory.js.map +0 -1
- package/dist/commands/mobile.d.ts +0 -4
- package/dist/commands/mobile.js +0 -382
- package/dist/commands/mobile.js.map +0 -1
- package/dist/commands/module.d.ts +0 -8
- package/dist/commands/module.js +0 -48
- package/dist/commands/module.js.map +0 -1
- package/dist/commands/objectives.d.ts +0 -2
- package/dist/commands/objectives.js +0 -243
- package/dist/commands/objectives.js.map +0 -1
- package/dist/commands/payments.d.ts +0 -2
- package/dist/commands/payments.js +0 -131
- package/dist/commands/payments.js.map +0 -1
- package/dist/commands/people.d.ts +0 -2
- package/dist/commands/people.js +0 -197
- package/dist/commands/people.js.map +0 -1
- package/dist/commands/phone.d.ts +0 -2
- package/dist/commands/phone.js +0 -289
- package/dist/commands/phone.js.map +0 -1
- package/dist/commands/projects.d.ts +0 -13
- package/dist/commands/projects.js +0 -220
- package/dist/commands/projects.js.map +0 -1
- package/dist/commands/queue.d.ts +0 -2
- package/dist/commands/queue.js +0 -121
- package/dist/commands/queue.js.map +0 -1
- package/dist/commands/register.d.ts +0 -2
- package/dist/commands/register.js +0 -88
- package/dist/commands/register.js.map +0 -1
- package/dist/commands/report.d.ts +0 -17
- package/dist/commands/report.js +0 -109
- package/dist/commands/report.js.map +0 -1
- package/dist/commands/sandbox.d.ts +0 -2
- package/dist/commands/sandbox.js +0 -558
- package/dist/commands/sandbox.js.map +0 -1
- package/dist/commands/search.d.ts +0 -2
- package/dist/commands/search.js +0 -223
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/seo.d.ts +0 -2
- package/dist/commands/seo.js +0 -528
- package/dist/commands/seo.js.map +0 -1
- package/dist/commands/sessions.d.ts +0 -6
- package/dist/commands/sessions.js +0 -109
- package/dist/commands/sessions.js.map +0 -1
- package/dist/commands/social-data.d.ts +0 -2
- package/dist/commands/social-data.js +0 -245
- package/dist/commands/social-data.js.map +0 -1
- package/dist/commands/social.d.ts +0 -2
- package/dist/commands/social.js +0 -407
- package/dist/commands/social.js.map +0 -1
- package/dist/commands/status.d.ts +0 -2
- package/dist/commands/status.js +0 -79
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/support.d.ts +0 -2
- package/dist/commands/support.js +0 -134
- package/dist/commands/support.js.map +0 -1
- package/dist/commands/tasks.d.ts +0 -2
- package/dist/commands/tasks.js +0 -409
- package/dist/commands/tasks.js.map +0 -1
- package/dist/commands/teams.d.ts +0 -69
- package/dist/commands/teams.js +0 -503
- package/dist/commands/teams.js.map +0 -1
- package/dist/commands/trading.d.ts +0 -2
- package/dist/commands/trading.js +0 -333
- package/dist/commands/trading.js.map +0 -1
- package/dist/commands/up.d.ts +0 -551
- package/dist/commands/up.js +0 -1149
- package/dist/commands/up.js.map +0 -1
- package/dist/commands/usage.d.ts +0 -2
- package/dist/commands/usage.js +0 -73
- package/dist/commands/usage.js.map +0 -1
- package/dist/commands/use.d.ts +0 -12
- package/dist/commands/use.js +0 -96
- package/dist/commands/use.js.map +0 -1
- package/dist/commands/users.d.ts +0 -10
- package/dist/commands/users.js +0 -82
- package/dist/commands/users.js.map +0 -1
- package/dist/commands/vault.d.ts +0 -2
- package/dist/commands/vault.js +0 -101
- package/dist/commands/vault.js.map +0 -1
- package/dist/commands/verification.d.ts +0 -2
- package/dist/commands/verification.js +0 -152
- package/dist/commands/verification.js.map +0 -1
- package/dist/commands/verify-email.d.ts +0 -19
- package/dist/commands/verify-email.js +0 -130
- package/dist/commands/verify-email.js.map +0 -1
- package/dist/commands/video.d.ts +0 -2
- package/dist/commands/video.js +0 -417
- package/dist/commands/video.js.map +0 -1
- package/dist/commands/voice.d.ts +0 -2
- package/dist/commands/voice.js +0 -516
- package/dist/commands/voice.js.map +0 -1
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -244
- package/dist/commands/wallet.js.map +0 -1
- package/dist/commands/webhooks.d.ts +0 -13
- package/dist/commands/webhooks.js +0 -119
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -2
- package/dist/commands/whoami.js +0 -53
- package/dist/commands/whoami.js.map +0 -1
- package/dist/config.d.ts +0 -42
- package/dist/config.js +0 -83
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js.map +0 -1
- package/dist/lib/brain-select.d.ts +0 -193
- package/dist/lib/brain-select.js +0 -403
- package/dist/lib/brain-select.js.map +0 -1
- package/dist/lib/cli-validation.d.ts +0 -12
- package/dist/lib/cli-validation.js +0 -46
- package/dist/lib/cli-validation.js.map +0 -1
- package/dist/lib/config-errors.d.ts +0 -4
- package/dist/lib/config-errors.js +0 -13
- package/dist/lib/config-errors.js.map +0 -1
- package/dist/lib/deprecation.d.ts +0 -158
- package/dist/lib/deprecation.js +0 -140
- package/dist/lib/deprecation.js.map +0 -1
- package/dist/lib/duration.d.ts +0 -8
- package/dist/lib/duration.js +0 -11
- package/dist/lib/duration.js.map +0 -1
- package/dist/lib/http-response.d.ts +0 -11
- package/dist/lib/http-response.js +0 -96
- package/dist/lib/http-response.js.map +0 -1
- package/dist/lib/llm-paywall.d.ts +0 -82
- package/dist/lib/llm-paywall.js +0 -231
- package/dist/lib/llm-paywall.js.map +0 -1
- package/dist/lib/pricing.d.ts +0 -132
- package/dist/lib/pricing.js +0 -148
- package/dist/lib/pricing.js.map +0 -1
- package/dist/lib/starter-config.d.ts +0 -13
- package/dist/lib/starter-config.js +0 -71
- package/dist/lib/starter-config.js.map +0 -1
- package/dist/lib/stream-frames.d.ts +0 -114
- package/dist/lib/stream-frames.js +0 -169
- package/dist/lib/stream-frames.js.map +0 -1
- package/dist/lib/teams-catalog.d.ts +0 -141
- package/dist/lib/teams-catalog.js +0 -841
- package/dist/lib/teams-catalog.js.map +0 -1
- package/dist/lib/unknown-command.d.ts +0 -71
- package/dist/lib/unknown-command.js +0 -132
- package/dist/lib/unknown-command.js.map +0 -1
- package/dist/lib/version-check.d.ts +0 -26
- package/dist/lib/version-check.js +0 -21
- package/dist/lib/version-check.js.map +0 -1
- package/dist/output.d.ts +0 -246
- package/dist/output.js +0 -539
- package/dist/output.js.map +0 -1
- package/dist/prompt.d.ts +0 -17
- package/dist/prompt.js +0 -70
- package/dist/prompt.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -14
- package/dist/version.js.map +0 -1
package/dist/commands/apps.js
DELETED
|
@@ -1,844 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { readFileSync, existsSync, rmSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
6
|
-
import { apiRequest, apiUploadGzip, handleApiError } from "../client.js";
|
|
7
|
-
import { agentError, agentOutput } from "../output.js";
|
|
8
|
-
export const appsCmd = new Command("apps")
|
|
9
|
-
.description("Web apps — create, deploy, manage Vercel-backed web applications with dedicated engineer agents")
|
|
10
|
-
.addHelpText("after", `
|
|
11
|
-
Subcommands:
|
|
12
|
-
naive apps list List all apps
|
|
13
|
-
naive apps create Create a new app
|
|
14
|
-
naive apps templates List starter templates (GitHub repo + clone commands)
|
|
15
|
-
naive apps show <id> Show app details
|
|
16
|
-
naive apps delete <id> Delete an app
|
|
17
|
-
naive apps deploy <id> [--dir <path>] Deploy (local directory upload, or agent workspace inside containers)
|
|
18
|
-
naive apps publish <id> --deployment <id> Promote a deployment to production
|
|
19
|
-
naive apps deployments <id> List deployment history
|
|
20
|
-
naive apps retry <id> Retry failed provisioning (noop if healthy)
|
|
21
|
-
naive apps secrets list <id> List env variable keys
|
|
22
|
-
naive apps secrets set <id> <KEY> <VALUE> Set an env variable
|
|
23
|
-
naive apps secrets delete <id> <KEY> Delete an env variable
|
|
24
|
-
naive apps secrets reveal <id> <KEY> Reveal env variable value
|
|
25
|
-
naive apps domains list <id> List app domains
|
|
26
|
-
naive apps domains add <id> <domain> Add a custom domain
|
|
27
|
-
naive apps domains remove <id> <domainId> Remove a domain
|
|
28
|
-
naive apps domains set-primary <id> <domainId> Set the primary production domain
|
|
29
|
-
naive apps domains connect <id> <domainId> Connect a company domain
|
|
30
|
-
naive apps domains disconnect <id> <domainId> Disconnect a company domain
|
|
31
|
-
naive apps domains verify-dns <id> <domainId> Verify DNS for connected domain
|
|
32
|
-
naive apps db tables <id> List database tables (fullstack only)
|
|
33
|
-
naive apps db query <id> "<sql>" Run SQL query (fullstack only)
|
|
34
|
-
naive apps vercel <id> <method> <path> Call any Vercel API operation (scoped to the app)
|
|
35
|
-
naive apps supabase <id> <method> <path> Call any Supabase Management API operation (scoped to the app)
|
|
36
|
-
|
|
37
|
-
Typical Workflow (direct — no orchestration needed):
|
|
38
|
-
1. naive apps create --name "My Site" --type frontend_only
|
|
39
|
-
→ Creates Vercel project, returns the starter template clone command
|
|
40
|
-
2. git clone the starter template, build it locally
|
|
41
|
-
3. naive apps deploy <id> (run inside your project directory)
|
|
42
|
-
→ Uploads your code to Vercel, returns preview URL
|
|
43
|
-
4. naive apps publish <id> --deployment <deploymentId>
|
|
44
|
-
→ Promotes preview to production
|
|
45
|
-
5. naive apps domains connect <id> <domainId>
|
|
46
|
-
→ Points a custom domain to the production app
|
|
47
|
-
|
|
48
|
-
Orchestrated Workflow (CEO / engineer agents):
|
|
49
|
-
Same commands run inside the agent container deploy from the agent's
|
|
50
|
-
workspace automatically — the engineer builds via the task system.
|
|
51
|
-
|
|
52
|
-
Fullstack Workflow (adds Supabase):
|
|
53
|
-
1. naive apps create --name "My App" --type fullstack
|
|
54
|
-
→ Creates Vercel + Supabase project
|
|
55
|
-
2. naive apps db tables <id> → inspect database schema
|
|
56
|
-
3. naive apps db query <id> "..." → run ad-hoc queries
|
|
57
|
-
4. naive apps secrets set <id> KEY VALUE --target production
|
|
58
|
-
→ configure environment variables
|
|
59
|
-
|
|
60
|
-
Direct Provider Access (advanced):
|
|
61
|
-
Any operation supported by the Vercel REST API or Supabase Management API
|
|
62
|
-
can be called through the scoped proxies — Naive injects the credentials:
|
|
63
|
-
naive apps vercel <id> GET v6/deployments
|
|
64
|
-
naive apps vercel <id> GET "v9/projects/<projectId>/domains"
|
|
65
|
-
naive apps supabase <id> GET "v1/projects/<ref>/config/auth"
|
|
66
|
-
naive apps supabase <id> POST "v1/projects/<ref>/database/query" --body '{"query":"select 1"}'
|
|
67
|
-
Consult the official Vercel / Supabase API docs for endpoints and payloads.
|
|
68
|
-
|
|
69
|
-
Each app gets a dedicated engineer agent that can build, deploy, and iterate
|
|
70
|
-
on the code autonomously via the kanban task system.
|
|
71
|
-
`);
|
|
72
|
-
// ─── List ────────────────────────────────────────────────────────────────────
|
|
73
|
-
appsCmd
|
|
74
|
-
.command("list")
|
|
75
|
-
.description("List all apps for the current company")
|
|
76
|
-
.action(async () => {
|
|
77
|
-
const resp = await apiRequest("GET", "/v1/apps");
|
|
78
|
-
handleApiError("apps.list", resp);
|
|
79
|
-
const data = resp.data;
|
|
80
|
-
const count = data.apps?.length ?? 0;
|
|
81
|
-
agentOutput({
|
|
82
|
-
action: "apps.list",
|
|
83
|
-
result: resp.data,
|
|
84
|
-
next_steps: [
|
|
85
|
-
...(count > 0
|
|
86
|
-
? [{ command: `naive apps show ${data.apps[0].id}`, description: "View details of the first app" }]
|
|
87
|
-
: []),
|
|
88
|
-
{ command: "naive apps create --name <name> --type frontend_only", description: "Create a new app" },
|
|
89
|
-
],
|
|
90
|
-
hints: [`${count} app${count !== 1 ? "s" : ""} found`],
|
|
91
|
-
related_commands: ["naive apps create", "naive apps show", "naive apps deploy"],
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
// ─── Create ──────────────────────────────────────────────────────────────────
|
|
95
|
-
appsCmd
|
|
96
|
-
.command("create")
|
|
97
|
-
.description("Create a new web app with a dedicated engineer agent")
|
|
98
|
-
.requiredOption("--name <name>", "App name")
|
|
99
|
-
.option("--type <type>", "App type: frontend_only or fullstack (default: frontend_only)", "frontend_only")
|
|
100
|
-
.option("--variant <variant>", "Template variant: dark-premium, clean-minimal, bold-energetic, warm-human")
|
|
101
|
-
// <desc...> so unquoted multi-word descriptions are not excess args
|
|
102
|
-
.option("--description <desc...>", "Short description (unquoted multi-word OK)")
|
|
103
|
-
.action(async (opts) => {
|
|
104
|
-
const type = String(opts.type ?? "frontend_only").trim() || "frontend_only";
|
|
105
|
-
if (type !== "frontend_only" && type !== "fullstack") {
|
|
106
|
-
agentError({
|
|
107
|
-
action: "apps.create",
|
|
108
|
-
error: {
|
|
109
|
-
code: "invalid_input",
|
|
110
|
-
message: `Invalid --type "${type}". Use frontend_only or fullstack.`,
|
|
111
|
-
},
|
|
112
|
-
recovery_steps: [
|
|
113
|
-
{
|
|
114
|
-
command: 'naive apps create --name <name> --type frontend_only',
|
|
115
|
-
description: "Create a frontend-only app (default)",
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
command: 'naive apps create --name <name> --type fullstack',
|
|
119
|
-
description: "Create a fullstack app with Supabase",
|
|
120
|
-
},
|
|
121
|
-
],
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
// `--description <desc...>` is variadic, so commander hands back a string[]
|
|
125
|
-
// for an unquoted multi-word description and a string for a quoted one.
|
|
126
|
-
const descriptionRaw = opts.description;
|
|
127
|
-
const description = Array.isArray(descriptionRaw)
|
|
128
|
-
? descriptionRaw.join(" ").trim() || undefined
|
|
129
|
-
: descriptionRaw
|
|
130
|
-
? String(descriptionRaw).trim() || undefined
|
|
131
|
-
: undefined;
|
|
132
|
-
const resp = await apiRequest("POST", "/v1/apps", {
|
|
133
|
-
name: opts.name,
|
|
134
|
-
type,
|
|
135
|
-
...(opts.variant && { variant: opts.variant }),
|
|
136
|
-
...(description && { description }),
|
|
137
|
-
});
|
|
138
|
-
handleApiError("apps.create", resp);
|
|
139
|
-
const data = resp.data;
|
|
140
|
-
const directMode = data.workspaceMode !== "container";
|
|
141
|
-
agentOutput({
|
|
142
|
-
action: "apps.create",
|
|
143
|
-
result: resp.data,
|
|
144
|
-
next_steps: [
|
|
145
|
-
{ command: `naive apps show ${data.id}`, description: "View app details" },
|
|
146
|
-
...(directMode && data.template?.cloneCommand
|
|
147
|
-
? [{ command: data.template.cloneCommand, description: "Clone the starter template to build locally" }]
|
|
148
|
-
: []),
|
|
149
|
-
{ command: `naive apps deploy ${data.id}`, description: directMode ? "Deploy from your project directory" : "Trigger first deployment" },
|
|
150
|
-
],
|
|
151
|
-
hints: [
|
|
152
|
-
`App "${data.name}" created with status: ${data.status}`,
|
|
153
|
-
...(directMode
|
|
154
|
-
? [
|
|
155
|
-
"Direct mode: clone the starter template, build locally, then run 'naive apps deploy' inside the project directory.",
|
|
156
|
-
...(data.template?.repoUrl ? [`Templates repo: ${data.template.repoUrl}`] : []),
|
|
157
|
-
]
|
|
158
|
-
: ["An engineer agent has been provisioned and can build the app"]),
|
|
159
|
-
],
|
|
160
|
-
related_commands: ["naive apps templates", "naive apps deploy", "naive apps secrets set", "naive apps show"],
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
// ─── Templates ───────────────────────────────────────────────────────────────
|
|
164
|
-
appsCmd
|
|
165
|
-
.command("templates")
|
|
166
|
-
.description("List starter templates — clone, customize, then 'naive apps deploy'")
|
|
167
|
-
.action(async () => {
|
|
168
|
-
const resp = await apiRequest("GET", "/v1/apps/templates");
|
|
169
|
-
handleApiError("apps.templates", resp);
|
|
170
|
-
agentOutput({
|
|
171
|
-
action: "apps.templates",
|
|
172
|
-
result: resp.data,
|
|
173
|
-
next_steps: [
|
|
174
|
-
{ command: "naive apps create --name <name> --type frontend_only", description: "Create an app for your template" },
|
|
175
|
-
],
|
|
176
|
-
hints: ["Clone a template, build it, then run 'naive apps deploy <app-id>' from the project directory"],
|
|
177
|
-
related_commands: ["naive apps create", "naive apps deploy"],
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
// ─── Show ────────────────────────────────────────────────────────────────────
|
|
181
|
-
appsCmd
|
|
182
|
-
.command("show <id>")
|
|
183
|
-
.description("Show full details of an app")
|
|
184
|
-
.action(async (id) => {
|
|
185
|
-
const resp = await apiRequest("GET", `/v1/apps/${id}`);
|
|
186
|
-
handleApiError("apps.show", resp);
|
|
187
|
-
agentOutput({
|
|
188
|
-
action: "apps.show",
|
|
189
|
-
result: resp.data,
|
|
190
|
-
next_steps: [
|
|
191
|
-
{ command: `naive apps deploy ${id}`, description: "Deploy latest code" },
|
|
192
|
-
{ command: `naive apps deployments ${id}`, description: "View deployment history" },
|
|
193
|
-
{ command: `naive apps secrets list ${id}`, description: "View environment variables" },
|
|
194
|
-
],
|
|
195
|
-
related_commands: ["naive apps deploy", "naive apps publish", "naive apps secrets set", "naive apps delete"],
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
// ─── Delete ──────────────────────────────────────────────────────────────────
|
|
199
|
-
appsCmd
|
|
200
|
-
.command("delete <id>")
|
|
201
|
-
.description("Permanently delete an app and all infrastructure")
|
|
202
|
-
.action(async (id) => {
|
|
203
|
-
const resp = await apiRequest("DELETE", `/v1/apps/${id}`);
|
|
204
|
-
handleApiError("apps.delete", resp);
|
|
205
|
-
agentOutput({
|
|
206
|
-
action: "apps.delete",
|
|
207
|
-
result: resp.data,
|
|
208
|
-
next_steps: [{ command: "naive apps list", description: "View remaining apps" }],
|
|
209
|
-
hints: ["App and all associated resources have been deleted"],
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
// ─── Deploy ──────────────────────────────────────────────────────────────────
|
|
213
|
-
/** Pack a local project directory into a gzipped tarball (node_modules etc. excluded). */
|
|
214
|
-
function packDirectory(dir) {
|
|
215
|
-
const tarball = join(tmpdir(), `naive-deploy-${Date.now()}.tgz`);
|
|
216
|
-
try {
|
|
217
|
-
execFileSync("tar", [
|
|
218
|
-
"czf", tarball,
|
|
219
|
-
"--exclude", "node_modules",
|
|
220
|
-
"--exclude", ".next",
|
|
221
|
-
"--exclude", ".git",
|
|
222
|
-
"--exclude", ".vercel",
|
|
223
|
-
"-C", dir,
|
|
224
|
-
".",
|
|
225
|
-
], { stdio: "ignore" });
|
|
226
|
-
return readFileSync(tarball);
|
|
227
|
-
}
|
|
228
|
-
finally {
|
|
229
|
-
rmSync(tarball, { force: true });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function looksLikeProject(dir) {
|
|
233
|
-
return existsSync(join(dir, "package.json")) || existsSync(join(dir, "index.html"));
|
|
234
|
-
}
|
|
235
|
-
appsCmd
|
|
236
|
-
.command("deploy <id>")
|
|
237
|
-
.description("Deploy the app — uploads your local project directory, or deploys the agent workspace when run inside a container")
|
|
238
|
-
.option("--dir <path>", "Local project directory to upload (default: current directory when outside a container)")
|
|
239
|
-
.option("--workspace-path <path>", "Container workspace path override (orchestrated mode, relative to hermes home)")
|
|
240
|
-
.action(async (id, opts) => {
|
|
241
|
-
const hermesHome = process.env.HERMES_HOME;
|
|
242
|
-
const cwd = process.cwd();
|
|
243
|
-
const insideContainer = Boolean(hermesHome && cwd.startsWith(hermesHome));
|
|
244
|
-
// ── Direct mode: pack & upload a local directory ──
|
|
245
|
-
if (opts.dir || (!insideContainer && !opts.workspacePath)) {
|
|
246
|
-
const dir = resolve(opts.dir ?? cwd);
|
|
247
|
-
if (!looksLikeProject(dir)) {
|
|
248
|
-
agentOutput({
|
|
249
|
-
action: "apps.deploy",
|
|
250
|
-
result: { error: `No package.json or index.html found in ${dir}` },
|
|
251
|
-
next_steps: [
|
|
252
|
-
{ command: "naive apps templates", description: "Get the starter template clone command" },
|
|
253
|
-
{ command: `naive apps deploy ${id} --dir <project-path>`, description: "Point at your project directory" },
|
|
254
|
-
],
|
|
255
|
-
hints: ["Run this command inside your project directory (or pass --dir)."],
|
|
256
|
-
});
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
const tarball = packDirectory(dir);
|
|
260
|
-
const resp = await apiUploadGzip(`/v1/apps/${id}/deploy`, tarball);
|
|
261
|
-
handleApiError("apps.deploy", resp);
|
|
262
|
-
const data = resp.data;
|
|
263
|
-
agentOutput({
|
|
264
|
-
action: "apps.deploy",
|
|
265
|
-
result: resp.data,
|
|
266
|
-
next_steps: [
|
|
267
|
-
{ command: `naive apps publish ${id} --deployment ${data.id}`, description: "Promote to production" },
|
|
268
|
-
{ command: `naive apps deployments ${id}`, description: "View all deployments" },
|
|
269
|
-
],
|
|
270
|
-
hints: [
|
|
271
|
-
`Uploaded ${Math.round(tarball.length / 1024)}KB from ${dir}`,
|
|
272
|
-
`Preview deployment: ${data.url}`,
|
|
273
|
-
],
|
|
274
|
-
related_commands: ["naive apps publish", "naive apps deployments"],
|
|
275
|
-
});
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
// ── Orchestrated mode: deploy from the container workspace ──
|
|
279
|
-
const body = {};
|
|
280
|
-
if (opts.workspacePath) {
|
|
281
|
-
body.workspacePath = opts.workspacePath;
|
|
282
|
-
}
|
|
283
|
-
else if (insideContainer) {
|
|
284
|
-
// Auto-detect: if CWD contains an app/ dir, send the relative path so
|
|
285
|
-
// the API deploys from the right location
|
|
286
|
-
if (existsSync(join(cwd, "app"))) {
|
|
287
|
-
body.workspacePath = cwd.slice(hermesHome.length + 1);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const resp = await apiRequest("POST", `/v1/apps/${id}/deploy`, Object.keys(body).length > 0 ? body : undefined);
|
|
291
|
-
handleApiError("apps.deploy", resp);
|
|
292
|
-
const data = resp.data;
|
|
293
|
-
agentOutput({
|
|
294
|
-
action: "apps.deploy",
|
|
295
|
-
result: resp.data,
|
|
296
|
-
next_steps: [
|
|
297
|
-
{ command: `naive apps publish ${id} --deployment ${data.id}`, description: "Promote to production" },
|
|
298
|
-
{ command: `naive apps deployments ${id}`, description: "View all deployments" },
|
|
299
|
-
],
|
|
300
|
-
hints: [`Preview deployment: ${data.url}`],
|
|
301
|
-
related_commands: ["naive apps publish", "naive apps deployments"],
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
// ─── Publish ─────────────────────────────────────────────────────────────────
|
|
305
|
-
appsCmd
|
|
306
|
-
.command("publish <id>")
|
|
307
|
-
.description("Promote a deployment to production")
|
|
308
|
-
.requiredOption("--deployment <deploymentId>", "Deployment ID to promote")
|
|
309
|
-
.action(async (id, opts) => {
|
|
310
|
-
const resp = await apiRequest("POST", `/v1/apps/${id}/publish`, {
|
|
311
|
-
deploymentId: opts.deployment,
|
|
312
|
-
});
|
|
313
|
-
handleApiError("apps.publish", resp);
|
|
314
|
-
const data = resp.data;
|
|
315
|
-
agentOutput({
|
|
316
|
-
action: "apps.publish",
|
|
317
|
-
result: resp.data,
|
|
318
|
-
next_steps: [
|
|
319
|
-
{ command: `naive apps show ${id}`, description: "View updated app details" },
|
|
320
|
-
{ command: `naive apps domains connect ${id} <domainId>`, description: "Connect a custom domain" },
|
|
321
|
-
],
|
|
322
|
-
hints: [`Production URL: ${data.url}`],
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
// ─── Deployments ─────────────────────────────────────────────────────────────
|
|
326
|
-
appsCmd
|
|
327
|
-
.command("deployments <id>")
|
|
328
|
-
.description("List deployment history for an app")
|
|
329
|
-
.action(async (id) => {
|
|
330
|
-
const resp = await apiRequest("GET", `/v1/apps/${id}/deployments`);
|
|
331
|
-
handleApiError("apps.deployments", resp);
|
|
332
|
-
const data = resp.data;
|
|
333
|
-
const count = data.deployments?.length ?? 0;
|
|
334
|
-
agentOutput({
|
|
335
|
-
action: "apps.deployments",
|
|
336
|
-
result: resp.data,
|
|
337
|
-
next_steps: count > 0
|
|
338
|
-
? [{ command: `naive apps publish ${id} --deployment ${data.deployments[0].id}`, description: "Promote latest to production" }]
|
|
339
|
-
: [{ command: `naive apps deploy ${id}`, description: "Trigger a deployment" }],
|
|
340
|
-
hints: [`${count} deployment${count !== 1 ? "s" : ""}`],
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
// ─── Secrets ─────────────────────────────────────────────────────────────────
|
|
344
|
-
const secretsCmd = appsCmd
|
|
345
|
-
.command("secrets")
|
|
346
|
-
.description("Manage app environment variables");
|
|
347
|
-
secretsCmd
|
|
348
|
-
.command("list <appId>")
|
|
349
|
-
.description("List environment variable keys (values are hidden)")
|
|
350
|
-
.action(async (appId) => {
|
|
351
|
-
const resp = await apiRequest("GET", `/v1/apps/${appId}/secrets`);
|
|
352
|
-
handleApiError("apps.secrets.list", resp);
|
|
353
|
-
agentOutput({
|
|
354
|
-
action: "apps.secrets.list",
|
|
355
|
-
result: resp.data,
|
|
356
|
-
next_steps: [
|
|
357
|
-
{ command: `naive apps secrets set ${appId} MY_KEY my_value --target preview`, description: "Add a variable" },
|
|
358
|
-
],
|
|
359
|
-
related_commands: ["naive apps secrets set", "naive apps secrets delete", "naive apps secrets reveal"],
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
secretsCmd
|
|
363
|
-
.command("set <appId> <key> <value>")
|
|
364
|
-
.description("Create or update an environment variable")
|
|
365
|
-
.requiredOption("--target <target>", "Target: preview or production")
|
|
366
|
-
.action(async (appId, key, value, opts) => {
|
|
367
|
-
const resp = await apiRequest("POST", `/v1/apps/${appId}/secrets`, {
|
|
368
|
-
key,
|
|
369
|
-
value,
|
|
370
|
-
target: opts.target,
|
|
371
|
-
});
|
|
372
|
-
handleApiError("apps.secrets.set", resp);
|
|
373
|
-
agentOutput({
|
|
374
|
-
action: "apps.secrets.set",
|
|
375
|
-
result: resp.data,
|
|
376
|
-
next_steps: [
|
|
377
|
-
{ command: `naive apps secrets list ${appId}`, description: "View all variables" },
|
|
378
|
-
{ command: `naive apps deploy ${appId}`, description: "Redeploy to apply changes" },
|
|
379
|
-
],
|
|
380
|
-
hints: [`Set ${key} for ${opts.target}. Redeploy for changes to take effect.`],
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
secretsCmd
|
|
384
|
-
.command("delete <appId> <key>")
|
|
385
|
-
.description("Delete an environment variable")
|
|
386
|
-
.requiredOption("--target <target>", "Target: preview or production")
|
|
387
|
-
.action(async (appId, key, opts) => {
|
|
388
|
-
const resp = await apiRequest("DELETE", `/v1/apps/${appId}/secrets/${encodeURIComponent(key)}?target=${opts.target}`);
|
|
389
|
-
handleApiError("apps.secrets.delete", resp);
|
|
390
|
-
agentOutput({
|
|
391
|
-
action: "apps.secrets.delete",
|
|
392
|
-
result: resp.data,
|
|
393
|
-
next_steps: [{ command: `naive apps secrets list ${appId}`, description: "View remaining variables" }],
|
|
394
|
-
hints: [`Deleted ${key} from ${opts.target}`],
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
secretsCmd
|
|
398
|
-
.command("reveal <appId> <key>")
|
|
399
|
-
.description("Reveal the value of an environment variable")
|
|
400
|
-
.option("--target <target>", "Target: preview or production (default: production)", "production")
|
|
401
|
-
.action(async (appId, key, opts) => {
|
|
402
|
-
// A typo must NOT silently fall through to production. `reveal` is the one
|
|
403
|
-
// secrets verb with plaintext egress, so `--target prevew` answering with the
|
|
404
|
-
// PRODUCTION value would hand the caller a secret it did not ask for.
|
|
405
|
-
const target = String(opts.target ?? "production");
|
|
406
|
-
if (target !== "preview" && target !== "production") {
|
|
407
|
-
agentError({
|
|
408
|
-
action: "apps.secrets.reveal",
|
|
409
|
-
error: {
|
|
410
|
-
code: "invalid_input",
|
|
411
|
-
message: `Invalid --target "${target}". Use preview or production.`,
|
|
412
|
-
},
|
|
413
|
-
recovery_steps: [
|
|
414
|
-
{
|
|
415
|
-
command: `naive apps secrets reveal ${appId} ${key} --target production`,
|
|
416
|
-
description: "Reveal the production value (default)",
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
command: `naive apps secrets reveal ${appId} ${key} --target preview`,
|
|
420
|
-
description: "Reveal the preview value",
|
|
421
|
-
},
|
|
422
|
-
],
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
const resp = await apiRequest("GET", `/v1/apps/${appId}/secrets/${encodeURIComponent(key)}/reveal?target=${target}`);
|
|
426
|
-
handleApiError("apps.secrets.reveal", resp);
|
|
427
|
-
agentOutput({
|
|
428
|
-
action: "apps.secrets.reveal",
|
|
429
|
-
result: resp.data,
|
|
430
|
-
next_steps: [
|
|
431
|
-
{ command: `naive apps secrets set ${appId} ${key} <new-value> --target ${target}`, description: "Update this variable" },
|
|
432
|
-
],
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
// ─── Domains ─────────────────────────────────────────────────────────────────
|
|
436
|
-
const domainsCmd = appsCmd
|
|
437
|
-
.command("domains")
|
|
438
|
-
.description("Manage app domains");
|
|
439
|
-
domainsCmd
|
|
440
|
-
.command("list <appId>")
|
|
441
|
-
.description("List domains attached to an app")
|
|
442
|
-
.action(async (appId) => {
|
|
443
|
-
const resp = await apiRequest("GET", `/v1/apps/${appId}/domains`);
|
|
444
|
-
handleApiError("apps.domains.list", resp);
|
|
445
|
-
agentOutput({
|
|
446
|
-
action: "apps.domains.list",
|
|
447
|
-
result: resp.data,
|
|
448
|
-
next_steps: [
|
|
449
|
-
{ command: `naive apps domains add ${appId} example.com`, description: "Add a custom domain" },
|
|
450
|
-
{ command: `naive apps domains connect ${appId} <domainId>`, description: "Connect a company domain" },
|
|
451
|
-
],
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
domainsCmd
|
|
455
|
-
.command("add <appId> <domain>")
|
|
456
|
-
.description("Add a custom domain to an app")
|
|
457
|
-
.action(async (appId, domain) => {
|
|
458
|
-
const resp = await apiRequest("POST", `/v1/apps/${appId}/domains`, { domain });
|
|
459
|
-
handleApiError("apps.domains.add", resp);
|
|
460
|
-
agentOutput({
|
|
461
|
-
action: "apps.domains.add",
|
|
462
|
-
result: resp.data,
|
|
463
|
-
next_steps: [{ command: `naive apps domains list ${appId}`, description: "View all domains" }],
|
|
464
|
-
hints: [`Added domain ${domain}. Configure DNS records to point to Vercel.`],
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
domainsCmd
|
|
468
|
-
.command("remove <appId> <domainId>")
|
|
469
|
-
.description("Remove a domain from an app")
|
|
470
|
-
.action(async (appId, domainId) => {
|
|
471
|
-
const resp = await apiRequest("DELETE", `/v1/apps/${appId}/domains/${domainId}`);
|
|
472
|
-
handleApiError("apps.domains.remove", resp);
|
|
473
|
-
agentOutput({
|
|
474
|
-
action: "apps.domains.remove",
|
|
475
|
-
result: resp.data,
|
|
476
|
-
next_steps: [{ command: `naive apps domains list ${appId}`, description: "View remaining domains" }],
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
domainsCmd
|
|
480
|
-
.command("set-primary <appId> <domainId>")
|
|
481
|
-
.description("Set a domain as the app's primary production domain")
|
|
482
|
-
.action(async (appId, domainId) => {
|
|
483
|
-
const resp = await apiRequest("POST", `/v1/apps/${appId}/domains/${domainId}/set-primary`);
|
|
484
|
-
handleApiError("apps.domains.set-primary", resp);
|
|
485
|
-
agentOutput({
|
|
486
|
-
action: "apps.domains.set-primary",
|
|
487
|
-
result: resp.data,
|
|
488
|
-
next_steps: [{ command: `naive apps domains list ${appId}`, description: "View updated domains" }],
|
|
489
|
-
hints: ["Primary domain updated. The next publish will alias production to this domain."],
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
domainsCmd
|
|
493
|
-
.command("connect <appId> <domainId>")
|
|
494
|
-
.description("Connect a company-owned domain to an app as its production domain")
|
|
495
|
-
.action(async (appId, domainId) => {
|
|
496
|
-
const resp = await apiRequest("POST", `/v1/apps/${appId}/connect-domain`, { domainId });
|
|
497
|
-
handleApiError("apps.domains.connect", resp);
|
|
498
|
-
const data = resp.data;
|
|
499
|
-
const isSystem = /\.usenaive\.ai$/i.test(data.domain ?? "");
|
|
500
|
-
agentOutput({
|
|
501
|
-
action: "apps.domains.connect",
|
|
502
|
-
result: resp.data,
|
|
503
|
-
next_steps: data.pendingDns
|
|
504
|
-
? [{ command: `naive apps domains verify-dns ${appId} ${domainId}`, description: "Verify DNS after configuring records" }]
|
|
505
|
-
: [{ command: `naive apps show ${appId}`, description: "View updated app" }],
|
|
506
|
-
hints: data.pendingDns
|
|
507
|
-
? isSystem
|
|
508
|
-
? [`System domain ${data.domain}: platform writes DNS on usenaive.ai. Retry verify-dns shortly if still pending.`]
|
|
509
|
-
: [`DNS records required for ${data.domain}. Add A and CNAME records, then verify.`]
|
|
510
|
-
: [`Domain ${data.domain} connected successfully`],
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
domainsCmd
|
|
514
|
-
.command("disconnect <appId> <domainId>")
|
|
515
|
-
.description("Disconnect a company domain from an app")
|
|
516
|
-
.action(async (appId, domainId) => {
|
|
517
|
-
const resp = await apiRequest("DELETE", `/v1/apps/${appId}/connect-domain/${domainId}`);
|
|
518
|
-
handleApiError("apps.domains.disconnect", resp);
|
|
519
|
-
agentOutput({
|
|
520
|
-
action: "apps.domains.disconnect",
|
|
521
|
-
result: resp.data,
|
|
522
|
-
next_steps: [{ command: `naive apps show ${appId}`, description: "View updated app" }],
|
|
523
|
-
});
|
|
524
|
-
});
|
|
525
|
-
domainsCmd
|
|
526
|
-
.command("verify-dns <appId> <domainId>")
|
|
527
|
-
.description("Verify DNS configuration for a connected domain")
|
|
528
|
-
.action(async (appId, domainId) => {
|
|
529
|
-
const resp = await apiRequest("POST", `/v1/apps/${appId}/verify-domain-dns`, { domainId });
|
|
530
|
-
handleApiError("apps.domains.verify-dns", resp);
|
|
531
|
-
const data = resp.data;
|
|
532
|
-
agentOutput({
|
|
533
|
-
action: "apps.domains.verify-dns",
|
|
534
|
-
result: resp.data,
|
|
535
|
-
next_steps: data.verified
|
|
536
|
-
? [{ command: `naive apps show ${appId}`, description: "View app with verified domain" }]
|
|
537
|
-
: [{ command: `naive apps domains verify-dns ${appId} ${domainId}`, description: "Try again after DNS propagation (up to 48h)" }],
|
|
538
|
-
hints: data.verified
|
|
539
|
-
? [`Domain ${data.domain} verified and connected`]
|
|
540
|
-
: [`DNS not ready for ${data.domain}. Propagation can take up to 48 hours.`],
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
// ─── Retry ──────────────────────────────────────────────────────────────────
|
|
544
|
-
appsCmd
|
|
545
|
-
.command("retry <id>")
|
|
546
|
-
.description("Retry failed provisioning (Vercel/Supabase); no-op success if the app is already healthy")
|
|
547
|
-
.action(async (id) => {
|
|
548
|
-
const resp = await apiRequest("POST", `/v1/apps/${id}/retry`);
|
|
549
|
-
handleApiError("apps.retry", resp);
|
|
550
|
-
agentOutput({
|
|
551
|
-
action: "apps.retry",
|
|
552
|
-
result: resp.data,
|
|
553
|
-
next_steps: [
|
|
554
|
-
{ command: `naive apps show ${id}`, description: "Check provisioning status" },
|
|
555
|
-
],
|
|
556
|
-
hints: ["Retrying provisioning — check status in a few seconds"],
|
|
557
|
-
related_commands: ["naive apps show", "naive apps deploy"],
|
|
558
|
-
});
|
|
559
|
-
});
|
|
560
|
-
// ─── Database (fullstack apps) ──────────────────────────────────────────────
|
|
561
|
-
const dbCmd = appsCmd
|
|
562
|
-
.command("db")
|
|
563
|
-
.description("Database operations for fullstack apps (Supabase)");
|
|
564
|
-
dbCmd
|
|
565
|
-
.command("tables <appId>")
|
|
566
|
-
.description("List database tables for a fullstack app")
|
|
567
|
-
.action(async (appId) => {
|
|
568
|
-
const resp = await apiRequest("GET", `/v1/apps/${appId}/db/tables`);
|
|
569
|
-
handleApiError("apps.db.tables", resp);
|
|
570
|
-
agentOutput({
|
|
571
|
-
action: "apps.db.tables",
|
|
572
|
-
result: resp.data,
|
|
573
|
-
next_steps: [
|
|
574
|
-
{ command: `naive apps db query ${appId} "SELECT * FROM <table> LIMIT 10"`, description: "Query a table" },
|
|
575
|
-
],
|
|
576
|
-
related_commands: ["naive apps db query"],
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
|
-
dbCmd
|
|
580
|
-
// [sqlParts...] so unquoted multi-word SQL (agents / probe wrappers that
|
|
581
|
-
// word-split) still reaches the API as one query string.
|
|
582
|
-
.command("query <appId> [sqlParts...]")
|
|
583
|
-
.description("Run a SQL query against the app's Supabase database")
|
|
584
|
-
.action(async (appId, sqlParts) => {
|
|
585
|
-
const sql = (sqlParts ?? []).join(" ").trim();
|
|
586
|
-
if (!sql) {
|
|
587
|
-
agentError({
|
|
588
|
-
action: "apps.db.query",
|
|
589
|
-
error: {
|
|
590
|
-
code: "invalid_input",
|
|
591
|
-
message: "Missing SQL. Usage: naive apps db query <appId> \"<sql>\"",
|
|
592
|
-
},
|
|
593
|
-
recovery_steps: [
|
|
594
|
-
{ command: `naive apps db query ${appId} "SELECT 1"`, description: "Run a quoted SQL query" },
|
|
595
|
-
],
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
const resp = await apiRequest("POST", `/v1/apps/${appId}/db/query`, { sql });
|
|
599
|
-
handleApiError("apps.db.query", resp);
|
|
600
|
-
agentOutput({
|
|
601
|
-
action: "apps.db.query",
|
|
602
|
-
result: resp.data,
|
|
603
|
-
next_steps: [
|
|
604
|
-
{ command: `naive apps db tables ${appId}`, description: "List available tables" },
|
|
605
|
-
],
|
|
606
|
-
related_commands: ["naive apps db tables"],
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
// ─── Provider Proxies (advanced) ─────────────────────────────────────────────
|
|
610
|
-
/**
|
|
611
|
-
* Scan an unquoted JSON value that may contain nested `()`, `{}`, `[]` and
|
|
612
|
-
* embedded quotes (e.g. edge-function `content:Deno.serve(() => …)` after
|
|
613
|
-
* shlex strips outer quotes — dogfood f877 B64).
|
|
614
|
-
*
|
|
615
|
-
* Agents often leave JSON-style `\"` inside the unquoted blob
|
|
616
|
-
* (`run:\"f877\"`). Treat those as literal `"` in the value — do not enter
|
|
617
|
-
* string mode on the escaped quote, or brace/paren depth tracking breaks.
|
|
618
|
-
*/
|
|
619
|
-
function scanUnquotedValue(input, start) {
|
|
620
|
-
let i = start;
|
|
621
|
-
let depthParen = 0;
|
|
622
|
-
let depthBrace = 0;
|
|
623
|
-
let depthBracket = 0;
|
|
624
|
-
let inStr = null;
|
|
625
|
-
let escape = false;
|
|
626
|
-
while (i < input.length) {
|
|
627
|
-
const c = input[i];
|
|
628
|
-
if (inStr) {
|
|
629
|
-
if (escape) {
|
|
630
|
-
escape = false;
|
|
631
|
-
i++;
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
if (c === "\\") {
|
|
635
|
-
escape = true;
|
|
636
|
-
i++;
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
if (c === inStr)
|
|
640
|
-
inStr = null;
|
|
641
|
-
i++;
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
// JSON-escaped quotes left after shlex strip → literal quote chars
|
|
645
|
-
if (c === "\\" && (input[i + 1] === '"' || input[i + 1] === "'")) {
|
|
646
|
-
i += 2;
|
|
647
|
-
continue;
|
|
648
|
-
}
|
|
649
|
-
if (c === '"' || c === "'") {
|
|
650
|
-
inStr = c;
|
|
651
|
-
i++;
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (c === "(") {
|
|
655
|
-
depthParen++;
|
|
656
|
-
i++;
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
if (c === ")") {
|
|
660
|
-
depthParen--;
|
|
661
|
-
i++;
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
if (c === "{") {
|
|
665
|
-
depthBrace++;
|
|
666
|
-
i++;
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
if (c === "}") {
|
|
670
|
-
if (depthBrace === 0 && depthParen === 0 && depthBracket === 0)
|
|
671
|
-
break;
|
|
672
|
-
depthBrace--;
|
|
673
|
-
i++;
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
if (c === "[") {
|
|
677
|
-
depthBracket++;
|
|
678
|
-
i++;
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (c === "]") {
|
|
682
|
-
if (depthBrace === 0 && depthParen === 0 && depthBracket === 0)
|
|
683
|
-
break;
|
|
684
|
-
depthBracket--;
|
|
685
|
-
i++;
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
if (c === "," && depthBrace === 0 && depthParen === 0 && depthBracket === 0)
|
|
689
|
-
break;
|
|
690
|
-
i++;
|
|
691
|
-
}
|
|
692
|
-
const raw = input.slice(start, i).trimEnd();
|
|
693
|
-
// Turn leftover `\"` / `\'` into real quotes in the scalar value
|
|
694
|
-
const value = raw.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
695
|
-
return { value, end: i };
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* After quoting keys, quote remaining unquoted scalar values — including
|
|
699
|
-
* code blobs with parens/braces that the simple `\w` regex cannot match.
|
|
700
|
-
*/
|
|
701
|
-
function quoteUnquotedValues(input) {
|
|
702
|
-
const out = [];
|
|
703
|
-
let i = 0;
|
|
704
|
-
let inString = false;
|
|
705
|
-
let escape = false;
|
|
706
|
-
while (i < input.length) {
|
|
707
|
-
const c = input[i];
|
|
708
|
-
if (inString) {
|
|
709
|
-
out.push(c);
|
|
710
|
-
if (escape)
|
|
711
|
-
escape = false;
|
|
712
|
-
else if (c === "\\")
|
|
713
|
-
escape = true;
|
|
714
|
-
else if (c === '"')
|
|
715
|
-
inString = false;
|
|
716
|
-
i++;
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
if (c === '"') {
|
|
720
|
-
inString = true;
|
|
721
|
-
out.push(c);
|
|
722
|
-
i++;
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
if (c === ":") {
|
|
726
|
-
out.push(c);
|
|
727
|
-
i++;
|
|
728
|
-
while (i < input.length && /\s/.test(input[i])) {
|
|
729
|
-
out.push(input[i]);
|
|
730
|
-
i++;
|
|
731
|
-
}
|
|
732
|
-
if (i >= input.length)
|
|
733
|
-
break;
|
|
734
|
-
const rest = input.slice(i);
|
|
735
|
-
if (rest.startsWith('"') ||
|
|
736
|
-
rest.startsWith("{") ||
|
|
737
|
-
rest.startsWith("[") ||
|
|
738
|
-
/^(true|false|null)(?=[,}\]\s]|$)/.test(rest) ||
|
|
739
|
-
/^-?\d+(\.\d+)?([eE][+-]?\d+)?(?=[,}\]\s]|$)/.test(rest)) {
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
const { value, end } = scanUnquotedValue(input, i);
|
|
743
|
-
out.push(JSON.stringify(value));
|
|
744
|
-
i = end;
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
747
|
-
out.push(c);
|
|
748
|
-
i++;
|
|
749
|
-
}
|
|
750
|
-
return out.join("");
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Parse --body JSON. Agents / shlex often strip quotes from
|
|
754
|
-
* `{"a":"b"}` → `{a:b}`, and word-split leave `{` `a:b` `}` tokens.
|
|
755
|
-
* Join tokens, try JSON.parse, then repair unquoted keys/values (including
|
|
756
|
-
* nested edge-function `files[].content` source strings).
|
|
757
|
-
*/
|
|
758
|
-
export function parseJsonBody(raw, flag) {
|
|
759
|
-
if (raw == null)
|
|
760
|
-
return undefined;
|
|
761
|
-
const text = (Array.isArray(raw) ? raw.join(" ") : String(raw)).trim();
|
|
762
|
-
if (!text)
|
|
763
|
-
return undefined;
|
|
764
|
-
try {
|
|
765
|
-
return JSON.parse(text);
|
|
766
|
-
}
|
|
767
|
-
catch {
|
|
768
|
-
// no-op — try repair below
|
|
769
|
-
}
|
|
770
|
-
try {
|
|
771
|
-
const withQuotedKeys = text.replace(/([{\[,]\s*)([A-Za-z_][\w]*)(\s*:)/g, '$1"$2"$3');
|
|
772
|
-
const repaired = quoteUnquotedValues(withQuotedKeys);
|
|
773
|
-
return JSON.parse(repaired);
|
|
774
|
-
}
|
|
775
|
-
catch {
|
|
776
|
-
console.error(`Invalid JSON passed to ${flag}`);
|
|
777
|
-
process.exit(1);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
appsCmd
|
|
781
|
-
.command("vercel <appId> <method> <path>")
|
|
782
|
-
.description("Call any Vercel REST API operation, scoped to the app's Vercel project (credentials injected by Naive)")
|
|
783
|
-
.option("--body <json...>", "JSON request body (unquoted / word-split OK for flat objects)")
|
|
784
|
-
.addHelpText("after", `
|
|
785
|
-
Examples:
|
|
786
|
-
naive apps vercel <id> GET v6/deployments List deployments
|
|
787
|
-
naive apps vercel <id> GET "v13/deployments/<deploymentId>" Deployment status
|
|
788
|
-
naive apps vercel <id> GET "v3/deployments/<deploymentId>/events" Build logs
|
|
789
|
-
naive apps vercel <id> GET "v9/projects/<projectId>/domains" Project domains
|
|
790
|
-
naive apps vercel <id> PATCH "v9/projects/<projectId>" --body '{"buildCommand":"next build"}'
|
|
791
|
-
|
|
792
|
-
Scope: only this app's project (vN/projects/{projectId}/**) and its
|
|
793
|
-
deployments (vN/deployments/**) are reachable. Deleting the project itself is
|
|
794
|
-
blocked — use 'naive apps delete'. See https://vercel.com/docs/rest-api.
|
|
795
|
-
`)
|
|
796
|
-
.action(async (appId, method, path, opts) => {
|
|
797
|
-
const body = parseJsonBody(opts.body, "--body");
|
|
798
|
-
const resp = await apiRequest(method.toUpperCase(), `/v1/apps/${appId}/vercel/proxy/${path.replace(/^\/+/, "")}`, body);
|
|
799
|
-
handleApiError("apps.vercel", resp);
|
|
800
|
-
agentOutput({
|
|
801
|
-
action: "apps.vercel",
|
|
802
|
-
result: resp.data,
|
|
803
|
-
next_steps: [{ command: `naive apps show ${appId}`, description: "View app details" }],
|
|
804
|
-
hints: [`Proxied ${method.toUpperCase()} ${path} to the Vercel API`],
|
|
805
|
-
related_commands: ["naive apps supabase", "naive apps show"],
|
|
806
|
-
});
|
|
807
|
-
});
|
|
808
|
-
appsCmd
|
|
809
|
-
.command("supabase <appId> <method> <path>")
|
|
810
|
-
.description("Call any Supabase Management API operation, scoped to the app's Supabase project (fullstack apps only)")
|
|
811
|
-
.option("--body <json...>", "JSON request body (unquoted / word-split OK for flat objects)")
|
|
812
|
-
.addHelpText("after", `
|
|
813
|
-
Examples:
|
|
814
|
-
naive apps supabase <id> GET "v1/projects/<ref>" Project details
|
|
815
|
-
naive apps supabase <id> GET "v1/projects/<ref>/config/auth" Auth config
|
|
816
|
-
naive apps supabase <id> PATCH "v1/projects/<ref>/config/auth" --body '{"site_url":"https://example.com"}'
|
|
817
|
-
naive apps supabase <id> GET "v1/projects/<ref>/functions" List edge functions
|
|
818
|
-
naive apps supabase <id> POST "v1/projects/<ref>/functions" --body '{"slug":"hello","name":"hello","verify_jwt":false,"body":"Deno.serve(() => new Response(\\"ok\\"))"}'
|
|
819
|
-
naive apps supabase <id> POST "v1/projects/<ref>/functions/deploy?slug=hello" --body '{"name":"hello","verify_jwt":false,"files":[{"name":"index.ts","content":"Deno.serve(() => new Response(\\"ok\\"))"}]}'
|
|
820
|
-
naive apps supabase <id> POST "v1/projects/<ref>/database/query" --body '{"query":"select * from users limit 10"}'
|
|
821
|
-
|
|
822
|
-
Edge functions:
|
|
823
|
-
- Create (JSON): POST …/functions with slug, name, and a source \`body\` string.
|
|
824
|
-
- Deploy/update (preferred): POST …/functions/deploy?slug=<slug> with files:[{name,content}].
|
|
825
|
-
The proxy converts that JSON into multipart file parts (one per source file).
|
|
826
|
-
Docs: /docs/api-reference/functions/overview
|
|
827
|
-
|
|
828
|
-
Scope: only this app's project (v1/projects/{ref}/**) is reachable. Deleting
|
|
829
|
-
the project itself is blocked — use 'naive apps delete'. See
|
|
830
|
-
https://supabase.com/docs/reference/api.
|
|
831
|
-
`)
|
|
832
|
-
.action(async (appId, method, path, opts) => {
|
|
833
|
-
const body = parseJsonBody(opts.body, "--body");
|
|
834
|
-
const resp = await apiRequest(method.toUpperCase(), `/v1/apps/${appId}/supabase/proxy/${path.replace(/^\/+/, "")}`, body);
|
|
835
|
-
handleApiError("apps.supabase", resp);
|
|
836
|
-
agentOutput({
|
|
837
|
-
action: "apps.supabase",
|
|
838
|
-
result: resp.data,
|
|
839
|
-
next_steps: [{ command: `naive apps show ${appId}`, description: "View app details" }],
|
|
840
|
-
hints: [`Proxied ${method.toUpperCase()} ${path} to the Supabase Management API`],
|
|
841
|
-
related_commands: ["naive apps vercel", "naive apps db query"],
|
|
842
|
-
});
|
|
843
|
-
});
|
|
844
|
-
//# sourceMappingURL=apps.js.map
|