kubeagent 0.1.7 → 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/auth.d.ts CHANGED
@@ -10,6 +10,7 @@ export declare function loadAuth(): AuthState | null;
10
10
  export declare function saveAuth(auth: AuthState): void;
11
11
  export declare function clearAuth(): void;
12
12
  export declare function loginBrowser(serverUrl: string, appUrl: string): Promise<AuthState>;
13
+ export declare function loginDevice(serverUrl: string, appUrl: string): Promise<AuthState>;
13
14
  export declare function createApiKey(auth: AuthState, name: string): Promise<{
14
15
  key: string;
15
16
  prefix: string;
package/dist/auth.js CHANGED
@@ -126,6 +126,61 @@ export async function loginBrowser(serverUrl, appUrl) {
126
126
  saveAuth(auth);
127
127
  return auth;
128
128
  }
129
+ // Device code flow — for headless/remote machines (RFC 8628 style):
130
+ // 1. POST /auth/device → get device_code + user_code
131
+ // 2. Print user_code and verification URL
132
+ // 3. Poll POST /auth/device/token until approved, expired, or timed out
133
+ export async function loginDevice(serverUrl, appUrl) {
134
+ // Step 1: request a device code
135
+ let initRes;
136
+ try {
137
+ initRes = await fetch(`${serverUrl}/auth/device`, {
138
+ method: "POST",
139
+ headers: { "Content-Type": "application/json", Origin: appUrl },
140
+ });
141
+ }
142
+ catch (err) {
143
+ throw new Error(`Could not reach server at ${serverUrl} — is it running?\n (${err.message})`);
144
+ }
145
+ if (!initRes.ok) {
146
+ throw new Error(`Failed to start device login (${initRes.status})`);
147
+ }
148
+ const { device_code, user_code, verification_uri, interval } = await initRes.json();
149
+ // Step 2: show the code
150
+ console.log(`\n Visit: ${verification_uri}`);
151
+ console.log(` Code: ${user_code}\n`);
152
+ console.log("Waiting for authorization...");
153
+ // Step 3: poll
154
+ const pollInterval = (interval ?? 5) * 1000;
155
+ const deadline = Date.now() + 15 * 60 * 1000;
156
+ while (Date.now() < deadline) {
157
+ await new Promise((r) => setTimeout(r, pollInterval));
158
+ let pollRes;
159
+ try {
160
+ pollRes = await fetch(`${serverUrl}/auth/device/token`, {
161
+ method: "POST",
162
+ headers: { "Content-Type": "application/json" },
163
+ body: JSON.stringify({ device_code }),
164
+ });
165
+ }
166
+ catch {
167
+ continue; // network blip — keep polling
168
+ }
169
+ if (pollRes.status === 202)
170
+ continue; // still pending
171
+ if (!pollRes.ok) {
172
+ const body = await pollRes.json().catch(() => ({}));
173
+ if (body.error === "expired_token")
174
+ throw new Error("Device code expired. Run `kubeagent login` again.");
175
+ throw new Error(body.error ?? `Unexpected response (${pollRes.status})`);
176
+ }
177
+ const { token, email, name } = await pollRes.json();
178
+ const auth = { serverUrl, appUrl, token, email, name };
179
+ saveAuth(auth);
180
+ return auth;
181
+ }
182
+ throw new Error("Login timed out (15 minutes).");
183
+ }
129
184
  export async function createApiKey(auth, name) {
130
185
  const url = `${auth.serverUrl}/keys`;
131
186
  let res;
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ import { sendResolve } from "./notify/index.js";
18
18
  import { diagnose } from "./diagnoser/index.js";
19
19
  import { buildSystemPrompt } from "./kb/loader.js";
20
20
  import { join } from "node:path";
21
- import { loadAuth, loginBrowser, createApiKey, showAccount, clearAuth } from "./auth.js";
21
+ import { loadAuth, loginBrowser, loginDevice, createApiKey, showAccount, clearAuth } from "./auth.js";
22
22
  import { fetchSlackWebhook } from "./proxy-client.js";
23
23
  import { createInterface } from "node:readline";
24
24
  import { scanProjectDirectory, matchProjectsToWorkloads, bestMatches } from "./onboard/project-matcher.js";
@@ -383,12 +383,15 @@ const DEFAULT_SERVER = "https://api.kubeagent.net";
383
383
  const DEFAULT_APP_URL = "https://app.kubeagent.net";
384
384
  program
385
385
  .command("login")
386
- .description("Log in to KubeAgent (opens browser)")
386
+ .description("Log in to KubeAgent (opens browser, or use --device for headless machines)")
387
387
  .option("-s, --server <url>", "API server URL", DEFAULT_SERVER)
388
388
  .option("--app-url <url>", "Dashboard URL (for browser login)", DEFAULT_APP_URL)
389
+ .option("--device", "Use device code flow (for remote/headless machines — no browser required)")
389
390
  .action(async (opts) => {
390
391
  try {
391
- const auth = await loginBrowser(opts.server, opts.appUrl);
392
+ const auth = opts.device
393
+ ? await loginDevice(opts.server, opts.appUrl)
394
+ : await loginBrowser(opts.server, opts.appUrl);
392
395
  console.log(chalk.green(`Logged in as ${auth.email ?? auth.name ?? "unknown"}`));
393
396
  const { prefix } = await createApiKey(auth, "cli-default");
394
397
  console.log(chalk.dim(`API key created: ${prefix}...`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubeagent",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "AI-powered Kubernetes management CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",