@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.
@@ -1,47 +0,0 @@
1
- // Minimal GraphQL client. Organization/team operations live on the
2
- // dreamlake-server GraphQL endpoint (POST {server}/graphql) rather than
3
- // the REST routes the rest of the CLI uses — same bearer token, same host.
4
- import { resolveRemote, resolveToken } from "./config.js";
5
- export class GraphQLClientError extends Error {
6
- }
7
- /**
8
- * Run a GraphQL query/mutation against {server}/graphql with the saved
9
- * token. Throws GraphQLClientError on transport errors or a non-empty
10
- * `errors` array (messages joined). Returns the `data` payload.
11
- */
12
- export async function gql(query, variables = {}, opts = {}) {
13
- const token = resolveToken();
14
- if (!token) {
15
- throw new GraphQLClientError("not authenticated. run 'dreamlake login' first");
16
- }
17
- const remote = resolveRemote();
18
- let res;
19
- try {
20
- res = await fetch(`${remote}/graphql`, {
21
- method: "POST",
22
- headers: {
23
- "Content-Type": "application/json",
24
- Authorization: `Bearer ${token}`,
25
- },
26
- body: JSON.stringify({ query, variables }),
27
- signal: AbortSignal.timeout(opts.timeoutMs ?? 30000),
28
- });
29
- }
30
- catch (err) {
31
- throw new GraphQLClientError(`request failed: ${err.message}`);
32
- }
33
- let body;
34
- try {
35
- body = (await res.json());
36
- }
37
- catch {
38
- throw new GraphQLClientError(`(${res.status}) non-JSON response`);
39
- }
40
- if (body.errors && body.errors.length > 0) {
41
- throw new GraphQLClientError(body.errors.map((e) => e.message).join("; "));
42
- }
43
- if (!res.ok) {
44
- throw new GraphQLClientError(`(${res.status}) request failed`);
45
- }
46
- return body.data;
47
- }
@@ -1,106 +0,0 @@
1
- // Small shared helpers for the dreamlake CLI subcommands. Adapted from
2
- // lakeshore/src/cli/helpers.ts — zero dependencies beyond `yaml` (already
3
- // a dep for the auth file). Keep this file presentation-only: glyphs,
4
- // table rendering, JSON emit, CSV/tag parsing.
5
- import { existsSync, readFileSync } from "node:fs";
6
- import path from "node:path";
7
- import YAML from "yaml";
8
- // ─── ✓ / ✗ / ⚠ glyph wrappers ────────────────────────────────────────
9
- export function ok(line) {
10
- process.stdout.write(`✓ ${line}\n`);
11
- }
12
- export function warn(line) {
13
- process.stdout.write(`⚠ ${line}\n`);
14
- }
15
- export function fail(line) {
16
- process.stderr.write(`✗ ${line}\n`);
17
- }
18
- // ─── splitCsv — comma-separated values → trimmed non-empty list ──────
19
- //
20
- // Used for `--tags a,b,c` and `--bindr name1,name2`. Mirrors the Python
21
- // CLI's `[t.strip() for t in s.split(",") if t.strip()]`.
22
- export function splitCsv(raw) {
23
- if (!raw)
24
- return [];
25
- return raw
26
- .split(",")
27
- .map((s) => s.trim())
28
- .filter((s) => s.length > 0);
29
- }
30
- // ─── renderTable ─────────────────────────────────────────────────────
31
- //
32
- // Minimal monospaced table renderer. Columns are exactly the `cols`
33
- // names; each row may have arbitrary keys (others are ignored).
34
- // Returns the rendered string (caller prints).
35
- export function renderTable(rows, cols) {
36
- if (rows.length === 0) {
37
- return "(none)\n";
38
- }
39
- const stringRows = rows.map((r) => cols.map((c) => stringifyCell(r[c])));
40
- const widths = cols.map((c, i) => Math.max(c.length, ...stringRows.map((r) => r[i].length)));
41
- const lines = [];
42
- // Header.
43
- lines.push(cols
44
- .map((c, i) => c.padEnd(widths[i]))
45
- .join(" ")
46
- .trimEnd());
47
- // Body.
48
- for (const r of stringRows) {
49
- lines.push(r.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd());
50
- }
51
- return lines.join("\n") + "\n";
52
- }
53
- function stringifyCell(v) {
54
- if (v == null)
55
- return "—";
56
- if (typeof v === "string")
57
- return v;
58
- if (typeof v === "number" || typeof v === "boolean")
59
- return String(v);
60
- if (Array.isArray(v))
61
- return v.map(stringifyCell).join(", ");
62
- return JSON.stringify(v);
63
- }
64
- // ─── emitJson — for `--json` / `--json-output` flags ─────────────────
65
- //
66
- // Single canonical JSON emitter. 2-space indent, trailing newline.
67
- export function emitJson(value) {
68
- process.stdout.write(JSON.stringify(value, null, 2) + "\n");
69
- }
70
- // ─── loadStructuredFile — YAML or JSON ───────────────────────────────
71
- export function loadStructuredFile(filePath) {
72
- const resolved = path.resolve(filePath);
73
- if (!existsSync(resolved)) {
74
- throw new Error(`file not found: ${resolved}`);
75
- }
76
- const text = readFileSync(resolved, "utf8");
77
- const trimmed = text.trim();
78
- if (trimmed.startsWith("{")) {
79
- try {
80
- const parsed = JSON.parse(trimmed);
81
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
82
- return parsed;
83
- }
84
- throw new Error("expected an object at top level");
85
- }
86
- catch (err) {
87
- throw new Error(`failed to parse JSON ${resolved}: ${err.message}`);
88
- }
89
- }
90
- try {
91
- const parsed = YAML.parse(text);
92
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
93
- return parsed;
94
- }
95
- throw new Error("expected an object at top level");
96
- }
97
- catch (err) {
98
- throw new Error(`failed to parse YAML ${resolved}: ${err.message}`);
99
- }
100
- }
101
- // ─── humanSize — bytes → "1.5 MB" / "12.3 KB" ───────────────────────
102
- export function humanSize(bytes) {
103
- if (bytes > 1024 * 1024)
104
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
105
- return `${(bytes / 1024).toFixed(1)} KB`;
106
- }
package/dist/cli/index.js DELETED
@@ -1,95 +0,0 @@
1
- // `dreamlake` CLI entry. Wires Commander to subcommand modules. Mirrors
2
- // the command surface of dreamlake-py's `dreamlake.cli` (the data-warehouse
3
- // CLI), implemented in lakeshore's TypeScript conventions.
4
- import { Command } from "commander";
5
- import { authFilePath, BUILTIN_ENVS } from "./auth/credentials.js";
6
- import { runLogin, runLogout, runProfile, runEnvList, runEnvUse, runEnvRemove, } from "./auth/commands.js";
7
- import { registerUploadCommand } from "./upload/index.js";
8
- import { registerDownloadCommand } from "./download/index.js";
9
- import { registerListCommand } from "./list/index.js";
10
- import { registerCreateCommand } from "./create/index.js";
11
- import { registerUpdateCommand } from "./update/index.js";
12
- import { registerDeleteCommand } from "./delete/index.js";
13
- import { registerOrgCommand } from "./org/index.js";
14
- import { registerTeamCommand } from "./team/index.js";
15
- import { registerPipelineCommand } from "./pipeline/index.js";
16
- import { registerWorkflowCommand } from "./workflow/index.js";
17
- async function main(argv) {
18
- const program = new Command();
19
- program
20
- .name("dreamlake")
21
- .description("DreamLake CLI — upload/download assets and manage episodes, bindrs, and datasets.")
22
- .showHelpAfterError();
23
- // ─── auth ────────────────────────────────────────────────────────
24
- //
25
- // Credentials are stored at `$XDG_CONFIG_HOME/dreamlake/auth.yml`
26
- // (default `~/.config/dreamlake/auth.yml`) with chmod 600.
27
- program
28
- .command("login")
29
- .description(`authenticate with DreamLake (built-in envs: ${Object.keys(BUILTIN_ENVS).join(", ")})`)
30
- .option("--env <name>", "environment to log into (built-in or custom; default: active, else staging)")
31
- .option("--url <url>", "DreamLake server URL (for a custom env, or to override)")
32
- .option("--bss <url>", "BSS server URL (for a custom env, or to override)")
33
- .option("--auth <url>", "vuer-auth server URL (must match what the target server trusts)")
34
- .option("--no-browser", "don't open the browser automatically")
35
- .option("--token <jwt>", "save a token directly instead of the device flow")
36
- .action(async (opts) => {
37
- const rc = await runLogin({
38
- env: opts.env,
39
- url: opts.url,
40
- bss: opts.bss,
41
- auth: opts.auth,
42
- noBrowser: opts.browser === false,
43
- token: opts.token,
44
- });
45
- process.exit(rc);
46
- });
47
- program
48
- .command("logout")
49
- .description("log out of the active environment (removes its saved token)")
50
- .action(async () => {
51
- process.exit(await runLogout());
52
- });
53
- program
54
- .command("profile")
55
- .description("show the current authenticated user")
56
- .option("--url <url>", "override the saved server URL")
57
- .action(async (opts) => {
58
- process.exit(await runProfile({ url: opts.url }));
59
- });
60
- // ─── env (switch between logged-in environments) ─────────────────
61
- const env = program
62
- .command("env")
63
- .description(`switch between environments (saved at ${authFilePath()})`);
64
- env
65
- .command("list")
66
- .description("list logged-in environments (* = active)")
67
- .option("--json", "emit JSON")
68
- .action(async (opts) => process.exit(await runEnvList(opts)));
69
- env
70
- .command("use")
71
- .description("switch the active environment")
72
- .argument("<name>", "environment name")
73
- .action(async (name) => process.exit(await runEnvUse(name)));
74
- env
75
- .command("remove")
76
- .description("remove a saved environment")
77
- .argument("<name>", "environment name")
78
- .action(async (name) => process.exit(await runEnvRemove(name)));
79
- // ─── data-warehouse commands ─────────────────────────────────────
80
- registerUploadCommand(program);
81
- registerDownloadCommand(program);
82
- registerListCommand(program);
83
- registerCreateCommand(program);
84
- registerUpdateCommand(program);
85
- registerDeleteCommand(program);
86
- registerOrgCommand(program);
87
- registerTeamCommand(program);
88
- registerPipelineCommand(program);
89
- registerWorkflowCommand(program);
90
- await program.parseAsync(argv);
91
- }
92
- main(process.argv).catch((err) => {
93
- process.stderr.write(`${err.stack ?? err}\n`);
94
- process.exit(1);
95
- });
@@ -1,254 +0,0 @@
1
- // `dreamlake list ...` — assets (default), bindrs, datasets, episodes.
2
- // Port of dreamlake-py's cli/commands/list.py. Where the Python CLI used
3
- // an interactive 20-per-page pager, we fetch all pages and render one
4
- // table (more script-friendly); `--json` emits raw JSON.
5
- import { resolveNamespace, resolveRemote, resolveToken } from "../config.js";
6
- import { emitJson, fail, humanSize, renderTable } from "../helpers.js";
7
- import { fetchAllContents, fetchAllEpisodes, fetchAllPages, fetchProjects, nodePathToCommaPrefix, relPathUnder, resolveProjectCtx, } from "../resources.js";
8
- import { formatProject, formatTarget, parseTarget } from "../target.js";
9
- export async function runListAssets(opts) {
10
- if (!opts.episode) {
11
- fail("--episode is required");
12
- return 1;
13
- }
14
- let t;
15
- try {
16
- t = parseTarget(opts.episode);
17
- }
18
- catch (err) {
19
- fail(err.message);
20
- return 1;
21
- }
22
- const remote = resolveRemote();
23
- const token = resolveToken();
24
- if (!token) {
25
- fail("not authenticated. run 'dreamlake login' first");
26
- return 1;
27
- }
28
- const namespace = await resolveNamespace(t.namespace, { token, remote });
29
- if (!namespace) {
30
- fail("namespace not specified and no authenticated user found. run 'dreamlake login'");
31
- return 1;
32
- }
33
- t.namespace = namespace;
34
- // Resolve the container node (episode root, or project root when the
35
- // target has no episode), then list its files via /nodes/:id/contents.
36
- let containerId;
37
- let containerPrefix;
38
- try {
39
- if (t.episode) {
40
- const episodes = await fetchAllEpisodes(remote, t.namespace, t.project, token);
41
- const ep = episodes.find((e) => e.name === t.episode);
42
- if (!ep) {
43
- fail(`episode '${t.episode}' not found in ${t.project}@${t.namespace}`);
44
- return 1;
45
- }
46
- containerId = ep.id;
47
- containerPrefix = nodePathToCommaPrefix(ep.nodePath ?? `/${t.project}/${t.episode}`);
48
- }
49
- else {
50
- const projects = await fetchProjects(remote, token, t.namespace);
51
- const proj = projects.find((p) => p.slug === t.project);
52
- if (!proj) {
53
- fail(`project '${t.project}' not found in namespace ${t.namespace}`);
54
- return 1;
55
- }
56
- containerId = proj.id;
57
- containerPrefix = `,${t.project},`;
58
- }
59
- const files = await fetchAllContents(remote, token, containerId, opts.type);
60
- let rows = files.map((f) => ({
61
- path: relPathUnder(f, containerPrefix),
62
- kind: f.kind,
63
- size: f.metadata?.size != null ? humanSize(f.metadata.size) : "—",
64
- created: String(f.createdAt ?? "").slice(0, 10),
65
- }));
66
- if (opts.prefix) {
67
- const want = opts.prefix.replace(/^\/+/, "");
68
- rows = rows.filter((r) => r.path.startsWith(want));
69
- }
70
- if (opts.json) {
71
- emitJson(rows);
72
- return 0;
73
- }
74
- process.stdout.write(`Listing ${opts.type ?? "all"} assets in ${formatTarget(t)}\n\n`);
75
- if (rows.length === 0) {
76
- process.stdout.write(" (no assets found)\n");
77
- return 0;
78
- }
79
- process.stdout.write(renderTable(rows, ["path", "kind", "size", "created"]));
80
- process.stdout.write(`\n ${rows.length} asset(s)\n`);
81
- return 0;
82
- }
83
- catch (err) {
84
- fail(err.message);
85
- return 1;
86
- }
87
- }
88
- // ─── projects ────────────────────────────────────────────────────────
89
- export async function runListProjects(opts) {
90
- const remote = resolveRemote();
91
- const token = resolveToken();
92
- if (!token) {
93
- fail("not authenticated. run 'dreamlake login' first");
94
- return 1;
95
- }
96
- const namespace = await resolveNamespace(opts.namespace, { token, remote });
97
- if (!namespace) {
98
- fail("namespace not specified and no authenticated user found. run 'dreamlake login'");
99
- return 1;
100
- }
101
- const projects = await fetchProjects(remote, token, namespace);
102
- if (opts.json) {
103
- emitJson(projects);
104
- return 0;
105
- }
106
- process.stdout.write(`Listing projects in namespace ${namespace}\n\n`);
107
- const rows = projects.map((p) => ({
108
- slug: p.slug,
109
- name: p.name ?? "",
110
- visibility: p.visibility ?? "",
111
- bindrs: p.bindrCount ?? 0,
112
- datasets: p.datasetCount ?? 0,
113
- created: String(p.createdAt ?? "").slice(0, 10),
114
- }));
115
- process.stdout.write(renderTable(rows, ["slug", "name", "visibility", "bindrs", "datasets", "created"]));
116
- process.stdout.write(`\n ${projects.length} project(s)\n`);
117
- return 0;
118
- }
119
- // ─── bindrs / datasets / episodes ────────────────────────────────────
120
- export async function runListBindrs(opts) {
121
- if (!opts.project) {
122
- fail("--project is required for listing bindrs");
123
- return 1;
124
- }
125
- const ctx = await resolveProjectCtx(opts.project);
126
- if (!ctx)
127
- return 1;
128
- const { s, token, remote } = ctx;
129
- const result = await fetchAllPages(remote, `/namespaces/${s.namespace}/projects/${s.project}/bindrs`, token, "bindrs");
130
- if (result === "not-found") {
131
- process.stdout.write(" (project not found)\n");
132
- return 0;
133
- }
134
- if (opts.json) {
135
- emitJson(result.items);
136
- return 0;
137
- }
138
- process.stdout.write(`Listing bindrs in ${formatProject(s)}\n\n`);
139
- const rows = result.items.map((d) => ({
140
- name: String(d.name ?? ""),
141
- files: Array.isArray(d.members) ? d.members.length : 0,
142
- tags: Array.isArray(d.tags) ? d.tags.join(", ") : "",
143
- description: String(d.description ?? "").slice(0, 40),
144
- created: String(d.createdAt ?? "").slice(0, 10),
145
- }));
146
- process.stdout.write(renderTable(rows, ["name", "files", "tags", "description", "created"]));
147
- process.stdout.write(`\n ${result.total} bindr(s)\n`);
148
- return 0;
149
- }
150
- export async function runListDatasets(opts) {
151
- if (!opts.project) {
152
- fail("--project is required for listing datasets");
153
- return 1;
154
- }
155
- const ctx = await resolveProjectCtx(opts.project);
156
- if (!ctx)
157
- return 1;
158
- const { s, token, remote } = ctx;
159
- const result = await fetchAllPages(remote, `/namespaces/${s.namespace}/projects/${s.project}/datasets`, token, "datasets");
160
- if (result === "not-found") {
161
- process.stdout.write(" (project not found)\n");
162
- return 0;
163
- }
164
- if (opts.json) {
165
- emitJson(result.items);
166
- return 0;
167
- }
168
- process.stdout.write(`Listing datasets in ${formatProject(s)}\n\n`);
169
- const rows = result.items.map((d) => ({
170
- name: String(d.name ?? ""),
171
- bindrs: Array.isArray(d.bindrs) ? d.bindrs.length : 0,
172
- tags: Array.isArray(d.tags) ? d.tags.join(", ") : "",
173
- description: String(d.description ?? "").slice(0, 40),
174
- created: String(d.createdAt ?? "").slice(0, 10),
175
- }));
176
- process.stdout.write(renderTable(rows, ["name", "bindrs", "tags", "description", "created"]));
177
- process.stdout.write(`\n ${result.total} dataset(s)\n`);
178
- return 0;
179
- }
180
- export async function runListEpisodes(opts) {
181
- if (!opts.project) {
182
- fail("--project is required for listing episodes");
183
- return 1;
184
- }
185
- const ctx = await resolveProjectCtx(opts.project);
186
- if (!ctx)
187
- return 1;
188
- const { s, token, remote } = ctx;
189
- const result = await fetchAllPages(remote, `/namespaces/${s.namespace}/projects/${s.project}/episodes`, token, "episodes");
190
- if (result === "not-found") {
191
- process.stdout.write(" (project not found)\n");
192
- return 0;
193
- }
194
- if (opts.json) {
195
- emitJson(result.items);
196
- return 0;
197
- }
198
- process.stdout.write(`Listing episodes in ${formatProject(s)}\n\n`);
199
- const rows = result.items.map((ep) => ({
200
- name: String(ep.name ?? ""),
201
- path: String(ep.nodePath ?? ""),
202
- status: String(ep.status ?? ""),
203
- tags: Array.isArray(ep.tags) ? ep.tags.join(", ") : "",
204
- created: String(ep.createdAt ?? "").slice(0, 10),
205
- }));
206
- process.stdout.write(renderTable(rows, ["name", "path", "status", "tags", "created"]));
207
- process.stdout.write(`\n ${result.total} episode(s)\n`);
208
- return 0;
209
- }
210
- // ─── registration ────────────────────────────────────────────────────
211
- export function registerListCommand(program) {
212
- const list = program
213
- .command("list")
214
- .description("list assets (default), or projects / bindrs / datasets / episodes")
215
- .option("--episode <target>", "episode scope: space[@namespace][:episode]")
216
- .option("--prefix <path>", "filter assets by path prefix")
217
- .option("--type <category>", "filter assets by category")
218
- .option("--json", "emit JSON instead of a table")
219
- .action(async (opts) => {
220
- process.exit(await runListAssets(opts));
221
- });
222
- list
223
- .command("project")
224
- .description("list projects in a namespace")
225
- .option("--namespace <slug>", "namespace (default: your own)")
226
- .option("--json", "emit JSON instead of a table")
227
- .action(async (opts) => {
228
- process.exit(await runListProjects(opts));
229
- });
230
- list
231
- .command("bindr")
232
- .description("list bindrs in a project")
233
- .requiredOption("--project <target>", "project scope: space[@namespace]")
234
- .option("--json", "emit JSON instead of a table")
235
- .action(async (opts) => {
236
- process.exit(await runListBindrs(opts));
237
- });
238
- list
239
- .command("dataset")
240
- .description("list datasets in a project")
241
- .requiredOption("--project <target>", "project scope: space[@namespace]")
242
- .option("--json", "emit JSON instead of a table")
243
- .action(async (opts) => {
244
- process.exit(await runListDatasets(opts));
245
- });
246
- list
247
- .command("episode")
248
- .description("list episodes in a project")
249
- .requiredOption("--project <target>", "project scope: space[@namespace]")
250
- .option("--json", "emit JSON instead of a table")
251
- .action(async (opts) => {
252
- process.exit(await runListEpisodes(opts));
253
- });
254
- }