@indexnetwork/cli 0.11.0 → 0.12.0-rc.53.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/README.md +9 -5
- package/dist/index.js +303 -92
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install -g @indexnetwork/cli
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
Index helps you find the right people—and helps the right people find you—based on what you are actually trying to do, not just a profile headline. The value is grounded intros: suggestions come from communities you share (syndicates, founder groups, firm networks), not from spraying the open web.
|
|
13
|
+
Index helps you find the right people—and helps the right people find you—based on what you are actually trying to do, not just a profile headline. The value is grounded intros: suggestions come from communities you share (syndicates, founder groups, firm networks), not from spraying the open web.
|
|
14
14
|
|
|
15
15
|
The flow below is one complete story—shape a room, invite people, publish what you need, run discovery inside that context, watch broker negotiations, then accept a match.
|
|
16
16
|
|
|
@@ -41,19 +41,23 @@ Words you will see elsewhere in this doc: **network** = a community you are in;
|
|
|
41
41
|
|
|
42
42
|
### `index login`
|
|
43
43
|
|
|
44
|
-
Authenticate with Index Network. Opens a browser window that
|
|
44
|
+
Authenticate with Index Network. Opens a browser window that uses your existing session (or a fresh OAuth flow) to call the session-only, fixed-shape CLI credential endpoint, which mints a 90-day API key while keeping CLI requests on the non-web compatibility surface.
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
index login # Browser-based auth (default)
|
|
48
|
-
index login --token <jwt> #
|
|
48
|
+
index login --token <jwt> # Legacy manual session token (skip browser)
|
|
49
49
|
index login --api-url <url> # Custom server URL
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Credentials are stored in `~/.index/credentials.json`.
|
|
52
|
+
Credentials are stored in `~/.index/credentials.json`. Current browser login explicitly requests protocol v2, binds the loopback callback with a one-time state, and stores both the API-key secret and its exact revocation ID. It sends the key with `x-api-key`. Re-login stores the successful replacement first, then calls the constrained CLI revocation endpoint with the replacement as caller plus the captured previous raw secret and exact row ID; cleanup failures leave the new login usable but print a warning directing the user to remove the prior key in web settings.
|
|
53
|
+
|
|
54
|
+
**Rolling deploy order:** v2 clients require the v2 web bridge and intentionally reject callbacks from older web deployments that cannot return the bound state. On dev, this CLI is an RC: wait for both the API and web deployments to succeed before testing v2 login. Do not relax state validation to make a new CLI work against old web.
|
|
55
|
+
|
|
56
|
+
**Temporary compatibility:** the already-released v1 CLI can still recover with `index login`; the web bridge asks the custom session-only endpoint for a separately tagged 90-day v1 API key and returns it under the old `session_token` callback field, and the API accepts only that tag as a Bearer fallback. An upgraded v2 CLI transparently exchanges a still-valid stored browser JWT through the same endpoint for a tagged v2 key before making any compatibility request; an old binary must re-login because its ordinary session JWT must remain on the web/Signal surface. Remove the v1 bridge only after released clients have aged out.
|
|
53
57
|
|
|
54
58
|
### `index logout`
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
Revoke the exact stored CLI API key through `POST /api/auth/cli-credential/revoke`, proving both the active `x-api-key` caller and strict `{keyId,targetKey}` self target, then clear local credentials. If server revocation succeeds but local cleanup fails, logout exits nonzero and asks you to remove the local file manually. The released v1 CLI can only remove its credential locally because it did not store a server key ID; its temporary server key expires after 90 days. Legacy API-key credentials without a revocation ID are retained by the new CLI with a non-success warning that directs you to remove the old key in web settings first rather than implying another login revokes it.
|
|
57
61
|
|
|
58
62
|
```bash
|
|
59
63
|
index logout
|
package/dist/index.js
CHANGED
|
@@ -253,44 +253,6 @@ function parseIntentArgs(positionals, result) {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
// src/auth.store.ts
|
|
257
|
-
import { join } from "node:path";
|
|
258
|
-
import { homedir } from "node:os";
|
|
259
|
-
import { mkdir, readFile, writeFile, unlink } from "node:fs/promises";
|
|
260
|
-
var CREDENTIALS_FILE = "credentials.json";
|
|
261
|
-
|
|
262
|
-
class CredentialStore {
|
|
263
|
-
dir;
|
|
264
|
-
filePath;
|
|
265
|
-
constructor(dir) {
|
|
266
|
-
this.dir = dir ?? join(homedir(), ".index");
|
|
267
|
-
this.filePath = join(this.dir, CREDENTIALS_FILE);
|
|
268
|
-
}
|
|
269
|
-
async load() {
|
|
270
|
-
try {
|
|
271
|
-
const raw = await readFile(this.filePath, "utf-8");
|
|
272
|
-
const parsed = JSON.parse(raw);
|
|
273
|
-
if (parsed.token && parsed.apiUrl) {
|
|
274
|
-
return parsed;
|
|
275
|
-
}
|
|
276
|
-
return null;
|
|
277
|
-
} catch {
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async save(credentials) {
|
|
282
|
-
await mkdir(this.dir, { recursive: true });
|
|
283
|
-
await writeFile(this.filePath, JSON.stringify(credentials, null, 2), {
|
|
284
|
-
mode: 384
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
async clear() {
|
|
288
|
-
try {
|
|
289
|
-
await unlink(this.filePath);
|
|
290
|
-
} catch {}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
256
|
// src/api.client.ts
|
|
295
257
|
class ApiError extends Error {
|
|
296
258
|
status;
|
|
@@ -306,15 +268,35 @@ class ApiError extends Error {
|
|
|
306
268
|
class ApiClient {
|
|
307
269
|
baseUrl;
|
|
308
270
|
token;
|
|
309
|
-
|
|
271
|
+
authKind;
|
|
272
|
+
constructor(baseUrl, token, authKind = "session") {
|
|
310
273
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
311
274
|
this.token = token;
|
|
275
|
+
this.authKind = authKind;
|
|
276
|
+
}
|
|
277
|
+
authHeaders() {
|
|
278
|
+
return this.authKind === "api_key" ? { "x-api-key": this.token } : { Authorization: `Bearer ${this.token}` };
|
|
312
279
|
}
|
|
313
280
|
async listSessions() {
|
|
314
281
|
const res = await this.get("/api/chat/sessions");
|
|
315
282
|
const body = await res.json();
|
|
316
283
|
return body.sessions;
|
|
317
284
|
}
|
|
285
|
+
async mintCliApiKey(protocolVersion = 2) {
|
|
286
|
+
const res = await this.post("/api/auth/cli-credential", { protocolVersion });
|
|
287
|
+
const body = await res.json();
|
|
288
|
+
if (typeof body.key !== "string" || !body.key || typeof body.id !== "string" || !body.id || typeof body.expiresAt !== "string" || !body.expiresAt) {
|
|
289
|
+
throw new Error("CLI credential response did not include a key, key ID, and expiry");
|
|
290
|
+
}
|
|
291
|
+
return { key: body.key, keyId: body.id };
|
|
292
|
+
}
|
|
293
|
+
async revokeApiKey(keyId, targetKey = this.token) {
|
|
294
|
+
const res = await this.post("/api/auth/cli-credential/revoke", { keyId, targetKey });
|
|
295
|
+
const body = await res.json();
|
|
296
|
+
if (body.success !== true) {
|
|
297
|
+
throw new Error("API-key revocation was not confirmed");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
318
300
|
async getMe() {
|
|
319
301
|
const res = await this.get("/api/auth/me");
|
|
320
302
|
const body = await res.json();
|
|
@@ -353,7 +335,7 @@ class ApiClient {
|
|
|
353
335
|
method: "POST",
|
|
354
336
|
headers: {
|
|
355
337
|
"Content-Type": "application/json",
|
|
356
|
-
|
|
338
|
+
...this.authHeaders()
|
|
357
339
|
},
|
|
358
340
|
body: JSON.stringify({
|
|
359
341
|
message: params.message,
|
|
@@ -475,7 +457,7 @@ class ApiClient {
|
|
|
475
457
|
async streamConversationEvents() {
|
|
476
458
|
const res = await fetch(`${this.baseUrl}/api/conversations/stream`, {
|
|
477
459
|
headers: {
|
|
478
|
-
|
|
460
|
+
...this.authHeaders(),
|
|
479
461
|
Accept: "text/event-stream"
|
|
480
462
|
}
|
|
481
463
|
});
|
|
@@ -505,9 +487,7 @@ class ApiClient {
|
|
|
505
487
|
}
|
|
506
488
|
async get(path) {
|
|
507
489
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
508
|
-
headers:
|
|
509
|
-
Authorization: `Bearer ${this.token}`
|
|
510
|
-
}
|
|
490
|
+
headers: this.authHeaders()
|
|
511
491
|
});
|
|
512
492
|
if (!res.ok) {
|
|
513
493
|
await this.handleError(res);
|
|
@@ -519,7 +499,7 @@ class ApiClient {
|
|
|
519
499
|
method: "POST",
|
|
520
500
|
headers: {
|
|
521
501
|
"Content-Type": "application/json",
|
|
522
|
-
|
|
502
|
+
...this.authHeaders()
|
|
523
503
|
},
|
|
524
504
|
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
525
505
|
});
|
|
@@ -533,7 +513,7 @@ class ApiClient {
|
|
|
533
513
|
method: "PATCH",
|
|
534
514
|
headers: {
|
|
535
515
|
"Content-Type": "application/json",
|
|
536
|
-
|
|
516
|
+
...this.authHeaders()
|
|
537
517
|
},
|
|
538
518
|
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
539
519
|
});
|
|
@@ -544,9 +524,7 @@ class ApiClient {
|
|
|
544
524
|
async del(path) {
|
|
545
525
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
546
526
|
method: "DELETE",
|
|
547
|
-
headers:
|
|
548
|
-
Authorization: `Bearer ${this.token}`
|
|
549
|
-
}
|
|
527
|
+
headers: this.authHeaders()
|
|
550
528
|
});
|
|
551
529
|
if (!res.ok) {
|
|
552
530
|
await this.handleError(res);
|
|
@@ -571,12 +549,96 @@ class ApiClient {
|
|
|
571
549
|
}
|
|
572
550
|
}
|
|
573
551
|
|
|
552
|
+
// src/auth.lifecycle.ts
|
|
553
|
+
async function storeReplacementCredentials(store, previous, replacement, options = {}) {
|
|
554
|
+
await store.save(replacement);
|
|
555
|
+
if (previous?.authKind !== "api_key")
|
|
556
|
+
return {};
|
|
557
|
+
if (!previous.keyId) {
|
|
558
|
+
return {
|
|
559
|
+
warning: "Login succeeded, but the previous CLI API key has no exact revocation ID and may remain active. It should be removed in Index web settings."
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (replacement.authKind === "api_key" && replacement.keyId === previous.keyId) {
|
|
563
|
+
return {};
|
|
564
|
+
}
|
|
565
|
+
const authKind = replacement.authKind ?? "session";
|
|
566
|
+
const createClient = options.clientFactory ?? ((apiUrl, token, kind) => new ApiClient(apiUrl, token, kind));
|
|
567
|
+
try {
|
|
568
|
+
await createClient(replacement.apiUrl, replacement.token, authKind).revokeApiKey(previous.keyId, previous.token);
|
|
569
|
+
return {};
|
|
570
|
+
} catch (error) {
|
|
571
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
572
|
+
return {
|
|
573
|
+
warning: `Login succeeded, but the previous CLI API key remains active because it could not be revoked.${detail} It should be removed in Index web settings.`
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/auth.store.ts
|
|
579
|
+
import { join } from "node:path";
|
|
580
|
+
import { homedir } from "node:os";
|
|
581
|
+
import { mkdir, readFile, writeFile, unlink } from "node:fs/promises";
|
|
582
|
+
var CREDENTIALS_FILE = "credentials.json";
|
|
583
|
+
|
|
584
|
+
class CredentialStore {
|
|
585
|
+
dir;
|
|
586
|
+
filePath;
|
|
587
|
+
constructor(dir) {
|
|
588
|
+
this.dir = dir ?? join(homedir(), ".index");
|
|
589
|
+
this.filePath = join(this.dir, CREDENTIALS_FILE);
|
|
590
|
+
}
|
|
591
|
+
async load() {
|
|
592
|
+
try {
|
|
593
|
+
const raw = await readFile(this.filePath, "utf-8");
|
|
594
|
+
const parsed = JSON.parse(raw);
|
|
595
|
+
if (parsed.token && parsed.apiUrl) {
|
|
596
|
+
return parsed;
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
} catch {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async save(credentials) {
|
|
604
|
+
await mkdir(this.dir, { recursive: true });
|
|
605
|
+
await writeFile(this.filePath, JSON.stringify(credentials, null, 2), {
|
|
606
|
+
mode: 384
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
async clear() {
|
|
610
|
+
try {
|
|
611
|
+
await unlink(this.filePath);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (error.code === "ENOENT")
|
|
614
|
+
return;
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
574
620
|
// src/login.command.ts
|
|
621
|
+
import { randomBytes } from "node:crypto";
|
|
575
622
|
import { createServer } from "node:http";
|
|
576
623
|
function closeServer(server) {
|
|
577
624
|
return new Promise((resolve) => {
|
|
578
|
-
|
|
579
|
-
|
|
625
|
+
let resolved = false;
|
|
626
|
+
const finish = () => {
|
|
627
|
+
if (resolved)
|
|
628
|
+
return;
|
|
629
|
+
resolved = true;
|
|
630
|
+
resolve();
|
|
631
|
+
};
|
|
632
|
+
try {
|
|
633
|
+
server.close(finish);
|
|
634
|
+
} catch {
|
|
635
|
+
finish();
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
server.closeAllConnections();
|
|
639
|
+
} catch {
|
|
640
|
+
finish();
|
|
641
|
+
}
|
|
580
642
|
});
|
|
581
643
|
}
|
|
582
644
|
async function handleLogin(apiUrl, appUrl, store, options = {}) {
|
|
@@ -584,60 +646,144 @@ async function handleLogin(apiUrl, appUrl, store, options = {}) {
|
|
|
584
646
|
const baseUrl = apiUrl.replace(/\/$/, "");
|
|
585
647
|
const baseAppUrl = appUrl.replace(/\/$/, "");
|
|
586
648
|
const callbackHost = options.callbackHost ?? "127.0.0.1";
|
|
649
|
+
const previousCredentials = await store.load();
|
|
587
650
|
let resolveCallback;
|
|
588
651
|
const callbackPromise = new Promise((resolve) => {
|
|
589
652
|
resolveCallback = resolve;
|
|
590
653
|
});
|
|
654
|
+
let expectedState = null;
|
|
655
|
+
let stateConsumed = false;
|
|
591
656
|
const server = (options.serverFactory ?? createServer)(async (req, res) => {
|
|
592
|
-
const url = new URL(req.url ?? "/",
|
|
593
|
-
if (url.pathname
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
657
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
658
|
+
if (req.method !== "GET" || url.pathname !== "/callback") {
|
|
659
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
660
|
+
res.end("Not Found");
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const callbackState = url.searchParams.get("state");
|
|
664
|
+
if (!expectedState || callbackState !== expectedState) {
|
|
665
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
666
|
+
res.end(callbackHtml("Authorization failed", "Invalid login state. Return to the terminal and try again."));
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (stateConsumed) {
|
|
670
|
+
res.writeHead(409, { "Content-Type": "text/html" });
|
|
671
|
+
res.end(callbackHtml("Authorization already completed", "This login callback has already been used."));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
stateConsumed = true;
|
|
675
|
+
const apiKey = url.searchParams.get("api_key");
|
|
676
|
+
const keyId = url.searchParams.get("key_id");
|
|
677
|
+
const sessionToken = url.searchParams.get("session_token");
|
|
678
|
+
if (apiKey && keyId) {
|
|
679
|
+
try {
|
|
680
|
+
const cleanup = await storeReplacementCredentials(store, previousCredentials, {
|
|
681
|
+
token: apiKey,
|
|
682
|
+
apiUrl: baseUrl,
|
|
683
|
+
authKind: "api_key",
|
|
684
|
+
keyId
|
|
685
|
+
}, { clientFactory: options.credentialClientFactory });
|
|
686
|
+
resolveCallback({ success: true, ...cleanup });
|
|
687
|
+
} catch {
|
|
688
|
+
resolveCallback({ success: false, error: "Failed to save CLI credentials." });
|
|
689
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
690
|
+
res.end(callbackHtml("Authorization failed", "CLI credentials could not be saved. Return to the terminal and try again."));
|
|
600
691
|
return;
|
|
601
692
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
693
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
694
|
+
res.end(callbackHtml("CLI authorized", "You can close this window and return to the terminal."));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (!apiKey && sessionToken) {
|
|
698
|
+
try {
|
|
699
|
+
const cleanup = await storeReplacementCredentials(store, previousCredentials, {
|
|
700
|
+
token: sessionToken,
|
|
701
|
+
apiUrl: baseUrl,
|
|
702
|
+
authKind: "session"
|
|
703
|
+
}, { clientFactory: options.credentialClientFactory });
|
|
704
|
+
resolveCallback({ success: true, ...cleanup });
|
|
705
|
+
} catch {
|
|
706
|
+
resolveCallback({ success: false, error: "Failed to save CLI credentials." });
|
|
707
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
708
|
+
res.end(callbackHtml("Authorization failed", "CLI credentials could not be saved. Return to the terminal and try again."));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
712
|
+
res.end(callbackHtml("CLI authorized", "You can close this window and return to the terminal."));
|
|
608
713
|
return;
|
|
609
714
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
715
|
+
resolveCallback({
|
|
716
|
+
success: false,
|
|
717
|
+
error: apiKey ? "CLI API-key callback did not include its key ID." : "No CLI credential received in callback."
|
|
718
|
+
});
|
|
719
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
720
|
+
res.end(callbackHtml("Authorization failed", "Incomplete CLI credentials received. Please try again."));
|
|
615
721
|
});
|
|
722
|
+
let listenSettled = false;
|
|
723
|
+
let rejectListen = null;
|
|
724
|
+
const handleServerError = (error) => {
|
|
725
|
+
if (!listenSettled) {
|
|
726
|
+
listenSettled = true;
|
|
727
|
+
rejectListen?.(error);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
resolveCallback({ success: false, error: "Local login callback server failed." });
|
|
731
|
+
};
|
|
732
|
+
server.on("error", handleServerError);
|
|
733
|
+
try {
|
|
734
|
+
await new Promise((resolve, reject) => {
|
|
735
|
+
rejectListen = reject;
|
|
736
|
+
server.listen(0, callbackHost, () => {
|
|
737
|
+
if (listenSettled)
|
|
738
|
+
return;
|
|
739
|
+
listenSettled = true;
|
|
740
|
+
resolve();
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
} catch (error) {
|
|
744
|
+
try {
|
|
745
|
+
await closeServer(server);
|
|
746
|
+
} finally {
|
|
747
|
+
server.off("error", handleServerError);
|
|
748
|
+
}
|
|
749
|
+
throw error;
|
|
750
|
+
}
|
|
616
751
|
const addr = server.address();
|
|
617
752
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
618
753
|
const callbackUrl = `http://${callbackHost}:${port}/callback`;
|
|
619
|
-
|
|
754
|
+
expectedState = randomBytes(32).toString("base64url");
|
|
755
|
+
const authUrlObject = new URL(`${baseAppUrl}/cli-auth`);
|
|
756
|
+
authUrlObject.searchParams.set("callback", callbackUrl);
|
|
757
|
+
authUrlObject.searchParams.set("version", "2");
|
|
758
|
+
authUrlObject.searchParams.set("state", expectedState);
|
|
759
|
+
const authUrl = authUrlObject.toString();
|
|
620
760
|
const timeout = setTimeout(() => {
|
|
621
761
|
resolveCallback({
|
|
622
762
|
success: false,
|
|
623
763
|
error: "Login timed out. No callback received."
|
|
624
764
|
});
|
|
625
|
-
closeServer(server);
|
|
626
765
|
}, timeoutMs);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
success: false,
|
|
633
|
-
error: "Login cancelled."
|
|
634
|
-
});
|
|
766
|
+
const abortHandler = () => {
|
|
767
|
+
clearTimeout(timeout);
|
|
768
|
+
resolveCallback({
|
|
769
|
+
success: false,
|
|
770
|
+
error: "Login cancelled."
|
|
635
771
|
});
|
|
772
|
+
};
|
|
773
|
+
if (options.signal?.aborted) {
|
|
774
|
+
abortHandler();
|
|
775
|
+
} else {
|
|
776
|
+
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
636
777
|
}
|
|
637
778
|
const wrappedPromise = callbackPromise.then(async (result) => {
|
|
638
779
|
clearTimeout(timeout);
|
|
639
|
-
|
|
640
|
-
|
|
780
|
+
options.signal?.removeEventListener("abort", abortHandler);
|
|
781
|
+
try {
|
|
782
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
783
|
+
await closeServer(server);
|
|
784
|
+
} finally {
|
|
785
|
+
server.off("error", handleServerError);
|
|
786
|
+
}
|
|
641
787
|
return result;
|
|
642
788
|
});
|
|
643
789
|
return {
|
|
@@ -693,6 +839,43 @@ function callbackHtml(title, message) {
|
|
|
693
839
|
</html>`;
|
|
694
840
|
}
|
|
695
841
|
|
|
842
|
+
// src/logout.command.ts
|
|
843
|
+
async function handleLogout(store, options = {}) {
|
|
844
|
+
const credentials = await store.load();
|
|
845
|
+
if (!credentials) {
|
|
846
|
+
return { success: true, message: "Already logged out." };
|
|
847
|
+
}
|
|
848
|
+
if (credentials.authKind !== "api_key") {
|
|
849
|
+
await store.clear();
|
|
850
|
+
return { success: true, message: "Logged out. Session cleared." };
|
|
851
|
+
}
|
|
852
|
+
if (!credentials.keyId) {
|
|
853
|
+
return {
|
|
854
|
+
success: false,
|
|
855
|
+
warning: "Stored API-key credentials do not include a revocation ID and were retained. Remove the old key in Index web settings first, then delete the local credentials and sign in again."
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const createClient = options.clientFactory ?? ((apiUrl, token) => new ApiClient(apiUrl, token, "api_key"));
|
|
859
|
+
try {
|
|
860
|
+
await createClient(credentials.apiUrl, credentials.token).revokeApiKey(credentials.keyId, credentials.token);
|
|
861
|
+
} catch (error) {
|
|
862
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
863
|
+
return {
|
|
864
|
+
success: false,
|
|
865
|
+
warning: `Logout could not revoke the stored CLI API key, so credentials were retained.${detail}`
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
await store.clear();
|
|
870
|
+
} catch {
|
|
871
|
+
return {
|
|
872
|
+
success: false,
|
|
873
|
+
warning: "The server API key was revoked, but local credential cleanup failed. Remove the local credentials file manually before signing in again."
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
return { success: true, message: "Logged out. CLI API key revoked." };
|
|
877
|
+
}
|
|
878
|
+
|
|
696
879
|
// src/output/index.ts
|
|
697
880
|
var exports_output = {};
|
|
698
881
|
__export(exports_output, {
|
|
@@ -3043,7 +3226,7 @@ async function onboardingComplete(client, json) {
|
|
|
3043
3226
|
// src/main.ts
|
|
3044
3227
|
var DEFAULT_API_URL = "https://protocol.index.network";
|
|
3045
3228
|
var DEFAULT_APP_URL = "https://index.network";
|
|
3046
|
-
var VERSION = "0.
|
|
3229
|
+
var VERSION = "0.12.0";
|
|
3047
3230
|
var BOX = { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│" };
|
|
3048
3231
|
function visualLen(s) {
|
|
3049
3232
|
return stripAnsi(s).length;
|
|
@@ -3209,20 +3392,42 @@ async function requireAuth(apiUrlOverride) {
|
|
|
3209
3392
|
process.exit(1);
|
|
3210
3393
|
}
|
|
3211
3394
|
const apiUrl = apiUrlOverride ?? creds.apiUrl;
|
|
3212
|
-
|
|
3395
|
+
if (creds.authKind !== "api_key") {
|
|
3396
|
+
try {
|
|
3397
|
+
const sessionClient = new ApiClient(apiUrl, creds.token, "session");
|
|
3398
|
+
const { key, keyId } = await sessionClient.mintCliApiKey();
|
|
3399
|
+
const migrated = { token: key, apiUrl, authKind: "api_key", keyId };
|
|
3400
|
+
await store.save(migrated);
|
|
3401
|
+
return new ApiClient(apiUrl, migrated.token, migrated.authKind);
|
|
3402
|
+
} catch {
|
|
3403
|
+
error("Stored CLI credentials need to be refreshed. Run `index login` again.", 1);
|
|
3404
|
+
process.exit(1);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
return new ApiClient(apiUrl, creds.token, creds.authKind);
|
|
3213
3408
|
}
|
|
3214
3409
|
async function runLogin(apiUrlOverride, appUrlOverride, manualToken) {
|
|
3215
3410
|
const store = new CredentialStore;
|
|
3216
3411
|
const apiUrl = apiUrlOverride ?? DEFAULT_API_URL;
|
|
3217
3412
|
const appUrl = appUrlOverride ?? DEFAULT_APP_URL;
|
|
3218
3413
|
if (manualToken) {
|
|
3219
|
-
await store.
|
|
3414
|
+
const previousCredentials = await store.load();
|
|
3220
3415
|
try {
|
|
3221
|
-
const
|
|
3222
|
-
const user = await
|
|
3416
|
+
const sessionClient = new ApiClient(apiUrl, manualToken, "session");
|
|
3417
|
+
const user = await sessionClient.getMe();
|
|
3418
|
+
const { key, keyId } = await sessionClient.mintCliApiKey();
|
|
3419
|
+
const cleanup = await storeReplacementCredentials(store, previousCredentials, {
|
|
3420
|
+
token: key,
|
|
3421
|
+
apiUrl,
|
|
3422
|
+
authKind: "api_key",
|
|
3423
|
+
keyId
|
|
3424
|
+
});
|
|
3223
3425
|
success(`Logged in as ${user.name} (${user.email})`);
|
|
3426
|
+
if (cleanup.warning)
|
|
3427
|
+
warn(cleanup.warning);
|
|
3224
3428
|
} catch {
|
|
3225
|
-
|
|
3429
|
+
error("Could not exchange that session token. Run `index login` instead.", 1);
|
|
3430
|
+
process.exitCode = 1;
|
|
3226
3431
|
}
|
|
3227
3432
|
return;
|
|
3228
3433
|
}
|
|
@@ -3256,21 +3461,27 @@ async function runLogin(apiUrlOverride, appUrlOverride, manualToken) {
|
|
|
3256
3461
|
try {
|
|
3257
3462
|
const creds = await store.load();
|
|
3258
3463
|
if (creds) {
|
|
3259
|
-
const client = new ApiClient(creds.apiUrl, creds.token);
|
|
3464
|
+
const client = new ApiClient(creds.apiUrl, creds.token, creds.authKind);
|
|
3260
3465
|
const user = await client.getMe();
|
|
3261
3466
|
success(`Logged in as ${user.name} (${user.email})`);
|
|
3262
3467
|
}
|
|
3263
3468
|
} catch {
|
|
3264
3469
|
success("Login successful! Token stored.");
|
|
3265
3470
|
}
|
|
3471
|
+
if (result.warning)
|
|
3472
|
+
warn(result.warning);
|
|
3266
3473
|
} else {
|
|
3267
3474
|
error(result.error ?? "Login failed.", 1);
|
|
3268
3475
|
}
|
|
3269
3476
|
}
|
|
3270
3477
|
async function runLogout() {
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3478
|
+
const result = await handleLogout(new CredentialStore);
|
|
3479
|
+
if (result.success) {
|
|
3480
|
+
success(result.message);
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
warn(result.warning);
|
|
3484
|
+
process.exitCode = 1;
|
|
3274
3485
|
}
|
|
3275
3486
|
async function main() {
|
|
3276
3487
|
const args = parseArgs(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indexnetwork/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-rc.53.1",
|
|
4
4
|
"description": "Command-line interface for Index Network",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"publish:all": "bun scripts/publish.ts"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@indexnetwork/cli-linux-x64": "0.
|
|
24
|
-
"@indexnetwork/cli-linux-arm64": "0.
|
|
25
|
-
"@indexnetwork/cli-darwin-x64": "0.
|
|
26
|
-
"@indexnetwork/cli-darwin-arm64": "0.
|
|
23
|
+
"@indexnetwork/cli-linux-x64": "0.12.0-rc.53.1",
|
|
24
|
+
"@indexnetwork/cli-linux-arm64": "0.12.0-rc.53.1",
|
|
25
|
+
"@indexnetwork/cli-darwin-x64": "0.12.0-rc.53.1",
|
|
26
|
+
"@indexnetwork/cli-darwin-arm64": "0.12.0-rc.53.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/bun": "latest"
|