trajectories-sh 1.22.0 → 1.23.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/dist/cli.js +205 -1
- package/dist/projects-ESYLKFUR.js +148 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { readFileSync } from "fs";
|
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { dirname, join } from "path";
|
|
8
8
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
var version = "1.
|
|
9
|
+
var version = "1.23.0";
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
12
12
|
version = pkg.version;
|
|
@@ -382,6 +382,210 @@ addCommentBodyOptions(
|
|
|
382
382
|
comment.command("update <comment-id>").description("Update the body of one of your comments").option("--json", "Print machine-readable JSON")
|
|
383
383
|
).action(updateCommentAction);
|
|
384
384
|
comment.command("delete <comment-id>").description("Delete one of your comments").option("-y, --yes", "Skip confirmation prompt").option("--json", "Print machine-readable JSON").action(deleteCommentAction);
|
|
385
|
+
async function resolveProject(ref) {
|
|
386
|
+
const { listProjects, resolveProjectRef } = await import("./projects-ESYLKFUR.js");
|
|
387
|
+
const projects = await listProjects();
|
|
388
|
+
return { ...resolveProjectRef(ref, projects), projects };
|
|
389
|
+
}
|
|
390
|
+
async function listProjectsAction(opts) {
|
|
391
|
+
try {
|
|
392
|
+
const { listProjects } = await import("./projects-ESYLKFUR.js");
|
|
393
|
+
const projects = await listProjects();
|
|
394
|
+
if (opts.json) {
|
|
395
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (projects.length === 0) {
|
|
399
|
+
console.log("\n No projects yet. Create one with: trajectories project create <name>\n");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const chalk = (await import("chalk")).default;
|
|
403
|
+
console.log(`
|
|
404
|
+
${chalk.bold(`Projects (${projects.length})`)}
|
|
405
|
+
`);
|
|
406
|
+
for (const p of projects) {
|
|
407
|
+
const sets = `${p.n_jobs} set${p.n_jobs === 1 ? "" : "s"}`;
|
|
408
|
+
const trials = `${p.n_trials} trajector${p.n_trials === 1 ? "y" : "ies"}`;
|
|
409
|
+
const rate = p.pass_rate != null ? ` \xB7 ${(p.pass_rate * 100).toFixed(0)}%` : "";
|
|
410
|
+
console.log(` ${chalk.bold(p.name)} ${chalk.dim(`(${p.visibility})`)}`);
|
|
411
|
+
console.log(` ${chalk.dim(p.id)}`);
|
|
412
|
+
console.log(` ${chalk.dim(`${sets} \xB7 ${trials}${rate}`)}`);
|
|
413
|
+
if (p.description) console.log(` ${chalk.dim(p.description)}`);
|
|
414
|
+
console.log("");
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
printCommentError(error, Boolean(opts.json));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async function showProjectAction(ref, opts) {
|
|
421
|
+
try {
|
|
422
|
+
const { getProject } = await import("./projects-ESYLKFUR.js");
|
|
423
|
+
const { id } = await resolveProject(ref);
|
|
424
|
+
const project2 = await getProject(id);
|
|
425
|
+
if (opts.json) {
|
|
426
|
+
console.log(JSON.stringify(project2, null, 2));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const chalk = (await import("chalk")).default;
|
|
430
|
+
console.log(`
|
|
431
|
+
${chalk.bold(project2.name)} ${chalk.dim(`(${project2.visibility})`)}`);
|
|
432
|
+
console.log(` ${chalk.dim(project2.id)}
|
|
433
|
+
`);
|
|
434
|
+
if (project2.description) console.log(` ${project2.description}
|
|
435
|
+
`);
|
|
436
|
+
console.log(
|
|
437
|
+
` ${project2.n_jobs} trajectory set${project2.n_jobs === 1 ? "" : "s"} \xB7 ${project2.n_trials} trajector${project2.n_trials === 1 ? "y" : "ies"}` + (project2.pass_rate != null ? ` \xB7 ${(project2.pass_rate * 100).toFixed(0)}% pass` : "") + "\n"
|
|
438
|
+
);
|
|
439
|
+
for (const job of project2.jobs || []) {
|
|
440
|
+
const trials = job.n_trials != null ? chalk.dim(` (${job.n_trials})`) : "";
|
|
441
|
+
console.log(` ${job.name || job.slug || job.id}${trials}`);
|
|
442
|
+
console.log(` ${chalk.dim(job.id)}`);
|
|
443
|
+
}
|
|
444
|
+
console.log("");
|
|
445
|
+
} catch (error) {
|
|
446
|
+
printCommentError(error, Boolean(opts.json));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function createProjectAction(name, opts) {
|
|
450
|
+
try {
|
|
451
|
+
const { createProject } = await import("./projects-ESYLKFUR.js");
|
|
452
|
+
const project2 = await createProject({
|
|
453
|
+
name,
|
|
454
|
+
description: opts.description,
|
|
455
|
+
color: opts.color,
|
|
456
|
+
slug: opts.slug,
|
|
457
|
+
visibility: opts.visibility
|
|
458
|
+
});
|
|
459
|
+
if (opts.json) {
|
|
460
|
+
console.log(JSON.stringify(project2));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
console.log(`
|
|
464
|
+
\u2713 Created project "${project2.name}" (${project2.visibility})`);
|
|
465
|
+
console.log(` ${project2.id}
|
|
466
|
+
`);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
printCommentError(error, Boolean(opts.json));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async function updateProjectAction(ref, opts) {
|
|
472
|
+
try {
|
|
473
|
+
const patch = {};
|
|
474
|
+
if (opts.name) patch.name = opts.name;
|
|
475
|
+
if (opts.description !== void 0) patch.description = opts.description;
|
|
476
|
+
if (opts.color) patch.color = opts.color;
|
|
477
|
+
if (opts.slug) patch.slug = opts.slug;
|
|
478
|
+
if (opts.visibility) patch.visibility = opts.visibility;
|
|
479
|
+
if (Object.keys(patch).length === 0) {
|
|
480
|
+
throw new Error("Nothing to update. Pass --name, --description, --color, --slug or --visibility");
|
|
481
|
+
}
|
|
482
|
+
const { updateProject } = await import("./projects-ESYLKFUR.js");
|
|
483
|
+
const { id } = await resolveProject(ref);
|
|
484
|
+
const project2 = await updateProject(id, patch);
|
|
485
|
+
if (opts.json) console.log(JSON.stringify(project2));
|
|
486
|
+
else console.log(`
|
|
487
|
+
\u2713 Updated "${project2.name}"
|
|
488
|
+
`);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
printCommentError(error, Boolean(opts.json));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async function deleteProjectAction(ref, opts) {
|
|
494
|
+
try {
|
|
495
|
+
const { deleteProject } = await import("./projects-ESYLKFUR.js");
|
|
496
|
+
const { id, project: project2 } = await resolveProject(ref);
|
|
497
|
+
const label = project2?.name ? `"${project2.name}"` : id;
|
|
498
|
+
if (!opts.yes) {
|
|
499
|
+
if (!process.stdin.isTTY) {
|
|
500
|
+
throw new Error("Refusing non-interactive deletion without --yes");
|
|
501
|
+
}
|
|
502
|
+
const readline = await import("readline");
|
|
503
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
504
|
+
const answer = await new Promise((resolve) => {
|
|
505
|
+
rl.question(
|
|
506
|
+
` Delete project ${label}? Its trajectory sets are ungrouped, not deleted. (y/N) `,
|
|
507
|
+
resolve
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
rl.close();
|
|
511
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
512
|
+
if (opts.json) console.log(JSON.stringify({ status: "cancelled", project_id: id }));
|
|
513
|
+
else console.log(" Cancelled.");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
await deleteProject(id);
|
|
518
|
+
if (opts.json) console.log(JSON.stringify({ status: "deleted", project_id: id }));
|
|
519
|
+
else console.log(` \u2713 Deleted project ${label}`);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
printCommentError(error, Boolean(opts.json));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async function projectAddAction(ref, sets, opts) {
|
|
525
|
+
try {
|
|
526
|
+
const { assignJobs } = await import("./projects-ESYLKFUR.js");
|
|
527
|
+
const { id, project: project2 } = await resolveProject(ref);
|
|
528
|
+
const result = await assignJobs(id, sets, "add");
|
|
529
|
+
if (opts.json) {
|
|
530
|
+
console.log(JSON.stringify(result));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const label = project2?.name ? `"${project2.name}"` : id;
|
|
534
|
+
console.log(
|
|
535
|
+
`
|
|
536
|
+
\u2713 Filed ${result.assigned} trajectory set${result.assigned === 1 ? "" : "s"} into ${label}
|
|
537
|
+
`
|
|
538
|
+
);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
printCommentError(error, Boolean(opts.json));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function projectRemoveAction(refOrSet, sets, opts) {
|
|
544
|
+
try {
|
|
545
|
+
const { assignJobs } = await import("./projects-ESYLKFUR.js");
|
|
546
|
+
if (opts.all) {
|
|
547
|
+
const result2 = await assignJobs(null, [refOrSet, ...sets], "remove");
|
|
548
|
+
if (opts.json) console.log(JSON.stringify(result2));
|
|
549
|
+
else
|
|
550
|
+
console.log(
|
|
551
|
+
`
|
|
552
|
+
\u2713 Removed ${result2.assigned} trajectory set${result2.assigned === 1 ? "" : "s"} from all projects
|
|
553
|
+
`
|
|
554
|
+
);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (sets.length === 0) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
"Specify which trajectory sets to remove, or pass --all to unfile them from every project"
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
const { id, project: project2 } = await resolveProject(refOrSet);
|
|
563
|
+
const result = await assignJobs(id, sets, "remove");
|
|
564
|
+
if (opts.json) {
|
|
565
|
+
console.log(JSON.stringify(result));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const label = project2?.name ? `"${project2.name}"` : id;
|
|
569
|
+
console.log(
|
|
570
|
+
`
|
|
571
|
+
\u2713 Removed ${result.assigned} trajectory set${result.assigned === 1 ? "" : "s"} from ${label}
|
|
572
|
+
`
|
|
573
|
+
);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
printCommentError(error, Boolean(opts.json));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
var project = program.command("project").description("Manage projects and the trajectory sets filed into them");
|
|
579
|
+
project.command("list").description("List projects in your organization").option("--json", "Print machine-readable JSON").action(listProjectsAction);
|
|
580
|
+
project.command("show <project>").description("Show a project and the trajectory sets in it (id, URL, slug or name)").option("--json", "Print machine-readable JSON").action(showProjectAction);
|
|
581
|
+
project.command("create <name>").description("Create a project").option("-d, --description <text>", "Project description").option(
|
|
582
|
+
"-c, --color <color>",
|
|
583
|
+
"Palette key: slate, blue, green, amber, purple, pink, teal, red"
|
|
584
|
+
).option("--slug <slug>", "URL slug (defaults to a slugified name)").option("--visibility <visibility>", "public, unlisted (default) or private").option("--json", "Print machine-readable JSON").action(createProjectAction);
|
|
585
|
+
project.command("update <project>").description("Rename a project or change its description, colour, slug or visibility").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("-c, --color <color>", "New palette key").option("--slug <slug>", "New URL slug").option("--visibility <visibility>", "public, unlisted or private").option("--json", "Print machine-readable JSON").action(updateProjectAction);
|
|
586
|
+
project.command("delete <project>").description("Delete a project (its trajectory sets are ungrouped, not deleted)").option("-y, --yes", "Skip confirmation prompt").option("--json", "Print machine-readable JSON").action(deleteProjectAction);
|
|
587
|
+
project.command("add <project> <sets...>").description("File one or more trajectory sets into a project (URLs or ids)").option("--json", "Print machine-readable JSON").action(projectAddAction);
|
|
588
|
+
project.command("remove <project|set> [sets...]").description("Remove trajectory sets from a project, or from all with --all").option("--all", "Unfile the given sets from every project").option("--json", "Print machine-readable JSON").action(projectRemoveAction);
|
|
385
589
|
program.command("whoami").description("Show current authentication status").action(async () => {
|
|
386
590
|
const chalk = (await import("chalk")).default;
|
|
387
591
|
const { status } = await import("./auth-4UEZSE4C.js");
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJobId
|
|
3
|
+
} from "./chunk-IUSBK45Q.js";
|
|
4
|
+
import "./chunk-35B24E3O.js";
|
|
5
|
+
import {
|
|
6
|
+
getApiUrl,
|
|
7
|
+
getAuthHeader
|
|
8
|
+
} from "./chunk-Q2ZEN43L.js";
|
|
9
|
+
|
|
10
|
+
// src/projects.ts
|
|
11
|
+
var PROJECT_COLORS = [
|
|
12
|
+
"slate",
|
|
13
|
+
"blue",
|
|
14
|
+
"green",
|
|
15
|
+
"amber",
|
|
16
|
+
"purple",
|
|
17
|
+
"pink",
|
|
18
|
+
"teal",
|
|
19
|
+
"red"
|
|
20
|
+
];
|
|
21
|
+
var PROJECT_VISIBILITIES = ["public", "unlisted", "private"];
|
|
22
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
|
+
async function apiRequest(path, init = {}) {
|
|
24
|
+
const auth = await getAuthHeader();
|
|
25
|
+
if (!auth) {
|
|
26
|
+
throw new Error("Not authenticated. Run: trajectories auth login");
|
|
27
|
+
}
|
|
28
|
+
const headers = {
|
|
29
|
+
...init.headers,
|
|
30
|
+
Authorization: auth
|
|
31
|
+
};
|
|
32
|
+
const response = await fetch(`${getApiUrl()}${path}`, { ...init, headers });
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const responseText = await response.text();
|
|
35
|
+
let detail = responseText;
|
|
36
|
+
try {
|
|
37
|
+
const payload = JSON.parse(responseText);
|
|
38
|
+
if (typeof payload?.detail === "string") detail = payload.detail;
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
const message = detail || `Request failed (${response.status})`;
|
|
42
|
+
const error = new Error(message);
|
|
43
|
+
error.status = response.status;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
if (response.status === 204) return void 0;
|
|
47
|
+
return response.json();
|
|
48
|
+
}
|
|
49
|
+
async function listProjects() {
|
|
50
|
+
return apiRequest("/api/org/projects");
|
|
51
|
+
}
|
|
52
|
+
async function getProject(projectId) {
|
|
53
|
+
return apiRequest(`/api/org/projects/${encodeURIComponent(projectId)}`);
|
|
54
|
+
}
|
|
55
|
+
function resolveProjectRef(ref, projects) {
|
|
56
|
+
const trimmed = (ref || "").trim();
|
|
57
|
+
if (!trimmed) throw new Error("A project is required");
|
|
58
|
+
const urlMatch = trimmed.match(/\/projects\/([0-9a-f-]{36})/i);
|
|
59
|
+
if (urlMatch) return { id: urlMatch[1], project: projects.find((p) => p.id === urlMatch[1]) };
|
|
60
|
+
if (UUID_RE.test(trimmed) && !trimmed.includes("/")) {
|
|
61
|
+
return { id: trimmed, project: projects.find((p) => p.id === trimmed) };
|
|
62
|
+
}
|
|
63
|
+
const needle = trimmed.toLowerCase();
|
|
64
|
+
const bySlug = projects.filter((p) => (p.slug || "").toLowerCase() === needle);
|
|
65
|
+
if (bySlug.length === 1) return { id: bySlug[0].id, project: bySlug[0] };
|
|
66
|
+
const byName = projects.filter((p) => (p.name || "").toLowerCase() === needle);
|
|
67
|
+
if (byName.length === 1) return { id: byName[0].id, project: byName[0] };
|
|
68
|
+
if (byName.length > 1) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${byName.length} projects are named "${trimmed}". Use the project id or URL instead:
|
|
71
|
+
` + byName.map((p) => ` ${p.id} ${p.name}`).join("\n")
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const partial = projects.filter((p) => (p.name || "").toLowerCase().includes(needle));
|
|
75
|
+
if (partial.length === 1) return { id: partial[0].id, project: partial[0] };
|
|
76
|
+
if (partial.length > 1) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`"${trimmed}" matches ${partial.length} projects. Be more specific, or use the id:
|
|
79
|
+
` + partial.map((p) => ` ${p.id} ${p.name}`).join("\n")
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(
|
|
83
|
+
`No project matching "${trimmed}". Run: trajectories project list`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
async function createProject(input) {
|
|
87
|
+
const color = input.color ?? "slate";
|
|
88
|
+
if (!PROJECT_COLORS.includes(color)) {
|
|
89
|
+
throw new Error(`--color must be one of: ${PROJECT_COLORS.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
const visibility = input.visibility ?? "unlisted";
|
|
92
|
+
if (!PROJECT_VISIBILITIES.includes(visibility)) {
|
|
93
|
+
throw new Error(`--visibility must be one of: ${PROJECT_VISIBILITIES.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
return apiRequest("/api/org/projects", {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "Content-Type": "application/json" },
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
name: input.name,
|
|
100
|
+
description: input.description ?? "",
|
|
101
|
+
color,
|
|
102
|
+
visibility,
|
|
103
|
+
...input.slug ? { slug: input.slug } : {}
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function updateProject(projectId, patch) {
|
|
108
|
+
if (patch.color && !PROJECT_COLORS.includes(patch.color)) {
|
|
109
|
+
throw new Error(`--color must be one of: ${PROJECT_COLORS.join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
if (patch.visibility && !PROJECT_VISIBILITIES.includes(patch.visibility)) {
|
|
112
|
+
throw new Error(`--visibility must be one of: ${PROJECT_VISIBILITIES.join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
return apiRequest(`/api/org/projects/${encodeURIComponent(projectId)}`, {
|
|
115
|
+
method: "PUT",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify(patch)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function deleteProject(projectId) {
|
|
121
|
+
await apiRequest(`/api/org/projects/${encodeURIComponent(projectId)}`, {
|
|
122
|
+
method: "DELETE"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function assignJobs(projectId, jobRefs, action) {
|
|
126
|
+
const jobIds = jobRefs.map((ref) => parseJobId(ref));
|
|
127
|
+
const unique = [...new Set(jobIds)];
|
|
128
|
+
if (unique.length === 0) throw new Error("At least one trajectory set is required");
|
|
129
|
+
if (unique.length > 500) {
|
|
130
|
+
throw new Error(`Cannot file more than 500 trajectory sets at once (got ${unique.length})`);
|
|
131
|
+
}
|
|
132
|
+
return apiRequest("/api/org/projects/assign", {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "Content-Type": "application/json" },
|
|
135
|
+
body: JSON.stringify({ job_ids: unique, project_id: projectId, action })
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export {
|
|
139
|
+
PROJECT_COLORS,
|
|
140
|
+
PROJECT_VISIBILITIES,
|
|
141
|
+
assignJobs,
|
|
142
|
+
createProject,
|
|
143
|
+
deleteProject,
|
|
144
|
+
getProject,
|
|
145
|
+
listProjects,
|
|
146
|
+
resolveProjectRef,
|
|
147
|
+
updateProject
|
|
148
|
+
};
|