@dreamlake/dreamlake-cli 0.2.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dreamlake.js +38 -26
- package/package.json +19 -27
- package/README.md +0 -171
- package/dist/cli/auth/commands.js +0 -240
- package/dist/cli/auth/constants.js +0 -16
- package/dist/cli/auth/credentials.js +0 -157
- package/dist/cli/auth/device-flow.js +0 -134
- package/dist/cli/auth/device-secret.js +0 -34
- package/dist/cli/client.js +0 -99
- package/dist/cli/config.js +0 -81
- package/dist/cli/create/index.js +0 -204
- package/dist/cli/delete/index.js +0 -228
- package/dist/cli/download/index.js +0 -128
- package/dist/cli/glob.js +0 -45
- package/dist/cli/graphql-helpers.js +0 -42
- package/dist/cli/graphql.js +0 -47
- package/dist/cli/helpers.js +0 -106
- package/dist/cli/index.js +0 -95
- package/dist/cli/list/index.js +0 -254
- package/dist/cli/org/index.js +0 -348
- package/dist/cli/pipeline/index.js +0 -606
- package/dist/cli/progress.js +0 -65
- package/dist/cli/prompt.js +0 -17
- package/dist/cli/resources.js +0 -134
- package/dist/cli/target.js +0 -85
- package/dist/cli/team/index.js +0 -411
- package/dist/cli/update/index.js +0 -256
- package/dist/cli/upload/index.js +0 -263
- package/dist/cli/upload/kinds.js +0 -85
- package/dist/cli/upload/multipart.js +0 -211
- package/dist/cli/workflow/index.js +0 -627
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// CLI-side credentials storage — multi-environment.
|
|
2
|
-
//
|
|
3
|
-
// One YAML file holds several named environments (staging, prod, custom…)
|
|
4
|
-
// each with its own server/bss/auth/namespace/token, plus which one is
|
|
5
|
-
// active. Switching environments never loses another's token.
|
|
6
|
-
//
|
|
7
|
-
// $XDG_CONFIG_HOME/dreamlake/auth.yml (default ~/.config/dreamlake/auth.yml, chmod 600)
|
|
8
|
-
//
|
|
9
|
-
// Shape:
|
|
10
|
-
// current: staging
|
|
11
|
-
// envs:
|
|
12
|
-
// staging: { server, bss, auth, namespace, token }
|
|
13
|
-
// prod: { server, bss, auth, namespace, token }
|
|
14
|
-
//
|
|
15
|
-
// Old flat files ({server,bss,namespace,token}) are migrated on read into
|
|
16
|
-
// a single env (named after the matching built-in, else "default").
|
|
17
|
-
import { existsSync, chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
import YAML from "yaml";
|
|
21
|
-
export const BUILTIN_ENVS = {
|
|
22
|
-
staging: {
|
|
23
|
-
server: "https://staging-api.dreamlake.ai",
|
|
24
|
-
bss: "https://bs-0b713023d6574ac2936eb1e9cbe9cfaa.ecs.us-east-1.on.aws",
|
|
25
|
-
auth: "https://staging-auth.vuer.ai",
|
|
26
|
-
},
|
|
27
|
-
prod: {
|
|
28
|
-
server: "https://api.dreamlake.ai",
|
|
29
|
-
bss: "https://bs-66d8a7267cdf47e1809553f1b0c00edf.ecs.us-east-1.on.aws",
|
|
30
|
-
auth: "https://auth.vuer.ai",
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
export function builtinEnv(name) {
|
|
34
|
-
return BUILTIN_ENVS[name] ?? null;
|
|
35
|
-
}
|
|
36
|
-
// ─── paths ───────────────────────────────────────────────────────────
|
|
37
|
-
export function configDir() {
|
|
38
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
39
|
-
if (xdg && xdg.length > 0)
|
|
40
|
-
return path.join(xdg, "dreamlake");
|
|
41
|
-
return path.join(homedir(), ".config", "dreamlake");
|
|
42
|
-
}
|
|
43
|
-
export function authFilePath() {
|
|
44
|
-
return path.join(configDir(), "auth.yml");
|
|
45
|
-
}
|
|
46
|
-
// ─── load / migrate ──────────────────────────────────────────────────
|
|
47
|
-
/** Name a migrated flat env after the built-in whose server matches, else "default". */
|
|
48
|
-
function nameForServer(server) {
|
|
49
|
-
for (const [name, urls] of Object.entries(BUILTIN_ENVS)) {
|
|
50
|
-
if (urls.server.replace(/\/+$/, "") === server.replace(/\/+$/, ""))
|
|
51
|
-
return name;
|
|
52
|
-
}
|
|
53
|
-
return "default";
|
|
54
|
-
}
|
|
55
|
-
/** Read the full auth file, migrating the legacy flat shape. Returns empty file if absent/corrupt. */
|
|
56
|
-
export function loadAuthFile(filePath) {
|
|
57
|
-
const p = filePath ?? authFilePath();
|
|
58
|
-
const empty = { current: "", envs: {} };
|
|
59
|
-
if (!existsSync(p))
|
|
60
|
-
return empty;
|
|
61
|
-
let parsed;
|
|
62
|
-
try {
|
|
63
|
-
parsed = YAML.parse(readFileSync(p, "utf8"));
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return empty;
|
|
67
|
-
}
|
|
68
|
-
if (!parsed || typeof parsed !== "object")
|
|
69
|
-
return empty;
|
|
70
|
-
const obj = parsed;
|
|
71
|
-
// New multi-env shape.
|
|
72
|
-
if (obj.envs && typeof obj.envs === "object") {
|
|
73
|
-
const envs = obj.envs;
|
|
74
|
-
const current = typeof obj.current === "string" ? obj.current : Object.keys(envs)[0] ?? "";
|
|
75
|
-
return { current, envs };
|
|
76
|
-
}
|
|
77
|
-
// Legacy flat shape → migrate.
|
|
78
|
-
if (typeof obj.server === "string" && typeof obj.token === "string") {
|
|
79
|
-
const name = nameForServer(obj.server);
|
|
80
|
-
return {
|
|
81
|
-
current: name,
|
|
82
|
-
envs: {
|
|
83
|
-
[name]: {
|
|
84
|
-
server: obj.server,
|
|
85
|
-
bss: typeof obj.bss === "string" ? obj.bss : "",
|
|
86
|
-
namespace: typeof obj.namespace === "string" ? obj.namespace : "",
|
|
87
|
-
token: obj.token,
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return empty;
|
|
93
|
-
}
|
|
94
|
-
function writeAuthFile(file, filePath) {
|
|
95
|
-
const p = filePath ?? authFilePath();
|
|
96
|
-
const dir = path.dirname(p);
|
|
97
|
-
if (!existsSync(dir))
|
|
98
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
99
|
-
writeFileSync(p, YAML.stringify({ current: file.current, envs: file.envs }), { mode: 0o600 });
|
|
100
|
-
chmodSync(p, 0o600);
|
|
101
|
-
return p;
|
|
102
|
-
}
|
|
103
|
-
// ─── accessors ───────────────────────────────────────────────────────
|
|
104
|
-
export function currentEnvName(filePath) {
|
|
105
|
-
const f = loadAuthFile(filePath);
|
|
106
|
-
return f.current && f.envs[f.current] ? f.current : null;
|
|
107
|
-
}
|
|
108
|
-
/** The active environment's saved auth, or null if none is logged in. */
|
|
109
|
-
export function currentEnv(filePath) {
|
|
110
|
-
const f = loadAuthFile(filePath);
|
|
111
|
-
return (f.current && f.envs[f.current]) || null;
|
|
112
|
-
}
|
|
113
|
-
export function listEnvs(filePath) {
|
|
114
|
-
const f = loadAuthFile(filePath);
|
|
115
|
-
return Object.entries(f.envs).map(([name, env]) => ({
|
|
116
|
-
name,
|
|
117
|
-
env,
|
|
118
|
-
current: name === f.current,
|
|
119
|
-
}));
|
|
120
|
-
}
|
|
121
|
-
/** Save an env (creating or overwriting) and optionally make it active. Returns the file path. */
|
|
122
|
-
export function saveEnv(name, env, opts = {}) {
|
|
123
|
-
const f = loadAuthFile(opts.filePath);
|
|
124
|
-
f.envs[name] = env;
|
|
125
|
-
if (opts.setCurrent ?? true)
|
|
126
|
-
f.current = name;
|
|
127
|
-
if (!f.current)
|
|
128
|
-
f.current = name;
|
|
129
|
-
return writeAuthFile(f, opts.filePath);
|
|
130
|
-
}
|
|
131
|
-
/** Switch the active env. Returns false if the env isn't defined. */
|
|
132
|
-
export function useEnv(name, filePath) {
|
|
133
|
-
const f = loadAuthFile(filePath);
|
|
134
|
-
if (!f.envs[name])
|
|
135
|
-
return false;
|
|
136
|
-
f.current = name;
|
|
137
|
-
writeAuthFile(f, filePath);
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
/** Remove one env. Returns false if it didn't exist. Picks a new current if needed. */
|
|
141
|
-
export function removeEnv(name, filePath) {
|
|
142
|
-
const f = loadAuthFile(filePath);
|
|
143
|
-
if (!f.envs[name])
|
|
144
|
-
return false;
|
|
145
|
-
delete f.envs[name];
|
|
146
|
-
if (f.current === name)
|
|
147
|
-
f.current = Object.keys(f.envs)[0] ?? "";
|
|
148
|
-
const p = filePath ?? authFilePath();
|
|
149
|
-
if (Object.keys(f.envs).length === 0) {
|
|
150
|
-
if (existsSync(p))
|
|
151
|
-
rmSync(p);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
writeAuthFile(f, filePath);
|
|
155
|
-
}
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
// Device authorization flow (RFC 8628) — port of dreamlake-py's
|
|
2
|
-
// auth/device_flow.py.
|
|
3
|
-
//
|
|
4
|
-
// 1. start → POST {VUER_AUTH}/api/device/start → user_code + verification URI
|
|
5
|
-
// 2. poll → POST {VUER_AUTH}/api/device/poll → access_token (when approved)
|
|
6
|
-
// 3. exchange → POST {DL}/auth/exchange (Bearer) → permanent dreamlake token
|
|
7
|
-
import { vuerAuthUrl, CLIENT_ID, DEFAULT_SCOPE } from "./constants.js";
|
|
8
|
-
import { hashDeviceSecret } from "./device-secret.js";
|
|
9
|
-
export class DeviceFlowError extends Error {
|
|
10
|
-
}
|
|
11
|
-
export class DeviceCodeExpiredError extends DeviceFlowError {
|
|
12
|
-
}
|
|
13
|
-
export class AuthorizationDeniedError extends DeviceFlowError {
|
|
14
|
-
}
|
|
15
|
-
export class TokenExchangeError extends DeviceFlowError {
|
|
16
|
-
}
|
|
17
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
18
|
-
export class DeviceFlowClient {
|
|
19
|
-
deviceSecret;
|
|
20
|
-
dreamlakeServerUrl;
|
|
21
|
-
authUrl;
|
|
22
|
-
constructor(deviceSecret, dreamlakeServerUrl, authUrl) {
|
|
23
|
-
this.deviceSecret = deviceSecret;
|
|
24
|
-
this.dreamlakeServerUrl = dreamlakeServerUrl;
|
|
25
|
-
this.dreamlakeServerUrl = dreamlakeServerUrl.replace(/\/+$/, "");
|
|
26
|
-
this.authUrl = vuerAuthUrl(authUrl);
|
|
27
|
-
}
|
|
28
|
-
/** Initiate device authorization flow with vuer-auth. */
|
|
29
|
-
async startDeviceFlow(scope = DEFAULT_SCOPE) {
|
|
30
|
-
const res = await fetch(`${this.authUrl}/api/device/start`, {
|
|
31
|
-
method: "POST",
|
|
32
|
-
headers: { "Content-Type": "application/json" },
|
|
33
|
-
body: JSON.stringify({
|
|
34
|
-
client_id: CLIENT_ID,
|
|
35
|
-
scope,
|
|
36
|
-
device_secret_hash: hashDeviceSecret(this.deviceSecret),
|
|
37
|
-
}),
|
|
38
|
-
signal: AbortSignal.timeout(10000),
|
|
39
|
-
});
|
|
40
|
-
if (!res.ok) {
|
|
41
|
-
throw new DeviceFlowError(`device start failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
42
|
-
}
|
|
43
|
-
const data = (await res.json());
|
|
44
|
-
const userCode = String(data.user_code ?? "");
|
|
45
|
-
const verificationUri = String(data.verification_uri ?? "");
|
|
46
|
-
return {
|
|
47
|
-
userCode,
|
|
48
|
-
deviceCode: String(data.device_code ?? ""),
|
|
49
|
-
verificationUri,
|
|
50
|
-
verificationUriComplete: String(data.verification_uri_complete ??
|
|
51
|
-
`${verificationUri}?code=${userCode.replace(/-/g, "")}`),
|
|
52
|
-
expiresIn: Number(data.expires_in ?? 600),
|
|
53
|
-
interval: Number(data.interval ?? 5),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Poll vuer-auth for authorization completion. Resolves with the
|
|
58
|
-
* vuer-auth access token (JWT). `onTick(elapsedSeconds)` is called
|
|
59
|
-
* before each poll for progress display.
|
|
60
|
-
*/
|
|
61
|
-
async pollForToken(maxAttempts = 120, onTick) {
|
|
62
|
-
const deviceSecretHash = hashDeviceSecret(this.deviceSecret);
|
|
63
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
64
|
-
if (onTick)
|
|
65
|
-
onTick(attempt * 5);
|
|
66
|
-
try {
|
|
67
|
-
const res = await fetch(`${this.authUrl}/api/device/poll`, {
|
|
68
|
-
method: "POST",
|
|
69
|
-
headers: { "Content-Type": "application/json" },
|
|
70
|
-
body: JSON.stringify({
|
|
71
|
-
client_id: CLIENT_ID,
|
|
72
|
-
device_secret_hash: deviceSecretHash,
|
|
73
|
-
}),
|
|
74
|
-
signal: AbortSignal.timeout(10000),
|
|
75
|
-
});
|
|
76
|
-
if (res.status === 200) {
|
|
77
|
-
const body = (await res.json());
|
|
78
|
-
const token = body.access_token;
|
|
79
|
-
if (typeof token !== "string" || !token) {
|
|
80
|
-
throw new TokenExchangeError(`device poll succeeded but no access_token in response (keys: ${Object.keys(body).join(", ")})`);
|
|
81
|
-
}
|
|
82
|
-
return token;
|
|
83
|
-
}
|
|
84
|
-
const error = (await res.json().catch(() => ({})))
|
|
85
|
-
.error;
|
|
86
|
-
if (error === "authorization_pending") {
|
|
87
|
-
await sleep(5000);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (error === "expired_token") {
|
|
91
|
-
throw new DeviceCodeExpiredError("Device code expired. Please run 'dreamlake login' again.");
|
|
92
|
-
}
|
|
93
|
-
if (error === "access_denied") {
|
|
94
|
-
throw new AuthorizationDeniedError("User denied authorization request.");
|
|
95
|
-
}
|
|
96
|
-
if (error === "slow_down") {
|
|
97
|
-
await sleep(10000);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
throw new TokenExchangeError(`Device flow error: ${error}`);
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
if (err instanceof DeviceFlowError)
|
|
104
|
-
throw err;
|
|
105
|
-
// Network blip — retry.
|
|
106
|
-
await sleep(5000);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
throw new DeviceFlowError("Authorization timed out after 10 minutes. Please run 'dreamlake login' again.");
|
|
110
|
-
}
|
|
111
|
-
/** Exchange a vuer-auth token for a permanent dreamlake token. */
|
|
112
|
-
async exchangeToken(vuerAuthToken) {
|
|
113
|
-
let res;
|
|
114
|
-
try {
|
|
115
|
-
res = await fetch(`${this.dreamlakeServerUrl}/auth/exchange`, {
|
|
116
|
-
method: "POST",
|
|
117
|
-
headers: { Authorization: `Bearer ${vuerAuthToken}` },
|
|
118
|
-
signal: AbortSignal.timeout(10000),
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
throw new TokenExchangeError(`Network error during token exchange: ${err.message}`);
|
|
123
|
-
}
|
|
124
|
-
if (!res.ok) {
|
|
125
|
-
const body = await res.text().catch(() => "");
|
|
126
|
-
throw new TokenExchangeError(`Token exchange failed: ${res.status} ${body || "(empty response body)"}`);
|
|
127
|
-
}
|
|
128
|
-
const data = (await res.json());
|
|
129
|
-
if (!data.dreamlake_token) {
|
|
130
|
-
throw new TokenExchangeError("Server response missing dreamlake_token field");
|
|
131
|
-
}
|
|
132
|
-
return data.dreamlake_token;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Device secret generation + persistence — port of dreamlake-py's
|
|
2
|
-
// auth/device_secret.py. The secret is a stable per-machine identifier
|
|
3
|
-
// used by the device-authorization flow; we persist it next to auth.yml
|
|
4
|
-
// (instead of dreamlake-py's config.json) at ~/.config/dreamlake/device_secret.
|
|
5
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { configDir } from "./credentials.js";
|
|
9
|
-
function deviceSecretPath() {
|
|
10
|
-
return path.join(configDir(), "device_secret");
|
|
11
|
-
}
|
|
12
|
-
/** Generate a cryptographically secure 64-char hex device secret (256 bits). */
|
|
13
|
-
export function generateDeviceSecret() {
|
|
14
|
-
return randomBytes(32).toString("hex");
|
|
15
|
-
}
|
|
16
|
-
/** SHA256 hash of the device secret, as a hex string. */
|
|
17
|
-
export function hashDeviceSecret(secret) {
|
|
18
|
-
return createHash("sha256").update(secret, "utf8").digest("hex");
|
|
19
|
-
}
|
|
20
|
-
/** Load the persisted device secret, generating and saving one if absent. */
|
|
21
|
-
export function getOrCreateDeviceSecret() {
|
|
22
|
-
const p = deviceSecretPath();
|
|
23
|
-
if (existsSync(p)) {
|
|
24
|
-
const existing = readFileSync(p, "utf8").trim();
|
|
25
|
-
if (existing)
|
|
26
|
-
return existing;
|
|
27
|
-
}
|
|
28
|
-
const secret = generateDeviceSecret();
|
|
29
|
-
const dir = path.dirname(p);
|
|
30
|
-
if (!existsSync(dir))
|
|
31
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
32
|
-
writeFileSync(p, secret, { mode: 0o600 });
|
|
33
|
-
return secret;
|
|
34
|
-
}
|
package/dist/cli/client.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// Thin fetch wrapper for dreamlake-server and BSS. Zero-dependency —
|
|
2
|
-
// uses Node's global `fetch`. Mirrors the per-call httpx usage scattered
|
|
3
|
-
// across dreamlake-py's cli/ modules, centralized here.
|
|
4
|
-
//
|
|
5
|
-
// On a non-2xx response we throw Error("(status) body") so callers get a
|
|
6
|
-
// clean message (matching lakeshore's auth/commands.ts error style).
|
|
7
|
-
// Callers that need to branch on status (404/409) catch HttpError and
|
|
8
|
-
// inspect `.status`.
|
|
9
|
-
export class HttpError extends Error {
|
|
10
|
-
status;
|
|
11
|
-
body;
|
|
12
|
-
constructor(status, body) {
|
|
13
|
-
super(`(${status}) ${HttpError.extractMessage(body)}`);
|
|
14
|
-
this.name = "HttpError";
|
|
15
|
-
this.status = status;
|
|
16
|
-
this.body = body;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Pull a human message out of an error body. The server returns
|
|
20
|
-
* `{ "error": "..." }` (or `{ "message": "..." }`); fall back to the
|
|
21
|
-
* raw text, trimmed, for non-JSON bodies (e.g. HTML 404 pages).
|
|
22
|
-
*/
|
|
23
|
-
static extractMessage(body) {
|
|
24
|
-
if (!body)
|
|
25
|
-
return "request failed";
|
|
26
|
-
try {
|
|
27
|
-
const parsed = JSON.parse(body);
|
|
28
|
-
if (parsed.error)
|
|
29
|
-
return parsed.error;
|
|
30
|
-
if (parsed.message)
|
|
31
|
-
return parsed.message;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
// not JSON
|
|
35
|
-
}
|
|
36
|
-
const oneLine = body.replace(/\s+/g, " ").trim();
|
|
37
|
-
return oneLine.length > 200 ? oneLine.slice(0, 200) + "…" : oneLine;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function buildUrl(base, path, query) {
|
|
41
|
-
const url = new URL(base.replace(/\/+$/, "") + path);
|
|
42
|
-
if (query) {
|
|
43
|
-
for (const [k, v] of Object.entries(query)) {
|
|
44
|
-
if (v === undefined || v === null)
|
|
45
|
-
continue;
|
|
46
|
-
url.searchParams.set(k, String(v));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return url.toString();
|
|
50
|
-
}
|
|
51
|
-
function authHeaders(token) {
|
|
52
|
-
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Issue a request and parse a JSON response. Throws HttpError on non-2xx.
|
|
56
|
-
* Returns the parsed JSON (or `undefined` for empty bodies).
|
|
57
|
-
*/
|
|
58
|
-
export async function requestJson(base, path, opts = {}) {
|
|
59
|
-
const { method = "GET", json, query, token, headers = {}, timeoutMs = 30000 } = opts;
|
|
60
|
-
const init = {
|
|
61
|
-
method,
|
|
62
|
-
headers: {
|
|
63
|
-
...authHeaders(token),
|
|
64
|
-
...(json !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
65
|
-
...headers,
|
|
66
|
-
},
|
|
67
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
68
|
-
};
|
|
69
|
-
if (json !== undefined)
|
|
70
|
-
init.body = JSON.stringify(json);
|
|
71
|
-
const res = await fetch(buildUrl(base, path, query), init);
|
|
72
|
-
if (!res.ok) {
|
|
73
|
-
const body = await res.text().catch(() => "");
|
|
74
|
-
throw new HttpError(res.status, body);
|
|
75
|
-
}
|
|
76
|
-
const text = await res.text();
|
|
77
|
-
if (!text)
|
|
78
|
-
return undefined;
|
|
79
|
-
try {
|
|
80
|
-
return JSON.parse(text);
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return text;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/** GET a raw Response (for streaming downloads). Throws HttpError on non-2xx. */
|
|
87
|
-
export async function requestRaw(url, opts = {}) {
|
|
88
|
-
const { token, timeoutMs = 300000, headers = {} } = opts;
|
|
89
|
-
const res = await fetch(url, {
|
|
90
|
-
headers: { ...authHeaders(token), ...headers },
|
|
91
|
-
redirect: "follow",
|
|
92
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
93
|
-
});
|
|
94
|
-
if (!res.ok) {
|
|
95
|
-
const body = await res.text().catch(() => "");
|
|
96
|
-
throw new HttpError(res.status, body);
|
|
97
|
-
}
|
|
98
|
-
return res;
|
|
99
|
-
}
|
package/dist/cli/config.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
// Shared connection config — TypeScript parity with dreamlake-py's
|
|
2
|
-
// cli/_config.py. Resolution precedence (high → low):
|
|
3
|
-
//
|
|
4
|
-
// remote/bss/token: flag → env var → saved auth.yml → default
|
|
5
|
-
// namespace: target's @ns → auth.yml → GET /auth/me → JWT decode
|
|
6
|
-
//
|
|
7
|
-
// Env vars: DREAMLAKE_REMOTE, DREAMLAKE_BSS_URL, DREAMLAKE_API_KEY
|
|
8
|
-
// Defaults: dreamlake-server :10334, BSS :10234
|
|
9
|
-
import { currentEnv } from "./auth/credentials.js";
|
|
10
|
-
export const DEFAULT_REMOTE = "http://localhost:10334";
|
|
11
|
-
export const DEFAULT_BSS = "http://localhost:10234";
|
|
12
|
-
function stripTrailingSlash(url) {
|
|
13
|
-
return url.replace(/\/+$/, "");
|
|
14
|
-
}
|
|
15
|
-
/** dreamlake-server base URL. flag > env > active environment > default. */
|
|
16
|
-
export function resolveRemote(flag) {
|
|
17
|
-
const v = flag ||
|
|
18
|
-
process.env.DREAMLAKE_REMOTE ||
|
|
19
|
-
currentEnv()?.server ||
|
|
20
|
-
DEFAULT_REMOTE;
|
|
21
|
-
return stripTrailingSlash(v);
|
|
22
|
-
}
|
|
23
|
-
/** BSS (big-streaming-server) base URL. */
|
|
24
|
-
export function resolveBss(flag) {
|
|
25
|
-
const v = flag ||
|
|
26
|
-
process.env.DREAMLAKE_BSS_URL ||
|
|
27
|
-
currentEnv()?.bss ||
|
|
28
|
-
DEFAULT_BSS;
|
|
29
|
-
return stripTrailingSlash(v);
|
|
30
|
-
}
|
|
31
|
-
/** Bearer token, or null if unauthenticated. */
|
|
32
|
-
export function resolveToken(flag) {
|
|
33
|
-
return flag || process.env.DREAMLAKE_API_KEY || currentEnv()?.token || null;
|
|
34
|
-
}
|
|
35
|
-
/** Decode a JWT payload without verifying the signature. Returns {} on failure. */
|
|
36
|
-
export function decodeJwtPayload(token) {
|
|
37
|
-
try {
|
|
38
|
-
const part = token.split(".")[1];
|
|
39
|
-
if (!part)
|
|
40
|
-
return {};
|
|
41
|
-
const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
|
|
42
|
-
const json = Buffer.from(b64, "base64").toString("utf8");
|
|
43
|
-
const parsed = JSON.parse(json);
|
|
44
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Resolve the current user's namespace slug. Mirrors _config.py:
|
|
52
|
-
* explicit (target @ns) → saved auth.yml → GET /auth/me → JWT decode.
|
|
53
|
-
* Returns null when nothing resolves (caller prints the login hint).
|
|
54
|
-
*/
|
|
55
|
-
export async function resolveNamespace(explicit, opts) {
|
|
56
|
-
if (explicit)
|
|
57
|
-
return explicit;
|
|
58
|
-
const saved = currentEnv();
|
|
59
|
-
if (saved?.namespace)
|
|
60
|
-
return saved.namespace;
|
|
61
|
-
// Query server for the authoritative slug.
|
|
62
|
-
try {
|
|
63
|
-
const res = await fetch(`${opts.remote}/auth/me`, {
|
|
64
|
-
headers: { Authorization: `Bearer ${opts.token}` },
|
|
65
|
-
signal: AbortSignal.timeout(5000),
|
|
66
|
-
});
|
|
67
|
-
if (res.ok) {
|
|
68
|
-
const body = (await res.json());
|
|
69
|
-
const slug = body.namespace?.slug;
|
|
70
|
-
if (slug)
|
|
71
|
-
return slug;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// fall through to JWT decode
|
|
76
|
-
}
|
|
77
|
-
// Fallback: decode from JWT (stale but better than nothing).
|
|
78
|
-
const payload = decodeJwtPayload(opts.token);
|
|
79
|
-
const fromJwt = payload.username ?? payload.sub;
|
|
80
|
-
return typeof fromJwt === "string" ? fromJwt : null;
|
|
81
|
-
}
|