@aexol/spectral 0.2.14 → 0.2.15

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.
@@ -18,11 +18,11 @@ const DEFAULT_BACKEND_URL = "https://studio.aexol.ai";
18
18
  const CALLBACK_TIMEOUT_MS = 120_000; // 2 minutes
19
19
  /**
20
20
  * Start a temporary HTTP server to receive the OAuth callback.
21
- * Returns the port and a promise that resolves with the JWT token.
21
+ * Returns the server and a promise that resolves with the JWT token.
22
22
  */
23
23
  function listenForCallback(timeoutMs) {
24
- return new Promise((resolve, reject) => {
25
- const server = createServer();
24
+ const server = createServer();
25
+ const tokenPromise = new Promise((resolve, reject) => {
26
26
  const timeout = setTimeout(() => {
27
27
  server.close();
28
28
  reject(new Error(`Timed out after ${timeoutMs / 1000}s waiting for browser authorization.`));
@@ -34,7 +34,7 @@ function listenForCallback(timeoutMs) {
34
34
  clearTimeout(timeout);
35
35
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
36
36
  res.end("<html><body><h1>✓ Authorized</h1><p>You can close this tab and return to your terminal.</p></body></html>");
37
- resolve({ token, server });
37
+ resolve(token);
38
38
  }
39
39
  else {
40
40
  res.writeHead(400, { "Content-Type": "text/plain" });
@@ -46,10 +46,8 @@ function listenForCallback(timeoutMs) {
46
46
  server.close();
47
47
  reject(new Error(`Failed to start local server: ${err.message}`));
48
48
  });
49
- server.listen(0, "127.0.0.1", () => {
50
- // Port 0 lets the OS assign an ephemeral port — we read it from the server.
51
- });
52
49
  });
50
+ return { server, tokenPromise };
53
51
  }
54
52
  /** Derive the landing base URL from the backend URL. */
55
53
  function deriveLandingUrl(backendUrl) {
@@ -72,8 +70,14 @@ export async function runLoginOAuth() {
72
70
  let server;
73
71
  let token;
74
72
  try {
75
- const result = await listenForCallback(CALLBACK_TIMEOUT_MS);
76
- server = result.server;
73
+ const { server: srv, tokenPromise } = listenForCallback(CALLBACK_TIMEOUT_MS);
74
+ server = srv;
75
+ // Start listening on a random port, then wait for it to be ready
76
+ server.listen(0, "127.0.0.1");
77
+ await new Promise((resolve, reject) => {
78
+ server.once("listening", resolve);
79
+ server.once("error", reject);
80
+ });
77
81
  const addr = server.address();
78
82
  const port = addr.port;
79
83
  // Open browser to the CLI auth page
@@ -93,7 +97,7 @@ export async function runLoginOAuth() {
93
97
  });
94
98
  // Wait for the token
95
99
  process.stdout.write(pc.dim("Waiting for authorization...\n"));
96
- token = result.token;
100
+ token = await tokenPromise;
97
101
  }
98
102
  catch (err) {
99
103
  const msg = err instanceof Error ? err.message : String(err);
package/dist/config.js CHANGED
@@ -56,16 +56,21 @@ export async function readConfig() {
56
56
  }
57
57
  try {
58
58
  const parsed = JSON.parse(raw);
59
- if (typeof parsed.apiUrl === "string" &&
60
- typeof parsed.teamApiKey === "string" &&
61
- parsed.teamApiKey.length > 0) {
62
- return {
63
- apiUrl: parsed.apiUrl,
64
- teamApiKey: parsed.teamApiKey,
65
- userJwt: typeof parsed.userJwt === "string" ? parsed.userJwt : undefined,
66
- };
67
- }
68
- return null;
59
+ if (typeof parsed.apiUrl !== "string")
60
+ return null;
61
+ const teamApiKey = typeof parsed.teamApiKey === "string" && parsed.teamApiKey.length > 0
62
+ ? parsed.teamApiKey
63
+ : "";
64
+ const userJwt = typeof parsed.userJwt === "string" && parsed.userJwt.length > 0
65
+ ? parsed.userJwt
66
+ : undefined;
67
+ if (!teamApiKey && !userJwt)
68
+ return null;
69
+ return {
70
+ apiUrl: parsed.apiUrl,
71
+ teamApiKey,
72
+ userJwt,
73
+ };
69
74
  }
70
75
  catch {
71
76
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": "Always-on coding agent for Aexol — branded pi wrapper with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,