cassian-cli 0.1.6 → 0.1.8
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 +9 -0
- package/dist/commands/login.js +34 -80
- package/dist/commands/ssh.js +6 -0
- package/dist/commands/up.js +1 -27
- package/dist/index.js +3 -2
- package/dist/lib/constants.d.ts +1 -1
- package/dist/lib/constants.js +1 -1
- package/dist/lib/push.d.ts +3 -0
- package/dist/lib/push.js +21 -0
- package/package.json +1 -1
package/dist/commands/exec.d.ts
CHANGED
package/dist/commands/exec.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ApiClient } from "../lib/api.js";
|
|
2
2
|
import { loadConfig } from "../lib/config.js";
|
|
3
|
+
import { pushWorkspace } from "../lib/push.js";
|
|
3
4
|
import { fatal, handleError } from "../lib/errors.js";
|
|
4
5
|
export async function exec(args, opts = {}) {
|
|
5
6
|
if (args.length === 0) {
|
|
@@ -21,9 +22,17 @@ export async function exec(args, opts = {}) {
|
|
|
21
22
|
if (!instance) {
|
|
22
23
|
fatal(`${config.name} is not running.`, "Run: cassian up");
|
|
23
24
|
}
|
|
25
|
+
if (!opts.noSync) {
|
|
26
|
+
try {
|
|
27
|
+
await pushWorkspace(client, instance.id, config);
|
|
28
|
+
}
|
|
29
|
+
catch { /* sync best-effort — don't block exec */ }
|
|
30
|
+
}
|
|
31
|
+
const mountPath = config.volumes?.[0]?.mount || "/workspace";
|
|
24
32
|
const result = await client.post(`/v1/instances/${instance.id}/exec`, {
|
|
25
33
|
command,
|
|
26
34
|
timeout,
|
|
35
|
+
workdir: mountPath,
|
|
27
36
|
});
|
|
28
37
|
if (result.stdout)
|
|
29
38
|
process.stdout.write(result.stdout);
|
package/dist/commands/login.js
CHANGED
|
@@ -1,90 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createInterface } from "readline";
|
|
2
2
|
import open from "open";
|
|
3
3
|
import { saveCredentials } from "../lib/auth.js";
|
|
4
4
|
import { success, dim } from "../lib/output.js";
|
|
5
5
|
import { fatal } from "../lib/errors.js";
|
|
6
6
|
import { PLATFORM_URL } from "../lib/constants.js";
|
|
7
|
-
const CALLBACK_PORT = 9876;
|
|
8
7
|
export async function login() {
|
|
9
|
-
const
|
|
10
|
-
const platformUrl = PLATFORM_URL;
|
|
8
|
+
const authUrl = `${PLATFORM_URL}/cli`;
|
|
11
9
|
console.log();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
res.end();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
|
|
25
|
-
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
26
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
27
|
-
if (req.method === "OPTIONS") {
|
|
28
|
-
res.writeHead(204);
|
|
29
|
-
res.end();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const url = new URL(req.url || "/", `http://localhost:${CALLBACK_PORT}`);
|
|
33
|
-
// Platform posts tokens directly to /callback?access_token=...&refresh_token=...
|
|
34
|
-
if (url.pathname === "/callback" && req.method === "POST") {
|
|
35
|
-
const params = url.searchParams;
|
|
36
|
-
const accessToken = params.get("access_token");
|
|
37
|
-
const refreshToken = params.get("refresh_token");
|
|
38
|
-
const expiresIn = parseInt(params.get("expires_in") || "3600");
|
|
39
|
-
if (!accessToken || !refreshToken) {
|
|
40
|
-
res.writeHead(400);
|
|
41
|
-
res.end("Missing tokens");
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
// Decode JWT to get email
|
|
45
|
-
const payload = JSON.parse(Buffer.from(accessToken.split(".")[1], "base64url").toString());
|
|
46
|
-
const email = payload.email || "unknown";
|
|
47
|
-
const creds = {
|
|
48
|
-
access_token: accessToken,
|
|
49
|
-
refresh_token: refreshToken,
|
|
50
|
-
expires_at: Math.floor(Date.now() / 1000) + expiresIn,
|
|
51
|
-
user_email: email,
|
|
52
|
-
};
|
|
53
|
-
saveCredentials(creds);
|
|
54
|
-
res.writeHead(200);
|
|
55
|
-
res.end("ok");
|
|
56
|
-
console.log();
|
|
57
|
-
success(`Logged in as ${email}`);
|
|
58
|
-
console.log();
|
|
59
|
-
server.close();
|
|
60
|
-
resolve();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
res.writeHead(404);
|
|
64
|
-
res.end();
|
|
65
|
-
});
|
|
66
|
-
server.listen(CALLBACK_PORT, "127.0.0.1", () => {
|
|
67
|
-
console.log(dim(" Login URL (open in any browser):"));
|
|
68
|
-
console.log(` ${authUrl}`);
|
|
69
|
-
console.log();
|
|
70
|
-
console.log(dim(" Press Enter to open in browser..."));
|
|
71
|
-
if (process.stdin.isTTY) {
|
|
72
|
-
process.stdin.setRawMode(true);
|
|
73
|
-
process.stdin.resume();
|
|
74
|
-
process.stdin.once("data", () => {
|
|
75
|
-
process.stdin.setRawMode(false);
|
|
76
|
-
process.stdin.pause();
|
|
77
|
-
open(authUrl).catch(() => { });
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
server.on("error", () => {
|
|
82
|
-
fatal("Could not start login. Try again.");
|
|
10
|
+
open(authUrl).catch(() => { });
|
|
11
|
+
console.log(dim(" Opening browser..."));
|
|
12
|
+
console.log(dim(` If it didn't open, visit: ${authUrl}`));
|
|
13
|
+
console.log();
|
|
14
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
const code = await new Promise((resolve) => {
|
|
16
|
+
rl.question(" Enter code: ", (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(answer.trim());
|
|
83
19
|
});
|
|
84
|
-
const timeout = setTimeout(() => {
|
|
85
|
-
server.close();
|
|
86
|
-
fatal("Login timed out.", "Run: cassian login");
|
|
87
|
-
}, 120000);
|
|
88
|
-
server.on("close", () => clearTimeout(timeout));
|
|
89
20
|
});
|
|
21
|
+
if (!code || !/^\d{6}$/.test(code)) {
|
|
22
|
+
fatal("Invalid code.", "Enter the 6-digit code shown in your browser.");
|
|
23
|
+
}
|
|
24
|
+
// Exchange code for tokens
|
|
25
|
+
const { AGENT_URL } = await import("../lib/constants.js");
|
|
26
|
+
const resp = await fetch(`${AGENT_URL}/v1/cli/auth?code=${code}`);
|
|
27
|
+
if (!resp.ok) {
|
|
28
|
+
const body = await resp.json().catch(() => ({}));
|
|
29
|
+
const msg = body.error || "Code exchange failed";
|
|
30
|
+
fatal(msg, "Run: cassian login");
|
|
31
|
+
}
|
|
32
|
+
const { access_token, refresh_token } = await resp.json();
|
|
33
|
+
const payload = JSON.parse(Buffer.from(access_token.split(".")[1], "base64url").toString());
|
|
34
|
+
const email = payload.email || "unknown";
|
|
35
|
+
saveCredentials({
|
|
36
|
+
access_token,
|
|
37
|
+
refresh_token,
|
|
38
|
+
expires_at: payload.exp || Math.floor(Date.now() / 1000) + 3600,
|
|
39
|
+
user_email: email,
|
|
40
|
+
});
|
|
41
|
+
console.log();
|
|
42
|
+
success(`Logged in as ${email}`);
|
|
43
|
+
console.log();
|
|
90
44
|
}
|
package/dist/commands/ssh.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ApiClient } from "../lib/api.js";
|
|
|
3
3
|
import { getCredentials, isTokenExpired, refreshToken } from "../lib/auth.js";
|
|
4
4
|
import { AGENT_URL } from "../lib/constants.js";
|
|
5
5
|
import { loadConfig } from "../lib/config.js";
|
|
6
|
+
import { pushWorkspace } from "../lib/push.js";
|
|
6
7
|
import { dim } from "../lib/output.js";
|
|
7
8
|
import { fatal, handleError } from "../lib/errors.js";
|
|
8
9
|
import { startBidirectionalSync } from "../lib/watcher.js";
|
|
@@ -21,6 +22,11 @@ export async function ssh() {
|
|
|
21
22
|
if (!instance) {
|
|
22
23
|
fatal(`${config.name} is not running.`, "Run: cassian up");
|
|
23
24
|
}
|
|
25
|
+
// Push local files before connecting
|
|
26
|
+
try {
|
|
27
|
+
await pushWorkspace(client, instance.id, config);
|
|
28
|
+
}
|
|
29
|
+
catch { /* best effort */ }
|
|
24
30
|
const stopSync = startBidirectionalSync(instance.id, config, AGENT_URL);
|
|
25
31
|
const connected = await connectWebSocket(instance, stopSync);
|
|
26
32
|
if (!connected) {
|
package/dist/commands/up.js
CHANGED
|
@@ -132,33 +132,7 @@ export async function up() {
|
|
|
132
132
|
},
|
|
133
133
|
});
|
|
134
134
|
spinner.succeed("Instance ready");
|
|
135
|
-
//
|
|
136
|
-
const mountPath = config.volumes?.[0]?.mount || "/workspace";
|
|
137
|
-
const wsSpinner = ora("Syncing files...").start();
|
|
138
|
-
try {
|
|
139
|
-
const tar = await import("tar");
|
|
140
|
-
const syncignore = config.workspace?.syncignore ?? [];
|
|
141
|
-
const defaultIgnore = ["**/.git/**", "**/node_modules/**", "**/__pycache__/**", "**/*.pyc", "**/.DS_Store", ...syncignore];
|
|
142
|
-
const ignoreRegexes = defaultIgnore.map((p) => {
|
|
143
|
-
const esc = p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\x00").replace(/\*/g, "[^/]*").replace(/\x00/g, ".*");
|
|
144
|
-
return new RegExp(`(^|/)${esc}($|/)`);
|
|
145
|
-
});
|
|
146
|
-
const chunks = [];
|
|
147
|
-
await new Promise((res, rej) => {
|
|
148
|
-
const pack = tar.create({ cwd: process.cwd(), filter: (p) => {
|
|
149
|
-
const rel = p.replace(/^\.\//, "");
|
|
150
|
-
return !ignoreRegexes.some((re) => re.test(rel));
|
|
151
|
-
} }, ["."]);
|
|
152
|
-
pack.on("data", (c) => chunks.push(c));
|
|
153
|
-
pack.on("end", res);
|
|
154
|
-
pack.on("error", rej);
|
|
155
|
-
});
|
|
156
|
-
await client.push(instance.id, Buffer.concat(chunks), mountPath);
|
|
157
|
-
wsSpinner.succeed("Files synced");
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
wsSpinner.warn("File sync skipped — you can sync manually once connected");
|
|
161
|
-
}
|
|
135
|
+
// Sync happens during ssh/exec, not during up
|
|
162
136
|
// Setup command
|
|
163
137
|
if (config.workspace?.setup) {
|
|
164
138
|
const setupSpinner = ora("Running setup...").start();
|
package/dist/index.js
CHANGED
|
@@ -42,9 +42,10 @@ program
|
|
|
42
42
|
.action(ssh);
|
|
43
43
|
program
|
|
44
44
|
.command("exec <command...>")
|
|
45
|
-
.description("
|
|
45
|
+
.description("Sync files and run a command on the instance")
|
|
46
46
|
.option("--timeout <seconds>", "Command timeout in seconds", "600")
|
|
47
|
-
.
|
|
47
|
+
.option("--no-sync", "Skip file sync before executing")
|
|
48
|
+
.action((command, opts) => exec(command, { timeout: opts.timeout, noSync: !opts.sync }));
|
|
48
49
|
program
|
|
49
50
|
.command("sync")
|
|
50
51
|
.description("Sync workspace from instance to local")
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const SUPABASE_URL = "https://ykgwmdvzzburglrkjutc.supabase.co";
|
|
2
2
|
export declare const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlrZ3dtZHZ6emJ1cmdscmtqdXRjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI0MzI0MzUsImV4cCI6MjA4ODAwODQzNX0.c-MSPTYr9_G77sy_2hAcY7hth8mSKmNQDzfTjIx8rE0";
|
|
3
3
|
export declare const AGENT_URL = "https://gpu.platops.ai";
|
|
4
|
-
export declare const PLATFORM_URL
|
|
4
|
+
export declare const PLATFORM_URL: string;
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const SUPABASE_URL = "https://ykgwmdvzzburglrkjutc.supabase.co";
|
|
2
2
|
export const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlrZ3dtZHZ6emJ1cmdscmtqdXRjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI0MzI0MzUsImV4cCI6MjA4ODAwODQzNX0.c-MSPTYr9_G77sy_2hAcY7hth8mSKmNQDzfTjIx8rE0";
|
|
3
3
|
export const AGENT_URL = "https://gpu.platops.ai";
|
|
4
|
-
export const PLATFORM_URL = "https://platform.trycassian.com";
|
|
4
|
+
export const PLATFORM_URL = process.env.CASSIAN_PLATFORM_URL || "https://platform.trycassian.com";
|
package/dist/lib/push.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export async function pushWorkspace(client, instanceId, config) {
|
|
2
|
+
const tar = await import("tar");
|
|
3
|
+
const mountPath = config.volumes?.[0]?.mount || "/workspace";
|
|
4
|
+
const syncignore = config.workspace?.syncignore ?? [];
|
|
5
|
+
const defaultIgnore = ["**/.git/**", "**/node_modules/**", "**/__pycache__/**", "**/*.pyc", "**/.DS_Store", ...syncignore];
|
|
6
|
+
const ignoreRegexes = defaultIgnore.map((p) => {
|
|
7
|
+
const esc = p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\x00").replace(/\*/g, "[^/]*").replace(/\x00/g, ".*");
|
|
8
|
+
return new RegExp(`(^|/)${esc}($|/)`);
|
|
9
|
+
});
|
|
10
|
+
const chunks = [];
|
|
11
|
+
await new Promise((res, rej) => {
|
|
12
|
+
const pack = tar.create({ cwd: process.cwd(), filter: (p) => {
|
|
13
|
+
const rel = p.replace(/^\.\//, "");
|
|
14
|
+
return !ignoreRegexes.some((re) => re.test(rel));
|
|
15
|
+
} }, ["."]);
|
|
16
|
+
pack.on("data", (c) => chunks.push(c));
|
|
17
|
+
pack.on("end", res);
|
|
18
|
+
pack.on("error", rej);
|
|
19
|
+
});
|
|
20
|
+
await client.push(instanceId, Buffer.concat(chunks), mountPath);
|
|
21
|
+
}
|