@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
package/bin/dreamlake.js
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// (workspace dev — no `pnpm build` required).
|
|
2
|
+
"use strict";
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
function isMusl() {
|
|
6
|
+
if (process.platform !== "linux") return false;
|
|
7
|
+
try {
|
|
8
|
+
return !process.report.getReport().header.glibcVersionRuntime;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
9
13
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
14
|
+
const key = process.platform + "-" + process.arch + (isMusl() ? "-musl" : "");
|
|
15
|
+
const pkg = "@dreamlake/dreamlake-cli-" + key;
|
|
16
|
+
const bin = process.platform === "win32" ? "dreamlake.exe" : "dreamlake";
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.error("dreamlake: neither dist/cli/index.js nor src/cli/index.ts found");
|
|
27
|
-
process.exit(2);
|
|
18
|
+
let binPath;
|
|
19
|
+
try {
|
|
20
|
+
binPath = require.resolve(pkg + "/" + bin);
|
|
21
|
+
} catch {
|
|
22
|
+
process.stderr.write(
|
|
23
|
+
"dreamlake: no binary for " + key + ".\n" +
|
|
24
|
+
"The platform package " + pkg + " is not installed. npm skips it when\n" +
|
|
25
|
+
"os/cpu do not match, and --no-optional skips it always. Reinstall without\n" +
|
|
26
|
+
"--no-optional, or use the native installer:\n" +
|
|
27
|
+
" curl -fsSL https://dl.dreamlake.ai/install.sh | sh\n",
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
let r = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
33
|
+
if (r.error && r.error.code === "EACCES") {
|
|
34
|
+
// Belt and braces: if a tarball ever ships the binary without the exec
|
|
35
|
+
// bit again, repair it once and retry rather than stranding the user.
|
|
36
|
+
try {
|
|
37
|
+
require("node:fs").chmodSync(binPath, 0o755);
|
|
38
|
+
r = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
if (r.error) {
|
|
42
|
+
process.stderr.write("dreamlake: " + r.error.message + "\n");
|
|
32
43
|
process.exit(1);
|
|
33
|
-
}
|
|
44
|
+
}
|
|
45
|
+
process.exit(r.status === null ? 1 : r.status);
|
package/package.json
CHANGED
|
@@ -1,37 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamlake/dreamlake-cli",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
"version": "0.9.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/dreamlake-ai/dreamlake-cli.git"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://cli.dreamlake.ai",
|
|
9
|
+
"description": "dreamlake — data-warehouse CLI. The `dreamlake` command uploads/downloads assets, manages episodes, bindrs, datasets, organizations and teams, and versions artifacts, workflow specs and pipelines against a DreamLake server + BSS.",
|
|
8
10
|
"bin": {
|
|
9
11
|
"dreamlake": "bin/dreamlake.js"
|
|
10
12
|
},
|
|
11
13
|
"files": [
|
|
12
|
-
"bin"
|
|
13
|
-
"dist",
|
|
14
|
-
"!dist/**/*.map"
|
|
14
|
+
"bin"
|
|
15
15
|
],
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc",
|
|
21
|
-
"cli": "tsx src/cli/index.ts",
|
|
22
|
-
"test": "tsc -p tsconfig.json --noEmit && node --import tsx --test src/cli/__tests__/*.test.ts",
|
|
23
|
-
"docs:dev": "pnpm -C docs dev",
|
|
24
|
-
"docs:build": "pnpm -C docs build",
|
|
25
|
-
"docs:preview": "pnpm -C docs preview"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@types/node": "^22.10.0",
|
|
29
|
-
"tsx": "^4.19.0",
|
|
30
|
-
"typescript": "^5.7.0"
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
31
18
|
},
|
|
32
|
-
"
|
|
33
|
-
"@
|
|
34
|
-
"
|
|
35
|
-
"
|
|
19
|
+
"optionalDependencies": {
|
|
20
|
+
"@dreamlake/dreamlake-cli-darwin-arm64": "0.9.2",
|
|
21
|
+
"@dreamlake/dreamlake-cli-darwin-x64": "0.9.2",
|
|
22
|
+
"@dreamlake/dreamlake-cli-linux-x64": "0.9.2",
|
|
23
|
+
"@dreamlake/dreamlake-cli-linux-arm64": "0.9.2",
|
|
24
|
+
"@dreamlake/dreamlake-cli-linux-x64-musl": "0.9.2",
|
|
25
|
+
"@dreamlake/dreamlake-cli-linux-arm64-musl": "0.9.2",
|
|
26
|
+
"@dreamlake/dreamlake-cli-win32-x64": "0.9.2",
|
|
27
|
+
"@dreamlake/dreamlake-cli-win32-arm64": "0.9.2"
|
|
36
28
|
}
|
|
37
29
|
}
|
package/README.md
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
# dreamlake-cli
|
|
2
|
-
|
|
3
|
-
`dreamlake` — a data-warehouse CLI for DreamLake. Uploads/downloads assets and
|
|
4
|
-
manages episodes, bindrs, and datasets against a DreamLake server + BSS
|
|
5
|
-
(big-streaming-server).
|
|
6
|
-
|
|
7
|
-
It is a TypeScript port of the CLI shipped in
|
|
8
|
-
[`dreamlake-py`](../dreamlake-workspace/dreamlake-py) (`dreamlake.cli`), built
|
|
9
|
-
on the [`lakeshore`](../lakeshore) conventions (Commander + native `fetch`,
|
|
10
|
-
pure ESM, token saved to a flat YAML file instead of the system keyring).
|
|
11
|
-
|
|
12
|
-
> The compiled `dreamlake` binary shares its name with the Python `dreamlake`
|
|
13
|
-
> entry point — this CLI is the TypeScript replacement. When both are on a
|
|
14
|
-
> machine, `PATH` order decides which one runs.
|
|
15
|
-
|
|
16
|
-
## Install / run
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
pnpm install
|
|
20
|
-
pnpm cli --help # dev — runs the TS source through tsx
|
|
21
|
-
pnpm build # emit dist/
|
|
22
|
-
node bin/dreamlake.js … # prod — runs compiled dist/ (falls back to tsx in dev)
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Auth & environments
|
|
26
|
-
|
|
27
|
-
The CLI knows two built-in environments — **`staging`** and **`prod`** — so you
|
|
28
|
-
never type their URLs. Each environment keeps its own token; switch between them
|
|
29
|
-
with one command.
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
dreamlake login --env staging # device-authorization flow (opens browser)
|
|
33
|
-
dreamlake login --env prod # built-in URLs; no --url/--bss needed
|
|
34
|
-
dreamlake login # re-auth the active env (or staging if none)
|
|
35
|
-
|
|
36
|
-
dreamlake env list # show logged-in envs (* = active)
|
|
37
|
-
dreamlake env use prod # switch active env — no URLs, no re-login
|
|
38
|
-
dreamlake env remove staging # forget a saved env
|
|
39
|
-
|
|
40
|
-
dreamlake profile # current user (shows [env: ...])
|
|
41
|
-
dreamlake logout # log out of the active env only
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Custom / self-hosted environments — define once, then switch by name forever:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
dreamlake login --env dev --url https://dev-api.example.com --bss https://dev-bss.example.com
|
|
48
|
-
dreamlake env use dev
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Flags / env vars (`DREAMLAKE_REMOTE`, `DREAMLAKE_BSS_URL`, `DREAMLAKE_API_KEY`)
|
|
52
|
-
still override the active environment for one-off commands. `--token <jwt>` saves
|
|
53
|
-
a token directly (skip the device flow). `--auth <url>` overrides the vuer-auth
|
|
54
|
-
server (built-ins set this automatically; staging needs `staging-auth.vuer.ai`).
|
|
55
|
-
|
|
56
|
-
Credentials live in `~/.config/dreamlake/auth.yml`
|
|
57
|
-
(`$XDG_CONFIG_HOME/dreamlake/auth.yml`, `chmod 600`) as a multi-environment file:
|
|
58
|
-
|
|
59
|
-
```yaml
|
|
60
|
-
current: prod
|
|
61
|
-
envs:
|
|
62
|
-
staging: { server, bss, auth, namespace, token }
|
|
63
|
-
prod: { server, bss, auth, namespace, token }
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Targets
|
|
67
|
-
|
|
68
|
-
Most commands take a target string:
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
--episode space[@namespace][:episode] # e.g. robotics@alice:2026/q1/run-042
|
|
72
|
-
--project space[@namespace] # e.g. robotics@alice
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
When `@namespace` is omitted it is resolved from your token (`/auth/me`).
|
|
76
|
-
|
|
77
|
-
## Commands
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
# Upload — single file, or a flat directory (resumable). Type auto-detected.
|
|
81
|
-
dreamlake upload ./mic.wav --episode robotics@alice:run-042 --to /microphone/front
|
|
82
|
-
dreamlake upload ./clip.mp4 --episode robotics:run-042 --to /camera/front --bindr cam-set
|
|
83
|
-
dreamlake upload ./capture/ --episode robotics@alice:run-042 --to /sensors --yes
|
|
84
|
-
|
|
85
|
-
# Download — a single file, or a whole folder/episode recursively.
|
|
86
|
-
dreamlake download --episode robotics@alice:run-042 --from /camera/front/clip.mp4 -o ./clip.mp4
|
|
87
|
-
dreamlake download --episode robotics@alice:run-042 --from /camera -o ./out/
|
|
88
|
-
|
|
89
|
-
# Projects. Private by default — pass --public (or --visibility public) to share.
|
|
90
|
-
dreamlake create project my-robots --description "robot captures" # private
|
|
91
|
-
dreamlake create project shared-set --public # public
|
|
92
|
-
dreamlake update project my-robots --public # flip visibility later
|
|
93
|
-
dreamlake list project
|
|
94
|
-
dreamlake delete project my-robots # HARD-deletes the whole subtree
|
|
95
|
-
|
|
96
|
-
# List — assets (default), or projects / bindrs / datasets / episodes.
|
|
97
|
-
dreamlake list --episode robotics@alice:run-042 --type video
|
|
98
|
-
dreamlake list project
|
|
99
|
-
dreamlake list bindr --project robotics@alice
|
|
100
|
-
dreamlake list dataset --project robotics@alice --json
|
|
101
|
-
dreamlake list episode --project robotics@alice
|
|
102
|
-
|
|
103
|
-
# Delete uploaded data (all HARD deletes — confirm prompt unless --yes).
|
|
104
|
-
dreamlake delete file /camera/front/clip.mp4 --episode robotics@alice:run-042
|
|
105
|
-
dreamlake delete episode run-042 --project robotics@alice
|
|
106
|
-
|
|
107
|
-
# Bindrs (members are episodes, matched by node-path glob).
|
|
108
|
-
dreamlake create bindr front-cam --project robotics@alice --episode "camera/front/*"
|
|
109
|
-
dreamlake update bindr front-cam --project robotics@alice --add "2026/04/*"
|
|
110
|
-
dreamlake delete bindr front-cam --project robotics@alice
|
|
111
|
-
|
|
112
|
-
# Datasets (members are bindrs, matched by name glob).
|
|
113
|
-
dreamlake create dataset train-v1 --project robotics@alice --tags robotics,training
|
|
114
|
-
dreamlake update dataset train-v1 --project robotics@alice --add "front-*"
|
|
115
|
-
dreamlake delete dataset train-v1 --project robotics@alice
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
> Videos are just files — upload/list/download them with `upload` / `list --type video`
|
|
119
|
-
> / `download`, no dedicated `video` command needed.
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# Organizations (GraphQL-backed; same token). Members resolve by email/name.
|
|
123
|
-
dreamlake org list
|
|
124
|
-
dreamlake org show my-org
|
|
125
|
-
dreamlake org create my-org --name "My Org" --description "..." # you become OWNER + a namespace is created
|
|
126
|
-
dreamlake org update my-org --description "..."
|
|
127
|
-
dreamlake org delete my-org # must have no remaining projects
|
|
128
|
-
dreamlake org leave my-org
|
|
129
|
-
dreamlake org member list my-org
|
|
130
|
-
dreamlake org member add my-org alice@example.com --role member
|
|
131
|
-
dreamlake org member role my-org alice@example.com --role owner
|
|
132
|
-
dreamlake org member remove my-org alice@example.com
|
|
133
|
-
|
|
134
|
-
# Teams (scoped to an org; nestable via --parent; visible|secret).
|
|
135
|
-
dreamlake team list # all teams you belong to
|
|
136
|
-
dreamlake team list my-org # teams in one org
|
|
137
|
-
dreamlake team show my-org backend
|
|
138
|
-
dreamlake team create my-org backend --name "Backend" --visibility secret
|
|
139
|
-
dreamlake team create my-org api --parent backend # nested team
|
|
140
|
-
dreamlake team update my-org backend --visibility visible
|
|
141
|
-
dreamlake team delete my-org backend # must have no child teams
|
|
142
|
-
dreamlake team leave my-org backend
|
|
143
|
-
dreamlake team member list my-org backend
|
|
144
|
-
dreamlake team member add my-org backend alice@example.com --role maintainer
|
|
145
|
-
dreamlake team member role my-org backend alice@example.com --role member
|
|
146
|
-
dreamlake team member remove my-org backend alice@example.com
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Configuration
|
|
150
|
-
|
|
151
|
-
Resolution precedence (high → low): flag → env var → saved `auth.yml` → default.
|
|
152
|
-
|
|
153
|
-
| Setting | Env var | Default |
|
|
154
|
-
| --- | --- | --- |
|
|
155
|
-
| DreamLake server | `DREAMLAKE_REMOTE` | `http://localhost:10334` |
|
|
156
|
-
| BSS server | `DREAMLAKE_BSS_URL` | `http://localhost:10234` |
|
|
157
|
-
| Token | `DREAMLAKE_API_KEY` | (from `auth.yml`) |
|
|
158
|
-
|
|
159
|
-
## Not implemented
|
|
160
|
-
|
|
161
|
-
By design, the CLI excludes the parts of `dreamlake-py` that aren't
|
|
162
|
-
command-line-shaped: `vectorize` (CLIP/LLaVA inference + Zaku + Qdrant), the
|
|
163
|
-
SDK-only rich types (`Video`/`TextTrack`/`VectorIndex`), and the deprecated
|
|
164
|
-
Episode-tracking stack.
|
|
165
|
-
|
|
166
|
-
## Development
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
pnpm test # tsc --noEmit + node --test (target/glob/kinds/credentials)
|
|
170
|
-
pnpm build # tsc → dist/
|
|
171
|
-
```
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
// `dreamlake login | logout | profile`.
|
|
2
|
-
//
|
|
3
|
-
// login: runs the device-authorization flow (default) or saves a token
|
|
4
|
-
// passed via --token (lakeshore-style escape hatch). On success the
|
|
5
|
-
// dreamlake token + resolved server/bss/namespace are written to
|
|
6
|
-
// ~/.config/dreamlake/auth.yml (NOT the system keyring — see credentials.ts).
|
|
7
|
-
//
|
|
8
|
-
// profile: hits GET /auth/me, falling back to a local JWT decode when the
|
|
9
|
-
// server is unreachable. logout: deletes the auth file.
|
|
10
|
-
import { spawn } from "node:child_process";
|
|
11
|
-
import { resolveRemote, resolveToken, decodeJwtPayload } from "../config.js";
|
|
12
|
-
import { emitJson, renderTable } from "../helpers.js";
|
|
13
|
-
import { authFilePath, builtinEnv, BUILTIN_ENVS, currentEnvName, listEnvs, removeEnv, saveEnv, useEnv, } from "./credentials.js";
|
|
14
|
-
import { getOrCreateDeviceSecret } from "./device-secret.js";
|
|
15
|
-
import { AuthorizationDeniedError, DeviceCodeExpiredError, DeviceFlowClient, TokenExchangeError, } from "./device-flow.js";
|
|
16
|
-
async function fetchAuthMe(remote, token) {
|
|
17
|
-
try {
|
|
18
|
-
const res = await fetch(`${remote}/auth/me`, {
|
|
19
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
20
|
-
signal: AbortSignal.timeout(5000),
|
|
21
|
-
});
|
|
22
|
-
if (res.ok)
|
|
23
|
-
return (await res.json());
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// unreachable
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
function openBrowser(url) {
|
|
31
|
-
const cmd = process.platform === "darwin"
|
|
32
|
-
? "open"
|
|
33
|
-
: process.platform === "win32"
|
|
34
|
-
? "start"
|
|
35
|
-
: "xdg-open";
|
|
36
|
-
try {
|
|
37
|
-
const child = spawn(cmd, [url], { stdio: "ignore", detached: true });
|
|
38
|
-
child.on("error", () => { });
|
|
39
|
-
child.unref();
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// best-effort
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
export async function runLogin(args) {
|
|
46
|
-
// Resolve which environment we're logging into and its URLs.
|
|
47
|
-
const envName = args.env ?? currentEnvName() ?? "staging";
|
|
48
|
-
const builtin = builtinEnv(envName);
|
|
49
|
-
const remote = (args.url ?? builtin?.server)?.replace(/\/+$/, "");
|
|
50
|
-
const bss = (args.bss ?? builtin?.bss)?.replace(/\/+$/, "");
|
|
51
|
-
const auth = args.auth ?? builtin?.auth;
|
|
52
|
-
if (!remote || !bss) {
|
|
53
|
-
process.stderr.write(`✗ unknown environment '${envName}'. Either use a built-in (${Object.keys(BUILTIN_ENVS).join(", ")}) ` +
|
|
54
|
-
`or define one: dreamlake login --env ${envName} --url <api> --bss <bss>\n`);
|
|
55
|
-
return 1;
|
|
56
|
-
}
|
|
57
|
-
process.stderr.write(`Logging into environment '${envName}' (${remote})\n`);
|
|
58
|
-
// ── Escape hatch: direct token (lakeshore style) ──────────────────
|
|
59
|
-
if (args.token) {
|
|
60
|
-
const me = await fetchAuthMe(remote, args.token);
|
|
61
|
-
if (!me) {
|
|
62
|
-
process.stderr.write(`✗ token validation failed against ${remote}/auth/me\n`);
|
|
63
|
-
return 1;
|
|
64
|
-
}
|
|
65
|
-
const namespace = me.namespace?.slug ?? "";
|
|
66
|
-
const p = saveEnv(envName, { server: remote, bss, auth, namespace, token: args.token });
|
|
67
|
-
process.stdout.write(`✓ Saved env '${envName}' to ${p}\n` +
|
|
68
|
-
` server: ${remote}\n` +
|
|
69
|
-
` bss: ${bss}\n` +
|
|
70
|
-
` namespace: ${namespace || "(none)"}\n`);
|
|
71
|
-
return 0;
|
|
72
|
-
}
|
|
73
|
-
// ── Device authorization flow ─────────────────────────────────────
|
|
74
|
-
const deviceSecret = getOrCreateDeviceSecret();
|
|
75
|
-
const client = new DeviceFlowClient(deviceSecret, remote, auth);
|
|
76
|
-
let flow;
|
|
77
|
-
try {
|
|
78
|
-
process.stderr.write("Initializing device authorization...\n\n");
|
|
79
|
-
flow = await client.startDeviceFlow();
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
process.stderr.write(`✗ ${err.message}\n`);
|
|
83
|
-
return 1;
|
|
84
|
-
}
|
|
85
|
-
process.stdout.write(`DEVICE AUTHORIZATION REQUIRED\n\n` +
|
|
86
|
-
` 1. Visit: ${flow.verificationUri}\n` +
|
|
87
|
-
` 2. Enter code: ${flow.userCode}\n\n` +
|
|
88
|
-
` (code expires in ${Math.floor(flow.expiresIn / 60)} minutes)\n\n`);
|
|
89
|
-
if (!args.noBrowser) {
|
|
90
|
-
openBrowser(flow.verificationUriComplete);
|
|
91
|
-
}
|
|
92
|
-
process.stderr.write("Waiting for authorization...\n");
|
|
93
|
-
let vuerToken;
|
|
94
|
-
try {
|
|
95
|
-
vuerToken = await client.pollForToken(120, (elapsed) => {
|
|
96
|
-
if (process.stderr.isTTY) {
|
|
97
|
-
process.stderr.write(`\r waiting (${elapsed}s)`);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
if (process.stderr.isTTY)
|
|
101
|
-
process.stderr.write("\n");
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
if (process.stderr.isTTY)
|
|
105
|
-
process.stderr.write("\n");
|
|
106
|
-
if (err instanceof DeviceCodeExpiredError) {
|
|
107
|
-
process.stderr.write("✗ Device code expired. Run 'dreamlake login' again.\n");
|
|
108
|
-
}
|
|
109
|
-
else if (err instanceof AuthorizationDeniedError) {
|
|
110
|
-
process.stderr.write("✗ Authorization denied.\n");
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
process.stderr.write(`✗ ${err.message}\n`);
|
|
114
|
-
}
|
|
115
|
-
return 1;
|
|
116
|
-
}
|
|
117
|
-
let token;
|
|
118
|
-
try {
|
|
119
|
-
process.stderr.write("Exchanging token with DreamLake server...\n");
|
|
120
|
-
token = await client.exchangeToken(vuerToken);
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
const msg = err instanceof TokenExchangeError ? err.message : err.message;
|
|
124
|
-
process.stderr.write(`✗ ${msg}\n`);
|
|
125
|
-
return 1;
|
|
126
|
-
}
|
|
127
|
-
const me = await fetchAuthMe(remote, token);
|
|
128
|
-
const namespace = me?.namespace?.slug ?? "";
|
|
129
|
-
saveEnv(envName, { server: remote, bss, auth, namespace, token });
|
|
130
|
-
process.stdout.write(`✓ Logged in to '${envName}' (now active)\n` +
|
|
131
|
-
` server: ${remote}\n` +
|
|
132
|
-
` bss: ${bss}\n` +
|
|
133
|
-
` namespace: ${namespace || "(none)"}\n`);
|
|
134
|
-
return 0;
|
|
135
|
-
}
|
|
136
|
-
// ─── logout ──────────────────────────────────────────────────────────
|
|
137
|
-
export async function runLogout() {
|
|
138
|
-
const name = currentEnvName();
|
|
139
|
-
if (!name) {
|
|
140
|
-
process.stdout.write(`No active environment (${authFilePath()})\n`);
|
|
141
|
-
return 0;
|
|
142
|
-
}
|
|
143
|
-
removeEnv(name);
|
|
144
|
-
const next = currentEnvName();
|
|
145
|
-
process.stdout.write(`✓ Logged out of '${name}'.` +
|
|
146
|
-
(next ? ` Active environment is now '${next}'.\n` : " No environments left.\n"));
|
|
147
|
-
return 0;
|
|
148
|
-
}
|
|
149
|
-
// ─── env (environment switching) ──────────────────────────────────────
|
|
150
|
-
export async function runEnvList(opts) {
|
|
151
|
-
const envs = listEnvs();
|
|
152
|
-
if (opts.json) {
|
|
153
|
-
emitJson(envs.map((e) => ({
|
|
154
|
-
name: e.name,
|
|
155
|
-
current: e.current,
|
|
156
|
-
server: e.env.server,
|
|
157
|
-
bss: e.env.bss,
|
|
158
|
-
namespace: e.env.namespace,
|
|
159
|
-
})));
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
|
-
process.stdout.write("Environments\n\n");
|
|
163
|
-
if (envs.length === 0) {
|
|
164
|
-
process.stdout.write(` (none logged in)\n hint: dreamlake login --env staging (built-in: ${Object.keys(BUILTIN_ENVS).join(", ")})\n`);
|
|
165
|
-
return 0;
|
|
166
|
-
}
|
|
167
|
-
const rows = envs.map((e) => ({
|
|
168
|
-
"": e.current ? "*" : "",
|
|
169
|
-
name: e.name,
|
|
170
|
-
server: e.env.server,
|
|
171
|
-
namespace: e.env.namespace || "(none)",
|
|
172
|
-
}));
|
|
173
|
-
process.stdout.write(renderTable(rows, ["", "name", "server", "namespace"]));
|
|
174
|
-
return 0;
|
|
175
|
-
}
|
|
176
|
-
export async function runEnvUse(name) {
|
|
177
|
-
if (useEnv(name)) {
|
|
178
|
-
process.stdout.write(`✓ Active environment is now '${name}'\n`);
|
|
179
|
-
return 0;
|
|
180
|
-
}
|
|
181
|
-
const known = listEnvs().map((e) => e.name);
|
|
182
|
-
process.stderr.write(`✗ environment '${name}' is not logged in.\n` +
|
|
183
|
-
(known.length ? ` logged in: ${known.join(", ")}\n` : "") +
|
|
184
|
-
` log in with: dreamlake login --env ${name}\n`);
|
|
185
|
-
return 1;
|
|
186
|
-
}
|
|
187
|
-
export async function runEnvRemove(name) {
|
|
188
|
-
if (removeEnv(name)) {
|
|
189
|
-
process.stdout.write(`✓ Removed environment '${name}'\n`);
|
|
190
|
-
return 0;
|
|
191
|
-
}
|
|
192
|
-
process.stderr.write(`✗ environment '${name}' not found\n`);
|
|
193
|
-
return 1;
|
|
194
|
-
}
|
|
195
|
-
export async function runProfile(args) {
|
|
196
|
-
const token = resolveToken();
|
|
197
|
-
if (!token) {
|
|
198
|
-
process.stdout.write("Not logged in. Run: dreamlake login\n");
|
|
199
|
-
return 1;
|
|
200
|
-
}
|
|
201
|
-
const remote = resolveRemote(args.url);
|
|
202
|
-
const me = await fetchAuthMe(remote, token);
|
|
203
|
-
if (me) {
|
|
204
|
-
const envName = currentEnvName();
|
|
205
|
-
process.stdout.write(`Current DreamLake user${envName ? ` [env: ${envName}]` : ""}\n`);
|
|
206
|
-
for (const field of ["id", "sub", "username", "name", "email"]) {
|
|
207
|
-
if (me[field] != null)
|
|
208
|
-
process.stdout.write(` ${field}: ${me[field]}\n`);
|
|
209
|
-
}
|
|
210
|
-
if (me.namespace?.slug) {
|
|
211
|
-
process.stdout.write(` namespace: ${me.namespace.slug} (id: ${me.namespace.id ?? "?"})\n`);
|
|
212
|
-
}
|
|
213
|
-
if (me.createdAt) {
|
|
214
|
-
process.stdout.write(` member since: ${String(me.createdAt).slice(0, 10)}\n`);
|
|
215
|
-
}
|
|
216
|
-
return 0;
|
|
217
|
-
}
|
|
218
|
-
// Fallback: decode JWT locally.
|
|
219
|
-
process.stderr.write("⚠ could not reach server, showing local token info.\n");
|
|
220
|
-
const payload = decodeJwtPayload(token);
|
|
221
|
-
if (Object.keys(payload).length === 0) {
|
|
222
|
-
process.stdout.write("Failed to decode token. Please log in again.\n");
|
|
223
|
-
return 1;
|
|
224
|
-
}
|
|
225
|
-
process.stdout.write("Current DreamLake user (from token)\n");
|
|
226
|
-
for (const field of ["sub", "username", "name", "email"]) {
|
|
227
|
-
if (payload[field] != null) {
|
|
228
|
-
process.stdout.write(` ${field}: ${payload[field]}\n`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
const exp = payload.exp;
|
|
232
|
-
if (typeof exp === "number") {
|
|
233
|
-
const expired = exp < Date.now() / 1000;
|
|
234
|
-
process.stdout.write(` expires: ${new Date(exp * 1000).toISOString()}${expired ? " (EXPIRED)" : ""}\n`);
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
process.stdout.write(" expires: never (long-lived token)\n");
|
|
238
|
-
}
|
|
239
|
-
return 0;
|
|
240
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Authentication constants — port of dreamlake-py's auth/constants.py.
|
|
2
|
-
/**
|
|
3
|
-
* Vuer-auth server URL (device-flow provider). Resolved lazily so a
|
|
4
|
-
* `--auth` flag / late env assignment can override it. NOTE: this must
|
|
5
|
-
* match the auth server the TARGET dreamlake-server trusts — e.g.
|
|
6
|
-
* staging-api.dreamlake.ai validates tokens from staging-auth.vuer.ai,
|
|
7
|
-
* not the production auth.vuer.ai (mismatched keys → "No matching key
|
|
8
|
-
* found in JWKS").
|
|
9
|
-
*/
|
|
10
|
-
export function vuerAuthUrl(override) {
|
|
11
|
-
return (override || process.env.VUER_AUTH_URL || "https://auth.vuer.ai").replace(/\/+$/, "");
|
|
12
|
-
}
|
|
13
|
-
/** OAuth client ID for dreamlake. */
|
|
14
|
-
export const CLIENT_ID = "dreamlake-client";
|
|
15
|
-
/** Default OAuth scopes. */
|
|
16
|
-
export const DEFAULT_SCOPE = "openid profile email";
|