airgen-cli 0.1.0 → 0.1.1

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/client.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * AIRGen HTTP API client with JWT authentication.
3
3
  *
4
4
  * Handles login, token caching, and automatic refresh.
5
+ * Persists session tokens to ~/.airgen-session for reuse across CLI invocations.
5
6
  */
6
7
  export interface ClientConfig {
7
8
  apiUrl: string;
@@ -13,6 +14,9 @@ export declare class AirgenClient {
13
14
  private auth;
14
15
  private loginPromise;
15
16
  constructor(config: ClientConfig);
17
+ private loadSession;
18
+ private saveSession;
19
+ private clearSession;
16
20
  /** The base API URL this client is configured for. */
17
21
  get apiUrl(): string;
18
22
  get<T = unknown>(path: string, query?: Record<string, string | number | undefined>): Promise<T>;
package/dist/client.js CHANGED
@@ -2,7 +2,12 @@
2
2
  * AIRGen HTTP API client with JWT authentication.
3
3
  *
4
4
  * Handles login, token caching, and automatic refresh.
5
+ * Persists session tokens to ~/.airgen-session for reuse across CLI invocations.
5
6
  */
7
+ import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ const SESSION_FILE = join(homedir(), ".airgen-session");
6
11
  export class AirgenClient {
7
12
  config;
8
13
  auth = {
@@ -13,6 +18,44 @@ export class AirgenClient {
13
18
  loginPromise = null;
14
19
  constructor(config) {
15
20
  this.config = config;
21
+ this.loadSession();
22
+ }
23
+ loadSession() {
24
+ try {
25
+ const raw = readFileSync(SESSION_FILE, "utf-8");
26
+ const session = JSON.parse(raw);
27
+ // Only reuse session if it matches current config
28
+ if (session.apiUrl === this.config.apiUrl && session.email === this.config.email) {
29
+ this.auth = {
30
+ accessToken: session.accessToken,
31
+ refreshToken: session.refreshToken,
32
+ tokenExpiresAt: session.tokenExpiresAt,
33
+ };
34
+ }
35
+ }
36
+ catch {
37
+ // No session file or invalid — start fresh
38
+ }
39
+ }
40
+ saveSession() {
41
+ try {
42
+ writeFileSync(SESSION_FILE, JSON.stringify({
43
+ apiUrl: this.config.apiUrl,
44
+ email: this.config.email,
45
+ accessToken: this.auth.accessToken,
46
+ refreshToken: this.auth.refreshToken,
47
+ tokenExpiresAt: this.auth.tokenExpiresAt,
48
+ }), { mode: 0o600 });
49
+ }
50
+ catch {
51
+ // Non-fatal — session just won't persist
52
+ }
53
+ }
54
+ clearSession() {
55
+ try {
56
+ unlinkSync(SESSION_FILE);
57
+ }
58
+ catch { /* ignore */ }
16
59
  }
17
60
  /** The base API URL this client is configured for. */
18
61
  get apiUrl() {
@@ -53,7 +96,7 @@ export class AirgenClient {
53
96
  headers["Authorization"] = `Bearer ${this.auth.accessToken}`;
54
97
  }
55
98
  if (this.auth.refreshToken) {
56
- headers["Cookie"] = `airgen_refresh=${this.auth.refreshToken}`;
99
+ headers["Cookie"] = `refreshToken=${this.auth.refreshToken}`;
57
100
  }
58
101
  const url = `${this.config.apiUrl}${path}`;
59
102
  const res = await globalThis.fetch(url, { method: "POST", headers, body });
@@ -105,7 +148,7 @@ export class AirgenClient {
105
148
  headers["Content-Type"] = "application/json";
106
149
  }
107
150
  if (this.auth.refreshToken) {
108
- headers["Cookie"] = `airgen_refresh=${this.auth.refreshToken}`;
151
+ headers["Cookie"] = `refreshToken=${this.auth.refreshToken}`;
109
152
  }
110
153
  const url = `${this.config.apiUrl}${path}`;
111
154
  try {
@@ -194,11 +237,12 @@ export class AirgenClient {
194
237
  this.auth.accessToken = data.token;
195
238
  this.auth.tokenExpiresAt = decodeTokenExpiry(data.token);
196
239
  this.auth.refreshToken = extractRefreshToken(res);
240
+ this.saveSession();
197
241
  }
198
242
  async refresh() {
199
243
  const headers = {};
200
244
  if (this.auth.refreshToken) {
201
- headers["Cookie"] = `airgen_refresh=${this.auth.refreshToken}`;
245
+ headers["Cookie"] = `refreshToken=${this.auth.refreshToken}`;
202
246
  }
203
247
  const res = await globalThis.fetch(`${this.config.apiUrl}/auth/refresh`, {
204
248
  method: "POST",
@@ -207,6 +251,7 @@ export class AirgenClient {
207
251
  if (!res.ok) {
208
252
  // Refresh failed — clear state and re-login
209
253
  this.auth = { accessToken: null, refreshToken: null, tokenExpiresAt: 0 };
254
+ this.clearSession();
210
255
  await this.login();
211
256
  return;
212
257
  }
@@ -217,6 +262,7 @@ export class AirgenClient {
217
262
  if (newRefresh) {
218
263
  this.auth.refreshToken = newRefresh;
219
264
  }
265
+ this.saveSession();
220
266
  }
221
267
  }
222
268
  // ── Error class ───────────────────────────────────────────────
@@ -248,8 +294,8 @@ function extractRefreshToken(res) {
248
294
  const setCookie = res.headers.get("set-cookie");
249
295
  if (!setCookie)
250
296
  return null;
251
- // Parse airgen_refresh cookie value
252
- const match = setCookie.match(/airgen_refresh=([^;]+)/);
297
+ // Parse refreshToken cookie value
298
+ const match = setCookie.match(/refreshToken=([^;]+)/);
253
299
  return match ? match[1] : null;
254
300
  }
255
301
  function buildQueryString(params) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airgen-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AIRGen CLI — requirements engineering from the command line",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",