@@ -4,13 +4,16 @@ import { join } from "node:path";
44import { homedir } from "node:os" ;
55import * as readline from "node:readline" ;
66import { stripJsoncComments } from "./services/jsonc.js" ;
7- import { startAuthFlow , clearCredentials , loadCredentials } from "./services/auth.js" ;
8- import { writeInstallDefaults , CONFIG_FILE } from "./config.js" ;
7+ import { startAuthFlow , clearCredentials , loadCredentials , CREDENTIALS_FILE } from "./services/auth.js" ;
8+ import { CONFIG , CONFIG_FILE , SUPERMEMORY_API_KEY , getApiBaseUrl , isConfigured , writeInstallDefaults } from "./config.js" ;
9+ import { SupermemoryClient } from "./services/client.js" ;
10+ import { getTags } from "./services/tags.js" ;
911
1012const OPENCODE_CONFIG_DIR = join ( homedir ( ) , ".config" , "opencode" ) ;
1113const OPENCODE_COMMAND_DIR = join ( OPENCODE_CONFIG_DIR , "command" ) ;
1214const OH_MY_OPENCODE_CONFIG = join ( OPENCODE_CONFIG_DIR , "oh-my-opencode.json" ) ;
1315const PLUGIN_NAME = "opencode-supermemory@latest" ;
16+ const DEFAULT_CONFIG_FILE = CONFIG_FILE ?? join ( OPENCODE_CONFIG_DIR , "supermemory.json" ) ;
1417
1518const SUPERMEMORY_INIT_COMMAND = `---
1619description: Initialize Supermemory with comprehensive codebase knowledge
@@ -203,6 +206,23 @@ This will remove the saved credentials from ~/.supermemory-opencode/credentials.
203206Inform the user whether logout succeeded and that they'll need to run /supermemory-login to re-authenticate.
204207` ;
205208
209+ const SUPERMEMORY_STATUS_COMMAND = `---
210+ description: Show Supermemory connection status
211+ ---
212+
213+ # Supermemory Status
214+
215+ Run this command to check whether OpenCode is connected to Supermemory:
216+
217+ \`\`\`bash
218+ bunx opencode-supermemory@latest status
219+ \`\`\`
220+
221+ Report the connection status, credential source, API URL, and account information if available.
222+
223+ Never print the full API key.
224+ ` ;
225+
206226function createReadline ( ) : readline . Interface {
207227 return readline . createInterface ( {
208228 input : process . stdin ,
@@ -317,6 +337,10 @@ function createCommands(): boolean {
317337 writeFileSync ( logoutPath , SUPERMEMORY_LOGOUT_COMMAND ) ;
318338 console . log ( `✓ Created /supermemory-logout command` ) ;
319339
340+ const statusPath = join ( OPENCODE_COMMAND_DIR , "supermemory-status.md" ) ;
341+ writeFileSync ( statusPath , SUPERMEMORY_STATUS_COMMAND ) ;
342+ console . log ( `✓ Created /supermemory-status command` ) ;
343+
320344 return true ;
321345}
322346
@@ -377,7 +401,7 @@ interface InstallOptions {
377401async function install ( options : InstallOptions ) : Promise < number > {
378402 console. log ( "\n🧠 opencode-supermemory installer\n" ) ;
379403
380- writeInstallDefaults ( existsSync ( CONFIG_FILE ) ) ;
404+ writeInstallDefaults ( existsSync ( DEFAULT_CONFIG_FILE ) ) ;
381405
382406 const rl = options . tui ? createReadline ( ) : null ;
383407
@@ -410,7 +434,7 @@ async function install(options: InstallOptions): Promise<number> {
410434 }
411435
412436 // Step 2: Create commands
413- console . log ( "\nStep 2: Create /supermemory-init, /supermemory-login, and /supermemory-logout commands" ) ;
437+ console . log ( "\nStep 2: Create /supermemory-init, /supermemory-login, /supermemory-logout, and /supermemory-status commands" ) ;
414438 if ( options . tui ) {
415439 const shouldCreate = await confirm ( rl ! , "Add supermemory commands?" ) ;
416440 if ( ! shouldCreate ) {
@@ -485,12 +509,152 @@ async function login(): Promise<number> {
485509 }
486510}
487511
512+ function maskKey ( key : string | undefined ) : string {
513+ if ( ! key ) return "not set" ;
514+ if ( key . length <= 12 ) return `${ key . slice ( 0 , 4 ) } ...` ;
515+ return `${ key . slice ( 0 , 6 ) } ...${ key . slice ( - 4 ) } ` ;
516+ }
517+
518+ function getConfiguredApiKeyFromFile ( ) : string | undefined {
519+ try {
520+ if ( ! existsSync ( DEFAULT_CONFIG_FILE ) ) return undefined ;
521+ const parsed = JSON . parse ( readFileSync ( DEFAULT_CONFIG_FILE , "utf-8" ) ) as { apiKey ? : string } ;
522+ return parsed . apiKey ;
523+ } catch {
524+ return undefined ;
525+ }
526+ }
527+
528+ function getKeySource ( ) : string {
529+ if ( process . env . SUPERMEMORY_API_KEY ) return "SUPERMEMORY_API_KEY env var" ;
530+ if ( getConfiguredApiKeyFromFile ( ) ) return DEFAULT_CONFIG_FILE ;
531+ if ( loadCredentials ( ) ) return CREDENTIALS_FILE ;
532+ return "not configured" ;
533+ }
534+
535+ function getDevTlsHint ( apiUrl : string ) : string | null {
536+ if ( ! apiUrl . includes ( ".dev.supermemory.ai" ) ) return null ;
537+ if ( process . env . NODE_EXTRA_CA_CERTS ) return null ;
538+ return "Dev API TLS: set NODE_EXTRA_CA_CERTS to your Portless CA before starting OpenCode." ;
539+ }
540+
541+ async function fetchJson ( apiUrl : string , path : string ) : Promise < unknown | null > {
542+ if ( ! SUPERMEMORY_API_KEY ) return null ;
543+ try {
544+ const response = await fetch ( `${ apiUrl } ${ path } ` , {
545+ headers : {
546+ Authorization : `Bearer ${ SUPERMEMORY_API_KEY } ` ,
547+ "x-sm-source" : "opencode" ,
548+ } ,
549+ } ) ;
550+ if ( ! response . ok ) return null ;
551+ return await response . json ( ) ;
552+ } catch {
553+ return null ;
554+ }
555+ }
556+
557+ function findAccountInfo ( value : unknown ) : { email ?: string ; name ?: string ; userId ?: string ; orgName ?: string } {
558+ const seen = new Set < unknown > ( ) ;
559+ const stack = [ value ] ;
560+ const result : { email ?: string ; name ?: string ; userId ?: string ; orgName ?: string } = { } ;
561+
562+ while ( stack . length > 0 ) {
563+ const item = stack . pop ( ) ;
564+ if ( ! item || typeof item !== "object" || seen . has ( item ) ) continue ;
565+ seen . add ( item ) ;
566+
567+ const record = item as Record < string , unknown> ;
568+ for ( const [ key , raw ] of Object . entries ( record ) ) {
569+ const lower = key . toLowerCase ( ) ;
570+ if ( ! result . email && lower === "email" && typeof raw === "string" ) result . email = raw ;
571+ if ( ! result . name && lower === "name" && typeof raw === "string" ) result . name = raw ;
572+ if ( ! result . userId && ( lower === "userid" || lower === "user_id" ) && typeof raw === "string" ) result . userId = raw ;
573+ if ( ! result . orgName && ( lower === "organizationname" || lower === "orgname" ) && typeof raw === "string" ) result . orgName = raw ;
574+
575+ if ( raw && typeof raw === "object" ) stack . push ( raw ) ;
576+ }
577+ }
578+
579+ return result ;
580+ }
581+
582+ async function getAccountInfo ( apiUrl : string ) : Promise < { email ? : string ; name ? : string ; userId ? : string ; orgName ? : string } > {
583+ for ( const path of [ "/v3/auth/account/memberships" , "/v3/account/memberships" , "/v3/me" ] ) {
584+ const data = await fetchJson ( apiUrl , path ) ;
585+ const info = findAccountInfo ( data ) ;
586+ if ( info . email || info . name || info . userId || info . orgName ) return info ;
587+ }
588+ return { } ;
589+ }
590+
591+ async function status ( ) : Promise < number > {
592+ const apiUrl = getApiBaseUrl ( ) ;
593+ const tags = getTags ( process . cwd ( ) ) ;
594+ const lines : string [ ] = [ ] ;
595+
596+ lines . push ( "supermemory status" ) ;
597+ lines . push ( "" ) ;
598+ lines . push ( `Connected: ${ isConfigured ( ) ? "checking..." : "no" } ` ) ;
599+ lines . push ( `API key: ${ maskKey ( SUPERMEMORY_API_KEY ) } (${ getKeySource ( ) } )` ) ;
600+ lines . push ( `API URL: ${ apiUrl } ` ) ;
601+ lines . push ( "Memory scope: current project + user profile" ) ;
602+ lines . push ( `Recall mode: ${ CONFIG . autoRecallEveryPrompt ? "auto-recall on every prompt" : "session/event based" } ` ) ;
603+ lines . push ( `Capture cadence: ${ CONFIG . captureEveryNTurns > 0 ? `every ${ CONFIG . captureEveryNTurns } turn${ CONFIG . captureEveryNTurns === 1 ? "" : "s" } + session end` : "session end only" } ` ) ;
604+ lines . push ( `Project tag: ${ tags . project } ` ) ;
605+ lines . push ( `User tag: ${ tags . user } ` ) ;
606+
607+ if ( ! isConfigured ( ) ) {
608+ lines . push ( "" ) ;
609+ lines . push ( "Run /supermemory-login to connect, or set SUPERMEMORY_API_KEY." ) ;
610+ console . log ( lines . join ( "\n" ) ) ;
611+ return 0 ;
612+ }
613+
614+ const client = new SupermemoryClient ( ) ;
615+ const [ profileResult , accountInfo ] = await Promise . all ( [
616+ client . getProfile ( tags . user ) ,
617+ getAccountInfo ( apiUrl ) ,
618+ ] ) ;
619+
620+ lines [ 2 ] = profileResult . success ? "Connected: yes" : "Connected: no" ;
621+
622+ if ( accountInfo . email || accountInfo . name || accountInfo . userId || accountInfo . orgName ) {
623+ lines . push ( "" ) ;
624+ lines . push ( "Account:" ) ;
625+ if ( accountInfo . email ) lines . push ( `Email: ${ accountInfo . email } ` ) ;
626+ if ( accountInfo . name ) lines . push ( `Name: ${ accountInfo . name } ` ) ;
627+ if ( accountInfo . userId ) lines . push ( `User ID: ${ accountInfo . userId } ` ) ;
628+ if ( accountInfo . orgName ) lines . push ( `Organization: ${ accountInfo . orgName } ` ) ;
629+ } else {
630+ lines . push ( "" ) ;
631+ lines . push ( "Account: authenticated API key (account details unavailable from API key)" ) ;
632+ }
633+
634+ if ( ! profileResult . success ) {
635+ lines . push ( "" ) ;
636+ lines . push ( `Connection check failed: ${ profileResult . error } ` ) ;
637+ const devTlsHint = getDevTlsHint ( apiUrl ) ;
638+ if ( devTlsHint ) lines . push ( devTlsHint ) ;
639+ }
640+
641+ console . log ( lines . join ( "\n" ) ) ;
642+ return 0 ;
643+ }
644+
488645function logout ( ) : number {
489646 if ( clearCredentials ( ) ) {
490647 console . log ( "✓ Logged out. Credentials cleared." ) ;
648+ console . log ( "This only logs out this local OpenCode install. To revoke the account-level OpenCode integration key, disconnect it from the Supermemory integrations page." ) ;
649+ if ( process . env . SUPERMEMORY_API_KEY ) {
650+ console . log ( "SUPERMEMORY_API_KEY is still set in this shell, so memory may remain active until you unset it or restart OpenCode." ) ;
651+ }
491652 return 0 ;
492653 } else {
493654 console . log ( "No credentials found." ) ;
655+ if ( process . env . SUPERMEMORY_API_KEY ) {
656+ console . log ( "SUPERMEMORY_API_KEY is still set in this shell." ) ;
657+ }
494658 return 0 ;
495659 }
496660}
@@ -505,11 +669,13 @@ Commands:
505669 --disable-context-recovery Disable Oh My OpenCode's context hook
506670 login Authenticate with Supermemory (opens browser)
507671 logout Clear stored credentials
672+ status Show Supermemory connection status
508673
509674Examples:
510675 bunx opencode-supermemory@latest install
511676 bunx opencode-supermemory@latest login
512677 bunx opencode-supermemory@latest logout
678+ bunx opencode-supermemory@latest status
513679` ) ;
514680}
515681
@@ -533,6 +699,8 @@ if (args[0] === "install") {
533699 login ( ) . then ( ( code ) => process . exit ( code ) ) ;
534700} else if ( args [ 0 ] === "logout" ) {
535701 process . exit ( logout ( ) ) ;
702+ } else if ( args [ 0 ] === "status" ) {
703+ status ( ) . then ( ( code ) => process . exit ( code ) ) ;
536704} else {
537705 console . error ( `Unknown command: ${ args [ 0 ] } ` ) ;
538706 printHelp ( ) ;
0 commit comments