@serviceme/devtools-core 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-Bzss8zOv.mjs +995 -0
- package/dist/auth-Bzss8zOv.mjs.map +1 -0
- package/dist/auth-DXam3c4S.js +1078 -0
- package/dist/auth-DXam3c4S.js.map +1 -0
- package/dist/auth.d.mts +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +14 -1029
- package/dist/auth.mjs +2 -991
- package/dist/device-CDDcwDY6.mjs +758 -0
- package/dist/device-CDDcwDY6.mjs.map +1 -0
- package/dist/device-DuAYH8bk.js +839 -0
- package/dist/device-DuAYH8bk.js.map +1 -0
- package/dist/device.d.mts +2 -455
- package/dist/device.d.ts +2 -455
- package/dist/device.js +15 -728
- package/dist/device.mjs +2 -679
- package/dist/index-2rX86yMq.d.mts +331 -0
- package/dist/index-BI11AfMH.d.mts +377 -0
- package/dist/index-BI11AfMH.d.ts +377 -0
- package/dist/index-CoA6y0MP.d.mts +498 -0
- package/dist/index-CoA6y0MP.d.ts +498 -0
- package/dist/index-DaW7IYA-.d.mts +185 -0
- package/dist/index-DaW7IYA-.d.ts +185 -0
- package/dist/index-Dp6FwWKh.d.ts +331 -0
- package/dist/index.d.mts +2340 -2101
- package/dist/index.d.ts +2340 -2101
- package/dist/index.js +8117 -10538
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7895 -10318
- package/dist/index.mjs.map +1 -1
- package/dist/rolldown-runtime-VH7oDXx4.js +28 -0
- package/dist/skill-linker.d.mts +82 -81
- package/dist/skill-linker.d.ts +82 -81
- package/dist/skill-linker.js +359 -402
- package/dist/skill-linker.js.map +1 -1
- package/dist/skill-linker.mjs +345 -357
- package/dist/skill-linker.mjs.map +1 -1
- package/dist/skill-store-Bym2yinU.mjs +388 -0
- package/dist/skill-store-Bym2yinU.mjs.map +1 -0
- package/dist/skill-store-CJdYyyxu.js +408 -0
- package/dist/skill-store-CJdYyyxu.js.map +1 -0
- package/dist/skill-store.d.mts +41 -42
- package/dist/skill-store.d.ts +41 -42
- package/dist/skill-store.js +5 -338
- package/dist/skill-store.mjs +2 -301
- package/dist/submit-Cg7wSa3x.mjs +84 -0
- package/dist/submit-Cg7wSa3x.mjs.map +1 -0
- package/dist/submit-CltE6YOZ.js +98 -0
- package/dist/submit-CltE6YOZ.js.map +1 -0
- package/dist/submit.d.mts +2 -3
- package/dist/submit.d.ts +2 -3
- package/dist/submit.js +4 -166
- package/dist/submit.mjs +2 -130
- package/dist/toolbox-CbfxpNg-.mjs +540 -0
- package/dist/toolbox-CbfxpNg-.mjs.map +1 -0
- package/dist/toolbox-CxnOr-FY.js +614 -0
- package/dist/toolbox-CxnOr-FY.js.map +1 -0
- package/dist/toolbox.d.mts +2 -244
- package/dist/toolbox.d.ts +2 -244
- package/dist/toolbox.js +14 -584
- package/dist/toolbox.mjs +2 -536
- package/dist/{types-B9gk3dXH.d.ts → types-BCV3IedE.d.mts} +25 -23
- package/dist/{types-B9gk3dXH.d.mts → types-BCV3IedE.d.ts} +25 -23
- package/dist/userHome-BquDeFVE.js +526 -0
- package/dist/userHome-BquDeFVE.js.map +1 -0
- package/dist/userHome-CcnAQPG1.mjs +212 -0
- package/dist/userHome-CcnAQPG1.mjs.map +1 -0
- package/package.json +10 -10
- package/dist/auth.js.map +0 -1
- package/dist/auth.mjs.map +0 -1
- package/dist/device.js.map +0 -1
- package/dist/device.mjs.map +0 -1
- package/dist/index-BMk4tqIT.d.mts +0 -328
- package/dist/index-CymN0x9Z.d.ts +0 -328
- package/dist/index-PD135hlB.d.mts +0 -626
- package/dist/index-PD135hlB.d.ts +0 -626
- package/dist/skill-store.js.map +0 -1
- package/dist/skill-store.mjs.map +0 -1
- package/dist/submit.js.map +0 -1
- package/dist/submit.mjs.map +0 -1
- package/dist/toolbox.js.map +0 -1
- package/dist/toolbox.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolbox-CxnOr-FY.js","names":["path","fsp","delay","getToolboxJsonPath"],"sources":["../src/toolbox/sort.ts","../src/toolbox/types.ts","../src/toolbox/ToolboxStore.ts","../src/toolbox/ToolboxCore.ts"],"sourcesContent":["/**\n * toolbox sort + dedup — pure helpers, no I/O.\n *\n * The Extension's `ToolBoxService` keeps a stable `order` index and\n * surfaces \"recently used\" via the `lastUsedAt` ISO timestamp. These\n * helpers codify that algorithm so the CLI and Extension agree on the\n * \"最近使用置顶\" UX.\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `sort.ts 排序与去重纯函数`\n */\n\nimport type { ExternalTool } from \"@serviceme/devtools-protocol\";\n\n/**\n * Parsed timestamp from `lastUsedAt`. Returns 0 when the field is\n * missing or unparseable (so the entry falls below entries with a\n * real timestamp).\n */\nfunction lastUsedTimestamp(tool: ExternalTool): number {\n\tif (!tool.lastUsedAt) return 0;\n\tconst ms = Date.parse(tool.lastUsedAt);\n\treturn Number.isFinite(ms) ? ms : 0;\n}\n\n/**\n * Sort by \"recently used\" — entries with newer `lastUsedAt` float to\n * the top, entries with no `lastUsedAt` sort to the bottom (preserving\n * their relative `order` index when both are absent).\n *\n * Ties (same `lastUsedAt` or both missing) are broken by `order`, then\n * by `id` for determinism.\n */\nexport function sortByRecentFirst(tools: readonly ExternalTool[]): ExternalTool[] {\n\treturn [...tools].sort((a, b) => {\n\t\tconst tsA = lastUsedTimestamp(a);\n\t\tconst tsB = lastUsedTimestamp(b);\n\t\tif (tsA !== tsB) return tsB - tsA;\n\t\tconst orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n\t\tconst orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n\t\tif (orderA !== orderB) return orderA - orderB;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n}\n\n/**\n * Sort by user-defined `order` field. Entries without `order` are\n * appended in their input order (stable sort via `id` tiebreaker).\n */\nexport function sortByUserOrder(tools: readonly ExternalTool[]): ExternalTool[] {\n\treturn [...tools].sort((a, b) => {\n\t\tconst orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n\t\tconst orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n\t\tif (orderA !== orderB) return orderA - orderB;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n}\n\n/**\n * Deduplicate by `id`. The first occurrence wins; later ones are\n * discarded. Used when merging built-in + user seeds so duplicates\n * from the on-disk file don't shadow the built-in defaults.\n */\nexport function dedupeById(tools: readonly ExternalTool[]): ExternalTool[] {\n\tconst seen = new Set<string>();\n\tconst out: ExternalTool[] = [];\n\tfor (const tool of tools) {\n\t\tif (seen.has(tool.id)) continue;\n\t\tseen.add(tool.id);\n\t\tout.push(tool);\n\t}\n\treturn out;\n}\n\n/**\n * Merge built-in defaults with user-stored tools. Defaults keep\n * `isDefault: true` and their `order`; user tools are appended with\n * their stored metadata. Output is sorted by user order.\n */\nexport function mergeWithDefaults(\n\tdefaults: readonly ExternalTool[],\n\tuserTools: readonly ExternalTool[]\n): ExternalTool[] {\n\tconst defaultIds = new Set(defaults.map((d) => d.id));\n\tconst uniqueUserTools = userTools.filter((t) => !defaultIds.has(t.id));\n\treturn sortByUserOrder([...defaults, ...uniqueUserTools]);\n}\n\n/**\n * Re-number `order` for a list of tool ids. Used by\n * `toolbox.update` when the user drags-and-drops entries in the UI.\n */\nexport function reindexOrder(\n\ttools: ExternalTool[],\n\tnewOrderIds: readonly string[]\n): ExternalTool[] {\n\tconst orderMap = new Map<string, number>();\n\tfor (const [index, id] of newOrderIds.entries()) {\n\t\torderMap.set(id, index);\n\t}\n\tfor (const tool of tools) {\n\t\tconst next = orderMap.get(tool.id);\n\t\tif (next !== undefined) tool.order = next;\n\t}\n\treturn tools;\n}\n\n/**\n * Stamp `lastUsedAt` on the targeted tool (clones the array). Returns\n * a new array; the input is left untouched.\n */\nexport function touchLastUsedAt(\n\ttools: readonly ExternalTool[],\n\tid: string,\n\twhen: Date = new Date()\n): ExternalTool[] {\n\treturn tools.map((tool) => (tool.id === id ? { ...tool, lastUsedAt: when.toISOString() } : tool));\n}\n","/**\n * toolbox types — internal-only data shapes.\n *\n * The public data model lives in `@serviceme/devtools-protocol/toolbox`\n * (`ExternalTool`, `ToolboxScope`, `ToolboxList`). The types below are\n * the persisted on-disk shape and the patch helpers, scoped to the\n * toolbox domain only.\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `toolbox/types.ts`\n */\n\nimport type { ExternalTool, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\n/** Schema version of the on-disk toolbox JSON files. Bumped on breaking changes. */\nexport const TOOLBOX_JSON_SCHEMA_VERSION = 1;\n\nexport interface PersistedToolbox {\n\tversion: number;\n\ttools: ExternalTool[];\n}\n\n/** Patch payload accepted by `ToolboxCore.update` (mirrors `ExternalToolPatch`). */\nexport type ToolboxPatch = Partial<Omit<ExternalTool, \"id\" | \"isDefault\">>;\n\n/**\n * Built-in (default) tool seeds. Mirrors the Extension's\n * `apps/extension/src/config/external-tools.ts`. The Core stores these\n * verbatim and never mutates them — the Extension's `ToolBoxService`\n * runs the same merge on top of the Core's view.\n */\nexport interface DefaultToolSeed {\n\tid: string;\n\tname: string;\n\tdescription: string;\n\ticon: string;\n\turl: string;\n}\n\n/** Built-in tools rendered when the JSON file is missing or empty. */\nexport const BUILTIN_DEFAULT_TOOLS: readonly DefaultToolSeed[] = [\n\t{\n\t\tid: \"builtin-docs\",\n\t\tname: \"SERVICEME Docs\",\n\t\tdescription: \"Official documentation portal\",\n\t\ticon: \"book\",\n\t\turl: \"https://docs.medalsoft.com/serviceme\",\n\t},\n\t{\n\t\tid: \"builtin-issues\",\n\t\tname: \"Issue Tracker\",\n\t\tdescription: \"Report bugs and feature requests\",\n\t\ticon: \"bug\",\n\t\turl: \"https://github.com/medalsoftchina/ms-devtools-vscode/issues\",\n\t},\n\t{\n\t\tid: \"builtin-changelog\",\n\t\tname: \"Changelog\",\n\t\tdescription: \"Release notes for every published version\",\n\t\ticon: \"history\",\n\t\turl: \"https://github.com/medalsoftchina/ms-devtools-vscode/releases\",\n\t},\n];\n\n/** Resolved toolbox shape returned by `ToolboxStore.read()`. */\nexport interface ResolvedToolbox {\n\tscope: ToolboxScope;\n\ttools: ExternalTool[];\n}\n","/**\n * ToolboxStore — JSON persistence for the toolbox entries.\n *\n * Two scopes:\n * - `user` → `~/.serviceme/toolbox.json`\n * - `workspace` → `<cwd>/.github/.serviceme-toolbox.json`\n *\n * Both files share the `PersistedToolbox` shape and the same atomic\n * write pattern (tmp + rename + fsync) as `IdentityStore`. Concurrent\n * writers are serialized via a mkdir-based file lock (Phase 6+ may\n * upgrade to `proper-lockfile`).\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `ToolboxStore.ts JSON 持久化(user + workspace scope)`\n * - `3.功能拆分.md` §3 — toolbox wire shape\n */\n\nimport * as fsp from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport type { ExternalTool, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\nimport { getToolboxJsonPath } from \"../paths/userHome\";\n\nimport { mergeWithDefaults, sortByUserOrder } from \"./sort\";\nimport {\n\tBUILTIN_DEFAULT_TOOLS,\n\ttype DefaultToolSeed,\n\ttype PersistedToolbox,\n\ttype ResolvedToolbox,\n\tTOOLBOX_JSON_SCHEMA_VERSION,\n} from \"./types\";\n\nconst FILE_MODE = 0o600;\nconst LOCK_DIR_MODE = 0o700;\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\n// `mkdir` (lock acquisition) and writing the pid file are two separate\n// syscalls, so there's a brief window where the lock dir exists but the\n// pid file doesn't yet. A grace period keeps a concurrent acquirer from\n// mistaking that window for an abandoned lock (see `isStaleLock`).\nconst LOCK_STALE_GRACE_MS = 200;\nconst TMP_SUFFIX = \".tmp\";\nexport const WORKSPACE_TOOLBOX_RELATIVE_PATH = path.join(\".github\", \".serviceme-toolbox.json\");\nconst LEGACY_WORKSPACE_TOOLBOX_FILENAME = \".ms-devtools-toolbox.json\";\n\n/**\n * One-time migration: the workspace-scope toolbox file used to be named\n * `.ms-devtools-toolbox.json`. If the new `.serviceme-toolbox.json` doesn't\n * exist yet but the legacy file does (in the same directory), rename it\n * forward so existing toolbox entries aren't silently lost.\n */\nasync function migrateLegacyWorkspaceToolboxFile(filePath: string | null): Promise<void> {\n\tif (!filePath) return;\n\tconst legacyPath = path.join(path.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);\n\tif (legacyPath === filePath) return;\n\ttry {\n\t\tawait fsp.access(filePath);\n\t\treturn;\n\t} catch {\n\t\t// new file missing — check the legacy path below\n\t}\n\ttry {\n\t\tawait fsp.rename(legacyPath, filePath);\n\t} catch {\n\t\t// legacy file doesn't exist either — nothing to migrate\n\t}\n}\n\nexport interface ToolboxFileBackend {\n\tread(filePath: string): Promise<PersistedToolbox | null>;\n\twrite(filePath: string, payload: PersistedToolbox): Promise<void>;\n\texists(filePath: string): Promise<boolean>;\n\t/** Maximum number of `.corrupted.*.bak` snapshots to keep per toolbox file (default: 5). */\n\tmaxBackupCount?: number;\n}\n\nexport interface ToolboxStoreOptions {\n\t/** Override the user-scope file path (default: `getToolboxJsonPath()`). */\n\tuserFilePath?: string;\n\t/** Override the workspace-scope file path resolver (default: cwd-relative). */\n\tresolveWorkspacePath?: () => string | null;\n\t/** Injectable built-in tool seeds (default: `BUILTIN_DEFAULT_TOOLS`). */\n\tdefaultTools?: readonly DefaultToolSeed[];\n\thooks?: ToolboxStoreHooks;\n\tbackend?: ToolboxFileBackend;\n\tlockTimeoutMs?: number;\n\tlockRetryMs?: number;\n}\n\nexport interface ToolboxStoreHooks {\n\tbeforeWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;\n\tafterWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;\n}\n\nexport class FsToolboxFileBackend implements ToolboxFileBackend {\n\tmaxBackupCount: number = 5;\n\n\tasync exists(filePath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fsp.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync read(filePath: string): Promise<PersistedToolbox | null> {\n\t\tlet buf: string;\n\t\ttry {\n\t\t\tbuf = await fsp.readFile(filePath, \"utf8\");\n\t\t} catch (err) {\n\t\t\tif (isNodeError(err) && err.code === \"ENOENT\") return null;\n\t\t\tthrow err;\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(buf) as unknown;\n\t\t\treturn coercePersistedToolbox(parsed);\n\t\t} catch {\n\t\t\t// File exists but is unparseable JSON or fails schema validation\n\t\t\t// (e.g. hand-edited, written by an older/incompatible schema, or\n\t\t\t// truncated by a crash). Quarantine it as a timestamped `.bak`\n\t\t\t// sibling and fall back to an empty toolbox (defaults still\n\t\t\t// merge in via `ToolboxStore.read()`) rather than crashing the\n\t\t\t// whole \"get external tools\" request.\n\t\t\tawait this.backupCorruptedFile(filePath);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async backupCorruptedFile(filePath: string): Promise<void> {\n\t\ttry {\n\t\t\tconst backupPath = `${filePath}.corrupted.${Date.now()}.bak`;\n\t\t\tawait fsp.copyFile(filePath, backupPath);\n\t\t\tawait this.purgeExcessBackups(filePath);\n\t\t} catch {\n\t\t\t// Best-effort backup — never let backup failure mask the real recovery.\n\t\t}\n\t}\n\n\tprivate async purgeExcessBackups(filePath: string): Promise<void> {\n\t\tconst dir = path.dirname(filePath);\n\t\tconst base = path.basename(filePath);\n\t\tlet entries: string[];\n\t\ttry {\n\t\t\tentries = await fsp.readdir(dir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tconst backups = entries\n\t\t\t.filter((n) => n.startsWith(base) && n.endsWith(\".bak\"))\n\t\t\t.map((n) => ({ name: n, filePath: path.join(dir, n) }))\n\t\t\t.sort((a, b) => {\n\t\t\t\t// Sort oldest-first so we drop the earliest ones first.\n\t\t\t\treturn a.name.localeCompare(b.name);\n\t\t\t});\n\t\tconst excess = backups.length - this.maxBackupCount;\n\t\tif (excess <= 0) return;\n\t\tawait Promise.all(\n\t\t\tbackups.slice(0, excess).map((b) => fsp.rm(b.filePath).catch(() => undefined))\n\t\t);\n\t}\n\n\tasync write(filePath: string, payload: PersistedToolbox): Promise<void> {\n\t\tawait fsp.mkdir(path.dirname(filePath), { recursive: true });\n\t\tconst tmpPath = `${filePath}${TMP_SUFFIX}`;\n\t\tconst bytes = Buffer.from(JSON.stringify(payload, null, \"\\t\"), \"utf8\");\n\t\tawait fsp.rm(tmpPath, { force: true });\n\t\tconst handle = await fsp.open(tmpPath, \"w\", FILE_MODE);\n\t\ttry {\n\t\t\tawait handle.writeFile(bytes);\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fsp.rename(tmpPath, filePath);\n\t\tawait fsp.chmod(filePath, FILE_MODE).catch(() => undefined);\n\t}\n}\n\nfunction coercePersistedToolbox(parsed: unknown): PersistedToolbox {\n\tif (typeof parsed !== \"object\" || parsed === null) {\n\t\tthrow new Error(\"toolbox.json: top-level must be an object\");\n\t}\n\tconst obj = parsed as Record<string, unknown>;\n\tconst version = obj.version;\n\tif (version !== TOOLBOX_JSON_SCHEMA_VERSION) {\n\t\tthrow new Error(`toolbox.json: unsupported schema version ${String(version)}`);\n\t}\n\tif (!Array.isArray(obj.tools)) {\n\t\tthrow new Error(\"toolbox.json: 'tools' must be an array\");\n\t}\n\treturn { version, tools: obj.tools as ExternalTool[] };\n}\n\nfunction isNodeError(value: unknown): value is NodeJS.ErrnoException {\n\treturn value instanceof Error && typeof (value as { code?: unknown }).code === \"string\";\n}\n\nfunction isProcessAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nclass ToolboxFileLock {\n\tprivate readonly dirPath: string;\n\tprivate readonly pidFilePath: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retryMs: number;\n\tprivate acquired = false;\n\n\tconstructor(filePath: string, timeoutMs: number, retryMs: number) {\n\t\tthis.dirPath = `${filePath}.lock`;\n\t\tthis.pidFilePath = path.join(this.dirPath, \"pid\");\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.retryMs = retryMs;\n\t}\n\n\tasync acquire(): Promise<void> {\n\t\tconst start = Date.now();\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tawait fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });\n\t\t\t\tawait fsp.writeFile(this.pidFilePath, String(process.pid), \"utf8\").catch(() => undefined);\n\t\t\t\tthis.acquired = true;\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tif (!isNodeError(err) || err.code !== \"EEXIST\") throw err;\n\t\t\t\tconst stale = await this.isStaleLock();\n\t\t\t\tif (stale) {\n\t\t\t\t\tawait fsp.rm(this.dirPath, { recursive: true, force: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - start >= this.timeoutMs) {\n\t\t\t\t\tthrow new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);\n\t\t\t\t}\n\t\t\t\tawait delay(this.retryMs);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async isStaleLock(): Promise<boolean> {\n\t\tlet pidStr: string;\n\t\ttry {\n\t\t\tpidStr = await fsp.readFile(this.pidFilePath, \"utf8\");\n\t\t} catch {\n\t\t\t// The pid file may not exist yet because another acquirer just\n\t\t\t// created the lock dir and hasn't finished writing its pid file\n\t\t\t// (mkdir + writeFile is not atomic). Give it a short grace window\n\t\t\t// before concluding the owner crashed between mkdir and writeFile.\n\t\t\ttry {\n\t\t\t\tconst stat = await fsp.stat(this.dirPath);\n\t\t\t\treturn Date.now() - stat.mtimeMs > LOCK_STALE_GRACE_MS;\n\t\t\t} catch {\n\t\t\t\t// Lock dir disappeared concurrently (e.g. released mid-check) —\n\t\t\t\t// not stale, just gone; the caller's next mkdir will succeed.\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tconst pid = Number.parseInt(pidStr.trim(), 10);\n\t\tif (!Number.isFinite(pid) || pid <= 0) return true;\n\t\treturn !isProcessAlive(pid);\n\t}\n\n\tasync release(): Promise<void> {\n\t\tif (!this.acquired) return;\n\t\tthis.acquired = false;\n\t\tawait fsp.rm(this.dirPath, { recursive: true, force: true });\n\t}\n}\n\nfunction defaultWorkspacePath(): string | null {\n\t// The store caller (ToolboxCore) supplies a cwd, but tests want a\n\t// stable default. Return null when the env var says \"no workspace\".\n\tif (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === \"1\") return null;\n\treturn path.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);\n}\n\nexport class ToolboxStore {\n\tprivate readonly userFilePath: string;\n\tprivate readonly resolveWorkspacePath: () => string | null;\n\tprivate readonly defaults: readonly DefaultToolSeed[];\n\tprivate readonly hooks: ToolboxStoreHooks;\n\tprivate readonly backend: ToolboxFileBackend;\n\tprivate readonly lockTimeoutMs: number;\n\tprivate readonly lockRetryMs: number;\n\n\tconstructor(opts: ToolboxStoreOptions = {}) {\n\t\tthis.userFilePath = opts.userFilePath ?? getToolboxJsonPath();\n\t\tthis.resolveWorkspacePath = opts.resolveWorkspacePath ?? defaultWorkspacePath;\n\t\tthis.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;\n\t\tthis.hooks = opts.hooks ?? {};\n\t\tthis.backend = opts.backend ?? new FsToolboxFileBackend();\n\t\tthis.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\t\tthis.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;\n\t}\n\n\t/** Read a scope; returns the resolved toolbox (with defaults merged when empty). */\n\tasync read(scope: ToolboxScope): Promise<ResolvedToolbox> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst stored = filePath ? await this.backend.read(filePath) : null;\n\t\tconst storedTools = stored?.tools ?? [];\n\t\tconst defaults = this.defaults.map((seed, index) => toDefaultTool(seed, index));\n\t\tconst merged = mergeWithDefaults(defaults, storedTools);\n\t\tconst userOnly = storedTools.filter((t) => !this.defaults.some((d) => d.id === t.id));\n\t\tvoid userOnly;\n\t\treturn { scope, tools: merged };\n\t}\n\n\t/**\n\t * Atomic write under a file lock. Replaces the entire `tools` array\n\t * with the provided snapshot.\n\t */\n\tasync write(scope: ToolboxScope, tools: readonly ExternalTool[]): Promise<void> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) {\n\t\t\tthrow new Error(`Cannot write toolbox scope '${scope}': file path is unavailable`);\n\t\t}\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst payload: PersistedToolbox = {\n\t\t\tversion: TOOLBOX_JSON_SCHEMA_VERSION,\n\t\t\ttools: sortByUserOrder([...tools]),\n\t\t};\n\t\tawait this.hooks.beforeWrite?.(scope, payload);\n\t\tconst lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tawait this.backend.write(filePath, payload);\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t\tawait this.hooks.afterWrite?.(scope, payload);\n\t}\n\n\t/**\n\t * Read-modify-write under the file lock. The mutator receives the\n\t * current user-only list (defaults not included) and returns the\n\t * replacement list. Throwing inside the mutator aborts the write.\n\t */\n\tasync mutate(\n\t\tscope: ToolboxScope,\n\t\tmutator: (current: ExternalTool[]) => Promise<ExternalTool[]>\n\t): Promise<ExternalTool[]> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) {\n\t\t\tthrow new Error(`Cannot mutate toolbox scope '${scope}': file path is unavailable`);\n\t\t}\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst stored = await this.backend.read(filePath);\n\t\t\tconst storedTools = stored?.tools ?? [];\n\t\t\tconst defaultIds = new Set(this.defaults.map((d) => d.id));\n\t\t\tconst userTools = storedTools.filter((t) => !defaultIds.has(t.id));\n\t\t\tconst next = await mutator(userTools);\n\t\t\tconst payload: PersistedToolbox = {\n\t\t\t\tversion: TOOLBOX_JSON_SCHEMA_VERSION,\n\t\t\t\ttools: sortByUserOrder([...next]),\n\t\t\t};\n\t\t\tawait this.hooks.beforeWrite?.(scope, payload);\n\t\t\tawait this.backend.write(filePath, payload);\n\t\t\tawait this.hooks.afterWrite?.(scope, payload);\n\t\t\treturn next;\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/** Wipe a scope entirely (used by `toolbox.remove --all` extensions). */\n\tasync clear(scope: ToolboxScope): Promise<void> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) return;\n\t\tawait fsp.rm(filePath, { force: true });\n\t}\n\n\t/** Test seam — resolve the user-scope file path. */\n\tgetUserFilePath(): string {\n\t\treturn this.userFilePath;\n\t}\n\n\t/** Test seam — resolve the workspace-scope file path (or null when disabled). */\n\tgetWorkspaceFilePath(): string | null {\n\t\treturn this.resolveWorkspacePath();\n\t}\n\n\tprivate filePathFor(scope: ToolboxScope): string | null {\n\t\treturn scope === \"user\" ? this.userFilePath : this.resolveWorkspacePath();\n\t}\n}\n\nfunction toDefaultTool(seed: DefaultToolSeed, order: number): ExternalTool {\n\treturn {\n\t\tid: seed.id,\n\t\tname: seed.name,\n\t\tdescription: seed.description,\n\t\ticon: seed.icon,\n\t\turl: seed.url,\n\t\tisDefault: true,\n\t\torder,\n\t\tscope: \"user\",\n\t};\n}\n","/**\n * ToolboxCore — Main entry for the toolbox domain.\n *\n * Orchestrates the user + workspace scopes via `ToolboxStore`, applies\n * the \"最近使用置顶\" sort, and exposes a small surface that mirrors\n * the Phase 5.1 bridge methods:\n *\n * - `list(scope?)` → `toolbox.list`\n * - `add(tool, scope)` → `toolbox.add`\n * - `remove(id, scope?)` → `toolbox.remove`\n * - `update(id, patch, scope?)` → `toolbox.update`\n *\n * Default tools (built-in seeds) are never user-deletable. `remove()`\n * is a no-op for default tools and surfaces a structured error so\n * callers can map it to `TOOLBOX_DEFAULT_IMMUTABLE` (Phase 5.3 error\n * code).\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `ToolboxCore.ts 主入口`\n * - `3.功能拆分.md` §3 — wire shape\n */\n\nimport type { ExternalTool, ToolboxList, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\nimport { sortByRecentFirst, sortByUserOrder, touchLastUsedAt } from \"./sort\";\nimport { ToolboxStore } from \"./ToolboxStore\";\nimport { BUILTIN_DEFAULT_TOOLS, type DefaultToolSeed, type ToolboxPatch } from \"./types\";\n\n/** Sentinel — caller tried to remove a built-in (immutable) tool. */\nexport class DefaultToolImmutableError extends Error {\n\tconstructor(public readonly toolId: string) {\n\t\tsuper(`Cannot remove default toolbox entry: ${toolId}`);\n\t\tthis.name = \"DefaultToolImmutableError\";\n\t}\n}\n\nexport interface ToolboxCoreOptions {\n\tstore?: ToolboxStore;\n\tdefaultTools?: readonly DefaultToolSeed[];\n\t/** Sort applied to the merged list returned by `list()`. Default: `sortByUserOrder`. */\n\tlistSort?: (tools: readonly ExternalTool[]) => ExternalTool[];\n}\n\nexport class ToolboxCore {\n\tprivate readonly store: ToolboxStore;\n\tprivate readonly defaults: readonly DefaultToolSeed[];\n\tprivate readonly listSort: (tools: readonly ExternalTool[]) => ExternalTool[];\n\n\tconstructor(opts: ToolboxCoreOptions = {}) {\n\t\tthis.store = opts.store ?? new ToolboxStore();\n\t\tthis.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;\n\t\tthis.listSort = opts.listSort ?? sortByUserOrder;\n\t}\n\n\t/** List all tools in a scope. Built-in defaults are merged in. */\n\tasync list(scope: ToolboxScope = \"user\"): Promise<ToolboxList> {\n\t\tconst resolved = await this.store.read(scope);\n\t\treturn {\n\t\t\tscope,\n\t\t\ttools: this.listSort(resolved.tools),\n\t\t};\n\t}\n\n\t/**\n\t * Append a new tool to the requested scope. Default tools are\n\t * rejected (they are seeds, not user entries).\n\t */\n\tasync add(tool: ExternalTool, scope: ToolboxScope = \"user\"): Promise<ToolboxList> {\n\t\tif (this.isDefaultId(tool.id)) {\n\t\t\tthrow new DefaultToolImmutableError(tool.id);\n\t\t}\n\t\tconst next = await this.store.mutate(scope, async (current) => {\n\t\t\tconst filtered = current.filter((t) => t.id !== tool.id);\n\t\t\treturn [\n\t\t\t\t...filtered,\n\t\t\t\t{\n\t\t\t\t\t...tool,\n\t\t\t\t\tscope,\n\t\t\t\t\tisDefault: false,\n\t\t\t\t\torder: tool.order ?? filtered.length,\n\t\t\t\t},\n\t\t\t];\n\t\t});\n\t\treturn { scope, tools: next };\n\t}\n\n\t/**\n\t * Remove a tool by id. Returns `success: false` when the id is a\n\t * built-in default (idempotent, never throws on missing entries).\n\t */\n\tasync remove(\n\t\tid: string,\n\t\tscope: ToolboxScope = \"user\"\n\t): Promise<{ scope: ToolboxScope; toolId: string; success: boolean }> {\n\t\tif (this.isDefaultId(id)) {\n\t\t\tthrow new DefaultToolImmutableError(id);\n\t\t}\n\t\tconst removed = await this.store.mutate(scope, async (current) =>\n\t\t\tcurrent.filter((t) => t.id !== id)\n\t\t);\n\t\treturn { scope, toolId: id, success: !removed.some((t) => t.id === id) };\n\t}\n\n\t/**\n\t * Patch a tool by id. Default tools can only have their `order`\n\t * updated; other patches are silently ignored for default entries\n\t * (callers can compare before/after to detect the ignore).\n\t */\n\tasync update(\n\t\tid: string,\n\t\tpatch: ToolboxPatch,\n\t\tscope: ToolboxScope = \"user\"\n\t): Promise<ToolboxList> {\n\t\tconst isDefault = this.isDefaultId(id);\n\t\tconst next = await this.store.mutate(scope, async (current) => {\n\t\t\tif (isDefault) {\n\t\t\t\t// Default tools are not stored on disk — silently ignore\n\t\t\t\t// non-order patches. The order field is also not persisted\n\t\t\t\t// (default order is set in memory at merge time), so the\n\t\t\t\t// caller can compare before/after to detect the ignore.\n\t\t\t\treturn current;\n\t\t\t}\n\t\t\treturn current.map((tool) =>\n\t\t\t\ttool.id === id ? { ...tool, ...patch, scope, isDefault: false } : tool\n\t\t\t);\n\t\t});\n\t\treturn { scope, tools: next };\n\t}\n\n\t/**\n\t * Stamp `lastUsedAt` on the targeted tool. This is the \"recently\n\t * used\" hook the Extension's webview uses when a user clicks a\n\t * toolbox entry.\n\t */\n\tasync recordUsage(\n\t\tid: string,\n\t\tscope: ToolboxScope = \"user\",\n\t\twhen: Date = new Date()\n\t): Promise<ExternalTool | null> {\n\t\tif (this.isDefaultId(id)) {\n\t\t\t// Defaults live in memory only — return a synthetic record so\n\t\t\t// callers can render the click without persisting anything.\n\t\t\tconst seed = this.defaults.find((d) => d.id === id);\n\t\t\tif (!seed) return null;\n\t\t\treturn { ...seed, scope, isDefault: true, order: 0, lastUsedAt: when.toISOString() };\n\t\t}\n\t\tlet updated: ExternalTool | null = null;\n\t\tawait this.store.mutate(scope, async (current) => {\n\t\t\tconst next = touchLastUsedAt(current, id, when);\n\t\t\tupdated = next.find((t) => t.id === id) ?? null;\n\t\t\treturn next;\n\t\t});\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Combined view: user + workspace scopes merged, sorted by recent\n\t * usage. Workspace tools overlay user tools (workspace entries win\n\t * on `id` collision).\n\t */\n\tasync listMerged(): Promise<ToolboxList> {\n\t\tconst [user, workspace] = await Promise.all([\n\t\t\tthis.store.read(\"user\"),\n\t\t\tthis.store.read(\"workspace\"),\n\t\t]);\n\t\tconst seen = new Set<string>();\n\t\tconst merged: ExternalTool[] = [];\n\t\tfor (const tool of workspace.tools) {\n\t\t\tseen.add(tool.id);\n\t\t\tmerged.push({ ...tool, scope: \"workspace\" });\n\t\t}\n\t\tfor (const tool of user.tools) {\n\t\t\tif (seen.has(tool.id)) continue;\n\t\t\tmerged.push({ ...tool, scope: \"user\" });\n\t\t}\n\t\treturn { scope: \"user\", tools: sortByRecentFirst(merged) };\n\t}\n\n\t/** Expose the underlying store (CLI / Bridge use it for path-level access). */\n\tgetStore(): ToolboxStore {\n\t\treturn this.store;\n\t}\n\n\tprivate isDefaultId(id: string): boolean {\n\t\treturn this.defaults.some((d) => d.id === id);\n\t}\n}\n"],"mappings":"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"}
|
package/dist/toolbox.d.mts
CHANGED
|
@@ -1,244 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* toolbox sort + dedup — pure helpers, no I/O.
|
|
5
|
-
*
|
|
6
|
-
* The Extension's `ToolBoxService` keeps a stable `order` index and
|
|
7
|
-
* surfaces "recently used" via the `lastUsedAt` ISO timestamp. These
|
|
8
|
-
* helpers codify that algorithm so the CLI and Extension agree on the
|
|
9
|
-
* "最近使用置顶" UX.
|
|
10
|
-
*
|
|
11
|
-
* Refs:
|
|
12
|
-
* - 4.功能规划.md §2.3 — `sort.ts 排序与去重纯函数`
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Sort by "recently used" — entries with newer `lastUsedAt` float to
|
|
17
|
-
* the top, entries with no `lastUsedAt` sort to the bottom (preserving
|
|
18
|
-
* their relative `order` index when both are absent).
|
|
19
|
-
*
|
|
20
|
-
* Ties (same `lastUsedAt` or both missing) are broken by `order`, then
|
|
21
|
-
* by `id` for determinism.
|
|
22
|
-
*/
|
|
23
|
-
declare function sortByRecentFirst(tools: readonly ExternalTool[]): ExternalTool[];
|
|
24
|
-
/**
|
|
25
|
-
* Sort by user-defined `order` field. Entries without `order` are
|
|
26
|
-
* appended in their input order (stable sort via `id` tiebreaker).
|
|
27
|
-
*/
|
|
28
|
-
declare function sortByUserOrder(tools: readonly ExternalTool[]): ExternalTool[];
|
|
29
|
-
/**
|
|
30
|
-
* Merge built-in defaults with user-stored tools. Defaults keep
|
|
31
|
-
* `isDefault: true` and their `order`; user tools are appended with
|
|
32
|
-
* their stored metadata. Output is sorted by user order.
|
|
33
|
-
*/
|
|
34
|
-
declare function mergeWithDefaults(defaults: readonly ExternalTool[], userTools: readonly ExternalTool[]): ExternalTool[];
|
|
35
|
-
/**
|
|
36
|
-
* Re-number `order` for a list of tool ids. Used by
|
|
37
|
-
* `toolbox.update` when the user drags-and-drops entries in the UI.
|
|
38
|
-
*/
|
|
39
|
-
declare function reindexOrder(tools: ExternalTool[], newOrderIds: readonly string[]): ExternalTool[];
|
|
40
|
-
/**
|
|
41
|
-
* Stamp `lastUsedAt` on the targeted tool (clones the array). Returns
|
|
42
|
-
* a new array; the input is left untouched.
|
|
43
|
-
*/
|
|
44
|
-
declare function touchLastUsedAt(tools: readonly ExternalTool[], id: string, when?: Date): ExternalTool[];
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* toolbox types — internal-only data shapes.
|
|
48
|
-
*
|
|
49
|
-
* The public data model lives in `@serviceme/devtools-protocol/toolbox`
|
|
50
|
-
* (`ExternalTool`, `ToolboxScope`, `ToolboxList`). The types below are
|
|
51
|
-
* the persisted on-disk shape and the patch helpers, scoped to the
|
|
52
|
-
* toolbox domain only.
|
|
53
|
-
*
|
|
54
|
-
* Refs:
|
|
55
|
-
* - 4.功能规划.md §2.3 — `toolbox/types.ts`
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
/** Schema version of the on-disk toolbox JSON files. Bumped on breaking changes. */
|
|
59
|
-
declare const TOOLBOX_JSON_SCHEMA_VERSION = 1;
|
|
60
|
-
interface PersistedToolbox {
|
|
61
|
-
version: number;
|
|
62
|
-
tools: ExternalTool[];
|
|
63
|
-
}
|
|
64
|
-
/** Patch payload accepted by `ToolboxCore.update` (mirrors `ExternalToolPatch`). */
|
|
65
|
-
type ToolboxPatch = Partial<Omit<ExternalTool, "id" | "isDefault">>;
|
|
66
|
-
/**
|
|
67
|
-
* Built-in (default) tool seeds. Mirrors the Extension's
|
|
68
|
-
* `apps/extension/src/config/external-tools.ts`. The Core stores these
|
|
69
|
-
* verbatim and never mutates them — the Extension's `ToolBoxService`
|
|
70
|
-
* runs the same merge on top of the Core's view.
|
|
71
|
-
*/
|
|
72
|
-
interface DefaultToolSeed {
|
|
73
|
-
id: string;
|
|
74
|
-
name: string;
|
|
75
|
-
description: string;
|
|
76
|
-
icon: string;
|
|
77
|
-
url: string;
|
|
78
|
-
}
|
|
79
|
-
/** Built-in tools rendered when the JSON file is missing or empty. */
|
|
80
|
-
declare const BUILTIN_DEFAULT_TOOLS: readonly DefaultToolSeed[];
|
|
81
|
-
/** Resolved toolbox shape returned by `ToolboxStore.read()`. */
|
|
82
|
-
interface ResolvedToolbox {
|
|
83
|
-
scope: ToolboxScope;
|
|
84
|
-
tools: ExternalTool[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* ToolboxStore — JSON persistence for the toolbox entries.
|
|
89
|
-
*
|
|
90
|
-
* Two scopes:
|
|
91
|
-
* - `user` → `~/.serviceme/toolbox.json`
|
|
92
|
-
* - `workspace` → `<cwd>/.github/.serviceme-toolbox.json`
|
|
93
|
-
*
|
|
94
|
-
* Both files share the `PersistedToolbox` shape and the same atomic
|
|
95
|
-
* write pattern (tmp + rename + fsync) as `IdentityStore`. Concurrent
|
|
96
|
-
* writers are serialized via a mkdir-based file lock (Phase 6+ may
|
|
97
|
-
* upgrade to `proper-lockfile`).
|
|
98
|
-
*
|
|
99
|
-
* Refs:
|
|
100
|
-
* - 4.功能规划.md §2.3 — `ToolboxStore.ts JSON 持久化(user + workspace scope)`
|
|
101
|
-
* - `3.功能拆分.md` §3 — toolbox wire shape
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
declare const WORKSPACE_TOOLBOX_RELATIVE_PATH: string;
|
|
105
|
-
interface ToolboxFileBackend {
|
|
106
|
-
read(filePath: string): Promise<PersistedToolbox | null>;
|
|
107
|
-
write(filePath: string, payload: PersistedToolbox): Promise<void>;
|
|
108
|
-
exists(filePath: string): Promise<boolean>;
|
|
109
|
-
/** Maximum number of `.corrupted.*.bak` snapshots to keep per toolbox file (default: 5). */
|
|
110
|
-
maxBackupCount?: number;
|
|
111
|
-
}
|
|
112
|
-
interface ToolboxStoreOptions {
|
|
113
|
-
/** Override the user-scope file path (default: `getToolboxJsonPath()`). */
|
|
114
|
-
userFilePath?: string;
|
|
115
|
-
/** Override the workspace-scope file path resolver (default: cwd-relative). */
|
|
116
|
-
resolveWorkspacePath?: () => string | null;
|
|
117
|
-
/** Injectable built-in tool seeds (default: `BUILTIN_DEFAULT_TOOLS`). */
|
|
118
|
-
defaultTools?: readonly DefaultToolSeed[];
|
|
119
|
-
hooks?: ToolboxStoreHooks;
|
|
120
|
-
backend?: ToolboxFileBackend;
|
|
121
|
-
lockTimeoutMs?: number;
|
|
122
|
-
lockRetryMs?: number;
|
|
123
|
-
}
|
|
124
|
-
interface ToolboxStoreHooks {
|
|
125
|
-
beforeWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;
|
|
126
|
-
afterWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;
|
|
127
|
-
}
|
|
128
|
-
declare class FsToolboxFileBackend implements ToolboxFileBackend {
|
|
129
|
-
maxBackupCount: number;
|
|
130
|
-
exists(filePath: string): Promise<boolean>;
|
|
131
|
-
read(filePath: string): Promise<PersistedToolbox | null>;
|
|
132
|
-
private backupCorruptedFile;
|
|
133
|
-
private purgeExcessBackups;
|
|
134
|
-
write(filePath: string, payload: PersistedToolbox): Promise<void>;
|
|
135
|
-
}
|
|
136
|
-
declare class ToolboxStore {
|
|
137
|
-
private readonly userFilePath;
|
|
138
|
-
private readonly resolveWorkspacePath;
|
|
139
|
-
private readonly defaults;
|
|
140
|
-
private readonly hooks;
|
|
141
|
-
private readonly backend;
|
|
142
|
-
private readonly lockTimeoutMs;
|
|
143
|
-
private readonly lockRetryMs;
|
|
144
|
-
constructor(opts?: ToolboxStoreOptions);
|
|
145
|
-
/** Read a scope; returns the resolved toolbox (with defaults merged when empty). */
|
|
146
|
-
read(scope: ToolboxScope): Promise<ResolvedToolbox>;
|
|
147
|
-
/**
|
|
148
|
-
* Atomic write under a file lock. Replaces the entire `tools` array
|
|
149
|
-
* with the provided snapshot.
|
|
150
|
-
*/
|
|
151
|
-
write(scope: ToolboxScope, tools: readonly ExternalTool[]): Promise<void>;
|
|
152
|
-
/**
|
|
153
|
-
* Read-modify-write under the file lock. The mutator receives the
|
|
154
|
-
* current user-only list (defaults not included) and returns the
|
|
155
|
-
* replacement list. Throwing inside the mutator aborts the write.
|
|
156
|
-
*/
|
|
157
|
-
mutate(scope: ToolboxScope, mutator: (current: ExternalTool[]) => Promise<ExternalTool[]>): Promise<ExternalTool[]>;
|
|
158
|
-
/** Wipe a scope entirely (used by `toolbox.remove --all` extensions). */
|
|
159
|
-
clear(scope: ToolboxScope): Promise<void>;
|
|
160
|
-
/** Test seam — resolve the user-scope file path. */
|
|
161
|
-
getUserFilePath(): string;
|
|
162
|
-
/** Test seam — resolve the workspace-scope file path (or null when disabled). */
|
|
163
|
-
getWorkspaceFilePath(): string | null;
|
|
164
|
-
private filePathFor;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* ToolboxCore — Main entry for the toolbox domain.
|
|
169
|
-
*
|
|
170
|
-
* Orchestrates the user + workspace scopes via `ToolboxStore`, applies
|
|
171
|
-
* the "最近使用置顶" sort, and exposes a small surface that mirrors
|
|
172
|
-
* the Phase 5.1 bridge methods:
|
|
173
|
-
*
|
|
174
|
-
* - `list(scope?)` → `toolbox.list`
|
|
175
|
-
* - `add(tool, scope)` → `toolbox.add`
|
|
176
|
-
* - `remove(id, scope?)` → `toolbox.remove`
|
|
177
|
-
* - `update(id, patch, scope?)` → `toolbox.update`
|
|
178
|
-
*
|
|
179
|
-
* Default tools (built-in seeds) are never user-deletable. `remove()`
|
|
180
|
-
* is a no-op for default tools and surfaces a structured error so
|
|
181
|
-
* callers can map it to `TOOLBOX_DEFAULT_IMMUTABLE` (Phase 5.3 error
|
|
182
|
-
* code).
|
|
183
|
-
*
|
|
184
|
-
* Refs:
|
|
185
|
-
* - 4.功能规划.md §2.3 — `ToolboxCore.ts 主入口`
|
|
186
|
-
* - `3.功能拆分.md` §3 — wire shape
|
|
187
|
-
*/
|
|
188
|
-
|
|
189
|
-
/** Sentinel — caller tried to remove a built-in (immutable) tool. */
|
|
190
|
-
declare class DefaultToolImmutableError extends Error {
|
|
191
|
-
readonly toolId: string;
|
|
192
|
-
constructor(toolId: string);
|
|
193
|
-
}
|
|
194
|
-
interface ToolboxCoreOptions {
|
|
195
|
-
store?: ToolboxStore;
|
|
196
|
-
defaultTools?: readonly DefaultToolSeed[];
|
|
197
|
-
/** Sort applied to the merged list returned by `list()`. Default: `sortByUserOrder`. */
|
|
198
|
-
listSort?: (tools: readonly ExternalTool[]) => ExternalTool[];
|
|
199
|
-
}
|
|
200
|
-
declare class ToolboxCore {
|
|
201
|
-
private readonly store;
|
|
202
|
-
private readonly defaults;
|
|
203
|
-
private readonly listSort;
|
|
204
|
-
constructor(opts?: ToolboxCoreOptions);
|
|
205
|
-
/** List all tools in a scope. Built-in defaults are merged in. */
|
|
206
|
-
list(scope?: ToolboxScope): Promise<ToolboxList>;
|
|
207
|
-
/**
|
|
208
|
-
* Append a new tool to the requested scope. Default tools are
|
|
209
|
-
* rejected (they are seeds, not user entries).
|
|
210
|
-
*/
|
|
211
|
-
add(tool: ExternalTool, scope?: ToolboxScope): Promise<ToolboxList>;
|
|
212
|
-
/**
|
|
213
|
-
* Remove a tool by id. Returns `success: false` when the id is a
|
|
214
|
-
* built-in default (idempotent, never throws on missing entries).
|
|
215
|
-
*/
|
|
216
|
-
remove(id: string, scope?: ToolboxScope): Promise<{
|
|
217
|
-
scope: ToolboxScope;
|
|
218
|
-
toolId: string;
|
|
219
|
-
success: boolean;
|
|
220
|
-
}>;
|
|
221
|
-
/**
|
|
222
|
-
* Patch a tool by id. Default tools can only have their `order`
|
|
223
|
-
* updated; other patches are silently ignored for default entries
|
|
224
|
-
* (callers can compare before/after to detect the ignore).
|
|
225
|
-
*/
|
|
226
|
-
update(id: string, patch: ToolboxPatch, scope?: ToolboxScope): Promise<ToolboxList>;
|
|
227
|
-
/**
|
|
228
|
-
* Stamp `lastUsedAt` on the targeted tool. This is the "recently
|
|
229
|
-
* used" hook the Extension's webview uses when a user clicks a
|
|
230
|
-
* toolbox entry.
|
|
231
|
-
*/
|
|
232
|
-
recordUsage(id: string, scope?: ToolboxScope, when?: Date): Promise<ExternalTool | null>;
|
|
233
|
-
/**
|
|
234
|
-
* Combined view: user + workspace scopes merged, sorted by recent
|
|
235
|
-
* usage. Workspace tools overlay user tools (workspace entries win
|
|
236
|
-
* on `id` collision).
|
|
237
|
-
*/
|
|
238
|
-
listMerged(): Promise<ToolboxList>;
|
|
239
|
-
/** Expose the underlying store (CLI / Bridge use it for path-level access). */
|
|
240
|
-
getStore(): ToolboxStore;
|
|
241
|
-
private isDefaultId;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export { BUILTIN_DEFAULT_TOOLS, DefaultToolImmutableError, type DefaultToolSeed, FsToolboxFileBackend, type PersistedToolbox, type ResolvedToolbox, TOOLBOX_JSON_SCHEMA_VERSION, ToolboxCore, type ToolboxCoreOptions, type ToolboxFileBackend, type ToolboxPatch, ToolboxStore, type ToolboxStoreHooks, type ToolboxStoreOptions, WORKSPACE_TOOLBOX_RELATIVE_PATH, mergeWithDefaults, reindexOrder, sortByRecentFirst, sortByUserOrder, touchLastUsedAt };
|
|
1
|
+
import { _ as reindexOrder, a as ToolboxFileBackend, b as touchLastUsedAt, c as ToolboxStoreOptions, d as DefaultToolSeed, f as PersistedToolbox, g as mergeWithDefaults, h as ToolboxPatch, i as FsToolboxFileBackend, l as WORKSPACE_TOOLBOX_RELATIVE_PATH, m as TOOLBOX_JSON_SCHEMA_VERSION, n as ToolboxCore, o as ToolboxStore, p as ResolvedToolbox, r as ToolboxCoreOptions, s as ToolboxStoreHooks, t as DefaultToolImmutableError, u as BUILTIN_DEFAULT_TOOLS, v as sortByRecentFirst, y as sortByUserOrder } from "./index-DaW7IYA-.mjs";
|
|
2
|
+
export { BUILTIN_DEFAULT_TOOLS, DefaultToolImmutableError, type DefaultToolSeed, FsToolboxFileBackend, type PersistedToolbox, type ResolvedToolbox, TOOLBOX_JSON_SCHEMA_VERSION, ToolboxCore, type ToolboxCoreOptions, type ToolboxFileBackend, type ToolboxPatch, ToolboxStore, type ToolboxStoreHooks, type ToolboxStoreOptions, WORKSPACE_TOOLBOX_RELATIVE_PATH, mergeWithDefaults, reindexOrder, sortByRecentFirst, sortByUserOrder, touchLastUsedAt };
|
package/dist/toolbox.d.ts
CHANGED
|
@@ -1,244 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* toolbox sort + dedup — pure helpers, no I/O.
|
|
5
|
-
*
|
|
6
|
-
* The Extension's `ToolBoxService` keeps a stable `order` index and
|
|
7
|
-
* surfaces "recently used" via the `lastUsedAt` ISO timestamp. These
|
|
8
|
-
* helpers codify that algorithm so the CLI and Extension agree on the
|
|
9
|
-
* "最近使用置顶" UX.
|
|
10
|
-
*
|
|
11
|
-
* Refs:
|
|
12
|
-
* - 4.功能规划.md §2.3 — `sort.ts 排序与去重纯函数`
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Sort by "recently used" — entries with newer `lastUsedAt` float to
|
|
17
|
-
* the top, entries with no `lastUsedAt` sort to the bottom (preserving
|
|
18
|
-
* their relative `order` index when both are absent).
|
|
19
|
-
*
|
|
20
|
-
* Ties (same `lastUsedAt` or both missing) are broken by `order`, then
|
|
21
|
-
* by `id` for determinism.
|
|
22
|
-
*/
|
|
23
|
-
declare function sortByRecentFirst(tools: readonly ExternalTool[]): ExternalTool[];
|
|
24
|
-
/**
|
|
25
|
-
* Sort by user-defined `order` field. Entries without `order` are
|
|
26
|
-
* appended in their input order (stable sort via `id` tiebreaker).
|
|
27
|
-
*/
|
|
28
|
-
declare function sortByUserOrder(tools: readonly ExternalTool[]): ExternalTool[];
|
|
29
|
-
/**
|
|
30
|
-
* Merge built-in defaults with user-stored tools. Defaults keep
|
|
31
|
-
* `isDefault: true` and their `order`; user tools are appended with
|
|
32
|
-
* their stored metadata. Output is sorted by user order.
|
|
33
|
-
*/
|
|
34
|
-
declare function mergeWithDefaults(defaults: readonly ExternalTool[], userTools: readonly ExternalTool[]): ExternalTool[];
|
|
35
|
-
/**
|
|
36
|
-
* Re-number `order` for a list of tool ids. Used by
|
|
37
|
-
* `toolbox.update` when the user drags-and-drops entries in the UI.
|
|
38
|
-
*/
|
|
39
|
-
declare function reindexOrder(tools: ExternalTool[], newOrderIds: readonly string[]): ExternalTool[];
|
|
40
|
-
/**
|
|
41
|
-
* Stamp `lastUsedAt` on the targeted tool (clones the array). Returns
|
|
42
|
-
* a new array; the input is left untouched.
|
|
43
|
-
*/
|
|
44
|
-
declare function touchLastUsedAt(tools: readonly ExternalTool[], id: string, when?: Date): ExternalTool[];
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* toolbox types — internal-only data shapes.
|
|
48
|
-
*
|
|
49
|
-
* The public data model lives in `@serviceme/devtools-protocol/toolbox`
|
|
50
|
-
* (`ExternalTool`, `ToolboxScope`, `ToolboxList`). The types below are
|
|
51
|
-
* the persisted on-disk shape and the patch helpers, scoped to the
|
|
52
|
-
* toolbox domain only.
|
|
53
|
-
*
|
|
54
|
-
* Refs:
|
|
55
|
-
* - 4.功能规划.md §2.3 — `toolbox/types.ts`
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
/** Schema version of the on-disk toolbox JSON files. Bumped on breaking changes. */
|
|
59
|
-
declare const TOOLBOX_JSON_SCHEMA_VERSION = 1;
|
|
60
|
-
interface PersistedToolbox {
|
|
61
|
-
version: number;
|
|
62
|
-
tools: ExternalTool[];
|
|
63
|
-
}
|
|
64
|
-
/** Patch payload accepted by `ToolboxCore.update` (mirrors `ExternalToolPatch`). */
|
|
65
|
-
type ToolboxPatch = Partial<Omit<ExternalTool, "id" | "isDefault">>;
|
|
66
|
-
/**
|
|
67
|
-
* Built-in (default) tool seeds. Mirrors the Extension's
|
|
68
|
-
* `apps/extension/src/config/external-tools.ts`. The Core stores these
|
|
69
|
-
* verbatim and never mutates them — the Extension's `ToolBoxService`
|
|
70
|
-
* runs the same merge on top of the Core's view.
|
|
71
|
-
*/
|
|
72
|
-
interface DefaultToolSeed {
|
|
73
|
-
id: string;
|
|
74
|
-
name: string;
|
|
75
|
-
description: string;
|
|
76
|
-
icon: string;
|
|
77
|
-
url: string;
|
|
78
|
-
}
|
|
79
|
-
/** Built-in tools rendered when the JSON file is missing or empty. */
|
|
80
|
-
declare const BUILTIN_DEFAULT_TOOLS: readonly DefaultToolSeed[];
|
|
81
|
-
/** Resolved toolbox shape returned by `ToolboxStore.read()`. */
|
|
82
|
-
interface ResolvedToolbox {
|
|
83
|
-
scope: ToolboxScope;
|
|
84
|
-
tools: ExternalTool[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* ToolboxStore — JSON persistence for the toolbox entries.
|
|
89
|
-
*
|
|
90
|
-
* Two scopes:
|
|
91
|
-
* - `user` → `~/.serviceme/toolbox.json`
|
|
92
|
-
* - `workspace` → `<cwd>/.github/.serviceme-toolbox.json`
|
|
93
|
-
*
|
|
94
|
-
* Both files share the `PersistedToolbox` shape and the same atomic
|
|
95
|
-
* write pattern (tmp + rename + fsync) as `IdentityStore`. Concurrent
|
|
96
|
-
* writers are serialized via a mkdir-based file lock (Phase 6+ may
|
|
97
|
-
* upgrade to `proper-lockfile`).
|
|
98
|
-
*
|
|
99
|
-
* Refs:
|
|
100
|
-
* - 4.功能规划.md §2.3 — `ToolboxStore.ts JSON 持久化(user + workspace scope)`
|
|
101
|
-
* - `3.功能拆分.md` §3 — toolbox wire shape
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
declare const WORKSPACE_TOOLBOX_RELATIVE_PATH: string;
|
|
105
|
-
interface ToolboxFileBackend {
|
|
106
|
-
read(filePath: string): Promise<PersistedToolbox | null>;
|
|
107
|
-
write(filePath: string, payload: PersistedToolbox): Promise<void>;
|
|
108
|
-
exists(filePath: string): Promise<boolean>;
|
|
109
|
-
/** Maximum number of `.corrupted.*.bak` snapshots to keep per toolbox file (default: 5). */
|
|
110
|
-
maxBackupCount?: number;
|
|
111
|
-
}
|
|
112
|
-
interface ToolboxStoreOptions {
|
|
113
|
-
/** Override the user-scope file path (default: `getToolboxJsonPath()`). */
|
|
114
|
-
userFilePath?: string;
|
|
115
|
-
/** Override the workspace-scope file path resolver (default: cwd-relative). */
|
|
116
|
-
resolveWorkspacePath?: () => string | null;
|
|
117
|
-
/** Injectable built-in tool seeds (default: `BUILTIN_DEFAULT_TOOLS`). */
|
|
118
|
-
defaultTools?: readonly DefaultToolSeed[];
|
|
119
|
-
hooks?: ToolboxStoreHooks;
|
|
120
|
-
backend?: ToolboxFileBackend;
|
|
121
|
-
lockTimeoutMs?: number;
|
|
122
|
-
lockRetryMs?: number;
|
|
123
|
-
}
|
|
124
|
-
interface ToolboxStoreHooks {
|
|
125
|
-
beforeWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;
|
|
126
|
-
afterWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;
|
|
127
|
-
}
|
|
128
|
-
declare class FsToolboxFileBackend implements ToolboxFileBackend {
|
|
129
|
-
maxBackupCount: number;
|
|
130
|
-
exists(filePath: string): Promise<boolean>;
|
|
131
|
-
read(filePath: string): Promise<PersistedToolbox | null>;
|
|
132
|
-
private backupCorruptedFile;
|
|
133
|
-
private purgeExcessBackups;
|
|
134
|
-
write(filePath: string, payload: PersistedToolbox): Promise<void>;
|
|
135
|
-
}
|
|
136
|
-
declare class ToolboxStore {
|
|
137
|
-
private readonly userFilePath;
|
|
138
|
-
private readonly resolveWorkspacePath;
|
|
139
|
-
private readonly defaults;
|
|
140
|
-
private readonly hooks;
|
|
141
|
-
private readonly backend;
|
|
142
|
-
private readonly lockTimeoutMs;
|
|
143
|
-
private readonly lockRetryMs;
|
|
144
|
-
constructor(opts?: ToolboxStoreOptions);
|
|
145
|
-
/** Read a scope; returns the resolved toolbox (with defaults merged when empty). */
|
|
146
|
-
read(scope: ToolboxScope): Promise<ResolvedToolbox>;
|
|
147
|
-
/**
|
|
148
|
-
* Atomic write under a file lock. Replaces the entire `tools` array
|
|
149
|
-
* with the provided snapshot.
|
|
150
|
-
*/
|
|
151
|
-
write(scope: ToolboxScope, tools: readonly ExternalTool[]): Promise<void>;
|
|
152
|
-
/**
|
|
153
|
-
* Read-modify-write under the file lock. The mutator receives the
|
|
154
|
-
* current user-only list (defaults not included) and returns the
|
|
155
|
-
* replacement list. Throwing inside the mutator aborts the write.
|
|
156
|
-
*/
|
|
157
|
-
mutate(scope: ToolboxScope, mutator: (current: ExternalTool[]) => Promise<ExternalTool[]>): Promise<ExternalTool[]>;
|
|
158
|
-
/** Wipe a scope entirely (used by `toolbox.remove --all` extensions). */
|
|
159
|
-
clear(scope: ToolboxScope): Promise<void>;
|
|
160
|
-
/** Test seam — resolve the user-scope file path. */
|
|
161
|
-
getUserFilePath(): string;
|
|
162
|
-
/** Test seam — resolve the workspace-scope file path (or null when disabled). */
|
|
163
|
-
getWorkspaceFilePath(): string | null;
|
|
164
|
-
private filePathFor;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* ToolboxCore — Main entry for the toolbox domain.
|
|
169
|
-
*
|
|
170
|
-
* Orchestrates the user + workspace scopes via `ToolboxStore`, applies
|
|
171
|
-
* the "最近使用置顶" sort, and exposes a small surface that mirrors
|
|
172
|
-
* the Phase 5.1 bridge methods:
|
|
173
|
-
*
|
|
174
|
-
* - `list(scope?)` → `toolbox.list`
|
|
175
|
-
* - `add(tool, scope)` → `toolbox.add`
|
|
176
|
-
* - `remove(id, scope?)` → `toolbox.remove`
|
|
177
|
-
* - `update(id, patch, scope?)` → `toolbox.update`
|
|
178
|
-
*
|
|
179
|
-
* Default tools (built-in seeds) are never user-deletable. `remove()`
|
|
180
|
-
* is a no-op for default tools and surfaces a structured error so
|
|
181
|
-
* callers can map it to `TOOLBOX_DEFAULT_IMMUTABLE` (Phase 5.3 error
|
|
182
|
-
* code).
|
|
183
|
-
*
|
|
184
|
-
* Refs:
|
|
185
|
-
* - 4.功能规划.md §2.3 — `ToolboxCore.ts 主入口`
|
|
186
|
-
* - `3.功能拆分.md` §3 — wire shape
|
|
187
|
-
*/
|
|
188
|
-
|
|
189
|
-
/** Sentinel — caller tried to remove a built-in (immutable) tool. */
|
|
190
|
-
declare class DefaultToolImmutableError extends Error {
|
|
191
|
-
readonly toolId: string;
|
|
192
|
-
constructor(toolId: string);
|
|
193
|
-
}
|
|
194
|
-
interface ToolboxCoreOptions {
|
|
195
|
-
store?: ToolboxStore;
|
|
196
|
-
defaultTools?: readonly DefaultToolSeed[];
|
|
197
|
-
/** Sort applied to the merged list returned by `list()`. Default: `sortByUserOrder`. */
|
|
198
|
-
listSort?: (tools: readonly ExternalTool[]) => ExternalTool[];
|
|
199
|
-
}
|
|
200
|
-
declare class ToolboxCore {
|
|
201
|
-
private readonly store;
|
|
202
|
-
private readonly defaults;
|
|
203
|
-
private readonly listSort;
|
|
204
|
-
constructor(opts?: ToolboxCoreOptions);
|
|
205
|
-
/** List all tools in a scope. Built-in defaults are merged in. */
|
|
206
|
-
list(scope?: ToolboxScope): Promise<ToolboxList>;
|
|
207
|
-
/**
|
|
208
|
-
* Append a new tool to the requested scope. Default tools are
|
|
209
|
-
* rejected (they are seeds, not user entries).
|
|
210
|
-
*/
|
|
211
|
-
add(tool: ExternalTool, scope?: ToolboxScope): Promise<ToolboxList>;
|
|
212
|
-
/**
|
|
213
|
-
* Remove a tool by id. Returns `success: false` when the id is a
|
|
214
|
-
* built-in default (idempotent, never throws on missing entries).
|
|
215
|
-
*/
|
|
216
|
-
remove(id: string, scope?: ToolboxScope): Promise<{
|
|
217
|
-
scope: ToolboxScope;
|
|
218
|
-
toolId: string;
|
|
219
|
-
success: boolean;
|
|
220
|
-
}>;
|
|
221
|
-
/**
|
|
222
|
-
* Patch a tool by id. Default tools can only have their `order`
|
|
223
|
-
* updated; other patches are silently ignored for default entries
|
|
224
|
-
* (callers can compare before/after to detect the ignore).
|
|
225
|
-
*/
|
|
226
|
-
update(id: string, patch: ToolboxPatch, scope?: ToolboxScope): Promise<ToolboxList>;
|
|
227
|
-
/**
|
|
228
|
-
* Stamp `lastUsedAt` on the targeted tool. This is the "recently
|
|
229
|
-
* used" hook the Extension's webview uses when a user clicks a
|
|
230
|
-
* toolbox entry.
|
|
231
|
-
*/
|
|
232
|
-
recordUsage(id: string, scope?: ToolboxScope, when?: Date): Promise<ExternalTool | null>;
|
|
233
|
-
/**
|
|
234
|
-
* Combined view: user + workspace scopes merged, sorted by recent
|
|
235
|
-
* usage. Workspace tools overlay user tools (workspace entries win
|
|
236
|
-
* on `id` collision).
|
|
237
|
-
*/
|
|
238
|
-
listMerged(): Promise<ToolboxList>;
|
|
239
|
-
/** Expose the underlying store (CLI / Bridge use it for path-level access). */
|
|
240
|
-
getStore(): ToolboxStore;
|
|
241
|
-
private isDefaultId;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export { BUILTIN_DEFAULT_TOOLS, DefaultToolImmutableError, type DefaultToolSeed, FsToolboxFileBackend, type PersistedToolbox, type ResolvedToolbox, TOOLBOX_JSON_SCHEMA_VERSION, ToolboxCore, type ToolboxCoreOptions, type ToolboxFileBackend, type ToolboxPatch, ToolboxStore, type ToolboxStoreHooks, type ToolboxStoreOptions, WORKSPACE_TOOLBOX_RELATIVE_PATH, mergeWithDefaults, reindexOrder, sortByRecentFirst, sortByUserOrder, touchLastUsedAt };
|
|
1
|
+
import { _ as reindexOrder, a as ToolboxFileBackend, b as touchLastUsedAt, c as ToolboxStoreOptions, d as DefaultToolSeed, f as PersistedToolbox, g as mergeWithDefaults, h as ToolboxPatch, i as FsToolboxFileBackend, l as WORKSPACE_TOOLBOX_RELATIVE_PATH, m as TOOLBOX_JSON_SCHEMA_VERSION, n as ToolboxCore, o as ToolboxStore, p as ResolvedToolbox, r as ToolboxCoreOptions, s as ToolboxStoreHooks, t as DefaultToolImmutableError, u as BUILTIN_DEFAULT_TOOLS, v as sortByRecentFirst, y as sortByUserOrder } from "./index-DaW7IYA-.js";
|
|
2
|
+
export { BUILTIN_DEFAULT_TOOLS, DefaultToolImmutableError, type DefaultToolSeed, FsToolboxFileBackend, type PersistedToolbox, type ResolvedToolbox, TOOLBOX_JSON_SCHEMA_VERSION, ToolboxCore, type ToolboxCoreOptions, type ToolboxFileBackend, type ToolboxPatch, ToolboxStore, type ToolboxStoreHooks, type ToolboxStoreOptions, WORKSPACE_TOOLBOX_RELATIVE_PATH, mergeWithDefaults, reindexOrder, sortByRecentFirst, sortByUserOrder, touchLastUsedAt };
|