chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianAppendActiveParams, ObsidianAppendActiveResponse } from './types'
export const appendActiveTool: ToolConfig<
ObsidianAppendActiveParams,
ObsidianAppendActiveResponse
> = {
id: 'obsidian_append_active',
name: 'Obsidian Append to Active File',
description: 'Append content to the currently active file in Obsidian',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Markdown content to append to the active file',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/active/`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'text/markdown',
}),
body: (params) => params.content,
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to append to active file: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
appended: true,
},
}
},
outputs: {
appended: {
type: 'boolean',
description: 'Whether content was successfully appended',
},
},
}
+74
View File
@@ -0,0 +1,74 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianAppendNoteParams, ObsidianAppendNoteResponse } from './types'
export const appendNoteTool: ToolConfig<ObsidianAppendNoteParams, ObsidianAppendNoteResponse> = {
id: 'obsidian_append_note',
name: 'Obsidian Append to Note',
description: 'Append content to an existing note in your Obsidian vault',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
filename: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the note relative to vault root (e.g. "folder/note.md")',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Markdown content to append to the note',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'text/markdown',
}),
body: (params) => params.content,
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to append to note: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
filename: params?.filename ?? '',
appended: true,
},
}
},
outputs: {
filename: {
type: 'string',
description: 'Path of the note',
},
appended: {
type: 'boolean',
description: 'Whether content was successfully appended',
},
},
}
@@ -0,0 +1,78 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianAppendPeriodicNoteParams, ObsidianAppendPeriodicNoteResponse } from './types'
export const appendPeriodicNoteTool: ToolConfig<
ObsidianAppendPeriodicNoteParams,
ObsidianAppendPeriodicNoteResponse
> = {
id: 'obsidian_append_periodic_note',
name: 'Obsidian Append to Periodic Note',
description:
'Append content to the current periodic note (daily, weekly, monthly, quarterly, or yearly). Creates the note if it does not exist.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
period: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Period type: daily, weekly, monthly, quarterly, or yearly',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Markdown content to append to the periodic note',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/periodic/${encodeURIComponent(params.period)}/`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'text/markdown',
}),
body: (params) => params.content,
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to append to periodic note: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
period: params?.period ?? '',
appended: true,
},
}
},
outputs: {
period: {
type: 'string',
description: 'Period type of the note',
},
appended: {
type: 'boolean',
description: 'Whether content was successfully appended',
},
},
}
+74
View File
@@ -0,0 +1,74 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianCreateNoteParams, ObsidianCreateNoteResponse } from './types'
export const createNoteTool: ToolConfig<ObsidianCreateNoteParams, ObsidianCreateNoteResponse> = {
id: 'obsidian_create_note',
name: 'Obsidian Create Note',
description: 'Create or replace a note in your Obsidian vault',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
filename: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path for the note relative to vault root (e.g. "folder/note.md")',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Markdown content for the note',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'text/markdown',
}),
body: (params) => params.content,
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to create note: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
filename: params?.filename ?? '',
created: true,
},
}
},
outputs: {
filename: {
type: 'string',
description: 'Path of the created note',
},
created: {
type: 'boolean',
description: 'Whether the note was successfully created',
},
},
}
+66
View File
@@ -0,0 +1,66 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianDeleteNoteParams, ObsidianDeleteNoteResponse } from './types'
export const deleteNoteTool: ToolConfig<ObsidianDeleteNoteParams, ObsidianDeleteNoteResponse> = {
id: 'obsidian_delete_note',
name: 'Obsidian Delete Note',
description: 'Delete a note from your Obsidian vault',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
filename: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the note to delete relative to vault root',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to delete note: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
filename: params?.filename ?? '',
deleted: true,
},
}
},
outputs: {
filename: {
type: 'string',
description: 'Path of the deleted note',
},
deleted: {
type: 'boolean',
description: 'Whether the note was successfully deleted',
},
},
}
@@ -0,0 +1,70 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianExecuteCommandParams, ObsidianExecuteCommandResponse } from './types'
export const executeCommandTool: ToolConfig<
ObsidianExecuteCommandParams,
ObsidianExecuteCommandResponse
> = {
id: 'obsidian_execute_command',
name: 'Obsidian Execute Command',
description: 'Execute a command in Obsidian (e.g. open daily note, toggle sidebar)',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
commandId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'ID of the command to execute (use List Commands operation to discover available commands)',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/commands/${encodeURIComponent(params.commandId.trim())}/`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to execute command: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
commandId: params?.commandId ?? '',
executed: true,
},
}
},
outputs: {
commandId: {
type: 'string',
description: 'ID of the executed command',
},
executed: {
type: 'boolean',
description: 'Whether the command was successfully executed',
},
},
}
+59
View File
@@ -0,0 +1,59 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianGetActiveParams, ObsidianGetActiveResponse } from './types'
export const getActiveTool: ToolConfig<ObsidianGetActiveParams, ObsidianGetActiveResponse> = {
id: 'obsidian_get_active',
name: 'Obsidian Get Active File',
description: 'Retrieve the content of the currently active file in Obsidian',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/active/`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/vnd.olrapi.note+json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
content: data.content ?? '',
filename: data.path ?? null,
},
}
},
outputs: {
content: {
type: 'string',
description: 'Markdown content of the active file',
},
filename: {
type: 'string',
description: 'Path to the active file',
optional: true,
},
},
}
+68
View File
@@ -0,0 +1,68 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianGetNoteParams, ObsidianGetNoteResponse } from './types'
export const getNoteTool: ToolConfig<ObsidianGetNoteParams, ObsidianGetNoteResponse> = {
id: 'obsidian_get_note',
name: 'Obsidian Get Note',
description: 'Retrieve the content of a note from your Obsidian vault',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
filename: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the note relative to vault root (e.g. "folder/note.md")',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'text/markdown',
}),
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to get note: ${error.message ?? response.statusText}`)
}
const content = await response.text()
return {
success: true,
output: {
content,
filename: params?.filename ?? '',
},
}
},
outputs: {
content: {
type: 'string',
description: 'Markdown content of the note',
},
filename: {
type: 'string',
description: 'Path to the note',
},
},
}
@@ -0,0 +1,67 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianGetPeriodicNoteParams, ObsidianGetPeriodicNoteResponse } from './types'
export const getPeriodicNoteTool: ToolConfig<
ObsidianGetPeriodicNoteParams,
ObsidianGetPeriodicNoteResponse
> = {
id: 'obsidian_get_periodic_note',
name: 'Obsidian Get Periodic Note',
description: 'Retrieve the current periodic note (daily, weekly, monthly, quarterly, or yearly)',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
period: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Period type: daily, weekly, monthly, quarterly, or yearly',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/periodic/${encodeURIComponent(params.period)}/`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'text/markdown',
}),
},
transformResponse: async (response, params) => {
const content = await response.text()
return {
success: true,
output: {
content,
period: params?.period ?? '',
},
}
},
outputs: {
content: {
type: 'string',
description: 'Markdown content of the periodic note',
},
period: {
type: 'string',
description: 'Period type of the note',
},
},
}
+16
View File
@@ -0,0 +1,16 @@
export { appendActiveTool as obsidianAppendActiveTool } from './append_active'
export { appendNoteTool as obsidianAppendNoteTool } from './append_note'
export { appendPeriodicNoteTool as obsidianAppendPeriodicNoteTool } from './append_periodic_note'
export { createNoteTool as obsidianCreateNoteTool } from './create_note'
export { deleteNoteTool as obsidianDeleteNoteTool } from './delete_note'
export { executeCommandTool as obsidianExecuteCommandTool } from './execute_command'
export { getActiveTool as obsidianGetActiveTool } from './get_active'
export { getNoteTool as obsidianGetNoteTool } from './get_note'
export { getPeriodicNoteTool as obsidianGetPeriodicNoteTool } from './get_periodic_note'
export { listCommandsTool as obsidianListCommandsTool } from './list_commands'
export { listFilesTool as obsidianListFilesTool } from './list_files'
export { openFileTool as obsidianOpenFileTool } from './open_file'
export { patchActiveTool as obsidianPatchActiveTool } from './patch_active'
export { patchNoteTool as obsidianPatchNoteTool } from './patch_note'
export { searchTool as obsidianSearchTool } from './search'
export * from './types'
+68
View File
@@ -0,0 +1,68 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianListCommandsParams, ObsidianListCommandsResponse } from './types'
export const listCommandsTool: ToolConfig<
ObsidianListCommandsParams,
ObsidianListCommandsResponse
> = {
id: 'obsidian_list_commands',
name: 'Obsidian List Commands',
description: 'List all available commands in Obsidian',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/commands/`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to list commands: ${error.message ?? response.statusText}`)
}
const data = await response.json()
return {
success: true,
output: {
commands:
data.commands?.map((cmd: { id: string; name: string }) => ({
id: cmd.id ?? '',
name: cmd.name ?? '',
})) ?? [],
},
}
},
outputs: {
commands: {
type: 'json',
description: 'List of available commands with IDs and names',
properties: {
id: { type: 'string', description: 'Command identifier' },
name: { type: 'string', description: 'Human-readable command name' },
},
},
},
}
+76
View File
@@ -0,0 +1,76 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianListFilesParams, ObsidianListFilesResponse } from './types'
export const listFilesTool: ToolConfig<ObsidianListFilesParams, ObsidianListFilesResponse> = {
id: 'obsidian_list_files',
name: 'Obsidian List Files',
description: 'List files and directories in your Obsidian vault',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
path: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Directory path relative to vault root. Leave empty to list root.',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
const path = params.path
? `/${params.path.trim().split('/').map(encodeURIComponent).join('/')}/`
: '/'
return `${base}/vault${path}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to list files: ${error.message ?? response.statusText}`)
}
const data = await response.json()
return {
success: true,
output: {
files:
data.files?.map((f: string | { path: string; type: string }) => {
if (typeof f === 'string') {
return { path: f, type: f.endsWith('/') ? 'directory' : 'file' }
}
return { path: f.path ?? '', type: f.type ?? 'file' }
}) ?? [],
},
}
},
outputs: {
files: {
type: 'json',
description: 'List of files and directories',
properties: {
path: { type: 'string', description: 'File or directory path' },
type: { type: 'string', description: 'Whether the entry is a file or directory' },
},
},
},
}
+73
View File
@@ -0,0 +1,73 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianOpenFileParams, ObsidianOpenFileResponse } from './types'
export const openFileTool: ToolConfig<ObsidianOpenFileParams, ObsidianOpenFileResponse> = {
id: 'obsidian_open_file',
name: 'Obsidian Open File',
description: 'Open a file in the Obsidian UI (creates the file if it does not exist)',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
filename: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the file relative to vault root',
},
newLeaf: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to open the file in a new leaf/tab',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
const leafParam = params.newLeaf ? '?newLeaf=true' : ''
return `${base}/open/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}${leafParam}`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to open file: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
filename: params?.filename ?? '',
opened: true,
},
}
},
outputs: {
filename: {
type: 'string',
description: 'Path of the opened file',
},
opened: {
type: 'boolean',
description: 'Whether the file was successfully opened',
},
},
}
+107
View File
@@ -0,0 +1,107 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianPatchActiveParams, ObsidianPatchActiveResponse } from './types'
export const patchActiveTool: ToolConfig<ObsidianPatchActiveParams, ObsidianPatchActiveResponse> = {
id: 'obsidian_patch_active',
name: 'Obsidian Patch Active File',
description:
'Insert or replace content at a specific heading, block reference, or frontmatter field in the active file',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Content to insert at the target location',
},
operation: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'How to insert content: append, prepend, or replace',
},
targetType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Type of target: heading, block, or frontmatter',
},
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Target identifier (heading text, block reference ID, or frontmatter field name)',
},
targetDelimiter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Delimiter for nested headings (default: "::")',
},
trimTargetWhitespace: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to trim whitespace from target before matching (default: false)',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/active/`
},
method: 'PATCH',
headers: (params) => {
const headers: Record<string, string> = {
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'text/markdown',
Operation: params.operation,
'Target-Type': params.targetType,
Target: encodeURIComponent(params.target),
}
if (params.targetDelimiter) {
headers['Target-Delimiter'] = params.targetDelimiter
}
if (params.trimTargetWhitespace) {
headers['Trim-Target-Whitespace'] = 'true'
}
return headers
},
body: (params) => params.content,
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to patch active file: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
patched: true,
},
}
},
outputs: {
patched: {
type: 'boolean',
description: 'Whether the active file was successfully patched',
},
},
}
+118
View File
@@ -0,0 +1,118 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianPatchNoteParams, ObsidianPatchNoteResponse } from './types'
export const patchNoteTool: ToolConfig<ObsidianPatchNoteParams, ObsidianPatchNoteResponse> = {
id: 'obsidian_patch_note',
name: 'Obsidian Patch Note',
description:
'Insert or replace content at a specific heading, block reference, or frontmatter field in a note',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
filename: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the note relative to vault root (e.g. "folder/note.md")',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Content to insert at the target location',
},
operation: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'How to insert content: append, prepend, or replace',
},
targetType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Type of target: heading, block, or frontmatter',
},
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Target identifier (heading text, block reference ID, or frontmatter field name)',
},
targetDelimiter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Delimiter for nested headings (default: "::")',
},
trimTargetWhitespace: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to trim whitespace from target before matching (default: false)',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
},
method: 'PATCH',
headers: (params) => {
const headers: Record<string, string> = {
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'text/markdown',
Operation: params.operation,
'Target-Type': params.targetType,
Target: encodeURIComponent(params.target),
}
if (params.targetDelimiter) {
headers['Target-Delimiter'] = params.targetDelimiter
}
if (params.trimTargetWhitespace) {
headers['Trim-Target-Whitespace'] = 'true'
}
return headers
},
body: (params) => params.content,
},
transformResponse: async (response, params) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Failed to patch note: ${error.message ?? response.statusText}`)
}
return {
success: true,
output: {
filename: params?.filename ?? '',
patched: true,
},
}
},
outputs: {
filename: {
type: 'string',
description: 'Path of the patched note',
},
patched: {
type: 'boolean',
description: 'Whether the note was successfully patched',
},
},
}
+95
View File
@@ -0,0 +1,95 @@
import type { ToolConfig } from '@/tools/types'
import type { ObsidianSearchParams, ObsidianSearchResponse } from './types'
export const searchTool: ToolConfig<ObsidianSearchParams, ObsidianSearchResponse> = {
id: 'obsidian_search',
name: 'Obsidian Search',
description: 'Search for text across notes in your Obsidian vault',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'API key from Obsidian Local REST API plugin settings',
},
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL for the Obsidian Local REST API',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Text to search for across vault notes',
},
contextLength: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of characters of context around each match (default: 100)',
},
},
request: {
url: (params) => {
const base = params.baseUrl.replace(/\/$/, '')
const contextParam = params.contextLength ? `&contextLength=${params.contextLength}` : ''
return `${base}/search/simple/?query=${encodeURIComponent(params.query)}${contextParam}`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(`Search failed: ${error.message ?? response.statusText}`)
}
const data = await response.json()
return {
success: true,
output: {
results:
data?.map(
(item: {
filename: string
score: number
matches: Array<{ match: { start: number; end: number }; context: string }>
}) => ({
filename: item.filename ?? '',
score: item.score ?? 0,
matches:
item.matches?.map((m: { context: string }) => ({
context: m.context ?? '',
})) ?? [],
})
) ?? [],
},
}
},
outputs: {
results: {
type: 'json',
description: 'Search results with filenames, scores, and matching contexts',
properties: {
filename: { type: 'string', description: 'Path to the matching note' },
score: { type: 'number', description: 'Relevance score' },
matches: {
type: 'json',
description: 'Matching text contexts',
properties: {
context: { type: 'string', description: 'Text surrounding the match' },
},
},
},
},
},
}
+190
View File
@@ -0,0 +1,190 @@
import type { ToolResponse } from '@/tools/types'
interface ObsidianBaseParams {
apiKey: string
baseUrl: string
}
export interface ObsidianListFilesParams extends ObsidianBaseParams {
path?: string
}
export interface ObsidianListFilesResponse extends ToolResponse {
output: {
files: Array<{
path: string
type: string
}>
}
}
export interface ObsidianGetNoteParams extends ObsidianBaseParams {
filename: string
}
export interface ObsidianGetNoteResponse extends ToolResponse {
output: {
content: string
filename: string
}
}
export interface ObsidianCreateNoteParams extends ObsidianBaseParams {
filename: string
content: string
}
export interface ObsidianCreateNoteResponse extends ToolResponse {
output: {
filename: string
created: boolean
}
}
export interface ObsidianAppendNoteParams extends ObsidianBaseParams {
filename: string
content: string
}
export interface ObsidianAppendNoteResponse extends ToolResponse {
output: {
filename: string
appended: boolean
}
}
export interface ObsidianPatchNoteParams extends ObsidianBaseParams {
filename: string
content: string
operation: string
targetType: string
target: string
targetDelimiter?: string
trimTargetWhitespace?: boolean
}
export interface ObsidianPatchNoteResponse extends ToolResponse {
output: {
filename: string
patched: boolean
}
}
export interface ObsidianDeleteNoteParams extends ObsidianBaseParams {
filename: string
}
export interface ObsidianDeleteNoteResponse extends ToolResponse {
output: {
filename: string
deleted: boolean
}
}
export interface ObsidianSearchParams extends ObsidianBaseParams {
query: string
contextLength?: number
}
export interface ObsidianSearchResponse extends ToolResponse {
output: {
results: Array<{
filename: string
score: number
matches: Array<{
context: string
}>
}>
}
}
export interface ObsidianGetActiveParams extends ObsidianBaseParams {}
export interface ObsidianGetActiveResponse extends ToolResponse {
output: {
content: string
filename: string | null
}
}
export interface ObsidianAppendActiveParams extends ObsidianBaseParams {
content: string
}
export interface ObsidianAppendActiveResponse extends ToolResponse {
output: {
appended: boolean
}
}
export interface ObsidianPatchActiveParams extends ObsidianBaseParams {
content: string
operation: string
targetType: string
target: string
targetDelimiter?: string
trimTargetWhitespace?: boolean
}
export interface ObsidianPatchActiveResponse extends ToolResponse {
output: {
patched: boolean
}
}
export interface ObsidianListCommandsParams extends ObsidianBaseParams {}
export interface ObsidianListCommandsResponse extends ToolResponse {
output: {
commands: Array<{
id: string
name: string
}>
}
}
export interface ObsidianExecuteCommandParams extends ObsidianBaseParams {
commandId: string
}
export interface ObsidianExecuteCommandResponse extends ToolResponse {
output: {
commandId: string
executed: boolean
}
}
export interface ObsidianOpenFileParams extends ObsidianBaseParams {
filename: string
newLeaf?: boolean
}
export interface ObsidianOpenFileResponse extends ToolResponse {
output: {
filename: string
opened: boolean
}
}
export interface ObsidianGetPeriodicNoteParams extends ObsidianBaseParams {
period: string
}
export interface ObsidianGetPeriodicNoteResponse extends ToolResponse {
output: {
content: string
period: string
}
}
export interface ObsidianAppendPeriodicNoteParams extends ObsidianBaseParams {
period: string
content: string
}
export interface ObsidianAppendPeriodicNoteResponse extends ToolResponse {
output: {
period: string
appended: boolean
}
}