@blinkdotnew/cli 0.4.2 → 0.5.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 +9 -20
- package/dist/cli.js +109 -33
- package/package.json +1 -1
- package/src/cli.ts +6 -9
- package/src/commands/auth.ts +130 -26
package/README.md
CHANGED
|
@@ -190,35 +190,24 @@ blink usage # Usage breakdown
|
|
|
190
190
|
blink usage --period month # Monthly summary
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
### Personal Access Tokens
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
blink tokens list # List all PATs
|
|
197
|
-
blink tokens create --name "CI key" # Create a new token (shown once)
|
|
198
|
-
blink tokens revoke tok_xxx --yes # Revoke a token
|
|
199
|
-
```
|
|
200
|
-
|
|
201
193
|
## Authentication
|
|
202
194
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
1. **`--token` flag** — per-command override
|
|
206
|
-
2. **`BLINK_API_KEY` env var** — for CI/CD and coding agents
|
|
207
|
-
3. **Config file** — saved by `blink login --interactive` at `~/.config/blink/config.toml`
|
|
208
|
-
|
|
209
|
-
Get your API key at [blink.new/settings?tab=api-keys](https://blink.new/settings?tab=api-keys).
|
|
195
|
+
Get your workspace API key from [blink.new/settings?tab=api-keys](https://blink.new/settings?tab=api-keys) (starts with `blnk_ak_`).
|
|
210
196
|
|
|
211
197
|
```bash
|
|
212
|
-
#
|
|
198
|
+
# Opens the API keys page in your browser, then prompts for the key
|
|
199
|
+
blink login
|
|
213
200
|
blink login --interactive
|
|
214
201
|
|
|
215
|
-
#
|
|
202
|
+
# Or set the env var directly (CI/agents)
|
|
216
203
|
export BLINK_API_KEY=blnk_ak_...
|
|
217
|
-
|
|
218
|
-
# Per-command
|
|
219
|
-
blink deploy ./dist --prod --token blnk_ak_...
|
|
220
204
|
```
|
|
221
205
|
|
|
206
|
+
Auth is resolved in this order:
|
|
207
|
+
1. **`--token` flag** — per-command override
|
|
208
|
+
2. **`BLINK_API_KEY` env var** — for CI/CD and coding agents
|
|
209
|
+
3. **Config file** — saved by `blink login --interactive` at `~/.config/blink/config.toml`
|
|
210
|
+
|
|
222
211
|
## Project Context
|
|
223
212
|
|
|
224
213
|
Commands that operate on a project resolve it in this order:
|
package/dist/cli.js
CHANGED
|
@@ -2074,12 +2074,113 @@ After linking, most commands work without specifying a project_id:
|
|
|
2074
2074
|
|
|
2075
2075
|
// src/commands/auth.ts
|
|
2076
2076
|
import chalk10 from "chalk";
|
|
2077
|
+
import { createServer } from "http";
|
|
2078
|
+
var TIMEOUT_MS = 12e4;
|
|
2079
|
+
function getBaseUrl() {
|
|
2080
|
+
return process.env.BLINK_APP_URL || "https://blink.new";
|
|
2081
|
+
}
|
|
2082
|
+
function generateState() {
|
|
2083
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
2084
|
+
}
|
|
2085
|
+
function findFreePort() {
|
|
2086
|
+
return new Promise((resolve, reject) => {
|
|
2087
|
+
const srv = createServer();
|
|
2088
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
2089
|
+
const port = srv.address().port;
|
|
2090
|
+
srv.close(() => resolve(port));
|
|
2091
|
+
});
|
|
2092
|
+
srv.on("error", reject);
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
function callbackHtml() {
|
|
2096
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Blink CLI</title>
|
|
2097
|
+
<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}
|
|
2098
|
+
.card{text-align:center;padding:3rem;border-radius:12px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.08)}
|
|
2099
|
+
h1{font-size:1.5rem;margin:0 0 .5rem}p{color:#666;margin:0}</style></head>
|
|
2100
|
+
<body><div class="card"><h1>\u2713 CLI authorized</h1><p>You can close this tab.</p></div></body></html>`;
|
|
2101
|
+
}
|
|
2102
|
+
function parseCallback(url) {
|
|
2103
|
+
const params = new URL(url, "http://localhost").searchParams;
|
|
2104
|
+
const key = params.get("key");
|
|
2105
|
+
const workspace_id = params.get("workspace_id");
|
|
2106
|
+
const workspace_name = params.get("workspace_name");
|
|
2107
|
+
const email = params.get("email");
|
|
2108
|
+
if (!key || !workspace_id || !workspace_name || !email) return null;
|
|
2109
|
+
return { key, workspace_id, workspace_name, email };
|
|
2110
|
+
}
|
|
2111
|
+
function startCallbackServer(port, expectedState) {
|
|
2112
|
+
let resolveCb;
|
|
2113
|
+
let rejectCb;
|
|
2114
|
+
const promise = new Promise((res, rej) => {
|
|
2115
|
+
resolveCb = res;
|
|
2116
|
+
rejectCb = rej;
|
|
2117
|
+
});
|
|
2118
|
+
const server = createServer((req, res) => {
|
|
2119
|
+
if (!req.url?.startsWith("/callback")) {
|
|
2120
|
+
res.writeHead(404).end();
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
const state = new URL(req.url, "http://localhost").searchParams.get("state");
|
|
2124
|
+
if (state !== expectedState) {
|
|
2125
|
+
res.writeHead(403).end("State mismatch");
|
|
2126
|
+
rejectCb(new Error("State mismatch \u2014 possible CSRF. Try again."));
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
const result = parseCallback(req.url);
|
|
2130
|
+
if (!result) {
|
|
2131
|
+
res.writeHead(400).end("Missing parameters");
|
|
2132
|
+
rejectCb(new Error("Incomplete callback \u2014 missing parameters."));
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
res.writeHead(200, { "Content-Type": "text/html" }).end(callbackHtml());
|
|
2136
|
+
resolveCb(result);
|
|
2137
|
+
});
|
|
2138
|
+
server.listen(port, "127.0.0.1");
|
|
2139
|
+
return { promise, close: () => server.close() };
|
|
2140
|
+
}
|
|
2141
|
+
async function openBrowserLogin(port, state) {
|
|
2142
|
+
const url = `${getBaseUrl()}/auth/cli?port=${port}&state=${state}`;
|
|
2143
|
+
const open = await import("open").then((m) => m.default).catch(() => null);
|
|
2144
|
+
if (open) await open(url).catch(() => {
|
|
2145
|
+
});
|
|
2146
|
+
console.log(chalk10.dim(` ${url}
|
|
2147
|
+
`));
|
|
2148
|
+
}
|
|
2149
|
+
async function waitForCallback(promise) {
|
|
2150
|
+
return Promise.race([
|
|
2151
|
+
promise,
|
|
2152
|
+
new Promise(
|
|
2153
|
+
(_, reject) => setTimeout(() => reject(new Error("Timed out after 120s \u2014 no callback received.")), TIMEOUT_MS)
|
|
2154
|
+
)
|
|
2155
|
+
]);
|
|
2156
|
+
}
|
|
2157
|
+
async function browserLogin() {
|
|
2158
|
+
const state = generateState();
|
|
2159
|
+
const port = await findFreePort();
|
|
2160
|
+
const { promise, close } = startCallbackServer(port, state);
|
|
2161
|
+
console.log(chalk10.bold("\n Opening browser to authorize...\n"));
|
|
2162
|
+
await openBrowserLogin(port, state);
|
|
2163
|
+
const result = await waitForCallback(promise).finally(close);
|
|
2164
|
+
writeConfig({ api_key: result.key, workspace_id: result.workspace_id });
|
|
2165
|
+
console.log(chalk10.green("\u2713") + ` Logged in as ${chalk10.bold(result.email)} (${result.workspace_name})`);
|
|
2166
|
+
}
|
|
2167
|
+
async function interactiveLogin() {
|
|
2168
|
+
const { password } = await import("@clack/prompts");
|
|
2169
|
+
const apiKey = await password({ message: "Paste your API key (blnk_ak_...):" });
|
|
2170
|
+
if (!apiKey?.startsWith("blnk_ak_")) {
|
|
2171
|
+
console.error("Error: API key must start with blnk_ak_");
|
|
2172
|
+
process.exit(1);
|
|
2173
|
+
}
|
|
2174
|
+
writeConfig({ api_key: apiKey });
|
|
2175
|
+
console.log(chalk10.green("\u2713") + " Saved to ~/.config/blink/config.toml");
|
|
2176
|
+
}
|
|
2077
2177
|
function registerAuthCommands(program2) {
|
|
2078
2178
|
program2.command("login").description("Authenticate with your Blink API key").option("--interactive", "Prompt for API key (for headless/SSH/CI environments)").addHelpText("after", `
|
|
2079
2179
|
Get your API key at: blink.new \u2192 Settings \u2192 API Keys (starts with blnk_ak_)
|
|
2080
2180
|
|
|
2081
2181
|
Examples:
|
|
2082
|
-
$ blink login
|
|
2182
|
+
$ blink login Opens browser for one-click auth
|
|
2183
|
+
$ blink login --interactive Paste key manually (headless/SSH/CI)
|
|
2083
2184
|
$ export BLINK_API_KEY=blnk_ak_... Alternative: set env var directly (no login needed)
|
|
2084
2185
|
|
|
2085
2186
|
In Blink Claw agents: BLINK_API_KEY is already set \u2014 login is not needed.
|
|
@@ -2089,28 +2190,8 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
2089
2190
|
console.log(chalk10.green("\u2713") + " Already authenticated via BLINK_API_KEY env var.");
|
|
2090
2191
|
return;
|
|
2091
2192
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
console.log(chalk10.bold("\n Open this page to get your API key:\n"));
|
|
2095
|
-
console.log(` ${chalk10.cyan(url)}
|
|
2096
|
-
`);
|
|
2097
|
-
const open = await import("open").then((m) => m.default).catch(() => null);
|
|
2098
|
-
if (open) {
|
|
2099
|
-
await open(url).catch(() => {
|
|
2100
|
-
});
|
|
2101
|
-
console.log(chalk10.dim(" (opened in browser)"));
|
|
2102
|
-
}
|
|
2103
|
-
console.log(chalk10.dim(" Then run: blink login --interactive\n"));
|
|
2104
|
-
return;
|
|
2105
|
-
}
|
|
2106
|
-
const { password } = await import("@clack/prompts");
|
|
2107
|
-
const apiKey = await password({ message: "Paste your API key (blnk_ak_...):" });
|
|
2108
|
-
if (!apiKey?.startsWith("blnk_ak_")) {
|
|
2109
|
-
console.error("Error: API key must start with blnk_ak_");
|
|
2110
|
-
process.exit(1);
|
|
2111
|
-
}
|
|
2112
|
-
writeConfig({ api_key: apiKey });
|
|
2113
|
-
console.log(chalk10.green("\u2713") + " Saved to ~/.config/blink/config.toml");
|
|
2193
|
+
if (opts.interactive) return interactiveLogin();
|
|
2194
|
+
return browserLogin();
|
|
2114
2195
|
});
|
|
2115
2196
|
program2.command("logout").description("Remove stored credentials").action(() => {
|
|
2116
2197
|
clearConfig();
|
|
@@ -2119,7 +2200,7 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
2119
2200
|
program2.command("whoami").description("Show current authentication status and key info").action(async () => {
|
|
2120
2201
|
const token = resolveToken();
|
|
2121
2202
|
if (!token) {
|
|
2122
|
-
console.error("Error: Not authenticated. Run `blink login
|
|
2203
|
+
console.error("Error: Not authenticated. Run `blink login` or set BLINK_API_KEY.");
|
|
2123
2204
|
process.exit(1);
|
|
2124
2205
|
}
|
|
2125
2206
|
if (isJsonMode()) {
|
|
@@ -3706,12 +3787,12 @@ var pkg = require2("../package.json");
|
|
|
3706
3787
|
var program = new Command();
|
|
3707
3788
|
program.name("blink").description("Blink platform CLI \u2014 build, deploy, and manage AI-powered apps").version(pkg.version).option("--token <key>", "Override API token for this command").option("--json", "Machine-readable JSON output (no colors, great for scripting)").option("-y, --yes", "Skip confirmation prompts").option("--debug", "Verbose request logging").option("--profile <name>", "Use named config profile from ~/.config/blink/config.toml").addHelpText("after", `
|
|
3708
3789
|
Auth:
|
|
3709
|
-
|
|
3710
|
-
$ blink login --interactive
|
|
3711
|
-
$ export BLINK_API_KEY=blnk_ak_... Or set env var directly (
|
|
3790
|
+
$ blink login Opens blink.new to copy your API key
|
|
3791
|
+
$ blink login --interactive Paste and save key to ~/.config/blink/config.toml
|
|
3792
|
+
$ export BLINK_API_KEY=blnk_ak_... Or set env var directly (CI/agents)
|
|
3712
3793
|
|
|
3713
3794
|
Quick Start:
|
|
3714
|
-
$ blink login
|
|
3795
|
+
$ blink login Get your workspace API key
|
|
3715
3796
|
$ blink link Link current dir to a project (interactive picker)
|
|
3716
3797
|
$ npm run build && blink deploy ./dist --prod Build then deploy to production
|
|
3717
3798
|
|
|
@@ -3836,11 +3917,6 @@ Billing:
|
|
|
3836
3917
|
$ blink credits Check credit balance
|
|
3837
3918
|
$ blink usage Usage breakdown
|
|
3838
3919
|
|
|
3839
|
-
Tokens (Personal Access Tokens):
|
|
3840
|
-
$ blink tokens list List PATs
|
|
3841
|
-
$ blink tokens create --name "CI" Create PAT (shown once!)
|
|
3842
|
-
$ blink tokens revoke <id> --yes Revoke a PAT
|
|
3843
|
-
|
|
3844
3920
|
Init:
|
|
3845
3921
|
$ blink init Create project + link to current dir
|
|
3846
3922
|
$ blink init --name "My App" Create with custom name
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -28,6 +28,8 @@ import { registerInitCommands } from './commands/init.js'
|
|
|
28
28
|
import { registerWorkspaceCommands } from './commands/workspace.js'
|
|
29
29
|
import { registerVersionCommands } from './commands/versions.js'
|
|
30
30
|
import { registerBillingCommands } from './commands/billing.js'
|
|
31
|
+
// PAT tokens are deprecated in favor of workspace API keys (blnk_ak_*).
|
|
32
|
+
// The tokens command is kept for backward compat but hidden from help.
|
|
31
33
|
import { registerTokenCommands } from './commands/tokens.js'
|
|
32
34
|
|
|
33
35
|
const require = createRequire(import.meta.url)
|
|
@@ -46,12 +48,12 @@ program
|
|
|
46
48
|
.option('--profile <name>', 'Use named config profile from ~/.config/blink/config.toml')
|
|
47
49
|
.addHelpText('after', `
|
|
48
50
|
Auth:
|
|
49
|
-
|
|
50
|
-
$ blink login --interactive
|
|
51
|
-
$ export BLINK_API_KEY=blnk_ak_... Or set env var directly (
|
|
51
|
+
$ blink login Opens blink.new to copy your API key
|
|
52
|
+
$ blink login --interactive Paste and save key to ~/.config/blink/config.toml
|
|
53
|
+
$ export BLINK_API_KEY=blnk_ak_... Or set env var directly (CI/agents)
|
|
52
54
|
|
|
53
55
|
Quick Start:
|
|
54
|
-
$ blink login
|
|
56
|
+
$ blink login Get your workspace API key
|
|
55
57
|
$ blink link Link current dir to a project (interactive picker)
|
|
56
58
|
$ npm run build && blink deploy ./dist --prod Build then deploy to production
|
|
57
59
|
|
|
@@ -176,11 +178,6 @@ Billing:
|
|
|
176
178
|
$ blink credits Check credit balance
|
|
177
179
|
$ blink usage Usage breakdown
|
|
178
180
|
|
|
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
181
|
Init:
|
|
185
182
|
$ blink init Create project + link to current dir
|
|
186
183
|
$ blink init --name "My App" Create with custom name
|
package/src/commands/auth.ts
CHANGED
|
@@ -1,9 +1,132 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
|
-
import {
|
|
3
|
-
import { requireToken, resolveToken } from '../lib/auth.js'
|
|
2
|
+
import { resolveToken } from '../lib/auth.js'
|
|
4
3
|
import { writeConfig, clearConfig } from '../lib/config.js'
|
|
5
4
|
import { printJson, isJsonMode } from '../lib/output.js'
|
|
6
5
|
import chalk from 'chalk'
|
|
6
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
|
|
7
|
+
import type { AddressInfo } from 'node:net'
|
|
8
|
+
|
|
9
|
+
const TIMEOUT_MS = 120_000
|
|
10
|
+
|
|
11
|
+
interface CallbackResult {
|
|
12
|
+
key: string
|
|
13
|
+
workspace_id: string
|
|
14
|
+
workspace_name: string
|
|
15
|
+
email: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getBaseUrl(): string {
|
|
19
|
+
return process.env.BLINK_APP_URL || 'https://blink.new'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function generateState(): string {
|
|
23
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function findFreePort(): Promise<number> {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const srv = createServer()
|
|
29
|
+
srv.listen(0, '127.0.0.1', () => {
|
|
30
|
+
const port = (srv.address() as AddressInfo).port
|
|
31
|
+
srv.close(() => resolve(port))
|
|
32
|
+
})
|
|
33
|
+
srv.on('error', reject)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function callbackHtml(): string {
|
|
38
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Blink CLI</title>
|
|
39
|
+
<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}
|
|
40
|
+
.card{text-align:center;padding:3rem;border-radius:12px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.08)}
|
|
41
|
+
h1{font-size:1.5rem;margin:0 0 .5rem}p{color:#666;margin:0}</style></head>
|
|
42
|
+
<body><div class="card"><h1>✓ CLI authorized</h1><p>You can close this tab.</p></div></body></html>`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseCallback(url: string): CallbackResult | null {
|
|
46
|
+
const params = new URL(url, 'http://localhost').searchParams
|
|
47
|
+
const key = params.get('key')
|
|
48
|
+
const workspace_id = params.get('workspace_id')
|
|
49
|
+
const workspace_name = params.get('workspace_name')
|
|
50
|
+
const email = params.get('email')
|
|
51
|
+
if (!key || !workspace_id || !workspace_name || !email) return null
|
|
52
|
+
return { key, workspace_id, workspace_name, email }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function startCallbackServer(
|
|
56
|
+
port: number,
|
|
57
|
+
expectedState: string,
|
|
58
|
+
): { promise: Promise<CallbackResult>; close: () => void } {
|
|
59
|
+
let resolveCb: (v: CallbackResult) => void
|
|
60
|
+
let rejectCb: (e: Error) => void
|
|
61
|
+
const promise = new Promise<CallbackResult>((res, rej) => { resolveCb = res; rejectCb = rej })
|
|
62
|
+
|
|
63
|
+
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
64
|
+
if (!req.url?.startsWith('/callback')) {
|
|
65
|
+
res.writeHead(404).end()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
const state = new URL(req.url, 'http://localhost').searchParams.get('state')
|
|
69
|
+
if (state !== expectedState) {
|
|
70
|
+
res.writeHead(403).end('State mismatch')
|
|
71
|
+
rejectCb(new Error('State mismatch — possible CSRF. Try again.'))
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
const result = parseCallback(req.url)
|
|
75
|
+
if (!result) {
|
|
76
|
+
res.writeHead(400).end('Missing parameters')
|
|
77
|
+
rejectCb(new Error('Incomplete callback — missing parameters.'))
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
res.writeHead(200, { 'Content-Type': 'text/html' }).end(callbackHtml())
|
|
81
|
+
resolveCb(result)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
server.listen(port, '127.0.0.1')
|
|
85
|
+
return { promise, close: () => server.close() }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function openBrowserLogin(port: number, state: string) {
|
|
89
|
+
const url = `${getBaseUrl()}/auth/cli?port=${port}&state=${state}`
|
|
90
|
+
const open = await import('open').then(m => m.default).catch(() => null)
|
|
91
|
+
if (open) await open(url).catch(() => {})
|
|
92
|
+
console.log(chalk.dim(` ${url}\n`))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function waitForCallback(
|
|
96
|
+
promise: Promise<CallbackResult>,
|
|
97
|
+
): Promise<CallbackResult> {
|
|
98
|
+
return Promise.race([
|
|
99
|
+
promise,
|
|
100
|
+
new Promise<never>((_, reject) =>
|
|
101
|
+
setTimeout(() => reject(new Error('Timed out after 120s — no callback received.')), TIMEOUT_MS),
|
|
102
|
+
),
|
|
103
|
+
])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function browserLogin() {
|
|
107
|
+
const state = generateState()
|
|
108
|
+
const port = await findFreePort()
|
|
109
|
+
const { promise, close } = startCallbackServer(port, state)
|
|
110
|
+
|
|
111
|
+
console.log(chalk.bold('\n Opening browser to authorize...\n'))
|
|
112
|
+
await openBrowserLogin(port, state)
|
|
113
|
+
|
|
114
|
+
const result = await waitForCallback(promise).finally(close)
|
|
115
|
+
|
|
116
|
+
writeConfig({ api_key: result.key, workspace_id: result.workspace_id })
|
|
117
|
+
console.log(chalk.green('✓') + ` Logged in as ${chalk.bold(result.email)} (${result.workspace_name})`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function interactiveLogin() {
|
|
121
|
+
const { password } = await import('@clack/prompts')
|
|
122
|
+
const apiKey = await password({ message: 'Paste your API key (blnk_ak_...):' }) as string
|
|
123
|
+
if (!apiKey?.startsWith('blnk_ak_')) {
|
|
124
|
+
console.error('Error: API key must start with blnk_ak_')
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
writeConfig({ api_key: apiKey })
|
|
128
|
+
console.log(chalk.green('✓') + ' Saved to ~/.config/blink/config.toml')
|
|
129
|
+
}
|
|
7
130
|
|
|
8
131
|
export function registerAuthCommands(program: Command) {
|
|
9
132
|
program.command('login')
|
|
@@ -13,7 +136,8 @@ export function registerAuthCommands(program: Command) {
|
|
|
13
136
|
Get your API key at: blink.new → Settings → API Keys (starts with blnk_ak_)
|
|
14
137
|
|
|
15
138
|
Examples:
|
|
16
|
-
$ blink login
|
|
139
|
+
$ blink login Opens browser for one-click auth
|
|
140
|
+
$ blink login --interactive Paste key manually (headless/SSH/CI)
|
|
17
141
|
$ export BLINK_API_KEY=blnk_ak_... Alternative: set env var directly (no login needed)
|
|
18
142
|
|
|
19
143
|
In Blink Claw agents: BLINK_API_KEY is already set — login is not needed.
|
|
@@ -24,28 +148,8 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
24
148
|
console.log(chalk.green('✓') + ' Already authenticated via BLINK_API_KEY env var.')
|
|
25
149
|
return
|
|
26
150
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!opts.interactive) {
|
|
30
|
-
console.log(chalk.bold('\n Open this page to get your API key:\n'))
|
|
31
|
-
console.log(` ${chalk.cyan(url)}\n`)
|
|
32
|
-
const open = await import('open').then(m => m.default).catch(() => null)
|
|
33
|
-
if (open) {
|
|
34
|
-
await open(url).catch(() => {})
|
|
35
|
-
console.log(chalk.dim(' (opened in browser)'))
|
|
36
|
-
}
|
|
37
|
-
console.log(chalk.dim(' Then run: blink login --interactive\n'))
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { password } = await import('@clack/prompts')
|
|
42
|
-
const apiKey = await password({ message: 'Paste your API key (blnk_ak_...):' }) as string
|
|
43
|
-
if (!apiKey?.startsWith('blnk_ak_')) {
|
|
44
|
-
console.error('Error: API key must start with blnk_ak_')
|
|
45
|
-
process.exit(1)
|
|
46
|
-
}
|
|
47
|
-
writeConfig({ api_key: apiKey })
|
|
48
|
-
console.log(chalk.green('✓') + ' Saved to ~/.config/blink/config.toml')
|
|
151
|
+
if (opts.interactive) return interactiveLogin()
|
|
152
|
+
return browserLogin()
|
|
49
153
|
})
|
|
50
154
|
|
|
51
155
|
program.command('logout')
|
|
@@ -60,7 +164,7 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
60
164
|
.action(async () => {
|
|
61
165
|
const token = resolveToken()
|
|
62
166
|
if (!token) {
|
|
63
|
-
console.error('Error: Not authenticated. Run `blink login
|
|
167
|
+
console.error('Error: Not authenticated. Run `blink login` or set BLINK_API_KEY.')
|
|
64
168
|
process.exit(1)
|
|
65
169
|
}
|
|
66
170
|
|