@blinkdotnew/cli 0.3.7 → 0.4.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 +152 -321
- package/dist/cli.js +1536 -78
- package/package.json +28 -2
- package/src/cli.ts +88 -1
- package/src/commands/auth-config.ts +129 -0
- package/src/commands/backend.ts +175 -0
- package/src/commands/billing.ts +56 -0
- package/src/commands/domains.ts +211 -0
- package/src/commands/env.ts +215 -0
- package/src/commands/functions.ts +136 -0
- package/src/commands/hosting.ts +117 -0
- package/src/commands/init.ts +77 -0
- package/src/commands/security.ts +136 -0
- package/src/commands/tokens.ts +98 -0
- package/src/commands/versions.ts +95 -0
- package/src/commands/workspace.ts +130 -0
- package/src/lib/api-app.ts +7 -1
- package/src/lib/api-resources.ts +3 -0
- package/src/lib/auth.ts +6 -1
- package/src/lib/config.ts +2 -2
- package/src/lib/project.ts +7 -1
package/package.json
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blinkdotnew/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Blink
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Blink CLI — full-stack cloud infrastructure from your terminal. Deploy, database, auth, storage, backend, domains, and more.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"blink": "dist/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "./dist/cli.js",
|
|
9
9
|
"type": "module",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"blink",
|
|
12
|
+
"cli",
|
|
13
|
+
"deploy",
|
|
14
|
+
"hosting",
|
|
15
|
+
"backend",
|
|
16
|
+
"database",
|
|
17
|
+
"auth",
|
|
18
|
+
"storage",
|
|
19
|
+
"domains",
|
|
20
|
+
"ai",
|
|
21
|
+
"full-stack",
|
|
22
|
+
"infrastructure",
|
|
23
|
+
"cloud",
|
|
24
|
+
"vibe-coding",
|
|
25
|
+
"cursor",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"codex"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://blink.new/docs/cli",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/blink-new/blink-sdk",
|
|
33
|
+
"directory": "packages/cli"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
10
36
|
"scripts": {
|
|
11
37
|
"build": "tsup",
|
|
12
38
|
"dev": "node --loader tsx src/cli.ts",
|
package/src/cli.ts
CHANGED
|
@@ -17,6 +17,18 @@ import { registerProjectCommands } from './commands/project.js'
|
|
|
17
17
|
import { registerAuthCommands } from './commands/auth.js'
|
|
18
18
|
import { registerAgentCommands } from './commands/agent.js'
|
|
19
19
|
import { registerSecretsCommands } from './commands/secrets.js'
|
|
20
|
+
import { registerAuthConfigCommands } from './commands/auth-config.js'
|
|
21
|
+
import { registerDomainsCommands } from './commands/domains.js'
|
|
22
|
+
import { registerHostingCommands } from './commands/hosting.js'
|
|
23
|
+
import { registerSecurityCommands } from './commands/security.js'
|
|
24
|
+
import { registerEnvCommands } from './commands/env.js'
|
|
25
|
+
import { registerBackendCommands } from './commands/backend.js'
|
|
26
|
+
import { registerFunctionsCommands } from './commands/functions.js'
|
|
27
|
+
import { registerInitCommands } from './commands/init.js'
|
|
28
|
+
import { registerWorkspaceCommands } from './commands/workspace.js'
|
|
29
|
+
import { registerVersionCommands } from './commands/versions.js'
|
|
30
|
+
import { registerBillingCommands } from './commands/billing.js'
|
|
31
|
+
import { registerTokenCommands } from './commands/tokens.js'
|
|
20
32
|
|
|
21
33
|
const require = createRequire(import.meta.url)
|
|
22
34
|
const pkg = require('../package.json') as { version: string }
|
|
@@ -109,6 +121,70 @@ LinkedIn (dedicated commands for the LinkedIn connector):
|
|
|
109
121
|
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
110
122
|
Link LinkedIn at: blink.new/claw (Agent Integrations tab)
|
|
111
123
|
|
|
124
|
+
Environment Variables (project secrets):
|
|
125
|
+
$ blink env list List env var names
|
|
126
|
+
$ blink env set STRIPE_KEY sk_live_xxx Set an env var
|
|
127
|
+
$ blink env delete OLD_KEY Remove an env var
|
|
128
|
+
$ blink env push .env Bulk import from .env file
|
|
129
|
+
$ blink env pull > .env.local Export as KEY=VALUE
|
|
130
|
+
|
|
131
|
+
Backend (Hono server on CF Workers):
|
|
132
|
+
$ blink backend deploy ./backend Deploy backend/ to Cloudflare Workers
|
|
133
|
+
$ blink backend status Check backend status + URL
|
|
134
|
+
$ blink backend logs Tail backend request logs
|
|
135
|
+
$ blink backend delete --yes Remove backend
|
|
136
|
+
|
|
137
|
+
Auth Config (Blink Auth providers):
|
|
138
|
+
$ blink auth-config get Show auth configuration
|
|
139
|
+
$ blink auth-config set --provider google --enabled true
|
|
140
|
+
$ blink auth-config byoc-set --provider google --client-id X --client-secret Y
|
|
141
|
+
|
|
142
|
+
Domains:
|
|
143
|
+
$ blink domains list List custom domains
|
|
144
|
+
$ blink domains add myapp.com Add a custom domain
|
|
145
|
+
$ blink domains verify <domain_id> Verify DNS
|
|
146
|
+
$ blink domains search "myapp" Search available domains
|
|
147
|
+
$ blink domains purchase myapp.com Buy a domain
|
|
148
|
+
|
|
149
|
+
Hosting:
|
|
150
|
+
$ blink hosting status Check hosting state + URLs
|
|
151
|
+
$ blink hosting activate Activate production hosting
|
|
152
|
+
$ blink hosting deactivate --yes Deactivate hosting
|
|
153
|
+
|
|
154
|
+
Security & CORS:
|
|
155
|
+
$ blink security get Show per-module auth policy
|
|
156
|
+
$ blink security set --module db --require-auth true
|
|
157
|
+
$ blink cors get Show allowed CORS origins
|
|
158
|
+
$ blink cors set --origins https://example.com
|
|
159
|
+
|
|
160
|
+
Functions (legacy edge functions):
|
|
161
|
+
$ blink functions list List edge functions
|
|
162
|
+
$ blink functions logs index View function logs
|
|
163
|
+
$ blink functions delete old-fn --yes Delete a function
|
|
164
|
+
|
|
165
|
+
Versions:
|
|
166
|
+
$ blink versions list List saved versions
|
|
167
|
+
$ blink versions save --message "v1.0" Save a snapshot
|
|
168
|
+
$ blink versions restore ver_xxx --yes Restore to a version
|
|
169
|
+
|
|
170
|
+
Workspace:
|
|
171
|
+
$ blink workspace list List workspaces
|
|
172
|
+
$ blink workspace create "My Team" Create workspace
|
|
173
|
+
$ blink workspace members List members + roles
|
|
174
|
+
|
|
175
|
+
Billing:
|
|
176
|
+
$ blink credits Check credit balance
|
|
177
|
+
$ blink usage Usage breakdown
|
|
178
|
+
|
|
179
|
+
Tokens (Personal Access Tokens):
|
|
180
|
+
$ blink tokens list List PATs
|
|
181
|
+
$ blink tokens create --name "CI" Create PAT (shown once!)
|
|
182
|
+
$ blink tokens revoke <id> --yes Revoke a PAT
|
|
183
|
+
|
|
184
|
+
Init:
|
|
185
|
+
$ blink init Create project + link to current dir
|
|
186
|
+
$ blink init --name "My App" Create with custom name
|
|
187
|
+
|
|
112
188
|
Agents (Claw — zero config on Fly machines, BLINK_AGENT_ID is already set):
|
|
113
189
|
$ blink agent list List all agents in workspace
|
|
114
190
|
$ blink agent status Show current agent details
|
|
@@ -118,7 +194,6 @@ Secrets (encrypted agent vault — values never shown):
|
|
|
118
194
|
$ blink secrets list List secret key names
|
|
119
195
|
$ blink secrets set GITHUB_TOKEN ghp_xxx Add/update a secret
|
|
120
196
|
$ blink secrets delete OLD_KEY Remove a secret
|
|
121
|
-
$ blink secrets set --agent clw_other KEY v Set secret on another agent (agent manager pattern)
|
|
122
197
|
|
|
123
198
|
Tip — check full context (agent + project + auth):
|
|
124
199
|
$ blink status
|
|
@@ -152,6 +227,18 @@ registerConnectorCommands(program)
|
|
|
152
227
|
registerLinkedInCommands(program)
|
|
153
228
|
registerPhoneCommands(program)
|
|
154
229
|
registerSmsCommands(program)
|
|
230
|
+
registerAuthConfigCommands(program)
|
|
231
|
+
registerDomainsCommands(program)
|
|
232
|
+
registerHostingCommands(program)
|
|
233
|
+
registerSecurityCommands(program)
|
|
234
|
+
registerEnvCommands(program)
|
|
235
|
+
registerBackendCommands(program)
|
|
236
|
+
registerFunctionsCommands(program)
|
|
237
|
+
registerInitCommands(program)
|
|
238
|
+
registerWorkspaceCommands(program)
|
|
239
|
+
registerVersionCommands(program)
|
|
240
|
+
registerBillingCommands(program)
|
|
241
|
+
registerTokenCommands(program)
|
|
155
242
|
|
|
156
243
|
program.command('use <project_id>')
|
|
157
244
|
.description('Set active project for this shell session (alternative to blink link)')
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { appRequest } from '../lib/api-app.js'
|
|
3
|
+
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { requireProjectId } from '../lib/project.js'
|
|
5
|
+
import { printJson, isJsonMode, withSpinner, printSuccess, printKv } from '../lib/output.js'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
function printAuthConfig(auth: { mode?: string; providers?: Record<string, { enabled?: boolean }> }) {
|
|
9
|
+
printKv('Mode', auth.mode ?? 'redirect')
|
|
10
|
+
console.log()
|
|
11
|
+
console.log(chalk.bold('Providers:'))
|
|
12
|
+
for (const [name, cfg] of Object.entries(auth.providers ?? {})) {
|
|
13
|
+
const status = cfg.enabled ? chalk.green('enabled') : chalk.dim('disabled')
|
|
14
|
+
console.log(` ${name.padEnd(12)} ${status}`)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function registerAuthConfigCommands(program: Command) {
|
|
19
|
+
const authConfig = program.command('auth-config')
|
|
20
|
+
.description('Configure Blink Auth — providers, mode, and BYOC credentials')
|
|
21
|
+
.addHelpText('after', `
|
|
22
|
+
Manage authentication settings for your Blink project.
|
|
23
|
+
Configure OAuth providers (Google, GitHub, Microsoft, Apple, Email)
|
|
24
|
+
and set managed/headless mode.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ blink auth-config get Show current auth config
|
|
28
|
+
$ blink auth-config set --provider google --enabled true
|
|
29
|
+
$ blink auth-config set --mode managed
|
|
30
|
+
$ blink auth-config byoc-set --provider google --client-id xxx --client-secret yyy
|
|
31
|
+
$ blink auth-config byoc-remove --provider google
|
|
32
|
+
`)
|
|
33
|
+
|
|
34
|
+
authConfig.command('get [project_id]')
|
|
35
|
+
.description('Show current auth configuration')
|
|
36
|
+
.addHelpText('after', `
|
|
37
|
+
Examples:
|
|
38
|
+
$ blink auth-config get Show auth config for linked project
|
|
39
|
+
$ blink auth-config get proj_xxx Show auth config for specific project
|
|
40
|
+
$ blink auth-config get --json Machine-readable output
|
|
41
|
+
`)
|
|
42
|
+
.action(async (projectArg: string | undefined) => {
|
|
43
|
+
requireToken()
|
|
44
|
+
const projectId = requireProjectId(projectArg)
|
|
45
|
+
const result = await withSpinner('Loading auth config...', () =>
|
|
46
|
+
appRequest(`/api/projects/${projectId}/auth`)
|
|
47
|
+
)
|
|
48
|
+
if (isJsonMode()) return printJson(result)
|
|
49
|
+
const auth = result?.auth ?? result
|
|
50
|
+
printAuthConfig(auth)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
authConfig.command('set [project_id]')
|
|
54
|
+
.description('Update auth provider or mode')
|
|
55
|
+
.option('--provider <name>', 'Provider to update (google/github/microsoft/apple/email)')
|
|
56
|
+
.option('--enabled <bool>', 'Enable or disable provider (true/false)')
|
|
57
|
+
.option('--mode <mode>', 'Auth mode (managed/headless)')
|
|
58
|
+
.addHelpText('after', `
|
|
59
|
+
Examples:
|
|
60
|
+
$ blink auth-config set --provider google --enabled true
|
|
61
|
+
$ blink auth-config set --provider email --enabled false
|
|
62
|
+
$ blink auth-config set --mode managed
|
|
63
|
+
$ blink auth-config set proj_xxx --provider github --enabled true
|
|
64
|
+
`)
|
|
65
|
+
.action(async (projectArg: string | undefined, opts) => {
|
|
66
|
+
requireToken()
|
|
67
|
+
const projectId = requireProjectId(projectArg)
|
|
68
|
+
const current = await appRequest(`/api/projects/${projectId}/auth`)
|
|
69
|
+
const auth = current?.auth ?? current ?? {}
|
|
70
|
+
if (opts.mode) auth.mode = opts.mode
|
|
71
|
+
if (opts.provider && opts.enabled !== undefined) {
|
|
72
|
+
if (!auth.providers) auth.providers = {}
|
|
73
|
+
const existing = auth.providers[opts.provider]
|
|
74
|
+
const providerConfig = typeof existing === 'object' && existing ? existing : {}
|
|
75
|
+
auth.providers[opts.provider] = { ...providerConfig, enabled: opts.enabled === 'true' }
|
|
76
|
+
}
|
|
77
|
+
const result = await withSpinner('Updating auth config...', () =>
|
|
78
|
+
appRequest(`/api/projects/${projectId}/auth`, { method: 'PUT', body: auth })
|
|
79
|
+
)
|
|
80
|
+
if (isJsonMode()) return printJson(result)
|
|
81
|
+
printSuccess('Auth config updated')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
registerByocCommands(authConfig)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function registerByocCommands(authConfig: Command) {
|
|
88
|
+
authConfig.command('byoc-set [project_id]')
|
|
89
|
+
.description('Set BYOC (Bring Your Own Credentials) for an OAuth provider')
|
|
90
|
+
.requiredOption('--provider <name>', 'Provider (google/github/microsoft/apple)')
|
|
91
|
+
.requiredOption('--client-id <id>', 'OAuth client ID')
|
|
92
|
+
.requiredOption('--client-secret <secret>', 'OAuth client secret')
|
|
93
|
+
.addHelpText('after', `
|
|
94
|
+
Examples:
|
|
95
|
+
$ blink auth-config byoc-set --provider google --client-id xxx --client-secret yyy
|
|
96
|
+
$ blink auth-config byoc-set proj_xxx --provider github --client-id xxx --client-secret yyy
|
|
97
|
+
`)
|
|
98
|
+
.action(async (projectArg: string | undefined, opts) => {
|
|
99
|
+
requireToken()
|
|
100
|
+
const projectId = requireProjectId(projectArg)
|
|
101
|
+
const body = { provider: opts.provider, client_id: opts.clientId, client_secret: opts.clientSecret }
|
|
102
|
+
await withSpinner('Setting BYOC credentials...', () =>
|
|
103
|
+
appRequest(`/api/projects/${projectId}/auth/byoc`, { body })
|
|
104
|
+
)
|
|
105
|
+
if (isJsonMode()) return printJson({ status: 'ok', provider: opts.provider })
|
|
106
|
+
printSuccess(`BYOC credentials set for ${opts.provider}`)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
authConfig.command('byoc-remove [project_id]')
|
|
110
|
+
.description('Remove BYOC credentials for an OAuth provider')
|
|
111
|
+
.requiredOption('--provider <name>', 'Provider (google/github/microsoft/apple)')
|
|
112
|
+
.addHelpText('after', `
|
|
113
|
+
Examples:
|
|
114
|
+
$ blink auth-config byoc-remove --provider google
|
|
115
|
+
$ blink auth-config byoc-remove proj_xxx --provider github
|
|
116
|
+
`)
|
|
117
|
+
.action(async (projectArg: string | undefined, opts) => {
|
|
118
|
+
requireToken()
|
|
119
|
+
const projectId = requireProjectId(projectArg)
|
|
120
|
+
await withSpinner('Removing BYOC credentials...', () =>
|
|
121
|
+
appRequest(`/api/projects/${projectId}/auth/byoc`, {
|
|
122
|
+
method: 'DELETE',
|
|
123
|
+
body: { provider: opts.provider },
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
if (isJsonMode()) return printJson({ status: 'ok', provider: opts.provider })
|
|
127
|
+
printSuccess(`BYOC credentials removed for ${opts.provider}`)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { appRequest } from '../lib/api-app.js'
|
|
3
|
+
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { requireProjectId } from '../lib/project.js'
|
|
5
|
+
import { printJson, printSuccess, printKv, printUrl, isJsonMode, withSpinner, printError } from '../lib/output.js'
|
|
6
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
7
|
+
import { join, relative } from 'node:path'
|
|
8
|
+
import chalk from 'chalk'
|
|
9
|
+
|
|
10
|
+
const EXCLUDED_FILES = new Set(['package-lock.json', 'bun.lockb', 'yarn.lock', 'pnpm-lock.yaml'])
|
|
11
|
+
|
|
12
|
+
function collectBackendFiles(dir: string): Array<{ path: string; source_code: string }> {
|
|
13
|
+
const files: Array<{ path: string; source_code: string }> = []
|
|
14
|
+
function walk(current: string) {
|
|
15
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
16
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
|
|
17
|
+
const fullPath = join(current, entry.name)
|
|
18
|
+
if (entry.isDirectory()) { walk(fullPath); continue }
|
|
19
|
+
if (EXCLUDED_FILES.has(entry.name)) continue
|
|
20
|
+
const ext = entry.name.split('.').pop()?.toLowerCase() ?? ''
|
|
21
|
+
if (!['ts', 'js', 'tsx', 'jsx', 'json'].includes(ext)) continue
|
|
22
|
+
files.push({
|
|
23
|
+
path: join('backend', relative(dir, fullPath)),
|
|
24
|
+
source_code: readFileSync(fullPath, 'utf-8'),
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
walk(dir)
|
|
29
|
+
return files
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveProjectAndDir(arg1?: string, arg2?: string): { projectId: string; dir: string } {
|
|
33
|
+
if (arg2) return { projectId: requireProjectId(arg1), dir: arg2 }
|
|
34
|
+
if (arg1?.startsWith('proj_')) return { projectId: requireProjectId(arg1), dir: 'backend' }
|
|
35
|
+
return { projectId: requireProjectId(), dir: arg1 ?? 'backend' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function registerBackendCommands(program: Command) {
|
|
39
|
+
const backend = program.command('backend')
|
|
40
|
+
.description('Manage backend (CF Workers for Platforms) for your project')
|
|
41
|
+
.addHelpText('after', `
|
|
42
|
+
Deploy, monitor, and manage your project's backend worker.
|
|
43
|
+
The backend is a Hono server running on Cloudflare Workers for Platforms.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
$ blink backend deploy Deploy backend/ folder
|
|
47
|
+
$ blink backend deploy ./server Deploy from custom dir
|
|
48
|
+
$ blink backend status Check deployment status
|
|
49
|
+
$ blink backend logs View recent logs
|
|
50
|
+
$ blink backend delete --yes Remove backend worker
|
|
51
|
+
`)
|
|
52
|
+
|
|
53
|
+
registerBackendDeploy(backend)
|
|
54
|
+
registerBackendStatus(backend)
|
|
55
|
+
registerBackendLogs(backend)
|
|
56
|
+
registerBackendDelete(backend)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function registerBackendDeploy(backend: Command) {
|
|
60
|
+
backend.command('deploy [project_id] [dir]')
|
|
61
|
+
.description('Deploy backend files to CF Workers for Platforms')
|
|
62
|
+
.addHelpText('after', `
|
|
63
|
+
Reads all .ts/.js/.json files from the backend/ directory (or specified dir),
|
|
64
|
+
bundles and deploys them as a Cloudflare Worker.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
$ blink backend deploy Deploy backend/ for linked project
|
|
68
|
+
$ blink backend deploy ./server Deploy from custom directory
|
|
69
|
+
$ blink backend deploy proj_xxx Deploy backend/ for specific project
|
|
70
|
+
$ blink backend deploy proj_xxx ./api Deploy ./api for specific project
|
|
71
|
+
`)
|
|
72
|
+
.action(async (arg1?: string, arg2?: string) => {
|
|
73
|
+
requireToken()
|
|
74
|
+
const { projectId, dir } = resolveProjectAndDir(arg1, arg2)
|
|
75
|
+
if (!statSync(dir, { throwIfNoEntry: false })?.isDirectory()) {
|
|
76
|
+
printError(`Directory not found: ${dir}`, `Create a backend/ folder with index.ts`)
|
|
77
|
+
process.exit(1)
|
|
78
|
+
}
|
|
79
|
+
const files = collectBackendFiles(dir)
|
|
80
|
+
if (!files.length) {
|
|
81
|
+
printError(`No .ts/.js files found in ${dir}`)
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
84
|
+
if (!isJsonMode()) console.log(chalk.dim(` ${files.length} file${files.length === 1 ? '' : 's'} in ${dir}`))
|
|
85
|
+
const result = await withSpinner('Deploying backend...', () =>
|
|
86
|
+
appRequest(`/api/v1/projects/${projectId}/backend/deploy`, { body: { files } })
|
|
87
|
+
)
|
|
88
|
+
if (isJsonMode()) return printJson(result)
|
|
89
|
+
printSuccess('Backend deployed')
|
|
90
|
+
if (result?.url) printUrl('URL', result.url)
|
|
91
|
+
printKv('Project', projectId)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function registerBackendStatus(backend: Command) {
|
|
96
|
+
backend.command('status [project_id]')
|
|
97
|
+
.description('Check backend deployment status')
|
|
98
|
+
.addHelpText('after', `
|
|
99
|
+
Examples:
|
|
100
|
+
$ blink backend status
|
|
101
|
+
$ blink backend status proj_xxx
|
|
102
|
+
$ blink backend status --json
|
|
103
|
+
`)
|
|
104
|
+
.action(async (projectArg?: string) => {
|
|
105
|
+
requireToken()
|
|
106
|
+
const projectId = requireProjectId(projectArg)
|
|
107
|
+
const result = await withSpinner('Checking status...', () =>
|
|
108
|
+
appRequest(`/api/v1/projects/${projectId}/backend/status`)
|
|
109
|
+
)
|
|
110
|
+
if (isJsonMode()) return printJson(result)
|
|
111
|
+
printKv('Project', projectId)
|
|
112
|
+
printKv('Enabled', result?.enabled ? 'yes' : 'no')
|
|
113
|
+
printKv('Tier', result?.tier ?? '-')
|
|
114
|
+
printKv('Requests', String(result?.requests_this_month ?? 0))
|
|
115
|
+
if (result?.enabled) printUrl('URL', `https://${projectId.slice(-8)}.backend.blink.new`)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function registerBackendLogs(backend: Command) {
|
|
120
|
+
backend.command('logs [project_id]')
|
|
121
|
+
.description('View recent backend logs')
|
|
122
|
+
.option('--minutes <n>', 'Minutes of logs to fetch', '30')
|
|
123
|
+
.option('--slug <name>', 'Function slug', 'index')
|
|
124
|
+
.addHelpText('after', `
|
|
125
|
+
Examples:
|
|
126
|
+
$ blink backend logs Last 30 min of logs
|
|
127
|
+
$ blink backend logs --minutes 60 Last hour
|
|
128
|
+
$ blink backend logs proj_xxx --minutes 5
|
|
129
|
+
$ blink backend logs --json
|
|
130
|
+
`)
|
|
131
|
+
.action(async (projectArg: string | undefined, opts) => {
|
|
132
|
+
requireToken()
|
|
133
|
+
const projectId = requireProjectId(projectArg)
|
|
134
|
+
const slug = opts.slug ?? 'index'
|
|
135
|
+
const since = new Date(Date.now() - parseInt(opts.minutes ?? '30') * 60 * 1000).toISOString()
|
|
136
|
+
const result = await withSpinner('Fetching logs...', () =>
|
|
137
|
+
appRequest(`/api/v1/projects/${projectId}/functions/${slug}/logs?since=${since}`)
|
|
138
|
+
)
|
|
139
|
+
if (isJsonMode()) return printJson(result)
|
|
140
|
+
const logs: Array<{ timestamp?: string; method?: string; path?: string; status_code?: number; latency_ms?: number; error?: string }> = result?.logs ?? result ?? []
|
|
141
|
+
if (!logs.length) { console.log(chalk.dim('(no logs)')); return }
|
|
142
|
+
for (const log of logs) {
|
|
143
|
+
const ts = log.timestamp ? chalk.dim(new Date(log.timestamp).toLocaleTimeString()) + ' ' : ''
|
|
144
|
+
const err = log.error ? chalk.red(` ${log.error}`) : ''
|
|
145
|
+
console.log(`${ts}${log.method ?? ''} ${log.path ?? ''} ${log.status_code ?? ''} ${log.latency_ms ?? ''}ms${err}`)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function registerBackendDelete(backend: Command) {
|
|
151
|
+
backend.command('delete [project_id]')
|
|
152
|
+
.description('Delete the backend worker')
|
|
153
|
+
.option('--yes', 'Skip confirmation')
|
|
154
|
+
.addHelpText('after', `
|
|
155
|
+
Examples:
|
|
156
|
+
$ blink backend delete --yes
|
|
157
|
+
$ blink backend delete proj_xxx --yes
|
|
158
|
+
`)
|
|
159
|
+
.action(async (projectArg: string | undefined, opts) => {
|
|
160
|
+
requireToken()
|
|
161
|
+
const projectId = requireProjectId(projectArg)
|
|
162
|
+
const skipConfirm = opts.yes || process.argv.includes('--yes') || process.argv.includes('-y')
|
|
163
|
+
if (!skipConfirm && !isJsonMode() && process.stdout.isTTY) {
|
|
164
|
+
const { confirm } = await import('@clack/prompts')
|
|
165
|
+
const ok = await confirm({ message: `Delete backend for project ${projectId}? This cannot be undone.` })
|
|
166
|
+
if (!ok) { console.log('Cancelled.'); return }
|
|
167
|
+
}
|
|
168
|
+
// TODO: needs /api/v1/ route with PAT auth (Phase 1a)
|
|
169
|
+
await withSpinner('Deleting backend...', () =>
|
|
170
|
+
appRequest(`/api/project/${projectId}/backend`, { method: 'DELETE' })
|
|
171
|
+
)
|
|
172
|
+
if (isJsonMode()) return printJson({ status: 'ok', project_id: projectId })
|
|
173
|
+
printSuccess(`Backend deleted for ${projectId}`)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { appRequest } from '../lib/api-app.js'
|
|
3
|
+
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { printJson, printKv, isJsonMode, withSpinner } from '../lib/output.js'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
|
|
7
|
+
function printUsage(data: { total?: number; breakdown?: Array<{ category: string; amount: number }> }) {
|
|
8
|
+
console.log()
|
|
9
|
+
printKv('Total', `${data.total ?? 0} credits used`)
|
|
10
|
+
const breakdown = data.breakdown ?? []
|
|
11
|
+
if (breakdown.length) {
|
|
12
|
+
console.log()
|
|
13
|
+
console.log(chalk.bold('Breakdown:'))
|
|
14
|
+
for (const item of breakdown) printKv(` ${item.category}`, `${item.amount}`)
|
|
15
|
+
}
|
|
16
|
+
console.log()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function registerBillingCommands(program: Command) {
|
|
20
|
+
program.command('credits')
|
|
21
|
+
.description('Check your credit balance and tier')
|
|
22
|
+
.addHelpText('after', `
|
|
23
|
+
Examples:
|
|
24
|
+
$ blink credits
|
|
25
|
+
$ blink credits --json
|
|
26
|
+
`)
|
|
27
|
+
.action(async () => {
|
|
28
|
+
requireToken()
|
|
29
|
+
const result = await withSpinner('Loading usage...', () =>
|
|
30
|
+
appRequest('/api/usage/summary?period=month')
|
|
31
|
+
)
|
|
32
|
+
if (isJsonMode()) return printJson(result)
|
|
33
|
+
const data = result ?? {}
|
|
34
|
+
console.log()
|
|
35
|
+
printKv('Used', `${data.total ?? 0} credits this month`)
|
|
36
|
+
console.log()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
program.command('usage')
|
|
40
|
+
.description('Show usage summary for the current billing period')
|
|
41
|
+
.option('--period <granularity>', 'Period granularity: hour, day, week, month', 'day')
|
|
42
|
+
.addHelpText('after', `
|
|
43
|
+
Examples:
|
|
44
|
+
$ blink usage
|
|
45
|
+
$ blink usage --period month
|
|
46
|
+
$ blink usage --json
|
|
47
|
+
`)
|
|
48
|
+
.action(async (opts) => {
|
|
49
|
+
requireToken()
|
|
50
|
+
const result = await withSpinner('Loading usage...', () =>
|
|
51
|
+
appRequest(`/api/usage/summary?period=${opts.period}`)
|
|
52
|
+
)
|
|
53
|
+
if (isJsonMode()) return printJson(result)
|
|
54
|
+
printUsage(result)
|
|
55
|
+
})
|
|
56
|
+
}
|