Plugin settings + API client

This commit is contained in:
Sreeram Sreedhar 2026-05-21 00:56:35 -04:00
parent 908f0b6bb9
commit 346670825c
2 changed files with 189 additions and 14 deletions

View file

@ -0,0 +1,148 @@
import { Notice, requestUrl } from "obsidian"
import type { SupermemorySettings } from "./types"
export interface PushResponse {
accepted: boolean
deletedCount: number
queuedCount: number
}
export interface ConnectionResponse {
id: string
}
export interface NotePayload {
path: string
content: string
title?: string
mtime?: number
frontmatter?: Record<string, unknown>
}
class SupermemoryAPIError extends Error {
constructor(
message: string,
public status?: number,
) {
super(message)
this.name = "SupermemoryAPIError"
}
}
class AuthenticationError extends Error {
constructor(message: string) {
super(message)
this.name = "AuthenticationError"
}
}
let _settings: SupermemorySettings | null = null
export function configure(settings: SupermemorySettings) {
_settings = settings
}
function getBaseUrl(): string {
if (!_settings) throw new Error("API not configured")
return _settings.apiBaseUrl.replace(/\/+$/, "")
}
function getApiKey(): string {
if (!_settings?.apiKey) throw new Error("API key not set")
return _settings.apiKey
}
async function makeAuthenticatedRequest<T>(
endpoint: string,
options: { method?: string; body?: unknown } = {},
): Promise<T> {
const apiKey = getApiKey()
const url = `${getBaseUrl()}${endpoint}`
try {
const response = await requestUrl({
url,
method: options.method ?? "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: options.body ? JSON.stringify(options.body) : undefined,
})
if (response.status === 401) {
throw new AuthenticationError(
"Invalid API key. Check your key in plugin settings.",
)
}
if (response.status === 429) {
throw new SupermemoryAPIError("Rate limited. Try again in a moment.", 429)
}
if (response.status >= 400) {
let errorMessage = `API request failed: ${response.status}`
try {
const errorBody = response.json as { error?: string }
if (errorBody.error) errorMessage = errorBody.error
} catch {}
throw new SupermemoryAPIError(errorMessage, response.status)
}
return response.json as T
} catch (err) {
if (err instanceof AuthenticationError) {
new Notice("Supermemory: invalid API key. Check plugin settings.")
throw err
}
if (err instanceof SupermemoryAPIError) {
if (err.status === 429) {
new Notice("Supermemory: rate limited. Try again in a moment.")
}
throw err
}
throw new SupermemoryAPIError(
`Network error: ${err instanceof Error ? err.message : "Unknown error"}`,
)
}
}
export async function createConnection(
vaultId: string,
vaultName: string,
containerTag: string,
): Promise<ConnectionResponse> {
return makeAuthenticatedRequest<ConnectionResponse>(
"/v3/connections/obsidian",
{
method: "POST",
body: { metadata: { vaultId, vaultName }, containerTag },
},
)
}
export async function pushNotes(
connectionId: string,
notes: NotePayload[],
): Promise<PushResponse> {
return makeAuthenticatedRequest<PushResponse>(
"/v3/connections/obsidian/push",
{
method: "POST",
body: { connectionId, notes },
},
)
}
export async function pushDeletions(
connectionId: string,
deletions: Array<{ path: string }>,
): Promise<PushResponse> {
return makeAuthenticatedRequest<PushResponse>(
"/v3/connections/obsidian/push",
{
method: "POST",
body: { connectionId, deletions },
},
)
}

View file

@ -1,4 +1,5 @@
import { Notice, Plugin } from "obsidian"
import { configure, createConnection } from "./api"
import { SupermemorySettingTab } from "./settings"
import type { SupermemorySettings } from "./types"
@ -17,32 +18,57 @@ export default class SupermemoryPlugin extends Plugin {
async onload() {
await this.loadSettings()
configure(this.settings)
this.addSettingTab(new SupermemorySettingTab(this.app, this))
this.addRibbonIcon("cloud-upload", "Sync vault to Supermemory", () => {
if (!this.settings.apiKey) {
new Notice("Set your Supermemory API key in plugin settings first.")
return
}
new Notice("Supermemory sync starting...")
this.syncVault()
})
this.addCommand({
id: "sync-vault",
name: "Sync vault now",
callback: () => {
if (!this.settings.apiKey) {
new Notice(
"Set your Supermemory API key in plugin settings first.",
)
return
}
new Notice("Supermemory sync starting...")
},
callback: () => this.syncVault(),
})
}
async ensureConnection(): Promise<string | null> {
if (!this.settings.apiKey) {
new Notice("Set your Supermemory API key in plugin settings first.")
return null
}
if (this.settings.connectionId) {
return this.settings.connectionId
}
const vaultId = (this.app as unknown as { appId: string }).appId
const vaultName = this.settings.vaultName || this.app.vault.getName()
try {
const result = await createConnection(
vaultId,
vaultName,
this.settings.containerTag,
)
this.settings.connectionId = result.id
await this.saveSettings()
return result.id
} catch (e) {
new Notice(
`Supermemory: failed to connect — ${e instanceof Error ? e.message : "unknown error"}`,
)
return null
}
}
private async syncVault() {
const connectionId = await this.ensureConnection()
if (!connectionId) return
new Notice("Supermemory: sync starting...")
}
async loadSettings() {
this.settings = Object.assign(
{},
@ -53,5 +79,6 @@ export default class SupermemoryPlugin extends Plugin {
async saveSettings() {
await this.saveData(this.settings)
configure(this.settings)
}
}