@graphit/cli 0.1.3 → 0.1.5
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/api/client.d.ts +16 -0
- package/dist/api/client.js +67 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/credentials.d.ts +15 -0
- package/dist/auth/credentials.js +34 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/login.d.ts +2 -0
- package/dist/auth/login.js +230 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/token.d.ts +5 -0
- package/dist/auth/token.js +42 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +68 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/connector.d.ts +2 -0
- package/dist/commands/connector.js +97 -0
- package/dist/commands/connector.js.map +1 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +124 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/ds.d.ts +2 -0
- package/dist/commands/ds.js +53 -0
- package/dist/commands/ds.js.map +1 -0
- package/dist/commands/kb.d.ts +2 -0
- package/dist/commands/kb.js +321 -0
- package/dist/commands/kb.js.map +1 -0
- package/dist/commands/query.d.ts +2 -0
- package/dist/commands/query.js +61 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +173 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/output/format.d.ts +8 -0
- package/dist/output/format.js +30 -0
- package/dist/output/format.js.map +1 -0
- package/dist/output/json.d.ts +1 -0
- package/dist/output/json.js +4 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/table.d.ts +2 -0
- package/dist/output/table.js +17 -0
- package/dist/output/table.js.map +1 -0
- package/dist/update-check.d.ts +3 -0
- package/dist/update-check.js +78 -0
- package/dist/update-check.js.map +1 -0
- package/package.json +1 -1
- package/skills/graphit/SKILL.md +16 -2
- package/skills/graphit/references/chart-patterns.md +11 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ApiError {
|
|
2
|
+
status: number;
|
|
3
|
+
detail: string;
|
|
4
|
+
retry_after_seconds?: number;
|
|
5
|
+
}
|
|
6
|
+
declare class ApiClient {
|
|
7
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
8
|
+
postUnauthed<T>(path: string, body: unknown): Promise<T>;
|
|
9
|
+
get<T>(path: string): Promise<T>;
|
|
10
|
+
post<T>(path: string, body: unknown): Promise<T>;
|
|
11
|
+
put<T>(path: string, body: unknown): Promise<T>;
|
|
12
|
+
delete<T>(path: string): Promise<T>;
|
|
13
|
+
private _fetch;
|
|
14
|
+
}
|
|
15
|
+
export declare const apiClient: ApiClient;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getValidIdToken } from "../auth/token.js";
|
|
2
|
+
import { getApiBaseUrl } from "../config.js";
|
|
3
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
4
|
+
class ApiClient {
|
|
5
|
+
async request(method, path, body) {
|
|
6
|
+
const { id_token } = await getValidIdToken();
|
|
7
|
+
return this._fetch(method, path, body, {
|
|
8
|
+
Authorization: `Bearer ${id_token}`,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
async postUnauthed(path, body) {
|
|
12
|
+
return this._fetch("POST", path, body);
|
|
13
|
+
}
|
|
14
|
+
async get(path) {
|
|
15
|
+
return this.request("GET", path);
|
|
16
|
+
}
|
|
17
|
+
async post(path, body) {
|
|
18
|
+
return this.request("POST", path, body);
|
|
19
|
+
}
|
|
20
|
+
async put(path, body) {
|
|
21
|
+
return this.request("PUT", path, body);
|
|
22
|
+
}
|
|
23
|
+
async delete(path) {
|
|
24
|
+
return this.request("DELETE", path);
|
|
25
|
+
}
|
|
26
|
+
async _fetch(method, path, body, extraHeaders) {
|
|
27
|
+
const url = `${getApiBaseUrl()}${path}`;
|
|
28
|
+
const headers = {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"X-Graphit-Source": "cli",
|
|
31
|
+
...extraHeaders,
|
|
32
|
+
};
|
|
33
|
+
const resp = await fetch(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
37
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
38
|
+
});
|
|
39
|
+
if (!resp.ok) {
|
|
40
|
+
let detail = `HTTP ${resp.status}`;
|
|
41
|
+
try {
|
|
42
|
+
const errBody = (await resp.json());
|
|
43
|
+
detail = errBody.detail || detail;
|
|
44
|
+
if (resp.status === 429) {
|
|
45
|
+
const retryAfter = parseInt(resp.headers.get("Retry-After") || "60", 10);
|
|
46
|
+
const err = {
|
|
47
|
+
status: 429,
|
|
48
|
+
detail,
|
|
49
|
+
retry_after_seconds: retryAfter,
|
|
50
|
+
};
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
if (e.status === 429)
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
const err = { status: resp.status, detail };
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
if (resp.status === 204)
|
|
62
|
+
return undefined;
|
|
63
|
+
return (await resp.json());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export const apiClient = new ApiClient();
|
|
67
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface StoredCredentials {
|
|
2
|
+
refresh_token: string;
|
|
3
|
+
user_id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
org_id: string;
|
|
6
|
+
org_name: string;
|
|
7
|
+
seat_type: string;
|
|
8
|
+
created_at: string;
|
|
9
|
+
id_token?: string;
|
|
10
|
+
id_token_expiry?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function getCredentialsPath(): string;
|
|
13
|
+
export declare function readCredentials(): Promise<StoredCredentials | null>;
|
|
14
|
+
export declare function writeCredentials(creds: StoredCredentials): Promise<void>;
|
|
15
|
+
export declare function deleteCredentials(): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink, mkdir, chmod } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
const CREDENTIALS_PATH = join(homedir(), ".graphit", "credentials.json");
|
|
6
|
+
export function getCredentialsPath() {
|
|
7
|
+
return CREDENTIALS_PATH;
|
|
8
|
+
}
|
|
9
|
+
export async function readCredentials() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await readFile(CREDENTIALS_PATH, "utf-8");
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function writeCredentials(creds) {
|
|
19
|
+
const dir = dirname(CREDENTIALS_PATH);
|
|
20
|
+
if (!existsSync(dir)) {
|
|
21
|
+
await mkdir(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
|
|
24
|
+
await chmod(CREDENTIALS_PATH, 0o600);
|
|
25
|
+
}
|
|
26
|
+
export async function deleteCredentials() {
|
|
27
|
+
try {
|
|
28
|
+
await unlink(CREDENTIALS_PATH);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// File may not exist
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/auth/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAcrC,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;AAEzE,MAAM,UAAU,kBAAkB;IAChC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAwB;IAC7D,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,MAAM,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
3
|
+
import { writeCredentials } from "./credentials.js";
|
|
4
|
+
import { apiClient } from "../api/client.js";
|
|
5
|
+
import { getFrontendUrl } from "../config.js";
|
|
6
|
+
import open from "open";
|
|
7
|
+
const AUTH_TIMEOUT_MS = 120_000;
|
|
8
|
+
function escapeHtml(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/&/g, "&")
|
|
11
|
+
.replace(/</g, "<")
|
|
12
|
+
.replace(/>/g, ">")
|
|
13
|
+
.replace(/"/g, """);
|
|
14
|
+
}
|
|
15
|
+
function renderCallbackPage(variant, title, message) {
|
|
16
|
+
const safeTitle = escapeHtml(title);
|
|
17
|
+
const safeMessage = escapeHtml(message);
|
|
18
|
+
const statusIcon = variant === "success"
|
|
19
|
+
? `<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
|
|
20
|
+
<circle cx="28" cy="28" r="28" fill="#4DB6AC"/>
|
|
21
|
+
<path d="M17 28.5L24 35.5L39 20.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
22
|
+
</svg>`
|
|
23
|
+
: `<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
|
|
24
|
+
<circle cx="28" cy="28" r="28" fill="#B91C1C"/>
|
|
25
|
+
<path d="M20 20L36 36M36 20L20 36" stroke="#fff" stroke-width="3" stroke-linecap="round"/>
|
|
26
|
+
</svg>`;
|
|
27
|
+
return `<!DOCTYPE html>
|
|
28
|
+
<html lang="en">
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="utf-8">
|
|
31
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
32
|
+
<title>${safeTitle} - Graphit</title>
|
|
33
|
+
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700&display=swap">
|
|
34
|
+
<style>
|
|
35
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
36
|
+
|
|
37
|
+
body {
|
|
38
|
+
font-family: 'Satoshi', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
39
|
+
min-height: 100vh;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
position: relative;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Mesh gradient background matching AuthLayout */
|
|
48
|
+
.bg-base {
|
|
49
|
+
position: fixed; inset: 0;
|
|
50
|
+
background: linear-gradient(135deg, #1a1a1c 0%, #1c2e2b 40%, #222224 70%, #1a2520 100%);
|
|
51
|
+
}
|
|
52
|
+
.bg-orb-bl {
|
|
53
|
+
position: fixed; inset: 0;
|
|
54
|
+
background: radial-gradient(ellipse 70% 65% at 15% 85%, rgba(77,182,172,0.45) 0%, rgba(77,182,172,0.15) 40%, transparent 70%);
|
|
55
|
+
}
|
|
56
|
+
.bg-orb-tr {
|
|
57
|
+
position: fixed; inset: 0;
|
|
58
|
+
background: radial-gradient(ellipse 55% 50% at 85% 15%, rgba(77,182,172,0.35) 0%, rgba(56,142,133,0.10) 45%, transparent 70%);
|
|
59
|
+
}
|
|
60
|
+
.bg-wash {
|
|
61
|
+
position: fixed; inset: 0;
|
|
62
|
+
background: radial-gradient(ellipse 45% 55% at 75% 55%, rgba(128,203,196,0.20) 0%, transparent 60%);
|
|
63
|
+
}
|
|
64
|
+
.bg-pool {
|
|
65
|
+
position: fixed; inset: 0;
|
|
66
|
+
background: radial-gradient(ellipse 50% 45% at 30% 40%, rgba(38,120,110,0.30) 0%, transparent 60%);
|
|
67
|
+
}
|
|
68
|
+
.bg-vignette {
|
|
69
|
+
position: fixed; inset: 0;
|
|
70
|
+
background: radial-gradient(ellipse 75% 75% at 50% 50%, transparent 10%, rgba(16,16,18,0.85) 100%);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.content {
|
|
74
|
+
position: relative;
|
|
75
|
+
z-index: 10;
|
|
76
|
+
text-align: center;
|
|
77
|
+
padding: 48px 40px;
|
|
78
|
+
max-width: 400px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.card {
|
|
82
|
+
background: rgba(255,255,255,0.08);
|
|
83
|
+
backdrop-filter: blur(24px);
|
|
84
|
+
-webkit-backdrop-filter: blur(24px);
|
|
85
|
+
border: 1px solid rgba(255,255,255,0.12);
|
|
86
|
+
border-radius: 16px;
|
|
87
|
+
padding: 40px 32px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.icon { margin-bottom: 20px; }
|
|
91
|
+
|
|
92
|
+
h1 {
|
|
93
|
+
font-size: 20px;
|
|
94
|
+
font-weight: 700;
|
|
95
|
+
color: #fff;
|
|
96
|
+
letter-spacing: -0.02em;
|
|
97
|
+
margin-bottom: 8px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
p {
|
|
101
|
+
font-size: 14px;
|
|
102
|
+
color: rgba(255,255,255,0.5);
|
|
103
|
+
line-height: 1.6;
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
106
|
+
</head>
|
|
107
|
+
<body>
|
|
108
|
+
<div class="bg-base"></div>
|
|
109
|
+
<div class="bg-orb-bl"></div>
|
|
110
|
+
<div class="bg-orb-tr"></div>
|
|
111
|
+
<div class="bg-wash"></div>
|
|
112
|
+
<div class="bg-pool"></div>
|
|
113
|
+
<div class="bg-vignette"></div>
|
|
114
|
+
|
|
115
|
+
<div class="content">
|
|
116
|
+
<div class="card">
|
|
117
|
+
<div class="icon">${statusIcon}</div>
|
|
118
|
+
<h1>${safeTitle}</h1>
|
|
119
|
+
<p>${safeMessage}</p>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</body>
|
|
123
|
+
</html>`;
|
|
124
|
+
}
|
|
125
|
+
function generateCodeVerifier() {
|
|
126
|
+
return randomBytes(32).toString("base64url");
|
|
127
|
+
}
|
|
128
|
+
function computeCodeChallenge(verifier) {
|
|
129
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
130
|
+
}
|
|
131
|
+
function generateState() {
|
|
132
|
+
return randomBytes(32).toString("hex");
|
|
133
|
+
}
|
|
134
|
+
export async function performLogin() {
|
|
135
|
+
const state = generateState();
|
|
136
|
+
const codeVerifier = generateCodeVerifier();
|
|
137
|
+
const codeChallenge = computeCodeChallenge(codeVerifier);
|
|
138
|
+
const { port, result } = await startCallbackServer(state, codeVerifier);
|
|
139
|
+
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
140
|
+
await apiClient.postUnauthed("/api/v1/cli/auth/init", {
|
|
141
|
+
state,
|
|
142
|
+
code_challenge: codeChallenge,
|
|
143
|
+
callback_url: callbackUrl,
|
|
144
|
+
});
|
|
145
|
+
const frontendUrl = getFrontendUrl();
|
|
146
|
+
const authUrl = `${frontendUrl}/auth/cli?state=${state}&callback=${encodeURIComponent(callbackUrl)}`;
|
|
147
|
+
console.log("\nOpening browser to log in to Graphit...");
|
|
148
|
+
console.log(` -> ${authUrl}\n`);
|
|
149
|
+
await open(authUrl);
|
|
150
|
+
console.log("Waiting for authentication...\n");
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
async function startCallbackServer(expectedState, codeVerifier) {
|
|
154
|
+
return new Promise((resolveServer) => {
|
|
155
|
+
let resolveLogin;
|
|
156
|
+
let rejectLogin;
|
|
157
|
+
const loginPromise = new Promise((res, rej) => {
|
|
158
|
+
resolveLogin = res;
|
|
159
|
+
rejectLogin = rej;
|
|
160
|
+
});
|
|
161
|
+
const timeout = setTimeout(() => {
|
|
162
|
+
server.close();
|
|
163
|
+
rejectLogin(new Error("Login timed out. Please try again."));
|
|
164
|
+
}, AUTH_TIMEOUT_MS);
|
|
165
|
+
const server = createServer(async (req, res) => {
|
|
166
|
+
const url = new URL(req.url || "/", `http://127.0.0.1`);
|
|
167
|
+
if (url.pathname !== "/callback") {
|
|
168
|
+
res.writeHead(404);
|
|
169
|
+
res.end("Not found");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const code = url.searchParams.get("code");
|
|
173
|
+
const returnedState = url.searchParams.get("state");
|
|
174
|
+
if (returnedState !== expectedState) {
|
|
175
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
176
|
+
res.end(renderCallbackPage("error", "Authentication failed", "State mismatch - possible CSRF attack."));
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
server.close();
|
|
179
|
+
rejectLogin(new Error("State mismatch in callback - possible CSRF."));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!code) {
|
|
183
|
+
const error = url.searchParams.get("error") || "No authorization code received";
|
|
184
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
185
|
+
res.end(renderCallbackPage("error", "Authentication failed", error));
|
|
186
|
+
clearTimeout(timeout);
|
|
187
|
+
server.close();
|
|
188
|
+
rejectLogin(new Error(error));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const tokenResp = await apiClient.postUnauthed("/api/v1/cli/auth/token", {
|
|
193
|
+
code,
|
|
194
|
+
code_verifier: codeVerifier,
|
|
195
|
+
state: expectedState,
|
|
196
|
+
});
|
|
197
|
+
const creds = {
|
|
198
|
+
refresh_token: tokenResp.refresh_token,
|
|
199
|
+
user_id: tokenResp.user_id,
|
|
200
|
+
email: tokenResp.email,
|
|
201
|
+
org_id: tokenResp.org_id,
|
|
202
|
+
org_name: tokenResp.org_name,
|
|
203
|
+
seat_type: tokenResp.seat_type,
|
|
204
|
+
created_at: new Date().toISOString(),
|
|
205
|
+
};
|
|
206
|
+
await writeCredentials(creds);
|
|
207
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
208
|
+
res.end(renderCallbackPage("success", "Logged in to Graphit", "You can close this tab and return to your terminal."));
|
|
209
|
+
clearTimeout(timeout);
|
|
210
|
+
server.close();
|
|
211
|
+
resolveLogin(creds);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
215
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
216
|
+
res.end(renderCallbackPage("error", "Authentication failed", msg));
|
|
217
|
+
clearTimeout(timeout);
|
|
218
|
+
server.close();
|
|
219
|
+
rejectLogin(err instanceof Error ? err : new Error(msg));
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
server.listen(0, "127.0.0.1", () => {
|
|
223
|
+
const addr = server.address();
|
|
224
|
+
if (addr && typeof addr === "object") {
|
|
225
|
+
resolveServer({ port: addr.port, result: loginPromise });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readCredentials, writeCredentials } from "./credentials.js";
|
|
2
|
+
const FIREBASE_TOKEN_URL = "https://securetoken.googleapis.com/v1/token";
|
|
3
|
+
const FIREBASE_API_KEY = process.env.GRAPHIT_FIREBASE_API_KEY || "AIzaSyCxahikMb2o0BroW1gqn2aWNad6S6-n-gI";
|
|
4
|
+
const TOKEN_BUFFER_SECONDS = 60;
|
|
5
|
+
export async function getValidIdToken() {
|
|
6
|
+
const creds = await readCredentials();
|
|
7
|
+
if (!creds) {
|
|
8
|
+
throw new Error("Not logged in. Run `graphit auth login` first.");
|
|
9
|
+
}
|
|
10
|
+
if (creds.id_token &&
|
|
11
|
+
creds.id_token_expiry &&
|
|
12
|
+
creds.id_token_expiry > Date.now() / 1000 + TOKEN_BUFFER_SECONDS) {
|
|
13
|
+
return { id_token: creds.id_token, user_id: creds.user_id };
|
|
14
|
+
}
|
|
15
|
+
const resp = await fetch(`${FIREBASE_TOKEN_URL}?key=${FIREBASE_API_KEY}`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
18
|
+
body: new URLSearchParams({
|
|
19
|
+
grant_type: "refresh_token",
|
|
20
|
+
refresh_token: creds.refresh_token,
|
|
21
|
+
}),
|
|
22
|
+
signal: AbortSignal.timeout(5000),
|
|
23
|
+
});
|
|
24
|
+
if (!resp.ok) {
|
|
25
|
+
const body = await resp.text();
|
|
26
|
+
if (body.includes("TOKEN_EXPIRED") || body.includes("INVALID_REFRESH_TOKEN")) {
|
|
27
|
+
throw new Error("Session expired. Run `graphit auth login` to re-authenticate.");
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`Token refresh failed (${resp.status}): ${body}`);
|
|
30
|
+
}
|
|
31
|
+
const data = (await resp.json());
|
|
32
|
+
const expiresIn = parseInt(data.expires_in, 10);
|
|
33
|
+
const expiry = Date.now() / 1000 + expiresIn;
|
|
34
|
+
await writeCredentials({
|
|
35
|
+
...creds,
|
|
36
|
+
refresh_token: data.refresh_token || creds.refresh_token,
|
|
37
|
+
id_token: data.id_token,
|
|
38
|
+
id_token_expiry: expiry,
|
|
39
|
+
});
|
|
40
|
+
return { id_token: data.id_token, user_id: data.user_id };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/auth/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,kBAAkB,GAAG,6CAA6C,CAAC;AACzE,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,yCAAyC,CAAC;AAC3G,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAOhC,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IAED,IACE,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,eAAe;QACrB,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,oBAAoB,EAChE,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,KAAK,CACtB,GAAG,kBAAkB,QAAQ,gBAAgB,EAAE,EAC/C;QACE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,IAAI,eAAe,CAAC;YACxB,UAAU,EAAE,eAAe;YAC3B,aAAa,EAAE,KAAK,CAAC,aAAa;SACnC,CAAC;QACF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;KAClC,CACF,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC;YAC7E,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAK9B,CAAC;IAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAE7C,MAAM,gBAAgB,CAAC;QACrB,GAAG,KAAK;QACR,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC,aAAa;QACxD,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,eAAe,EAAE,MAAM;KACxB,CAAC,CAAC;IAEH,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readCredentials, deleteCredentials, getCredentialsPath } from "../auth/credentials.js";
|
|
2
|
+
import { performLogin } from "../auth/login.js";
|
|
3
|
+
import { apiClient } from "../api/client.js";
|
|
4
|
+
import { output, errorOutput } from "../output/format.js";
|
|
5
|
+
export function registerAuthCommands(program) {
|
|
6
|
+
const auth = program.command("auth").description("Authentication commands");
|
|
7
|
+
auth
|
|
8
|
+
.command("login")
|
|
9
|
+
.description("Log in to Graphit via browser")
|
|
10
|
+
.action(async function () {
|
|
11
|
+
try {
|
|
12
|
+
const creds = await performLogin();
|
|
13
|
+
console.log(`Logged in as ${creds.email}`);
|
|
14
|
+
console.log(`Organization: ${creds.org_name}`);
|
|
15
|
+
console.log(`Seat type: ${creds.seat_type}`);
|
|
16
|
+
console.log(`\nCredentials saved to ${getCredentialsPath()}`);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
errorOutput(err);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
auth
|
|
23
|
+
.command("status")
|
|
24
|
+
.description("Show current authentication status")
|
|
25
|
+
.action(async function () {
|
|
26
|
+
try {
|
|
27
|
+
const creds = await readCredentials();
|
|
28
|
+
if (!creds) {
|
|
29
|
+
errorOutput(new Error("Not logged in. Run `graphit auth login` first."));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const me = await apiClient.get("/api/v1/cli/me");
|
|
33
|
+
const tokenExpiry = creds.id_token_expiry
|
|
34
|
+
? new Date(creds.id_token_expiry * 1000).toISOString()
|
|
35
|
+
: "none";
|
|
36
|
+
output(this, {
|
|
37
|
+
email: me.email,
|
|
38
|
+
org_name: me.org_name,
|
|
39
|
+
org_id: me.org_id,
|
|
40
|
+
seat_type: me.seat_type,
|
|
41
|
+
token_expiry: tokenExpiry,
|
|
42
|
+
credentials: getCredentialsPath(),
|
|
43
|
+
}, { keyValue: true });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
errorOutput(err);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
auth
|
|
50
|
+
.command("logout")
|
|
51
|
+
.description("Log out and clear stored credentials")
|
|
52
|
+
.action(async function () {
|
|
53
|
+
try {
|
|
54
|
+
try {
|
|
55
|
+
await apiClient.post("/api/v1/cli/auth/revoke", {});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Best-effort server-side revocation
|
|
59
|
+
}
|
|
60
|
+
await deleteCredentials();
|
|
61
|
+
console.log("Logged out. Credentials removed.");
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
errorOutput(err);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAU1D,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC;IAE5E,IAAI;SACD,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,+BAA+B,CAAC;SAC5C,MAAM,CAAC,KAAK;QACX,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,GAAG,CAAC,0BAA0B,kBAAkB,EAAE,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,oCAAoC,CAAC;SACjD,MAAM,CAAC,KAAK;QACX,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;YACtC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,WAAW,CAAC,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC,CAAC;gBACzE,OAAO;YACT,CAAC;YAED,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,CAAa,gBAAgB,CAAC,CAAC;YAE7D,MAAM,WAAW,GAAG,KAAK,CAAC,eAAe;gBACvC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;gBACtD,CAAC,CAAC,MAAM,CAAC;YAEX,MAAM,CAAC,IAAI,EAAE;gBACX,KAAK,EAAE,EAAE,CAAC,KAAK;gBACf,QAAQ,EAAE,EAAE,CAAC,QAAQ;gBACrB,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,YAAY,EAAE,WAAW;gBACzB,WAAW,EAAE,kBAAkB,EAAE;aAClC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,sCAAsC,CAAC;SACnD,MAAM,CAAC,KAAK;QACX,IAAI,CAAC;YACH,IAAI,CAAC;gBACH,MAAM,SAAS,CAAC,IAAI,CAAO,yBAAyB,EAAE,EAAE,CAAC,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;YACvC,CAAC;YACD,MAAM,iBAAiB,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { apiClient } from "../api/client.js";
|
|
3
|
+
import { output, errorOutput } from "../output/format.js";
|
|
4
|
+
export function registerConnectorCommands(program) {
|
|
5
|
+
const connector = program.command("connector").description("Connection management");
|
|
6
|
+
connector
|
|
7
|
+
.command("list")
|
|
8
|
+
.description("List active connections")
|
|
9
|
+
.action(async function () {
|
|
10
|
+
try {
|
|
11
|
+
const resp = await apiClient.get("/api/v1/cli/connectors");
|
|
12
|
+
output(this, resp.connectors, {
|
|
13
|
+
columns: ["id", "name", "account", "auth_type"],
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
errorOutput(err);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const add = connector.command("add").description("Add a new connection");
|
|
21
|
+
add
|
|
22
|
+
.command("snowflake-keypair")
|
|
23
|
+
.description("Add Snowflake via keypair auth")
|
|
24
|
+
.requiredOption("--account <account>", "Snowflake account identifier")
|
|
25
|
+
.requiredOption("--user <user>", "Snowflake username")
|
|
26
|
+
.requiredOption("--key <path>", "Path to private key file (.p8)")
|
|
27
|
+
.option("--warehouse <wh>", "Default warehouse")
|
|
28
|
+
.option("--role <role>", "Default role")
|
|
29
|
+
.option("--database <db>", "Default database")
|
|
30
|
+
.action(async function () {
|
|
31
|
+
try {
|
|
32
|
+
const opts = this.opts();
|
|
33
|
+
const keyStats = await stat(opts.key);
|
|
34
|
+
const mode = keyStats.mode & 0o777;
|
|
35
|
+
if (mode > 0o600) {
|
|
36
|
+
console.error(`Warning: Key file has loose permissions (${mode.toString(8)}). Consider chmod 600.`);
|
|
37
|
+
}
|
|
38
|
+
const keyPem = await readFile(opts.key, "utf-8");
|
|
39
|
+
const resp = await apiClient.post("/api/v1/cli/connectors/snowflake-keypair", {
|
|
40
|
+
account: opts.account,
|
|
41
|
+
user: opts.user,
|
|
42
|
+
private_key_pem: keyPem,
|
|
43
|
+
warehouse: opts.warehouse || null,
|
|
44
|
+
role: opts.role || null,
|
|
45
|
+
database: opts.database || null,
|
|
46
|
+
});
|
|
47
|
+
output(this, resp, { keyValue: true });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
errorOutput(err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
add
|
|
54
|
+
.command("snowflake-oauth")
|
|
55
|
+
.description("Add Snowflake via OAuth (opens browser)")
|
|
56
|
+
.action(function () {
|
|
57
|
+
console.error(JSON.stringify({
|
|
58
|
+
error: "Snowflake OAuth is configured via the Graphit web app. Use snowflake-keypair for CLI setup.",
|
|
59
|
+
}));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
add
|
|
63
|
+
.command("github")
|
|
64
|
+
.description("Add GitHub connection (opens browser)")
|
|
65
|
+
.action(function () {
|
|
66
|
+
console.error(JSON.stringify({
|
|
67
|
+
error: "GitHub connections are configured via the Graphit web app.",
|
|
68
|
+
}));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
connector
|
|
72
|
+
.command("test <id>")
|
|
73
|
+
.description("Test a connection")
|
|
74
|
+
.action(async function (id) {
|
|
75
|
+
try {
|
|
76
|
+
const resp = await apiClient.post(`/api/v1/cli/connectors/${id}/test`, {});
|
|
77
|
+
output(this, resp, { keyValue: true });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
errorOutput(err);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
connector
|
|
84
|
+
.command("remove <id>")
|
|
85
|
+
.description("Remove a connection (requires --yes)")
|
|
86
|
+
.requiredOption("--yes", "Confirm removal")
|
|
87
|
+
.action(async function (id) {
|
|
88
|
+
try {
|
|
89
|
+
await apiClient.delete(`/api/v1/cli/connectors/${id}`);
|
|
90
|
+
console.log(JSON.stringify({ status: "removed", id }));
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
errorOutput(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.js","sourceRoot":"","sources":["../../src/commands/connector.ts"],"names":[],"mappings":"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"}
|