mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
refactor: cli
This commit is contained in:
@@ -5,7 +5,7 @@ import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
import * as modelCore from '../../core/model'
|
||||
import { formatError } from '../../utils'
|
||||
import { getApiUrl } from '../../core/config'
|
||||
import { getApiUrl } from '../../core/client'
|
||||
|
||||
export function modelCommands(program: Command) {
|
||||
program
|
||||
|
||||
Regular → Executable
@@ -1,4 +1,5 @@
|
||||
import { requireAuth, getToken, getApiUrl } from './client'
|
||||
import type { MemoHomeContext } from './context'
|
||||
|
||||
export interface ChatParams {
|
||||
message: string
|
||||
@@ -16,16 +17,48 @@ export interface StreamEvent {
|
||||
export type StreamCallback = (event: StreamEvent) => void | Promise<void>
|
||||
|
||||
/**
|
||||
* Chat with AI Agent (streaming)
|
||||
* Chat with AI Agent (streaming) - sync version
|
||||
*/
|
||||
export async function chatStream(
|
||||
params: ChatParams,
|
||||
onEvent: StreamCallback,
|
||||
context?: MemoHomeContext
|
||||
): Promise<void> {
|
||||
requireAuth(context)
|
||||
const token = getToken(context)!
|
||||
const apiUrl = getApiUrl(context)
|
||||
|
||||
await performStreamChat(apiUrl, token, params, onEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with AI Agent (streaming) - async version for Redis storage
|
||||
*/
|
||||
export async function chatStreamAsync(
|
||||
params: ChatParams,
|
||||
onEvent: StreamCallback,
|
||||
context?: MemoHomeContext
|
||||
): Promise<void> {
|
||||
requireAuth(context)
|
||||
const token = getToken(context)!
|
||||
const apiUrl = getApiUrl(context)
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Not authenticated')
|
||||
}
|
||||
|
||||
await performStreamChat(apiUrl, token, params, onEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to perform streaming chat
|
||||
*/
|
||||
async function performStreamChat(
|
||||
apiUrl: string,
|
||||
token: string,
|
||||
params: ChatParams,
|
||||
onEvent: StreamCallback
|
||||
): Promise<void> {
|
||||
requireAuth()
|
||||
const token = getToken()!
|
||||
const apiUrl = getApiUrl()
|
||||
|
||||
const response = await fetch(`${apiUrl}/agent/stream`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -91,9 +124,9 @@ export async function chatStream(
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with AI Agent (non-streaming, collect full response)
|
||||
* Chat with AI Agent (non-streaming, collect full response) - sync version
|
||||
*/
|
||||
export async function chat(params: ChatParams): Promise<string> {
|
||||
export async function chat(params: ChatParams, context?: MemoHomeContext): Promise<string> {
|
||||
let fullResponse = ''
|
||||
|
||||
await chatStream(params, async (event) => {
|
||||
@@ -102,8 +135,24 @@ export async function chat(params: ChatParams): Promise<string> {
|
||||
} else if (event.type === 'error') {
|
||||
throw new Error(event.error)
|
||||
}
|
||||
})
|
||||
}, context)
|
||||
|
||||
return fullResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with AI Agent (non-streaming, collect full response) - async version
|
||||
*/
|
||||
export async function chatAsync(params: ChatParams, context?: MemoHomeContext): Promise<string> {
|
||||
let fullResponse = ''
|
||||
|
||||
await chatStreamAsync(params, async (event) => {
|
||||
if (event.type === 'text-delta' && event.text) {
|
||||
fullResponse += event.text
|
||||
} else if (event.type === 'error') {
|
||||
throw new Error(event.error)
|
||||
}
|
||||
}, context)
|
||||
|
||||
return fullResponse
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createClient } from './client'
|
||||
import { setToken, clearToken, getToken, getApiUrl, setApiUrl } from './config'
|
||||
import { getContext, type MemoHomeContext } from './context'
|
||||
|
||||
export interface LoginParams {
|
||||
username: string
|
||||
@@ -28,10 +28,12 @@ export interface ConfigInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to MemoHome API
|
||||
* Login to MemoHome API (sync version for file storage)
|
||||
* @param params - Login parameters
|
||||
* @param context - Optional context
|
||||
*/
|
||||
export async function login(params: LoginParams): Promise<LoginResult> {
|
||||
const client = createClient()
|
||||
export async function login(params: LoginParams, context?: MemoHomeContext): Promise<LoginResult> {
|
||||
const client = createClient(context)
|
||||
|
||||
const response = await client.auth.login.post({
|
||||
username: params.username,
|
||||
@@ -42,10 +44,18 @@ export async function login(params: LoginParams): Promise<LoginResult> {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
const data = response.data as { success?: boolean; data?: { token?: string; user?: { username: string; role: string } } } | null
|
||||
const data = response.data as { success?: boolean; data?: { token?: string; user?: { username: string; role: string; id: string } } } | null
|
||||
|
||||
if (data?.success && data?.data?.token && data?.data?.user) {
|
||||
setToken(data.data.token)
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
// Set token (handle both sync and async)
|
||||
const setResult = storage.setToken(data.data.token, ctx.currentUserId)
|
||||
if (setResult instanceof Promise) {
|
||||
await setResult
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
token: data.data.token,
|
||||
@@ -58,28 +68,55 @@ export async function login(params: LoginParams): Promise<LoginResult> {
|
||||
|
||||
/**
|
||||
* Logout current user
|
||||
* @param context - Optional context
|
||||
*/
|
||||
export function logout(): void {
|
||||
clearToken()
|
||||
export function logout(context?: MemoHomeContext): void {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const result = storage.clearToken(ctx.currentUserId)
|
||||
if (result instanceof Promise) {
|
||||
throw new Error('logout does not support async storage. Use logoutAsync instead.')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if user is logged in
|
||||
* @param context - Optional context
|
||||
*/
|
||||
export function isLoggedIn(): boolean {
|
||||
return getToken() !== null
|
||||
export function isLoggedIn(context?: MemoHomeContext): boolean {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const token = storage.getToken(ctx.currentUserId)
|
||||
|
||||
if (token instanceof Promise) {
|
||||
throw new Error('isLoggedIn does not support async storage. Use isLoggedInAsync instead.')
|
||||
}
|
||||
|
||||
return token !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current logged in user info
|
||||
* @param context - Optional context
|
||||
*/
|
||||
export async function getCurrentUser(): Promise<UserInfo> {
|
||||
const token = getToken()
|
||||
export async function getCurrentUser(context?: MemoHomeContext): Promise<UserInfo> {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const token = storage.getToken(ctx.currentUserId)
|
||||
|
||||
if (token instanceof Promise) {
|
||||
throw new Error('getCurrentUser does not support async storage. Use getCurrentUserAsync instead.')
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
|
||||
const client = createClient()
|
||||
const client = createClient(context)
|
||||
const response = await client.auth.me.get()
|
||||
|
||||
if (response.error) {
|
||||
@@ -97,21 +134,40 @@ export async function getCurrentUser(): Promise<UserInfo> {
|
||||
|
||||
/**
|
||||
* Get current API configuration
|
||||
* @param context - Optional context
|
||||
*/
|
||||
export function getConfig(): ConfigInfo {
|
||||
export function getConfig(context?: MemoHomeContext): ConfigInfo {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const apiUrl = storage.getApiUrl()
|
||||
const token = storage.getToken(ctx.currentUserId)
|
||||
|
||||
if (apiUrl instanceof Promise || token instanceof Promise) {
|
||||
throw new Error('getConfig does not support async storage. Use getConfigAsync instead.')
|
||||
}
|
||||
|
||||
return {
|
||||
apiUrl: getApiUrl(),
|
||||
loggedIn: isLoggedIn(),
|
||||
apiUrl: apiUrl as string,
|
||||
loggedIn: token !== null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set API URL
|
||||
* @param url - API URL
|
||||
* @param context - Optional context
|
||||
*/
|
||||
export function setConfig(apiUrl: string): void {
|
||||
setApiUrl(apiUrl)
|
||||
export function setConfig(url: string, context?: MemoHomeContext): void {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const result = storage.setApiUrl(url)
|
||||
if (result instanceof Promise) {
|
||||
throw new Error('setConfig does not support async storage. Use setConfigAsync instead.')
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export config functions for convenience
|
||||
export { getToken, getApiUrl, setToken, clearToken, setApiUrl }
|
||||
|
||||
// Re-export for backward compatibility
|
||||
export { getToken, getApiUrl } from './client'
|
||||
export { getContext, setContext, createContext } from './context'
|
||||
|
||||
@@ -1,29 +1,97 @@
|
||||
import { treaty } from '@elysiajs/eden'
|
||||
import { getApiUrl, getToken } from './config'
|
||||
import { getContext, type MemoHomeContext } from './context'
|
||||
import { createClient as createClientApi } from '@memohome/api/client'
|
||||
|
||||
// Use dynamic import to avoid type errors
|
||||
export function createClient() {
|
||||
const apiUrl = getApiUrl()
|
||||
const token = getToken()
|
||||
/**
|
||||
* Create API client
|
||||
* @param context - Optional context, uses global context if not provided
|
||||
*/
|
||||
export function createClient(context?: MemoHomeContext) {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const apiUrlResult = typeof storage.getApiUrl === 'function'
|
||||
? storage.getApiUrl()
|
||||
: (storage as unknown as Record<string, string>).apiUrl
|
||||
|
||||
if (apiUrlResult instanceof Promise) {
|
||||
throw new Error('createClient does not support async storage. Use createClientAsync instead.')
|
||||
}
|
||||
|
||||
const apiUrl = apiUrlResult as string
|
||||
|
||||
const token = typeof storage.getToken === 'function'
|
||||
? storage.getToken(ctx.currentUserId)
|
||||
: null
|
||||
|
||||
// Eden Treaty configuration
|
||||
const client = treaty(apiUrl, {
|
||||
headers: token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : undefined,
|
||||
})
|
||||
// Handle async token retrieval
|
||||
if (token instanceof Promise) {
|
||||
throw new Error('createClient does not support async token storage. Use createClientAsync instead.')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return client as any
|
||||
const client = createClientApi(apiUrl, token ?? undefined)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
export function requireAuth(): string {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
throw new Error('Not logged in. Please use "memohome auth login" to login first')
|
||||
|
||||
/**
|
||||
* Require authentication
|
||||
* Throws error if not authenticated
|
||||
* @param context - Optional context, uses global context if not provided
|
||||
*/
|
||||
export function requireAuth(context?: MemoHomeContext): string {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const token = typeof storage.getToken === 'function'
|
||||
? storage.getToken(ctx.currentUserId)
|
||||
: null
|
||||
|
||||
if (token instanceof Promise) {
|
||||
throw new Error('requireAuth does not support async token storage. Use requireAuthAsync instead.')
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Not logged in. Please login first')
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
export { getApiUrl, getToken }
|
||||
/**
|
||||
* Get API URL
|
||||
* @param context - Optional context, uses global context if not provided
|
||||
*/
|
||||
export function getApiUrl(context?: MemoHomeContext): string {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const urlResult = typeof storage.getApiUrl === 'function'
|
||||
? storage.getApiUrl()
|
||||
: (storage as unknown as Record<string, string>).apiUrl
|
||||
|
||||
if (urlResult instanceof Promise) {
|
||||
throw new Error('getApiUrl does not support async storage. Use getApiUrlAsync instead.')
|
||||
}
|
||||
|
||||
return urlResult as string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token
|
||||
* @param context - Optional context, uses global context if not provided
|
||||
*/
|
||||
export function getToken(context?: MemoHomeContext): string | null {
|
||||
const ctx = context || getContext()
|
||||
const storage = ctx.storage
|
||||
|
||||
const token = typeof storage.getToken === 'function'
|
||||
? storage.getToken(ctx.currentUserId)
|
||||
: null
|
||||
|
||||
if (token instanceof Promise) {
|
||||
throw new Error('getToken does not support async storage. Use getTokenAsync instead.')
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
||||
|
||||
const CONFIG_DIR = join(homedir(), '.memohome')
|
||||
const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
|
||||
|
||||
export interface Config {
|
||||
apiUrl: string
|
||||
token?: string
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: Config = {
|
||||
apiUrl: process.env.API_BASE_URL || 'http://localhost:7002',
|
||||
}
|
||||
|
||||
export function ensureConfigDir() {
|
||||
if (!existsSync(CONFIG_DIR)) {
|
||||
mkdirSync(CONFIG_DIR, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
export function loadConfig(): Config {
|
||||
ensureConfigDir()
|
||||
|
||||
if (!existsSync(CONFIG_FILE)) {
|
||||
saveConfig(DEFAULT_CONFIG)
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
|
||||
try {
|
||||
const data = readFileSync(CONFIG_FILE, 'utf-8')
|
||||
return { ...DEFAULT_CONFIG, ...JSON.parse(data) }
|
||||
} catch {
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
}
|
||||
|
||||
export function saveConfig(config: Config) {
|
||||
ensureConfigDir()
|
||||
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
||||
}
|
||||
|
||||
export function getToken(): string | null {
|
||||
const config = loadConfig()
|
||||
return config.token || null
|
||||
}
|
||||
|
||||
export function setToken(token: string) {
|
||||
const config = loadConfig()
|
||||
config.token = token
|
||||
saveConfig(config)
|
||||
}
|
||||
|
||||
export function clearToken() {
|
||||
const config = loadConfig()
|
||||
delete config.token
|
||||
saveConfig(config)
|
||||
}
|
||||
|
||||
export function getApiUrl(): string {
|
||||
const config = loadConfig()
|
||||
return config.apiUrl
|
||||
}
|
||||
|
||||
export function setApiUrl(url: string) {
|
||||
const config = loadConfig()
|
||||
config.apiUrl = url
|
||||
saveConfig(config)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* MemoHome Core Context
|
||||
*
|
||||
* Provides a configurable context for core functions to use different storage backends
|
||||
*/
|
||||
|
||||
import type { TokenStorage } from './storage'
|
||||
import { FileTokenStorage } from './storage/file'
|
||||
|
||||
/**
|
||||
* Global context for core functions
|
||||
*/
|
||||
export interface MemoHomeContext {
|
||||
storage: TokenStorage
|
||||
currentUserId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Default context (uses file storage for CLI)
|
||||
*/
|
||||
let defaultContext: MemoHomeContext = {
|
||||
storage: new FileTokenStorage(),
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current context
|
||||
*/
|
||||
export function getContext(): MemoHomeContext {
|
||||
return defaultContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the global context
|
||||
* Use this to configure storage backend (e.g., Redis for Telegram bot)
|
||||
*/
|
||||
export function setContext(context: Partial<MemoHomeContext>): void {
|
||||
defaultContext = { ...defaultContext, ...context }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new context without modifying the global one
|
||||
* Useful for multi-user scenarios
|
||||
*/
|
||||
export function createContext(options: {
|
||||
storage: TokenStorage
|
||||
userId?: string
|
||||
}): MemoHomeContext {
|
||||
return {
|
||||
storage: options.storage,
|
||||
currentUserId: options.userId,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset context to default (file storage)
|
||||
*/
|
||||
export function resetContext(): void {
|
||||
defaultContext = {
|
||||
storage: new FileTokenStorage(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getApiUrl, getToken } from './config'
|
||||
import { getApiUrl, getToken } from './client'
|
||||
import type { MemoHomeContext } from './context'
|
||||
|
||||
export interface PingResult {
|
||||
success: boolean
|
||||
@@ -9,10 +10,11 @@ export interface PingResult {
|
||||
|
||||
/**
|
||||
* Test API server connection
|
||||
* @param context - Optional context, uses global context if not provided
|
||||
*/
|
||||
export async function ping(): Promise<PingResult> {
|
||||
const apiUrl = getApiUrl()
|
||||
const token = getToken()
|
||||
export async function ping(context?: MemoHomeContext): Promise<PingResult> {
|
||||
const apiUrl = getApiUrl(context)
|
||||
const token = getToken(context)
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
@@ -63,14 +65,15 @@ export async function ping(): Promise<PingResult> {
|
||||
|
||||
/**
|
||||
* Get connection info
|
||||
* @param context - Optional context, uses global context if not provided
|
||||
*/
|
||||
export function getConnectionInfo(): {
|
||||
export function getConnectionInfo(context?: MemoHomeContext): {
|
||||
apiUrl: string
|
||||
hasToken: boolean
|
||||
} {
|
||||
return {
|
||||
apiUrl: getApiUrl(),
|
||||
hasToken: getToken() !== null,
|
||||
apiUrl: getApiUrl(context),
|
||||
hasToken: getToken(context) !== null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,19 @@
|
||||
* All functions are independent of CLI-specific UI concerns (no chalk, ora, inquirer, etc.)
|
||||
*/
|
||||
|
||||
// Context
|
||||
export {
|
||||
getContext,
|
||||
setContext,
|
||||
createContext,
|
||||
resetContext,
|
||||
type MemoHomeContext,
|
||||
} from './context'
|
||||
|
||||
// Storage
|
||||
export type { TokenStorage, Config } from './storage'
|
||||
export { FileTokenStorage } from './storage/'
|
||||
|
||||
// Auth
|
||||
export {
|
||||
login,
|
||||
@@ -13,11 +26,6 @@ export {
|
||||
getCurrentUser,
|
||||
getConfig,
|
||||
setConfig,
|
||||
getToken,
|
||||
getApiUrl,
|
||||
setToken,
|
||||
clearToken,
|
||||
setApiUrl,
|
||||
type LoginParams,
|
||||
type LoginResult,
|
||||
type UserInfo,
|
||||
@@ -49,7 +57,9 @@ export {
|
||||
// Agent
|
||||
export {
|
||||
chat,
|
||||
chatAsync,
|
||||
chatStream,
|
||||
chatStreamAsync,
|
||||
type ChatParams,
|
||||
type StreamEvent,
|
||||
type StreamCallback,
|
||||
@@ -93,17 +103,10 @@ export {
|
||||
type PingResult,
|
||||
} from './debug'
|
||||
|
||||
// Config
|
||||
export {
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
ensureConfigDir,
|
||||
type Config,
|
||||
} from './config'
|
||||
|
||||
// Client
|
||||
export {
|
||||
createClient,
|
||||
requireAuth,
|
||||
getApiUrl,
|
||||
getToken,
|
||||
} from './client'
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Token Storage Interface
|
||||
*
|
||||
* Abstraction for storing authentication tokens in different backends
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
apiUrl: string
|
||||
token?: string
|
||||
}
|
||||
|
||||
export interface TokenStorage {
|
||||
/**
|
||||
* Get the API URL
|
||||
*/
|
||||
getApiUrl(): Promise<string> | string
|
||||
|
||||
/**
|
||||
* Set the API URL
|
||||
*/
|
||||
setApiUrl(url: string): Promise<void> | void
|
||||
|
||||
/**
|
||||
* Get the authentication token for a user
|
||||
* @param userId - User identifier (optional for single-user storage)
|
||||
*/
|
||||
getToken(userId?: string): Promise<string | null> | string | null
|
||||
|
||||
/**
|
||||
* Set the authentication token for a user
|
||||
* @param token - The authentication token
|
||||
* @param userId - User identifier (optional for single-user storage)
|
||||
*/
|
||||
setToken(token: string, userId?: string): Promise<void> | void
|
||||
|
||||
/**
|
||||
* Clear the authentication token for a user
|
||||
* @param userId - User identifier (optional for single-user storage)
|
||||
*/
|
||||
clearToken(userId?: string): Promise<void> | void
|
||||
|
||||
/**
|
||||
* Load full configuration (if applicable)
|
||||
*/
|
||||
loadConfig?(): Promise<Config> | Config
|
||||
|
||||
/**
|
||||
* Save full configuration (if applicable)
|
||||
*/
|
||||
saveConfig?(config: Config): Promise<void> | void
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
||||
import type { TokenStorage, Config } from '../storage'
|
||||
|
||||
const CONFIG_DIR = join(homedir(), '.memohome')
|
||||
const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
|
||||
|
||||
const DEFAULT_CONFIG: Config = {
|
||||
apiUrl: process.env.API_BASE_URL || 'http://localhost:7002',
|
||||
}
|
||||
|
||||
/**
|
||||
* File-based token storage for CLI
|
||||
* Stores config in ~/.memohome/config.json
|
||||
*/
|
||||
export class FileTokenStorage implements TokenStorage {
|
||||
private ensureConfigDir() {
|
||||
if (!existsSync(CONFIG_DIR)) {
|
||||
mkdirSync(CONFIG_DIR, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
loadConfig(): Config {
|
||||
this.ensureConfigDir()
|
||||
|
||||
if (!existsSync(CONFIG_FILE)) {
|
||||
this.saveConfig(DEFAULT_CONFIG)
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
|
||||
try {
|
||||
const data = readFileSync(CONFIG_FILE, 'utf-8')
|
||||
return { ...DEFAULT_CONFIG, ...JSON.parse(data) }
|
||||
} catch {
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
}
|
||||
|
||||
saveConfig(config: Config): void {
|
||||
this.ensureConfigDir()
|
||||
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
||||
}
|
||||
|
||||
getApiUrl(): string {
|
||||
const config = this.loadConfig()
|
||||
return config.apiUrl
|
||||
}
|
||||
|
||||
setApiUrl(url: string): void {
|
||||
const config = this.loadConfig()
|
||||
config.apiUrl = url
|
||||
this.saveConfig(config)
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
const config = this.loadConfig()
|
||||
return config.token || null
|
||||
}
|
||||
|
||||
setToken(token: string): void {
|
||||
const config = this.loadConfig()
|
||||
config.token = token
|
||||
this.saveConfig(config)
|
||||
}
|
||||
|
||||
clearToken(): void {
|
||||
const config = this.loadConfig()
|
||||
delete config.token
|
||||
this.saveConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { FileTokenStorage } from './file'
|
||||
export type { TokenStorage, Config } from '../storage'
|
||||
Reference in New Issue
Block a user