octocode-cli 1.2.0 → 1.2.2

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.
@@ -2,7 +2,7 @@
2
2
  import fs, { existsSync, readFileSync, mkdirSync, writeFileSync, statSync } from "node:fs";
3
3
  import path, { join, dirname, resolve } from "node:path";
4
4
  import childProcess, { spawnSync, exec, execSync, execFile as execFile$2 } from "node:child_process";
5
- import { isMac, isWindows, getAppDataPath, HOME, getCredentials, isTokenExpired, getCredentialsSync, deleteCredentials, storeCredentials, getCredentialsFilePath, getTokenFromEnv, getEnvTokenSource, isRefreshTokenExpired, updateToken, isUsingSecureStorage as isUsingSecureStorage$1 } from "octocode-shared";
5
+ import { isMac, isWindows, getAppDataPath, HOME, getTokenWithRefresh, hasEnvToken, getEnvTokenSource, getCredentialsSync, isTokenExpired, getCredentials, deleteCredentials, storeCredentials, getCredentialsFilePath, resolveTokenFull, isUsingSecureStorage as isUsingSecureStorage$1 } from "octocode-shared";
6
6
  import os, { homedir } from "node:os";
7
7
  import { promisify } from "node:util";
8
8
  import { fileURLToPath } from "node:url";
@@ -278,10 +278,21 @@ function printWelcome() {
278
278
  }
279
279
  function printGoodbye() {
280
280
  console.log();
281
- console.log(c("magenta", "─".repeat(66)));
282
- console.log(c("magenta", " Thanks for using Octocode CLI"));
283
- console.log(c("magenta", ` 🔍🐙 ${c("underscore", "https://octocode.ai")}`));
284
- console.log(c("magenta", "─".repeat(66)));
281
+ console.log(
282
+ ` ${c("cyan", "💡")} ${bold("Quick tips for better AI coding with Octocode:")}`
283
+ );
284
+ console.log();
285
+ console.log(
286
+ ` ${c("green", "▸")} ${dim("Prompts:")} Use ${c("cyan", "/research")}, ${c("cyan", "/plan")}, ${c("cyan", "/implement")} in chat`
287
+ );
288
+ console.log(
289
+ ` ${c("green", "▸")} ${dim("Skills:")} Add all via ${c("cyan", "Manage System Skills")} → ${c("cyan", "Octocode Official")}`
290
+ );
291
+ console.log(
292
+ ` ${c("green", "▸")} ${dim("Context:")} Add ${c("cyan", "AGENTS.md")} to your project ${dim("(you can ask octocode)")}`
293
+ );
294
+ console.log();
295
+ console.log(` 🔍🐙 ${c("underscore", "https://octocode.ai")}`);
285
296
  console.log();
286
297
  }
287
298
  const activeSpinners = /* @__PURE__ */ new Set();
@@ -367,6 +378,11 @@ class Spinner {
367
378
  if (text) this.text = text;
368
379
  return this.stop("⚠", "yellow");
369
380
  }
381
+ /** Update the spinner text while running */
382
+ update(text) {
383
+ this.text = text;
384
+ return this;
385
+ }
370
386
  }
371
387
  function dirExists(dirPath) {
372
388
  try {
@@ -7305,7 +7321,7 @@ const SKILLS_MARKETPLACES = [
7305
7321
  branch: "main",
7306
7322
  skillsPath: "packages/octocode-cli/skills",
7307
7323
  skillPattern: "skill-folders",
7308
- description: "Official research, planning, review, generation & roast skills",
7324
+ description: "Official research, planning, review & roast skills",
7309
7325
  url: "https://github.com/ArekSrorth/octocode-mcp"
7310
7326
  },
7311
7327
  // === COMMUNITY MARKETPLACES ===
@@ -7922,6 +7938,110 @@ async function installSkill(skill) {
7922
7938
  await pressEnterToContinue$2();
7923
7939
  return result.success;
7924
7940
  }
7941
+ async function showOfficialFlowMenu(totalSkills, notInstalledCount) {
7942
+ console.log();
7943
+ console.log(` ${bold("Octocode Official Skills")}`);
7944
+ console.log(` ${dim(`${totalSkills} skills available`)}`);
7945
+ console.log();
7946
+ const choices = [];
7947
+ if (notInstalledCount > 0) {
7948
+ choices.push({
7949
+ name: `${c("green", "⚡")} Install All Skills (${notInstalledCount} to install)`,
7950
+ value: "install-all",
7951
+ description: dim("One-click install of all Octocode skills")
7952
+ });
7953
+ } else {
7954
+ choices.push({
7955
+ name: `${c("green", "✓")} All skills installed!`,
7956
+ value: "browse",
7957
+ description: dim("Browse to reinstall or view details")
7958
+ });
7959
+ }
7960
+ choices.push({
7961
+ name: `${c("cyan", "📋")} Browse Skills Individually`,
7962
+ value: "browse",
7963
+ description: dim("View details and install one by one")
7964
+ });
7965
+ choices.push(
7966
+ new Separator()
7967
+ );
7968
+ choices.push({
7969
+ name: `${c("dim", "← Back")}`,
7970
+ value: "back"
7971
+ });
7972
+ const choice = await select({
7973
+ message: "",
7974
+ choices,
7975
+ loop: false,
7976
+ theme: {
7977
+ prefix: " ",
7978
+ style: {
7979
+ highlight: (text) => c("magenta", text)
7980
+ }
7981
+ }
7982
+ });
7983
+ return choice;
7984
+ }
7985
+ async function installAllSkills(skills) {
7986
+ const destDir = getSkillsDestDir();
7987
+ const skillsToInstall = skills.filter((skill) => !isSkillInstalled(skill.name));
7988
+ if (skillsToInstall.length === 0) {
7989
+ console.log();
7990
+ console.log(` ${c("green", "✓")} All skills are already installed!`);
7991
+ console.log();
7992
+ await pressEnterToContinue$2();
7993
+ return;
7994
+ }
7995
+ console.log();
7996
+ console.log(
7997
+ ` ${bold("Installing")} ${skillsToInstall.length} ${bold("skills...")}`
7998
+ );
7999
+ console.log();
8000
+ const spinner = new Spinner(
8001
+ `Installing ${skillsToInstall.length} skills...`
8002
+ ).start();
8003
+ let installed = 0;
8004
+ let failed = 0;
8005
+ const errors = [];
8006
+ for (const skill of skillsToInstall) {
8007
+ spinner.update(
8008
+ `Installing ${skill.displayName}... (${installed + failed + 1}/${skillsToInstall.length})`
8009
+ );
8010
+ const result = await installMarketplaceSkill(skill, destDir);
8011
+ if (result.success) {
8012
+ installed++;
8013
+ } else {
8014
+ failed++;
8015
+ errors.push({
8016
+ skill: skill.displayName,
8017
+ error: result.error || "Unknown error"
8018
+ });
8019
+ }
8020
+ }
8021
+ if (failed === 0) {
8022
+ spinner.succeed(`All ${installed} skills installed successfully!`);
8023
+ } else {
8024
+ spinner.warn(`Installed ${installed} skills, ${failed} failed`);
8025
+ }
8026
+ console.log();
8027
+ if (installed > 0) {
8028
+ console.log(
8029
+ ` ${c("green", "✓")} Successfully installed ${installed} skill(s)`
8030
+ );
8031
+ console.log(` ${dim("Location:")} ${c("cyan", destDir)}`);
8032
+ }
8033
+ if (errors.length > 0) {
8034
+ console.log();
8035
+ console.log(` ${c("red", "✗")} Failed to install:`);
8036
+ for (const { skill, error } of errors) {
8037
+ console.log(` ${c("red", "•")} ${skill}: ${dim(error)}`);
8038
+ }
8039
+ }
8040
+ console.log();
8041
+ console.log(` ${bold("Skills are now available in Claude Code!")}`);
8042
+ console.log();
8043
+ await pressEnterToContinue$2();
8044
+ }
7925
8045
  async function runMarketplaceFlow() {
7926
8046
  console.log();
7927
8047
  console.log(
@@ -7982,6 +8102,58 @@ async function runMarketplaceFlow() {
7982
8102
  }
7983
8103
  }
7984
8104
  }
8105
+ async function installAllOctocodeSkills() {
8106
+ const source = SKILLS_MARKETPLACES.find((s) => s.id === "octocode-official");
8107
+ if (!source) {
8108
+ return {
8109
+ installed: 0,
8110
+ alreadyInstalled: 0,
8111
+ failed: 0,
8112
+ allInstalled: false
8113
+ };
8114
+ }
8115
+ let skills;
8116
+ try {
8117
+ skills = await fetchMarketplaceSkills(source);
8118
+ } catch {
8119
+ return {
8120
+ installed: 0,
8121
+ alreadyInstalled: 0,
8122
+ failed: 0,
8123
+ allInstalled: false
8124
+ };
8125
+ }
8126
+ if (skills.length === 0) {
8127
+ return { installed: 0, alreadyInstalled: 0, failed: 0, allInstalled: true };
8128
+ }
8129
+ const destDir = getSkillsDestDir();
8130
+ const skillsToInstall = skills.filter((skill) => !isSkillInstalled(skill.name));
8131
+ const alreadyInstalled = skills.length - skillsToInstall.length;
8132
+ if (skillsToInstall.length === 0) {
8133
+ return {
8134
+ installed: 0,
8135
+ alreadyInstalled,
8136
+ failed: 0,
8137
+ allInstalled: true
8138
+ };
8139
+ }
8140
+ let installed = 0;
8141
+ let failed = 0;
8142
+ for (const skill of skillsToInstall) {
8143
+ const result = await installMarketplaceSkill(skill, destDir);
8144
+ if (result.success) {
8145
+ installed++;
8146
+ } else {
8147
+ failed++;
8148
+ }
8149
+ }
8150
+ return {
8151
+ installed,
8152
+ alreadyInstalled,
8153
+ failed,
8154
+ allInstalled: failed === 0
8155
+ };
8156
+ }
7985
8157
  async function runOctocodeOfficialFlow() {
7986
8158
  const source = SKILLS_MARKETPLACES.find((s) => s.id === "octocode-official");
7987
8159
  if (!source) {
@@ -8014,16 +8186,39 @@ async function runOctocodeOfficialFlow() {
8014
8186
  await pressEnterToContinue$2();
8015
8187
  return;
8016
8188
  }
8017
- let inSkillsBrowser = true;
8018
- while (inSkillsBrowser) {
8019
- const skillChoice = await browseSkills(source, skills);
8020
- if (skillChoice === "back") {
8021
- inSkillsBrowser = false;
8022
- continue;
8023
- }
8024
- const detailChoice = await showSkillDetails(skillChoice);
8025
- if (detailChoice === "install") {
8026
- await installSkill(skillChoice);
8189
+ const notInstalledCount = skills.filter(
8190
+ (s) => !isSkillInstalled(s.name)
8191
+ ).length;
8192
+ let inOfficialFlow = true;
8193
+ while (inOfficialFlow) {
8194
+ const menuChoice = await showOfficialFlowMenu(
8195
+ skills.length,
8196
+ notInstalledCount
8197
+ );
8198
+ switch (menuChoice) {
8199
+ case "install-all":
8200
+ await installAllSkills(skills);
8201
+ inOfficialFlow = false;
8202
+ break;
8203
+ case "browse": {
8204
+ let inSkillsBrowser = true;
8205
+ while (inSkillsBrowser) {
8206
+ const skillChoice = await browseSkills(source, skills);
8207
+ if (skillChoice === "back") {
8208
+ inSkillsBrowser = false;
8209
+ continue;
8210
+ }
8211
+ const detailChoice = await showSkillDetails(skillChoice);
8212
+ if (detailChoice === "install") {
8213
+ await installSkill(skillChoice);
8214
+ }
8215
+ }
8216
+ break;
8217
+ }
8218
+ case "back":
8219
+ default:
8220
+ inOfficialFlow = false;
8221
+ break;
8027
8222
  }
8028
8223
  }
8029
8224
  }
@@ -9239,36 +9434,6 @@ async function exchangeDeviceCode(options) {
9239
9434
  function toTimestamp2(apiTimeInMs, expirationInSeconds) {
9240
9435
  return new Date(apiTimeInMs + expirationInSeconds * 1e3).toISOString();
9241
9436
  }
9242
- async function refreshToken(options) {
9243
- const request$1 = options.request || request;
9244
- const response = await oauthRequest(
9245
- request$1,
9246
- "POST /login/oauth/access_token",
9247
- {
9248
- client_id: options.clientId,
9249
- client_secret: options.clientSecret,
9250
- grant_type: "refresh_token",
9251
- refresh_token: options.refreshToken
9252
- }
9253
- );
9254
- const apiTimeInMs = new Date(response.headers.date).getTime();
9255
- const authentication = {
9256
- clientType: "github-app",
9257
- clientId: options.clientId,
9258
- clientSecret: options.clientSecret,
9259
- token: response.data.access_token,
9260
- refreshToken: response.data.refresh_token,
9261
- expiresAt: toTimestamp3(apiTimeInMs, response.data.expires_in),
9262
- refreshTokenExpiresAt: toTimestamp3(
9263
- apiTimeInMs,
9264
- response.data.refresh_token_expires_in
9265
- )
9266
- };
9267
- return { ...response, authentication };
9268
- }
9269
- function toTimestamp3(apiTimeInMs, expirationInSeconds) {
9270
- return new Date(apiTimeInMs + expirationInSeconds * 1e3).toISOString();
9271
- }
9272
9437
  async function getOAuthAccessToken(state, options) {
9273
9438
  const cachedAuthentication = getCachedAuthentication(state, options.auth);
9274
9439
  if (cachedAuthentication) return cachedAuthentication;
@@ -9543,60 +9708,57 @@ async function logout(hostname = DEFAULT_HOSTNAME, options) {
9543
9708
  await deleteCredentials(hostname);
9544
9709
  return { success: true };
9545
9710
  }
9546
- async function refreshAuthToken(hostname = DEFAULT_HOSTNAME) {
9547
- const credentials = await getCredentials(hostname);
9548
- if (!credentials) {
9711
+ function getAuthStatus(hostname = DEFAULT_HOSTNAME) {
9712
+ if (hasEnvToken()) {
9713
+ const envSource = getEnvTokenSource();
9549
9714
  return {
9550
- success: false,
9551
- error: `Not logged in to ${hostname}`
9715
+ authenticated: true,
9716
+ hostname,
9717
+ username: void 0,
9718
+ // Can't determine username from env token
9719
+ tokenSource: "env",
9720
+ // Store the specific env var for display (e.g., 'env:GH_TOKEN')
9721
+ envTokenSource: envSource ?? void 0
9552
9722
  };
9553
9723
  }
9554
- if (!credentials.token.refreshToken) {
9724
+ const credentials = getCredentialsSync(hostname);
9725
+ if (credentials) {
9726
+ const tokenExpired = isTokenExpired(credentials);
9555
9727
  return {
9556
- success: false,
9557
- error: "Token does not support refresh (OAuth App tokens do not expire)"
9728
+ authenticated: !tokenExpired,
9729
+ hostname: credentials.hostname,
9730
+ username: credentials.username,
9731
+ tokenExpired,
9732
+ tokenSource: "octocode"
9558
9733
  };
9559
9734
  }
9560
- if (isRefreshTokenExpired(credentials)) {
9735
+ const ghAuth = checkGitHubAuth();
9736
+ if (ghAuth.authenticated) {
9561
9737
  return {
9562
- success: false,
9563
- error: "Refresh token has expired. Please login again."
9738
+ authenticated: true,
9739
+ hostname,
9740
+ username: ghAuth.username,
9741
+ tokenSource: "gh-cli"
9564
9742
  };
9565
9743
  }
9566
- try {
9567
- const response = await refreshToken({
9568
- clientType: "github-app",
9569
- // Required: refreshToken API only works with GitHub Apps
9570
- clientId: DEFAULT_CLIENT_ID,
9571
- clientSecret: "",
9572
- // Not available for public OAuth apps
9573
- refreshToken: credentials.token.refreshToken,
9574
- request: request.defaults({
9575
- baseUrl: getApiBaseUrl(hostname)
9576
- })
9577
- });
9578
- const newToken = {
9579
- token: response.authentication.token,
9580
- tokenType: "oauth",
9581
- refreshToken: response.authentication.refreshToken,
9582
- expiresAt: response.authentication.expiresAt,
9583
- refreshTokenExpiresAt: response.authentication.refreshTokenExpiresAt
9584
- };
9585
- await updateToken(hostname, newToken);
9586
- return {
9587
- success: true,
9588
- username: credentials.username,
9589
- hostname
9590
- };
9591
- } catch (error) {
9744
+ return {
9745
+ authenticated: false,
9746
+ tokenSource: "none"
9747
+ };
9748
+ }
9749
+ async function getAuthStatusAsync(hostname = DEFAULT_HOSTNAME) {
9750
+ if (hasEnvToken()) {
9751
+ const envSource = getEnvTokenSource();
9592
9752
  return {
9593
- success: false,
9594
- error: error instanceof Error ? error.message : "Token refresh failed"
9753
+ authenticated: true,
9754
+ hostname,
9755
+ username: void 0,
9756
+ // Can't determine username from env token
9757
+ tokenSource: "env",
9758
+ envTokenSource: envSource ?? void 0
9595
9759
  };
9596
9760
  }
9597
- }
9598
- function getAuthStatus(hostname = DEFAULT_HOSTNAME) {
9599
- const credentials = getCredentialsSync(hostname);
9761
+ const credentials = await getCredentials(hostname);
9600
9762
  if (credentials) {
9601
9763
  const tokenExpired = isTokenExpired(credentials);
9602
9764
  return {
@@ -9622,31 +9784,12 @@ function getAuthStatus(hostname = DEFAULT_HOSTNAME) {
9622
9784
  };
9623
9785
  }
9624
9786
  async function getOctocodeToken(hostname = DEFAULT_HOSTNAME) {
9625
- const credentials = await getCredentials(hostname);
9626
- if (credentials) {
9627
- if (isTokenExpired(credentials)) {
9628
- if (credentials.token.refreshToken) {
9629
- const result = await refreshAuthToken(hostname);
9630
- if (result.success) {
9631
- const updated = await getCredentials(hostname);
9632
- if (updated?.token.token) {
9633
- return {
9634
- token: updated.token.token,
9635
- source: "octocode",
9636
- username: updated.username
9637
- };
9638
- }
9639
- }
9640
- }
9641
- return {
9642
- token: null,
9643
- source: "none"
9644
- };
9645
- }
9787
+ const result = await getTokenWithRefresh(hostname, DEFAULT_CLIENT_ID);
9788
+ if (result.token) {
9646
9789
  return {
9647
- token: credentials.token.token,
9790
+ token: result.token,
9648
9791
  source: "octocode",
9649
- username: credentials.username
9792
+ username: result.username
9650
9793
  };
9651
9794
  }
9652
9795
  return {
@@ -9676,22 +9819,23 @@ async function getToken(hostname = DEFAULT_HOSTNAME, preferredSource = "auto") {
9676
9819
  if (preferredSource === "gh") {
9677
9820
  return getGhCliToken(hostname);
9678
9821
  }
9679
- const envToken = getTokenFromEnv();
9680
- if (envToken) {
9681
- const source = getEnvTokenSource();
9822
+ const result = await resolveTokenFull({
9823
+ hostname,
9824
+ getGhCliToken: getGitHubCLIToken
9825
+ });
9826
+ if (result?.token) {
9827
+ const source = result.source === "gh-cli" ? "gh-cli" : result.source?.startsWith("env:") ? "env" : "octocode";
9682
9828
  return {
9683
- token: envToken,
9684
- source: "env",
9685
- username: void 0,
9686
- // Include which env var was used for debugging
9687
- envSource: source ?? void 0
9829
+ token: result.token,
9830
+ source,
9831
+ username: result.username,
9832
+ envSource: result.source?.startsWith("env:") ? result.source : void 0
9688
9833
  };
9689
9834
  }
9690
- const ghResult = getGhCliToken(hostname);
9691
- if (ghResult.token) {
9692
- return ghResult;
9693
- }
9694
- return getOctocodeToken(hostname);
9835
+ return {
9836
+ token: null,
9837
+ source: "none"
9838
+ };
9695
9839
  }
9696
9840
  function getStoragePath() {
9697
9841
  return getCredentialsFilePath();
@@ -9699,6 +9843,19 @@ function getStoragePath() {
9699
9843
  function isUsingSecureStorage() {
9700
9844
  return isUsingSecureStorage$1();
9701
9845
  }
9846
+ function getTokenType(source, envSource) {
9847
+ switch (source) {
9848
+ case "env":
9849
+ return envSource ?? "env:GITHUB_TOKEN";
9850
+ case "gh-cli":
9851
+ return "gh-cli";
9852
+ case "octocode":
9853
+ return "octocode-storage";
9854
+ case "none":
9855
+ default:
9856
+ return "none";
9857
+ }
9858
+ }
9702
9859
  function getOctocodeState() {
9703
9860
  const allClients = getAllClientInstallStatus();
9704
9861
  const installedClients = allClients.filter((c2) => c2.octocodeInstalled);
@@ -9752,12 +9909,12 @@ function getSkillsState() {
9752
9909
  hasSkills: skills.length > 0
9753
9910
  };
9754
9911
  }
9755
- function getAppState() {
9912
+ async function getAppState() {
9756
9913
  return {
9757
9914
  octocode: getOctocodeState(),
9758
9915
  skills: getSkillsState(),
9759
9916
  currentClient: detectCurrentClient(),
9760
- githubAuth: getAuthStatus()
9917
+ githubAuth: await getAuthStatusAsync()
9761
9918
  };
9762
9919
  }
9763
9920
  async function pressEnterToContinue() {
@@ -9795,35 +9952,54 @@ function buildSkillsMenuItem(skills) {
9795
9952
  return {
9796
9953
  name: `🧠 Manage System Skills ${c("green", "✓")}`,
9797
9954
  value: "skills",
9798
- description: `${skills.totalInstalledCount} installed • ${skills.destDir}`
9955
+ description: `${skills.totalInstalledCount} installed • Research, PR Review & more`
9799
9956
  };
9800
9957
  }
9801
9958
  if (skills.totalInstalledCount > 0) {
9802
9959
  return {
9803
9960
  name: "🧠 Manage System Skills",
9804
9961
  value: "skills",
9805
- description: `${skills.totalInstalledCount} installed • ${skills.destDir}`
9962
+ description: `${skills.totalInstalledCount}/${skills.skills.length} installed • Get more skills!`
9806
9963
  };
9807
9964
  }
9808
9965
  return {
9809
- name: "🧠 Manage System Skills",
9966
+ name: `🧠 ${bold("Manage System Skills")} ${c("cyan", "★")}`,
9810
9967
  value: "skills",
9811
- description: `No skills installed ${skills.destDir}`
9968
+ description: `${c("cyan", "→")} Install skills for AI-powered coding workflows`
9812
9969
  };
9813
9970
  }
9971
+ function getAuthSourceDisplay(auth2) {
9972
+ switch (auth2.tokenSource) {
9973
+ case "gh-cli":
9974
+ return "gh CLI";
9975
+ case "env": {
9976
+ if (auth2.envTokenSource) {
9977
+ const varName = auth2.envTokenSource.replace("env:", "");
9978
+ return `env (${varName})`;
9979
+ }
9980
+ return "env var";
9981
+ }
9982
+ case "octocode":
9983
+ return "Octocode";
9984
+ default:
9985
+ return "unknown";
9986
+ }
9987
+ }
9814
9988
  function buildAuthMenuItem(auth2) {
9815
9989
  if (auth2.authenticated) {
9816
- const source = auth2.tokenSource === "gh-cli" ? "gh CLI" : "Octocode";
9990
+ const source = getAuthSourceDisplay(auth2);
9991
+ const user = auth2.username ? `@${auth2.username}` : "";
9992
+ const userPart = user ? `${user} ` : "";
9817
9993
  return {
9818
9994
  name: `🔑 Manage Auth ${c("green", "✓")}`,
9819
9995
  value: "auth",
9820
- description: `@${auth2.username || "unknown"} via ${source}`
9996
+ description: `${userPart}via ${source}`
9821
9997
  };
9822
9998
  }
9823
9999
  return {
9824
- name: `🔑 Manage Auth ${c("red", "✗")}`,
10000
+ name: `🔑 ${bold("Manage Auth")} ${c("red", "✗ Required!")}`,
9825
10001
  value: "auth",
9826
- description: "Not configured - set up auth"
10002
+ description: `${c("yellow", "→")} Sign in to access GitHub`
9827
10003
  };
9828
10004
  }
9829
10005
  function buildStatusLine(state) {
@@ -9865,9 +10041,52 @@ function buildOctocodeMenuItem(state) {
9865
10041
  description: "Configure Octocode MCP - 0 IDEs configured"
9866
10042
  };
9867
10043
  }
10044
+ function printContextualHints(state) {
10045
+ if (!state.githubAuth.authenticated) {
10046
+ console.log();
10047
+ console.log(
10048
+ ` ${c("yellow", "⚠")} ${bold("Auth required!")} Run ${c("cyan", "🔑 Manage Auth")} to access GitHub repos`
10049
+ );
10050
+ } else if (state.octocode.isInstalled && state.skills.totalInstalledCount === 0) {
10051
+ console.log();
10052
+ console.log(
10053
+ ` ${c("cyan", "💡")} ${dim("Boost your AI coding:")} Install ${c("magenta", "Skills")} for research, PR review & more!`
10054
+ );
10055
+ }
10056
+ console.log();
10057
+ console.log(` ${c("yellow", "Hints:")}`);
10058
+ console.log(
10059
+ c("yellow", ` ▸ Prompts: Use /research, /plan, /implement in chat`)
10060
+ );
10061
+ console.log(
10062
+ c(
10063
+ "yellow",
10064
+ ` ▸ Skills: Add all via Manage System Skills → Octocode Official`
10065
+ )
10066
+ );
10067
+ console.log(
10068
+ c(
10069
+ "yellow",
10070
+ ` ▸ Context: Add AGENTS.md to your project (you can ask octocode)`
10071
+ )
10072
+ );
10073
+ console.log(
10074
+ c(
10075
+ "yellow",
10076
+ ` ▸ Auth: Supports Octocode OAuth and gh CLI (if installed)`
10077
+ )
10078
+ );
10079
+ console.log(
10080
+ c(
10081
+ "yellow",
10082
+ ` ▸ MCP: Manage all system MCP servers via Manage System MCP`
10083
+ )
10084
+ );
10085
+ }
9868
10086
  async function showMainMenu(state) {
9869
10087
  console.log();
9870
10088
  console.log(` ${dim("Status:")} ${buildStatusLine(state)}`);
10089
+ printContextualHints(state);
9871
10090
  const choices = [];
9872
10091
  choices.push(buildOctocodeMenuItem(state));
9873
10092
  choices.push(buildSkillsMenuItem(state.skills));
@@ -9925,6 +10144,14 @@ async function showOctocodeMenu(state) {
9925
10144
  description: "Server options & preferences"
9926
10145
  });
9927
10146
  }
10147
+ if (!state.skills.allInstalled && state.skills.hasSkills) {
10148
+ const notInstalled = state.skills.skills.filter((s) => !s.installed).length;
10149
+ choices.push({
10150
+ name: `🧠 Install All Skills ${c("cyan", `(${notInstalled} available)`)}`,
10151
+ value: "install-skills",
10152
+ description: "One-click install of all Octocode skills"
10153
+ });
10154
+ }
9928
10155
  choices.push(
9929
10156
  new Separator()
9930
10157
  );
@@ -9948,7 +10175,7 @@ async function showOctocodeMenu(state) {
9948
10175
  }
9949
10176
  async function runOctocodeFlow() {
9950
10177
  await loadInquirer();
9951
- let state = getAppState();
10178
+ let state = await getAppState();
9952
10179
  console.log();
9953
10180
  printInstalledIDEs(state.octocode.installedClients);
9954
10181
  let inMenu = true;
@@ -9958,7 +10185,7 @@ async function runOctocodeFlow() {
9958
10185
  firstRun = false;
9959
10186
  } else {
9960
10187
  const spinner = new Spinner(" Refreshing...").start();
9961
- state = getAppState();
10188
+ state = await getAppState();
9962
10189
  spinner.clear();
9963
10190
  }
9964
10191
  const choice = await showOctocodeMenu(state);
@@ -9971,6 +10198,45 @@ async function runOctocodeFlow() {
9971
10198
  await runConfigOptionsFlow();
9972
10199
  console.log();
9973
10200
  break;
10201
+ case "install-skills": {
10202
+ console.log();
10203
+ const skillsSpinner = new Spinner(
10204
+ "Installing all Octocode skills..."
10205
+ ).start();
10206
+ const result = await installAllOctocodeSkills();
10207
+ if (result.installed > 0) {
10208
+ skillsSpinner.succeed(
10209
+ `Installed ${result.installed} skill${result.installed !== 1 ? "s" : ""}!`
10210
+ );
10211
+ console.log();
10212
+ console.log(
10213
+ ` ${c("green", "✓")} ${result.installed} skill${result.installed !== 1 ? "s" : ""} installed successfully`
10214
+ );
10215
+ if (result.alreadyInstalled > 0) {
10216
+ console.log(
10217
+ ` ${dim(`(${result.alreadyInstalled} already installed)`)}`
10218
+ );
10219
+ }
10220
+ if (result.failed > 0) {
10221
+ console.log(
10222
+ ` ${c("yellow", "⚠")} ${result.failed} skill${result.failed !== 1 ? "s" : ""} failed to install`
10223
+ );
10224
+ }
10225
+ console.log();
10226
+ console.log(` ${bold("Skills are now available in Claude Code!")}`);
10227
+ } else if (result.allInstalled) {
10228
+ skillsSpinner.succeed("All skills already installed!");
10229
+ console.log();
10230
+ console.log(` ${c("green", "✓")} All Octocode skills are installed`);
10231
+ } else {
10232
+ skillsSpinner.fail("Failed to install skills");
10233
+ console.log();
10234
+ console.log(` ${c("red", "✗")} Could not install skills`);
10235
+ }
10236
+ console.log();
10237
+ await pressEnterToContinue();
10238
+ break;
10239
+ }
9974
10240
  case "back":
9975
10241
  default:
9976
10242
  inMenu = false;
@@ -10043,46 +10309,54 @@ async function runMCPConfigFlow() {
10043
10309
  }
10044
10310
  async function showAuthMenu(status) {
10045
10311
  const choices = [];
10046
- const isUsingOctocode = status.tokenSource === "octocode";
10047
- const isUsingGhCli = status.tokenSource === "gh-cli";
10048
- const isAuthenticated = status.authenticated;
10049
- if (isAuthenticated) {
10312
+ const isUsingEnv = status.tokenSource === "env";
10313
+ const ghCliToken = getGitHubCLIToken();
10314
+ const ghAuth = checkGitHubAuth();
10315
+ const octocodeCredentials = await getCredentials();
10316
+ const hasGhCli = !!ghCliToken;
10317
+ const hasOctocode = !!octocodeCredentials;
10318
+ const hasEnv = hasEnvToken();
10319
+ if (isUsingEnv && hasEnv) {
10320
+ const envVar = status.envTokenSource?.replace("env:", "") || "environment variable";
10050
10321
  choices.push({
10051
- name: "🔍 Manage Auth Tokens",
10052
- value: "check-token",
10053
- description: "View and delete stored tokens"
10322
+ name: `ℹ️ Using ${c("cyan", envVar)} ${dim("(takes priority)")}`,
10323
+ value: "back",
10324
+ // No action, just info
10325
+ description: "Token set via environment variable"
10054
10326
  });
10055
- if (isUsingGhCli) {
10056
- choices.push({
10057
- name: "🔓 Sign Out (gh CLI)",
10058
- value: "gh-logout",
10059
- description: "Opens gh auth logout"
10060
- });
10061
- } else if (isUsingOctocode) {
10062
- choices.push({
10063
- name: "🔓 Sign Out",
10064
- value: "logout",
10065
- description: "Remove Octocode token"
10066
- });
10067
- }
10068
- } else {
10327
+ choices.push(
10328
+ new Separator()
10329
+ );
10330
+ }
10331
+ if (hasGhCli) {
10332
+ const userPart = ghAuth.username ? ` (@${ghAuth.username})` : "";
10333
+ choices.push({
10334
+ name: `🗑️ Delete gh CLI token${userPart}`,
10335
+ value: "gh-logout",
10336
+ description: "Opens gh auth logout"
10337
+ });
10338
+ }
10339
+ if (hasOctocode) {
10340
+ const userPart = octocodeCredentials.username ? ` (@${octocodeCredentials.username})` : "";
10341
+ const storageType = isUsingSecureStorage() ? "keychain" : "file";
10342
+ choices.push({
10343
+ name: `🗑️ Delete Octocode token${userPart}`,
10344
+ value: "logout",
10345
+ description: `Remove from ${storageType}`
10346
+ });
10347
+ }
10348
+ if (!hasOctocode) {
10069
10349
  choices.push({
10070
10350
  name: `🔐 Sign In via Octocode ${c("green", "(Recommended)")}`,
10071
10351
  value: "login",
10072
10352
  description: "Quick browser sign in"
10073
10353
  });
10354
+ }
10355
+ if (!hasGhCli) {
10074
10356
  choices.push({
10075
10357
  name: "🔐 Sign In via gh CLI",
10076
10358
  value: "gh-guidance",
10077
- description: "Use existing GitHub CLI installation"
10078
- });
10079
- choices.push(
10080
- new Separator()
10081
- );
10082
- choices.push({
10083
- name: `${dim("🔍 View all auth methods")}`,
10084
- value: "check-token",
10085
- description: "Check env vars, gh CLI, stored tokens"
10359
+ description: ghAuth.installed ? "Use existing GitHub CLI" : "GitHub CLI not installed"
10086
10360
  });
10087
10361
  }
10088
10362
  choices.push(
@@ -10108,43 +10382,82 @@ async function showAuthMenu(status) {
10108
10382
  }
10109
10383
  async function runLoginFlow() {
10110
10384
  console.log();
10385
+ console.log(c("blue", "━".repeat(66)));
10111
10386
  console.log(` ${bold("🔐 GitHub Authentication")}`);
10387
+ console.log(c("blue", "━".repeat(66)));
10112
10388
  console.log();
10113
10389
  console.log(
10114
10390
  ` ${dim("This will open your browser to authenticate with GitHub.")}`
10115
10391
  );
10116
10392
  console.log();
10117
10393
  let verificationShown = false;
10394
+ let authSpinner = null;
10118
10395
  const spinner = new Spinner("Connecting to GitHub...").start();
10119
10396
  const result = await login({
10120
10397
  onVerification: (verification) => {
10121
10398
  spinner.stop();
10122
10399
  verificationShown = true;
10400
+ console.log();
10401
+ console.log(c("yellow", " ┌" + "─".repeat(50) + "┐"));
10123
10402
  console.log(
10124
- ` ${c("yellow", "!")} First copy your one-time code: ${bold(verification.user_code)}`
10403
+ c("yellow", " │ ") + `${c("yellow", "!")} Your one-time code: ${bold(c("cyan", verification.user_code))}` + " ".repeat(50 - 26 - verification.user_code.length) + c("yellow", "│")
10125
10404
  );
10405
+ console.log(c("yellow", " └" + "─".repeat(50) + "┘"));
10126
10406
  console.log();
10407
+ console.log(` ${bold("1.")} Copy the code above`);
10127
10408
  console.log(
10128
- ` ${bold("Press Enter")} to open ${c("cyan", verification.verification_uri)} in your browser...`
10409
+ ` ${bold("2.")} ${bold("Press Enter")} to open ${c("cyan", verification.verification_uri)}`
10129
10410
  );
10411
+ console.log(` ${bold("3.")} Paste the code in your browser`);
10130
10412
  console.log();
10131
- console.log(` ${dim("Waiting for authentication...")}`);
10413
+ authSpinner = new Spinner(
10414
+ `Waiting for browser authentication... ${dim("(typically 10-30 seconds)")}`
10415
+ ).start();
10132
10416
  }
10133
10417
  });
10418
+ if (authSpinner) {
10419
+ authSpinner.stop();
10420
+ }
10134
10421
  if (!verificationShown) {
10135
10422
  spinner.stop();
10136
10423
  }
10137
10424
  console.log();
10138
10425
  if (result.success) {
10139
- console.log(` ${c("green", "")} Authentication complete!`);
10426
+ console.log(c("green", "" + "─".repeat(50) + "┐"));
10140
10427
  console.log(
10141
- ` ${c("green", "✓")} Logged in as ${c("cyan", result.username || "unknown")}`
10428
+ c("green", " │ ") + `${c("green", "✓")} ${bold("Authentication successful!")}` + " ".repeat(22) + c("green", "│")
10142
10429
  );
10430
+ console.log(c("green", " └" + "─".repeat(50) + "┘"));
10143
10431
  console.log();
10432
+ console.log(
10433
+ ` ${c("green", "✓")} Logged in as ${c("cyan", "@" + (result.username || "unknown"))}`
10434
+ );
10144
10435
  console.log(` ${dim("Credentials stored in:")} ${getStoragePath()}`);
10436
+ console.log();
10437
+ console.log(` ${c("cyan", "💡")} ${bold("What's next?")}`);
10438
+ console.log(
10439
+ ` ${dim("•")} Install ${c("magenta", "Skills")} for AI-powered research & PR reviews`
10440
+ );
10441
+ console.log(
10442
+ ` ${dim("•")} Use ${c("cyan", "/research")} prompt to explore any GitHub repo`
10443
+ );
10444
+ console.log(
10445
+ ` ${dim("•")} Add ${c("cyan", "AGENTS.md")} to your project for better AI context`
10446
+ );
10145
10447
  } else {
10448
+ console.log(c("red", " ┌" + "─".repeat(50) + "┐"));
10146
10449
  console.log(
10147
- ` ${c("red", "✗")} Authentication failed: ${result.error || "Unknown error"}`
10450
+ c("red", " │ ") + `${c("red", "✗")} ${bold("Authentication failed")}` + " ".repeat(27) + c("red", "│")
10451
+ );
10452
+ console.log(c("red", " └" + "─".repeat(50) + "┘"));
10453
+ console.log();
10454
+ console.log(` ${c("red", "Error:")} ${result.error || "Unknown error"}`);
10455
+ console.log();
10456
+ console.log(` ${bold("Troubleshooting:")}`);
10457
+ console.log(` ${dim("•")} Make sure you copied the code correctly`);
10458
+ console.log(` ${dim("•")} Check your browser didn't block the popup`);
10459
+ console.log(
10460
+ ` ${dim("•")} Try running ${c("cyan", "octocode login")} again`
10148
10461
  );
10149
10462
  }
10150
10463
  console.log();
@@ -10152,7 +10465,7 @@ async function runLoginFlow() {
10152
10465
  return result.success;
10153
10466
  }
10154
10467
  async function runLogoutFlow() {
10155
- const status = getAuthStatus();
10468
+ const status = await getAuthStatusAsync();
10156
10469
  console.log();
10157
10470
  console.log(` ${bold("🔓 Sign Out")}`);
10158
10471
  console.log(
@@ -10233,192 +10546,64 @@ async function showGhCliGuidance() {
10233
10546
  await pressEnterToContinue();
10234
10547
  }
10235
10548
  }
10236
- async function runCheckTokenFlow() {
10237
- let inTokenCheck = true;
10238
- while (inTokenCheck) {
10239
- console.log();
10240
- console.log(` ${bold("🔍 Auth Token Status")}`);
10241
- console.log();
10242
- const ghCliToken = getGitHubCLIToken();
10243
- const ghAuth = checkGitHubAuth();
10244
- let ghCliAvailable = false;
10245
- let ghCliUsername = "";
10246
- if (ghCliToken) {
10247
- ghCliAvailable = true;
10248
- ghCliUsername = ghAuth.username || "";
10249
- }
10250
- const octocodeCredentials = await getCredentials();
10251
- let octocodeAvailable = false;
10252
- let octocodeUsername = "";
10253
- const storageType = isUsingSecureStorage() ? "keychain" : "encrypted file";
10254
- if (octocodeCredentials) {
10255
- octocodeAvailable = true;
10256
- octocodeUsername = octocodeCredentials.username || "";
10257
- }
10258
- if (ghCliAvailable) {
10259
- const userPart = ghCliUsername ? ` (@${ghCliUsername})` : "";
10260
- console.log(` ${c("green", "✓")} gh CLI${userPart}`);
10261
- } else {
10262
- const statusText = ghAuth.installed ? "Not authenticated" : "Not installed";
10263
- console.log(` ${c("red", "✗")} gh CLI: ${dim(statusText)}`);
10264
- }
10265
- if (octocodeAvailable) {
10266
- const userPart = octocodeUsername ? ` (@${octocodeUsername})` : "";
10267
- console.log(` ${c("green", "✓")} Octocode (${storageType})${userPart}`);
10268
- } else {
10269
- console.log(
10270
- ` ${c("red", "✗")} Octocode (${storageType}): ${dim("Not stored")}`
10271
- );
10272
- }
10273
- const availableCount = (ghCliAvailable ? 1 : 0) + (octocodeAvailable ? 1 : 0);
10274
- console.log();
10275
- if (availableCount > 0) {
10276
- console.log(` ${dim("→")} ${availableCount} auth method(s) available`);
10277
- } else {
10278
- console.log(` ${c("yellow", "⚠")} No authentication methods available`);
10279
- }
10280
- console.log();
10281
- const choices = [];
10282
- if (ghCliAvailable) {
10283
- choices.push({
10284
- name: `🗑️ Delete gh CLI token`,
10285
- value: "delete-gh",
10286
- description: ghCliUsername ? `@${ghCliUsername}` : "Opens gh auth logout"
10287
- });
10288
- } else {
10289
- choices.push({
10290
- name: `🔐 Sign In via gh CLI`,
10291
- value: "gh-guidance",
10292
- description: "Use GitHub CLI to authenticate"
10293
- });
10294
- }
10295
- if (octocodeAvailable) {
10296
- choices.push({
10297
- name: `🗑️ Delete Octocode token`,
10298
- value: "delete-octocode",
10299
- description: octocodeUsername ? `@${octocodeUsername}` : `From ${storageType}`
10300
- });
10301
- } else {
10302
- choices.push({
10303
- name: `🔐 Sign In via Octocode`,
10304
- value: "login-octocode",
10305
- description: "Quick browser sign in"
10306
- });
10307
- }
10308
- choices.push(
10309
- new Separator()
10310
- );
10311
- choices.push({
10312
- name: `${c("dim", "← Back")}`,
10313
- value: "back"
10314
- });
10315
- const choice = await selectWithCancel({
10316
- message: "",
10317
- choices,
10318
- pageSize: 10,
10319
- loop: false,
10320
- theme: {
10321
- prefix: " ",
10322
- style: {
10323
- highlight: (text) => c("magenta", text)
10324
- }
10325
- }
10326
- });
10327
- switch (choice) {
10328
- case "delete-gh": {
10329
- console.log();
10330
- const confirmGh = await selectWithCancel({
10331
- message: `Delete gh CLI token${ghCliUsername ? ` (@${ghCliUsername})` : ""}?`,
10332
- choices: [
10333
- { name: "Yes, sign out", value: "yes" },
10334
- { name: "No, cancel", value: "no" }
10335
- ],
10336
- theme: {
10337
- prefix: " ",
10338
- style: {
10339
- highlight: (text) => c("red", text)
10340
- }
10341
- }
10342
- });
10343
- if (confirmGh === "yes") {
10344
- console.log();
10345
- console.log(` ${dim("Opening gh auth logout...")}`);
10346
- const ghResult = runGitHubAuthLogout();
10347
- if (ghResult.success) {
10348
- console.log(` ${c("green", "✓")} Signed out of gh CLI`);
10349
- } else {
10350
- console.log(` ${c("yellow", "!")} Sign out was cancelled`);
10351
- }
10352
- console.log();
10353
- await pressEnterToContinue();
10354
- }
10355
- break;
10356
- }
10357
- case "delete-octocode": {
10358
- console.log();
10359
- const confirmOctocode = await selectWithCancel({
10360
- message: `Delete Octocode token${octocodeUsername ? ` (@${octocodeUsername})` : ""} from ${storageType}?`,
10361
- choices: [
10362
- { name: "Yes, delete", value: "yes" },
10363
- { name: "No, cancel", value: "no" }
10364
- ],
10365
- theme: {
10366
- prefix: " ",
10367
- style: {
10368
- highlight: (text) => c("red", text)
10369
- }
10370
- }
10371
- });
10372
- if (confirmOctocode === "yes") {
10373
- const result = await logout();
10374
- if (result.success) {
10375
- console.log();
10376
- console.log(` ${c("green", "✓")} Octocode token deleted`);
10377
- } else {
10378
- console.log();
10379
- console.log(
10380
- ` ${c("red", "✗")} Failed to delete: ${result.error || "Unknown error"}`
10381
- );
10382
- }
10383
- console.log();
10384
- await pressEnterToContinue();
10385
- }
10386
- break;
10387
- }
10388
- case "login-octocode": {
10389
- const success = await runLoginFlow();
10390
- if (success) {
10391
- inTokenCheck = false;
10392
- }
10393
- break;
10394
- }
10395
- case "gh-guidance": {
10396
- await showGhCliGuidance();
10397
- break;
10549
+ function getDetailedAuthSource(status) {
10550
+ switch (status.tokenSource) {
10551
+ case "gh-cli":
10552
+ return "gh CLI";
10553
+ case "env": {
10554
+ if (status.envTokenSource) {
10555
+ const varName = status.envTokenSource.replace("env:", "");
10556
+ return `${varName} env var`;
10398
10557
  }
10399
- case "back":
10400
- default:
10401
- inTokenCheck = false;
10402
- break;
10558
+ return "environment variable";
10403
10559
  }
10560
+ case "octocode":
10561
+ return isUsingSecureStorage() ? "keychain" : "file";
10562
+ default:
10563
+ return "unknown";
10404
10564
  }
10405
10565
  }
10406
10566
  function displayAuthStatus(status) {
10407
10567
  console.log(` ${bold("🔐 GitHub Authentication")}`);
10408
10568
  console.log();
10409
10569
  if (status.authenticated) {
10410
- const source = status.tokenSource === "gh-cli" ? "gh CLI" : isUsingSecureStorage() ? "keychain" : "file";
10411
- console.log(
10412
- ` ${c("green", "✓")} Signed in as ${c("cyan", "@" + (status.username || "unknown"))} ${dim(`via ${source}`)}`
10413
- );
10570
+ const source = getDetailedAuthSource(status);
10571
+ if (status.tokenSource === "env") {
10572
+ const envVarName = status.envTokenSource ? status.envTokenSource.replace("env:", "") : "environment variable";
10573
+ console.log(
10574
+ ` ${c("green", "✓")} Using ${c("cyan", envVarName)} ${dim("(token configured)")}`
10575
+ );
10576
+ } else {
10577
+ console.log(
10578
+ ` ${c("green", "✓")} Signed in as ${c("cyan", "@" + (status.username || "unknown"))} ${dim(`via ${source}`)}`
10579
+ );
10580
+ }
10414
10581
  if (status.tokenExpired) {
10415
10582
  console.log(
10416
10583
  ` ${c("yellow", "⚠")} Session expired - please sign in again`
10417
10584
  );
10418
10585
  }
10586
+ console.log();
10587
+ console.log(
10588
+ ` ${c("green", "✓")} ${dim("Ready to access GitHub repositories!")}`
10589
+ );
10419
10590
  } else {
10420
- console.log(` ${c("yellow", "")} Not signed in`);
10421
- console.log(` ${dim("Sign in to access private repositories.")}`);
10591
+ console.log(c("yellow", "" + "─".repeat(56) + "┐"));
10592
+ console.log(
10593
+ c("yellow", " │ ") + `${c("yellow", "⚠")} ${bold("Authentication Required")}` + " ".repeat(31) + c("yellow", "│")
10594
+ );
10595
+ console.log(c("yellow", " └" + "─".repeat(56) + "┘"));
10596
+ console.log();
10597
+ console.log(` ${dim("Without auth, Octocode cannot:")}`);
10598
+ console.log(` ${c("red", "✗")} Access private repositories`);
10599
+ console.log(` ${c("red", "✗")} Search code in your organization`);
10600
+ console.log(
10601
+ ` ${c("red", "✗")} Provide full GitHub research capabilities`
10602
+ );
10603
+ console.log();
10604
+ console.log(
10605
+ ` ${c("cyan", "→")} Select ${c("green", '"Sign In via Octocode"')} below to authenticate`
10606
+ );
10422
10607
  }
10423
10608
  console.log();
10424
10609
  }
@@ -10427,14 +10612,10 @@ async function runAuthFlow() {
10427
10612
  console.log();
10428
10613
  let inAuthMenu = true;
10429
10614
  while (inAuthMenu) {
10430
- const status = getAuthStatus();
10615
+ const status = await getAuthStatusAsync();
10431
10616
  displayAuthStatus(status);
10432
10617
  const choice = await showAuthMenu(status);
10433
10618
  switch (choice) {
10434
- case "check-token":
10435
- await runCheckTokenFlow();
10436
- console.log();
10437
- break;
10438
10619
  case "login":
10439
10620
  await runLoginFlow();
10440
10621
  console.log();
@@ -10447,6 +10628,22 @@ async function runAuthFlow() {
10447
10628
  console.log();
10448
10629
  break;
10449
10630
  case "gh-logout": {
10631
+ const confirmGh = await selectWithCancel({
10632
+ message: "Sign out of gh CLI?",
10633
+ choices: [
10634
+ { name: "Yes, sign out", value: "yes" },
10635
+ { name: "No, cancel", value: "no" }
10636
+ ],
10637
+ theme: {
10638
+ prefix: " ",
10639
+ style: {
10640
+ highlight: (text) => c("red", text)
10641
+ }
10642
+ }
10643
+ });
10644
+ if (confirmGh !== "yes") {
10645
+ break;
10646
+ }
10450
10647
  console.log();
10451
10648
  console.log(` ${dim("Opening gh auth logout...")}`);
10452
10649
  console.log();
@@ -10511,10 +10708,10 @@ async function runMenuLoop() {
10511
10708
  while (running) {
10512
10709
  let state;
10513
10710
  if (firstRun) {
10514
- state = getAppState();
10711
+ state = await getAppState();
10515
10712
  } else {
10516
10713
  const spinner = new Spinner(" Loading...").start();
10517
- state = getAppState();
10714
+ state = await getAppState();
10518
10715
  spinner.clear();
10519
10716
  }
10520
10717
  if (!firstRun) {
@@ -11203,7 +11400,7 @@ const tokenCommand = {
11203
11400
  name: "token",
11204
11401
  aliases: ["t"],
11205
11402
  description: "Print the GitHub token (matches octocode-mcp priority)",
11206
- usage: "octocode token [--type <auto|octocode|gh>] [--hostname <host>] [--source]",
11403
+ usage: "octocode token [--type <auto|octocode|gh>] [--hostname <host>] [--source] [--json]",
11207
11404
  options: [
11208
11405
  {
11209
11406
  name: "type",
@@ -11222,12 +11419,18 @@ const tokenCommand = {
11222
11419
  name: "source",
11223
11420
  short: "s",
11224
11421
  description: "Show token source and user info"
11422
+ },
11423
+ {
11424
+ name: "json",
11425
+ short: "j",
11426
+ description: 'Output as JSON: {"token": "...", "type": "..."}'
11225
11427
  }
11226
11428
  ],
11227
11429
  handler: async (args) => {
11228
11430
  const hostnameOpt = args.options["hostname"] ?? args.options["H"];
11229
11431
  const hostname = (typeof hostnameOpt === "string" ? hostnameOpt : void 0) || "github.com";
11230
11432
  const showSource = Boolean(args.options["source"] || args.options["s"]);
11433
+ const jsonOutput = Boolean(args.options["json"] || args.options["j"]);
11231
11434
  const typeOpt = args.options["type"] ?? args.options["t"];
11232
11435
  const typeArg = (typeof typeOpt === "string" ? typeOpt : void 0) || "auto";
11233
11436
  let tokenSource;
@@ -11246,6 +11449,11 @@ const tokenCommand = {
11246
11449
  tokenSource = "auto";
11247
11450
  break;
11248
11451
  default:
11452
+ if (jsonOutput) {
11453
+ console.log(JSON.stringify({ token: null, type: "none" }));
11454
+ process.exitCode = 1;
11455
+ return;
11456
+ }
11249
11457
  console.log();
11250
11458
  console.log(` ${c("red", "✗")} Invalid token type: ${typeArg}`);
11251
11459
  console.log(` ${dim("Valid options:")} octocode, gh, auto`);
@@ -11254,6 +11462,17 @@ const tokenCommand = {
11254
11462
  return;
11255
11463
  }
11256
11464
  const result = await getToken(hostname, tokenSource);
11465
+ if (jsonOutput) {
11466
+ const output = {
11467
+ token: result.token,
11468
+ type: getTokenType(result.source, result.envSource)
11469
+ };
11470
+ console.log(JSON.stringify(output));
11471
+ if (!result.token) {
11472
+ process.exitCode = 1;
11473
+ }
11474
+ return;
11475
+ }
11257
11476
  if (!result.token) {
11258
11477
  console.log();
11259
11478
  if (tokenSource === "octocode") {
@@ -11569,7 +11788,7 @@ function showCommandHelp(command) {
11569
11788
  }
11570
11789
  }
11571
11790
  function showVersion() {
11572
- const version = "1.1.3";
11791
+ const version = "1.2.2";
11573
11792
  console.log(`octocode v${version}`);
11574
11793
  }
11575
11794
  async function runCLI(argv) {