@aexol/spectral 0.2.16 → 0.2.19
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
|
|
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);
|
package/dist/commands/login.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* `spectral login` — interactive authentication against the Aexol MCP backend.
|
|
3
3
|
*
|
|
4
4
|
* Flow:
|
|
5
|
-
* 1.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
19
|
+
import { password, select } from "@inquirer/prompts";
|
|
18
20
|
import pc from "picocolors";
|
|
19
|
-
import {
|
|
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
|
-
|
|
66
|
-
let
|
|
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
|
|
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/dist/server/pi-bridge.js
CHANGED
|
@@ -656,6 +656,10 @@ export class PiBridge {
|
|
|
656
656
|
if (this.disposed)
|
|
657
657
|
return;
|
|
658
658
|
this.disposed = true;
|
|
659
|
+
// Persist the current in-flight message (if any) before tearing down.
|
|
660
|
+
// Without this, cancelling mid-turn loses the message along with its
|
|
661
|
+
// token_usage event, which breaks context-window tracking on reconnect.
|
|
662
|
+
this.finalizePendingMessage();
|
|
659
663
|
try {
|
|
660
664
|
this.unsubscribe?.();
|
|
661
665
|
}
|