cassian-cli 0.1.2 → 0.1.4
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/login.js +2 -14
- package/dist/commands/ssh.js +4 -16
- package/dist/commands/up.js +16 -1
- package/dist/lib/api.js +3 -11
- package/dist/lib/auth.d.ts +1 -1
- package/dist/lib/auth.js +6 -18
- package/dist/lib/constants.d.ts +4 -0
- package/dist/lib/constants.js +4 -0
- package/dist/lib/watcher.js +1 -8
- package/dist/types.d.ts +0 -1
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import { createServer } from "http";
|
|
2
|
-
import { readFileSync, existsSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { join } from "path";
|
|
5
2
|
import open from "open";
|
|
6
3
|
import { saveCredentials } from "../lib/auth.js";
|
|
7
4
|
import { success, dim } from "../lib/output.js";
|
|
8
5
|
import { fatal } from "../lib/errors.js";
|
|
9
|
-
|
|
10
|
-
const CONFIG_FILE = join(CASSIAN_DIR, "config.json");
|
|
6
|
+
import { PLATFORM_URL } from "../lib/constants.js";
|
|
11
7
|
const CALLBACK_PORT = 9876;
|
|
12
|
-
function getCliConfig() {
|
|
13
|
-
if (!existsSync(CONFIG_FILE)) {
|
|
14
|
-
fatal("Cassian CLI is not configured.", "Visit trycassian.com to get started.");
|
|
15
|
-
}
|
|
16
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
17
|
-
}
|
|
18
8
|
export async function login() {
|
|
19
|
-
const config = getCliConfig();
|
|
20
9
|
const callbackUrl = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
21
|
-
const platformUrl =
|
|
10
|
+
const platformUrl = PLATFORM_URL;
|
|
22
11
|
console.log();
|
|
23
12
|
console.log(" Opening Cassian in your browser...");
|
|
24
13
|
console.log();
|
|
@@ -62,7 +51,6 @@ export async function login() {
|
|
|
62
51
|
refresh_token: refreshToken,
|
|
63
52
|
expires_at: Math.floor(Date.now() / 1000) + expiresIn,
|
|
64
53
|
user_email: email,
|
|
65
|
-
agent_url: config.agent_url,
|
|
66
54
|
};
|
|
67
55
|
saveCredentials(creds);
|
|
68
56
|
res.writeHead(200);
|
package/dist/commands/ssh.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import { ApiClient } from "../lib/api.js";
|
|
3
3
|
import { getCredentials, isTokenExpired, refreshToken } from "../lib/auth.js";
|
|
4
|
+
import { AGENT_URL } from "../lib/constants.js";
|
|
4
5
|
import { loadConfig } from "../lib/config.js";
|
|
5
6
|
import { dim } from "../lib/output.js";
|
|
6
7
|
import { fatal, handleError } from "../lib/errors.js";
|
|
@@ -20,8 +21,7 @@ export async function ssh() {
|
|
|
20
21
|
if (!instance) {
|
|
21
22
|
fatal(`${config.name} is not running.`, "Run: cassian up");
|
|
22
23
|
}
|
|
23
|
-
const
|
|
24
|
-
const stopSync = startBidirectionalSync(instance.id, config, creds?.agent_url ?? "");
|
|
24
|
+
const stopSync = startBidirectionalSync(instance.id, config, AGENT_URL);
|
|
25
25
|
const connected = await connectWebSocket(instance, stopSync);
|
|
26
26
|
if (!connected) {
|
|
27
27
|
stopSync();
|
|
@@ -39,26 +39,14 @@ async function getToken() {
|
|
|
39
39
|
if (!creds)
|
|
40
40
|
throw new Error("Not logged in. Run 'cassian login' first.");
|
|
41
41
|
if (isTokenExpired(creds)) {
|
|
42
|
-
const
|
|
43
|
-
const configPath = `${process.env.HOME}/.cassian/config.json`;
|
|
44
|
-
let supabaseUrl = "";
|
|
45
|
-
if (existsSync(configPath)) {
|
|
46
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
47
|
-
supabaseUrl = config.supabase_url || "";
|
|
48
|
-
}
|
|
49
|
-
if (!supabaseUrl)
|
|
50
|
-
throw new Error("Session expired. Run 'cassian login' again.");
|
|
51
|
-
const refreshed = await refreshToken(creds, supabaseUrl);
|
|
42
|
+
const refreshed = await refreshToken(creds);
|
|
52
43
|
return refreshed.access_token;
|
|
53
44
|
}
|
|
54
45
|
return creds.access_token;
|
|
55
46
|
}
|
|
56
47
|
async function connectWebSocket(instance, stopSync) {
|
|
57
|
-
const creds = getCredentials();
|
|
58
|
-
if (!creds)
|
|
59
|
-
return false;
|
|
60
48
|
const token = await getToken();
|
|
61
|
-
const agentUrl =
|
|
49
|
+
const agentUrl = AGENT_URL.replace(/^http/, "ws");
|
|
62
50
|
const wsUrl = `${agentUrl}/v1/instances/${instance.id}/terminal?token=${encodeURIComponent(token)}`;
|
|
63
51
|
return new Promise((resolve) => {
|
|
64
52
|
const ws = new WebSocket(wsUrl);
|
package/dist/commands/up.js
CHANGED
|
@@ -4,6 +4,7 @@ import { execSync } from "child_process";
|
|
|
4
4
|
import ora from "ora";
|
|
5
5
|
import { ApiClient } from "../lib/api.js";
|
|
6
6
|
import { getCredentials } from "../lib/auth.js";
|
|
7
|
+
import { AGENT_URL } from "../lib/constants.js";
|
|
7
8
|
import { loadConfig, loadDevOverrides, findSshPublicKey, parseSizeToGb } from "../lib/config.js";
|
|
8
9
|
import { info, header, green } from "../lib/output.js";
|
|
9
10
|
import { fatal, handleError } from "../lib/errors.js";
|
|
@@ -67,7 +68,7 @@ export async function up() {
|
|
|
67
68
|
}
|
|
68
69
|
// Verify connectivity — no technical details exposed
|
|
69
70
|
const connectSpinner = ora("Connecting to Cassian...").start();
|
|
70
|
-
const agentUrl =
|
|
71
|
+
const agentUrl = AGENT_URL;
|
|
71
72
|
let reachable = false;
|
|
72
73
|
for (let i = 0; i < 10; i++) {
|
|
73
74
|
await new Promise((r) => setTimeout(r, 1000));
|
|
@@ -100,6 +101,20 @@ export async function up() {
|
|
|
100
101
|
await new Promise((r) => setTimeout(r, 1000));
|
|
101
102
|
spinner.text = "Starting instance...";
|
|
102
103
|
}
|
|
104
|
+
// Validate GPU type against what the host offers before provisioning
|
|
105
|
+
if (config.gpu.type) {
|
|
106
|
+
const gpus = await client.get("/v1/gpus").catch(() => null);
|
|
107
|
+
if (gpus && gpus.gpus.length > 0) {
|
|
108
|
+
const available = gpus.gpus[0].name.toLowerCase().replace(/[\s_]/g, "-");
|
|
109
|
+
const requested = config.gpu.type.toLowerCase().replace(/[\s_]/g, "-");
|
|
110
|
+
// Check if the requested type is a substring match of what's available
|
|
111
|
+
if (!available.includes(requested) && !requested.includes(available.split("-").pop())) {
|
|
112
|
+
spinner.fail("GPU type not available");
|
|
113
|
+
const names = [...new Set(gpus.gpus.map((g) => g.name))];
|
|
114
|
+
fatal(`GPU type '${config.gpu.type}' is not available on this host.`, `Available: ${names.join(", ")} — update gpu.type in cassian.yaml`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
103
118
|
const instance = await client.post("/v1/instances", {
|
|
104
119
|
name: config.name,
|
|
105
120
|
gpu_count: config.gpu.count,
|
package/dist/lib/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCredentials, isTokenExpired, refreshToken } from "./auth.js";
|
|
2
|
+
import { AGENT_URL } from "./constants.js";
|
|
2
3
|
export class ApiClient {
|
|
3
4
|
agentUrl;
|
|
4
5
|
credentials;
|
|
@@ -7,20 +8,11 @@ export class ApiClient {
|
|
|
7
8
|
if (!creds)
|
|
8
9
|
throw new Error("Not logged in. Run 'cassian login' first.");
|
|
9
10
|
this.credentials = creds;
|
|
10
|
-
this.agentUrl =
|
|
11
|
+
this.agentUrl = AGENT_URL;
|
|
11
12
|
}
|
|
12
13
|
async getToken() {
|
|
13
14
|
if (isTokenExpired(this.credentials)) {
|
|
14
|
-
|
|
15
|
-
const { readFileSync, existsSync } = await import("fs");
|
|
16
|
-
let supabaseUrl = "";
|
|
17
|
-
if (existsSync(configPath)) {
|
|
18
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
19
|
-
supabaseUrl = config.supabase_url || "";
|
|
20
|
-
}
|
|
21
|
-
if (!supabaseUrl)
|
|
22
|
-
throw new Error("Session expired. Run 'cassian login' again.");
|
|
23
|
-
this.credentials = await refreshToken(this.credentials, supabaseUrl);
|
|
15
|
+
this.credentials = await refreshToken(this.credentials);
|
|
24
16
|
}
|
|
25
17
|
return this.credentials.access_token;
|
|
26
18
|
}
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export declare function getCredentials(): Credentials | null;
|
|
|
3
3
|
export declare function saveCredentials(creds: Credentials): void;
|
|
4
4
|
export declare function clearCredentials(): void;
|
|
5
5
|
export declare function isTokenExpired(creds: Credentials): boolean;
|
|
6
|
-
export declare function refreshToken(creds: Credentials
|
|
6
|
+
export declare function refreshToken(creds: Credentials): Promise<Credentials>;
|
|
7
7
|
export declare function getAgentUrl(): string;
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
+
import { SUPABASE_URL, SUPABASE_ANON_KEY, AGENT_URL } from "./constants.js";
|
|
4
5
|
const CREDENTIALS_DIR = join(homedir(), ".cassian");
|
|
5
6
|
const CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
|
|
6
7
|
export function getCredentials() {
|
|
@@ -20,18 +21,16 @@ export function saveCredentials(creds) {
|
|
|
20
21
|
}
|
|
21
22
|
export function clearCredentials() {
|
|
22
23
|
if (existsSync(CREDENTIALS_FILE)) {
|
|
23
|
-
writeFileSync(CREDENTIALS_FILE, "");
|
|
24
|
-
const { unlinkSync } = require("fs");
|
|
25
24
|
unlinkSync(CREDENTIALS_FILE);
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
export function isTokenExpired(creds) {
|
|
29
28
|
return Date.now() / 1000 > creds.expires_at - 60; // 60s buffer
|
|
30
29
|
}
|
|
31
|
-
export async function refreshToken(creds
|
|
32
|
-
const resp = await fetch(`${
|
|
30
|
+
export async function refreshToken(creds) {
|
|
31
|
+
const resp = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {
|
|
33
32
|
method: "POST",
|
|
34
|
-
headers: { "Content-Type": "application/json", apikey:
|
|
33
|
+
headers: { "Content-Type": "application/json", apikey: SUPABASE_ANON_KEY },
|
|
35
34
|
body: JSON.stringify({ refresh_token: creds.refresh_token }),
|
|
36
35
|
});
|
|
37
36
|
if (!resp.ok) {
|
|
@@ -48,16 +47,5 @@ export async function refreshToken(creds, supabaseUrl) {
|
|
|
48
47
|
return updated;
|
|
49
48
|
}
|
|
50
49
|
export function getAgentUrl() {
|
|
51
|
-
|
|
52
|
-
if (!creds)
|
|
53
|
-
throw new Error("Not logged in. Run 'cassian login' first.");
|
|
54
|
-
return creds.agent_url;
|
|
55
|
-
}
|
|
56
|
-
function getAnonKey() {
|
|
57
|
-
const configPath = join(CREDENTIALS_DIR, "config.json");
|
|
58
|
-
if (existsSync(configPath)) {
|
|
59
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
60
|
-
return config.supabase_anon_key || "";
|
|
61
|
-
}
|
|
62
|
-
return "";
|
|
50
|
+
return AGENT_URL;
|
|
63
51
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const SUPABASE_URL = "https://ykgwmdvzzburglrkjutc.supabase.co";
|
|
2
|
+
export declare const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlrZ3dtZHZ6emJ1cmdscmtqdXRjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI0MzI0MzUsImV4cCI6MjA4ODAwODQzNX0.c-MSPTYr9_G77sy_2hAcY7hth8mSKmNQDzfTjIx8rE0";
|
|
3
|
+
export declare const AGENT_URL = "https://gpu.platops.ai";
|
|
4
|
+
export declare const PLATFORM_URL = "https://platform.trycassian.com";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const SUPABASE_URL = "https://ykgwmdvzzburglrkjutc.supabase.co";
|
|
2
|
+
export const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlrZ3dtZHZ6emJ1cmdscmtqdXRjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI0MzI0MzUsImV4cCI6MjA4ODAwODQzNX0.c-MSPTYr9_G77sy_2hAcY7hth8mSKmNQDzfTjIx8rE0";
|
|
3
|
+
export const AGENT_URL = "https://gpu.platops.ai";
|
|
4
|
+
export const PLATFORM_URL = "https://platform.trycassian.com";
|
package/dist/lib/watcher.js
CHANGED
|
@@ -34,14 +34,7 @@ async function getToken() {
|
|
|
34
34
|
if (!creds)
|
|
35
35
|
throw new Error("Not logged in");
|
|
36
36
|
if (isTokenExpired(creds)) {
|
|
37
|
-
const
|
|
38
|
-
const cfgPath = `${process.env.HOME}/.cassian/config.json`;
|
|
39
|
-
let supabaseUrl = "";
|
|
40
|
-
if (ef(cfgPath))
|
|
41
|
-
supabaseUrl = JSON.parse(rf(cfgPath, "utf-8")).supabase_url || "";
|
|
42
|
-
if (!supabaseUrl)
|
|
43
|
-
throw new Error("Session expired");
|
|
44
|
-
const refreshed = await refreshToken(creds, supabaseUrl);
|
|
37
|
+
const refreshed = await refreshToken(creds);
|
|
45
38
|
return refreshed.access_token;
|
|
46
39
|
}
|
|
47
40
|
return creds.access_token;
|
package/dist/types.d.ts
CHANGED