@levr-one/cli 0.1.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.
@@ -0,0 +1,40 @@
1
+ import "./credentials-CfHLkU7k.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-DunBmYLR.js";
3
+ import { saveWorkspace } from "./workspace-store-BcyMJAht.js";
4
+ import "./token-refresh-waF23pyw.js";
5
+ import { resolveToken } from "./resolve-token-BL8vL_ok.js";
6
+
7
+ //#region src/commands/workspace/selectHandler.ts
8
+ async function selectHandler(_flags, workspaceId) {
9
+ let auth;
10
+ try {
11
+ auth = await resolveToken();
12
+ } catch {
13
+ this.logger.error("Not authenticated. Run 'levr auth login' first.");
14
+ this.process.exitCode = 1;
15
+ return;
16
+ }
17
+ if (auth.type === "pat") {
18
+ this.logger.error("Workspace selection requires JWT auth. Run 'levr auth login'.");
19
+ this.process.exitCode = 1;
20
+ return;
21
+ }
22
+ configureClient(auth);
23
+ const result = await authGetSitesV1();
24
+ if (result.error) {
25
+ this.logger.error("Failed to list workspaces.");
26
+ this.process.exitCode = 1;
27
+ return;
28
+ }
29
+ const site = result.data.sites.find((s) => s.workspace_id === workspaceId);
30
+ if (!site) {
31
+ this.logger.error(`Workspace ${workspaceId} not found. Run 'levr workspace list'.`);
32
+ this.process.exitCode = 1;
33
+ return;
34
+ }
35
+ saveWorkspace(workspaceId);
36
+ this.logger.success(`Workspace set to ${site.workspace_name} (${site.workspace_id})`);
37
+ }
38
+
39
+ //#endregion
40
+ export { selectHandler };
@@ -0,0 +1,51 @@
1
+ import { getApiUrl, getPatToken, readCredentials } from "./credentials-CfHLkU7k.js";
2
+ import { authGetProfileV1, configureClient } from "./sdk-client-DunBmYLR.js";
3
+ import { isTokenExpired } from "./token-refresh-waF23pyw.js";
4
+ import chalk from "chalk";
5
+
6
+ //#region src/commands/auth/statusHandler.ts
7
+ async function statusHandler() {
8
+ const apiUrl = getApiUrl();
9
+ const pat = getPatToken();
10
+ if (pat) {
11
+ if (await checkApiReachable(pat, "pat")) {
12
+ this.process.stdout.write(`${chalk.green("ok")} Authenticated via LEVR_TOKEN (PAT)\n`);
13
+ this.process.stdout.write(` API: ${apiUrl} (reachable)\n`);
14
+ } else {
15
+ this.process.stdout.write(`${chalk.yellow("warn")} LEVR_TOKEN is set but API is unreachable\n`);
16
+ this.process.stdout.write(` API: ${apiUrl}\n`);
17
+ }
18
+ return;
19
+ }
20
+ const creds = readCredentials();
21
+ if (!creds) {
22
+ this.process.stdout.write(`${chalk.red("error")} Not authenticated. Run 'levr auth login' or set LEVR_TOKEN.\n`);
23
+ this.process.exitCode = 1;
24
+ return;
25
+ }
26
+ if (isTokenExpired(creds)) {
27
+ this.process.stdout.write(`${chalk.red("error")} Token expired. Run 'levr auth login' to re-authenticate.\n`);
28
+ this.process.exitCode = 1;
29
+ return;
30
+ }
31
+ const reachable = await checkApiReachable(creds.access_token, "jwt");
32
+ const expiresAt = new Date(creds.expires_at);
33
+ const hoursLeft = Math.max(0, Math.round((expiresAt.getTime() - Date.now()) / (1e3 * 60 * 60)));
34
+ this.process.stdout.write(`${chalk.green("ok")} Authenticated as ${chalk.bold(creds.user.email)}\n`);
35
+ this.process.stdout.write(` API: ${apiUrl}${reachable ? " (reachable)" : " (unreachable)"}\n`);
36
+ this.process.stdout.write(` Auth: JWT via credentials file (expires in ${hoursLeft}h)\n`);
37
+ }
38
+ async function checkApiReachable(token, type) {
39
+ try {
40
+ configureClient({
41
+ token,
42
+ type
43
+ });
44
+ return !(await authGetProfileV1()).error;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ //#endregion
51
+ export { statusHandler };
@@ -0,0 +1,48 @@
1
+ import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./credentials-CfHLkU7k.js";
2
+
3
+ //#region src/auth/token-refresh.ts
4
+ const REFRESH_BUFFER_MS = 300 * 1e3;
5
+ /**
6
+ * Check if stored JWT credentials are expired (or within 5 min of expiry).
7
+ */
8
+ function isTokenExpired(creds) {
9
+ const expiresAt = new Date(creds.expires_at).getTime();
10
+ return Date.now() >= expiresAt - REFRESH_BUFFER_MS;
11
+ }
12
+ /**
13
+ * Refresh JWT credentials using the stored refresh token.
14
+ * Returns updated credentials or null if refresh failed.
15
+ */
16
+ async function refreshToken(creds) {
17
+ const apiUrl = getApiUrl();
18
+ try {
19
+ const res = await fetch(`${apiUrl}/v1/oauth/token`, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
22
+ body: new URLSearchParams({
23
+ grant_type: "refresh_token",
24
+ refresh_token: creds.refresh_token,
25
+ client_id: CLI_CLIENT_ID
26
+ })
27
+ });
28
+ if (!res.ok) {
29
+ deleteCredentials();
30
+ return null;
31
+ }
32
+ const data = await res.json();
33
+ const updated = {
34
+ ...creds,
35
+ access_token: data.access_token,
36
+ refresh_token: data.refresh_token,
37
+ expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
38
+ };
39
+ writeCredentials(updated);
40
+ return updated;
41
+ } catch {
42
+ deleteCredentials();
43
+ return null;
44
+ }
45
+ }
46
+
47
+ //#endregion
48
+ export { isTokenExpired, refreshToken };
@@ -0,0 +1,29 @@
1
+ import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+
5
+ //#region src/workspace/workspace-store.ts
6
+ const WORKSPACE_PATH = join(join(homedir(), ".config", "levr"), "workspace.json");
7
+ function loadWorkspace() {
8
+ try {
9
+ const raw = readFileSync(WORKSPACE_PATH, "utf8");
10
+ const data = JSON.parse(raw);
11
+ return typeof data.workspace_id === "string" ? data.workspace_id : null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ function saveWorkspace(workspaceId) {
17
+ mkdirSync(dirname(WORKSPACE_PATH), { recursive: true });
18
+ const tmp = WORKSPACE_PATH + ".tmp";
19
+ writeFileSync(tmp, JSON.stringify({ workspace_id: workspaceId }, null, 2), { mode: 384 });
20
+ renameSync(tmp, WORKSPACE_PATH);
21
+ }
22
+ function clearWorkspace() {
23
+ try {
24
+ unlinkSync(WORKSPACE_PATH);
25
+ } catch {}
26
+ }
27
+
28
+ //#endregion
29
+ export { clearWorkspace, loadWorkspace, saveWorkspace };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@levr-one/cli",
3
+ "version": "0.1.0",
4
+ "description": "The command-line interface for Levr",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "dist/cli.js",
8
+ "bin": {
9
+ "__levr_bash_complete": "dist/bash-complete.js",
10
+ "levr": "dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "dependencies": {
17
+ "@stricli/auto-complete": "^1.2.0",
18
+ "@stricli/core": "^1.2.0",
19
+ "chalk": "^5.6.2",
20
+ "open": "^10.1.0",
21
+ "ora": "^9.0.0",
22
+ "zod": "^3.25.0"
23
+ },
24
+ "devDependencies": {
25
+ "@eslint/js": "^9.18.0",
26
+ "@types/node": "^20.10.0",
27
+ "eslint": "^9.18.0",
28
+ "eslint-config-prettier": "^10.0.1",
29
+ "eslint-plugin-prettier": "^5.2.2",
30
+ "globals": "^16.0.0",
31
+ "npm-run-all": "^4.1.5",
32
+ "prettier": "^3.4.2",
33
+ "rimraf": "^6.0.1",
34
+ "tsdown": "^0.15.6",
35
+ "typescript": "^5.7.3",
36
+ "typescript-eslint": "^8.20.0",
37
+ "vitest": "^4.1.0-beta.2"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/BitModern/levr.git",
48
+ "directory": "packages/cli"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/BitModern/levr/issues"
52
+ },
53
+ "homepage": "https://github.com/BitModern/levr/tree/main/packages/cli#readme"
54
+ }