@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.
- package/README.md +172 -0
- package/dist/app-0F0Zyyt7.js +350 -0
- package/dist/bash-complete.js +13 -0
- package/dist/cli.js +15 -0
- package/dist/credentials-CfHLkU7k.js +54 -0
- package/dist/currentHandler-oXFeNNgd.js +11 -0
- package/dist/listHandler-BiwIo88i.js +44 -0
- package/dist/loginHandler-C8D6DgjW.js +363 -0
- package/dist/logoutHandler-BBtdpxHX.js +14 -0
- package/dist/pushHandler-DkCrbV7y.js +529 -0
- package/dist/resolve-token-BL8vL_ok.js +33 -0
- package/dist/resolve-workspace-lHEoGPHe.js +89 -0
- package/dist/sdk-client-DunBmYLR.js +30559 -0
- package/dist/selectHandler-C2UDAWOl.js +40 -0
- package/dist/statusHandler-DQDmEOGI.js +51 -0
- package/dist/token-refresh-waF23pyw.js +48 -0
- package/dist/workspace-store-BcyMJAht.js +29 -0
- package/package.json +54 -0
|
@@ -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
|
+
}
|