obsidian-mcp-server 2.0.6 → 3.0.0
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.
- package/CLAUDE.md +364 -0
- package/Dockerfile +99 -0
- package/LICENSE +4 -6
- package/README.md +246 -206
- package/changelog/3.0.x/3.0.0.md +102 -0
- package/changelog/template.md +51 -0
- package/dist/config/server-config.d.ts +19 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +55 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -295
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts +8 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.js +8 -0
- package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +36 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +9 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts +23 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.js +47 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +13 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +30 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts +21 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js +38 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts +45 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.js +66 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +120 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +531 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +38 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +42 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +58 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +66 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +19 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js +43 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +92 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +245 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +13 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js +38 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +273 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +13 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +38 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +68 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +178 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +77 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +172 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +22 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +88 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +81 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +83 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +167 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +76 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +244 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +41 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +76 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -0
- package/dist/services/obsidian/frontmatter-ops.d.ts +34 -0
- package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -0
- package/dist/services/obsidian/frontmatter-ops.js +230 -0
- package/dist/services/obsidian/frontmatter-ops.js.map +1 -0
- package/dist/services/obsidian/obsidian-service.d.ts +73 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -0
- package/dist/services/obsidian/obsidian-service.js +399 -0
- package/dist/services/obsidian/obsidian-service.js.map +1 -0
- package/dist/services/obsidian/section-extractor.d.ts +13 -0
- package/dist/services/obsidian/section-extractor.d.ts.map +1 -0
- package/dist/services/obsidian/section-extractor.js +124 -0
- package/dist/services/obsidian/section-extractor.js.map +1 -0
- package/dist/services/obsidian/types.d.ts +91 -0
- package/dist/services/obsidian/types.d.ts.map +1 -0
- package/dist/services/obsidian/types.js +7 -0
- package/dist/services/obsidian/types.js.map +1 -0
- package/package.json +63 -69
- package/server.json +167 -0
- package/CHANGELOG.md +0 -118
- package/dist/config/index.d.ts +0 -41
- package/dist/config/index.js +0 -191
- package/dist/mcp-server/server.d.ts +0 -33
- package/dist/mcp-server/server.js +0 -211
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.d.ts +0 -51
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.js +0 -168
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.d.ts +0 -19
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.js +0 -91
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +0 -77
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +0 -341
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +0 -18
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +0 -69
- package/dist/mcp-server/tools/obsidianListNotesTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianListNotesTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianListNotesTool/logic.d.ts +0 -68
- package/dist/mcp-server/tools/obsidianListNotesTool/logic.js +0 -215
- package/dist/mcp-server/tools/obsidianListNotesTool/registration.d.ts +0 -23
- package/dist/mcp-server/tools/obsidianListNotesTool/registration.js +0 -98
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +0 -2
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +0 -42
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +0 -152
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +0 -52
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +0 -2
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +0 -28
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +0 -161
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +0 -52
- package/dist/mcp-server/tools/obsidianReadNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianReadNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianReadNoteTool/logic.d.ts +0 -87
- package/dist/mcp-server/tools/obsidianReadNoteTool/logic.js +0 -216
- package/dist/mcp-server/tools/obsidianReadNoteTool/registration.d.ts +0 -20
- package/dist/mcp-server/tools/obsidianReadNoteTool/registration.js +0 -101
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +0 -255
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +0 -583
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +0 -22
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +0 -111
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.d.ts +0 -183
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.js +0 -490
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.d.ts +0 -21
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.js +0 -108
- package/dist/mcp-server/transports/auth/core/authContext.d.ts +0 -33
- package/dist/mcp-server/transports/auth/core/authContext.js +0 -24
- package/dist/mcp-server/transports/auth/core/authTypes.d.ts +0 -17
- package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
- package/dist/mcp-server/transports/auth/core/authUtils.d.ts +0 -18
- package/dist/mcp-server/transports/auth/core/authUtils.js +0 -45
- package/dist/mcp-server/transports/auth/index.d.ts +0 -10
- package/dist/mcp-server/transports/auth/index.js +0 -9
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
- package/dist/mcp-server/transports/httpErrorHandler.d.ts +0 -26
- package/dist/mcp-server/transports/httpErrorHandler.js +0 -73
- package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
- package/dist/mcp-server/transports/httpTransport.js +0 -208
- package/dist/mcp-server/transports/stdioTransport.d.ts +0 -42
- package/dist/mcp-server/transports/stdioTransport.js +0 -63
- package/dist/services/obsidianRestAPI/index.d.ts +0 -15
- package/dist/services/obsidianRestAPI/index.js +0 -17
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +0 -38
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +0 -62
- package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +0 -22
- package/dist/services/obsidianRestAPI/methods/commandMethods.js +0 -31
- package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +0 -16
- package/dist/services/obsidianRestAPI/methods/openMethods.js +0 -21
- package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +0 -37
- package/dist/services/obsidianRestAPI/methods/patchMethods.js +0 -94
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +0 -42
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +0 -66
- package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +0 -25
- package/dist/services/obsidianRestAPI/methods/searchMethods.js +0 -36
- package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +0 -58
- package/dist/services/obsidianRestAPI/methods/vaultMethods.js +0 -144
- package/dist/services/obsidianRestAPI/service.d.ts +0 -195
- package/dist/services/obsidianRestAPI/service.js +0 -379
- package/dist/services/obsidianRestAPI/types.d.ts +0 -127
- package/dist/services/obsidianRestAPI/types.js +0 -7
- package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +0 -4
- package/dist/services/obsidianRestAPI/vaultCache/index.js +0 -4
- package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +0 -88
- package/dist/services/obsidianRestAPI/vaultCache/service.js +0 -299
- package/dist/types-global/errors.d.ts +0 -73
- package/dist/types-global/errors.js +0 -71
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.js +0 -13
- package/dist/utils/internal/asyncUtils.d.ts +0 -54
- package/dist/utils/internal/asyncUtils.js +0 -101
- package/dist/utils/internal/errorHandler.d.ts +0 -176
- package/dist/utils/internal/errorHandler.js +0 -351
- package/dist/utils/internal/index.d.ts +0 -4
- package/dist/utils/internal/index.js +0 -4
- package/dist/utils/internal/logger.d.ts +0 -141
- package/dist/utils/internal/logger.js +0 -406
- package/dist/utils/internal/requestContext.d.ts +0 -83
- package/dist/utils/internal/requestContext.js +0 -72
- package/dist/utils/metrics/index.d.ts +0 -1
- package/dist/utils/metrics/index.js +0 -1
- package/dist/utils/metrics/tokenCounter.d.ts +0 -27
- package/dist/utils/metrics/tokenCounter.js +0 -128
- package/dist/utils/obsidian/index.d.ts +0 -5
- package/dist/utils/obsidian/index.js +0 -5
- package/dist/utils/obsidian/obsidianApiUtils.d.ts +0 -14
- package/dist/utils/obsidian/obsidianApiUtils.js +0 -29
- package/dist/utils/obsidian/obsidianStatUtils.d.ts +0 -68
- package/dist/utils/obsidian/obsidianStatUtils.js +0 -143
- package/dist/utils/parsing/dateParser.d.ts +0 -56
- package/dist/utils/parsing/dateParser.js +0 -104
- package/dist/utils/parsing/index.d.ts +0 -2
- package/dist/utils/parsing/index.js +0 -3
- package/dist/utils/parsing/jsonParser.d.ts +0 -80
- package/dist/utils/parsing/jsonParser.js +0 -133
- package/dist/utils/security/idGenerator.d.ts +0 -140
- package/dist/utils/security/idGenerator.js +0 -194
- package/dist/utils/security/index.d.ts +0 -3
- package/dist/utils/security/index.js +0 -3
- package/dist/utils/security/rateLimiter.d.ts +0 -156
- package/dist/utils/security/rateLimiter.js +0 -235
- package/dist/utils/security/sanitization.d.ts +0 -244
- package/dist/utils/security/sanitization.js +0 -599
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview obsidian_search_notes — text/dataview/jsonlogic search.
|
|
3
|
+
* Caps results at 100 hits and surfaces an `excluded` indicator when more
|
|
4
|
+
* were returned upstream. Text-mode hits are additionally capped per file
|
|
5
|
+
* via `maxMatchesPerHit` so a single match-heavy note can't blow the
|
|
6
|
+
* response budget — clipped hits carry `truncated: true` and `totalMatches`.
|
|
7
|
+
* @module mcp-server/tools/definitions/obsidian-search-notes.tool
|
|
8
|
+
*/
|
|
9
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
10
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
11
|
+
import { getObsidianService } from '../../../services/obsidian/obsidian-service.js';
|
|
12
|
+
const HIT_CAP = 100;
|
|
13
|
+
const DEFAULT_MATCHES_PER_HIT = 10;
|
|
14
|
+
const EXCLUSION_HINT = 'Narrow your query (add filters or more specific terms) to surface the rest.';
|
|
15
|
+
const TextHitSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
filename: z.string().describe('Vault-relative path of the matching note.'),
|
|
18
|
+
score: z.number().optional().describe('Relevance score from the search index, when available.'),
|
|
19
|
+
matches: z
|
|
20
|
+
.array(z
|
|
21
|
+
.object({
|
|
22
|
+
context: z.string().describe('Surrounding text around the match.'),
|
|
23
|
+
match: z
|
|
24
|
+
.object({
|
|
25
|
+
start: z.number().describe('Match start offset in the surrounding context.'),
|
|
26
|
+
end: z.number().describe('Match end offset in the surrounding context.'),
|
|
27
|
+
})
|
|
28
|
+
.describe('Match offsets within the context window.'),
|
|
29
|
+
})
|
|
30
|
+
.describe('A single match within a file.'))
|
|
31
|
+
.describe('Per-match context windows. Capped per file by `maxMatchesPerHit`.'),
|
|
32
|
+
totalMatches: z
|
|
33
|
+
.number()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Total matches in this file. Present only when `matches` was clipped to `maxMatchesPerHit`.'),
|
|
36
|
+
truncated: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('True when `matches` was clipped to `maxMatchesPerHit`. Use `obsidian_get_note` to read the full file when more context is needed.'),
|
|
40
|
+
})
|
|
41
|
+
.describe('A file with one or more text-search matches.');
|
|
42
|
+
const StructuredHitSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
filename: z.string().describe('Vault-relative path of the matching note.'),
|
|
45
|
+
result: z.unknown().describe('The query result for this file — shape determined by the query.'),
|
|
46
|
+
})
|
|
47
|
+
.describe('A file with a structured (Dataview/JSONLogic) result value.');
|
|
48
|
+
const ExcludedSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
count: z.number().describe('Number of additional hits the upstream returned beyond the cap.'),
|
|
51
|
+
hint: z.string().describe('Suggestion for narrowing the query.'),
|
|
52
|
+
})
|
|
53
|
+
.optional();
|
|
54
|
+
export const obsidianSearchNotes = tool('obsidian_search_notes', {
|
|
55
|
+
description: `Search the vault. Modes:
|
|
56
|
+
- \`text\`: substring match with context windows. Pass \`query\` as a string and optionally \`pathPrefix\` to filter the returned filenames.
|
|
57
|
+
- \`dataview\`: a Dataview DQL query (\`TABLE …\`). Pass \`query\` as the DQL string. Use this for path/date/metadata filters; \`file.mtime\`, \`file.path\`, etc. are queryable.
|
|
58
|
+
- \`jsonlogic\`: a JSONLogic tree evaluated over each note's NoteJson. Pass \`logic\` as a JSON object. Available \`var\` paths: \`path\` (string), \`content\` (string), \`frontmatter.<key>\` (any), \`tags\` (string[]), \`stat.ctime\` / \`stat.mtime\` / \`stat.size\` (number). Custom operators include \`glob\` and \`regexp\`.
|
|
59
|
+
|
|
60
|
+
Results are capped at ${HIT_CAP} hits; an \`excluded\` indicator reports the overflow. Text-mode hits are additionally clipped to \`maxMatchesPerHit\` matches per file (default ${DEFAULT_MATCHES_PER_HIT}); when clipped, the hit carries \`truncated: true\` and \`totalMatches\`.`,
|
|
61
|
+
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
62
|
+
input: z.object({
|
|
63
|
+
mode: z
|
|
64
|
+
.enum(['text', 'dataview', 'jsonlogic'])
|
|
65
|
+
.describe('Search algorithm and request body shape.'),
|
|
66
|
+
query: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('For `text` mode: substring to match. For `dataview` mode: the DQL query string. Required for those modes.'),
|
|
70
|
+
logic: z
|
|
71
|
+
.record(z.string(), z.unknown())
|
|
72
|
+
.optional()
|
|
73
|
+
.describe('JSONLogic tree. Required and only used when `mode` is `"jsonlogic"`.'),
|
|
74
|
+
contextLength: z
|
|
75
|
+
.number()
|
|
76
|
+
.int()
|
|
77
|
+
.positive()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('Characters of context on each side of the match (text mode only). Default 100.'),
|
|
80
|
+
pathPrefix: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Filter returned filenames by prefix (text mode only, applied client-side).'),
|
|
84
|
+
maxMatchesPerHit: z
|
|
85
|
+
.number()
|
|
86
|
+
.int()
|
|
87
|
+
.positive()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe(`Cap on match contexts returned per file in text mode. When clipped, the hit carries \`truncated: true\` and \`totalMatches\`. Default ${DEFAULT_MATCHES_PER_HIT}.`),
|
|
90
|
+
}),
|
|
91
|
+
output: z.object({
|
|
92
|
+
result: z
|
|
93
|
+
.discriminatedUnion('mode', [
|
|
94
|
+
z
|
|
95
|
+
.object({
|
|
96
|
+
mode: z.literal('text').describe('Echoed mode.'),
|
|
97
|
+
hits: z.array(TextHitSchema).describe('Matching files with per-match context.'),
|
|
98
|
+
excluded: ExcludedSchema.describe('Present when results were truncated to the cap.'),
|
|
99
|
+
})
|
|
100
|
+
.describe('Text-search results.'),
|
|
101
|
+
z
|
|
102
|
+
.object({
|
|
103
|
+
mode: z.literal('dataview').describe('Echoed mode.'),
|
|
104
|
+
hits: z
|
|
105
|
+
.array(StructuredHitSchema)
|
|
106
|
+
.describe('Matching files with the query result per file.'),
|
|
107
|
+
excluded: ExcludedSchema.describe('Present when results were truncated to the cap.'),
|
|
108
|
+
})
|
|
109
|
+
.describe('Dataview DQL results.'),
|
|
110
|
+
z
|
|
111
|
+
.object({
|
|
112
|
+
mode: z.literal('jsonlogic').describe('Echoed mode.'),
|
|
113
|
+
hits: z
|
|
114
|
+
.array(StructuredHitSchema)
|
|
115
|
+
.describe('Matching files with the JSONLogic result per file.'),
|
|
116
|
+
excluded: ExcludedSchema.describe('Present when results were truncated to the cap.'),
|
|
117
|
+
})
|
|
118
|
+
.describe('JSONLogic results.'),
|
|
119
|
+
])
|
|
120
|
+
.describe('Mode-discriminated search payload.'),
|
|
121
|
+
}),
|
|
122
|
+
auth: ['tool:obsidian_search_notes:read'],
|
|
123
|
+
errors: [
|
|
124
|
+
{
|
|
125
|
+
reason: 'path_prefix_invalid_mode',
|
|
126
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
127
|
+
when: '`pathPrefix` was provided in a non-text mode (only `text` supports prefix filtering).',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
reason: 'query_required',
|
|
131
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
132
|
+
when: '`query` is missing for `text` or `dataview` mode (required for both).',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
reason: 'logic_required',
|
|
136
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
137
|
+
when: '`logic` is missing for `jsonlogic` mode.',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
async handler(input, ctx) {
|
|
141
|
+
const svc = getObsidianService();
|
|
142
|
+
if (input.pathPrefix && input.mode !== 'text') {
|
|
143
|
+
throw ctx.fail('path_prefix_invalid_mode', '`pathPrefix` is only valid in text mode.', {
|
|
144
|
+
mode: input.mode,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (input.mode === 'text') {
|
|
148
|
+
if (!input.query) {
|
|
149
|
+
throw ctx.fail('query_required', '`query` is required for text mode.', {
|
|
150
|
+
mode: input.mode,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const all = await svc.searchText(ctx, input.query, input.contextLength);
|
|
154
|
+
const prefix = input.pathPrefix;
|
|
155
|
+
const filtered = prefix ? all.filter((h) => h.filename.startsWith(prefix)) : all;
|
|
156
|
+
const matchCap = input.maxMatchesPerHit ?? DEFAULT_MATCHES_PER_HIT;
|
|
157
|
+
const clipped = filtered.map((h) => clipMatches(h, matchCap));
|
|
158
|
+
const capped = applyCap(clipped);
|
|
159
|
+
return { result: { mode: 'text', ...capped } };
|
|
160
|
+
}
|
|
161
|
+
if (input.mode === 'dataview') {
|
|
162
|
+
if (!input.query) {
|
|
163
|
+
throw ctx.fail('query_required', '`query` (DQL string) is required for dataview mode.', {
|
|
164
|
+
mode: input.mode,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const all = await svc.searchDataview(ctx, input.query);
|
|
168
|
+
const capped = applyCap(all);
|
|
169
|
+
return { result: { mode: 'dataview', ...capped } };
|
|
170
|
+
}
|
|
171
|
+
if (!input.logic) {
|
|
172
|
+
throw ctx.fail('logic_required', '`logic` (JSONLogic tree) is required for jsonlogic mode.', {
|
|
173
|
+
mode: input.mode,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const all = await svc.searchJsonLogic(ctx, input.logic);
|
|
177
|
+
const capped = applyCap(all);
|
|
178
|
+
return { result: { mode: 'jsonlogic', ...capped } };
|
|
179
|
+
},
|
|
180
|
+
format: ({ result }) => {
|
|
181
|
+
const lines = [`**Search (${result.mode}) — ${result.hits.length} hits**`];
|
|
182
|
+
if (result.excluded) {
|
|
183
|
+
lines.push(`_Excluded ${result.excluded.count} additional hits — ${result.excluded.hint}_`);
|
|
184
|
+
}
|
|
185
|
+
if (result.hits.length === 0) {
|
|
186
|
+
lines.push('', '_No matches. Try broader terms, a different mode, or check that the path/filter is correct._');
|
|
187
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
188
|
+
}
|
|
189
|
+
lines.push('');
|
|
190
|
+
if (result.mode === 'text') {
|
|
191
|
+
for (const h of result.hits) {
|
|
192
|
+
const score = h.score !== undefined ? ` (score: ${h.score})` : '';
|
|
193
|
+
const trunc = h.truncated
|
|
194
|
+
? ` — truncated, showing first ${h.matches.length} of ${h.totalMatches} matches`
|
|
195
|
+
: '';
|
|
196
|
+
lines.push(`### ${h.filename}${score}${trunc}`);
|
|
197
|
+
for (const m of h.matches) {
|
|
198
|
+
lines.push(`- match[${m.match.start}–${m.match.end}]: ${truncate(m.context, 240)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
for (const h of result.hits) {
|
|
204
|
+
lines.push(`### ${h.filename}`);
|
|
205
|
+
lines.push('```json');
|
|
206
|
+
lines.push(safeJsonStringify(h.result));
|
|
207
|
+
lines.push('```');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
function applyCap(all) {
|
|
214
|
+
if (all.length <= HIT_CAP)
|
|
215
|
+
return { hits: all, excluded: undefined };
|
|
216
|
+
return {
|
|
217
|
+
hits: all.slice(0, HIT_CAP),
|
|
218
|
+
excluded: { count: all.length - HIT_CAP, hint: EXCLUSION_HINT },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function clipMatches(hit, cap) {
|
|
222
|
+
if (hit.matches.length <= cap)
|
|
223
|
+
return hit;
|
|
224
|
+
return {
|
|
225
|
+
...hit,
|
|
226
|
+
matches: hit.matches.slice(0, cap),
|
|
227
|
+
truncated: true,
|
|
228
|
+
totalMatches: hit.matches.length,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function truncate(s, n) {
|
|
232
|
+
if (s.length <= n)
|
|
233
|
+
return s;
|
|
234
|
+
return `${s.slice(0, n)}…`;
|
|
235
|
+
}
|
|
236
|
+
function safeJsonStringify(v) {
|
|
237
|
+
try {
|
|
238
|
+
return JSON.stringify(v, null, 2);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return String(v);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=obsidian-search-notes.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obsidian-search-notes.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-search-notes.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview obsidian_write_note — create or overwrite a note (whole file)
|
|
3
|
+
* or replace a single section in place via PATCH-with-replace. Idempotent.
|
|
4
|
+
* @module mcp-server/tools/definitions/obsidian-write-note.tool
|
|
5
|
+
*/
|
|
6
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
export declare const obsidianWriteNote: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9
|
+
type: z.ZodLiteral<"path">;
|
|
10
|
+
path: z.ZodString;
|
|
11
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
12
|
+
type: z.ZodLiteral<"active">;
|
|
13
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
14
|
+
type: z.ZodLiteral<"periodic">;
|
|
15
|
+
period: z.ZodEnum<{
|
|
16
|
+
daily: "daily";
|
|
17
|
+
weekly: "weekly";
|
|
18
|
+
monthly: "monthly";
|
|
19
|
+
quarterly: "quarterly";
|
|
20
|
+
yearly: "yearly";
|
|
21
|
+
}>;
|
|
22
|
+
date: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>], "type">;
|
|
24
|
+
content: z.ZodString;
|
|
25
|
+
section: z.ZodOptional<z.ZodObject<{
|
|
26
|
+
type: z.ZodEnum<{
|
|
27
|
+
heading: "heading";
|
|
28
|
+
block: "block";
|
|
29
|
+
frontmatter: "frontmatter";
|
|
30
|
+
}>;
|
|
31
|
+
target: z.ZodString;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
contentType: z.ZodDefault<z.ZodEnum<{
|
|
34
|
+
markdown: "markdown";
|
|
35
|
+
json: "json";
|
|
36
|
+
}>>;
|
|
37
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
38
|
+
path: z.ZodString;
|
|
39
|
+
sectionTargeted: z.ZodBoolean;
|
|
40
|
+
}, z.core.$strip>, undefined>;
|
|
41
|
+
//# sourceMappingURL=obsidian-write-note.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obsidian-write-note.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-write-note.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAIjD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA0D5B,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview obsidian_write_note — create or overwrite a note (whole file)
|
|
3
|
+
* or replace a single section in place via PATCH-with-replace. Idempotent.
|
|
4
|
+
* @module mcp-server/tools/definitions/obsidian-write-note.tool
|
|
5
|
+
*/
|
|
6
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { getObsidianService } from '../../../services/obsidian/obsidian-service.js';
|
|
8
|
+
import { ContentTypeSchema, SectionSchema, TargetSchema } from './_shared/schemas.js';
|
|
9
|
+
export const obsidianWriteNote = tool('obsidian_write_note', {
|
|
10
|
+
description: 'Create or overwrite a note. With `section` provided, replaces just that heading/block/frontmatter section in place; otherwise overwrites the whole file (creating it if missing). Idempotent — repeated calls with the same input converge on the same result. For heading sections, `content` is the new body; the heading line itself is preserved automatically and a leading duplicate heading is stripped.',
|
|
11
|
+
annotations: { idempotentHint: true, destructiveHint: true },
|
|
12
|
+
input: z.object({
|
|
13
|
+
target: TargetSchema.describe('Where the note lives.'),
|
|
14
|
+
content: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('Body to write. For heading sections, the new section body — do not repeat the heading line (it stays in place). Markdown unless `contentType` is `json`.'),
|
|
17
|
+
section: SectionSchema.optional().describe('Optional sub-document target. When set, only this section is replaced; rest of the note is untouched.'),
|
|
18
|
+
contentType: ContentTypeSchema,
|
|
19
|
+
}),
|
|
20
|
+
output: z.object({
|
|
21
|
+
path: z.string().describe('Resolved vault-relative path of the note that was written.'),
|
|
22
|
+
sectionTargeted: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.describe('True when only a section was replaced; false for full-file writes.'),
|
|
25
|
+
}),
|
|
26
|
+
auth: ['tool:obsidian_write_note:write'],
|
|
27
|
+
async handler(input, ctx) {
|
|
28
|
+
const svc = getObsidianService();
|
|
29
|
+
const { target } = input;
|
|
30
|
+
if (input.section) {
|
|
31
|
+
const body = input.section.type === 'heading'
|
|
32
|
+
? stripLeadingHeading(input.content, input.section.target)
|
|
33
|
+
: input.content;
|
|
34
|
+
await svc.patchNote(ctx, target, body, {
|
|
35
|
+
operation: 'replace',
|
|
36
|
+
targetType: input.section.type,
|
|
37
|
+
target: input.section.target,
|
|
38
|
+
targetDelimiter: input.section.type === 'heading' ? '::' : undefined,
|
|
39
|
+
contentType: input.contentType,
|
|
40
|
+
applyIfContentPreexists: true,
|
|
41
|
+
});
|
|
42
|
+
const path = await svc.resolvePath(ctx, target);
|
|
43
|
+
return { path, sectionTargeted: true };
|
|
44
|
+
}
|
|
45
|
+
await svc.writeNote(ctx, target, input.content, input.contentType);
|
|
46
|
+
const path = await svc.resolvePath(ctx, target);
|
|
47
|
+
return { path, sectionTargeted: false };
|
|
48
|
+
},
|
|
49
|
+
format: (result) => [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: [`**Wrote ${result.path}**`, `*Section targeted:* ${result.sectionTargeted}`].join('\n'),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* If `content` opens with a markdown heading whose text matches the leaf of
|
|
58
|
+
* `headingTarget` (delimited by `::`), drop that heading line plus a single
|
|
59
|
+
* blank line. Upstream PATCH-replace operates on the section *body*, so a
|
|
60
|
+
* caller-supplied heading line would otherwise be embedded as a duplicate.
|
|
61
|
+
*/
|
|
62
|
+
function stripLeadingHeading(content, headingTarget) {
|
|
63
|
+
const leaf = headingTarget.split('::').pop()?.trim();
|
|
64
|
+
if (!leaf)
|
|
65
|
+
return content;
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
const first = lines[0]?.trimEnd() ?? '';
|
|
68
|
+
const m = /^(#{1,6})\s+(.+)$/.exec(first);
|
|
69
|
+
if (!m || m[2]?.trim() !== leaf)
|
|
70
|
+
return content;
|
|
71
|
+
lines.shift();
|
|
72
|
+
if (lines[0]?.trim() === '')
|
|
73
|
+
lines.shift();
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=obsidian-write-note.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obsidian-write-note.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-write-note.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Read-modify-write helpers for the YAML frontmatter block of a
|
|
3
|
+
* note's raw content. Used by the composed manage-frontmatter / manage-tags
|
|
4
|
+
* tools when the upstream Local REST API has no single-call equivalent.
|
|
5
|
+
* @module services/obsidian/frontmatter-ops
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Returns the full file content with `key` removed from the frontmatter.
|
|
9
|
+
* If the file has no frontmatter or the key isn't present, returns content
|
|
10
|
+
* unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export declare function deleteFrontmatterKey(content: string, key: string): string;
|
|
13
|
+
export interface TagReconcileResult {
|
|
14
|
+
/** Tags actually changed (added/removed) at one or more locations. */
|
|
15
|
+
applied: string[];
|
|
16
|
+
/** Updated content with the requested tag mutations applied. */
|
|
17
|
+
content: string;
|
|
18
|
+
/** Tags that were already in the desired state at the targeted location(s). */
|
|
19
|
+
skipped: string[];
|
|
20
|
+
}
|
|
21
|
+
export type TagOperation = 'add' | 'remove';
|
|
22
|
+
export type TagLocation = 'frontmatter' | 'inline' | 'both';
|
|
23
|
+
/**
|
|
24
|
+
* Add or remove tags across frontmatter (`tags:` array) and inline `#tag`
|
|
25
|
+
* syntax. Inline occurrences inside fenced code blocks are left alone — they
|
|
26
|
+
* are code, not tags.
|
|
27
|
+
*/
|
|
28
|
+
export declare function reconcileTags(content: string, tags: string[], operation: TagOperation, location: TagLocation): TagReconcileResult;
|
|
29
|
+
/** Read-only helpers for `obsidian_manage_tags list`. */
|
|
30
|
+
export declare function listTagsFromContent(content: string, frontmatter: Record<string, unknown>): {
|
|
31
|
+
frontmatter: string[];
|
|
32
|
+
inline: string[];
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=frontmatter-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-ops.d.ts","sourceRoot":"","sources":["../../../src/services/obsidian/frontmatter-ops.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqCH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOzE;AAED,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,EAAE,YAAY,EACvB,QAAQ,EAAE,WAAW,GACpB,kBAAkB,CAqBpB;AA6JD,yDAAyD;AACzD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC;IACD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAkBA"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Read-modify-write helpers for the YAML frontmatter block of a
|
|
3
|
+
* note's raw content. Used by the composed manage-frontmatter / manage-tags
|
|
4
|
+
* tools when the upstream Local REST API has no single-call equivalent.
|
|
5
|
+
* @module services/obsidian/frontmatter-ops
|
|
6
|
+
*/
|
|
7
|
+
import { dump as yamlDump, load as yamlLoad } from 'js-yaml';
|
|
8
|
+
const FM_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
9
|
+
function splice(content) {
|
|
10
|
+
const m = FM_RE.exec(content);
|
|
11
|
+
if (!m)
|
|
12
|
+
return { hasFrontmatter: false, yamlText: '', body: content };
|
|
13
|
+
return {
|
|
14
|
+
hasFrontmatter: true,
|
|
15
|
+
yamlText: m[1] ?? '',
|
|
16
|
+
body: content.slice(m[0].length),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function loadFrontmatter(yamlText) {
|
|
20
|
+
const loaded = yamlLoad(yamlText);
|
|
21
|
+
return loaded && typeof loaded === 'object' ? loaded : {};
|
|
22
|
+
}
|
|
23
|
+
function emit(frontmatter, body) {
|
|
24
|
+
const keys = Object.keys(frontmatter);
|
|
25
|
+
if (keys.length === 0) {
|
|
26
|
+
return body.replace(/^\s+/, '');
|
|
27
|
+
}
|
|
28
|
+
const yamlText = yamlDump(frontmatter, { lineWidth: 1000, noRefs: true });
|
|
29
|
+
return `---\n${yamlText.trimEnd()}\n---\n${body.startsWith('\n') ? body.slice(1) : body}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the full file content with `key` removed from the frontmatter.
|
|
33
|
+
* If the file has no frontmatter or the key isn't present, returns content
|
|
34
|
+
* unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export function deleteFrontmatterKey(content, key) {
|
|
37
|
+
const { hasFrontmatter, yamlText, body } = splice(content);
|
|
38
|
+
if (!hasFrontmatter)
|
|
39
|
+
return content;
|
|
40
|
+
const fm = loadFrontmatter(yamlText);
|
|
41
|
+
if (!(key in fm))
|
|
42
|
+
return content;
|
|
43
|
+
delete fm[key];
|
|
44
|
+
return emit(fm, body);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Add or remove tags across frontmatter (`tags:` array) and inline `#tag`
|
|
48
|
+
* syntax. Inline occurrences inside fenced code blocks are left alone — they
|
|
49
|
+
* are code, not tags.
|
|
50
|
+
*/
|
|
51
|
+
export function reconcileTags(content, tags, operation, location) {
|
|
52
|
+
const norm = (t) => t.replace(/^#+/, '').trim();
|
|
53
|
+
const wanted = tags.map(norm).filter((t) => t.length > 0);
|
|
54
|
+
const applied = new Set();
|
|
55
|
+
const skipped = new Set();
|
|
56
|
+
let updated = content;
|
|
57
|
+
if (location === 'frontmatter' || location === 'both') {
|
|
58
|
+
updated = mutateFrontmatterTags(updated, wanted, operation, applied, skipped);
|
|
59
|
+
}
|
|
60
|
+
if (location === 'inline' || location === 'both') {
|
|
61
|
+
updated = mutateInlineTags(updated, wanted, operation, applied, skipped);
|
|
62
|
+
}
|
|
63
|
+
// For location='both', a tag that was already-in-frontmatter may have been
|
|
64
|
+
// missing inline (or vice versa). If applied is non-empty for the tag, drop
|
|
65
|
+
// it from skipped.
|
|
66
|
+
for (const t of applied)
|
|
67
|
+
skipped.delete(t);
|
|
68
|
+
return { content: updated, applied: [...applied], skipped: [...skipped] };
|
|
69
|
+
}
|
|
70
|
+
function mutateFrontmatterTags(content, tags, operation, applied, skipped) {
|
|
71
|
+
const { hasFrontmatter, yamlText, body } = splice(content);
|
|
72
|
+
const fm = hasFrontmatter ? { ...loadFrontmatter(yamlText) } : {};
|
|
73
|
+
const existing = normalizeTagList(fm.tags);
|
|
74
|
+
const set = new Set(existing);
|
|
75
|
+
let changed = false;
|
|
76
|
+
for (const tag of tags) {
|
|
77
|
+
if (operation === 'add') {
|
|
78
|
+
if (set.has(tag))
|
|
79
|
+
skipped.add(tag);
|
|
80
|
+
else {
|
|
81
|
+
set.add(tag);
|
|
82
|
+
applied.add(tag);
|
|
83
|
+
changed = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (set.has(tag)) {
|
|
88
|
+
set.delete(tag);
|
|
89
|
+
applied.add(tag);
|
|
90
|
+
changed = true;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
skipped.add(tag);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!changed)
|
|
98
|
+
return content;
|
|
99
|
+
const ordered = [...set];
|
|
100
|
+
if (ordered.length === 0) {
|
|
101
|
+
delete fm.tags;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
fm.tags = ordered;
|
|
105
|
+
}
|
|
106
|
+
if (Object.keys(fm).length === 0) {
|
|
107
|
+
return body.replace(/^\s+/, '');
|
|
108
|
+
}
|
|
109
|
+
return emit(fm, body);
|
|
110
|
+
}
|
|
111
|
+
function normalizeTagList(value) {
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return value
|
|
114
|
+
.filter((v) => typeof v === 'string')
|
|
115
|
+
.map((v) => v.replace(/^#+/, '').trim())
|
|
116
|
+
.filter((v) => v.length > 0);
|
|
117
|
+
}
|
|
118
|
+
if (typeof value === 'string') {
|
|
119
|
+
return value
|
|
120
|
+
.split(/[\s,]+/)
|
|
121
|
+
.map((v) => v.replace(/^#+/, '').trim())
|
|
122
|
+
.filter((v) => v.length > 0);
|
|
123
|
+
}
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const FENCED_CODE_BLOCK = /(```[\s\S]*?```|~~~[\s\S]*?~~~)/g;
|
|
127
|
+
const INLINE_CODE = /(`[^`\n]+`)/g;
|
|
128
|
+
function mutateInlineTags(content, tags, operation, applied, skipped) {
|
|
129
|
+
const segments = splitProtectedSegments(content);
|
|
130
|
+
let updatedNonCode = false;
|
|
131
|
+
if (operation === 'add') {
|
|
132
|
+
for (const tag of tags) {
|
|
133
|
+
const re = makeInlineTagRegex(tag);
|
|
134
|
+
const present = segments.some((s) => !s.protected && re.test(s.text));
|
|
135
|
+
if (present) {
|
|
136
|
+
skipped.add(tag);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
applied.add(tag);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const additions = tags
|
|
143
|
+
.filter((t) => applied.has(t))
|
|
144
|
+
.map((t) => `#${t}`)
|
|
145
|
+
.join(' ');
|
|
146
|
+
if (additions.length > 0) {
|
|
147
|
+
const trailing = segments.length > 0 ? (segments[segments.length - 1] ?? null) : null;
|
|
148
|
+
if (trailing && !trailing.protected) {
|
|
149
|
+
const sep = trailing.text.endsWith('\n') ? '' : '\n';
|
|
150
|
+
trailing.text = `${trailing.text}${sep}${additions}\n`;
|
|
151
|
+
updatedNonCode = true;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
segments.push({ protected: false, text: `\n${additions}\n` });
|
|
155
|
+
updatedNonCode = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
for (const tag of tags) {
|
|
161
|
+
const re = makeInlineTagRegex(tag);
|
|
162
|
+
let found = false;
|
|
163
|
+
for (const s of segments) {
|
|
164
|
+
if (s.protected)
|
|
165
|
+
continue;
|
|
166
|
+
if (re.test(s.text)) {
|
|
167
|
+
s.text = s.text.replace(re, (_full, leading) => leading);
|
|
168
|
+
// collapse double spaces left behind
|
|
169
|
+
s.text = s.text.replace(/[ \t]{2,}/g, ' ').replace(/ \n/g, '\n');
|
|
170
|
+
found = true;
|
|
171
|
+
updatedNonCode = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (found)
|
|
175
|
+
applied.add(tag);
|
|
176
|
+
else
|
|
177
|
+
skipped.add(tag);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!updatedNonCode)
|
|
181
|
+
return content;
|
|
182
|
+
return segments.map((s) => s.text).join('');
|
|
183
|
+
}
|
|
184
|
+
function splitProtectedSegments(content) {
|
|
185
|
+
const segments = [];
|
|
186
|
+
let cursor = 0;
|
|
187
|
+
const re = new RegExp(`${FENCED_CODE_BLOCK.source}|${INLINE_CODE.source}`, 'g');
|
|
188
|
+
for (;;) {
|
|
189
|
+
const m = re.exec(content);
|
|
190
|
+
if (!m)
|
|
191
|
+
break;
|
|
192
|
+
const matched = m[0] ?? '';
|
|
193
|
+
if (m.index > cursor) {
|
|
194
|
+
segments.push({ protected: false, text: content.slice(cursor, m.index) });
|
|
195
|
+
}
|
|
196
|
+
segments.push({ protected: true, text: matched });
|
|
197
|
+
cursor = m.index + matched.length;
|
|
198
|
+
}
|
|
199
|
+
if (cursor < content.length) {
|
|
200
|
+
segments.push({ protected: false, text: content.slice(cursor) });
|
|
201
|
+
}
|
|
202
|
+
return segments;
|
|
203
|
+
}
|
|
204
|
+
function makeInlineTagRegex(tag) {
|
|
205
|
+
const escaped = tag.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
206
|
+
return new RegExp(`(^|[^\\w/])#${escaped}(?![\\w/-])`, 'g');
|
|
207
|
+
}
|
|
208
|
+
/** Read-only helpers for `obsidian_manage_tags list`. */
|
|
209
|
+
export function listTagsFromContent(content, frontmatter) {
|
|
210
|
+
const fmTags = normalizeTagList(frontmatter.tags);
|
|
211
|
+
const inline = [];
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
for (const seg of splitProtectedSegments(content)) {
|
|
214
|
+
if (seg.protected)
|
|
215
|
+
continue;
|
|
216
|
+
const re = /(^|[^\w/])#([a-zA-Z][\w/-]*)/g;
|
|
217
|
+
for (;;) {
|
|
218
|
+
const m = re.exec(seg.text);
|
|
219
|
+
if (!m)
|
|
220
|
+
break;
|
|
221
|
+
const t = m[2];
|
|
222
|
+
if (t && !seen.has(t)) {
|
|
223
|
+
seen.add(t);
|
|
224
|
+
inline.push(t);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { frontmatter: fmTags, inline };
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=frontmatter-ops.js.map
|