cassian-cli 0.3.11 → 0.3.12
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/commands/exec.d.ts +1 -0
- package/dist/commands/exec.js +18 -4
- package/dist/commands/switch.d.ts +1 -0
- package/dist/commands/switch.js +64 -0
- package/dist/index.js +8 -2
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +1 -1
- package/dist/lib/push.js +11 -1
- package/package.json +1 -1
package/dist/commands/exec.d.ts
CHANGED
package/dist/commands/exec.js
CHANGED
|
@@ -7,7 +7,7 @@ export async function exec(args, opts = {}) {
|
|
|
7
7
|
fatal("No command specified.", "Usage: cassian exec <command>");
|
|
8
8
|
}
|
|
9
9
|
const command = args.join(" ");
|
|
10
|
-
const timeout = parseInt(opts.timeout || "
|
|
10
|
+
const timeout = parseInt(opts.timeout || "3600");
|
|
11
11
|
let config;
|
|
12
12
|
try {
|
|
13
13
|
config = loadConfig();
|
|
@@ -26,13 +26,27 @@ export async function exec(args, opts = {}) {
|
|
|
26
26
|
try {
|
|
27
27
|
await pushWorkspace(client, instance.id, config);
|
|
28
28
|
}
|
|
29
|
-
catch {
|
|
29
|
+
catch (e) {
|
|
30
|
+
if (e.message.includes("limit")) {
|
|
31
|
+
fatal(e.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (opts.detach) {
|
|
36
|
+
const result = await client.post(`/v1/instances/${instance.id}/exec`, {
|
|
37
|
+
command,
|
|
38
|
+
timeout,
|
|
39
|
+
workdir: "/workspace",
|
|
40
|
+
detach: true,
|
|
41
|
+
});
|
|
42
|
+
process.stdout.write(`\x1b[32m✓\x1b[0m Running in background (task: ${result.task_id})\n`);
|
|
43
|
+
process.stdout.write(`\x1b[90m View output: cassian logs\x1b[0m\n`);
|
|
44
|
+
return;
|
|
30
45
|
}
|
|
31
|
-
const mountPath = config.volumes?.[0]?.mount || "/workspace";
|
|
32
46
|
const result = await client.post(`/v1/instances/${instance.id}/exec`, {
|
|
33
47
|
command,
|
|
34
48
|
timeout,
|
|
35
|
-
workdir:
|
|
49
|
+
workdir: "/workspace",
|
|
36
50
|
});
|
|
37
51
|
if (result.stdout)
|
|
38
52
|
process.stdout.write(result.stdout);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function switchGpu(): Promise<void>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { AGENT_URL } from "../lib/constants.js";
|
|
4
|
+
import { loadConfig, findConfigFile } from "../lib/config.js";
|
|
5
|
+
const ALL_GPUS = [
|
|
6
|
+
{ value: "h100-sxm", label: "H100 SXM", memory: "80GB", hint: "best for large training" },
|
|
7
|
+
{ value: "h100-pcie", label: "H100 PCIe", memory: "80GB" },
|
|
8
|
+
{ value: "a100-sxm", label: "A100 SXM", memory: "80GB", hint: "reliable workhorse" },
|
|
9
|
+
{ value: "a100-pcie", label: "A100 PCIe", memory: "80GB" },
|
|
10
|
+
{ value: "l40s", label: "L40S", memory: "48GB", hint: "great for inference" },
|
|
11
|
+
{ value: "l4", label: "L4", memory: "24GB", hint: "efficient inference" },
|
|
12
|
+
{ value: "a10g", label: "A10G", memory: "24GB", hint: "good all-rounder" },
|
|
13
|
+
{ value: "rtx4090", label: "RTX 4090", memory: "24GB", hint: "fast, affordable" },
|
|
14
|
+
{ value: "rtx3090", label: "RTX 3090", memory: "24GB" },
|
|
15
|
+
];
|
|
16
|
+
async function fetchAvailability() {
|
|
17
|
+
try {
|
|
18
|
+
const resp = await fetch(`${AGENT_URL}/v1/gpu-types`);
|
|
19
|
+
if (!resp.ok)
|
|
20
|
+
return {};
|
|
21
|
+
const { gpu_types } = await resp.json();
|
|
22
|
+
const map = {};
|
|
23
|
+
for (const g of gpu_types)
|
|
24
|
+
map[g.type.toLowerCase()] = g.available;
|
|
25
|
+
return map;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function switchGpu() {
|
|
32
|
+
const configPath = findConfigFile();
|
|
33
|
+
if (!configPath) {
|
|
34
|
+
p.log.error("No cassian.yaml found. Run 'cassian init' first.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const config = loadConfig(configPath);
|
|
38
|
+
const currentType = config.gpu?.type || "unknown";
|
|
39
|
+
p.log.info(`Current GPU: \x1b[32m${currentType.toUpperCase()}\x1b[0m`);
|
|
40
|
+
console.log();
|
|
41
|
+
const availability = await fetchAvailability();
|
|
42
|
+
const gpuType = await p.select({
|
|
43
|
+
message: "Switch to GPU",
|
|
44
|
+
options: ALL_GPUS.map(gpu => {
|
|
45
|
+
const avail = availability[gpu.value] ?? 0;
|
|
46
|
+
const tag = avail > 0 ? `\x1b[32m${avail} available\x1b[0m` : `\x1b[90munavailable\x1b[0m`;
|
|
47
|
+
const hint = gpu.hint ? `${gpu.memory} · ${gpu.hint} · ${tag}` : `${gpu.memory} · ${tag}`;
|
|
48
|
+
return { value: gpu.value, label: gpu.label, hint };
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
if (p.isCancel(gpuType)) {
|
|
52
|
+
p.cancel("Cancelled.");
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
if (gpuType === currentType) {
|
|
56
|
+
p.log.info("Already using that GPU type.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const yaml = readFileSync(configPath, "utf-8");
|
|
60
|
+
const updated = yaml.replace(/^(\s*type:\s*).*$/m, `$1${gpuType}`);
|
|
61
|
+
writeFileSync(configPath, updated);
|
|
62
|
+
p.log.success(`Switched to \x1b[32m${gpuType.toUpperCase()}\x1b[0m`);
|
|
63
|
+
p.log.info("Run \x1b[32mcassian up\x1b[0m to start with the new GPU.");
|
|
64
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { logs } from "./commands/logs.js";
|
|
|
15
15
|
import { volumeCreate, volumeList, volumeDelete } from "./commands/volume.js";
|
|
16
16
|
import { sync } from "./commands/sync.js";
|
|
17
17
|
import { exec } from "./commands/exec.js";
|
|
18
|
+
import { switchGpu } from "./commands/switch.js";
|
|
18
19
|
import { createRequire } from "module";
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
20
21
|
const { version } = require("../package.json");
|
|
@@ -46,9 +47,10 @@ program
|
|
|
46
47
|
program
|
|
47
48
|
.command("exec <command...>")
|
|
48
49
|
.description("Sync files and run a command on the instance")
|
|
49
|
-
.option("--timeout <seconds>", "Command timeout in seconds", "
|
|
50
|
+
.option("--timeout <seconds>", "Command timeout in seconds", "3600")
|
|
50
51
|
.option("--no-sync", "Skip file sync before executing")
|
|
51
|
-
.
|
|
52
|
+
.option("-d, --detach", "Run in background, return immediately")
|
|
53
|
+
.action((command, opts) => exec(command, { timeout: opts.timeout, noSync: !opts.sync, detach: opts.detach }));
|
|
52
54
|
program
|
|
53
55
|
.command("sync")
|
|
54
56
|
.description("Sync workspace from instance to local")
|
|
@@ -81,6 +83,10 @@ volume
|
|
|
81
83
|
.description("Delete a volume (irreversible)")
|
|
82
84
|
.requiredOption("--name <name>", "Volume name to delete")
|
|
83
85
|
.action((opts) => volumeDelete(opts.name));
|
|
86
|
+
program
|
|
87
|
+
.command("switch")
|
|
88
|
+
.description("Switch GPU type (shows live availability)")
|
|
89
|
+
.action(switchGpu);
|
|
84
90
|
program
|
|
85
91
|
.command("init")
|
|
86
92
|
.description("Set up a new Cassian project (interactive or via flags)")
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ import type { CassianConfig, DevOverrides } from "../types.js";
|
|
|
2
2
|
export declare function loadConfig(path?: string): CassianConfig;
|
|
3
3
|
export declare function loadDevOverrides(): DevOverrides;
|
|
4
4
|
export declare function findSshPublicKey(override?: string): string;
|
|
5
|
+
export declare function findConfigFile(): string | null;
|
|
5
6
|
export declare function parseSizeToGb(size: string): number;
|
package/dist/lib/config.js
CHANGED
|
@@ -35,7 +35,7 @@ export function findSshPublicKey(override) {
|
|
|
35
35
|
}
|
|
36
36
|
throw new Error("No SSH public key found. Run: ssh-keygen -t ed25519");
|
|
37
37
|
}
|
|
38
|
-
function findConfigFile() {
|
|
38
|
+
export function findConfigFile() {
|
|
39
39
|
for (const name of ["cassian.yaml", "cassian.yml"]) {
|
|
40
40
|
const p = resolve(process.cwd(), name);
|
|
41
41
|
if (existsSync(p))
|
package/dist/lib/push.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const MAX_PUSH_SIZE_MB = 500;
|
|
1
2
|
export async function pushWorkspace(client, instanceId, config) {
|
|
2
3
|
const tar = await import("tar");
|
|
3
4
|
const mountPath = config.volumes?.[0]?.mount || "/workspace";
|
|
@@ -50,5 +51,14 @@ export async function pushWorkspace(client, instanceId, config) {
|
|
|
50
51
|
pack.on("end", res);
|
|
51
52
|
pack.on("error", rej);
|
|
52
53
|
});
|
|
53
|
-
|
|
54
|
+
const payload = Buffer.concat(chunks);
|
|
55
|
+
const sizeMb = payload.length / (1024 * 1024);
|
|
56
|
+
if (sizeMb > MAX_PUSH_SIZE_MB) {
|
|
57
|
+
throw new Error(`Workspace is ${Math.round(sizeMb)}MB (limit: ${MAX_PUSH_SIZE_MB}MB). ` +
|
|
58
|
+
`Add large folders to 'workspace.exclude' in cassian.yaml.`);
|
|
59
|
+
}
|
|
60
|
+
if (sizeMb > 100) {
|
|
61
|
+
process.stderr.write(`\x1b[33m⚠ Workspace is ${Math.round(sizeMb)}MB — consider adding excludes to speed up sync.\x1b[0m\n`);
|
|
62
|
+
}
|
|
63
|
+
await client.push(instanceId, payload, mountPath);
|
|
54
64
|
}
|