diff --git a/.githooks/sync-versions.ts b/.githooks/sync-versions.ts index 3935f929..b2d6bf68 100644 --- a/.githooks/sync-versions.ts +++ b/.githooks/sync-versions.ts @@ -49,8 +49,9 @@ if (eslintConfigVersion) { console.log(`🔄 Catalog @truenine/eslint10-config: ^${eslintConfigVersion}`) } -const packages: readonly PackageEntry[] = [ +const topLevelWorkspacePackages: readonly PackageEntry[] = [ { path: 'cli/package.json', name: 'cli' }, + { path: 'mcp/package.json', name: 'mcp' }, { path: 'gui/package.json', name: 'gui' }, { path: 'doc/package.json', name: 'doc' }, ] @@ -109,7 +110,7 @@ const cliNpmPackages = discoverNpmSubPackages('cli', 'cli-napi') let changed = false -for (const pkg of [...packages, ...libraryPackages, ...packagesPackages, ...cliNpmPackages]) { +for (const pkg of [...topLevelWorkspacePackages, ...libraryPackages, ...packagesPackages, ...cliNpmPackages]) { const fullPath = resolve(pkg.path) try { const content = readFileSync(fullPath, 'utf-8').replace(/^\uFEFF/, '') @@ -199,12 +200,10 @@ if (changed) { const filesToStage = [ 'package.json', 'Cargo.toml', - 'cli/package.json', - 'gui/package.json', - 'doc/package.json', 'gui/src-tauri/Cargo.toml', 'gui/src-tauri/tauri.conf.json', 'libraries/init-bundle/public/public/tnmsc.example.json', + ...topLevelWorkspacePackages.map(p => p.path), ...libraryPackages.map(p => p.path), ...packagesPackages.map(p => p.path), ...cliNpmPackages.map(p => p.path), diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index a0489b19..c73cd576 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -1,4 +1,4 @@ -name: Release CLI +name: Release Packages env: NODE_VERSION: '25' @@ -27,6 +27,7 @@ on: - .github/workflows/release-*.yml - assets/** - cli/** + - mcp/** - gui/** - libraries/** - scripts/** @@ -47,7 +48,9 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 10 outputs: - publish: ${{ steps.check.outputs.publish }} + publish_cli: ${{ steps.check.outputs.publish_cli }} + publish_mcp: ${{ steps.check.outputs.publish_mcp }} + publish_npm: ${{ steps.check.outputs.publish_npm }} version: ${{ steps.check.outputs.version }} steps: - uses: actions/checkout@v4 @@ -58,16 +61,46 @@ jobs: - name: Check if should publish id: check run: | - version=$(jq -r '.version' cli/package.json) - name=$(jq -r '.name' cli/package.json) - npm_version=$(npm view "$name" version --registry https://registry.npmjs.org/ 2>/dev/null || echo "") - if [[ "$version" != "$npm_version" ]]; then - echo "Version $version not published to npm, will publish" - echo "publish=true" >> "$GITHUB_OUTPUT" - echo "version=$version" >> "$GITHUB_OUTPUT" + check_publish_state() { + local package_json_path="$1" + local output_key="$2" + local version + local name + local npm_version + + version=$(jq -r '.version' "$package_json_path") + name=$(jq -r '.name' "$package_json_path") + npm_version=$(npm view "$name" version --registry https://registry.npmjs.org/ 2>/dev/null || echo "") + + if [[ "$version" != "$npm_version" ]]; then + echo "$name@$version is not published to npm, will publish" + echo "${output_key}=true" >> "$GITHUB_OUTPUT" + return 0 + fi + + echo "$name@$version already published to npm, skipping" + echo "${output_key}=false" >> "$GITHUB_OUTPUT" + return 1 + } + + version=$(jq -r '.version' package.json) + echo "version=$version" >> "$GITHUB_OUTPUT" + + cli_needs_publish=false + mcp_needs_publish=false + + if check_publish_state cli/package.json publish_cli; then + cli_needs_publish=true + fi + + if check_publish_state mcp/package.json publish_mcp; then + mcp_needs_publish=true + fi + + if [[ "$cli_needs_publish" == "true" || "$mcp_needs_publish" == "true" ]]; then + echo "publish_npm=true" >> "$GITHUB_OUTPUT" else - echo "Version $version already published to npm, skipping" - echo "publish=false" >> "$GITHUB_OUTPUT" + echo "publish_npm=false" >> "$GITHUB_OUTPUT" fi # 1.5. GUI 版本检查(独立于 npm,检查 GitHub Release) @@ -101,7 +134,7 @@ jobs: # 2. 构建 NAPI 二进制(5 平台矩阵) build-napi: needs: check-version - if: needs.check-version.outputs.publish == 'true' + if: needs.check-version.outputs.publish_cli == 'true' timeout-minutes: 45 strategy: fail-fast: false @@ -196,7 +229,7 @@ jobs: # 3. 收集并发布 NAPI 平台子包到 npm publish-napi: needs: [check-version, build-napi] - if: needs.check-version.outputs.publish == 'true' + if: needs.check-version.outputs.publish_cli == 'true' runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -271,7 +304,7 @@ jobs: # 4. 架构包就绪后,发布主包到 npm publish-cli: needs: [check-version, publish-napi] - if: needs.check-version.outputs.publish == 'true' + if: needs.check-version.outputs.publish_cli == 'true' runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -290,11 +323,35 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # 4.5. CLI 可用后,发布 MCP 包到 npm + publish-mcp: + needs: [check-version, publish-cli] + if: | + needs.check-version.outputs.publish_mcp == 'true' && + (needs.publish-cli.result == 'success' || needs.publish-cli.result == 'skipped') + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-node-pnpm + - name: Setup npm registry + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: https://registry.npmjs.org/ + - name: Build + run: pnpm exec turbo run build --filter=@truenine/memory-sync-mcp + - name: Publish to npm + working-directory: ./mcp + run: pnpm publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # 5. 构建 CLI 独立二进制(仅 artifact,不发 Release) build-binary: needs: [check-version, check-gui-version, publish-napi] if: | - (needs.check-version.outputs.publish == 'true' || needs.check-gui-version.outputs.should_release == 'true') && + (needs.check-version.outputs.publish_cli == 'true' || needs.check-gui-version.outputs.should_release == 'true') && (needs.publish-napi.result == 'success' || needs.publish-napi.result == 'skipped') timeout-minutes: 60 strategy: diff --git a/Cargo.toml b/Cargo.toml index 7563e081..1769fc44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ ] [workspace.package] -version = "2026.10317.12338" +version = "2026.10318.10339" edition = "2024" license = "AGPL-3.0-only" authors = ["TrueNine"] diff --git a/cli/npm/darwin-arm64/package.json b/cli/npm/darwin-arm64/package.json index 7c5aec60..64df9c7d 100644 --- a/cli/npm/darwin-arm64/package.json +++ b/cli/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-darwin-arm64", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "os": [ "darwin" ], diff --git a/cli/npm/darwin-x64/package.json b/cli/npm/darwin-x64/package.json index de1fcf55..7ae9d565 100644 --- a/cli/npm/darwin-x64/package.json +++ b/cli/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-darwin-x64", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "os": [ "darwin" ], diff --git a/cli/npm/linux-arm64-gnu/package.json b/cli/npm/linux-arm64-gnu/package.json index 01d2ea63..250d1beb 100644 --- a/cli/npm/linux-arm64-gnu/package.json +++ b/cli/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-linux-arm64-gnu", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "os": [ "linux" ], diff --git a/cli/npm/linux-x64-gnu/package.json b/cli/npm/linux-x64-gnu/package.json index dc9e6e5f..fbbed140 100644 --- a/cli/npm/linux-x64-gnu/package.json +++ b/cli/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-linux-x64-gnu", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "os": [ "linux" ], diff --git a/cli/npm/win32-x64-msvc/package.json b/cli/npm/win32-x64-msvc/package.json index b6583d4b..415ed390 100644 --- a/cli/npm/win32-x64-msvc/package.json +++ b/cli/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-win32-x64-msvc", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "os": [ "win32" ], diff --git a/cli/package.json b/cli/package.json index d12f15d7..2d3efb18 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,7 +1,7 @@ { "name": "@truenine/memory-sync-cli", "type": "module", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "description": "TrueNine Memory Synchronization CLI", "author": "TrueNine", "license": "AGPL-3.0-only", @@ -13,6 +13,7 @@ }, "exports": { ".": { + "types": "./dist/index.d.mts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" }, @@ -27,8 +28,9 @@ }, "main": "./dist/index.mjs", "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", "bin": { - "tnmsc": "./dist/index.mjs" + "tnmsc": "./dist/main.mjs" }, "files": [ "dist", diff --git a/cli/src/cli-runtime.ts b/cli/src/cli-runtime.ts new file mode 100644 index 00000000..d6bc5cee --- /dev/null +++ b/cli/src/cli-runtime.ts @@ -0,0 +1,41 @@ +import process from 'node:process' +import {toJsonCommandResult} from '@/commands/JsonOutputCommand' +import {buildUnhandledExceptionDiagnostic} from '@/diagnostics' +import {PluginPipeline} from '@/PluginPipeline' +import {createDefaultPluginConfig} from './plugin.config' +import {createLogger, drainBufferedDiagnostics} from './plugins/plugin-core' + +export function isJsonMode(argv: readonly string[]): boolean { + return argv.some(arg => arg === '--json' || arg === '-j' || /^-[^-]*j/.test(arg)) +} + +function writeJsonFailure(error: unknown): void { + const errorMessage = error instanceof Error ? error.message : String(error) + const logger = createLogger('main', 'silent') + logger.error(buildUnhandledExceptionDiagnostic('main', error)) + process.stdout.write(`${JSON.stringify(toJsonCommandResult({ + success: false, + filesAffected: 0, + dirsAffected: 0, + message: errorMessage + }, drainBufferedDiagnostics()))}\n`) +} + +export async function runCli(argv: readonly string[] = process.argv): Promise { + try { + const pipeline = new PluginPipeline(...argv) + const userPluginConfig = await createDefaultPluginConfig(argv) + const result = await pipeline.run(userPluginConfig) + return result.success ? 0 : 1 + } + catch (error) { + if (isJsonMode(argv)) { + writeJsonFailure(error) + return 1 + } + + const logger = createLogger('main', 'error') + logger.error(buildUnhandledExceptionDiagnostic('main', error)) + return 1 + } +} diff --git a/cli/src/commands/CleanupUtils.test.ts b/cli/src/commands/CleanupUtils.test.ts index 5ebef8cb..46d17c49 100644 --- a/cli/src/commands/CleanupUtils.test.ts +++ b/cli/src/commands/CleanupUtils.test.ts @@ -430,7 +430,7 @@ describe('collectDeletionTargets', () => { expect(result.violations).toEqual([expect.objectContaining({ targetPath: path.resolve(path.join(workspaceDir, 'aindex', 'app')), protectionMode: 'direct', - protectedPath: path.resolve(protectedAppMdxFile) + protectedPath: path.resolve(path.join(workspaceDir, 'aindex', 'app', 'workspace.src.mdx')) })]) } finally { diff --git a/cli/src/config.ts b/cli/src/config.ts index 27b1c5bf..d2cffaca 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -45,8 +45,8 @@ const DEFAULT_AINDEX: Required = { commands: {src: 'commands', dist: 'dist/commands'}, subAgents: {src: 'subagents', dist: 'dist/subagents'}, rules: {src: 'rules', dist: 'dist/rules'}, - globalPrompt: {src: 'global.src.mdx', dist: 'dist/global.mdx'}, - workspacePrompt: {src: 'workspace.src.mdx', dist: 'dist/workspace.mdx'}, + globalPrompt: {src: 'app/global.src.mdx', dist: 'dist/global.mdx'}, + workspacePrompt: {src: 'app/workspace.src.mdx', dist: 'dist/workspace.mdx'}, app: {src: 'app', dist: 'dist/app'}, ext: {src: 'ext', dist: 'dist/ext'}, arch: {src: 'arch', dist: 'dist/arch'} @@ -70,7 +70,7 @@ const DEFAULT_OPTIONS: Required = { * Convert UserConfigFile to PluginOptions * UserConfigFile is the JSON schema, PluginOptions includes plugins */ -function userConfigToPluginOptions(userConfig: UserConfigFile): Partial { +export function userConfigToPluginOptions(userConfig: UserConfigFile): Partial { return { ...userConfig.version != null ? {version: userConfig.version} : {}, ...userConfig.workspaceDir != null ? {workspaceDir: userConfig.workspaceDir} : {}, diff --git a/cli/src/core/config/mod.rs b/cli/src/core/config/mod.rs index fdf19718..81d9836e 100644 --- a/cli/src/core/config/mod.rs +++ b/cli/src/core/config/mod.rs @@ -652,7 +652,7 @@ mod tests { "subAgents": {"src": "src/agents", "dist": "dist/agents"}, "rules": {"src": "src/rules", "dist": "dist/rules"}, "globalPrompt": {"src": "app/global.src.mdx", "dist": "dist/global.mdx"}, - "workspacePrompt": {"src": "app/workspace.src.mdx", "dist": "dist/app/workspace.mdx"}, + "workspacePrompt": {"src": "app/workspace.src.mdx", "dist": "dist/workspace.mdx"}, "app": {"src": "app", "dist": "dist/app"}, "ext": {"src": "ext", "dist": "dist/ext"}, "arch": {"src": "arch", "dist": "dist/arch"} diff --git a/cli/src/index.test.ts b/cli/src/index.test.ts new file mode 100644 index 00000000..0727ccea --- /dev/null +++ b/cli/src/index.test.ts @@ -0,0 +1,11 @@ +import {describe, expect, it} from 'vitest' + +describe('library entrypoint', () => { + it('can be imported without executing the CLI runtime', async () => { + const mod = await import('./index') + + expect(typeof mod.runCli).toBe('function') + expect(typeof mod.createDefaultPluginConfig).toBe('function') + expect(typeof mod.listPrompts).toBe('function') + }) +}) diff --git a/cli/src/index.ts b/cli/src/index.ts index 981d6de7..4b8dd474 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -1,52 +1,14 @@ -import process from 'node:process' -import {toJsonCommandResult} from '@/commands/JsonOutputCommand' -import {buildUnhandledExceptionDiagnostic} from '@/diagnostics' -import {PluginPipeline} from '@/PluginPipeline' -import {createLogger, drainBufferedDiagnostics} from './plugins/plugin-core' - export * from './Aindex' +export * from './cli-runtime' export * from './config' export * from './ConfigLoader' - export { - default + createDefaultPluginConfig } from './plugin.config' - -async function main(): Promise { - const pipeline = new PluginPipeline(...process.argv) - const {default: userPluginConfigPromise} = await import('./plugin.config') - const userPluginConfig = await userPluginConfigPromise - const result = await pipeline.run(userPluginConfig) - if (!result.success) process.exit(1) -} - -function isJsonMode(argv: readonly string[]): boolean { - return argv.some(arg => arg === '--json' || arg === '-j' || /^-[^-]*j/.test(arg)) -} - -function writeJsonFailure(error: unknown): void { - const errorMessage = error instanceof Error ? error.message : String(error) - const logger = createLogger('main', 'silent') - logger.error(buildUnhandledExceptionDiagnostic('main', error)) - process.stdout.write(`${JSON.stringify(toJsonCommandResult({ - success: false, - filesAffected: 0, - dirsAffected: 0, - message: errorMessage - }, drainBufferedDiagnostics()))}\n`) -} - -main().catch((e: unknown) => { - if (isJsonMode(process.argv)) { - writeJsonFailure(e) - process.exit(1) - } - const logger = createLogger('main', 'error') - logger.error(buildUnhandledExceptionDiagnostic('main', e)) - process.exit(1) -}) - +export * from './PluginPipeline' export { DEFAULT_USER_CONFIG, PathPlaceholders } from './plugins/plugin-core' + +export * from './prompts' diff --git a/cli/src/main.ts b/cli/src/main.ts new file mode 100644 index 00000000..3363c275 --- /dev/null +++ b/cli/src/main.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +import process from 'node:process' +import {runCli} from './cli-runtime' + +void runCli(process.argv).then(exitCode => process.exit(exitCode)) diff --git a/cli/src/plugin-runtime.ts b/cli/src/plugin-runtime.ts index d0572474..9520a941 100644 --- a/cli/src/plugin-runtime.ts +++ b/cli/src/plugin-runtime.ts @@ -21,6 +21,7 @@ import {JsonOutputCommand, toJsonCommandResult} from '@/commands/JsonOutputComma import {PluginsCommand} from '@/commands/PluginsCommand' import {buildUnhandledExceptionDiagnostic} from '@/diagnostics' import {discoverOutputRuntimeTargets} from '@/pipeline/OutputRuntimeTargets' +import {createDefaultPluginConfig} from './plugin.config' import {createLogger, drainBufferedDiagnostics, setGlobalLogLevel} from './plugins/plugin-core' /** @@ -60,8 +61,7 @@ async function main(): Promise { if (json) setGlobalLogLevel('silent') - const {default: userPluginConfigPromise} = await import('./plugin.config') - const userPluginConfig: PipelineConfig = await userPluginConfigPromise + const userPluginConfig: PipelineConfig = await createDefaultPluginConfig(process.argv) let command = resolveRuntimeCommand(subcommand, dryRun) diff --git a/cli/src/plugin.config.ts b/cli/src/plugin.config.ts index f2813458..d40d7204 100644 --- a/cli/src/plugin.config.ts +++ b/cli/src/plugin.config.ts @@ -1,3 +1,4 @@ +import type {PipelineConfig} from '@/config' import process from 'node:process' import {GenericSkillsOutputPlugin} from '@truenine/plugin-agentskills-compact' import {AgentsOutputPlugin} from '@truenine/plugin-agentsmd' @@ -20,30 +21,36 @@ import {WindsurfOutputPlugin} from '@truenine/plugin-windsurf' import {defineConfig} from '@/config' import {TraeCNIDEOutputPlugin} from '@/plugins/plugin-trae-cn-ide' -export default defineConfig({ - pipelineArgs: process.argv, - pluginOptions: { - plugins: [ - new AgentsOutputPlugin(), - new ClaudeCodeCLIOutputPlugin(), - new CodexCLIOutputPlugin(), - new JetBrainsAIAssistantCodexOutputPlugin(), - new DroidCLIOutputPlugin(), - new GeminiCLIOutputPlugin(), - new GenericSkillsOutputPlugin(), - new OpencodeCLIOutputPlugin(), - new QoderIDEPluginOutputPlugin(), - new TraeIDEOutputPlugin(), - new TraeCNIDEOutputPlugin(), - new WarpIDEOutputPlugin(), - new WindsurfOutputPlugin(), - new CursorOutputPlugin(), - new GitExcludeOutputPlugin(), +export async function createDefaultPluginConfig( + pipelineArgs: readonly string[] = process.argv +): Promise { + return defineConfig({ + pipelineArgs, + pluginOptions: { + plugins: [ + new AgentsOutputPlugin(), + new ClaudeCodeCLIOutputPlugin(), + new CodexCLIOutputPlugin(), + new JetBrainsAIAssistantCodexOutputPlugin(), + new DroidCLIOutputPlugin(), + new GeminiCLIOutputPlugin(), + new GenericSkillsOutputPlugin(), + new OpencodeCLIOutputPlugin(), + new QoderIDEPluginOutputPlugin(), + new TraeIDEOutputPlugin(), + new TraeCNIDEOutputPlugin(), + new WarpIDEOutputPlugin(), + new WindsurfOutputPlugin(), + new CursorOutputPlugin(), + new GitExcludeOutputPlugin(), - new JetBrainsIDECodeStyleConfigOutputPlugin(), - new EditorConfigOutputPlugin(), - new VisualStudioCodeIDEConfigOutputPlugin(), - new ReadmeMdConfigFileOutputPlugin() - ] - } -}) + new JetBrainsIDECodeStyleConfigOutputPlugin(), + new EditorConfigOutputPlugin(), + new VisualStudioCodeIDEConfigOutputPlugin(), + new ReadmeMdConfigFileOutputPlugin() + ] + } + }) +} + +export default createDefaultPluginConfig diff --git a/cli/src/plugins/plugin-core/AindexTypes.ts b/cli/src/plugins/plugin-core/AindexTypes.ts index 0230ca74..6266fd5c 100644 --- a/cli/src/plugins/plugin-core/AindexTypes.ts +++ b/cli/src/plugins/plugin-core/AindexTypes.ts @@ -121,7 +121,7 @@ export const AINDEX_RELATIVE_PATHS = { DIST_RULES: 'dist/rules', DIST_APP: 'dist/app', DIST_GLOBAL_MEMORY: 'dist/global.mdx', - DIST_WORKSPACE_MEMORY: 'dist/app/workspace.mdx', + DIST_WORKSPACE_MEMORY: 'dist/workspace.mdx', APP: 'app' // App source path (standalone at root) } as const diff --git a/cli/src/prompts.test.ts b/cli/src/prompts.test.ts new file mode 100644 index 00000000..3678aa45 --- /dev/null +++ b/cli/src/prompts.test.ts @@ -0,0 +1,297 @@ +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' +import {afterEach, describe, expect, it} from 'vitest' +import { + getPrompt, + listPrompts, + resolvePromptDefinition, + upsertPromptSource, + writePromptArtifacts +} from './prompts' + +const tempDirs: string[] = [] + +function createTempWorkspace(prefix: string): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)) + tempDirs.push(dir) + return dir +} + +function writeFile(filePath: string, content: string, modifiedAt: Date): void { + fs.mkdirSync(path.dirname(filePath), {recursive: true}) + fs.writeFileSync(filePath, content, 'utf8') + fs.utimesSync(filePath, modifiedAt, modifiedAt) +} + +function serviceOptions(workspaceDir: string) { + return { + loadUserConfig: false, + pluginOptions: { + workspaceDir + } + } as const +} + +afterEach(() => { + for (const dir of tempDirs.splice(0)) fs.rmSync(dir, {recursive: true, force: true}) +}) + +describe('prompt catalog service', () => { + it('lists every managed prompt family with status metadata', async () => { + const workspaceDir = createTempWorkspace('tnmsc-prompts-') + const aindexDir = path.join(workspaceDir, 'aindex') + const now = Date.now() + + writeFile( + path.join(aindexDir, 'app', 'global.src.mdx'), + '---\ndescription: global zh\n---\nGlobal zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'app', 'global.mdx'), + '---\ndescription: global en\n---\nGlobal en', + new Date(now - 10_000) + ) + writeFile( + path.join(aindexDir, 'dist', 'global.mdx'), + '---\ndescription: global dist\n---\nGlobal dist', + new Date(now - 10_000) + ) + + writeFile( + path.join(aindexDir, 'app', 'workspace.src.mdx'), + '---\ndescription: workspace zh\n---\nWorkspace zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'app', 'workspace.mdx'), + '---\ndescription: workspace en\n---\nWorkspace en', + new Date(now + 1_000) + ) + writeFile( + path.join(aindexDir, 'dist', 'workspace.mdx'), + '---\ndescription: workspace dist\n---\nWorkspace dist', + new Date(now + 1_000) + ) + + writeFile( + path.join(aindexDir, 'app', 'project-a', 'agt.src.mdx'), + '---\ndescription: project zh\n---\nProject zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'app', 'project-a', 'agt.mdx'), + '---\ndescription: project en\n---\nProject en', + new Date(now + 1_000) + ) + writeFile( + path.join(aindexDir, 'dist', 'app', 'project-a', 'agt.mdx'), + '---\ndescription: project dist\n---\nProject dist', + new Date(now + 1_000) + ) + + writeFile( + path.join(aindexDir, 'app', 'project-b', 'docs', 'agt.mdx'), + '---\ndescription: child legacy zh\n---\nChild legacy zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'dist', 'app', 'project-b', 'docs', 'agt.mdx'), + '---\ndescription: child dist\n---\nChild dist', + new Date(now + 1_000) + ) + + writeFile( + path.join(aindexDir, 'skills', 'reviewer', 'skill.src.mdx'), + '---\ndescription: skill zh\n---\nSkill zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'skills', 'reviewer', 'skill.mdx'), + '---\ndescription: skill en\n---\nSkill en', + new Date(now + 1_000) + ) + writeFile( + path.join(aindexDir, 'skills', 'reviewer', 'guide.src.mdx'), + '---\ndescription: guide zh\n---\nGuide zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'dist', 'skills', 'reviewer', 'skill.mdx'), + '---\ndescription: skill dist\n---\nSkill dist', + new Date(now + 1_000) + ) + writeFile( + path.join(aindexDir, 'dist', 'skills', 'reviewer', 'guide.mdx'), + '---\ndescription: guide dist\n---\nGuide dist', + new Date(now + 1_000) + ) + + writeFile( + path.join(aindexDir, 'commands', 'dev', 'build.src.mdx'), + '---\ndescription: command zh\n---\nCommand zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'dist', 'commands', 'dev', 'build.mdx'), + '---\ndescription: command dist\n---\nCommand dist', + new Date(now + 1_000) + ) + + writeFile( + path.join(aindexDir, 'subagents', 'qa', 'boot.src.mdx'), + '---\nname: boot\ndescription: subagent zh\n---\nSubagent zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'subagents', 'qa', 'boot.mdx'), + '---\nname: boot\ndescription: subagent en\n---\nSubagent en', + new Date(now + 1_000) + ) + writeFile( + path.join(aindexDir, 'dist', 'subagents', 'qa', 'boot.mdx'), + '---\nname: boot\ndescription: subagent dist\n---\nSubagent dist', + new Date(now + 1_000) + ) + + writeFile( + path.join(aindexDir, 'rules', 'frontend.src.mdx'), + '---\ndescription: rule zh\nglobs: ["src/**"]\n---\nRule zh', + new Date(now) + ) + writeFile( + path.join(aindexDir, 'dist', 'rules', 'frontend.mdx'), + '---\ndescription: rule dist\nglobs: ["src/**"]\n---\nRule dist', + new Date(now + 1_000) + ) + + const prompts = await listPrompts(serviceOptions(workspaceDir)) + + expect(prompts.map(prompt => prompt.kind)).toEqual([ + 'command', + 'global-memory', + 'project-child-memory', + 'project-memory', + 'rule', + 'skill-child-doc', + 'skill', + 'subagent', + 'workspace-memory' + ]) + expect(prompts.find(prompt => prompt.promptId === 'global-memory')).toEqual(expect.objectContaining({enStatus: 'stale', distStatus: 'stale'})) + expect(prompts.find(prompt => prompt.promptId === 'workspace-memory')).toEqual(expect.objectContaining({enStatus: 'ready', distStatus: 'ready'})) + expect(prompts.find(prompt => prompt.promptId === 'project-child-memory:project-b/docs')).toEqual(expect.objectContaining({ + legacyZhSource: true, + enStatus: 'missing', + distStatus: 'ready' + })) + expect(prompts.find(prompt => prompt.promptId === 'command:dev/build')).toEqual(expect.objectContaining({enStatus: 'missing', distStatus: 'ready'})) + + const filtered = await listPrompts({ + ...serviceOptions(workspaceDir), + kinds: ['command'], + distStatus: ['ready'] + }) + + expect(filtered.map(prompt => prompt.promptId)).toEqual(['command:dev/build']) + }) + + it('returns prompt contents and expected paths', async () => { + const workspaceDir = createTempWorkspace('tnmsc-prompt-details-') + const aindexDir = path.join(workspaceDir, 'aindex') + const modifiedAt = new Date() + + writeFile( + path.join(aindexDir, 'skills', 'reviewer', 'skill.src.mdx'), + '---\ndescription: skill zh\n---\nSkill zh', + modifiedAt + ) + writeFile( + path.join(aindexDir, 'skills', 'reviewer', 'skill.mdx'), + '---\ndescription: skill en\n---\nSkill en', + modifiedAt + ) + writeFile( + path.join(aindexDir, 'dist', 'skills', 'reviewer', 'skill.mdx'), + '---\ndescription: skill dist\n---\nSkill dist', + modifiedAt + ) + + const prompt = await getPrompt('skill:reviewer', serviceOptions(workspaceDir)) + const resolvedPaths = await resolvePromptDefinition('skill:reviewer', serviceOptions(workspaceDir)) + + expect(prompt).toEqual(expect.objectContaining({ + promptId: 'skill:reviewer', + frontMatter: expect.objectContaining({description: 'skill zh'}) + })) + expect(prompt?.src.zh?.content).toContain('Skill zh') + expect(prompt?.src.en?.content).toContain('Skill en') + expect(prompt?.dist?.content).toContain('Skill dist') + expect(resolvedPaths).toEqual(prompt?.paths) + }) + + it('migrates legacy project memory to the new zh/en source convention', async () => { + const workspaceDir = createTempWorkspace('tnmsc-project-migration-') + const aindexDir = path.join(workspaceDir, 'aindex') + const legacyPath = path.join(aindexDir, 'app', 'project-c', 'agt.mdx') + + writeFile( + legacyPath, + '---\ndescription: legacy zh\n---\nLegacy zh', + new Date() + ) + + const migrated = await upsertPromptSource({ + ...serviceOptions(workspaceDir), + promptId: 'project-memory:project-c', + locale: 'en', + content: '---\ndescription: translated en\n---\nTranslated en' + }) + + expect(fs.readFileSync(path.join(aindexDir, 'app', 'project-c', 'agt.src.mdx'), 'utf8')).toContain('Legacy zh') + expect(fs.readFileSync(legacyPath, 'utf8')).toContain('Translated en') + expect(migrated.src.zh?.legacySource).toBeUndefined() + expect(migrated.src.en?.content).toContain('Translated en') + + const rewritten = await upsertPromptSource({ + ...serviceOptions(workspaceDir), + promptId: 'project-memory:project-c', + locale: 'zh', + content: '---\ndescription: rewritten zh\n---\nRewritten zh' + }) + + expect(fs.readFileSync(path.join(aindexDir, 'app', 'project-c', 'agt.src.mdx'), 'utf8')).toContain('Rewritten zh') + expect(fs.existsSync(legacyPath)).toBe(false) + expect(rewritten.exists.en).toBe(false) + }) + + it('writes translation artifacts independently for en and dist', async () => { + const workspaceDir = createTempWorkspace('tnmsc-translation-write-') + const aindexDir = path.join(workspaceDir, 'aindex') + + writeFile( + path.join(aindexDir, 'commands', 'dev', 'ship.src.mdx'), + '---\ndescription: ship zh\n---\nShip zh', + new Date() + ) + + const afterEnWrite = await writePromptArtifacts({ + ...serviceOptions(workspaceDir), + promptId: 'command:dev/ship', + enContent: '---\ndescription: ship en\n---\nShip en' + }) + + expect(afterEnWrite.src.en?.content).toContain('Ship en') + expect(afterEnWrite.distStatus).toBe('missing') + + const afterDistWrite = await writePromptArtifacts({ + ...serviceOptions(workspaceDir), + promptId: 'command:dev/ship', + distContent: '---\ndescription: ship dist\n---\nShip dist' + }) + + expect(afterDistWrite.dist?.content).toContain('Ship dist') + expect(afterDistWrite.distStatus).toBe('ready') + }) +}) diff --git a/cli/src/prompts.ts b/cli/src/prompts.ts new file mode 100644 index 00000000..5050d0ec --- /dev/null +++ b/cli/src/prompts.ts @@ -0,0 +1,754 @@ +import type {PluginOptions, YAMLFrontMatter} from '@/plugins/plugin-core' +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' +import {parseMarkdown} from '@truenine/md-compiler/markdown' +import glob from 'fast-glob' +import {mergeConfig, userConfigToPluginOptions} from './config' +import {getConfigLoader} from './ConfigLoader' +import {PathPlaceholders} from './plugins/plugin-core' + +export type ManagedPromptKind + = | 'global-memory' + | 'workspace-memory' + | 'project-memory' + | 'project-child-memory' + | 'skill' + | 'skill-child-doc' + | 'command' + | 'subagent' + | 'rule' + +export type PromptArtifactState = 'missing' | 'stale' | 'ready' +export type PromptSourceLocale = 'zh' | 'en' + +export interface PromptServiceOptions { + readonly cwd?: string + readonly loadUserConfig?: boolean + readonly pluginOptions?: Partial +} + +export interface ListPromptsOptions extends PromptServiceOptions { + readonly kinds?: readonly ManagedPromptKind[] + readonly query?: string + readonly enStatus?: readonly PromptArtifactState[] + readonly distStatus?: readonly PromptArtifactState[] +} + +export interface PromptArtifactRecord { + readonly path: string + readonly exists: true + readonly mtime: string + readonly mtimeMs: number + readonly size: number + readonly legacySource?: true + readonly frontMatter?: YAMLFrontMatter + readonly content?: string +} + +export interface PromptCatalogPaths { + readonly zh: string + readonly en: string + readonly dist: string +} + +export interface PromptCatalogPresence { + readonly zh: boolean + readonly en: boolean + readonly dist: boolean +} + +export interface PromptCatalogItem { + readonly promptId: string + readonly kind: ManagedPromptKind + readonly logicalName: string + readonly paths: PromptCatalogPaths + readonly exists: PromptCatalogPresence + readonly enStatus: PromptArtifactState + readonly distStatus: PromptArtifactState + readonly updatedAt?: string + readonly legacyZhSource?: true +} + +export interface PromptDetails extends PromptCatalogItem { + readonly src: { + readonly zh?: PromptArtifactRecord + readonly en?: PromptArtifactRecord + } + readonly dist?: PromptArtifactRecord + readonly frontMatter?: YAMLFrontMatter +} + +export interface UpsertPromptSourceInput extends PromptServiceOptions { + readonly promptId: string + readonly locale?: PromptSourceLocale + readonly content: string +} + +export interface WritePromptArtifactsInput extends PromptServiceOptions { + readonly promptId: string + readonly enContent?: string + readonly distContent?: string +} + +interface ResolvedPromptEnvironment { + readonly options: Required + readonly workspaceDir: string + readonly aindexDir: string +} + +interface PromptDefinition { + readonly promptId: string + readonly kind: ManagedPromptKind + readonly logicalName: string + readonly paths: PromptCatalogPaths + readonly legacyZhPath?: string +} + +interface PromptIdDescriptor { + readonly kind: ManagedPromptKind + readonly projectName?: string + readonly relativeName?: string + readonly skillName?: string +} + +const SOURCE_PROMPT_EXTENSION = '.src.mdx' +const MDX_EXTENSION = '.mdx' +const PROJECT_MEMORY_FILE_NAME = 'agt' +const SKILL_ENTRY_FILE_NAME = 'skill' +const LEGACY_PROJECT_MEMORY_KINDS = new Set([ + 'project-memory', + 'project-child-memory' +]) + +function normalizeSlashPath(value: string): string { + return value.replaceAll('\\', '/') +} + +function normalizeRelativeIdentifier(value: string, fieldName: string): string { + const normalized = normalizeSlashPath(value).trim() + if (normalized.length === 0) throw new Error(`${fieldName} cannot be empty`) + + const segments = normalized.split('/') + for (const segment of segments) { + if (segment.length === 0 || segment === '.' || segment === '..') throw new Error(`${fieldName} contains an invalid path segment`) + } + + return segments.join('/') +} + +function isSingleSegmentIdentifier(value: string): boolean { + return !normalizeSlashPath(value).includes('/') +} + +function resolveConfiguredPath(rawPath: string, workspaceDir: string): string { + let resolved = rawPath + + if (resolved.startsWith(PathPlaceholders.USER_HOME)) resolved = resolved.replace(PathPlaceholders.USER_HOME, os.homedir()) + + if (resolved.includes(PathPlaceholders.WORKSPACE)) resolved = resolved.replace(PathPlaceholders.WORKSPACE, workspaceDir) + + return path.normalize(resolved) +} + +function resolvePromptEnvironment(options: PromptServiceOptions = {}): ResolvedPromptEnvironment { + const {cwd, loadUserConfig = true, pluginOptions = {}} = options + let userConfigOptions: Partial = {} + + if (loadUserConfig) { + const userConfigResult = getConfigLoader().load(cwd) + if (userConfigResult.found) userConfigOptions = userConfigToPluginOptions(userConfigResult.config) + } + + const mergedOptions = mergeConfig(userConfigOptions, pluginOptions) + const workspaceDir = resolveConfiguredPath(mergedOptions.workspaceDir, '') + const aindexDir = path.join(workspaceDir, mergedOptions.aindex.dir) + + return { + options: mergedOptions, + workspaceDir, + aindexDir + } +} + +function deriveEnglishSourcePath(zhPath: string): string { + if (zhPath.endsWith(SOURCE_PROMPT_EXTENSION)) return `${zhPath.slice(0, -SOURCE_PROMPT_EXTENSION.length)}${MDX_EXTENSION}` + + const ext = path.extname(zhPath) + if (ext === MDX_EXTENSION) return zhPath + return `${zhPath}${MDX_EXTENSION}` +} + +function stripPromptExtension(filePath: string): string { + if (filePath.endsWith(SOURCE_PROMPT_EXTENSION)) return filePath.slice(0, -SOURCE_PROMPT_EXTENSION.length) + + if (filePath.endsWith(MDX_EXTENSION)) return filePath.slice(0, -MDX_EXTENSION.length) + + return filePath +} + +function listFiles(cwd: string, patterns: readonly string[]): string[] { + if (!(fs.existsSync(cwd) && fs.statSync(cwd).isDirectory())) return [] + + return glob.sync([...patterns], { + cwd, + dot: true, + onlyFiles: true + }).map(normalizeSlashPath) +} + +function buildGlobalMemoryDefinition(env: ResolvedPromptEnvironment): PromptDefinition { + const zhPath = path.join(env.aindexDir, env.options.aindex.globalPrompt.src) + + return { + promptId: 'global-memory', + kind: 'global-memory', + logicalName: 'global-memory', + paths: { + zh: zhPath, + en: deriveEnglishSourcePath(zhPath), + dist: path.join(env.aindexDir, env.options.aindex.globalPrompt.dist) + } + } +} + +function buildWorkspaceMemoryDefinition(env: ResolvedPromptEnvironment): PromptDefinition { + const zhPath = path.join(env.aindexDir, env.options.aindex.workspacePrompt.src) + + return { + promptId: 'workspace-memory', + kind: 'workspace-memory', + logicalName: 'workspace-memory', + paths: { + zh: zhPath, + en: deriveEnglishSourcePath(zhPath), + dist: path.join(env.aindexDir, env.options.aindex.workspacePrompt.dist) + } + } +} + +function buildProjectMemoryDefinition( + env: ResolvedPromptEnvironment, + projectName: string, + relativeName?: string +): PromptDefinition { + const normalizedProjectName = normalizeRelativeIdentifier(projectName, 'projectName') + if (!isSingleSegmentIdentifier(normalizedProjectName)) throw new Error('projectName must be a single path segment') + + const normalizedRelativeName = relativeName == null + ? '' + : normalizeRelativeIdentifier(relativeName, 'relativeName') + const sourceDir = normalizedRelativeName.length === 0 + ? path.join(env.aindexDir, env.options.aindex.app.src, normalizedProjectName) + : path.join(env.aindexDir, env.options.aindex.app.src, normalizedProjectName, normalizedRelativeName) + const distDir = normalizedRelativeName.length === 0 + ? path.join(env.aindexDir, env.options.aindex.app.dist, normalizedProjectName) + : path.join(env.aindexDir, env.options.aindex.app.dist, normalizedProjectName, normalizedRelativeName) + const legacyPath = path.join(sourceDir, `${PROJECT_MEMORY_FILE_NAME}${MDX_EXTENSION}`) + const logicalSuffix = normalizedRelativeName.length === 0 + ? normalizedProjectName + : `${normalizedProjectName}/${normalizedRelativeName}` + + return { + promptId: normalizedRelativeName.length === 0 + ? `project-memory:${normalizedProjectName}` + : `project-child-memory:${logicalSuffix}`, + kind: normalizedRelativeName.length === 0 ? 'project-memory' : 'project-child-memory', + logicalName: logicalSuffix, + paths: { + zh: path.join(sourceDir, `${PROJECT_MEMORY_FILE_NAME}${SOURCE_PROMPT_EXTENSION}`), + en: legacyPath, + dist: path.join(distDir, `${PROJECT_MEMORY_FILE_NAME}${MDX_EXTENSION}`) + }, + legacyZhPath: legacyPath + } +} + +function buildSkillDefinition( + env: ResolvedPromptEnvironment, + skillName: string +): PromptDefinition { + const normalizedSkillName = normalizeRelativeIdentifier(skillName, 'skillName') + if (!isSingleSegmentIdentifier(normalizedSkillName)) throw new Error('skillName must be a single path segment') + + const sourceDir = path.join(env.aindexDir, env.options.aindex.skills.src, normalizedSkillName) + const distDir = path.join(env.aindexDir, env.options.aindex.skills.dist, normalizedSkillName) + + return { + promptId: `skill:${normalizedSkillName}`, + kind: 'skill', + logicalName: normalizedSkillName, + paths: { + zh: path.join(sourceDir, `${SKILL_ENTRY_FILE_NAME}${SOURCE_PROMPT_EXTENSION}`), + en: path.join(sourceDir, `${SKILL_ENTRY_FILE_NAME}${MDX_EXTENSION}`), + dist: path.join(distDir, `${SKILL_ENTRY_FILE_NAME}${MDX_EXTENSION}`) + } + } +} + +function buildSkillChildDocDefinition( + env: ResolvedPromptEnvironment, + skillName: string, + relativeName: string +): PromptDefinition { + const normalizedSkillName = normalizeRelativeIdentifier(skillName, 'skillName') + const normalizedRelativeName = normalizeRelativeIdentifier(relativeName, 'relativeName') + if (!isSingleSegmentIdentifier(normalizedSkillName)) throw new Error('skillName must be a single path segment') + + const sourceDir = path.join(env.aindexDir, env.options.aindex.skills.src, normalizedSkillName) + const distDir = path.join(env.aindexDir, env.options.aindex.skills.dist, normalizedSkillName) + + return { + promptId: `skill-child-doc:${normalizedSkillName}/${normalizedRelativeName}`, + kind: 'skill-child-doc', + logicalName: `${normalizedSkillName}/${normalizedRelativeName}`, + paths: { + zh: path.join(sourceDir, `${normalizedRelativeName}${SOURCE_PROMPT_EXTENSION}`), + en: path.join(sourceDir, `${normalizedRelativeName}${MDX_EXTENSION}`), + dist: path.join(distDir, `${normalizedRelativeName}${MDX_EXTENSION}`) + } + } +} + +function buildFlatPromptDefinition( + env: ResolvedPromptEnvironment, + kind: Extract, + relativeName: string +): PromptDefinition { + const normalizedRelativeName = normalizeRelativeIdentifier(relativeName, 'relativeName') + const sourceDir = kind === 'command' + ? path.join(env.aindexDir, env.options.aindex.commands.src) + : kind === 'subagent' + ? path.join(env.aindexDir, env.options.aindex.subAgents.src) + : path.join(env.aindexDir, env.options.aindex.rules.src) + const distDir = kind === 'command' + ? path.join(env.aindexDir, env.options.aindex.commands.dist) + : kind === 'subagent' + ? path.join(env.aindexDir, env.options.aindex.subAgents.dist) + : path.join(env.aindexDir, env.options.aindex.rules.dist) + + return { + promptId: `${kind}:${normalizedRelativeName}`, + kind, + logicalName: normalizedRelativeName, + paths: { + zh: path.join(sourceDir, `${normalizedRelativeName}${SOURCE_PROMPT_EXTENSION}`), + en: path.join(sourceDir, `${normalizedRelativeName}${MDX_EXTENSION}`), + dist: path.join(distDir, `${normalizedRelativeName}${MDX_EXTENSION}`) + } + } +} + +function parsePromptId(promptId: string): PromptIdDescriptor { + switch (promptId) { + case 'global-memory': return {kind: 'global-memory'} + case 'workspace-memory': return {kind: 'workspace-memory'} + default: break + } + + const separatorIndex = promptId.indexOf(':') + if (separatorIndex === -1) throw new Error(`Unsupported promptId: ${promptId}`) + + const kind = promptId.slice(0, separatorIndex) as ManagedPromptKind + const rawValue = promptId.slice(separatorIndex + 1) + const normalizedValue = normalizeRelativeIdentifier(rawValue, 'promptId') + + switch (kind) { + case 'project-memory': + if (!isSingleSegmentIdentifier(normalizedValue)) throw new Error('project-memory promptId must include a single project name') + return {kind, projectName: normalizedValue} + case 'project-child-memory': { + const [projectName, ...rest] = normalizedValue.split('/') + const relativeName = rest.join('/') + if (projectName == null || relativeName.length === 0) throw new Error('project-child-memory promptId must include project and child path') + return {kind, projectName, relativeName} + } + case 'skill': + if (!isSingleSegmentIdentifier(normalizedValue)) throw new Error('skill promptId must include a single skill name') + return {kind, skillName: normalizedValue} + case 'skill-child-doc': { + const [skillName, ...rest] = normalizedValue.split('/') + const relativeName = rest.join('/') + if (skillName == null || relativeName.length === 0) throw new Error('skill-child-doc promptId must include skill and child path') + return {kind, skillName, relativeName} + } + case 'command': + case 'subagent': + case 'rule': return {kind, relativeName: normalizedValue} + default: throw new Error(`Unsupported promptId: ${promptId}`) + } +} + +function buildPromptDefinitionFromId( + promptId: string, + env: ResolvedPromptEnvironment +): PromptDefinition { + const descriptor = parsePromptId(promptId) + + switch (descriptor.kind) { + case 'global-memory': return buildGlobalMemoryDefinition(env) + case 'workspace-memory': return buildWorkspaceMemoryDefinition(env) + case 'project-memory': return buildProjectMemoryDefinition(env, descriptor.projectName!) + case 'project-child-memory': return buildProjectMemoryDefinition(env, descriptor.projectName!, descriptor.relativeName) + case 'skill': return buildSkillDefinition(env, descriptor.skillName!) + case 'skill-child-doc': return buildSkillChildDocDefinition(env, descriptor.skillName!, descriptor.relativeName!) + case 'command': + case 'subagent': + case 'rule': return buildFlatPromptDefinition(env, descriptor.kind, descriptor.relativeName!) + } +} + +function collectFlatPromptIds( + env: ResolvedPromptEnvironment, + kind: Extract +): string[] { + const sourceDir = kind === 'command' + ? path.join(env.aindexDir, env.options.aindex.commands.src) + : kind === 'subagent' + ? path.join(env.aindexDir, env.options.aindex.subAgents.src) + : path.join(env.aindexDir, env.options.aindex.rules.src) + const distDir = kind === 'command' + ? path.join(env.aindexDir, env.options.aindex.commands.dist) + : kind === 'subagent' + ? path.join(env.aindexDir, env.options.aindex.subAgents.dist) + : path.join(env.aindexDir, env.options.aindex.rules.dist) + const names = new Set() + + for (const match of listFiles(sourceDir, [`**/*${SOURCE_PROMPT_EXTENSION}`, `**/*${MDX_EXTENSION}`])) names.add(stripPromptExtension(match)) + + for (const match of listFiles(distDir, [`**/*${MDX_EXTENSION}`])) names.add(stripPromptExtension(match)) + + return [...names].sort().map(name => `${kind}:${name}`) +} + +function collectSkillPromptIds(env: ResolvedPromptEnvironment): string[] { + const sourceRoot = path.join(env.aindexDir, env.options.aindex.skills.src) + const distRoot = path.join(env.aindexDir, env.options.aindex.skills.dist) + const skillNames = new Set() + + if (fs.existsSync(sourceRoot) && fs.statSync(sourceRoot).isDirectory()) { + for (const entry of fs.readdirSync(sourceRoot, {withFileTypes: true})) { + if (entry.isDirectory()) skillNames.add(entry.name) + } + } + + if (fs.existsSync(distRoot) && fs.statSync(distRoot).isDirectory()) { + for (const entry of fs.readdirSync(distRoot, {withFileTypes: true})) { + if (entry.isDirectory()) skillNames.add(entry.name) + } + } + + const promptIds: string[] = [] + + for (const skillName of [...skillNames].sort()) { + promptIds.push(`skill:${skillName}`) + + const sourceDir = path.join(sourceRoot, skillName) + const distDir = path.join(distRoot, skillName) + const childNames = new Set() + + for (const match of listFiles(sourceDir, [`**/*${SOURCE_PROMPT_EXTENSION}`, `**/*${MDX_EXTENSION}`])) { + const stripped = stripPromptExtension(match) + if (stripped === SKILL_ENTRY_FILE_NAME) continue + childNames.add(stripped) + } + + for (const match of listFiles(distDir, [`**/*${MDX_EXTENSION}`])) { + const stripped = stripPromptExtension(match) + if (stripped === SKILL_ENTRY_FILE_NAME) continue + childNames.add(stripped) + } + + for (const childName of [...childNames].sort()) promptIds.push(`skill-child-doc:${skillName}/${childName}`) + } + + return promptIds +} + +function collectProjectPromptIds(env: ResolvedPromptEnvironment): string[] { + const sourceRoot = path.join(env.aindexDir, env.options.aindex.app.src) + const distRoot = path.join(env.aindexDir, env.options.aindex.app.dist) + const relativeDirs = new Set() + + for (const match of listFiles(sourceRoot, [`**/${PROJECT_MEMORY_FILE_NAME}${SOURCE_PROMPT_EXTENSION}`, `**/${PROJECT_MEMORY_FILE_NAME}${MDX_EXTENSION}`])) { + const directory = normalizeSlashPath(path.posix.dirname(normalizeSlashPath(match))) + if (directory !== '.') relativeDirs.add(directory) + } + + for (const match of listFiles(distRoot, [`**/${PROJECT_MEMORY_FILE_NAME}${MDX_EXTENSION}`])) { + const directory = normalizeSlashPath(path.posix.dirname(normalizeSlashPath(match))) + if (directory !== '.') relativeDirs.add(directory) + } + + const promptIds: string[] = [] + + for (const relativeDir of [...relativeDirs].sort()) { + const [projectName, ...rest] = relativeDir.split('/') + const childPath = rest.join('/') + if (projectName == null || projectName.length === 0) continue + + promptIds.push(childPath.length === 0 + ? `project-memory:${projectName}` + : `project-child-memory:${projectName}/${childPath}`) + } + + return promptIds +} + +function collectDiscoveredPromptIds(env: ResolvedPromptEnvironment): string[] { + const promptIds = new Set() + const globalDefinition = buildGlobalMemoryDefinition(env) + const workspaceDefinition = buildWorkspaceMemoryDefinition(env) + + if ( + fs.existsSync(globalDefinition.paths.zh) + || fs.existsSync(globalDefinition.paths.en) + || fs.existsSync(globalDefinition.paths.dist) + ) { + promptIds.add(globalDefinition.promptId) + } + + if ( + fs.existsSync(workspaceDefinition.paths.zh) + || fs.existsSync(workspaceDefinition.paths.en) + || fs.existsSync(workspaceDefinition.paths.dist) + ) { + promptIds.add(workspaceDefinition.promptId) + } + + for (const promptId of collectProjectPromptIds(env)) promptIds.add(promptId) + for (const promptId of collectSkillPromptIds(env)) promptIds.add(promptId) + for (const promptId of collectFlatPromptIds(env, 'command')) promptIds.add(promptId) + for (const promptId of collectFlatPromptIds(env, 'subagent')) promptIds.add(promptId) + for (const promptId of collectFlatPromptIds(env, 'rule')) promptIds.add(promptId) + + return [...promptIds].sort() +} + +function parseFrontMatter(content: string): YAMLFrontMatter | undefined { + try { + return parseMarkdown(content).yamlFrontMatter + } + catch { + return void 0 + } +} + +function readArtifact( + filePath: string, + includeContent: boolean, + legacySource: boolean = false +): PromptArtifactRecord | undefined { + if (!(fs.existsSync(filePath) && fs.statSync(filePath).isFile())) return void 0 + + const stat = fs.statSync(filePath) + const rawContent = includeContent ? fs.readFileSync(filePath, 'utf8') : void 0 + + const artifact: PromptArtifactRecord = { + path: filePath, + exists: true, + mtime: stat.mtime.toISOString(), + mtimeMs: stat.mtimeMs, + size: stat.size, + ...legacySource ? {legacySource: true} : {}, + ...rawContent != null ? {content: rawContent} : {} + } + + const frontMatter = rawContent != null ? parseFrontMatter(rawContent) : void 0 + if (frontMatter != null) Object.assign(artifact, {frontMatter}) + + return artifact +} + +function resolveArtifactStatus( + zhArtifact: PromptArtifactRecord | undefined, + targetArtifact: PromptArtifactRecord | undefined +): PromptArtifactState { + if (targetArtifact == null) return 'missing' + if (zhArtifact != null && targetArtifact.mtimeMs < zhArtifact.mtimeMs) return 'stale' + return 'ready' +} + +function hydratePrompt( + definition: PromptDefinition, + includeContent: boolean +): PromptDetails | null { + const hasCanonicalZh = fs.existsSync(definition.paths.zh) + const {legacyZhPath} = definition + const hasLegacyZh = !hasCanonicalZh + && legacyZhPath != null + && fs.existsSync(legacyZhPath) + const zhArtifactPath = hasCanonicalZh + ? definition.paths.zh + : hasLegacyZh + ? legacyZhPath + : void 0 + const zhArtifact = zhArtifactPath != null + ? readArtifact(zhArtifactPath, includeContent, hasLegacyZh) + : void 0 + const enArtifact = hasCanonicalZh || legacyZhPath !== definition.paths.en + ? readArtifact(definition.paths.en, includeContent) + : void 0 + const distArtifact = readArtifact(definition.paths.dist, includeContent) + + if (zhArtifact == null && enArtifact == null && distArtifact == null) return null + + const updatedAt = [zhArtifact, enArtifact, distArtifact] + .filter((artifact): artifact is PromptArtifactRecord => artifact != null) + .sort((a, b) => b.mtimeMs - a.mtimeMs)[0] + ?.mtime + + const prompt: PromptDetails = { + promptId: definition.promptId, + kind: definition.kind, + logicalName: definition.logicalName, + paths: definition.paths, + exists: { + zh: zhArtifact != null, + en: enArtifact != null, + dist: distArtifact != null + }, + enStatus: resolveArtifactStatus(zhArtifact, enArtifact), + distStatus: resolveArtifactStatus(zhArtifact, distArtifact), + ...updatedAt != null ? {updatedAt} : {}, + ...zhArtifact?.legacySource === true ? {legacyZhSource: true} : {}, + src: { + ...zhArtifact != null ? {zh: zhArtifact} : {}, + ...enArtifact != null ? {en: enArtifact} : {} + } + } + + if (distArtifact != null) Object.assign(prompt, {dist: distArtifact}) + + const frontMatter = zhArtifact?.frontMatter ?? enArtifact?.frontMatter ?? distArtifact?.frontMatter + if (frontMatter != null) Object.assign(prompt, {frontMatter}) + + return prompt +} + +function matchesFilter( + value: T, + allowed: readonly T[] | undefined +): boolean { + if (allowed == null || allowed.length === 0) return true + return allowed.includes(value) +} + +function matchesQuery(item: PromptCatalogItem, query: string | undefined): boolean { + if (query == null || query.trim().length === 0) return true + const normalizedQuery = query.trim().toLowerCase() + return item.promptId.toLowerCase().includes(normalizedQuery) + || item.logicalName.toLowerCase().includes(normalizedQuery) +} + +function toCatalogItem(prompt: PromptDetails): PromptCatalogItem { + return { + promptId: prompt.promptId, + kind: prompt.kind, + logicalName: prompt.logicalName, + paths: prompt.paths, + exists: prompt.exists, + enStatus: prompt.enStatus, + distStatus: prompt.distStatus, + ...prompt.updatedAt != null ? {updatedAt: prompt.updatedAt} : {}, + ...prompt.legacyZhSource === true ? {legacyZhSource: true} : {} + } +} + +function isProjectMemoryDefinition(definition: PromptDefinition): boolean { + return LEGACY_PROJECT_MEMORY_KINDS.has(definition.kind) +} + +function writeTextFile(filePath: string, content: string): void { + fs.mkdirSync(path.dirname(filePath), {recursive: true}) + fs.writeFileSync(filePath, content, 'utf8') +} + +function prepareProjectMemoryForEnglishWrite(definition: PromptDefinition): void { + if (!isProjectMemoryDefinition(definition)) return + if (fs.existsSync(definition.paths.zh)) return + if (definition.legacyZhPath == null || !fs.existsSync(definition.legacyZhPath)) return + + const legacyContent = fs.readFileSync(definition.legacyZhPath, 'utf8') + writeTextFile(definition.paths.zh, legacyContent) +} + +function migrateLegacyProjectMemorySourceOnZhWrite(definition: PromptDefinition): void { + if (!isProjectMemoryDefinition(definition)) return + if (definition.legacyZhPath == null || definition.legacyZhPath === definition.paths.zh) return + if (!fs.existsSync(definition.legacyZhPath)) return + + fs.rmSync(definition.legacyZhPath, {force: true}) +} + +export async function listPrompts( + options: ListPromptsOptions = {} +): Promise { + const env = resolvePromptEnvironment(options) + const items = collectDiscoveredPromptIds(env) + .map(promptId => hydratePrompt(buildPromptDefinitionFromId(promptId, env), false)) + .filter((item): item is PromptDetails => item != null) + .map(toCatalogItem) + .filter(item => matchesFilter(item.kind, options.kinds)) + .filter(item => matchesFilter(item.enStatus, options.enStatus)) + .filter(item => matchesFilter(item.distStatus, options.distStatus)) + .filter(item => matchesQuery(item, options.query)) + + return items.sort((a, b) => a.promptId.localeCompare(b.promptId)) +} + +export async function getPrompt( + promptId: string, + options: PromptServiceOptions = {} +): Promise { + const env = resolvePromptEnvironment(options) + return hydratePrompt(buildPromptDefinitionFromId(promptId, env), true) +} + +export async function upsertPromptSource( + input: UpsertPromptSourceInput +): Promise { + const env = resolvePromptEnvironment(input) + const locale = input.locale ?? 'zh' + const definition = buildPromptDefinitionFromId(input.promptId, env) + + if (locale === 'zh') { + writeTextFile(definition.paths.zh, input.content) + migrateLegacyProjectMemorySourceOnZhWrite(definition) + } else { + prepareProjectMemoryForEnglishWrite(definition) + writeTextFile(definition.paths.en, input.content) + } + + const prompt = hydratePrompt(definition, true) + if (prompt == null) throw new Error(`Failed to load prompt after write: ${input.promptId}`) + return prompt +} + +export async function writePromptArtifacts( + input: WritePromptArtifactsInput +): Promise { + if (input.enContent == null && input.distContent == null) throw new Error('writePromptArtifacts requires enContent or distContent') + + const env = resolvePromptEnvironment(input) + const definition = buildPromptDefinitionFromId(input.promptId, env) + + if (input.enContent != null) { + prepareProjectMemoryForEnglishWrite(definition) + writeTextFile(definition.paths.en, input.enContent) + } + + if (input.distContent != null) writeTextFile(definition.paths.dist, input.distContent) + + const prompt = hydratePrompt(definition, true) + if (prompt == null) throw new Error(`Failed to load prompt after write: ${input.promptId}`) + return prompt +} + +export async function resolvePromptDefinition( + promptId: string, + options: PromptServiceOptions = {} +): Promise { + const env = resolvePromptEnvironment(options) + return buildPromptDefinitionFromId(promptId, env).paths +} diff --git a/cli/tsdown.config.ts b/cli/tsdown.config.ts index 44ee7cd9..24437ae6 100644 --- a/cli/tsdown.config.ts +++ b/cli/tsdown.config.ts @@ -71,7 +71,7 @@ export default defineConfig([ noExternal: noExternalDeps, format: ['esm', 'cjs'], minify: true, - dts: false, + dts: {sourcemap: false}, outputOptions: {exports: 'named'}, define: { __CLI_VERSION__: JSON.stringify(pkg.version), @@ -79,6 +79,27 @@ export default defineConfig([ __KIRO_GLOBAL_POWERS_REGISTRY__: kiroGlobalPowersRegistry } }, + { + entry: ['./src/main.ts'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src'), + ...pluginAliases + }, + noExternal: noExternalDeps, + format: ['esm'], + minify: true, + dts: false, + outputOptions: {banner: '#!/usr/bin/env node'}, + define: { + __CLI_VERSION__: JSON.stringify(pkg.version), + __CLI_PACKAGE_NAME__: JSON.stringify(pkg.name), + __KIRO_GLOBAL_POWERS_REGISTRY__: kiroGlobalPowersRegistry + } + }, { entry: ['./src/plugin-runtime.ts'], platform: 'node', diff --git a/doc/package.json b/doc/package.json index 51356fd1..16ed523e 100644 --- a/doc/package.json +++ b/doc/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-docs", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "private": true, "description": "Documentation site for @truenine/memory-sync, built with Next.js 16 and MDX.", "engines": { diff --git a/gui/package.json b/gui/package.json index 23fb4c55..d4fb3275 100644 --- a/gui/package.json +++ b/gui/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-gui", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "private": true, "engines": { "node": ">=25.2.1", diff --git a/gui/src-tauri/Cargo.toml b/gui/src-tauri/Cargo.toml index 0d0f2220..0bce7367 100644 --- a/gui/src-tauri/Cargo.toml +++ b/gui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "memory-sync-gui" -version = "2026.10317.12338" +version = "2026.10318.10339" description = "Memory Sync desktop GUI application" authors.workspace = true edition.workspace = true diff --git a/gui/src-tauri/tauri.conf.json b/gui/src-tauri/tauri.conf.json index 70fef1e6..466decf6 100644 --- a/gui/src-tauri/tauri.conf.json +++ b/gui/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.tauri.app/config/2", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "productName": "Memory Sync", "identifier": "org.truenine.memory-sync", "build": { diff --git a/libraries/logger/package.json b/libraries/logger/package.json index 845f9372..ae93394a 100644 --- a/libraries/logger/package.json +++ b/libraries/logger/package.json @@ -1,7 +1,7 @@ { "name": "@truenine/logger", "type": "module", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "private": true, "description": "Rust-powered structured logger for Node.js via N-API", "license": "AGPL-3.0-only", diff --git a/libraries/md-compiler/package.json b/libraries/md-compiler/package.json index 7421a963..7785053f 100644 --- a/libraries/md-compiler/package.json +++ b/libraries/md-compiler/package.json @@ -1,7 +1,7 @@ { "name": "@truenine/md-compiler", "type": "module", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "private": true, "description": "Rust-powered MDX→Markdown compiler for Node.js with pure-TS fallback", "license": "AGPL-3.0-only", diff --git a/libraries/script-runtime/package.json b/libraries/script-runtime/package.json index c19c58ef..6a529d63 100644 --- a/libraries/script-runtime/package.json +++ b/libraries/script-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@truenine/script-runtime", "type": "module", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "private": true, "description": "Rust-backed TypeScript proxy runtime for tnmsc", "license": "AGPL-3.0-only", diff --git a/mcp/eslint.config.ts b/mcp/eslint.config.ts new file mode 100644 index 00000000..f5b4e8de --- /dev/null +++ b/mcp/eslint.config.ts @@ -0,0 +1,26 @@ +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' + +import eslint10 from '@truenine/eslint10-config' + +const configDir = dirname(fileURLToPath(import.meta.url)) + +const config = eslint10({ + type: 'lib', + typescript: { + strictTypescriptEslint: true, + tsconfigPath: resolve(configDir, 'tsconfig.json'), + parserOptions: { + allowDefaultProject: true + } + }, + ignores: [ + '.turbo/**', + '*.md', + '**/*.md', + '**/*.toml', + 'dist/**' + ] +}) + +export default config as unknown diff --git a/mcp/package.json b/mcp/package.json new file mode 100644 index 00000000..47e5869d --- /dev/null +++ b/mcp/package.json @@ -0,0 +1,58 @@ +{ + "name": "@truenine/memory-sync-mcp", + "type": "module", + "version": "2026.10318.10339", + "description": "MCP stdio server for managing memory-sync prompt sources and translation artifacts", + "author": "TrueNine", + "license": "AGPL-3.0-only", + "homepage": "https://github.com/TrueNine/memory-sync", + "repository": { + "type": "git", + "url": "git+https://github.com/TrueNine/memory-sync.git", + "directory": "mcp" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "bin": { + "memory-sync-mcp": "./dist/main.mjs" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "build": "tsdown", + "check": "run-p typecheck lint", + "lint": "eslint --cache .", + "lintfix": "eslint --fix --cache .", + "prepublishOnly": "run-s build", + "test": "vitest run", + "typecheck": "tsc --noEmit -p tsconfig.lib.json" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "catalog:", + "@truenine/memory-sync-cli": "workspace:*", + "zod": "catalog:" + }, + "devDependencies": { + "@truenine/eslint10-config": "catalog:", + "@types/node": "catalog:", + "eslint": "catalog:", + "npm-run-all2": "catalog:", + "tsdown": "catalog:", + "tsx": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/mcp/src/env.d.ts b/mcp/src/env.d.ts new file mode 100644 index 00000000..4e5d52b1 --- /dev/null +++ b/mcp/src/env.d.ts @@ -0,0 +1,2 @@ +declare const __MCP_PACKAGE_NAME__: string +declare const __MCP_VERSION__: string diff --git a/mcp/src/index.ts b/mcp/src/index.ts new file mode 100644 index 00000000..e4830aa4 --- /dev/null +++ b/mcp/src/index.ts @@ -0,0 +1,4 @@ +export { + createMemorySyncMcpServer, + runMemorySyncMcpStdioServer +} from './server' diff --git a/mcp/src/main.ts b/mcp/src/main.ts new file mode 100644 index 00000000..af93fa06 --- /dev/null +++ b/mcp/src/main.ts @@ -0,0 +1,10 @@ +#!/usr/bin/env node + +import process from 'node:process' +import {runMemorySyncMcpStdioServer} from './server' + +void runMemorySyncMcpStdioServer().catch((error: unknown) => { + const message = error instanceof Error ? error.stack ?? error.message : String(error) + process.stderr.write(`${message}\n`) + process.exit(1) +}) diff --git a/mcp/src/server.test.ts b/mcp/src/server.test.ts new file mode 100644 index 00000000..451568ef --- /dev/null +++ b/mcp/src/server.test.ts @@ -0,0 +1,255 @@ +import type {Buffer} from 'node:buffer' +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' +import process from 'node:process' +import {fileURLToPath} from 'node:url' +import {Client} from '@modelcontextprotocol/sdk/client/index.js' +import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js' +import {afterEach, describe, expect, it} from 'vitest' + +const tempDirs: string[] = [] +const serverMainPath = fileURLToPath(new URL('./main.ts', import.meta.url)) +const tsxPackageJsonPath = fileURLToPath(new URL('../node_modules/tsx/package.json', import.meta.url)) +const tsxCliPath = path.join(path.dirname(tsxPackageJsonPath), 'dist', 'cli.mjs') + +function createTempDir(prefix: string): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)) + tempDirs.push(dir) + return dir +} + +function createTransportEnv(homeDir: string): Record { + const envEntries = Object.entries(process.env) + .filter((entry): entry is [string, string] => typeof entry[1] === 'string') + + return { + ...Object.fromEntries(envEntries), + HOME: homeDir, + USERPROFILE: homeDir, + XDG_CACHE_HOME: path.join(homeDir, '.cache'), + XDG_CONFIG_HOME: path.join(homeDir, '.config'), + XDG_DATA_HOME: path.join(homeDir, '.local', 'share'), + XDG_STATE_HOME: path.join(homeDir, '.local', 'state') + } +} + +interface TextContentBlock { + readonly type: string + readonly text?: string +} + +function getTextBlock(result: unknown): string { + if ( + typeof result !== 'object' + || result == null + || !('content' in result) + || !Array.isArray(result.content) + ) { + throw new Error('Expected content blocks in MCP result') + } + + const textBlock = result.content + .find((block): block is TextContentBlock => typeof block === 'object' && block != null && 'type' in block && (block as {type?: unknown}).type === 'text') + if (textBlock?.text == null) throw new Error('Expected a text content block in MCP result') + return textBlock.text +} + +function parseToolResult(result: unknown): T { + return JSON.parse(getTextBlock(result)) as T +} + +afterEach(() => { + for (const dir of tempDirs.splice(0)) fs.rmSync(dir, {recursive: true, force: true}) +}) + +describe('memory-sync MCP stdio server', () => { + it('registers all tools and supports prompt source and translation flows', async () => { + const workspaceDir = createTempDir('tnmsc-mcp-workspace-') + const homeDir = createTempDir('tnmsc-mcp-home-') + const client = new Client({ + name: 'memory-sync-mcp-test-client', + version: '0.0.0' + }) + const transport = new StdioClientTransport({ + command: process.execPath, + args: [tsxCliPath, serverMainPath], + cwd: workspaceDir, + env: createTransportEnv(homeDir), + stderr: 'pipe' + }) + let stderrOutput = '' + + transport.stderr?.on('data', (chunk: Buffer | string) => stderrOutput += String(chunk)) + + try { + await client.connect(transport) + + const tools = await client.listTools() + expect(tools.tools.map(tool => tool.name).sort()).toEqual([ + 'apply_prompt_translation', + 'get_prompt', + 'list_prompts', + 'upsert_prompt_src' + ]) + expect( + tools.tools.find(tool => tool.name === 'apply_prompt_translation')?.inputSchema.properties + ).toMatchObject({ + promptId: expect.any(Object), + enContent: expect.any(Object), + distContent: expect.any(Object) + }) + + const invalidApplyResult = await client.callTool({ + name: 'apply_prompt_translation', + arguments: { + workspaceDir, + promptId: 'global-memory' + } + }) + expect(invalidApplyResult.isError).toBe(true) + expect(getTextBlock(invalidApplyResult)).toContain('apply_prompt_translation requires enContent or distContent') + + const upsertGlobalResult = await client.callTool({ + name: 'upsert_prompt_src', + arguments: { + workspaceDir, + promptId: 'global-memory', + content: '---\ndescription: global zh\n---\nGlobal zh' + } + }) + const upsertedGlobal = parseToolResult<{prompt: {enStatus: string, distStatus: string}}>(upsertGlobalResult) + expect(upsertedGlobal.prompt).toMatchObject({ + enStatus: 'missing', + distStatus: 'missing' + }) + + const missingPromptListResult = await client.callTool({ + name: 'list_prompts', + arguments: { + workspaceDir, + enStatus: ['missing'], + distStatus: ['missing'] + } + }) + const missingPromptList = parseToolResult<{prompts: {promptId: string}[]}>(missingPromptListResult) + expect(missingPromptList.prompts.map(prompt => prompt.promptId)).toEqual(['global-memory']) + + const globalPromptResult = await client.callTool({ + name: 'get_prompt', + arguments: { + workspaceDir, + promptId: 'global-memory' + } + }) + const globalPrompt = parseToolResult<{ + prompt: { + src: { + zh?: {content?: string} + en?: {content?: string} + } + } + }>(globalPromptResult) + expect(globalPrompt.prompt.src.zh?.content).toContain('Global zh') + expect(globalPrompt.prompt.src.en).toBeUndefined() + + const applyEnglishResult = await client.callTool({ + name: 'apply_prompt_translation', + arguments: { + workspaceDir, + promptId: 'global-memory', + enContent: '---\ndescription: global en\n---\nGlobal en' + } + }) + const globalAfterEnglish = parseToolResult<{ + prompt: { + enStatus: string + distStatus: string + src: { + en?: {content?: string} + } + } + }>(applyEnglishResult) + expect(globalAfterEnglish.prompt).toMatchObject({ + enStatus: 'ready', + distStatus: 'missing' + }) + expect(globalAfterEnglish.prompt.src.en?.content).toContain('Global en') + + const applyDistResult = await client.callTool({ + name: 'apply_prompt_translation', + arguments: { + workspaceDir, + promptId: 'global-memory', + distContent: '---\ndescription: global dist\n---\nGlobal dist' + } + }) + const globalAfterDist = parseToolResult<{ + prompt: { + distStatus: string + dist?: {content?: string} + } + }>(applyDistResult) + expect(globalAfterDist.prompt.distStatus).toBe('ready') + expect(globalAfterDist.prompt.dist?.content).toContain('Global dist') + + await client.callTool({ + name: 'upsert_prompt_src', + arguments: { + workspaceDir, + promptId: 'command:demo/build', + content: '---\ndescription: command zh\n---\nCommand zh' + } + }) + const applyCommandTranslationResult = await client.callTool({ + name: 'apply_prompt_translation', + arguments: { + workspaceDir, + promptId: 'command:demo/build', + enContent: '---\ndescription: command en\n---\nCommand en', + distContent: '---\ndescription: command dist\n---\nCommand dist' + } + }) + const commandPrompt = parseToolResult<{ + prompt: { + promptId: string + enStatus: string + distStatus: string + src: { + en?: {content?: string} + } + dist?: {content?: string} + } + }>(applyCommandTranslationResult) + expect(commandPrompt.prompt).toMatchObject({ + promptId: 'command:demo/build', + enStatus: 'ready', + distStatus: 'ready' + }) + expect(commandPrompt.prompt.src.en?.content).toContain('Command en') + expect(commandPrompt.prompt.dist?.content).toContain('Command dist') + + const filteredListResult = await client.callTool({ + name: 'list_prompts', + arguments: { + workspaceDir, + kinds: ['command'], + query: 'demo', + enStatus: ['ready'], + distStatus: ['ready'] + } + }) + const filteredPrompts = parseToolResult<{prompts: {promptId: string}[]}>(filteredListResult) + expect(filteredPrompts.prompts.map(prompt => prompt.promptId)).toEqual(['command:demo/build']) + } + catch (error) { + if (stderrOutput.trim().length === 0) throw error + + const errorMessage = error instanceof Error ? error.message : String(error) + throw new Error(`${errorMessage}\nMCP stderr:\n${stderrOutput.trim()}`) + } + finally { + await client.close() + } + }) +}) diff --git a/mcp/src/server.ts b/mcp/src/server.ts new file mode 100644 index 00000000..dbbefac0 --- /dev/null +++ b/mcp/src/server.ts @@ -0,0 +1,238 @@ +import type { + ListPromptsOptions, + ManagedPromptKind, + PromptArtifactState, + PromptServiceOptions, + PromptSourceLocale +} from '@truenine/memory-sync-cli' +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js' +import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js' +import {getPrompt, listPrompts, upsertPromptSource, writePromptArtifacts} from '@truenine/memory-sync-cli' +import {z} from 'zod' + +const promptKindSchema = z.enum([ + 'global-memory', + 'workspace-memory', + 'project-memory', + 'project-child-memory', + 'skill', + 'skill-child-doc', + 'command', + 'subagent', + 'rule' +] satisfies readonly ManagedPromptKind[]) + +const promptStatusSchema = z.enum([ + 'missing', + 'stale', + 'ready' +] satisfies readonly PromptArtifactState[]) + +const localeSchema = z.enum(['zh', 'en'] satisfies readonly PromptSourceLocale[]) + +const workspaceInputSchema = { + workspaceDir: z.string().min(1).optional() +} +const MCP_PACKAGE_NAME = typeof __MCP_PACKAGE_NAME__ === 'string' + ? __MCP_PACKAGE_NAME__ + : '@truenine/memory-sync-mcp' +const MCP_VERSION = typeof __MCP_VERSION__ === 'string' + ? __MCP_VERSION__ + : '0.0.0-dev' + +function createPromptServiceOptions( + workspaceDir?: string +): PromptServiceOptions { + if (workspaceDir == null) return {} + + return { + cwd: workspaceDir, + pluginOptions: { + workspaceDir + } + } +} + +function validateTranslationPayload(input: { + readonly enContent?: string | undefined + readonly distContent?: string | undefined +}): void { + if (input.enContent == null && input.distContent == null) throw new Error('apply_prompt_translation requires enContent or distContent') +} + +function toJsonText(value: unknown): string { + return JSON.stringify(value, null, 2) +} + +function buildSuccessResult(value: unknown): { + content: [{type: 'text', text: string}] +} { + return { + content: [ + { + type: 'text', + text: toJsonText(value) + } + ] + } +} + +function buildErrorResult(error: unknown): { + content: [{type: 'text', text: string}] + isError: true +} { + const message = error instanceof Error ? error.message : String(error) + + return { + content: [ + { + type: 'text', + text: message + } + ], + isError: true + } +} + +function registerListPromptsTool(server: McpServer): void { + server.registerTool( + 'list_prompts', + { + title: 'List Prompts', + description: 'List managed prompt records with zh/en/dist status signals.', + inputSchema: { + ...workspaceInputSchema, + kinds: z.array(promptKindSchema).optional(), + query: z.string().min(1).optional(), + enStatus: z.array(promptStatusSchema).optional(), + distStatus: z.array(promptStatusSchema).optional() + } + }, + async input => { + try { + const promptOptions: ListPromptsOptions = { + ...createPromptServiceOptions(input.workspaceDir), + ...input.kinds != null ? {kinds: input.kinds} : {}, + ...input.query != null ? {query: input.query} : {}, + ...input.enStatus != null ? {enStatus: input.enStatus} : {}, + ...input.distStatus != null ? {distStatus: input.distStatus} : {} + } + const prompts = await listPrompts(promptOptions) + + return buildSuccessResult({prompts}) + } + catch (error) { + return buildErrorResult(error) + } + } + ) +} + +function registerGetPromptTool(server: McpServer): void { + server.registerTool( + 'get_prompt', + { + title: 'Get Prompt', + description: 'Read a single managed prompt and return its source and dist artifacts.', + inputSchema: { + ...workspaceInputSchema, + promptId: z.string().min(1) + } + }, + async input => { + try { + const prompt = await getPrompt(input.promptId, createPromptServiceOptions(input.workspaceDir)) + + return buildSuccessResult({prompt}) + } + catch (error) { + return buildErrorResult(error) + } + } + ) +} + +function registerUpsertPromptSrcTool(server: McpServer): void { + server.registerTool( + 'upsert_prompt_src', + { + title: 'Upsert Prompt Source', + description: 'Create or update zh/en source prompt files without touching dist.', + inputSchema: { + ...workspaceInputSchema, + promptId: z.string().min(1), + locale: localeSchema.optional(), + content: z.string() + } + }, + async input => { + try { + const prompt = await upsertPromptSource({ + ...createPromptServiceOptions(input.workspaceDir), + promptId: input.promptId, + content: input.content, + ...input.locale != null ? {locale: input.locale} : {} + }) + + return buildSuccessResult({prompt}) + } + catch (error) { + return buildErrorResult(error) + } + } + ) +} + +function registerApplyPromptTranslationTool(server: McpServer): void { + server.registerTool( + 'apply_prompt_translation', + { + title: 'Apply Prompt Translation', + description: 'Write externally generated en source and optional dist prompt content.', + inputSchema: { + ...workspaceInputSchema, + promptId: z.string().min(1), + enContent: z.string().optional(), + distContent: z.string().optional() + } + }, + async input => { + try { + validateTranslationPayload(input) + + const prompt = await writePromptArtifacts({ + ...createPromptServiceOptions(input.workspaceDir), + promptId: input.promptId, + ...input.enContent != null ? {enContent: input.enContent} : {}, + ...input.distContent != null ? {distContent: input.distContent} : {} + }) + + return buildSuccessResult({prompt}) + } + catch (error) { + return buildErrorResult(error) + } + } + ) +} + +export function createMemorySyncMcpServer(): McpServer { + const server = new McpServer({ + name: MCP_PACKAGE_NAME, + version: MCP_VERSION + }) + + registerListPromptsTool(server) + registerGetPromptTool(server) + registerUpsertPromptSrcTool(server) + registerApplyPromptTranslationTool(server) + + return server +} + +export async function runMemorySyncMcpStdioServer(): Promise { + const server = createMemorySyncMcpServer() + const transport = new StdioServerTransport() + + await server.connect(transport) +} diff --git a/mcp/tsconfig.json b/mcp/tsconfig.json new file mode 100644 index 00000000..337fa92e --- /dev/null +++ b/mcp/tsconfig.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "noUncheckedSideEffectImports": true, + "incremental": true, + "composite": false, + "target": "ESNext", + "lib": [ + "ESNext" + ], + "moduleDetection": "force", + "useDefineForClassFields": true, + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "paths": { + "@/*": [ + "./src/*" + ] + }, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true, + "declaration": true, + "declarationMap": true, + "importHelpers": true, + "newLine": "lf", + "noEmit": true, + "noEmitHelpers": false, + "removeComments": false, + "sourceMap": true, + "stripInternal": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*", + "eslint.config.ts", + "tsdown.config.ts", + "vitest.config.ts" + ], + "exclude": [ + "../node_modules", + "dist" + ] +} diff --git a/mcp/tsconfig.lib.json b/mcp/tsconfig.lib.json new file mode 100644 index 00000000..12ffb0d0 --- /dev/null +++ b/mcp/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": [ + "src/**/*", + "eslint.config.ts", + "tsdown.config.ts", + "vitest.config.ts" + ] +} diff --git a/mcp/tsdown.config.ts b/mcp/tsdown.config.ts new file mode 100644 index 00000000..1908c42b --- /dev/null +++ b/mcp/tsdown.config.ts @@ -0,0 +1,45 @@ +import {readFileSync} from 'node:fs' +import {resolve} from 'node:path' +import {defineConfig} from 'tsdown' + +const pkg = JSON.parse(readFileSync('./package.json', 'utf8')) as {name: string, version: string} + +export default defineConfig([ + { + entry: ['./src/index.ts', '!**/*.{spec,test}.*'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src') + }, + format: ['esm'], + minify: false, + dts: {sourcemap: false}, + define: { + __MCP_PACKAGE_NAME__: JSON.stringify(pkg.name), + __MCP_VERSION__: JSON.stringify(pkg.version) + } + }, + { + entry: ['./src/main.ts'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src') + }, + format: ['esm'], + minify: false, + dts: false, + outputOptions: { + banner: '#!/usr/bin/env node' + }, + define: { + __MCP_PACKAGE_NAME__: JSON.stringify(pkg.name), + __MCP_VERSION__: JSON.stringify(pkg.version) + } + } +]) diff --git a/mcp/vitest.config.ts b/mcp/vitest.config.ts new file mode 100644 index 00000000..e4748c04 --- /dev/null +++ b/mcp/vitest.config.ts @@ -0,0 +1,11 @@ +import {configDefaults, defineConfig} from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + passWithNoTests: true, + exclude: [...configDefaults.exclude, 'dist/**'], + onConsoleLog: () => false, + testTimeout: 30000 + } +}) diff --git a/package.json b/package.json index 75ec6bbf..258cf3bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync", - "version": "2026.10317.12338", + "version": "2026.10318.10339", "description": "Cross-AI-tool prompt synchronisation toolkit (CLI + Tauri desktop GUI) — one ruleset, multi-target adaptation. Monorepo powered by pnpm + Turbo.", "license": "AGPL-3.0-only", "keywords": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5241bbb1..d229ef66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ catalogs: '@mdx-js/react': specifier: ^3.1.1 version: 3.1.1 + '@modelcontextprotocol/sdk': + specifier: ^1.27.1 + version: 1.27.1 '@monaco-editor/react': specifier: ^4.7.0 version: 4.7.0 @@ -517,6 +520,43 @@ importers: specifier: 'catalog:' version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 'catalog:' + version: 1.27.1(zod@4.3.6) + '@truenine/memory-sync-cli': + specifier: workspace:* + version: link:../cli + zod: + specifier: 'catalog:' + version: 4.3.6 + devDependencies: + '@truenine/eslint10-config': + specifier: 'catalog:' + version: 2026.10209.11105(57cd6091d29b00e41b508df9848011ef) + '@types/node': + specifier: 'catalog:' + version: 25.3.3 + eslint: + specifier: 'catalog:' + version: 10.0.2(jiti@2.6.1) + npm-run-all2: + specifier: 'catalog:' + version: 8.0.4 + tsdown: + specifier: 'catalog:' + version: 0.21.0-beta.2(synckit@0.11.12)(typescript@5.9.3) + tsx: + specifier: 'catalog:' + version: 4.21.0 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + packages: '@antfu/eslint-config@6.7.1': @@ -956,6 +996,12 @@ packages: resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1281,6 +1327,16 @@ packages: '@types/react': '>=16' react: '>=16' + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -2633,6 +2689,10 @@ packages: '@vue/shared@3.5.26': resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2643,9 +2703,20 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -2708,6 +2779,10 @@ packages: birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2731,10 +2806,22 @@ packages: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + caniuse-lite@1.0.30001775: resolution: {integrity: sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==} @@ -2814,15 +2901,35 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-js-compat@3.48.0: resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2904,6 +3011,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2935,6 +3046,13 @@ packages: oxc-resolver: optional: true + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} @@ -2950,6 +3068,10 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.20.0: resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} engines: {node: '>=10.13.0'} @@ -2958,9 +3080,21 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + es-toolkit@1.44.0: resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} @@ -2973,6 +3107,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -3240,6 +3377,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@2.0.3: resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} @@ -3250,10 +3391,28 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.3.1: + resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -3293,6 +3452,9 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-wrap-ansi@0.2.0: resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} @@ -3319,6 +3481,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -3338,6 +3504,14 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@11.3.3: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} @@ -3347,10 +3521,21 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} @@ -3376,6 +3561,10 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3386,6 +3575,18 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hono@4.12.8: + resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==} + engines: {node: '>=16.9.0'} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} @@ -3395,6 +3596,10 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -3425,10 +3630,21 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -3468,6 +3684,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + isbot@5.1.35: resolution: {integrity: sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg==} engines: {node: '>=18'} @@ -3495,6 +3714,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.2.1: + resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -3532,6 +3754,12 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -3686,6 +3914,10 @@ packages: resolution: {integrity: sha512-PPeGSRa+8stQEKvCr2Xym9KIqf2SPwl1chc7cxbK+aY6ORpwOcowtARQEXstZBjQwXTE5GnfE0zg0MFFy+XPzA==} engines: {vscode: ^1.55.0} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -3734,10 +3966,18 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3854,6 +4094,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} @@ -3887,6 +4135,10 @@ packages: resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} engines: {node: '>=18'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + next@16.1.6: resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} engines: {node: '>=20.9.0'} @@ -3927,12 +4179,27 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-deep-merge@2.0.0: resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3961,6 +4228,10 @@ packages: parse-statements@1.0.11: resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3969,6 +4240,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3988,6 +4262,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -4026,6 +4304,10 @@ packages: engines: {node: '>=14'} hasBin: true + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4033,6 +4315,10 @@ packages: pure-rand@7.0.1: resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -4042,6 +4328,14 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -4133,6 +4427,10 @@ packages: resolution: {integrity: sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==} engines: {node: '>= 0.8.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -4176,6 +4474,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4198,6 +4500,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + seroval-plugins@1.5.0: resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==} engines: {node: '>=10'} @@ -4208,6 +4514,13 @@ packages: resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} engines: {node: '>=10'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -4224,6 +4537,22 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -4265,6 +4594,10 @@ packages: state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -4338,6 +4671,10 @@ packages: resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} engines: {node: '>=20'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + toml-eslint-parser@0.10.1: resolution: {integrity: sha512-9mjy3frhioGIVGcwamlVlUyJ9x+WHw/TXiz9R4YOlmsIuBN43r9Dp8HZ35SF9EKjHrn3BUZj04CF+YqZ2oJ+7w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4437,6 +4774,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript-eslint@8.50.0: resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4486,6 +4827,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -4517,6 +4862,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -4628,6 +4977,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xml-lexer@0.2.2: resolution: {integrity: sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==} @@ -5085,6 +5437,10 @@ snapshots: '@eslint/core': 1.1.0 levn: 0.4.1 + '@hono/node-server@1.19.11(hono@4.12.8)': + dependencies: + hono: 4.12.8 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -5337,6 +5693,28 @@ snapshots: '@types/react': 19.2.14 react: 19.2.4 + '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.11(hono@4.12.8) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.1(express@5.2.1) + hono: 4.12.8 + jose: 6.2.1 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - supports-color + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 @@ -6592,12 +6970,21 @@ snapshots: '@vue/shared@3.5.26': {} + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 acorn@8.16.0: {} + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -6605,6 +6992,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-styles@6.2.3: {} ansis@4.2.0: {} @@ -6659,6 +7053,20 @@ snapshots: birpc@4.0.0: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} brace-expansion@2.0.2: @@ -6683,8 +7091,20 @@ snapshots: builtin-modules@5.0.0: {} + bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + caniuse-lite@1.0.30001775: {} ccount@2.0.1: {} @@ -6747,14 +7167,27 @@ snapshots: confbox@0.2.4: {} + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} cookie-es@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + core-js-compat@3.48.0: dependencies: browserslist: 4.28.1 + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -6822,6 +7255,8 @@ snapshots: defu@6.1.4: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -6840,12 +7275,22 @@ snapshots: dts-resolver@2.1.3: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + electron-to-chromium@1.5.302: {} emnapi@1.8.1: {} empathic@2.0.0: {} + encodeurl@2.0.0: {} + enhanced-resolve@5.20.0: dependencies: graceful-fs: 4.2.11 @@ -6853,8 +7298,16 @@ snapshots: entities@7.0.1: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + es-toolkit@1.44.0: {} esbuild@0.27.3: @@ -6888,6 +7341,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} @@ -7241,14 +7696,60 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + eventemitter3@2.0.3: {} eventemitter3@5.0.4: {} events@3.3.0: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + expect-type@1.3.0: {} + express-rate-limit@8.3.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.8: {} extend@3.0.2: {} @@ -7289,6 +7790,8 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 + fast-uri@3.1.0: {} + fast-wrap-ansi@0.2.0: dependencies: fast-string-width: 3.0.2 @@ -7313,6 +7816,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up-simple@1.0.1: {} find-up@5.0.0: @@ -7329,6 +7843,10 @@ snapshots: format@0.2.2: {} + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 @@ -7338,8 +7856,28 @@ snapshots: fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -7360,18 +7898,36 @@ snapshots: globrex@0.1.2: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hono@4.12.8: {} + hookable@6.0.1: {} html-entities@2.6.0: {} html-escaper@2.0.2: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -7390,8 +7946,14 @@ snapshots: indent-string@5.0.0: {} + inherits@2.0.4: {} + internmap@2.0.3: {} + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -7423,6 +7985,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + isbot@5.1.35: {} isexe@2.0.0: {} @@ -7444,6 +8008,8 @@ snapshots: jiti@2.6.1: {} + jose@6.2.1: {} + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -7466,6 +8032,10 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-with-bigint@3.5.7: {} @@ -7602,6 +8172,8 @@ snapshots: fast-deep-equal: 3.1.3 svgson: 5.3.1 + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -7764,8 +8336,12 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + media-typer@1.1.0: {} + memorystream@0.3.1: {} + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} micromark-core-commonmark@2.0.3: @@ -8044,6 +8620,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + minimatch@10.2.4: dependencies: brace-expansion: 5.0.4 @@ -8074,6 +8656,8 @@ snapshots: natural-orderby@5.0.0: {} + negotiator@1.0.0: {} + next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 16.1.6 @@ -8119,10 +8703,22 @@ snapshots: dependencies: boolbase: 1.0.0 + object-assign@4.1.1: {} + object-deep-merge@2.0.0: {} + object-inspect@1.13.4: {} + obug@2.1.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -8160,10 +8756,14 @@ snapshots: parse-statements@1.0.11: {} + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} + path-to-regexp@8.3.0: {} + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -8174,6 +8774,8 @@ snapshots: pidtree@0.6.0: {} + pkce-challenge@5.0.1: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -8217,16 +8819,34 @@ snapshots: prettier@3.8.1: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} pure-rand@7.0.1: {} + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + quansync@0.2.11: {} quansync@1.0.0: {} queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -8349,6 +8969,8 @@ snapshots: rename-keys@1.2.0: {} + require-from-string@2.0.2: {} + reselect@5.1.1: {} reserved-identifiers@1.2.0: {} @@ -8424,6 +9046,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -8442,12 +9074,39 @@ snapshots: semver@7.7.4: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + seroval-plugins@1.5.0(seroval@1.5.0): dependencies: seroval: 1.5.0 seroval@1.5.0: {} + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + sharp@0.34.5: dependencies: '@img/colour': 1.1.0 @@ -8488,6 +9147,34 @@ snapshots: shell-quote@1.8.3: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -8515,6 +9202,8 @@ snapshots: state-local@1.0.7: {} + statuses@2.0.2: {} + std-env@3.10.0: {} stringify-entities@4.0.4: @@ -8572,6 +9261,8 @@ snapshots: '@sindresorhus/base62': 1.0.0 reserved-identifiers: 1.2.0 + toidentifier@1.0.1: {} + toml-eslint-parser@0.10.1: dependencies: eslint-visitor-keys: 3.4.3 @@ -8660,6 +9351,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typescript-eslint@8.50.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) @@ -8727,6 +9424,8 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -8756,6 +9455,8 @@ snapshots: util-deprecate@1.0.2: {} + vary@1.1.2: {} + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -8865,6 +9566,8 @@ snapshots: word-wrap@1.2.5: {} + wrappy@1.0.2: {} + xml-lexer@0.2.2: dependencies: eventemitter3: 2.0.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1cc1908e..5ff51408 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - cli - cli/npm/* + - mcp - libraries/* - libraries/logger/npm/* - libraries/md-compiler/npm/* @@ -9,6 +10,7 @@ packages: catalog: '@clack/prompts': ^1.0.1 + '@modelcontextprotocol/sdk': ^1.27.1 '@mdx-js/react': ^3.1.1 '@monaco-editor/react': ^4.7.0 '@next/mdx': ^16.1.6