@aexol/spectral 0.2.16 → 0.2.18

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/cli.js CHANGED
@@ -93,8 +93,7 @@ function printHeader() {
93
93
  TAGLINE,
94
94
  "",
95
95
  "Subcommands:",
96
- " spectral login Authenticate with a team API key",
97
- " spectral login --oauth Authorize via browser (Aexol Studio OAuth)",
96
+ " spectral login Interactive authentication (team API key or OAuth)",
98
97
  " spectral logout Remove stored Aexol credentials",
99
98
  " spectral serve Connect this machine to the Aexol relay backend",
100
99
  " spectral bind Link this directory to an Aexol Studio project",
@@ -163,11 +162,6 @@ async function main() {
163
162
  // Subcommands. Dynamic import keeps the cold-start path light when users
164
163
  // are just running pi.
165
164
  if (first === "login") {
166
- if (args[1] === "--oauth") {
167
- const { runLoginOAuth } = await import("./commands/login-oauth.js");
168
- await runLoginOAuth();
169
- process.exit(0);
170
- }
171
165
  const { runLogin } = await import("./commands/login.js");
172
166
  await runLogin();
173
167
  process.exit(0);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `spectral login --oauth` browser-based OAuth login for CLI machine ownership.
2
+ * OAuth login for CLI machine ownership (called from interactive `spectral login`).
3
3
  *
4
4
  * Flow:
5
5
  * 1. Start a temporary HTTP server on a random localhost port.
@@ -2,9 +2,11 @@
2
2
  * `spectral login` — interactive authentication against the Aexol MCP backend.
3
3
  *
4
4
  * Flow:
5
- * 1. Prompt for API URL (default = SPECTRAL_MCP_URL env, else hard default).
6
- * 2. Prompt for team API key (masked input). Verify the sk-aexol-team- prefix
7
- * locally so we don't send obviously-wrong credentials.
5
+ * 1. Choose auth method: Team API key or OAuth (browser).
6
+ * 2a. API key: prompt for team API key (masked input). Verify the
7
+ * sk-aexol-team- prefix locally so we don't send obviously-wrong
8
+ * credentials. The API URL is taken from env or the hard default.
9
+ * 2b. OAuth: open browser for Aexol Studio authorization.
8
10
  * 3. Verify reachability + auth by calling tools/list against the URL.
9
11
  * 4. On success: persist to ~/.spectral/config.json (mode 0600).
10
12
  *
@@ -14,9 +16,9 @@
14
16
  * wrapper delegates to it after collecting prompts. Tests target
15
17
  * `performLogin` directly so we don't have to drive `@inquirer/prompts`.
16
18
  */
17
- import { input, password } from "@inquirer/prompts";
19
+ import { password, select } from "@inquirer/prompts";
18
20
  import pc from "picocolors";
19
- import { DEFAULT_API_URL, TEAM_API_KEY_PREFIX, getConfigFile, validateTeamApiKey, writeConfig, } from "../config.js";
21
+ import { TEAM_API_KEY_PREFIX, getApiUrl, getConfigFile, validateTeamApiKey, writeConfig, } from "../config.js";
20
22
  import { AexolMcpClient, AexolMcpError } from "../mcp-client.js";
21
23
  /**
22
24
  * Pure login logic. Throws `Error` with one of these stable message prefixes:
@@ -62,15 +64,34 @@ export async function performLogin(opts) {
62
64
  export async function runLogin() {
63
65
  process.stdout.write(pc.bold("Spectral login\n"));
64
66
  process.stdout.write(pc.dim(`Authenticate against the Aexol MCP backend. Credentials are stored at ${getConfigFile()} (chmod 600).\n\n`));
65
- const defaultUrl = process.env.SPECTRAL_MCP_URL ?? DEFAULT_API_URL;
66
- let apiUrl;
67
+ // Step 1: choose auth method
68
+ let authMethod;
69
+ try {
70
+ authMethod = await select({
71
+ message: "How would you like to authenticate?",
72
+ choices: [
73
+ { name: "Team API key", value: "apikey" },
74
+ { name: "OAuth (browser)", value: "oauth" },
75
+ ],
76
+ });
77
+ }
78
+ catch (err) {
79
+ const name = err?.name;
80
+ if (name === "ExitPromptError") {
81
+ process.stderr.write(pc.yellow("\nLogin cancelled.\n"));
82
+ process.exit(130);
83
+ }
84
+ throw err;
85
+ }
86
+ if (authMethod === "oauth") {
87
+ const { runLoginOAuth } = await import("./login-oauth.js");
88
+ await runLoginOAuth();
89
+ return;
90
+ }
91
+ // Team API key flow: no URL prompt — use env or default.
92
+ const apiUrl = getApiUrl();
67
93
  let teamApiKey;
68
94
  try {
69
- apiUrl = (await input({
70
- message: "Aexol MCP URL",
71
- default: defaultUrl,
72
- validate: (v) => (v.trim().length > 0 ? true : "URL is required"),
73
- })).trim();
74
95
  teamApiKey = (await password({
75
96
  message: `Team API key (starts with "${TEAM_API_KEY_PREFIX}")`,
76
97
  mask: "*",
@@ -86,7 +107,6 @@ export async function runLogin() {
86
107
  })).trim();
87
108
  }
88
109
  catch (err) {
89
- // @inquirer/prompts throws ExitPromptError on Ctrl+C; treat as cancellation.
90
110
  const name = err?.name;
91
111
  if (name === "ExitPromptError") {
92
112
  process.stderr.write(pc.yellow("\nLogin cancelled.\n"));
@@ -89,7 +89,12 @@ export default async function aexolMcpExtension(pi) {
89
89
  return;
90
90
  }
91
91
  const apiUrl = getApiUrl(cfg.apiUrl);
92
- const client = new AexolMcpClient(apiUrl, cfg.teamApiKey);
92
+ const token = cfg.teamApiKey || cfg.userJwt || "";
93
+ if (!token) {
94
+ process.stderr.write(`[aexol-mcp] No auth token in ${getConfigFile()}; Aexol tools disabled. Run \`spectral login\`.\n`);
95
+ return;
96
+ }
97
+ const client = new AexolMcpClient(apiUrl, token);
93
98
  // ---- Load local Studio binding -------------------------------------------
94
99
  try {
95
100
  currentBinding = await readStudioBinding();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
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,