feat: v2
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
import { defineCommand } from "citty";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import { stdin, stdout } from "node:process";
|
||||
import { apiRequest } from "../lib/api";
|
||||
import {
|
||||
readSession,
|
||||
removeSessions,
|
||||
writeSession,
|
||||
type KuberSession,
|
||||
} from "../lib/session";
|
||||
|
||||
type LoginResponse = KuberSession;
|
||||
|
||||
async function promptUsername(): Promise<string> {
|
||||
const readline = createInterface({ input: stdin, output: stdout });
|
||||
try {
|
||||
return (await readline.question("Username: ")).trim();
|
||||
} finally {
|
||||
readline.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function promptPassword(): Promise<string> {
|
||||
if (!stdin.isTTY || !stdin.setRawMode) {
|
||||
throw new Error("Password input requires an interactive terminal");
|
||||
}
|
||||
|
||||
stdout.write("Password: ");
|
||||
stdin.setRawMode(true);
|
||||
stdin.resume();
|
||||
return new Promise((resolve, reject) => {
|
||||
let password = "";
|
||||
const cleanup = () => {
|
||||
stdin.off("data", onData);
|
||||
stdin.setRawMode(false);
|
||||
stdin.pause();
|
||||
stdout.write("\n");
|
||||
};
|
||||
const onData = (chunk: Buffer) => {
|
||||
const value = chunk.toString("utf8");
|
||||
if (value === "\u0003") {
|
||||
cleanup();
|
||||
reject(new Error("Login cancelled"));
|
||||
return;
|
||||
}
|
||||
if (value === "\r" || value === "\n") {
|
||||
cleanup();
|
||||
resolve(password);
|
||||
return;
|
||||
}
|
||||
if (value === "\u007f" || value === "\b") {
|
||||
password = password.slice(0, -1);
|
||||
return;
|
||||
}
|
||||
password += value;
|
||||
};
|
||||
stdin.on("data", onData);
|
||||
});
|
||||
}
|
||||
|
||||
export async function loginUser(
|
||||
username: string,
|
||||
password: string,
|
||||
persistent: boolean,
|
||||
): Promise<LoginResponse> {
|
||||
const session = await apiRequest<LoginResponse>(
|
||||
"/login",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ username, password, persistent }),
|
||||
},
|
||||
{ authenticated: false },
|
||||
);
|
||||
await writeSession(session, persistent);
|
||||
return session;
|
||||
}
|
||||
|
||||
export const login = defineCommand({
|
||||
meta: {
|
||||
name: "login",
|
||||
description: "Log in to kuber.astrxl.dev",
|
||||
},
|
||||
args: {
|
||||
persist: {
|
||||
type: "boolean",
|
||||
description: "Keep the login across reboots",
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const username = String(args._[0] ?? "").trim() || (await promptUsername());
|
||||
if (!username) throw new Error("Username is required");
|
||||
const session = await loginUser(
|
||||
username,
|
||||
await promptPassword(),
|
||||
Boolean(args.persist),
|
||||
);
|
||||
console.log(`Logged in as ${session.user.username}`);
|
||||
},
|
||||
});
|
||||
|
||||
export const logout = defineCommand({
|
||||
meta: {
|
||||
name: "logout",
|
||||
description: "Log out of kuber.astrxl.dev",
|
||||
},
|
||||
async run() {
|
||||
const session = await readSession();
|
||||
if (session) {
|
||||
try {
|
||||
await apiRequest<void>(
|
||||
"/logout",
|
||||
{
|
||||
method: "POST",
|
||||
},
|
||||
{ session },
|
||||
);
|
||||
} finally {
|
||||
await removeSessions();
|
||||
}
|
||||
} else {
|
||||
await removeSessions();
|
||||
}
|
||||
console.log("Logged out");
|
||||
},
|
||||
});
|
||||
|
||||
export const whoami = defineCommand({
|
||||
meta: {
|
||||
name: "whoami",
|
||||
description: "Show the current kuber user",
|
||||
},
|
||||
async run() {
|
||||
const result = await apiRequest<KuberSession["user"]>("/me");
|
||||
console.log(`${result.username} (${result.roles.join(", ")})`);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user