openclaw-cloudflare-vectorize-memory 0.1.1 → 0.1.2

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.
@@ -0,0 +1,18 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+
3
+ export default definePluginEntry({
4
+ id: "memory-cloudflare-vectorize",
5
+ name: "Cloudflare Vectorize Memory",
6
+ description: "OpenClaw memory plugin backed by Cloudflare Vectorize and Workers AI embeddings.",
7
+ register(api) {
8
+ api.registerCli(() => {}, {
9
+ descriptors: [
10
+ {
11
+ name: "cf-memory",
12
+ description: "Manage Cloudflare Vectorize memory",
13
+ hasSubcommands: true,
14
+ },
15
+ ],
16
+ });
17
+ },
18
+ });
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import type { OpenClawConfig } from \"openclaw/plugin-sdk/config-runtime\";\r\nimport type { MigrationDuplicateStrategy } from \"./types.js\";\r\nimport { formatMigrationSummary, runCloudflareMemoryMigration } from \"./migration.js\";\r\nimport { createCloudflareMemoryService } from \"./service-factory.js\";\r\nimport type { MetadataFilter } from \"./types.js\";\r\n\r\ntype CliCommand = {\r\n\tcommand: (name: string) => CliCommand;\r\n\tdescription: (description: string) => CliCommand;\r\n\targument: (name: string, description: string) => CliCommand;\r\n\toption: (flags: string, description: string) => CliCommand;\r\n\taction: (handler: (...args: unknown[]) => Promise<void> | void) => CliCommand;\r\n\topts?: () => Record<string, unknown>;\r\n};\r\n\r\nfunction printJson(value: unknown): void {\r\n\tconsole.log(JSON.stringify(value, null, 2));\r\n}\r\n\r\nfunction parseMetadataFlag(value: string | undefined): Record<string, string | number | boolean> | undefined {\r\n\tif (!value) {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst parsed = JSON.parse(value) as unknown;\r\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n\t\tthrow new Error(\"--metadata must be a JSON object.\");\r\n\t}\r\n\treturn parsed as Record<string, string | number | boolean>;\r\n}\r\n\r\nfunction parseFilterFlag(value: string | undefined): MetadataFilter | undefined {\r\n\tif (!value) {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst parsed = JSON.parse(value) as unknown;\r\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n\t\tthrow new Error(\"--filter must be a JSON object.\");\r\n\t}\r\n\treturn parsed as MetadataFilter;\r\n}\r\n\r\nfunction isCliCommand(value: unknown): value is CliCommand {\r\n\treturn Boolean(value) && typeof value === \"object\" && typeof (value as CliCommand).opts === \"function\";\r\n}\r\n\r\nfunction resolveInvocation(args: unknown[]): { positionals: unknown[]; options: Record<string, unknown> } {\r\n\tconst maybeCommand = args.at(-1);\r\n\tif (!isCliCommand(maybeCommand)) {\r\n\t\treturn {\r\n\t\t\tpositionals: args,\r\n\t\t\toptions: {},\r\n\t\t};\r\n\t}\r\n\treturn {\r\n\t\tpositionals: args.slice(0, -1),\r\n\t\toptions: maybeCommand.opts?.() ?? {},\r\n\t};\r\n}\r\n\r\nfunction parseDuplicateStrategy(value: unknown): MigrationDuplicateStrategy | undefined {\r\n\tif (value === undefined) {\r\n\t\treturn undefined;\r\n\t}\r\n\tif (value === \"overwrite\" || value === \"skip\" || value === \"fail\") {\r\n\t\treturn value;\r\n\t}\r\n\tthrow new Error(\"--if-exists must be overwrite, skip, or fail.\");\r\n}\r\n\r\nexport function registerCloudflareMemoryCli(\r\n\tprogram: {\r\n\t\tcommand: (name: string) => CliCommand;\r\n\t},\r\n\tparams: {\r\n\t\tpluginConfig: unknown;\r\n\t\topenClawConfig: OpenClawConfig;\r\n\t\tresolvePath?: (input: string) => string;\r\n\t},\r\n): void {\r\n\tconst root = program.command(\"cf-memory\").description(\"Manage Cloudflare memory records.\");\r\n\r\n\tfunction resolveOptions(args: unknown[]): Record<string, unknown> {\r\n\t\treturn resolveInvocation(args).options;\r\n\t}\r\n\r\n\troot\r\n\t\t.command(\"doctor\")\r\n\t\t.description(\"Validate Workers AI and Vectorize configuration.\")\r\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst options = resolveOptions(args);\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst report = await service.doctor({\r\n\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\r\n\t\t\t});\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(report);\r\n\t\t\t} else {\r\n\t\t\t\tfor (const check of report.checks) {\r\n\t\t\t\t\tconsole.log(`[${check.status}] ${check.name}: ${check.message}`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!report.ok) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"search\")\r\n\t\t.description(\"Search stored Cloudflare memory.\")\r\n\t\t.argument(\"<query>\", \"Semantic search query.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.option(\"--limit <count>\", \"Maximum number of results.\")\r\n\t\t.option(\"--filter <json>\", \"Optional metadata filter JSON.\")\r\n\t\t.action(async (query, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst results = await service.search({\r\n\t\t\t\tquery: String(query),\r\n\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\tmaxResults: options.limit ? Number(options.limit) : undefined,\r\n\t\t\t\tfilter: parseFilterFlag(options.filter as string | undefined),\r\n\t\t\t});\r\n\t\t\tprintJson(results);\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"upsert\")\r\n\t\t.description(\"Insert or update a memory record.\")\r\n\t\t.argument(\"<text>\", \"Memory text.\")\r\n\t\t.option(\"--id <id>\", \"Stable logical id.\")\r\n\t\t.option(\"--title <title>\", \"Optional title.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.option(\"--source <source>\", \"Optional source label.\")\r\n\t\t.option(\"--metadata <json>\", \"Optional metadata JSON object.\")\r\n\t\t.action(async (text, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst result = await service.upsert({\r\n\t\t\t\tinput: {\r\n\t\t\t\t\tid: options.id as string | undefined,\r\n\t\t\t\t\ttitle: options.title as string | undefined,\r\n\t\t\t\t\ttext: String(text),\r\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\t\tsource: options.source as string | undefined,\r\n\t\t\t\t\tmetadata: parseMetadataFlag(options.metadata as string | undefined),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tprintJson(result);\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"delete\")\r\n\t\t.description(\"Delete a memory record.\")\r\n\t\t.argument(\"<id>\", \"Logical memory record id.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.action(async (id, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst mutationId = await service.delete({\r\n\t\t\t\tid: String(id),\r\n\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t});\r\n\t\t\tprintJson({ id, mutationId });\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"migrate\")\r\n\t\t.description(\"Migrate legacy markdown memory into Cloudflare Vectorize.\")\r\n\t\t.argument(\"[sources...]\", \"Markdown files, directories, or glob patterns. Defaults to the current OpenClaw memory corpus when omitted.\")\r\n\t\t.option(\"--workspace <path>\", \"Workspace root used for default-provider discovery and relative path normalization.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Target namespace override.\")\r\n\t\t.option(\"--derive-namespace-from-path\", \"Derive namespaces from the first relative path segment instead of using a single target namespace.\")\r\n\t\t.option(\"--if-exists <strategy>\", \"Duplicate handling: overwrite, skip, or fail.\")\r\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\r\n\t\t.option(\"--dry-run\", \"Plan the migration without writing records.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst { positionals, options } = resolveInvocation(args);\r\n\t\t\tconst rawSources = positionals[0];\r\n\t\t\tconst sourcePaths =\r\n\t\t\t\tpositionals.length === 0\r\n\t\t\t\t\t? []\r\n\t\t\t\t\t: Array.isArray(rawSources)\r\n\t\t\t\t\t\t? rawSources.map((value) => String(value))\r\n\t\t\t\t\t\t: positionals.map((value) => String(value));\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst summary = await runCloudflareMemoryMigration({\r\n\t\t\t\tservice,\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tsourcePaths,\r\n\t\t\t\t\tworkspaceDir: options.workspace as string | undefined,\r\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\t\tnamespaceStrategy: options.deriveNamespaceFromPath ? \"path\" : \"single-target\",\r\n\t\t\t\t\tduplicateStrategy: parseDuplicateStrategy(options.ifExists),\r\n\t\t\t\t\tdryRun: Boolean(options.dryRun),\r\n\t\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(summary);\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log(formatMigrationSummary(summary));\r\n\t\t\t}\r\n\t\t\tif (summary.failed > 0) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import type { OpenClawConfig } from \"openclaw/plugin-sdk/config-runtime\";\nimport type { MigrationDuplicateStrategy } from \"./types.js\";\nimport { formatMigrationSummary, runCloudflareMemoryMigration } from \"./migration.js\";\nimport { createCloudflareMemoryService } from \"./service-factory.js\";\nimport type { MetadataFilter } from \"./types.js\";\n\ntype CliCommand = {\n\tcommand: (name: string) => CliCommand;\n\tdescription: (description: string) => CliCommand;\n\targument: (name: string, description: string) => CliCommand;\n\toption: (flags: string, description: string) => CliCommand;\n\taction: (handler: (...args: unknown[]) => Promise<void> | void) => CliCommand;\n\topts?: () => Record<string, unknown>;\n};\n\nfunction printJson(value: unknown): void {\n\tconsole.log(JSON.stringify(value, null, 2));\n}\n\nfunction parseMetadataFlag(value: string | undefined): Record<string, string | number | boolean> | undefined {\n\tif (!value) {\n\t\treturn undefined;\n\t}\n\tconst parsed = JSON.parse(value) as unknown;\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(\"--metadata must be a JSON object.\");\n\t}\n\treturn parsed as Record<string, string | number | boolean>;\n}\n\nfunction parseFilterFlag(value: string | undefined): MetadataFilter | undefined {\n\tif (!value) {\n\t\treturn undefined;\n\t}\n\tconst parsed = JSON.parse(value) as unknown;\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(\"--filter must be a JSON object.\");\n\t}\n\treturn parsed as MetadataFilter;\n}\n\nfunction isCliCommand(value: unknown): value is CliCommand {\n\treturn Boolean(value) && typeof value === \"object\" && typeof (value as CliCommand).opts === \"function\";\n}\n\nfunction resolveInvocation(args: unknown[]): { positionals: unknown[]; options: Record<string, unknown> } {\n\tconst maybeCommand = args.at(-1);\n\tif (!isCliCommand(maybeCommand)) {\n\t\treturn {\n\t\t\tpositionals: args,\n\t\t\toptions: {},\n\t\t};\n\t}\n\treturn {\n\t\tpositionals: args.slice(0, -1),\n\t\toptions: maybeCommand.opts?.() ?? {},\n\t};\n}\n\nfunction parseDuplicateStrategy(value: unknown): MigrationDuplicateStrategy | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tif (value === \"overwrite\" || value === \"skip\" || value === \"fail\") {\n\t\treturn value;\n\t}\n\tthrow new Error(\"--if-exists must be overwrite, skip, or fail.\");\n}\n\nexport function registerCloudflareMemoryCli(\n\tprogram: {\n\t\tcommand: (name: string) => CliCommand;\n\t},\n\tparams: {\n\t\tpluginConfig: unknown;\n\t\topenClawConfig: OpenClawConfig;\n\t\tresolvePath?: (input: string) => string;\n\t},\n): void {\n\tconst root = program.command(\"cf-memory\").description(\"Manage Cloudflare memory records.\");\n\n\tfunction resolveOptions(args: unknown[]): Record<string, unknown> {\n\t\treturn resolveInvocation(args).options;\n\t}\n\n\troot\n\t\t.command(\"doctor\")\n\t\t.description(\"Validate Workers AI and Vectorize configuration.\")\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\n\t\t.option(\"--json\", \"Print structured JSON output.\")\n\t\t.action(async (...args) => {\n\t\t\tconst options = resolveOptions(args);\n\t\t\tconst service = await createCloudflareMemoryService({\n\t\t\t\tpluginConfig: params.pluginConfig,\n\t\t\t\topenClawConfig: params.openClawConfig,\n\t\t\t\tenv: process.env,\n\t\t\t\tresolvePath: params.resolvePath,\n\t\t\t});\n\t\t\tconst report = await service.doctor({\n\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\n\t\t\t});\n\t\t\tif (options.json) {\n\t\t\t\tprintJson(report);\n\t\t\t} else {\n\t\t\t\tfor (const check of report.checks) {\n\t\t\t\t\tconsole.log(`[${check.status}] ${check.name}: ${check.message}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!report.ok) {\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t}\n\t\t});\n\n\troot\n\t\t.command(\"search\")\n\t\t.description(\"Search stored Cloudflare memory.\")\n\t\t.argument(\"<query>\", \"Semantic search query.\")\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\n\t\t.option(\"--limit <count>\", \"Maximum number of results.\")\n\t\t.option(\"--filter <json>\", \"Optional metadata filter JSON.\")\n\t\t.action(async (query, opts) => {\n\t\t\tconst options = opts as Record<string, unknown>;\n\t\t\tconst service = await createCloudflareMemoryService({\n\t\t\t\tpluginConfig: params.pluginConfig,\n\t\t\t\topenClawConfig: params.openClawConfig,\n\t\t\t\tenv: process.env,\n\t\t\t\tresolvePath: params.resolvePath,\n\t\t\t});\n\t\t\tconst results = await service.search({\n\t\t\t\tquery: String(query),\n\t\t\t\tnamespace: options.namespace as string | undefined,\n\t\t\t\tmaxResults: options.limit ? Number(options.limit) : undefined,\n\t\t\t\tfilter: parseFilterFlag(options.filter as string | undefined),\n\t\t\t});\n\t\t\tprintJson(results);\n\t\t});\n\n\troot\n\t\t.command(\"upsert\")\n\t\t.description(\"Insert or update a memory record.\")\n\t\t.argument(\"<text>\", \"Memory text.\")\n\t\t.option(\"--id <id>\", \"Stable logical id.\")\n\t\t.option(\"--title <title>\", \"Optional title.\")\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\n\t\t.option(\"--source <source>\", \"Optional source label.\")\n\t\t.option(\"--metadata <json>\", \"Optional metadata JSON object.\")\n\t\t.action(async (text, opts) => {\n\t\t\tconst options = opts as Record<string, unknown>;\n\t\t\tconst service = await createCloudflareMemoryService({\n\t\t\t\tpluginConfig: params.pluginConfig,\n\t\t\t\topenClawConfig: params.openClawConfig,\n\t\t\t\tenv: process.env,\n\t\t\t\tresolvePath: params.resolvePath,\n\t\t\t});\n\t\t\tconst result = await service.upsert({\n\t\t\t\tinput: {\n\t\t\t\t\tid: options.id as string | undefined,\n\t\t\t\t\ttitle: options.title as string | undefined,\n\t\t\t\t\ttext: String(text),\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\n\t\t\t\t\tsource: options.source as string | undefined,\n\t\t\t\t\tmetadata: parseMetadataFlag(options.metadata as string | undefined),\n\t\t\t\t},\n\t\t\t});\n\t\t\tprintJson(result);\n\t\t});\n\n\troot\n\t\t.command(\"delete\")\n\t\t.description(\"Delete a memory record.\")\n\t\t.argument(\"<id>\", \"Logical memory record id.\")\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\n\t\t.action(async (id, opts) => {\n\t\t\tconst options = opts as Record<string, unknown>;\n\t\t\tconst service = await createCloudflareMemoryService({\n\t\t\t\tpluginConfig: params.pluginConfig,\n\t\t\t\topenClawConfig: params.openClawConfig,\n\t\t\t\tenv: process.env,\n\t\t\t\tresolvePath: params.resolvePath,\n\t\t\t});\n\t\t\tconst mutationId = await service.delete({\n\t\t\t\tid: String(id),\n\t\t\t\tnamespace: options.namespace as string | undefined,\n\t\t\t});\n\t\t\tprintJson({ id, mutationId });\n\t\t});\n\n\troot\n\t\t.command(\"migrate\")\n\t\t.description(\"Migrate legacy markdown memory into Cloudflare Vectorize.\")\n\t\t.argument(\"[sources...]\", \"Markdown files, directories, or glob patterns. Defaults to the current OpenClaw memory corpus when omitted.\")\n\t\t.option(\"--workspace <path>\", \"Workspace root used for default-provider discovery and relative path normalization.\")\n\t\t.option(\"--namespace <namespace>\", \"Target namespace override.\")\n\t\t.option(\"--derive-namespace-from-path\", \"Derive namespaces from the first relative path segment instead of using a single target namespace.\")\n\t\t.option(\"--if-exists <strategy>\", \"Duplicate handling: overwrite, skip, or fail.\")\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\n\t\t.option(\"--dry-run\", \"Plan the migration without writing records.\")\n\t\t.option(\"--json\", \"Print structured JSON output.\")\n\t\t.action(async (...args) => {\n\t\t\tconst { positionals, options } = resolveInvocation(args);\n\t\t\tconst rawSources = positionals[0];\n\t\t\tconst sourcePaths =\n\t\t\t\tpositionals.length === 0 ? [] : Array.isArray(rawSources) ? rawSources.map((value) => String(value)) : positionals.map((value) => String(value));\n\t\t\tconst service = await createCloudflareMemoryService({\n\t\t\t\tpluginConfig: params.pluginConfig,\n\t\t\t\topenClawConfig: params.openClawConfig,\n\t\t\t\tenv: process.env,\n\t\t\t\tresolvePath: params.resolvePath,\n\t\t\t});\n\t\t\tconst summary = await runCloudflareMemoryMigration({\n\t\t\t\tservice,\n\t\t\t\toptions: {\n\t\t\t\t\tsourcePaths,\n\t\t\t\t\tworkspaceDir: options.workspace as string | undefined,\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\n\t\t\t\t\tnamespaceStrategy: options.deriveNamespaceFromPath ? \"path\" : \"single-target\",\n\t\t\t\t\tduplicateStrategy: parseDuplicateStrategy(options.ifExists),\n\t\t\t\t\tdryRun: Boolean(options.dryRun),\n\t\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (options.json) {\n\t\t\t\tprintJson(summary);\n\t\t\t} else {\n\t\t\t\tconsole.log(formatMigrationSummary(summary));\n\t\t\t}\n\t\t\tif (summary.failed > 0) {\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t}\n\t\t});\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -43,31 +43,34 @@ function _() {
43
43
  }
44
44
  };
45
45
  }
46
- var v = g({
46
+ function v(e) {
47
+ e.registerCli(({ program: t }) => {
48
+ c(t, {
49
+ pluginConfig: e.pluginConfig,
50
+ openClawConfig: e.config,
51
+ resolvePath: e.resolvePath
52
+ });
53
+ }, { descriptors: [{
54
+ name: "cf-memory",
55
+ description: "Manage Cloudflare Vectorize memory",
56
+ hasSubcommands: !0
57
+ }] });
58
+ }
59
+ var y = g({
47
60
  id: n,
48
61
  name: r,
49
62
  description: t,
50
63
  kind: "memory",
51
64
  configSchema: a,
52
65
  register(e) {
53
- a.parse?.(e.pluginConfig ?? {}), e.registerMemoryEmbeddingProvider(_()), e.registerMemoryCapability({
66
+ a.parse?.(e.pluginConfig ?? {}), v(e), !(e.registrationMode === "cli-metadata" || typeof e.registerMemoryEmbeddingProvider != "function" || typeof e.registerMemoryCapability != "function" || typeof e.registerTool != "function") && (e.registerMemoryEmbeddingProvider(_()), e.registerMemoryCapability({
54
67
  promptBuilder: l,
55
68
  runtime: d({
56
69
  pluginConfig: e.pluginConfig,
57
70
  resolvePath: e.resolvePath
58
71
  }),
59
72
  publicArtifacts: u(e.pluginConfig, e.resolvePath)
60
- }), e.registerTool((t) => m(e.pluginConfig, t), { names: ["cloudflare_memory_search"] }), e.registerTool((t) => p(e.pluginConfig, t), { names: ["cloudflare_memory_get"] }), e.registerTool((t) => h(e.pluginConfig, t), { names: ["cloudflare_memory_upsert"] }), e.registerTool((t) => f(e.pluginConfig, t), { names: ["cloudflare_memory_delete"] }), e.registerCli(({ program: t }) => {
61
- c(t, {
62
- pluginConfig: e.pluginConfig,
63
- openClawConfig: e.config,
64
- resolvePath: e.resolvePath
65
- });
66
- }, { descriptors: [{
67
- name: "cf-memory",
68
- description: "Manage Cloudflare Vectorize memory",
69
- hasSubcommands: !0
70
- }] }), o({
73
+ }), e.registerTool((t) => m(e.pluginConfig, t), { names: ["cloudflare_memory_search"] }), e.registerTool((t) => p(e.pluginConfig, t), { names: ["cloudflare_memory_get"] }), e.registerTool((t) => h(e.pluginConfig, t), { names: ["cloudflare_memory_upsert"] }), e.registerTool((t) => f(e.pluginConfig, t), { names: ["cloudflare_memory_delete"] }), o({
71
74
  pluginConfig: e.pluginConfig,
72
75
  openClawConfig: e.config,
73
76
  env: process.env,
@@ -77,10 +80,10 @@ var v = g({
77
80
  }).catch((t) => {
78
81
  let r = t instanceof Error ? t.message : "Unknown configuration error.";
79
82
  e.logger.warn(`${n}: deferred config validation reported: ${r}`);
80
- });
83
+ }));
81
84
  }
82
85
  });
83
86
  //#endregion
84
- export { v as default };
87
+ export { y as default };
85
88
 
86
89
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { MemoryEmbeddingProviderAdapter } from \"openclaw/plugin-sdk/memory-core-host-engine-embeddings\";\nimport { definePluginEntry, type OpenClawPluginApi } from \"openclaw/plugin-sdk/plugin-entry\";\nimport { registerCloudflareMemoryCli } from \"./cli.js\";\nimport { getPluginConfigFromOpenClawConfig, pluginConfigSchema, resolvePluginConfig } from \"./config.js\";\nimport { DEFAULT_EMBEDDING_MODEL, PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from \"./constants.js\";\nimport { buildPromptSection } from \"./prompt.js\";\nimport { createPublicArtifactsProvider } from \"./public-artifacts.js\";\nimport { createMemoryRuntime } from \"./runtime.js\";\nimport { CloudflareMemoryService } from \"./service.js\";\nimport { createDeleteTool, createGetTool, createSearchTool, createUpsertTool } from \"./tools.js\";\n\nfunction createMemoryEmbeddingProviderAdapter(): MemoryEmbeddingProviderAdapter {\n\treturn {\n\t\tid: \"cloudflare-workers-ai\",\n\t\tdefaultModel: DEFAULT_EMBEDDING_MODEL,\n\t\ttransport: \"remote\",\n\t\tallowExplicitWhenConfiguredAuto: true,\n\t\tasync create(options) {\n\t\t\tconst pluginConfig = getPluginConfigFromOpenClawConfig(options.config);\n\t\t\tconst resolved = await resolvePluginConfig({\n\t\t\t\tpluginConfig,\n\t\t\t\topenClawConfig: options.config,\n\t\t\t\tenv: process.env,\n\t\t\t});\n\t\t\tconst service = new CloudflareMemoryService(\n\t\t\t\t{\n\t\t\t\t\t...resolved,\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tworkersAiBaseUrl: options.remote?.baseUrl && options.remote.baseUrl.trim().length > 0 ? options.remote.baseUrl : resolved.workersAiBaseUrl,\n\t\t\t\t\tapiToken: typeof options.remote?.apiKey === \"string\" && options.remote.apiKey.trim().length > 0 ? options.remote.apiKey : resolved.apiToken,\n\t\t\t\t},\n\t\t\t\toptions.config,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tprovider: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tembedQuery: (text) => service.embeddings.embedQuery(text),\n\t\t\t\t\tembedBatch: (texts) => service.embeddings.embedBatch(texts),\n\t\t\t\t},\n\t\t\t\truntime: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tcacheKeyData: {\n\t\t\t\t\t\taccountId: resolved.accountId,\n\t\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport default definePluginEntry({\n\tid: PLUGIN_ID,\n\tname: PLUGIN_NAME,\n\tdescription: PLUGIN_DESCRIPTION,\n\tkind: \"memory\",\n\tconfigSchema: pluginConfigSchema,\n\tregister(api: OpenClawPluginApi) {\n\t\tpluginConfigSchema.parse?.(api.pluginConfig ?? {});\n\n\t\tapi.registerMemoryEmbeddingProvider(createMemoryEmbeddingProviderAdapter());\n\t\tapi.registerMemoryCapability({\n\t\t\tpromptBuilder: buildPromptSection,\n\t\t\truntime: createMemoryRuntime({\n\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t}),\n\t\t\tpublicArtifacts: createPublicArtifactsProvider(api.pluginConfig, api.resolvePath),\n\t\t});\n\n\t\tapi.registerTool((ctx) => createSearchTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_search\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createGetTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_get\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createUpsertTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_upsert\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createDeleteTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_delete\"],\n\t\t});\n\n\t\tapi.registerCli(\n\t\t\t({ program }) => {\n\t\t\t\tregisterCloudflareMemoryCli(program, {\n\t\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\t\topenClawConfig: api.config,\n\t\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t\t});\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescriptors: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"cf-memory\",\n\t\t\t\t\t\tdescription: \"Manage Cloudflare Vectorize memory\",\n\t\t\t\t\t\thasSubcommands: true,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t);\n\n\t\tvoid resolvePluginConfig({\n\t\t\tpluginConfig: api.pluginConfig,\n\t\t\topenClawConfig: api.config,\n\t\t\tenv: process.env,\n\t\t\tresolvePath: api.resolvePath,\n\t\t})\n\t\t\t.then((resolved) => {\n\t\t\t\tapi.logger.info(`${PLUGIN_ID}: registered for index ${resolved.indexName} using model ${resolved.model}.`);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"Unknown configuration error.\";\n\t\t\t\tapi.logger.warn(`${PLUGIN_ID}: deferred config validation reported: ${message}`);\n\t\t\t});\n\t},\n});\n"],"mappings":";;;;;;;;;;AAWA,SAAS,IAAuE;AAC/E,QAAO;EACN,IAAI;EACJ,cAAc;EACd,WAAW;EACX,iCAAiC;EACjC,MAAM,OAAO,GAAS;GAErB,IAAM,IAAW,MAAM,EAAoB;IAC1C,cAFoB,EAAkC,EAAQ,OAAO;IAGrE,gBAAgB,EAAQ;IACxB,KAAK,QAAQ;IACb,CAAC,EACI,IAAU,IAAI,EACnB;IACC,GAAG;IACH,OAAO,EAAQ,SAAS,EAAS;IACjC,kBAAkB,EAAQ,QAAQ,WAAW,EAAQ,OAAO,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,UAAU,EAAS;IAC1H,UAAU,OAAO,EAAQ,QAAQ,UAAW,YAAY,EAAQ,OAAO,OAAO,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,SAAS,EAAS;IACnI,EACD,EAAQ,OACR;AACD,UAAO;IACN,UAAU;KACT,IAAI;KACJ,OAAO,EAAQ,SAAS,EAAS;KACjC,aAAa,MAAS,EAAQ,WAAW,WAAW,EAAK;KACzD,aAAa,MAAU,EAAQ,WAAW,WAAW,EAAM;KAC3D;IACD,SAAS;KACR,IAAI;KACJ,cAAc;MACb,WAAW,EAAS;MACpB,OAAO,EAAQ,SAAS,EAAS;MACjC;KACD;IACD;;EAEF;;AAGF,IAAA,IAAe,EAAkB;CAChC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,MAAM;CACN,cAAc;CACd,SAAS,GAAwB;AA6C3B,EA5CL,EAAmB,QAAQ,EAAI,gBAAgB,EAAE,CAAC,EAElD,EAAI,gCAAgC,GAAsC,CAAC,EAC3E,EAAI,yBAAyB;GAC5B,eAAe;GACf,SAAS,EAAoB;IAC5B,cAAc,EAAI;IAClB,aAAa,EAAI;IACjB,CAAC;GACF,iBAAiB,EAA8B,EAAI,cAAc,EAAI,YAAY;GACjF,CAAC,EAEF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAc,EAAI,cAAc,EAAI,EAAE,EAC/D,OAAO,CAAC,wBAAwB,EAChC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EAEF,EAAI,aACF,EAAE,iBAAc;AAChB,KAA4B,GAAS;IACpC,cAAc,EAAI;IAClB,gBAAgB,EAAI;IACpB,aAAa,EAAI;IACjB,CAAC;KAEH,EACC,aAAa,CACZ;GACC,MAAM;GACN,aAAa;GACb,gBAAgB;GAChB,CACD,EACD,CACD,EAEI,EAAoB;GACxB,cAAc,EAAI;GAClB,gBAAgB,EAAI;GACpB,KAAK,QAAQ;GACb,aAAa,EAAI;GACjB,CAAC,CACA,MAAM,MAAa;AACnB,KAAI,OAAO,KAAK,GAAG,EAAU,yBAAyB,EAAS,UAAU,eAAe,EAAS,MAAM,GAAG;IACzG,CACD,OAAO,MAAmB;GAC1B,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU;AACzD,KAAI,OAAO,KAAK,GAAG,EAAU,yCAAyC,IAAU;IAC/E;;CAEJ,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { MemoryEmbeddingProviderAdapter } from \"openclaw/plugin-sdk/memory-core-host-engine-embeddings\";\nimport { definePluginEntry, type OpenClawPluginApi } from \"openclaw/plugin-sdk/plugin-entry\";\nimport { registerCloudflareMemoryCli } from \"./cli.js\";\nimport { getPluginConfigFromOpenClawConfig, pluginConfigSchema, resolvePluginConfig } from \"./config.js\";\nimport { DEFAULT_EMBEDDING_MODEL, PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from \"./constants.js\";\nimport { buildPromptSection } from \"./prompt.js\";\nimport { createPublicArtifactsProvider } from \"./public-artifacts.js\";\nimport { createMemoryRuntime } from \"./runtime.js\";\nimport { CloudflareMemoryService } from \"./service.js\";\nimport { createDeleteTool, createGetTool, createSearchTool, createUpsertTool } from \"./tools.js\";\n\nfunction createMemoryEmbeddingProviderAdapter(): MemoryEmbeddingProviderAdapter {\n\treturn {\n\t\tid: \"cloudflare-workers-ai\",\n\t\tdefaultModel: DEFAULT_EMBEDDING_MODEL,\n\t\ttransport: \"remote\",\n\t\tallowExplicitWhenConfiguredAuto: true,\n\t\tasync create(options) {\n\t\t\tconst pluginConfig = getPluginConfigFromOpenClawConfig(options.config);\n\t\t\tconst resolved = await resolvePluginConfig({\n\t\t\t\tpluginConfig,\n\t\t\t\topenClawConfig: options.config,\n\t\t\t\tenv: process.env,\n\t\t\t});\n\t\t\tconst service = new CloudflareMemoryService(\n\t\t\t\t{\n\t\t\t\t\t...resolved,\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tworkersAiBaseUrl: options.remote?.baseUrl && options.remote.baseUrl.trim().length > 0 ? options.remote.baseUrl : resolved.workersAiBaseUrl,\n\t\t\t\t\tapiToken: typeof options.remote?.apiKey === \"string\" && options.remote.apiKey.trim().length > 0 ? options.remote.apiKey : resolved.apiToken,\n\t\t\t\t},\n\t\t\t\toptions.config,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tprovider: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tembedQuery: (text) => service.embeddings.embedQuery(text),\n\t\t\t\t\tembedBatch: (texts) => service.embeddings.embedBatch(texts),\n\t\t\t\t},\n\t\t\t\truntime: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tcacheKeyData: {\n\t\t\t\t\t\taccountId: resolved.accountId,\n\t\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction registerCloudflareMemoryCliEntry(api: Pick<OpenClawPluginApi, \"registerCli\" | \"pluginConfig\" | \"config\" | \"resolvePath\">): void {\n\tapi.registerCli(\n\t\t({ program }) => {\n\t\t\tregisterCloudflareMemoryCli(program, {\n\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\topenClawConfig: api.config,\n\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t});\n\t\t},\n\t\t{\n\t\t\tdescriptors: [\n\t\t\t\t{\n\t\t\t\t\tname: \"cf-memory\",\n\t\t\t\t\tdescription: \"Manage Cloudflare Vectorize memory\",\n\t\t\t\t\thasSubcommands: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t);\n}\n\nexport default definePluginEntry({\n\tid: PLUGIN_ID,\n\tname: PLUGIN_NAME,\n\tdescription: PLUGIN_DESCRIPTION,\n\tkind: \"memory\",\n\tconfigSchema: pluginConfigSchema,\n\tregister(api: OpenClawPluginApi) {\n\t\tpluginConfigSchema.parse?.(api.pluginConfig ?? {});\n\n\t\tregisterCloudflareMemoryCliEntry(api);\n\n\t\tif (\n\t\t\tapi.registrationMode === \"cli-metadata\" ||\n\t\t\ttypeof api.registerMemoryEmbeddingProvider !== \"function\" ||\n\t\t\ttypeof api.registerMemoryCapability !== \"function\" ||\n\t\t\ttypeof api.registerTool !== \"function\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tapi.registerMemoryEmbeddingProvider(createMemoryEmbeddingProviderAdapter());\n\t\tapi.registerMemoryCapability({\n\t\t\tpromptBuilder: buildPromptSection,\n\t\t\truntime: createMemoryRuntime({\n\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t}),\n\t\t\tpublicArtifacts: createPublicArtifactsProvider(api.pluginConfig, api.resolvePath),\n\t\t});\n\n\t\tapi.registerTool((ctx) => createSearchTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_search\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createGetTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_get\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createUpsertTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_upsert\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createDeleteTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_delete\"],\n\t\t});\n\n\t\tvoid resolvePluginConfig({\n\t\t\tpluginConfig: api.pluginConfig,\n\t\t\topenClawConfig: api.config,\n\t\t\tenv: process.env,\n\t\t\tresolvePath: api.resolvePath,\n\t\t})\n\t\t\t.then((resolved) => {\n\t\t\t\tapi.logger.info(`${PLUGIN_ID}: registered for index ${resolved.indexName} using model ${resolved.model}.`);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"Unknown configuration error.\";\n\t\t\t\tapi.logger.warn(`${PLUGIN_ID}: deferred config validation reported: ${message}`);\n\t\t\t});\n\t},\n});\n"],"mappings":";;;;;;;;;;AAWA,SAAS,IAAuE;AAC/E,QAAO;EACN,IAAI;EACJ,cAAc;EACd,WAAW;EACX,iCAAiC;EACjC,MAAM,OAAO,GAAS;GAErB,IAAM,IAAW,MAAM,EAAoB;IAC1C,cAFoB,EAAkC,EAAQ,OAAO;IAGrE,gBAAgB,EAAQ;IACxB,KAAK,QAAQ;IACb,CAAC,EACI,IAAU,IAAI,EACnB;IACC,GAAG;IACH,OAAO,EAAQ,SAAS,EAAS;IACjC,kBAAkB,EAAQ,QAAQ,WAAW,EAAQ,OAAO,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,UAAU,EAAS;IAC1H,UAAU,OAAO,EAAQ,QAAQ,UAAW,YAAY,EAAQ,OAAO,OAAO,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,SAAS,EAAS;IACnI,EACD,EAAQ,OACR;AACD,UAAO;IACN,UAAU;KACT,IAAI;KACJ,OAAO,EAAQ,SAAS,EAAS;KACjC,aAAa,MAAS,EAAQ,WAAW,WAAW,EAAK;KACzD,aAAa,MAAU,EAAQ,WAAW,WAAW,EAAM;KAC3D;IACD,SAAS;KACR,IAAI;KACJ,cAAc;MACb,WAAW,EAAS;MACpB,OAAO,EAAQ,SAAS,EAAS;MACjC;KACD;IACD;;EAEF;;AAGF,SAAS,EAAiC,GAA+F;AACxI,GAAI,aACF,EAAE,iBAAc;AAChB,IAA4B,GAAS;GACpC,cAAc,EAAI;GAClB,gBAAgB,EAAI;GACpB,aAAa,EAAI;GACjB,CAAC;IAEH,EACC,aAAa,CACZ;EACC,MAAM;EACN,aAAa;EACb,gBAAgB;EAChB,CACD,EACD,CACD;;AAGF,IAAA,IAAe,EAAkB;CAChC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,MAAM;CACN,cAAc;CACd,SAAS,GAAwB;AAChC,IAAmB,QAAQ,EAAI,gBAAgB,EAAE,CAAC,EAElD,EAAiC,EAAI,EAGpC,IAAI,qBAAqB,kBACzB,OAAO,EAAI,mCAAoC,cAC/C,OAAO,EAAI,4BAA6B,cACxC,OAAO,EAAI,gBAAiB,gBAK7B,EAAI,gCAAgC,GAAsC,CAAC,EAC3E,EAAI,yBAAyB;GAC5B,eAAe;GACf,SAAS,EAAoB;IAC5B,cAAc,EAAI;IAClB,aAAa,EAAI;IACjB,CAAC;GACF,iBAAiB,EAA8B,EAAI,cAAc,EAAI,YAAY;GACjF,CAAC,EAEF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAc,EAAI,cAAc,EAAI,EAAE,EAC/D,OAAO,CAAC,wBAAwB,EAChC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EAEG,EAAoB;GACxB,cAAc,EAAI;GAClB,gBAAgB,EAAI;GACpB,KAAK,QAAQ;GACb,aAAa,EAAI;GACjB,CAAC,CACA,MAAM,MAAa;AACnB,KAAI,OAAO,KAAK,GAAG,EAAU,yBAAyB,EAAS,UAAU,eAAe,EAAS,MAAM,GAAG;IACzG,CACD,OAAO,MAAmB;GAC1B,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU;AACzD,KAAI,OAAO,KAAK,GAAG,EAAU,yCAAyC,IAAU;IAC/E;;CAEJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"migration.js","names":[],"sources":["../src/migration.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\r\nimport { glob, readFile, stat } from \"node:fs/promises\";\r\nimport { basename, extname, isAbsolute, relative, resolve } from \"node:path\";\r\nimport { listMemoryFiles } from \"openclaw/plugin-sdk/memory-core\";\r\nimport { sanitizeNamespace } from \"./namespace.js\";\r\nimport type { CloudflareMemoryService } from \"./service.js\";\r\nimport type {\r\n\tDoctorReport,\r\n\tMetadataValue,\r\n\tMigrationDuplicateStrategy,\r\n\tMigrationNamespaceStrategy,\r\n\tMigrationResult,\r\n\tMigrationRunOptions,\r\n\tMigrationSourceMode,\r\n\tMigrationSummary,\r\n} from \"./types.js\";\r\n\r\ntype DiscoveredMigrationFile = {\r\n\tabsolutePath: string;\r\n\trelativePath: string;\r\n};\r\n\r\ntype ParsedMigrationRecord = {\r\n\tinput: {\r\n\t\tid: string;\r\n\t\ttext: string;\r\n\t\ttitle?: string;\r\n\t\tmetadata: Record<string, MetadataValue>;\r\n\t\tnamespace?: string;\r\n\t\tsource?: string;\r\n\t};\r\n\tsourcePath: string;\r\n\trelativePath: string;\r\n};\r\n\r\nconst MARKDOWN_EXTENSIONS = new Set([\".md\", \".markdown\"]);\r\nconst RESERVED_FRONTMATTER_FIELDS = new Set([\"id\", \"namespace\", \"source\", \"title\"]);\r\n\r\nfunction normalizePathForMetadata(value: string): string {\r\n\treturn value.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\r\n}\r\n\r\nfunction normalizeRelativePath(value: string): string {\r\n\tconst normalized = normalizePathForMetadata(value).replace(/^\\/+/, \"\");\r\n\treturn normalized || basename(value);\r\n}\r\n\r\nfunction isMarkdownFile(value: string): boolean {\r\n\treturn MARKDOWN_EXTENSIONS.has(extname(value).toLowerCase());\r\n}\r\n\r\nfunction hasGlobMagic(value: string): boolean {\r\n\treturn /[*?[\\]{}]/.test(value);\r\n}\r\n\r\nfunction shouldIgnoreDiscoveredFile(value: string): boolean {\r\n\tconst normalized = normalizePathForMetadata(value).toLowerCase();\r\n\treturn normalized.includes(\"/node_modules/\") || normalized.includes(\"/.git/\");\r\n}\r\n\r\nasync function statIfExists(path: string) {\r\n\ttry {\r\n\t\treturn await stat(path);\r\n\t} catch {\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nasync function collectDirectoryMarkdownFiles(directory: string): Promise<string[]> {\r\n\tconst matches: string[] = [];\r\n\tfor await (const match of glob(\"**/*.{md,markdown}\", { cwd: directory })) {\r\n\t\tconst absolutePath = resolve(directory, match);\r\n\t\tif (!shouldIgnoreDiscoveredFile(absolutePath)) {\r\n\t\t\tmatches.push(absolutePath);\r\n\t\t}\r\n\t}\r\n\treturn matches;\r\n}\r\n\r\nasync function collectGlobMatches(pattern: string, workspaceDir: string): Promise<string[]> {\r\n\tconst matches: string[] = [];\r\n\tconst normalizedPattern = normalizePathForMetadata(pattern);\r\n\tconst iterator = isAbsolute(pattern) ? glob(normalizedPattern) : glob(normalizedPattern, { cwd: workspaceDir });\r\n\tfor await (const match of iterator) {\r\n\t\tconst absolutePath = isAbsolute(match) ? match : resolve(workspaceDir, match);\r\n\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(absolutePath)) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst fileStats = await statIfExists(absolutePath);\r\n\t\tif (fileStats?.isFile()) {\r\n\t\t\tmatches.push(absolutePath);\r\n\t\t}\r\n\t}\r\n\treturn matches;\r\n}\r\n\r\nexport async function discoverMigrationFiles(params: {\r\n\tworkspaceDir: string;\r\n\tsourceMode: MigrationSourceMode;\r\n\tsourcePaths?: string[];\r\n}): Promise<DiscoveredMigrationFile[]> {\r\n\tconst workspaceDir = resolve(params.workspaceDir);\r\n\tconst discovered = new Map<string, DiscoveredMigrationFile>();\r\n\r\n\tif (params.sourceMode === \"default-provider\") {\r\n\t\tfor (const relPath of await listMemoryFiles(workspaceDir)) {\r\n\t\t\tconst relativePath = normalizeRelativePath(relPath);\r\n\t\t\tconst absolutePath = resolve(workspaceDir, relativePath);\r\n\t\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(relativePath)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tconst fileStats = await statIfExists(absolutePath);\r\n\t\t\tif (fileStats?.isFile()) {\r\n\t\t\t\tdiscovered.set(absolutePath.toLowerCase(), {\r\n\t\t\t\t\tabsolutePath,\r\n\t\t\t\t\trelativePath,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tfor (const input of params.sourcePaths ?? []) {\r\n\t\tconst absoluteInput = resolve(workspaceDir, input);\r\n\t\tconst fileStats = await statIfExists(absoluteInput);\r\n\t\tlet matches: string[] = [];\r\n\t\tif (fileStats?.isDirectory()) {\r\n\t\t\tmatches = await collectDirectoryMarkdownFiles(absoluteInput);\r\n\t\t} else if (fileStats?.isFile()) {\r\n\t\t\tmatches = isMarkdownFile(absoluteInput) ? [absoluteInput] : [];\r\n\t\t} else if (hasGlobMagic(input)) {\r\n\t\t\tmatches = await collectGlobMatches(input, workspaceDir);\r\n\t\t}\r\n\r\n\t\tfor (const match of matches) {\r\n\t\t\tconst relativePath = normalizeRelativePath(relative(workspaceDir, match));\r\n\t\t\tdiscovered.set(match.toLowerCase(), {\r\n\t\t\t\tabsolutePath: match,\r\n\t\t\t\trelativePath,\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\treturn [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));\r\n}\r\n\r\nfunction parseFrontmatterValue(value: string): MetadataValue | undefined {\r\n\tconst trimmed = value.trim();\r\n\tif (!trimmed) {\r\n\t\treturn \"\";\r\n\t}\r\n\tif (/^\"(.*)\"$/.test(trimmed) || /^'(.*)'$/.test(trimmed)) {\r\n\t\treturn trimmed.slice(1, -1);\r\n\t}\r\n\tif (trimmed === \"true\") {\r\n\t\treturn true;\r\n\t}\r\n\tif (trimmed === \"false\") {\r\n\t\treturn false;\r\n\t}\r\n\tconst numeric = Number(trimmed);\r\n\tif (!Number.isNaN(numeric) && trimmed !== \"\") {\r\n\t\treturn numeric;\r\n\t}\r\n\tif (trimmed.startsWith(\"[\") || trimmed.startsWith(\"{\")) {\r\n\t\treturn undefined;\r\n\t}\r\n\treturn trimmed;\r\n}\r\n\r\nfunction parseFrontmatter(content: string): { body: string; attributes: Record<string, MetadataValue> } {\r\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\");\r\n\tif (!normalized.startsWith(\"---\\n\")) {\r\n\t\treturn { body: normalized, attributes: {} };\r\n\t}\r\n\r\n\tconst lines = normalized.split(\"\\n\");\r\n\tconst closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === \"---\");\r\n\tif (closingIndex === -1) {\r\n\t\treturn { body: normalized, attributes: {} };\r\n\t}\r\n\r\n\tconst attributes: Record<string, MetadataValue> = {};\r\n\tfor (const line of lines.slice(1, closingIndex)) {\r\n\t\tconst separatorIndex = line.indexOf(\":\");\r\n\t\tif (separatorIndex === -1) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst key = line.slice(0, separatorIndex).trim();\r\n\t\tif (!key) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst parsedValue = parseFrontmatterValue(line.slice(separatorIndex + 1));\r\n\t\tif (parsedValue !== undefined) {\r\n\t\t\tattributes[key] = parsedValue;\r\n\t\t}\r\n\t}\r\n\r\n\treturn {\r\n\t\tbody: lines.slice(closingIndex + 1).join(\"\\n\"),\r\n\t\tattributes,\r\n\t};\r\n}\r\n\r\nfunction extractHeadingTitleAndBody(content: string): { title?: string; text: string } {\r\n\tconst lines = content.split(\"\\n\");\r\n\tconst firstContentLine = lines.findIndex((line) => line.trim().length > 0);\r\n\tif (firstContentLine === -1) {\r\n\t\treturn { text: \"\" };\r\n\t}\r\n\r\n\tconst headingMatch = /^#\\s+(.+?)\\s*$/.exec(lines[firstContentLine]?.trim() ?? \"\");\r\n\tif (!headingMatch) {\r\n\t\treturn { text: content.trim() };\r\n\t}\r\n\r\n\tconst remainingLines = [...lines];\r\n\tremainingLines.splice(firstContentLine, 1);\r\n\tif ((remainingLines[firstContentLine] ?? \"\").trim() === \"\") {\r\n\t\tremainingLines.splice(firstContentLine, 1);\r\n\t}\r\n\tconst body = remainingLines.join(\"\\n\").trim();\r\n\treturn {\r\n\t\ttitle: headingMatch[1].trim(),\r\n\t\ttext: body || content.trim(),\r\n\t};\r\n}\r\n\r\nfunction buildStableLogicalId(relativePath: string): string {\r\n\tconst withoutExtension = relativePath.replace(/\\.(md|markdown)$/i, \"\");\r\n\tconst slug = withoutExtension\r\n\t\t.toLowerCase()\r\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\r\n\t\t.replace(/-{2,}/g, \"-\")\r\n\t\t.replace(/^-+|-+$/g, \"\")\r\n\t\t.slice(0, 80);\r\n\tconst hash = createHash(\"sha1\").update(relativePath).digest(\"hex\").slice(0, 10);\r\n\treturn `${slug || \"memory\"}-${hash}`;\r\n}\r\n\r\nfunction pickTitle(relativePath: string, frontmatterTitle: MetadataValue | undefined, headingTitle: string | undefined): string | undefined {\r\n\tif (typeof frontmatterTitle === \"string\" && frontmatterTitle.trim().length > 0) {\r\n\t\treturn frontmatterTitle.trim();\r\n\t}\r\n\tif (headingTitle) {\r\n\t\treturn headingTitle;\r\n\t}\r\n\tconst fileName = basename(relativePath, extname(relativePath)).trim();\r\n\treturn fileName || undefined;\r\n}\r\n\r\nfunction buildTargetNamespace(service: CloudflareMemoryService, options: MigrationRunOptions, workspaceDir: string): string {\r\n\treturn service.resolveNamespace({\r\n\t\tnamespace: options.namespace,\r\n\t\tworkspaceDir,\r\n\t});\r\n}\r\n\r\nfunction deriveRecordNamespace(params: {\r\n\trelativePath: string;\r\n\tfrontmatterNamespace: MetadataValue | undefined;\r\n\ttargetNamespace: string;\r\n\tnamespaceStrategy: MigrationNamespaceStrategy;\r\n}): string {\r\n\tif (params.namespaceStrategy === \"single-target\") {\r\n\t\treturn params.targetNamespace;\r\n\t}\r\n\r\n\tif (typeof params.frontmatterNamespace === \"string\" && params.frontmatterNamespace.trim().length > 0) {\r\n\t\treturn sanitizeNamespace(params.frontmatterNamespace);\r\n\t}\r\n\r\n\tconst firstSegment = normalizeRelativePath(params.relativePath).split(\"/\")[0];\r\n\tif (!firstSegment || firstSegment === \".\" || firstSegment === \"..\") {\r\n\t\treturn params.targetNamespace;\r\n\t}\r\n\treturn sanitizeNamespace(firstSegment);\r\n}\r\n\r\nexport async function parseMigrationFile(params: {\r\n\tfile: DiscoveredMigrationFile;\r\n\tsourceMode: MigrationSourceMode;\r\n\ttargetNamespace: string;\r\n\tnamespaceStrategy: MigrationNamespaceStrategy;\r\n}): Promise<ParsedMigrationRecord | null> {\r\n\tconst raw = await readFile(params.file.absolutePath, \"utf8\");\r\n\tconst { body, attributes } = parseFrontmatter(raw);\r\n\tconst { title: headingTitle, text: extractedText } = extractHeadingTitleAndBody(body);\r\n\tconst title = pickTitle(params.file.relativePath, attributes.title, headingTitle);\r\n\tconst text = extractedText.trim() || title || \"\";\r\n\tif (!text) {\r\n\t\treturn null;\r\n\t}\r\n\r\n\tconst metadata: Record<string, MetadataValue> = {\r\n\t\tlegacySourceMode: params.sourceMode,\r\n\t\tlegacySourcePath: params.file.relativePath.startsWith(\"..\") ? params.file.absolutePath : params.file.relativePath,\r\n\t};\r\n\tfor (const [key, value] of Object.entries(attributes)) {\r\n\t\tif (RESERVED_FRONTMATTER_FIELDS.has(key)) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tmetadata[key] = value;\r\n\t}\r\n\r\n\tconst logicalId =\r\n\t\ttypeof attributes.id === \"string\" && attributes.id.trim().length > 0 ? attributes.id.trim() : buildStableLogicalId(params.file.relativePath);\r\n\tconst namespace = deriveRecordNamespace({\r\n\t\trelativePath: params.file.relativePath,\r\n\t\tfrontmatterNamespace: attributes.namespace,\r\n\t\ttargetNamespace: params.targetNamespace,\r\n\t\tnamespaceStrategy: params.namespaceStrategy,\r\n\t});\r\n\tconst source =\r\n\t\ttypeof attributes.source === \"string\" && attributes.source.trim().length > 0\r\n\t\t\t? attributes.source.trim()\r\n\t\t\t: params.sourceMode === \"default-provider\"\r\n\t\t\t\t? \"openclaw-default-memory\"\r\n\t\t\t\t: \"markdown-import\";\r\n\r\n\treturn {\r\n\t\tsourcePath: params.file.absolutePath,\r\n\t\trelativePath: params.file.relativePath,\r\n\t\tinput: {\r\n\t\t\tid: logicalId,\r\n\t\t\tnamespace,\r\n\t\t\ttitle,\r\n\t\t\ttext,\r\n\t\t\tsource,\r\n\t\t\tmetadata,\r\n\t\t},\r\n\t};\r\n}\r\n\r\nfunction formatDoctorFailure(report: DoctorReport): string {\r\n\tconst failedChecks = report.checks.filter((check) => check.status === \"fail\");\r\n\treturn failedChecks.map((check) => `${check.name}: ${check.message}`).join(\" | \");\r\n}\r\n\r\nexport async function runCloudflareMemoryMigration(params: {\r\n\tservice: CloudflareMemoryService;\r\n\toptions?: MigrationRunOptions;\r\n}): Promise<MigrationSummary> {\r\n\tconst options = params.options ?? {};\r\n\tconst workspaceDir = resolve(options.workspaceDir ?? process.cwd());\r\n\tconst sourceMode: MigrationSourceMode = (options.sourcePaths?.length ?? 0) > 0 ? \"paths\" : \"default-provider\";\r\n\tconst namespaceStrategy = options.namespaceStrategy ?? \"single-target\";\r\n\tconst duplicateStrategy: MigrationDuplicateStrategy = options.duplicateStrategy ?? \"overwrite\";\r\n\tconst dryRun = options.dryRun ?? false;\r\n\tconst targetNamespace = buildTargetNamespace(params.service, options, workspaceDir);\r\n\tconst doctor = await params.service.doctor({\r\n\t\tcreateIndexIfMissing: options.createIndexIfMissing ?? false,\r\n\t});\r\n\tif (!doctor.ok) {\r\n\t\tthrow new Error(`Migration validation failed. ${formatDoctorFailure(doctor)}`);\r\n\t}\r\n\r\n\tconst discoveredFiles = await discoverMigrationFiles({\r\n\t\tworkspaceDir,\r\n\t\tsourceMode,\r\n\t\tsourcePaths: options.sourcePaths,\r\n\t});\r\n\tif (discoveredFiles.length === 0) {\r\n\t\tthrow new Error(\r\n\t\t\tsourceMode === \"default-provider\"\r\n\t\t\t\t? `No default OpenClaw markdown memory files were found under ${workspaceDir}.`\r\n\t\t\t\t: \"No markdown files matched the provided migration sources.\",\r\n\t\t);\r\n\t}\r\n\r\n\tconst results: MigrationResult[] = [];\r\n\tlet preparedRecords = 0;\r\n\tlet imported = 0;\r\n\tlet skipped = 0;\r\n\tlet failed = 0;\r\n\r\n\tfor (const file of discoveredFiles) {\r\n\t\ttry {\r\n\t\t\tconst parsed = await parseMigrationFile({\r\n\t\t\t\tfile,\r\n\t\t\t\tsourceMode,\r\n\t\t\t\ttargetNamespace,\r\n\t\t\t\tnamespaceStrategy,\r\n\t\t\t});\r\n\t\t\tif (!parsed) {\r\n\t\t\t\tskipped += 1;\r\n\t\t\t\tresults.push({\r\n\t\t\t\t\taction: \"skipped\",\r\n\t\t\t\t\tsourcePath: file.absolutePath,\r\n\t\t\t\t\trelativePath: file.relativePath,\r\n\t\t\t\t\treason: \"File did not contain any importable markdown content.\",\r\n\t\t\t\t});\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tpreparedRecords += 1;\r\n\t\t\tconst logicalId = parsed.input.id;\r\n\t\t\tconst namespace = parsed.input.namespace;\r\n\t\t\tif (!logicalId || !namespace) {\r\n\t\t\t\tthrow new Error(\"Parsed migration record is missing a logical id or namespace.\");\r\n\t\t\t}\r\n\r\n\t\t\tif (duplicateStrategy !== \"overwrite\") {\r\n\t\t\t\tconst existing = await params.service.get({\r\n\t\t\t\t\tid: logicalId,\r\n\t\t\t\t\tnamespace,\r\n\t\t\t\t});\r\n\t\t\t\tif (existing) {\r\n\t\t\t\t\tif (duplicateStrategy === \"skip\") {\r\n\t\t\t\t\t\tskipped += 1;\r\n\t\t\t\t\t\tresults.push({\r\n\t\t\t\t\t\t\taction: \"skipped\",\r\n\t\t\t\t\t\t\tsourcePath: parsed.sourcePath,\r\n\t\t\t\t\t\t\trelativePath: parsed.relativePath,\r\n\t\t\t\t\t\t\tlogicalId,\r\n\t\t\t\t\t\t\tnamespace,\r\n\t\t\t\t\t\t\ttitle: parsed.input.title,\r\n\t\t\t\t\t\t\treason: \"A record with the same logical id already exists.\",\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthrow new Error(`A record with logical id ${logicalId} already exists in namespace ${namespace}.`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (dryRun) {\r\n\t\t\t\tresults.push({\r\n\t\t\t\t\taction: \"would-import\",\r\n\t\t\t\t\tsourcePath: parsed.sourcePath,\r\n\t\t\t\t\trelativePath: parsed.relativePath,\r\n\t\t\t\t\tlogicalId,\r\n\t\t\t\t\tnamespace,\r\n\t\t\t\t\ttitle: parsed.input.title,\r\n\t\t\t\t});\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tawait params.service.upsert({\r\n\t\t\t\tinput: parsed.input,\r\n\t\t\t});\r\n\t\t\timported += 1;\r\n\t\t\tresults.push({\r\n\t\t\t\taction: \"imported\",\r\n\t\t\t\tsourcePath: parsed.sourcePath,\r\n\t\t\t\trelativePath: parsed.relativePath,\r\n\t\t\t\tlogicalId,\r\n\t\t\t\tnamespace,\r\n\t\t\t\ttitle: parsed.input.title,\r\n\t\t\t});\r\n\t\t} catch (error) {\r\n\t\t\tfailed += 1;\r\n\t\t\tresults.push({\r\n\t\t\t\taction: \"failed\",\r\n\t\t\t\tsourcePath: file.absolutePath,\r\n\t\t\t\trelativePath: file.relativePath,\r\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown migration failure.\",\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\treturn {\r\n\t\tdryRun,\r\n\t\tsourceMode,\r\n\t\tworkspaceDir,\r\n\t\tnamespaceStrategy,\r\n\t\ttargetNamespace: namespaceStrategy === \"single-target\" ? targetNamespace : undefined,\r\n\t\tdiscoveredFiles: discoveredFiles.length,\r\n\t\tpreparedRecords,\r\n\t\timported,\r\n\t\tskipped,\r\n\t\tfailed,\r\n\t\tdoctor,\r\n\t\tresults,\r\n\t};\r\n}\r\n\r\nexport function formatMigrationSummary(summary: MigrationSummary): string {\r\n\tconst lines = [\r\n\t\t`${summary.dryRun ? \"Dry-run\" : \"Migration\"} ${summary.failed > 0 ? \"completed with failures\" : \"completed\"}.`,\r\n\t\t`Source mode: ${summary.sourceMode}`,\r\n\t\t`Workspace: ${summary.workspaceDir}`,\r\n\t\t`Files scanned: ${summary.discoveredFiles}`,\r\n\t\t`Records prepared: ${summary.preparedRecords}`,\r\n\t\t`Imported: ${summary.imported}`,\r\n\t\t`Skipped: ${summary.skipped}`,\r\n\t\t`Failed: ${summary.failed}`,\r\n\t];\r\n\r\n\tif (summary.targetNamespace) {\r\n\t\tlines.splice(3, 0, `Target namespace: ${summary.targetNamespace}`);\r\n\t}\r\n\r\n\tconst failedResults = summary.results.filter((result) => result.action === \"failed\").slice(0, 10);\r\n\tif (failedResults.length > 0) {\r\n\t\tlines.push(\"\", \"Failures:\");\r\n\t\tfor (const result of failedResults) {\r\n\t\t\tlines.push(`- ${result.relativePath}: ${result.error}`);\r\n\t\t}\r\n\t}\r\n\r\n\treturn lines.join(\"\\n\");\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration.js","names":[],"sources":["../src/migration.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { glob, readFile, stat } from \"node:fs/promises\";\nimport { basename, extname, isAbsolute, relative, resolve } from \"node:path\";\nimport { listMemoryFiles } from \"openclaw/plugin-sdk/memory-core\";\nimport { sanitizeNamespace } from \"./namespace.js\";\nimport type { CloudflareMemoryService } from \"./service.js\";\nimport type {\n\tDoctorReport,\n\tMetadataValue,\n\tMigrationDuplicateStrategy,\n\tMigrationNamespaceStrategy,\n\tMigrationResult,\n\tMigrationRunOptions,\n\tMigrationSourceMode,\n\tMigrationSummary,\n} from \"./types.js\";\n\ntype DiscoveredMigrationFile = {\n\tabsolutePath: string;\n\trelativePath: string;\n};\n\ntype ParsedMigrationRecord = {\n\tinput: {\n\t\tid: string;\n\t\ttext: string;\n\t\ttitle?: string;\n\t\tmetadata: Record<string, MetadataValue>;\n\t\tnamespace?: string;\n\t\tsource?: string;\n\t};\n\tsourcePath: string;\n\trelativePath: string;\n};\n\nconst MARKDOWN_EXTENSIONS = new Set([\".md\", \".markdown\"]);\nconst RESERVED_FRONTMATTER_FIELDS = new Set([\"id\", \"namespace\", \"source\", \"title\"]);\n\nfunction normalizePathForMetadata(value: string): string {\n\treturn value.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n\nfunction normalizeRelativePath(value: string): string {\n\tconst normalized = normalizePathForMetadata(value).replace(/^\\/+/, \"\");\n\treturn normalized || basename(value);\n}\n\nfunction isMarkdownFile(value: string): boolean {\n\treturn MARKDOWN_EXTENSIONS.has(extname(value).toLowerCase());\n}\n\nfunction hasGlobMagic(value: string): boolean {\n\treturn /[*?[\\]{}]/.test(value);\n}\n\nfunction shouldIgnoreDiscoveredFile(value: string): boolean {\n\tconst normalized = normalizePathForMetadata(value).toLowerCase();\n\treturn normalized.includes(\"/node_modules/\") || normalized.includes(\"/.git/\");\n}\n\nasync function statIfExists(path: string) {\n\ttry {\n\t\treturn await stat(path);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function collectDirectoryMarkdownFiles(directory: string): Promise<string[]> {\n\tconst matches: string[] = [];\n\tfor await (const match of glob(\"**/*.{md,markdown}\", { cwd: directory })) {\n\t\tconst absolutePath = resolve(directory, match);\n\t\tif (!shouldIgnoreDiscoveredFile(absolutePath)) {\n\t\t\tmatches.push(absolutePath);\n\t\t}\n\t}\n\treturn matches;\n}\n\nasync function collectGlobMatches(pattern: string, workspaceDir: string): Promise<string[]> {\n\tconst matches: string[] = [];\n\tconst normalizedPattern = normalizePathForMetadata(pattern);\n\tconst iterator = isAbsolute(pattern) ? glob(normalizedPattern) : glob(normalizedPattern, { cwd: workspaceDir });\n\tfor await (const match of iterator) {\n\t\tconst absolutePath = isAbsolute(match) ? match : resolve(workspaceDir, match);\n\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(absolutePath)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst fileStats = await statIfExists(absolutePath);\n\t\tif (fileStats?.isFile()) {\n\t\t\tmatches.push(absolutePath);\n\t\t}\n\t}\n\treturn matches;\n}\n\nexport async function discoverMigrationFiles(params: {\n\tworkspaceDir: string;\n\tsourceMode: MigrationSourceMode;\n\tsourcePaths?: string[];\n}): Promise<DiscoveredMigrationFile[]> {\n\tconst workspaceDir = resolve(params.workspaceDir);\n\tconst discovered = new Map<string, DiscoveredMigrationFile>();\n\n\tif (params.sourceMode === \"default-provider\") {\n\t\tfor (const relPath of await listMemoryFiles(workspaceDir)) {\n\t\t\tconst relativePath = normalizeRelativePath(relPath);\n\t\t\tconst absolutePath = resolve(workspaceDir, relativePath);\n\t\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(relativePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst fileStats = await statIfExists(absolutePath);\n\t\t\tif (fileStats?.isFile()) {\n\t\t\t\tdiscovered.set(absolutePath.toLowerCase(), {\n\t\t\t\t\tabsolutePath,\n\t\t\t\t\trelativePath,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const input of params.sourcePaths ?? []) {\n\t\tconst absoluteInput = resolve(workspaceDir, input);\n\t\tconst fileStats = await statIfExists(absoluteInput);\n\t\tlet matches: string[] = [];\n\t\tif (fileStats?.isDirectory()) {\n\t\t\tmatches = await collectDirectoryMarkdownFiles(absoluteInput);\n\t\t} else if (fileStats?.isFile()) {\n\t\t\tmatches = isMarkdownFile(absoluteInput) ? [absoluteInput] : [];\n\t\t} else if (hasGlobMagic(input)) {\n\t\t\tmatches = await collectGlobMatches(input, workspaceDir);\n\t\t}\n\n\t\tfor (const match of matches) {\n\t\t\tconst relativePath = normalizeRelativePath(relative(workspaceDir, match));\n\t\t\tdiscovered.set(match.toLowerCase(), {\n\t\t\t\tabsolutePath: match,\n\t\t\t\trelativePath,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));\n}\n\nfunction parseFrontmatterValue(value: string): MetadataValue | undefined {\n\tconst trimmed = value.trim();\n\tif (!trimmed) {\n\t\treturn \"\";\n\t}\n\tif (/^\"(.*)\"$/.test(trimmed) || /^'(.*)'$/.test(trimmed)) {\n\t\treturn trimmed.slice(1, -1);\n\t}\n\tif (trimmed === \"true\") {\n\t\treturn true;\n\t}\n\tif (trimmed === \"false\") {\n\t\treturn false;\n\t}\n\tconst numeric = Number(trimmed);\n\tif (!Number.isNaN(numeric) && trimmed !== \"\") {\n\t\treturn numeric;\n\t}\n\tif (trimmed.startsWith(\"[\") || trimmed.startsWith(\"{\")) {\n\t\treturn undefined;\n\t}\n\treturn trimmed;\n}\n\nfunction parseFrontmatter(content: string): { body: string; attributes: Record<string, MetadataValue> } {\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\");\n\tif (!normalized.startsWith(\"---\\n\")) {\n\t\treturn { body: normalized, attributes: {} };\n\t}\n\n\tconst lines = normalized.split(\"\\n\");\n\tconst closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === \"---\");\n\tif (closingIndex === -1) {\n\t\treturn { body: normalized, attributes: {} };\n\t}\n\n\tconst attributes: Record<string, MetadataValue> = {};\n\tfor (const line of lines.slice(1, closingIndex)) {\n\t\tconst separatorIndex = line.indexOf(\":\");\n\t\tif (separatorIndex === -1) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = line.slice(0, separatorIndex).trim();\n\t\tif (!key) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst parsedValue = parseFrontmatterValue(line.slice(separatorIndex + 1));\n\t\tif (parsedValue !== undefined) {\n\t\t\tattributes[key] = parsedValue;\n\t\t}\n\t}\n\n\treturn {\n\t\tbody: lines.slice(closingIndex + 1).join(\"\\n\"),\n\t\tattributes,\n\t};\n}\n\nfunction extractHeadingTitleAndBody(content: string): { title?: string; text: string } {\n\tconst lines = content.split(\"\\n\");\n\tconst firstContentLine = lines.findIndex((line) => line.trim().length > 0);\n\tif (firstContentLine === -1) {\n\t\treturn { text: \"\" };\n\t}\n\n\tconst headingMatch = /^#\\s+(.+?)\\s*$/.exec(lines[firstContentLine]?.trim() ?? \"\");\n\tif (!headingMatch) {\n\t\treturn { text: content.trim() };\n\t}\n\n\tconst remainingLines = [...lines];\n\tremainingLines.splice(firstContentLine, 1);\n\tif ((remainingLines[firstContentLine] ?? \"\").trim() === \"\") {\n\t\tremainingLines.splice(firstContentLine, 1);\n\t}\n\tconst body = remainingLines.join(\"\\n\").trim();\n\treturn {\n\t\ttitle: headingMatch[1].trim(),\n\t\ttext: body || content.trim(),\n\t};\n}\n\nfunction buildStableLogicalId(relativePath: string): string {\n\tconst withoutExtension = relativePath.replace(/\\.(md|markdown)$/i, \"\");\n\tconst slug = withoutExtension\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/-{2,}/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 80);\n\tconst hash = createHash(\"sha1\").update(relativePath).digest(\"hex\").slice(0, 10);\n\treturn `${slug || \"memory\"}-${hash}`;\n}\n\nfunction pickTitle(relativePath: string, frontmatterTitle: MetadataValue | undefined, headingTitle: string | undefined): string | undefined {\n\tif (typeof frontmatterTitle === \"string\" && frontmatterTitle.trim().length > 0) {\n\t\treturn frontmatterTitle.trim();\n\t}\n\tif (headingTitle) {\n\t\treturn headingTitle;\n\t}\n\tconst fileName = basename(relativePath, extname(relativePath)).trim();\n\treturn fileName || undefined;\n}\n\nfunction buildTargetNamespace(service: CloudflareMemoryService, options: MigrationRunOptions, workspaceDir: string): string {\n\treturn service.resolveNamespace({\n\t\tnamespace: options.namespace,\n\t\tworkspaceDir,\n\t});\n}\n\nfunction deriveRecordNamespace(params: {\n\trelativePath: string;\n\tfrontmatterNamespace: MetadataValue | undefined;\n\ttargetNamespace: string;\n\tnamespaceStrategy: MigrationNamespaceStrategy;\n}): string {\n\tif (params.namespaceStrategy === \"single-target\") {\n\t\treturn params.targetNamespace;\n\t}\n\n\tif (typeof params.frontmatterNamespace === \"string\" && params.frontmatterNamespace.trim().length > 0) {\n\t\treturn sanitizeNamespace(params.frontmatterNamespace);\n\t}\n\n\tconst firstSegment = normalizeRelativePath(params.relativePath).split(\"/\")[0];\n\tif (!firstSegment || firstSegment === \".\" || firstSegment === \"..\") {\n\t\treturn params.targetNamespace;\n\t}\n\treturn sanitizeNamespace(firstSegment);\n}\n\nexport async function parseMigrationFile(params: {\n\tfile: DiscoveredMigrationFile;\n\tsourceMode: MigrationSourceMode;\n\ttargetNamespace: string;\n\tnamespaceStrategy: MigrationNamespaceStrategy;\n}): Promise<ParsedMigrationRecord | null> {\n\tconst raw = await readFile(params.file.absolutePath, \"utf8\");\n\tconst { body, attributes } = parseFrontmatter(raw);\n\tconst { title: headingTitle, text: extractedText } = extractHeadingTitleAndBody(body);\n\tconst title = pickTitle(params.file.relativePath, attributes.title, headingTitle);\n\tconst text = extractedText.trim() || title || \"\";\n\tif (!text) {\n\t\treturn null;\n\t}\n\n\tconst metadata: Record<string, MetadataValue> = {\n\t\tlegacySourceMode: params.sourceMode,\n\t\tlegacySourcePath: params.file.relativePath.startsWith(\"..\") ? params.file.absolutePath : params.file.relativePath,\n\t};\n\tfor (const [key, value] of Object.entries(attributes)) {\n\t\tif (RESERVED_FRONTMATTER_FIELDS.has(key)) {\n\t\t\tcontinue;\n\t\t}\n\t\tmetadata[key] = value;\n\t}\n\n\tconst logicalId =\n\t\ttypeof attributes.id === \"string\" && attributes.id.trim().length > 0 ? attributes.id.trim() : buildStableLogicalId(params.file.relativePath);\n\tconst namespace = deriveRecordNamespace({\n\t\trelativePath: params.file.relativePath,\n\t\tfrontmatterNamespace: attributes.namespace,\n\t\ttargetNamespace: params.targetNamespace,\n\t\tnamespaceStrategy: params.namespaceStrategy,\n\t});\n\tconst source =\n\t\ttypeof attributes.source === \"string\" && attributes.source.trim().length > 0\n\t\t\t? attributes.source.trim()\n\t\t\t: params.sourceMode === \"default-provider\"\n\t\t\t\t? \"openclaw-default-memory\"\n\t\t\t\t: \"markdown-import\";\n\n\treturn {\n\t\tsourcePath: params.file.absolutePath,\n\t\trelativePath: params.file.relativePath,\n\t\tinput: {\n\t\t\tid: logicalId,\n\t\t\tnamespace,\n\t\t\ttitle,\n\t\t\ttext,\n\t\t\tsource,\n\t\t\tmetadata,\n\t\t},\n\t};\n}\n\nfunction formatDoctorFailure(report: DoctorReport): string {\n\tconst failedChecks = report.checks.filter((check) => check.status === \"fail\");\n\treturn failedChecks.map((check) => `${check.name}: ${check.message}`).join(\" | \");\n}\n\nexport async function runCloudflareMemoryMigration(params: { service: CloudflareMemoryService; options?: MigrationRunOptions }): Promise<MigrationSummary> {\n\tconst options = params.options ?? {};\n\tconst workspaceDir = resolve(options.workspaceDir ?? process.cwd());\n\tconst sourceMode: MigrationSourceMode = (options.sourcePaths?.length ?? 0) > 0 ? \"paths\" : \"default-provider\";\n\tconst namespaceStrategy = options.namespaceStrategy ?? \"single-target\";\n\tconst duplicateStrategy: MigrationDuplicateStrategy = options.duplicateStrategy ?? \"overwrite\";\n\tconst dryRun = options.dryRun ?? false;\n\tconst targetNamespace = buildTargetNamespace(params.service, options, workspaceDir);\n\tconst doctor = await params.service.doctor({\n\t\tcreateIndexIfMissing: options.createIndexIfMissing ?? false,\n\t});\n\tif (!doctor.ok) {\n\t\tthrow new Error(`Migration validation failed. ${formatDoctorFailure(doctor)}`);\n\t}\n\n\tconst discoveredFiles = await discoverMigrationFiles({\n\t\tworkspaceDir,\n\t\tsourceMode,\n\t\tsourcePaths: options.sourcePaths,\n\t});\n\tif (discoveredFiles.length === 0) {\n\t\tthrow new Error(\n\t\t\tsourceMode === \"default-provider\"\n\t\t\t\t? `No default OpenClaw markdown memory files were found under ${workspaceDir}.`\n\t\t\t\t: \"No markdown files matched the provided migration sources.\",\n\t\t);\n\t}\n\n\tconst results: MigrationResult[] = [];\n\tlet preparedRecords = 0;\n\tlet imported = 0;\n\tlet skipped = 0;\n\tlet failed = 0;\n\n\tfor (const file of discoveredFiles) {\n\t\ttry {\n\t\t\tconst parsed = await parseMigrationFile({\n\t\t\t\tfile,\n\t\t\t\tsourceMode,\n\t\t\t\ttargetNamespace,\n\t\t\t\tnamespaceStrategy,\n\t\t\t});\n\t\t\tif (!parsed) {\n\t\t\t\tskipped += 1;\n\t\t\t\tresults.push({\n\t\t\t\t\taction: \"skipped\",\n\t\t\t\t\tsourcePath: file.absolutePath,\n\t\t\t\t\trelativePath: file.relativePath,\n\t\t\t\t\treason: \"File did not contain any importable markdown content.\",\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpreparedRecords += 1;\n\t\t\tconst logicalId = parsed.input.id;\n\t\t\tconst namespace = parsed.input.namespace;\n\t\t\tif (!logicalId || !namespace) {\n\t\t\t\tthrow new Error(\"Parsed migration record is missing a logical id or namespace.\");\n\t\t\t}\n\n\t\t\tif (duplicateStrategy !== \"overwrite\") {\n\t\t\t\tconst existing = await params.service.get({\n\t\t\t\t\tid: logicalId,\n\t\t\t\t\tnamespace,\n\t\t\t\t});\n\t\t\t\tif (existing) {\n\t\t\t\t\tif (duplicateStrategy === \"skip\") {\n\t\t\t\t\t\tskipped += 1;\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\taction: \"skipped\",\n\t\t\t\t\t\t\tsourcePath: parsed.sourcePath,\n\t\t\t\t\t\t\trelativePath: parsed.relativePath,\n\t\t\t\t\t\t\tlogicalId,\n\t\t\t\t\t\t\tnamespace,\n\t\t\t\t\t\t\ttitle: parsed.input.title,\n\t\t\t\t\t\t\treason: \"A record with the same logical id already exists.\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`A record with logical id ${logicalId} already exists in namespace ${namespace}.`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (dryRun) {\n\t\t\t\tresults.push({\n\t\t\t\t\taction: \"would-import\",\n\t\t\t\t\tsourcePath: parsed.sourcePath,\n\t\t\t\t\trelativePath: parsed.relativePath,\n\t\t\t\t\tlogicalId,\n\t\t\t\t\tnamespace,\n\t\t\t\t\ttitle: parsed.input.title,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tawait params.service.upsert({\n\t\t\t\tinput: parsed.input,\n\t\t\t});\n\t\t\timported += 1;\n\t\t\tresults.push({\n\t\t\t\taction: \"imported\",\n\t\t\t\tsourcePath: parsed.sourcePath,\n\t\t\t\trelativePath: parsed.relativePath,\n\t\t\t\tlogicalId,\n\t\t\t\tnamespace,\n\t\t\t\ttitle: parsed.input.title,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tfailed += 1;\n\t\t\tresults.push({\n\t\t\t\taction: \"failed\",\n\t\t\t\tsourcePath: file.absolutePath,\n\t\t\t\trelativePath: file.relativePath,\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown migration failure.\",\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tdryRun,\n\t\tsourceMode,\n\t\tworkspaceDir,\n\t\tnamespaceStrategy,\n\t\ttargetNamespace: namespaceStrategy === \"single-target\" ? targetNamespace : undefined,\n\t\tdiscoveredFiles: discoveredFiles.length,\n\t\tpreparedRecords,\n\t\timported,\n\t\tskipped,\n\t\tfailed,\n\t\tdoctor,\n\t\tresults,\n\t};\n}\n\nexport function formatMigrationSummary(summary: MigrationSummary): string {\n\tconst lines = [\n\t\t`${summary.dryRun ? \"Dry-run\" : \"Migration\"} ${summary.failed > 0 ? \"completed with failures\" : \"completed\"}.`,\n\t\t`Source mode: ${summary.sourceMode}`,\n\t\t`Workspace: ${summary.workspaceDir}`,\n\t\t`Files scanned: ${summary.discoveredFiles}`,\n\t\t`Records prepared: ${summary.preparedRecords}`,\n\t\t`Imported: ${summary.imported}`,\n\t\t`Skipped: ${summary.skipped}`,\n\t\t`Failed: ${summary.failed}`,\n\t];\n\n\tif (summary.targetNamespace) {\n\t\tlines.splice(3, 0, `Target namespace: ${summary.targetNamespace}`);\n\t}\n\n\tconst failedResults = summary.results.filter((result) => result.action === \"failed\").slice(0, 10);\n\tif (failedResults.length > 0) {\n\t\tlines.push(\"\", \"Failures:\");\n\t\tfor (const result of failedResults) {\n\t\t\tlines.push(`- ${result.relativePath}: ${result.error}`);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- declare function handler(event: unknown): Promise<void>;
2
-
3
- export default handler;
1
+ declare function handler(event: unknown): Promise<void>;
2
+
3
+ export default handler;
@@ -1,17 +1,17 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- const bootstrapPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "BOOTSTRAP.md");
5
-
6
- export default async function handler(event) {
7
- if (event?.type !== "agent" || event?.action !== "bootstrap") {
8
- return;
9
- }
10
-
11
- const bootstrapFiles = event?.context?.bootstrapFiles;
12
- if (!Array.isArray(bootstrapFiles) || bootstrapFiles.includes(bootstrapPath)) {
13
- return;
14
- }
15
-
16
- bootstrapFiles.push(bootstrapPath);
17
- }
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ const bootstrapPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "BOOTSTRAP.md");
5
+
6
+ export default async function handler(event) {
7
+ if (event?.type !== "agent" || event?.action !== "bootstrap") {
8
+ return;
9
+ }
10
+
11
+ const bootstrapFiles = event?.context?.bootstrapFiles;
12
+ if (!Array.isArray(bootstrapFiles) || bootstrapFiles.includes(bootstrapPath)) {
13
+ return;
14
+ }
15
+
16
+ bootstrapFiles.push(bootstrapPath);
17
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "openclaw-cloudflare-vectorize-memory",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "OpenClaw memory plugin for Cloudflare Vectorize and Workers AI embeddings.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/types/index.d.ts",
8
8
  "files": [
9
+ "cli-metadata.ts",
9
10
  "dist",
10
11
  "hooks",
11
12
  "openclaw.plugin.json",