@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.
Files changed (3) hide show
  1. package/README.md +9 -5
  2. package/dist/index.js +303 -92
  3. 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 exchanges your existing session for a CLI token, or starts a fresh OAuth flow.
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> # Manual token (skip browser)
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
- Clear stored credentials.
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
- constructor(baseUrl, token) {
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
- Authorization: `Bearer ${this.token}`
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
- Authorization: `Bearer ${this.token}`,
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
- Authorization: `Bearer ${this.token}`
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
- Authorization: `Bearer ${this.token}`
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
- server.close(() => resolve());
579
- server.closeAllConnections();
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 ?? "/", `http://localhost`);
593
- if (url.pathname === "/callback") {
594
- const sessionToken = url.searchParams.get("session_token");
595
- if (sessionToken) {
596
- await store.save({ token: sessionToken, apiUrl: baseUrl });
597
- resolveCallback({ success: true });
598
- res.writeHead(200, { "Content-Type": "text/html" });
599
- res.end(callbackHtml("CLI authorized", "You can close this window and return to the terminal."));
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
- resolveCallback({
603
- success: false,
604
- error: "No session token received in callback."
605
- });
606
- res.writeHead(400, { "Content-Type": "text/html" });
607
- res.end(callbackHtml("Authorization failed", "No session token received. Please try again."));
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
- res.writeHead(404, { "Content-Type": "text/plain" });
611
- res.end("Not Found");
612
- });
613
- await new Promise((resolve) => {
614
- server.listen(0, callbackHost, () => resolve());
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
- const authUrl = `${baseAppUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
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
- if (options.signal) {
628
- options.signal.addEventListener("abort", () => {
629
- clearTimeout(timeout);
630
- closeServer(server);
631
- resolveCallback({
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
- await new Promise((r) => setTimeout(r, 100));
640
- await closeServer(server);
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.11.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
- return new ApiClient(apiUrl, creds.token);
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.save({ token: manualToken, apiUrl });
3414
+ const previousCredentials = await store.load();
3220
3415
  try {
3221
- const client = new ApiClient(apiUrl, manualToken);
3222
- const user = await client.getMe();
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
- success("Token stored. Could not verify — check with `index conversation`.");
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 store = new CredentialStore;
3272
- await store.clear();
3273
- success("Logged out. Session cleared.");
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.11.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.11.0",
24
- "@indexnetwork/cli-linux-arm64": "0.11.0",
25
- "@indexnetwork/cli-darwin-x64": "0.11.0",
26
- "@indexnetwork/cli-darwin-arm64": "0.11.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"