pi2dsh 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/README.zh.md +122 -0
  4. package/dist/cli.d.mts +2 -0
  5. package/dist/cli.mjs +128 -0
  6. package/dist/cli.mjs.map +1 -0
  7. package/dist/compat/pi-ai.d.mts +2597 -0
  8. package/dist/compat/pi-ai.d.mts.map +1 -0
  9. package/dist/compat/pi-ai.mjs +4669 -0
  10. package/dist/compat/pi-ai.mjs.map +1 -0
  11. package/dist/compat/pi-coding-agent.d.mts +745 -0
  12. package/dist/compat/pi-coding-agent.d.mts.map +1 -0
  13. package/dist/compat/pi-coding-agent.mjs +4 -0
  14. package/dist/compat/pi-tui.d.mts +3 -0
  15. package/dist/compat/pi-tui.mjs +3622 -0
  16. package/dist/compat/pi-tui.mjs.map +1 -0
  17. package/dist/host.d.mts +35 -0
  18. package/dist/host.d.mts.map +1 -0
  19. package/dist/host.mjs +197 -0
  20. package/dist/host.mjs.map +1 -0
  21. package/dist/index.d.mts +69 -0
  22. package/dist/index.d.mts.map +1 -0
  23. package/dist/index.mjs +5 -0
  24. package/dist/mcp-config-jL9w70It.mjs +1535 -0
  25. package/dist/mcp-config-jL9w70It.mjs.map +1 -0
  26. package/dist/pi-coding-agent-Dsg6_0ua.mjs +2060 -0
  27. package/dist/pi-coding-agent-Dsg6_0ua.mjs.map +1 -0
  28. package/dist/pi-config-shim-CZ1wFzqM.mjs +27 -0
  29. package/dist/pi-config-shim-CZ1wFzqM.mjs.map +1 -0
  30. package/dist/pi-tui-iHoF2tFc.d.mts +1043 -0
  31. package/dist/pi-tui-iHoF2tFc.d.mts.map +1 -0
  32. package/dist/pi-tui-utils-CcaVtm-3.mjs +895 -0
  33. package/dist/pi-tui-utils-CcaVtm-3.mjs.map +1 -0
  34. package/dist/pi-types-KazmR2O5.d.mts +62 -0
  35. package/dist/pi-types-KazmR2O5.d.mts.map +1 -0
  36. package/dist/pi-uuid-Db8ShZsK.mjs +47 -0
  37. package/dist/pi-uuid-Db8ShZsK.mjs.map +1 -0
  38. package/dist/rolldown-runtime-C2Q2p085.mjs +15 -0
  39. package/dist/runtime-D84Hv_3m.mjs +1499 -0
  40. package/dist/runtime-D84Hv_3m.mjs.map +1 -0
  41. package/dist/runtime.d.mts +31 -0
  42. package/dist/runtime.d.mts.map +1 -0
  43. package/dist/runtime.mjs +3 -0
  44. package/dist/source-D7Ir-rPT.mjs +154 -0
  45. package/dist/source-D7Ir-rPT.mjs.map +1 -0
  46. package/dist/types-7IWJPPvS.d.mts +59 -0
  47. package/dist/types-7IWJPPvS.d.mts.map +1 -0
  48. package/package.json +135 -0
@@ -0,0 +1,1535 @@
1
+
2
+ import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
3
+ import { builtinModules } from "node:module";
4
+ import { cp, lstat, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
5
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
6
+ import ts from "typescript";
7
+ import { fileURLToPath } from "node:url";
8
+ import { parse } from "yaml";
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ //#region src/compatibility.ts
12
+ const rule = (level, detail) => ({
13
+ level,
14
+ detail
15
+ });
16
+ const PI_CODING_AGENT_PACKAGES = Object.freeze(["@earendil-works/pi-coding-agent", "@mariozechner/pi-coding-agent"]);
17
+ const PI_TUI_PACKAGES = Object.freeze(["@earendil-works/pi-tui", "@mariozechner/pi-tui"]);
18
+ const PI_AI_PACKAGES = Object.freeze(["@earendil-works/pi-ai", "@mariozechner/pi-ai"]);
19
+ const VENDORED = "Vendored byte-identical from Pi, so semantics match Pi exactly.";
20
+ const HEADLESS_COMPONENT = "Constructible headless component with Pi-exact signatures; renders plain text, never a terminal.";
21
+ const RUNTIME_STUB = "Importable, but calling it fails explicitly: it belongs to Pi's internal agent runtime and needs a native DSH port.";
22
+ const HOST_IMPORT_RULES = Object.freeze({
23
+ "pi-coding-agent": Object.freeze({
24
+ defineTool: rule("full", "Identity helper, preserved."),
25
+ CONFIG_DIR_NAME: rule("partial", "The conventional config directory name is preserved, while DSH owns the actual profile layout."),
26
+ DEFAULT_MAX_LINES: rule("full", VENDORED),
27
+ DEFAULT_MAX_BYTES: rule("full", VENDORED),
28
+ VERSION: rule("partial", "Reports a pi2dsh compatibility marker instead of a Pi release version."),
29
+ CURRENT_SESSION_VERSION: rule("full", VENDORED),
30
+ getAgentDir: rule("partial", "Redirected to an isolated DSH-owned pi2dsh directory instead of Pi global state."),
31
+ getPackageDir: rule("partial", "Resolves inside the DSH-owned pi2dsh agent directory."),
32
+ formatSize: rule("full", VENDORED),
33
+ truncateHead: rule("full", VENDORED),
34
+ truncateTail: rule("full", VENDORED),
35
+ truncateLine: rule("full", VENDORED),
36
+ withFileMutationQueue: rule("full", VENDORED),
37
+ SessionManager: rule("full", `${VENDORED} Sessions live under the DSH-owned pi2dsh agent directory.`),
38
+ parseSessionEntries: rule("full", VENDORED),
39
+ migrateSessionEntries: rule("full", VENDORED),
40
+ getLatestCompactionEntry: rule("full", VENDORED),
41
+ sessionEntryToContextMessages: rule("full", VENDORED),
42
+ buildContextEntries: rule("full", VENDORED),
43
+ buildSessionContext: rule("full", VENDORED),
44
+ loadEntriesFromFile: rule("full", VENDORED),
45
+ findMostRecentSession: rule("full", VENDORED),
46
+ getDefaultSessionDir: rule("full", VENDORED),
47
+ assertValidSessionId: rule("full", VENDORED),
48
+ convertToLlm: rule("full", VENDORED),
49
+ createCustomMessage: rule("full", VENDORED),
50
+ createBranchSummaryMessage: rule("full", VENDORED),
51
+ createCompactionSummaryMessage: rule("full", VENDORED),
52
+ bashExecutionToText: rule("full", VENDORED),
53
+ estimateTokens: rule("full", "Reimplements Pi's chars/4 heuristic over the same message roles."),
54
+ calculateContextTokens: rule("full", "Sums the Pi chars/4 heuristic across messages."),
55
+ DEFAULT_COMPACTION_SETTINGS: rule("full", "Pi's default compaction thresholds, preserved as constants."),
56
+ serializeConversation: rule("partial", "JSON serialization without Pi's prompt-oriented formatting."),
57
+ shouldCompact: rule("partial", "Always reports false: DSH owns compaction scheduling."),
58
+ compact: rule("unsupported", "Pi compaction execution belongs to DSH's native compaction plugin; calling fails explicitly."),
59
+ findCutPoint: rule("unsupported", RUNTIME_STUB),
60
+ generateSummary: rule("unsupported", RUNTIME_STUB),
61
+ generateSummaryWithUsage: rule("unsupported", RUNTIME_STUB),
62
+ generateBranchSummary: rule("unsupported", RUNTIME_STUB),
63
+ parseFrontmatter: rule("full", "Frontmatter parsing with Pi-compatible semantics."),
64
+ stripFrontmatter: rule("full", "Frontmatter stripping with Pi-compatible semantics."),
65
+ copyToClipboard: rule("partial", "Attempts the platform clipboard command (pbcopy/clip/wl-copy/xclip); resolves false when none succeeds."),
66
+ resizeImage: rule("partial", "Passes images through un-resized; Pi resizes only to save tokens, so content is preserved."),
67
+ convertToPng: rule("partial", "PNG input passes through; other formats fail explicitly without Pi's wasm codec."),
68
+ getShellConfig: rule("partial", "Standard shell detection without Pi's managed-bin PATH handling."),
69
+ getBinDir: rule("partial", "Reports the first PATH segment instead of Pi's managed bin directory."),
70
+ Theme: rule("partial", "Headless theme: styling calls return their input text unstyled."),
71
+ theme: rule("partial", "A headless theme singleton; jiti-loaded Pi extensions must not rely on Pi global theme state anyway."),
72
+ initTheme: rule("partial", "Accepted as a no-op; DSH surfaces own presentation."),
73
+ getSettingsListTheme: rule("partial", "Returns an unstyled theme with Pi's exact field shape."),
74
+ getSelectListTheme: rule("partial", "Returns an unstyled theme with Pi's exact field shape."),
75
+ getMarkdownTheme: rule("partial", "Returns a plain-text headless theme; Pi terminal styling is intentionally discarded."),
76
+ getLanguageFromPath: rule("partial", "Extension-based language detection covering common languages."),
77
+ highlightCode: rule("partial", "Splits lines without terminal syntax colors."),
78
+ DynamicBorder: rule("full", "Headless implementation of Pi's one-line border component; pass an explicit color function as Pi itself recommends."),
79
+ SettingsManager: rule("partial", "In-memory settings with Pi's getter/setter surface; DSH owns real persisted configuration."),
80
+ InMemorySettingsStorage: rule("partial", "In-memory storage stub honoring the withLock contract."),
81
+ FileSettingsStorage: rule("partial", "Alias of the in-memory storage; DSH owns persisted settings."),
82
+ ModelRegistry: rule("partial", "A local registry container; DSH llm adapters own real model routing."),
83
+ createEventBus: rule("full", "Pi event-bus semantics: async handler isolation and unsubscribe functions."),
84
+ readStoredCredential: rule("partial", "Reads Pi-style auth.json from the pi2dsh-owned agent directory; DSH credentials stay authoritative for DSH model calls."),
85
+ parseSkillBlock: rule("full", "Pi's skill_content block parser, reimplemented over the same wire shape."),
86
+ wrapRegisteredTool: rule("unsupported", RUNTIME_STUB),
87
+ ProjectTrustStore: rule("unsupported", RUNTIME_STUB),
88
+ DefaultResourceLoader: rule("unsupported", RUNTIME_STUB),
89
+ DefaultPackageManager: rule("unsupported", RUNTIME_STUB),
90
+ ModelRuntime: rule("unsupported", RUNTIME_STUB),
91
+ createAgentSession: rule("unsupported", RUNTIME_STUB),
92
+ createCodingTools: rule("unsupported", RUNTIME_STUB),
93
+ createReadOnlyTools: rule("unsupported", RUNTIME_STUB),
94
+ createBashTool: rule("unsupported", RUNTIME_STUB),
95
+ createReadTool: rule("unsupported", RUNTIME_STUB),
96
+ createEditTool: rule("unsupported", RUNTIME_STUB),
97
+ createWriteTool: rule("unsupported", RUNTIME_STUB),
98
+ createGrepTool: rule("unsupported", RUNTIME_STUB),
99
+ createFindTool: rule("unsupported", RUNTIME_STUB),
100
+ createLsTool: rule("unsupported", RUNTIME_STUB),
101
+ loadSkills: rule("unsupported", RUNTIME_STUB),
102
+ loadSkillsFromDir: rule("unsupported", RUNTIME_STUB),
103
+ formatSkillsForPrompt: rule("unsupported", RUNTIME_STUB),
104
+ CustomEditor: rule("partial", HEADLESS_COMPONENT),
105
+ ToolExecutionComponent: rule("partial", HEADLESS_COMPONENT),
106
+ FooterComponent: rule("partial", HEADLESS_COMPONENT),
107
+ BorderedLoader: rule("partial", HEADLESS_COMPONENT),
108
+ CustomMessageComponent: rule("partial", HEADLESS_COMPONENT),
109
+ AssistantMessageComponent: rule("partial", HEADLESS_COMPONENT),
110
+ UserMessageComponent: rule("partial", HEADLESS_COMPONENT),
111
+ ExtensionSelectorComponent: rule("partial", HEADLESS_COMPONENT),
112
+ ExtensionInputComponent: rule("partial", HEADLESS_COMPONENT),
113
+ ExtensionEditorComponent: rule("partial", HEADLESS_COMPONENT),
114
+ SettingsSelectorComponent: rule("partial", HEADLESS_COMPONENT),
115
+ renderDiff: rule("partial", "Plain unified-style diff lines without terminal colors."),
116
+ truncateToVisualLines: rule("partial", "Visual-line truncation backed by Pi's vendored width math."),
117
+ keyHint: rule("partial", "Plain-text key hint without theme styling."),
118
+ keyText: rule("partial", "Plain-text key name without theme styling."),
119
+ rawKeyHint: rule("partial", "Plain-text key hint without theme styling.")
120
+ }),
121
+ "pi-tui": Object.freeze({
122
+ visibleWidth: rule("full", VENDORED),
123
+ truncateToWidth: rule("full", VENDORED),
124
+ wrapTextWithAnsi: rule("full", VENDORED),
125
+ sliceByColumn: rule("full", VENDORED),
126
+ sliceWithWidth: rule("full", VENDORED),
127
+ stripTerminalSequences: rule("full", VENDORED),
128
+ getOsc8LinkAtColumn: rule("full", VENDORED),
129
+ normalizeTerminalOutput: rule("full", VENDORED),
130
+ extractAnsiCode: rule("full", VENDORED),
131
+ getGraphemeCellRange: rule("full", VENDORED),
132
+ getGraphemeSegmenter: rule("full", VENDORED),
133
+ getWordSegmenter: rule("full", VENDORED),
134
+ applyBackgroundToLine: rule("full", VENDORED),
135
+ isWhitespaceChar: rule("full", VENDORED),
136
+ isPunctuationChar: rule("full", VENDORED),
137
+ cjkBreakRegex: rule("full", VENDORED),
138
+ PUNCTUATION_REGEX: rule("full", VENDORED),
139
+ fuzzyMatch: rule("full", VENDORED),
140
+ fuzzyFilter: rule("full", VENDORED),
141
+ parseKey: rule("full", VENDORED),
142
+ matchesKey: rule("full", VENDORED),
143
+ isKeyRelease: rule("full", VENDORED),
144
+ isKeyRepeat: rule("full", VENDORED),
145
+ decodeKittyPrintable: rule("full", VENDORED),
146
+ isKittyProtocolActive: rule("full", VENDORED),
147
+ setKittyProtocolActive: rule("full", VENDORED),
148
+ Key: rule("full", VENDORED),
149
+ getKeybindings: rule("full", `${VENDORED} Bindings only match when a surface feeds terminal input, which DSH does not.`),
150
+ setKeybindings: rule("full", VENDORED),
151
+ KeybindingsManager: rule("full", VENDORED),
152
+ TUI_KEYBINDINGS: rule("full", VENDORED),
153
+ parseOsc11BackgroundColor: rule("full", VENDORED),
154
+ parseTerminalColorSchemeReport: rule("full", VENDORED),
155
+ renderLatex: rule("full", VENDORED),
156
+ getPngDimensions: rule("full", VENDORED),
157
+ getJpegDimensions: rule("full", VENDORED),
158
+ getGifDimensions: rule("full", VENDORED),
159
+ getWebpDimensions: rule("full", VENDORED),
160
+ getImageDimensions: rule("full", VENDORED),
161
+ calculateImageRows: rule("full", VENDORED),
162
+ allocateImageId: rule("full", VENDORED),
163
+ encodeKitty: rule("partial", `${VENDORED} No DSH surface consumes the escape sequences.`),
164
+ encodeITerm2: rule("partial", `${VENDORED} No DSH surface consumes the escape sequences.`),
165
+ deleteKittyImage: rule("partial", `${VENDORED} No DSH surface consumes the escape sequences.`),
166
+ deleteAllKittyImages: rule("partial", `${VENDORED} No DSH surface consumes the escape sequences.`),
167
+ detectCapabilities: rule("partial", `${VENDORED} Headless environments report no image protocol.`),
168
+ getCapabilities: rule("partial", VENDORED),
169
+ setCapabilities: rule("partial", VENDORED),
170
+ resetCapabilitiesCache: rule("partial", VENDORED),
171
+ getCellDimensions: rule("partial", VENDORED),
172
+ setCellDimensions: rule("partial", VENDORED),
173
+ hyperlink: rule("full", VENDORED),
174
+ imageFallback: rule("full", VENDORED),
175
+ CombinedAutocompleteProvider: rule("partial", `${VENDORED} Suggestions surface only if a DSH UI asks for them.`),
176
+ Marked: rule("full", "Re-exported from the same marked dependency Pi uses."),
177
+ CURSOR_MARKER: rule("full", "Pi's exact APC marker; vendored width math treats it as zero-width."),
178
+ Text: rule("partial", HEADLESS_COMPONENT),
179
+ Spacer: rule("partial", HEADLESS_COMPONENT),
180
+ Container: rule("partial", HEADLESS_COMPONENT),
181
+ Box: rule("partial", HEADLESS_COMPONENT),
182
+ Markdown: rule("partial", HEADLESS_COMPONENT),
183
+ TruncatedText: rule("partial", HEADLESS_COMPONENT),
184
+ Editor: rule("partial", `${HEADLESS_COMPONENT} Text editing state works; interactive keyboard flows do not.`),
185
+ Input: rule("partial", `${HEADLESS_COMPONENT} Value state and submit/escape callbacks work; kill-ring editing does not.`),
186
+ SelectList: rule("partial", `${HEADLESS_COMPONENT} Filtering and selection state work; keyboard interaction does not.`),
187
+ SettingsList: rule("partial", `${HEADLESS_COMPONENT} Value updates work; keyboard interaction does not.`),
188
+ ScrollView: rule("partial", HEADLESS_COMPONENT),
189
+ VStack: rule("partial", HEADLESS_COMPONENT),
190
+ HStack: rule("partial", HEADLESS_COMPONENT),
191
+ Loader: rule("partial", HEADLESS_COMPONENT),
192
+ CancellableLoader: rule("partial", HEADLESS_COMPONENT),
193
+ Image: rule("partial", `${HEADLESS_COMPONENT} Renders a text placeholder; image bytes flow through DSH attachments instead.`),
194
+ isFocusable: rule("full", "Structural check preserved."),
195
+ isViewportTUI: rule("partial", "Always false: no viewport TUI exists in DSH surfaces.")
196
+ }),
197
+ "pi-ai": Object.freeze({
198
+ StringEnum: rule("full", "Preserves Pi flat string-enum JSON Schema generation without loading provider SDKs."),
199
+ registerProvider: rule("partial", "Recorded in a bridge-local registry; DSH llm adapters own real routing."),
200
+ getProviders: rule("partial", "Returns the bridge-local registry contents."),
201
+ getProvider: rule("partial", "Reads the bridge-local registry."),
202
+ getModel: rule("partial", "Resolves no Pi model objects; DSH owns model routing."),
203
+ getModels: rule("partial", "Returns an empty list; DSH owns model routing."),
204
+ complete: rule("unsupported", "Pi provider SDK calls have no DSH mapping; calling fails explicitly."),
205
+ stream: rule("unsupported", "Pi provider SDK calls have no DSH mapping; calling fails explicitly."),
206
+ Type: rule("full", "Re-exported from the same typebox dependency Pi resolves for extensions."),
207
+ uuidv7: rule("full", VENDORED),
208
+ isContextOverflow: rule("full", VENDORED),
209
+ isRecoverableLength: rule("full", VENDORED),
210
+ isRetryableAssistantError: rule("full", VENDORED),
211
+ contentText: rule("full", "Pi's text-block joiner, reimplemented with identical semantics."),
212
+ clampThinkingLevel: rule("full", "Pi's clamping walk over the extended thinking-level ladder."),
213
+ getSupportedThinkingLevels: rule("full", "Pi's thinkingLevelMap filter, preserved."),
214
+ modelsAreEqual: rule("full", "Id+provider equality, preserved.")
215
+ })
216
+ });
217
+ const CONTEXT_RULES = Object.freeze({
218
+ cwd: rule("full", "Mapped to the active DSH agent session working directory."),
219
+ signal: rule("full", "Mapped to the active DSH cancellation signal when one is available."),
220
+ hasUI: rule("full", "Reports whether the native DSH userQuestions service is available to back Pi dialogs."),
221
+ mode: rule("partial", "Reports rpc mode so Pi extensions can choose their documented headless fallback."),
222
+ isIdle: rule("partial", "Command contexts report idle; tool/lifecycle contexts conservatively report non-idle."),
223
+ isProjectTrusted: rule("partial", "Fails closed as untrusted because DSH does not expose Pi project-trust state."),
224
+ hasPendingMessages: rule("partial", "Conservatively reports no Pi-specific pending-message queue."),
225
+ getContextUsage: rule("partial", "Returns no Pi token-usage projection."),
226
+ getSystemPrompt: rule("full", "Returns the system prompt currently assembled by the bridge."),
227
+ getSystemPromptOptions: rule("partial", "Returns an empty Pi option projection in command contexts."),
228
+ waitForIdle: rule("partial", "Mapped to the DSH agent idle boundary when available."),
229
+ sessionManager: rule("partial", "A real read-only projection: DSH durable messages plus pi2dsh sidecar entries, exposed through Pi's exact 14-method surface as a single-branch tree."),
230
+ modelRegistry: rule("partial", "Exposes no Pi provider registry because DSH owns model adapters."),
231
+ model: rule("partial", "Reflects the model override recorded by setModel(); DSH configuration remains authoritative."),
232
+ scopedModels: rule("partial", "No Pi scoped-model list is projected from DSH configuration."),
233
+ thinkingLevel: rule("partial", "Reflects the level recorded by setThinkingLevel(); applied as reasoningEffort on the next request."),
234
+ abort: rule("partial", "Mapped to agent.cancel({ kind: \"hook\" }) on the live DSH agent."),
235
+ shutdown: rule("unsupported", "A migrated package may not shut down the DSH host; calling fails explicitly."),
236
+ compact: rule("unsupported", "Pi compaction control requires a native DSH compaction integration; calling fails explicitly."),
237
+ newSession: rule("unsupported", "Session replacement belongs to the DSH host; calling fails explicitly."),
238
+ fork: rule("unsupported", "Pi entry-tree forking has no DSH equivalent (DSH fork is boundary-based); calling fails explicitly."),
239
+ navigateTree: rule("unsupported", "Pi tree navigation has no DSH equivalent; calling fails explicitly."),
240
+ switchSession: rule("unsupported", "Session switching belongs to the DSH host; calling fails explicitly."),
241
+ reload: rule("unsupported", "Extension reload belongs to the DSH host (HMR); calling fails explicitly.")
242
+ });
243
+ const UI_CONTEXT_RULES = Object.freeze({
244
+ notify: rule("full", "Captured as a command result when applicable and emitted through DSH logging."),
245
+ setStatus: rule("partial", "Accepted as a no-op because DSH owns status presentation."),
246
+ setWidget: rule("partial", "Accepted as a no-op because Pi terminal widgets cannot render in DSH."),
247
+ select: rule("full", "Mapped to one native DSH userQuestions single-select request."),
248
+ confirm: rule("full", "Mapped to one native DSH userQuestions Yes/No request."),
249
+ input: rule("full", "Mapped to one native DSH userQuestions free-text request."),
250
+ editor: rule("partial", "Mapped to one DSH userQuestions free-text request; multi-line editing UX is not emulated."),
251
+ custom: rule("partial", "Resolves undefined, exactly like Pi's own rpc mode; guarded fallbacks keep working."),
252
+ onTerminalInput: rule("partial", "Raw terminal input is absent; feature-detected listeners remain disabled."),
253
+ setWorkingMessage: rule("partial", "Accepted as a no-op; DSH owns progress presentation."),
254
+ setWorkingVisible: rule("partial", "Accepted as a no-op; DSH owns progress presentation."),
255
+ setWorkingIndicator: rule("partial", "Accepted as a no-op; DSH owns progress presentation."),
256
+ setHiddenThinkingLabel: rule("partial", "Accepted as a no-op; DSH owns thinking presentation."),
257
+ setFooter: rule("partial", "Accepted as a no-op; DSH owns footer presentation."),
258
+ setHeader: rule("partial", "Accepted as a no-op; DSH owns header presentation."),
259
+ setTitle: rule("partial", "Accepted as a no-op; DSH owns window titles."),
260
+ pasteToEditor: rule("partial", "Appends to a per-agent editor buffer readable through getEditorText()."),
261
+ setEditorText: rule("partial", "Stored in a per-agent editor buffer readable through getEditorText()."),
262
+ getEditorText: rule("partial", "Reads the per-agent editor buffer maintained by the bridge."),
263
+ addAutocompleteProvider: rule("partial", "Registration is recorded; no DSH surface requests suggestions."),
264
+ setEditorComponent: rule("partial", "Registration is recorded; no DSH surface mounts a Pi editor component."),
265
+ getEditorComponent: rule("partial", "Returns the recorded factory."),
266
+ theme: rule("partial", "A headless theme whose styling calls return unstyled text."),
267
+ getAllThemes: rule("partial", "Lists the single headless theme."),
268
+ getTheme: rule("partial", "Resolves only the headless theme."),
269
+ setTheme: rule("partial", "Accepts the headless theme; other names report an explicit error result."),
270
+ getToolsExpanded: rule("partial", "A bridge-local presentation flag."),
271
+ setToolsExpanded: rule("partial", "A bridge-local presentation flag.")
272
+ });
273
+ const API_RULES = Object.freeze({
274
+ registerTool: {
275
+ level: "partial",
276
+ detail: "Registered as a native DSH tool. Text and image results use native DSH content/attachments; unsupported JSON Schema constraints and Pi-only error details are explicitly degraded."
277
+ },
278
+ unregisterTool: {
279
+ level: "full",
280
+ detail: "Disposes the exact native DSH tool registration and removes it from the migrated package registry."
281
+ },
282
+ registerCommand: {
283
+ level: "partial",
284
+ detail: "Registered in ctx.commands; ui.notify becomes the result, while interactive Pi TUI methods fail explicitly in headless DSH."
285
+ },
286
+ registerShortcut: {
287
+ level: "partial",
288
+ detail: "Registration is recorded and introspectable; DSH surfaces feed no terminal key input, so handlers never fire — the same as Pi's non-TUI modes."
289
+ },
290
+ registerFlag: {
291
+ level: "partial",
292
+ detail: "The declared default is available through getFlag; Pi process flags cannot be added to the DSH launcher."
293
+ },
294
+ getFlag: {
295
+ level: "partial",
296
+ detail: "Returns the migrated flag default because DSH cannot register the original Pi CLI flag."
297
+ },
298
+ registerProvider: {
299
+ level: "partial",
300
+ detail: "The provider declaration is recorded and introspectable; model calls stay on native DSH llm adapters and credentials, which own transports and secrets."
301
+ },
302
+ unregisterProvider: {
303
+ level: "partial",
304
+ detail: "Removes the recorded provider declaration."
305
+ },
306
+ registerMessageRenderer: {
307
+ level: "partial",
308
+ detail: "Registration is accepted; DSH owns presentation, so the renderer is never invoked — matching Pi's non-TUI surfaces."
309
+ },
310
+ registerEntryRenderer: {
311
+ level: "partial",
312
+ detail: "Registration is accepted; DSH owns presentation, so the renderer is never invoked — matching Pi's non-TUI surfaces."
313
+ },
314
+ registerMarkdownTransformer: {
315
+ level: "partial",
316
+ detail: "Registration is accepted; DSH owns presentation, so the transformer is never invoked — matching Pi's non-TUI surfaces."
317
+ },
318
+ sendMessage: {
319
+ level: "partial",
320
+ detail: "Mapped to native DSH inject/steer/followup delivery with honest plugin provenance; Pi display/details metadata awaits the custom session-entry seam."
321
+ },
322
+ sendUserMessage: {
323
+ level: "full",
324
+ detail: "Mapped to native DSH steer/followup delivery with text and attachment-backed image content."
325
+ },
326
+ appendEntry: {
327
+ level: "partial",
328
+ detail: "Persisted in a pi2dsh sidecar next to the DSH session and replayed on session start; DSH's main log stays untouched because it has no out-of-repo plugin-event channel yet."
329
+ },
330
+ setSessionName: {
331
+ level: "partial",
332
+ detail: "Persisted in the pi2dsh sidecar and announced through session_info_changed; DSH's own title events are also projected when present."
333
+ },
334
+ getSessionName: {
335
+ level: "partial",
336
+ detail: "Reads the sidecar-persisted session name."
337
+ },
338
+ setLabel: {
339
+ level: "partial",
340
+ detail: "Persisted in the pi2dsh sidecar and reflected by the sessionManager projection."
341
+ },
342
+ exec: {
343
+ level: "partial",
344
+ detail: "Mapped to ctx.subprocess, so the selected local/E2B provider owns execution, isolation, cancellation, and tree cleanup; output is bounded to 64 MiB per stream."
345
+ },
346
+ getActiveTools: {
347
+ level: "partial",
348
+ detail: "Returns every tool visible in the current DSH agent scope, including native and migrated tools; scope-local tools follow DSH composition rules."
349
+ },
350
+ getAllTools: {
351
+ level: "partial",
352
+ detail: "Returns metadata for all tools visible in the current DSH scope, without Pi-specific prompt guidelines unavailable from DSH schemas."
353
+ },
354
+ setActiveTools: {
355
+ level: "partial",
356
+ detail: "Mapped to the active DSH agent scope through tools.restrict({ allow }), preserving per-agent global-tool visibility without mutating other agents; DSH scope-local tools remain visible by design."
357
+ },
358
+ getCommands: {
359
+ level: "partial",
360
+ detail: "Returns commands registered by this migrated Pi package, not every command visible in the DSH scope."
361
+ },
362
+ setModel: {
363
+ level: "partial",
364
+ detail: "Recorded as a per-agent override applied through the agent/request waterfall on the next model call; DSH remains authoritative for provider routing."
365
+ },
366
+ getThinkingLevel: {
367
+ level: "partial",
368
+ detail: "Returns the level recorded by setThinkingLevel (default off)."
369
+ },
370
+ setThinkingLevel: {
371
+ level: "partial",
372
+ detail: "Recorded per agent and applied as reasoningEffort through the agent/request waterfall; DSH validates the effort id at the request boundary."
373
+ },
374
+ events: {
375
+ level: "full",
376
+ detail: "Package-local Pi extension event-bus emit/on semantics are preserved for migrated extensions in the same bundle."
377
+ }
378
+ });
379
+ const OBSERVED_NEVER_FIRES = (moment) => ({
380
+ level: "partial",
381
+ detail: `Registration is accepted; ${moment} never occurs on DSH surfaces, so the handler never fires. Loading is unaffected.`
382
+ });
383
+ const EVENT_RULES = Object.freeze({
384
+ session_start: {
385
+ level: "full",
386
+ detail: "Mapped to agent/session-start."
387
+ },
388
+ session_shutdown: {
389
+ level: "full",
390
+ detail: "Mapped to agent disposal and plugin teardown with duplicate suppression."
391
+ },
392
+ session_info_changed: {
393
+ level: "partial",
394
+ detail: "Fired by setSessionName() and projected from DSH session/title events."
395
+ },
396
+ agent_start: {
397
+ level: "full",
398
+ detail: "Mapped to the DSH turn/start boundary."
399
+ },
400
+ agent_settled: {
401
+ level: "full",
402
+ detail: "Mapped to the DSH turn/end boundary."
403
+ },
404
+ turn_start: {
405
+ level: "full",
406
+ detail: "Mapped from durable turn/start events."
407
+ },
408
+ tool_execution_start: {
409
+ level: "full",
410
+ detail: "Mapped from durable tool/call events."
411
+ },
412
+ tool_execution_end: {
413
+ level: "full",
414
+ detail: "Mapped from finalized tools/result events."
415
+ },
416
+ tool_execution_update: {
417
+ level: "partial",
418
+ detail: "Fired from migrated Pi tools' own onUpdate callbacks; DSH-native tools expose no partial-result stream."
419
+ },
420
+ tool_call: {
421
+ level: "partial",
422
+ detail: "Blocking is supported, and in-place argument mutation reaches migrated Pi tools; mutating a DSH-native tool's arguments is rejected because DSH logs arguments before policy."
423
+ },
424
+ tool_result: {
425
+ level: "partial",
426
+ detail: "Text replacement and success-to-error blocking are supported; arbitrary details and error recovery are not."
427
+ },
428
+ before_agent_start: {
429
+ level: "partial",
430
+ detail: "System-prompt replacement is supported; Pi raw prompt and custom-message injection are unavailable at DSH assembly time."
431
+ },
432
+ agent_end: {
433
+ level: "partial",
434
+ detail: "The lifecycle boundary is mapped, but the reconstructed Pi message history is intentionally minimal."
435
+ },
436
+ turn_end: {
437
+ level: "partial",
438
+ detail: "The lifecycle boundary and tool results are mapped; the exact Pi final-message shape is not guaranteed."
439
+ },
440
+ message_start: {
441
+ level: "partial",
442
+ detail: "Durable user, assistant, and tool-result messages are mapped without Pi-specific provider metadata."
443
+ },
444
+ message_end: {
445
+ level: "partial",
446
+ detail: "Durable messages are observed, but message replacement is not supported."
447
+ },
448
+ message_update: {
449
+ level: "partial",
450
+ detail: "Projected from DSH assistant/chunk events with accumulated text; Pi's full AgentMessage accumulation state is approximated."
451
+ },
452
+ session_before_compact: {
453
+ level: "partial",
454
+ detail: "Projected from DSH compaction/start as a notification; cancel/replace cannot reach DSH's compactor."
455
+ },
456
+ session_compact: {
457
+ level: "partial",
458
+ detail: "Projected from DSH compaction summary/end events."
459
+ },
460
+ model_select: {
461
+ level: "partial",
462
+ detail: "Fired by setModel() and projected from request/header model changes in the durable log."
463
+ },
464
+ thinking_level_select: {
465
+ level: "partial",
466
+ detail: "Fired by setThinkingLevel(); DSH-side reasoning changes surface through request/header projection."
467
+ },
468
+ context: {
469
+ level: "unsupported",
470
+ detail: "Pi context-list replacement conflicts with DSH append-only request reconstruction; the handler is accepted but never fires."
471
+ },
472
+ before_provider_request: {
473
+ level: "unsupported",
474
+ detail: "Provider payload mutation belongs in a native DSH LLM adapter; the handler is accepted but never fires."
475
+ },
476
+ before_provider_headers: {
477
+ level: "unsupported",
478
+ detail: "Provider header mutation belongs in a native DSH LLM adapter; the handler is accepted but never fires."
479
+ },
480
+ after_provider_response: {
481
+ level: "unsupported",
482
+ detail: "Provider response interception belongs in a native DSH LLM adapter; the handler is accepted but never fires."
483
+ },
484
+ user_bash: OBSERVED_NEVER_FIRES("Pi's ! command surface"),
485
+ input: OBSERVED_NEVER_FIRES("raw Pi terminal input"),
486
+ project_trust: {
487
+ level: "unsupported",
488
+ detail: "Project trust must remain owned by the DSH host; the handler is accepted but never consulted."
489
+ },
490
+ resources_discover: {
491
+ level: "unsupported",
492
+ detail: "Dynamic resource discovery must be converted into DSH providers; the handler is accepted but never fires."
493
+ },
494
+ session_before_switch: OBSERVED_NEVER_FIRES("Pi session switching"),
495
+ session_before_fork: OBSERVED_NEVER_FIRES("Pi tree forking"),
496
+ session_before_tree: OBSERVED_NEVER_FIRES("Pi session-tree navigation"),
497
+ session_tree: OBSERVED_NEVER_FIRES("Pi session-tree navigation")
498
+ });
499
+ function ruleForApi(method) {
500
+ return API_RULES[method];
501
+ }
502
+ function ruleForEvent(event) {
503
+ return EVENT_RULES[event] ?? {
504
+ level: "unsupported",
505
+ detail: `Unknown Pi event ${JSON.stringify(event)} has no verified DSH mapping.`
506
+ };
507
+ }
508
+ function ruleForHostImport(packageName, importedName) {
509
+ const family = PI_CODING_AGENT_PACKAGES.includes(packageName) ? "pi-coding-agent" : PI_TUI_PACKAGES.includes(packageName) ? "pi-tui" : PI_AI_PACKAGES.includes(packageName) ? "pi-ai" : void 0;
510
+ return family === void 0 ? void 0 : HOST_IMPORT_RULES[family]?.[importedName];
511
+ }
512
+ function ruleForContextProperty(property) {
513
+ return CONTEXT_RULES[property];
514
+ }
515
+ function ruleForUiContextProperty(property) {
516
+ return UI_CONTEXT_RULES[property];
517
+ }
518
+ //#endregion
519
+ //#region src/module-graph.ts
520
+ const SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
521
+ ".ts",
522
+ ".tsx",
523
+ ".mts",
524
+ ".cts",
525
+ ".js",
526
+ ".jsx",
527
+ ".mjs",
528
+ ".cjs"
529
+ ]);
530
+ const MODULE_EXTENSIONS = [
531
+ ".ts",
532
+ ".tsx",
533
+ ".mts",
534
+ ".cts",
535
+ ".js",
536
+ ".jsx",
537
+ ".mjs",
538
+ ".cjs",
539
+ ".json"
540
+ ];
541
+ function sourceKind(path) {
542
+ if (path.endsWith(".js") || path.endsWith(".mjs") || path.endsWith(".cjs")) return ts.ScriptKind.JS;
543
+ if (path.endsWith(".jsx")) return ts.ScriptKind.JSX;
544
+ if (path.endsWith(".tsx")) return ts.ScriptKind.TSX;
545
+ return ts.ScriptKind.TS;
546
+ }
547
+ function literalModule(node) {
548
+ return node !== void 0 && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : void 0;
549
+ }
550
+ function isImportMetaUrl(node) {
551
+ return node !== void 0 && ts.isPropertyAccessExpression(node) && node.name.text === "url" && ts.isMetaProperty(node.expression) && node.expression.keywordToken === ts.SyntaxKind.ImportKeyword;
552
+ }
553
+ function localReferences(path, text) {
554
+ const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, sourceKind(path));
555
+ const values = /* @__PURE__ */ new Map();
556
+ const createRequireNames = /* @__PURE__ */ new Set(["createRequire"]);
557
+ const requireNames = /* @__PURE__ */ new Set(["require"]);
558
+ const add = (kind, specifier) => {
559
+ if (specifier.startsWith(".") || specifier.startsWith("#")) values.set(`${kind}:${specifier}`, {
560
+ kind,
561
+ specifier
562
+ });
563
+ };
564
+ function collectRequireAliases(node) {
565
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && (node.moduleSpecifier.text === "node:module" || node.moduleSpecifier.text === "module") && node.importClause?.namedBindings !== void 0 && ts.isNamedImports(node.importClause.namedBindings)) {
566
+ for (const element of node.importClause.namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === "createRequire") createRequireNames.add(element.name.text);
567
+ } else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer !== void 0 && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && createRequireNames.has(node.initializer.expression.text)) requireNames.add(node.name.text);
568
+ ts.forEachChild(node, collectRequireAliases);
569
+ }
570
+ collectRequireAliases(source);
571
+ function visit(node) {
572
+ if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier !== void 0 && ts.isStringLiteral(node.moduleSpecifier)) add("module", node.moduleSpecifier.text);
573
+ else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) add("module", node.moduleReference.expression.text);
574
+ else if (ts.isCallExpression(node) && node.arguments.length > 0 && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && requireNames.has(node.expression.text))) {
575
+ const specifier = literalModule(node.arguments[0]);
576
+ if (specifier !== void 0) add("module", specifier);
577
+ } else if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "URL" && isImportMetaUrl(node.arguments?.[1])) {
578
+ const specifier = literalModule(node.arguments?.[0]);
579
+ if (specifier !== void 0) add("asset", specifier);
580
+ }
581
+ ts.forEachChild(node, visit);
582
+ }
583
+ visit(source);
584
+ return [...values.values()];
585
+ }
586
+ function externalPackage(specifier) {
587
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("#") || specifier.startsWith("node:") || builtinModules.includes(specifier)) return void 0;
588
+ const parts = specifier.split("/");
589
+ return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
590
+ }
591
+ function runtimeExternalPackages(path, text) {
592
+ const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, sourceKind(path));
593
+ const packages = /* @__PURE__ */ new Set();
594
+ const createRequireNames = /* @__PURE__ */ new Set(["createRequire"]);
595
+ const requireNames = /* @__PURE__ */ new Set(["require"]);
596
+ const add = (specifier) => {
597
+ const name = externalPackage(specifier);
598
+ if (name !== void 0 && name.length > 0) packages.add(name);
599
+ };
600
+ function collectRequireAliases(node) {
601
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && (node.moduleSpecifier.text === "node:module" || node.moduleSpecifier.text === "module") && node.importClause?.namedBindings !== void 0 && ts.isNamedImports(node.importClause.namedBindings)) {
602
+ for (const element of node.importClause.namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === "createRequire") createRequireNames.add(element.name.text);
603
+ } else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer !== void 0 && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && createRequireNames.has(node.initializer.expression.text)) requireNames.add(node.name.text);
604
+ ts.forEachChild(node, collectRequireAliases);
605
+ }
606
+ collectRequireAliases(source);
607
+ function visit(node) {
608
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
609
+ const clause = node.importClause;
610
+ const named = clause?.namedBindings;
611
+ const namedImportsAreTypeOnly = named !== void 0 && ts.isNamedImports(named) && named.elements.length > 0 && named.elements.every((element) => element.isTypeOnly);
612
+ if (clause === void 0 || !clause.isTypeOnly && (clause.name !== void 0 || !namedImportsAreTypeOnly)) add(node.moduleSpecifier.text);
613
+ } else if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== void 0 && ts.isStringLiteral(node.moduleSpecifier)) add(node.moduleSpecifier.text);
614
+ else if (ts.isImportEqualsDeclaration(node) && !node.isTypeOnly && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) add(node.moduleReference.expression.text);
615
+ else if (ts.isCallExpression(node) && node.arguments.length > 0 && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && requireNames.has(node.expression.text))) {
616
+ const specifier = literalModule(node.arguments[0]);
617
+ if (specifier !== void 0) add(specifier);
618
+ }
619
+ ts.forEachChild(node, visit);
620
+ }
621
+ visit(source);
622
+ return [...packages];
623
+ }
624
+ function inside(rootDir, path) {
625
+ const pathRelative = relative(rootDir, path);
626
+ return pathRelative === "" || pathRelative !== ".." && !pathRelative.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
627
+ }
628
+ function sourceAlternates(base) {
629
+ const extension = extname(base);
630
+ const stem = extension.length > 0 ? base.slice(0, -extension.length) : base;
631
+ if (extension === ".js") return [`${stem}.ts`, `${stem}.tsx`];
632
+ if (extension === ".mjs") return [`${stem}.mts`, `${stem}.ts`];
633
+ if (extension === ".cjs") return [`${stem}.cts`, `${stem}.ts`];
634
+ if (extension === ".jsx") return [`${stem}.tsx`, `${stem}.ts`];
635
+ return [];
636
+ }
637
+ async function isFile(path) {
638
+ try {
639
+ return (await stat(path)).isFile();
640
+ } catch (error) {
641
+ if (error.code === "ENOENT") return false;
642
+ throw error;
643
+ }
644
+ }
645
+ function subpathImportTarget(rootDir, specifier, importsMap) {
646
+ const conditionValue = (value) => {
647
+ if (typeof value === "string") return value;
648
+ if (typeof value !== "object" || value === null) return void 0;
649
+ const record = value;
650
+ for (const condition of [
651
+ "import",
652
+ "node",
653
+ "default"
654
+ ]) {
655
+ const candidate = conditionValue(record[condition]);
656
+ if (candidate !== void 0) return candidate;
657
+ }
658
+ };
659
+ const direct = conditionValue(importsMap[specifier]);
660
+ if (direct !== void 0) return resolve(rootDir, direct);
661
+ for (const [pattern, value] of Object.entries(importsMap)) {
662
+ const star = pattern.indexOf("*");
663
+ if (star === -1) continue;
664
+ const prefix = pattern.slice(0, star);
665
+ const suffix = pattern.slice(star + 1);
666
+ if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) continue;
667
+ const wildcard = specifier.slice(prefix.length, specifier.length - suffix.length);
668
+ const target = conditionValue(value);
669
+ if (target !== void 0) return resolve(rootDir, target.replace("*", wildcard));
670
+ }
671
+ }
672
+ async function packageImportsMap(rootDir) {
673
+ try {
674
+ const parsed = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8"));
675
+ return typeof parsed.imports === "object" && parsed.imports !== null ? parsed.imports : {};
676
+ } catch {
677
+ return {};
678
+ }
679
+ }
680
+ async function resolveModule(fromFile, specifier, rootDir, importsMap) {
681
+ if (specifier.startsWith("#")) {
682
+ const target = subpathImportTarget(rootDir, specifier, importsMap);
683
+ if (target === void 0) throw new Error(`cannot resolve subpath import ${JSON.stringify(specifier)} through the package "imports" map`);
684
+ return resolveModule(fromFile, relative(dirname(fromFile), target).startsWith(".") ? relative(dirname(fromFile), target) : `./${relative(dirname(fromFile), target)}`, rootDir, importsMap);
685
+ }
686
+ const base = resolve(dirname(fromFile), specifier);
687
+ const candidates = [
688
+ base,
689
+ ...sourceAlternates(base),
690
+ ...extname(base) === "" ? MODULE_EXTENSIONS.map((extension) => `${base}${extension}`) : [],
691
+ ...MODULE_EXTENSIONS.map((extension) => join(base, `index${extension}`))
692
+ ];
693
+ for (const candidate of [...new Set(candidates)]) {
694
+ if (!inside(rootDir, candidate)) throw new Error(`extension import escapes the Pi package: ${specifier} from ${fromFile}`);
695
+ if (await isFile(candidate)) return candidate;
696
+ }
697
+ throw new Error(`cannot resolve local extension import ${JSON.stringify(specifier)} from ${fromFile}`);
698
+ }
699
+ async function expandAsset(path, rootDir) {
700
+ if (!inside(rootDir, path)) throw new Error(`extension asset escapes the Pi package: ${path}`);
701
+ let info;
702
+ try {
703
+ info = await lstat(path);
704
+ } catch (error) {
705
+ if (error.code !== "ENOENT") throw error;
706
+ for (const alternate of sourceAlternates(path)) if (await isFile(alternate)) return [alternate];
707
+ throw error;
708
+ }
709
+ if (info.isSymbolicLink()) throw new Error(`refusing to copy symbolic link from Pi package: ${path}`);
710
+ if (info.isFile()) return [path];
711
+ if (!info.isDirectory()) return [];
712
+ const output = [];
713
+ for (const entry of await readdir(path)) output.push(...await expandAsset(join(path, entry), rootDir));
714
+ return output;
715
+ }
716
+ async function collectLocalClosure(rootDir, entries) {
717
+ const queue = entries.map((path) => resolve(path));
718
+ const closure = /* @__PURE__ */ new Set();
719
+ const issues = [];
720
+ const importsMap = await packageImportsMap(rootDir);
721
+ while (queue.length > 0) {
722
+ const source = queue.shift();
723
+ if (closure.has(source)) continue;
724
+ if (!inside(rootDir, source)) throw new Error(`extension source escapes the Pi package: ${source}`);
725
+ const info = await lstat(source);
726
+ if (info.isSymbolicLink()) throw new Error(`refusing to copy symbolic link from Pi package: ${source}`);
727
+ if (!info.isFile()) throw new Error(`extension closure contains a non-file path: ${source}`);
728
+ closure.add(source);
729
+ if (!SCRIPT_EXTENSIONS.has(extname(source))) continue;
730
+ const text = await readFile(source, "utf8");
731
+ for (const reference of localReferences(source, text)) try {
732
+ if (reference.kind === "module") queue.push(await resolveModule(source, reference.specifier, rootDir, importsMap));
733
+ else queue.push(...await expandAsset(resolve(dirname(source), reference.specifier), rootDir));
734
+ } catch (error) {
735
+ issues.push({
736
+ file: source,
737
+ kind: reference.kind,
738
+ specifier: reference.specifier,
739
+ detail: error instanceof Error ? error.message : String(error)
740
+ });
741
+ }
742
+ }
743
+ return {
744
+ files: [...closure].sort(),
745
+ issues
746
+ };
747
+ }
748
+ //#endregion
749
+ //#region src/analyzer.ts
750
+ function literalText(node) {
751
+ return node !== void 0 && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : void 0;
752
+ }
753
+ function hasModifier(node, kind) {
754
+ return ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) === true;
755
+ }
756
+ function parameterIdentifier(node) {
757
+ const parameter = node.parameters[0];
758
+ return parameter !== void 0 && ts.isIdentifier(parameter.name) ? parameter.name.text : void 0;
759
+ }
760
+ function extensionApiReceivers(source) {
761
+ const receivers = /* @__PURE__ */ new Set();
762
+ const functions = /* @__PURE__ */ new Map();
763
+ function index(node) {
764
+ if (ts.isFunctionDeclaration(node) && node.name !== void 0) functions.set(node.name.text, node);
765
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer !== void 0 && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) functions.set(node.name.text, node.initializer);
766
+ if (ts.isParameter(node) && node.type !== void 0 && /(?:^|\.)ExtensionAPI\b/u.test(node.type.getText(source)) && ts.isIdentifier(node.name)) receivers.add(node.name.text);
767
+ if (ts.isParameter(node) && ts.isIdentifier(node.name) && /^(?:pi|extensionApi)$/iu.test(node.name.text)) receivers.add(node.name.text);
768
+ ts.forEachChild(node, index);
769
+ }
770
+ index(source);
771
+ for (const statement of source.statements) {
772
+ if (ts.isFunctionDeclaration(statement) && hasModifier(statement, ts.SyntaxKind.ExportKeyword) && hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
773
+ const name = parameterIdentifier(statement);
774
+ if (name !== void 0) receivers.add(name);
775
+ }
776
+ if (ts.isExportAssignment(statement)) {
777
+ const expression = statement.expression;
778
+ if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
779
+ const name = parameterIdentifier(expression);
780
+ if (name !== void 0) receivers.add(name);
781
+ } else if (ts.isIdentifier(expression)) {
782
+ const candidate = functions.get(expression.text);
783
+ if (candidate !== void 0) {
784
+ const name = parameterIdentifier(candidate);
785
+ if (name !== void 0) receivers.add(name);
786
+ }
787
+ }
788
+ }
789
+ if (ts.isExportDeclaration(statement) && statement.moduleSpecifier === void 0 && statement.exportClause !== void 0 && ts.isNamedExports(statement.exportClause)) for (const element of statement.exportClause.elements) {
790
+ if (element.name.text !== "default" || element.propertyName === void 0 || !ts.isIdentifier(element.propertyName)) continue;
791
+ const candidate = functions.get(element.propertyName.text);
792
+ if (candidate !== void 0) {
793
+ const name = parameterIdentifier(candidate);
794
+ if (name !== void 0) receivers.add(name);
795
+ }
796
+ }
797
+ }
798
+ return receivers;
799
+ }
800
+ function extensionApiProperties(source, receivers) {
801
+ const properties = /* @__PURE__ */ new Set();
802
+ function visit(node) {
803
+ if ((ts.isPropertyDeclaration(node) || ts.isParameter(node)) && ts.isIdentifier(node.name)) {
804
+ if (node.type !== void 0 && /(?:^|\.)(?:Pi)?ExtensionAPI\b/u.test(node.type.getText(source)) || /^(?:pi|extensionApi)$/iu.test(node.name.text)) properties.add(node.name.text);
805
+ } else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isPropertyAccessExpression(node.left) && node.left.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(node.right) && receivers.has(node.right.text)) properties.add(node.left.name.text);
806
+ ts.forEachChild(node, visit);
807
+ }
808
+ visit(source);
809
+ return properties;
810
+ }
811
+ function enclosingFunctionName(node) {
812
+ const parent = node.parent;
813
+ if (ts.isMethodDeclaration(parent) && parent.name !== void 0) return parent.name.getText();
814
+ if ((ts.isArrowFunction(parent) || ts.isFunctionExpression(parent)) && ts.isPropertyAssignment(parent.parent)) return parent.parent.name.getText();
815
+ }
816
+ function extensionContextReceivers(source) {
817
+ const receivers = /* @__PURE__ */ new Set();
818
+ function visit(node) {
819
+ if (ts.isParameter(node) && ts.isIdentifier(node.name)) {
820
+ const typedContext = node.type !== void 0 && /(?:^|\.)(?:Extension|ExtensionCommand|ToolExecution)Context\b/u.test(node.type.getText(source));
821
+ const conventionalHandlerContext = /^(?:ctx|context)$/iu.test(node.name.text) && /^(?:execute|handler)$/u.test(enclosingFunctionName(node) ?? "");
822
+ if (typedContext || conventionalHandlerContext) receivers.add(node.name.text);
823
+ }
824
+ ts.forEachChild(node, visit);
825
+ }
826
+ visit(source);
827
+ return receivers;
828
+ }
829
+ const PI_SHIMMED_PACKAGES = /* @__PURE__ */ new Set([
830
+ ...PI_CODING_AGENT_PACKAGES,
831
+ ...PI_TUI_PACKAGES,
832
+ ...PI_AI_PACKAGES
833
+ ]);
834
+ const PI_HOST_PACKAGES = /* @__PURE__ */ new Set([
835
+ ...PI_SHIMMED_PACKAGES,
836
+ "typebox",
837
+ "@sinclair/typebox"
838
+ ]);
839
+ function dependencyNames(packageJson) {
840
+ const names = /* @__PURE__ */ new Set();
841
+ for (const field of [
842
+ "dependencies",
843
+ "optionalDependencies",
844
+ "peerDependencies"
845
+ ]) {
846
+ const value = packageJson[field];
847
+ if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
848
+ for (const [name, specifier] of Object.entries(value)) if (typeof specifier === "string") names.add(name);
849
+ }
850
+ return names;
851
+ }
852
+ function pushFinding(findings, rootDir, file, source, node, capability, level, detail) {
853
+ const position = source.getLineAndCharacterOfPosition(node.getStart(source));
854
+ findings.push({
855
+ capability,
856
+ level,
857
+ file: relative(rootDir, file).replaceAll("\\", "/"),
858
+ line: position.line + 1,
859
+ detail
860
+ });
861
+ }
862
+ async function analyzeExtension(rootDir, file) {
863
+ const text = await readFile(file, "utf8");
864
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, sourceKind(file));
865
+ const findings = [];
866
+ const receivers = extensionApiReceivers(source);
867
+ const apiProperties = extensionApiProperties(source, receivers);
868
+ const contextReceivers = extensionContextReceivers(source);
869
+ const methodAliases = /* @__PURE__ */ new Map();
870
+ const eventBusAliases = /* @__PURE__ */ new Set();
871
+ const uiAliases = /* @__PURE__ */ new Set();
872
+ function reportHostImport(packageName, importedName, node) {
873
+ const matched = ruleForHostImport(packageName, importedName);
874
+ if (matched === void 0) {
875
+ pushFinding(findings, rootDir, file, source, node, `host-import(${packageName}:${importedName})`, "unsupported", `The pi2dsh host shim does not export ${JSON.stringify(importedName)} from ${JSON.stringify(packageName)}.`);
876
+ return;
877
+ }
878
+ pushFinding(findings, rootDir, file, source, node, `host-import(${packageName}:${importedName})`, matched.level, matched.detail);
879
+ }
880
+ for (const statement of source.statements) if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && PI_SHIMMED_PACKAGES.has(statement.moduleSpecifier.text)) {
881
+ const packageName = statement.moduleSpecifier.text;
882
+ const clause = statement.importClause;
883
+ if (clause === void 0) {
884
+ reportHostImport(packageName, "<side-effect>", statement);
885
+ continue;
886
+ }
887
+ if (clause.isTypeOnly) continue;
888
+ if (clause.name !== void 0) reportHostImport(packageName, "default", clause.name);
889
+ if (clause.namedBindings !== void 0 && ts.isNamespaceImport(clause.namedBindings)) reportHostImport(packageName, "*", clause.namedBindings);
890
+ else if (clause.namedBindings !== void 0) {
891
+ for (const element of clause.namedBindings.elements) if (!element.isTypeOnly) reportHostImport(packageName, element.propertyName?.text ?? element.name.text, element);
892
+ }
893
+ } else if (ts.isExportDeclaration(statement) && !statement.isTypeOnly && statement.moduleSpecifier !== void 0 && ts.isStringLiteral(statement.moduleSpecifier) && PI_HOST_PACKAGES.has(statement.moduleSpecifier.text)) {
894
+ const packageName = statement.moduleSpecifier.text;
895
+ if (statement.exportClause === void 0 || ts.isNamespaceExport(statement.exportClause)) reportHostImport(packageName, "*", statement);
896
+ else for (const element of statement.exportClause.elements) if (!element.isTypeOnly) reportHostImport(packageName, element.propertyName?.text ?? element.name.text, element);
897
+ } else if (ts.isImportEqualsDeclaration(statement) && !statement.isTypeOnly && ts.isExternalModuleReference(statement.moduleReference) && ts.isStringLiteral(statement.moduleReference.expression) && PI_HOST_PACKAGES.has(statement.moduleReference.expression.text)) reportHostImport(statement.moduleReference.expression.text, "*", statement);
898
+ const declarations = [];
899
+ const isApiReceiver = (node) => ts.isIdentifier(node) && receivers.has(node.text) || ts.isPropertyAccessExpression(node) && node.expression.kind === ts.SyntaxKind.ThisKeyword && apiProperties.has(node.name.text);
900
+ function collectDeclarations(node) {
901
+ if (ts.isVariableDeclaration(node)) declarations.push(node);
902
+ ts.forEachChild(node, collectDeclarations);
903
+ }
904
+ collectDeclarations(source);
905
+ for (let pass = 0; pass < declarations.length + 1; pass += 1) {
906
+ let changed = false;
907
+ for (const declaration of declarations) {
908
+ const initializer = declaration.initializer;
909
+ if (initializer === void 0) continue;
910
+ if (ts.isIdentifier(declaration.name) && isApiReceiver(initializer)) {
911
+ if (!receivers.has(declaration.name.text)) {
912
+ receivers.add(declaration.name.text);
913
+ changed = true;
914
+ }
915
+ }
916
+ if (ts.isIdentifier(declaration.name) && ts.isIdentifier(initializer) && contextReceivers.has(initializer.text)) {
917
+ if (!contextReceivers.has(declaration.name.text)) {
918
+ contextReceivers.add(declaration.name.text);
919
+ changed = true;
920
+ }
921
+ }
922
+ if (ts.isIdentifier(declaration.name) && ts.isPropertyAccessExpression(initializer) && initializer.name.text === "ui" && ts.isIdentifier(initializer.expression) && contextReceivers.has(initializer.expression.text) && !uiAliases.has(declaration.name.text)) {
923
+ uiAliases.add(declaration.name.text);
924
+ changed = true;
925
+ }
926
+ if (ts.isIdentifier(declaration.name) && ts.isPropertyAccessExpression(initializer) && isApiReceiver(initializer.expression)) {
927
+ if (initializer.name.text === "events") {
928
+ if (!eventBusAliases.has(declaration.name.text)) {
929
+ eventBusAliases.add(declaration.name.text);
930
+ changed = true;
931
+ }
932
+ } else if (!methodAliases.has(declaration.name.text)) {
933
+ methodAliases.set(declaration.name.text, initializer.name.text);
934
+ changed = true;
935
+ }
936
+ }
937
+ if (ts.isObjectBindingPattern(declaration.name) && isApiReceiver(initializer)) for (const element of declaration.name.elements) {
938
+ if (!ts.isIdentifier(element.name)) continue;
939
+ const method = element.propertyName !== void 0 && ts.isIdentifier(element.propertyName) ? element.propertyName.text : element.name.text;
940
+ if (method === "events") {
941
+ if (!eventBusAliases.has(element.name.text)) {
942
+ eventBusAliases.add(element.name.text);
943
+ changed = true;
944
+ }
945
+ } else if (!methodAliases.has(element.name.text)) {
946
+ methodAliases.set(element.name.text, method);
947
+ changed = true;
948
+ }
949
+ }
950
+ }
951
+ if (!changed) break;
952
+ }
953
+ function reportMethod(method, args, node) {
954
+ if (method === "on") {
955
+ const event = literalText(args[0]);
956
+ if (event === void 0) pushFinding(findings, rootDir, file, source, node, "on(<dynamic>)", "unsupported", "Dynamic event names cannot be audited or mapped safely.");
957
+ else {
958
+ const rule = ruleForEvent(event);
959
+ pushFinding(findings, rootDir, file, source, node, `on(${event})`, rule.level, rule.detail);
960
+ }
961
+ return;
962
+ }
963
+ const rule = ruleForApi(method);
964
+ if (rule === void 0) {
965
+ pushFinding(findings, rootDir, file, source, node, method, "unsupported", `Unknown ExtensionAPI method ${JSON.stringify(method)} cannot be audited or mapped safely.`);
966
+ return;
967
+ }
968
+ pushFinding(findings, rootDir, file, source, node, method, rule.level, rule.detail);
969
+ }
970
+ function reportEventBus(method, node) {
971
+ const rule = ruleForApi("events");
972
+ if ((method === "on" || method === "emit") && rule !== void 0) pushFinding(findings, rootDir, file, source, node, `events.${method}`, rule.level, rule.detail);
973
+ else pushFinding(findings, rootDir, file, source, node, `events.${method}`, "unsupported", `Unknown Pi event-bus method ${JSON.stringify(method)} cannot be mapped safely.`);
974
+ }
975
+ function reportContext(property, node) {
976
+ const matched = ruleForContextProperty(property);
977
+ if (matched === void 0) pushFinding(findings, rootDir, file, source, node, `ctx.${property}`, "unsupported", `Unknown Pi extension-context property ${JSON.stringify(property)} cannot be audited or mapped safely.`);
978
+ else pushFinding(findings, rootDir, file, source, node, `ctx.${property}`, matched.level, matched.detail);
979
+ }
980
+ function reportUiContext(property, node) {
981
+ const matched = ruleForUiContextProperty(property);
982
+ if (matched === void 0) pushFinding(findings, rootDir, file, source, node, `ctx.ui.${property}`, "unsupported", `Unknown Pi UI-context property ${JSON.stringify(property)} cannot be audited or mapped safely.`);
983
+ else pushFinding(findings, rootDir, file, source, node, `ctx.ui.${property}`, matched.level, matched.detail);
984
+ }
985
+ function visit(node) {
986
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
987
+ const method = methodAliases.get(node.expression.text);
988
+ if (method !== void 0) reportMethod(method, node.arguments, node);
989
+ } else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
990
+ const target = node.expression.expression;
991
+ if (isApiReceiver(target)) reportMethod(node.expression.name.text, node.arguments, node);
992
+ else if (ts.isIdentifier(target) && eventBusAliases.has(target.text)) reportEventBus(node.expression.name.text, node);
993
+ else if (ts.isPropertyAccessExpression(target) && target.name.text === "events" && isApiReceiver(target.expression)) reportEventBus(node.expression.name.text, node);
994
+ } else if (ts.isCallExpression(node) && ts.isElementAccessExpression(node.expression) && isApiReceiver(node.expression.expression)) {
995
+ const method = literalText(node.expression.argumentExpression);
996
+ if (method === void 0) pushFinding(findings, rootDir, file, source, node, "<dynamic-api-method>", "unsupported", "Dynamic ExtensionAPI method access cannot be audited or mapped safely.");
997
+ else reportMethod(method, node.arguments, node);
998
+ }
999
+ if (ts.isPropertyAccessExpression(node)) {
1000
+ const target = node.expression;
1001
+ if (ts.isPropertyAccessExpression(target) && target.name.text === "ui" && ts.isIdentifier(target.expression) && contextReceivers.has(target.expression.text)) reportUiContext(node.name.text, node);
1002
+ else if (ts.isIdentifier(target) && uiAliases.has(target.text)) reportUiContext(node.name.text, node);
1003
+ else if (ts.isIdentifier(target) && contextReceivers.has(target.text) && node.name.text !== "ui") reportContext(node.name.text, node);
1004
+ } else if (ts.isElementAccessExpression(node) && ts.isIdentifier(node.expression) && contextReceivers.has(node.expression.text)) {
1005
+ const property = literalText(node.argumentExpression);
1006
+ if (property === void 0) pushFinding(findings, rootDir, file, source, node, "ctx.<dynamic>", "unsupported", "Dynamic Pi extension-context access cannot be audited or mapped safely.");
1007
+ else if (property !== "ui") reportContext(property, node);
1008
+ }
1009
+ ts.forEachChild(node, visit);
1010
+ }
1011
+ visit(source);
1012
+ return findings;
1013
+ }
1014
+ async function analyzePackage(pkg) {
1015
+ const extensionClosure = await collectLocalClosure(pkg.rootDir, pkg.resources.extensions);
1016
+ const findings = (await Promise.all(extensionClosure.files.filter((file) => SCRIPT_EXTENSIONS.has(extname(file))).map((file) => analyzeExtension(pkg.rootDir, file)))).flat();
1017
+ if (pkg.resources.extensions.length > 0 && findings.length === 0) findings.push({
1018
+ capability: "static-audit",
1019
+ level: "unsupported",
1020
+ file: pkg.resources.extensions.map((file) => relative(pkg.rootDir, file).replaceAll("\\", "/")).join(", "),
1021
+ line: 1,
1022
+ detail: "No ExtensionAPI use was statically proven across the local module closure; conversion fails closed instead of claiming compatibility."
1023
+ });
1024
+ for (const issue of extensionClosure.issues) findings.push({
1025
+ capability: `${issue.kind}(${issue.specifier})`,
1026
+ level: "fatal",
1027
+ file: relative(pkg.rootDir, issue.file).replaceAll("\\", "/"),
1028
+ line: 1,
1029
+ detail: `The local extension closure is incomplete: ${issue.detail}`
1030
+ });
1031
+ const declaredDependencies = dependencyNames(pkg.packageJson);
1032
+ for (const file of extensionClosure.files.filter((candidate) => SCRIPT_EXTENSIONS.has(extname(candidate)))) {
1033
+ const text = await readFile(file, "utf8");
1034
+ for (const packageName of runtimeExternalPackages(file, text)) {
1035
+ if (PI_HOST_PACKAGES.has(packageName) || declaredDependencies.has(packageName)) continue;
1036
+ findings.push({
1037
+ capability: `undeclared-runtime-dependency(${packageName})`,
1038
+ level: "fatal",
1039
+ file: relative(pkg.rootDir, file).replaceAll("\\", "/"),
1040
+ line: 1,
1041
+ detail: `The extension imports ${JSON.stringify(packageName)} at runtime, but the Pi package does not declare it as a dependency.`
1042
+ });
1043
+ }
1044
+ }
1045
+ const resourceFinding = (file, capability, level, detail) => ({
1046
+ capability,
1047
+ level,
1048
+ file: relative(pkg.rootDir, file).replaceAll("\\", "/"),
1049
+ line: 1,
1050
+ detail
1051
+ });
1052
+ for (const file of pkg.resources.skills.filter((path) => basename(path) === "SKILL.md" || path.endsWith(".md"))) findings.push(resourceFinding(file, "skill", "full", "Copied as a DSH filesystem skill with its resource directory intact."));
1053
+ for (const file of pkg.resources.prompts) findings.push(resourceFinding(file, "prompt", "full", "Registered as a DSH slash command with Pi-compatible argument expansion."));
1054
+ for (const file of pkg.resources.themes) findings.push(resourceFinding(file, "theme", "unsupported", "Pi terminal themes have no effect in DSH Web or headless surfaces."));
1055
+ findings.sort((left, right) => left.file.localeCompare(right.file) || left.line - right.line || left.capability.localeCompare(right.capability));
1056
+ const summary = {
1057
+ full: 0,
1058
+ partial: 0,
1059
+ unsupported: 0,
1060
+ fatal: 0
1061
+ };
1062
+ for (const finding of findings) summary[finding.level] += 1;
1063
+ const verdict = summary.fatal > 0 ? "blocked" : summary.partial > 0 || summary.unsupported > 0 ? "review" : "ready";
1064
+ return {
1065
+ schemaVersion: 1,
1066
+ package: pkg.identity,
1067
+ verdict,
1068
+ summary,
1069
+ resources: {
1070
+ extensions: pkg.resources.extensions.map((file) => relative(pkg.rootDir, file).replaceAll("\\", "/")),
1071
+ skills: pkg.resources.skills.map((file) => relative(pkg.rootDir, file).replaceAll("\\", "/")),
1072
+ prompts: pkg.resources.prompts.map((file) => relative(pkg.rootDir, file).replaceAll("\\", "/")),
1073
+ themes: pkg.resources.themes.map((file) => relative(pkg.rootDir, file).replaceAll("\\", "/"))
1074
+ },
1075
+ findings
1076
+ };
1077
+ }
1078
+ //#endregion
1079
+ //#region src/generator.ts
1080
+ const SHIMMED_PI_HOST_PACKAGES = /* @__PURE__ */ new Set([
1081
+ "@earendil-works/pi-coding-agent",
1082
+ "@mariozechner/pi-coding-agent",
1083
+ "@earendil-works/pi-tui",
1084
+ "@mariozechner/pi-tui",
1085
+ "@earendil-works/pi-ai",
1086
+ "@mariozechner/pi-ai",
1087
+ "typebox",
1088
+ "@sinclair/typebox"
1089
+ ]);
1090
+ function packageSlug(name) {
1091
+ return name.replace(/^@/u, "").replaceAll("/", "-").replace(/[^a-zA-Z0-9._-]+/gu, "-").toLowerCase().replace(/^-+|-+$/gu, "") || "package";
1092
+ }
1093
+ function stringRecord$1(value) {
1094
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
1095
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
1096
+ }
1097
+ async function assertEmptyOrMissing(path) {
1098
+ try {
1099
+ if (!(await stat(path)).isDirectory()) throw new Error(`output exists and is not a directory: ${path}`);
1100
+ if ((await readdir(path)).length > 0) throw new Error(`output directory is not empty: ${path}`);
1101
+ } catch (error) {
1102
+ if (error.code !== "ENOENT") throw error;
1103
+ }
1104
+ }
1105
+ function parseFrontmatter(text) {
1106
+ const normalized = text.replace(/\r\n?/gu, "\n");
1107
+ if (!normalized.startsWith("---")) return {
1108
+ attributes: {},
1109
+ body: normalized
1110
+ };
1111
+ const endIndex = normalized.indexOf("\n---", 3);
1112
+ if (endIndex === -1) return {
1113
+ attributes: {},
1114
+ body: normalized
1115
+ };
1116
+ const raw = parse(normalized.slice(4, endIndex));
1117
+ return {
1118
+ attributes: typeof raw === "object" && raw !== null && !Array.isArray(raw) ? stringRecord$1(raw) : {},
1119
+ body: normalized.slice(endIndex + 4).trim()
1120
+ };
1121
+ }
1122
+ async function assertNoSymlinks(path) {
1123
+ const info = await lstat(path);
1124
+ if (info.isSymbolicLink()) throw new Error(`refusing to copy symbolic link from Pi package: ${path}`);
1125
+ if (!info.isDirectory()) return;
1126
+ for (const entry of await readdir(path)) await assertNoSymlinks(join(path, entry));
1127
+ }
1128
+ async function copyExtensions(pkg, outDir) {
1129
+ const copied = [];
1130
+ const runtimePackages = /* @__PURE__ */ new Set();
1131
+ const closure = await collectLocalClosure(pkg.rootDir, pkg.resources.extensions);
1132
+ if (closure.issues.length > 0) throw new Error(`cannot snapshot extension closure:\n${closure.issues.map((issue) => `- ${issue.file}: ${issue.detail}`).join("\n")}`);
1133
+ for (const source of closure.files) {
1134
+ if (SCRIPT_EXTENSIONS.has(extname(source))) {
1135
+ const text = await readFile(source, "utf8");
1136
+ for (const packageName of runtimeExternalPackages(source, text)) runtimePackages.add(packageName);
1137
+ }
1138
+ const targetRelative = `vendor/${relative(pkg.rootDir, source).replaceAll("\\", "/")}`;
1139
+ const target = join(outDir, targetRelative);
1140
+ await mkdir(dirname(target), { recursive: true });
1141
+ await cp(source, target, { dereference: false });
1142
+ if (pkg.resources.extensions.map((entry) => resolve(entry)).includes(source)) copied.push(targetRelative);
1143
+ }
1144
+ for (const source of pkg.resources.skills) {
1145
+ if (!SCRIPT_EXTENSIONS.has(extname(source))) continue;
1146
+ const text = await readFile(source, "utf8");
1147
+ for (const packageName of runtimeExternalPackages(source, text)) runtimePackages.add(packageName);
1148
+ }
1149
+ return {
1150
+ entries: copied,
1151
+ runtimePackages
1152
+ };
1153
+ }
1154
+ async function copySkills(pkg, outDir) {
1155
+ const entryFiles = pkg.resources.skills.filter((file) => basename(file) === "SKILL.md" || file.endsWith(".md"));
1156
+ if (entryFiles.length === 0) return [];
1157
+ const names = /* @__PURE__ */ new Set();
1158
+ for (const entry of entryFiles) {
1159
+ const isBundle = basename(entry) === "SKILL.md";
1160
+ let name = isBundle ? basename(dirname(entry)) : basename(entry, ".md");
1161
+ if (names.has(name)) {
1162
+ const candidate = `${basename(dirname(isBundle ? dirname(entry) : entry))}-${name}`.replace(/[^a-zA-Z0-9._-]+/gu, "-");
1163
+ name = names.has(candidate) ? `${candidate}-${names.size}` : candidate;
1164
+ }
1165
+ names.add(name);
1166
+ const target = join(outDir, "skills", isBundle ? name : `${name}.md`);
1167
+ await mkdir(dirname(target), { recursive: true });
1168
+ const source = isBundle ? dirname(entry) : entry;
1169
+ await assertNoSymlinks(source);
1170
+ await cp(source, target, {
1171
+ recursive: isBundle,
1172
+ dereference: false
1173
+ });
1174
+ }
1175
+ return ["skills"];
1176
+ }
1177
+ async function copyPrompts(pkg, outDir) {
1178
+ const prompts = [];
1179
+ const names = /* @__PURE__ */ new Set();
1180
+ for (const source of pkg.resources.prompts) {
1181
+ const name = basename(source, ".md").toLowerCase().replace(/[^a-z0-9_-]+/gu, "-");
1182
+ if (names.has(name)) throw new Error(`prompt command name collision while flattening Pi package: ${name}`);
1183
+ names.add(name);
1184
+ const targetRelative = `prompts/${name}.md`;
1185
+ const target = join(outDir, targetRelative);
1186
+ await mkdir(dirname(target), { recursive: true });
1187
+ await assertNoSymlinks(source);
1188
+ await cp(source, target, { dereference: false });
1189
+ const { attributes, body } = parseFrontmatter(await readFile(source, "utf8"));
1190
+ const firstLine = body.split(/\r?\n/u).map((line) => line.trim()).find(Boolean);
1191
+ prompts.push({
1192
+ name,
1193
+ description: attributes.description ?? firstLine ?? `Run migrated Pi prompt ${name}`,
1194
+ ...attributes["argument-hint"] !== void 0 ? { argumentHint: attributes["argument-hint"] } : {},
1195
+ path: targetRelative
1196
+ });
1197
+ }
1198
+ return prompts;
1199
+ }
1200
+ async function copyNotices(pkg, outDir) {
1201
+ const copied = [];
1202
+ for (const entry of await readdir(pkg.rootDir)) {
1203
+ if (!/^(?:licen[cs]e|notice|copying)(?:[._-].*)?$/iu.test(entry)) continue;
1204
+ const source = join(pkg.rootDir, entry);
1205
+ const info = await lstat(source);
1206
+ if (info.isSymbolicLink()) throw new Error(`refusing to copy symbolic link from Pi package: ${source}`);
1207
+ if (!info.isFile()) continue;
1208
+ await cp(source, join(outDir, entry), { dereference: false });
1209
+ copied.push(entry);
1210
+ }
1211
+ return copied.sort();
1212
+ }
1213
+ function generatedPackageJson(pkg, generatedName, runtimeSpec, runtimePackages, hasSkills) {
1214
+ const declaredDependencies = {
1215
+ ...stringRecord$1(pkg.packageJson.dependencies),
1216
+ ...stringRecord$1(pkg.packageJson.optionalDependencies),
1217
+ ...stringRecord$1(pkg.packageJson.peerDependencies)
1218
+ };
1219
+ const externalRuntimePackages = [...runtimePackages].filter((name) => !SHIMMED_PI_HOST_PACKAGES.has(name));
1220
+ const missing = externalRuntimePackages.filter((name) => declaredDependencies[name] === void 0);
1221
+ if (missing.length > 0) throw new Error(`runtime dependencies are imported but not declared by the Pi package: ${missing.join(", ")}`);
1222
+ const declaredRuntime = {
1223
+ ...stringRecord$1(pkg.packageJson.dependencies),
1224
+ ...stringRecord$1(pkg.packageJson.optionalDependencies)
1225
+ };
1226
+ const dependencies = {
1227
+ ...Object.fromEntries(Object.entries(declaredRuntime).filter(([name]) => !SHIMMED_PI_HOST_PACKAGES.has(name))),
1228
+ ...Object.fromEntries(externalRuntimePackages.sort().map((name) => [name, declaredDependencies[name]])),
1229
+ ...runtimeSpec === void 0 ? {
1230
+ jiti: "^2.7.0",
1231
+ "get-east-asian-width": "^1.6.0",
1232
+ marked: "^16.4.1",
1233
+ typebox: "^1.0.4"
1234
+ } : {},
1235
+ ...hasSkills ? { "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.6" } : {},
1236
+ ...runtimeSpec !== void 0 ? { pi2dsh: runtimeSpec } : {}
1237
+ };
1238
+ const remapImports = (value) => {
1239
+ if (typeof value === "string") return value.startsWith("./") ? `./vendor/${value.slice(2)}` : value;
1240
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, remapImports(entry)]));
1241
+ return value;
1242
+ };
1243
+ const sourceImports = pkg.packageJson.imports;
1244
+ return {
1245
+ name: generatedName,
1246
+ version: pkg.identity.version,
1247
+ description: `DeepSeek Harness adapter generated from ${pkg.identity.name}`,
1248
+ type: "module",
1249
+ main: "./index.js",
1250
+ ...typeof sourceImports === "object" && sourceImports !== null ? { imports: remapImports(sourceImports) } : {},
1251
+ files: [
1252
+ "index.js",
1253
+ "cordis.patch.yml",
1254
+ "pi2dsh.manifest.json",
1255
+ "pi2dsh.report.json",
1256
+ "README.md",
1257
+ "LICENSE*",
1258
+ "NOTICE*",
1259
+ "COPYING*",
1260
+ "PI2DSH-LICENSE",
1261
+ "runtime",
1262
+ "vendor",
1263
+ "skills",
1264
+ "prompts"
1265
+ ],
1266
+ dependencies,
1267
+ ...runtimeSpec === void 0 ? { peerDependencies: {
1268
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
1269
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6"
1270
+ } } : {},
1271
+ keywords: [
1272
+ "dsh-plugin",
1273
+ "deepseek-harness",
1274
+ "pi-package",
1275
+ "pi2dsh"
1276
+ ],
1277
+ license: typeof pkg.packageJson.license === "string" ? pkg.packageJson.license : "UNLICENSED",
1278
+ dsh: { bundle: { patch: "./cordis.patch.yml" } }
1279
+ };
1280
+ }
1281
+ function generatedReadme(pkg, packageName, report) {
1282
+ return `# ${packageName}\n\nGenerated by [pi2dsh](https://github.com/weijiafu14/pi2dsh) from \`${pkg.identity.name}@${pkg.identity.version}\`.\n\nCompatibility verdict: **${report.verdict}** (full ${report.summary.full}, partial ${report.summary.partial}, unsupported ${report.summary.unsupported}).\n\nReview \`pi2dsh.report.json\` before installation. This bundle executes the original Pi extension source and should only be installed when that source is trusted.\n\nInstall with DeepSeek Harness (keep the \`file:\` prefix so pnpm installs this bundle's dependencies instead of creating a bare link):\n\n\`\`\`sh\ndsh plugin --profile headless add file:$PWD\ndsh --profile headless --dump-config\n\`\`\`\n`;
1283
+ }
1284
+ function pluginSource(manifest, runtimeImport) {
1285
+ const injections = ["tools", "systemPrompt"];
1286
+ if (manifest.prompts.length > 0 || manifest.report?.findings.some((item) => item.capability === "registerCommand") === true) injections.push("commands");
1287
+ if (manifest.skillDirs.length > 0) injections.push("skills");
1288
+ return `import { applyPiPackage } from ${JSON.stringify(runtimeImport)}\n\nexport const name = ${JSON.stringify(`pi2dsh:${packageSlug(manifest.package.name)}`)}\nexport const inject = ${JSON.stringify(injections)}\n\nconst manifest = ${JSON.stringify(manifest, null, 2)}\n\nexport async function apply(ctx, config = {}) {\n await applyPiPackage(ctx, { rootUrl: new URL('.', import.meta.url), manifest, config })\n}\n`;
1289
+ }
1290
+ async function firstExisting(paths) {
1291
+ for (const path of paths) try {
1292
+ await stat(path);
1293
+ return path;
1294
+ } catch {}
1295
+ throw new Error(`cannot locate pi2dsh runtime artifact; tried: ${paths.join(", ")}`);
1296
+ }
1297
+ async function copyEmbeddedRuntime(outDir) {
1298
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
1299
+ const runtimeSource = await firstExisting([join(moduleDir, "runtime.mjs"), join(moduleDir, "../dist/runtime.mjs")]);
1300
+ const runtimeRoot = dirname(runtimeSource);
1301
+ const targetRoot = join(outDir, "runtime");
1302
+ await mkdir(targetRoot, { recursive: true });
1303
+ for (const entry of await readdir(runtimeRoot)) if (entry.endsWith(".mjs")) await cp(join(runtimeRoot, entry), join(targetRoot, entry));
1304
+ for (const sub of ["compat", join("compat", "vendor")]) {
1305
+ const sourceDir = join(runtimeRoot, sub);
1306
+ try {
1307
+ const entries = await readdir(sourceDir);
1308
+ await mkdir(join(targetRoot, sub), { recursive: true });
1309
+ for (const entry of entries) if (entry.endsWith(".mjs")) await cp(join(sourceDir, entry), join(targetRoot, sub, entry));
1310
+ } catch {}
1311
+ }
1312
+ await cp(join(targetRoot, "runtime.mjs"), join(targetRoot, "pi2dsh-runtime.mjs"));
1313
+ const license = await firstExisting([join(moduleDir, "../LICENSE"), join(moduleDir, "../../LICENSE")]);
1314
+ await cp(license, join(outDir, "PI2DSH-LICENSE"));
1315
+ }
1316
+ function patchSource(generatedName, slug) {
1317
+ return `- insert:\n - id: pi2dsh-${slug}\n name: ${JSON.stringify(generatedName)}\n`;
1318
+ }
1319
+ function enforceReport(report, options) {
1320
+ if (report.summary.fatal > 0) throw new Error(`conversion blocked: ${report.summary.fatal} fatal finding(s) — the bundle cannot be built or trusted; run inspect for details`);
1321
+ if (options.strict && (report.summary.partial > 0 || report.summary.unsupported > 0)) throw new Error("strict conversion requires every detected Pi API use to have full compatibility");
1322
+ }
1323
+ async function generateBundle(pkg, options) {
1324
+ const outDir = resolve(options.outDir);
1325
+ const report = await analyzePackage(pkg);
1326
+ enforceReport(report, options);
1327
+ await assertEmptyOrMissing(outDir);
1328
+ await mkdir(outDir, { recursive: true });
1329
+ const slug = packageSlug(pkg.identity.name);
1330
+ const packageName = `dsh-pi-${slug}`;
1331
+ const extensionSnapshot = await copyExtensions(pkg, outDir);
1332
+ const skillDirs = await copySkills(pkg, outDir);
1333
+ const prompts = await copyPrompts(pkg, outDir);
1334
+ await copyNotices(pkg, outDir);
1335
+ const manifest = {
1336
+ schemaVersion: 1,
1337
+ package: pkg.identity,
1338
+ extensions: extensionSnapshot.entries,
1339
+ skillDirs,
1340
+ prompts,
1341
+ report
1342
+ };
1343
+ const runtimeSpec = options.runtimeSpec;
1344
+ if (runtimeSpec === void 0) await copyEmbeddedRuntime(outDir);
1345
+ await Promise.all([
1346
+ writeFile(join(outDir, "package.json"), `${JSON.stringify(generatedPackageJson(pkg, packageName, runtimeSpec, extensionSnapshot.runtimePackages, skillDirs.length > 0), null, 2)}\n`),
1347
+ writeFile(join(outDir, "pi2dsh.manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`),
1348
+ writeFile(join(outDir, "pi2dsh.report.json"), `${JSON.stringify(report, null, 2)}\n`),
1349
+ writeFile(join(outDir, "index.js"), pluginSource(manifest, runtimeSpec === void 0 ? "./runtime/pi2dsh-runtime.mjs" : "pi2dsh/runtime")),
1350
+ writeFile(join(outDir, "cordis.patch.yml"), patchSource(packageName, slug)),
1351
+ writeFile(join(outDir, "README.md"), generatedReadme(pkg, packageName, report))
1352
+ ]);
1353
+ return {
1354
+ outDir,
1355
+ report,
1356
+ packageName
1357
+ };
1358
+ }
1359
+ //#endregion
1360
+ //#region src/mcp-config.ts
1361
+ /** Pi's documented config precedence, lowest to highest. */
1362
+ function piMcpConfigPaths(cwd) {
1363
+ const home = homedir();
1364
+ return [
1365
+ join(home, ".config", "mcp", "mcp.json"),
1366
+ join(home, ".agents", "mcp.json"),
1367
+ join(home, ".agents", "mcp", "mcp.json"),
1368
+ join(getAgentDir(), "mcp.json"),
1369
+ join(cwd, ".mcp.json"),
1370
+ join(cwd, ".pi", "mcp.json")
1371
+ ];
1372
+ }
1373
+ function stringRecord(value) {
1374
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1375
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
1376
+ return entries.length === 0 ? void 0 : Object.fromEntries(entries);
1377
+ }
1378
+ function parseServer(name, raw, sourcePath) {
1379
+ if (typeof raw !== "object" || raw === null) return void 0;
1380
+ const record = raw;
1381
+ const disabled = record.disabled === true;
1382
+ if (typeof record.url === "string") return {
1383
+ name,
1384
+ sourcePath,
1385
+ transport: "streamable-http",
1386
+ url: record.url,
1387
+ ...stringRecord(record.headers) === void 0 ? {} : { headers: stringRecord(record.headers) },
1388
+ disabled
1389
+ };
1390
+ if (typeof record.command === "string") return {
1391
+ name,
1392
+ sourcePath,
1393
+ transport: "stdio",
1394
+ command: record.command,
1395
+ ...Array.isArray(record.args) ? { args: record.args.map(String) } : {},
1396
+ ...stringRecord(record.env) === void 0 ? {} : { env: stringRecord(record.env) },
1397
+ ...typeof record.cwd === "string" ? { cwd: record.cwd } : {},
1398
+ disabled
1399
+ };
1400
+ }
1401
+ function collectPiMcpServers(cwd, extraPaths = []) {
1402
+ const servers = /* @__PURE__ */ new Map();
1403
+ const sources = [];
1404
+ for (const path of [...piMcpConfigPaths(cwd), ...extraPaths]) {
1405
+ if (!existsSync(path)) continue;
1406
+ let parsed;
1407
+ try {
1408
+ parsed = JSON.parse(readFileSync(path, "utf8"));
1409
+ } catch {
1410
+ continue;
1411
+ }
1412
+ const mcpServers = parsed?.mcpServers;
1413
+ if (typeof mcpServers !== "object" || mcpServers === null) continue;
1414
+ sources.push(path);
1415
+ for (const [name, raw] of Object.entries(mcpServers)) {
1416
+ const record = raw;
1417
+ const existing = servers.get(name);
1418
+ if (existing !== void 0 && typeof record === "object" && record !== null && record.command === void 0 && record.url === void 0 && typeof record.disabled === "boolean") {
1419
+ servers.set(name, {
1420
+ ...existing,
1421
+ disabled: record.disabled,
1422
+ sourcePath: path
1423
+ });
1424
+ continue;
1425
+ }
1426
+ const server = parseServer(name, raw, path);
1427
+ if (server !== void 0) servers.set(name, server);
1428
+ }
1429
+ }
1430
+ return {
1431
+ servers,
1432
+ sources
1433
+ };
1434
+ }
1435
+ const SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,32}$/u;
1436
+ const ENV_REFERENCE_PATTERN = /^\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?$/u;
1437
+ const SECRETISH_KEY_PATTERN = /token|secret|key|password|credential/iu;
1438
+ function js(expression) {
1439
+ return { __pi2dshJs: expression };
1440
+ }
1441
+ function convertValueMap(server, kind, values, warnings) {
1442
+ if (values === void 0) return void 0;
1443
+ const output = {};
1444
+ for (const [key, value] of Object.entries(values)) {
1445
+ const reference = ENV_REFERENCE_PATTERN.exec(value);
1446
+ if (reference !== null) {
1447
+ output[key] = js(`process.env.${reference[1]}`);
1448
+ continue;
1449
+ }
1450
+ if (SECRETISH_KEY_PATTERN.test(key)) warnings.push({
1451
+ server,
1452
+ message: `${kind}.${key} carries a literal value; move the secret to an environment variable and reference it as $NAME so the patch stays credential-free`
1453
+ });
1454
+ output[key] = value;
1455
+ }
1456
+ return output;
1457
+ }
1458
+ function convertPiMcpConfig(cwd, extraPaths = []) {
1459
+ const { servers, sources } = collectPiMcpServers(cwd, extraPaths);
1460
+ const warnings = [];
1461
+ const entries = [];
1462
+ for (const server of servers.values()) {
1463
+ if (server.disabled) {
1464
+ warnings.push({
1465
+ server: server.name,
1466
+ message: "skipped: disabled in Pi configuration"
1467
+ });
1468
+ continue;
1469
+ }
1470
+ if (!SERVER_NAME_PATTERN.test(server.name)) {
1471
+ warnings.push({
1472
+ server: server.name,
1473
+ message: "skipped: DSH serverName must match [A-Za-z0-9_-]{1,32}; rename the server in the Pi config"
1474
+ });
1475
+ continue;
1476
+ }
1477
+ const config = {
1478
+ serverName: server.name,
1479
+ transport: server.transport
1480
+ };
1481
+ if (server.transport === "stdio") {
1482
+ config.command = server.command;
1483
+ if (server.args !== void 0) config.args = server.args;
1484
+ const env = convertValueMap(server.name, "env", server.env, warnings);
1485
+ if (env !== void 0) config.env = env;
1486
+ if (server.cwd !== void 0) config.cwd = server.cwd;
1487
+ } else {
1488
+ config.url = server.url;
1489
+ const headers = convertValueMap(server.name, "headers", server.headers, warnings);
1490
+ if (headers !== void 0) config.headers = headers;
1491
+ }
1492
+ entries.push({
1493
+ id: `mcp-${server.name}`,
1494
+ name: "@deepseek-ai/dsh-mcp-client",
1495
+ config
1496
+ });
1497
+ }
1498
+ return {
1499
+ servers: [...servers.values()],
1500
+ entries,
1501
+ warnings,
1502
+ sources
1503
+ };
1504
+ }
1505
+ function yamlScalar(value, indent) {
1506
+ if (typeof value === "object" && value !== null && "__pi2dshJs" in value) return `!!js ${value.__pi2dshJs}`;
1507
+ if (typeof value === "string") return /^[A-Za-z0-9@._\/:-]+$/u.test(value) ? value : JSON.stringify(value);
1508
+ if (Array.isArray(value)) {
1509
+ if (value.length === 0) return "[]";
1510
+ return `\n${value.map((item) => `${indent} - ${yamlScalar(item, `${indent} `)}`).join("\n")}`;
1511
+ }
1512
+ if (typeof value === "object" && value !== null) {
1513
+ const record = value;
1514
+ const keys = Object.keys(record);
1515
+ if (keys.length === 0) return "{}";
1516
+ return `\n${keys.map((key) => `${indent} ${key}: ${yamlScalar(record[key], `${indent} `)}`).join("\n")}`;
1517
+ }
1518
+ return String(value);
1519
+ }
1520
+ /** Render the converted entries as a cordis.patch.yml `insert` block. */
1521
+ function renderMcpPatch(result) {
1522
+ if (result.entries.length === 0) return "# no enabled MCP servers found in Pi configuration\n";
1523
+ const lines = ["- insert:"];
1524
+ for (const entry of result.entries) {
1525
+ lines.push(` - id: ${String(entry.id)}`);
1526
+ lines.push(` name: '@deepseek-ai/dsh-mcp-client'`);
1527
+ lines.push(` inject: [tools]`);
1528
+ lines.push(` config:${yamlScalar(entry.config, " ")}`);
1529
+ }
1530
+ return `${lines.join("\n")}\n`;
1531
+ }
1532
+ //#endregion
1533
+ export { ruleForHostImport as _, analyzePackage as a, EVENT_RULES as c, PI_CODING_AGENT_PACKAGES as d, PI_TUI_PACKAGES as f, ruleForEvent as g, ruleForContextProperty as h, generateBundle as i, HOST_IMPORT_RULES as l, ruleForApi as m, convertPiMcpConfig as n, API_RULES as o, UI_CONTEXT_RULES as p, renderMcpPatch as r, CONTEXT_RULES as s, collectPiMcpServers as t, PI_AI_PACKAGES as u, ruleForUiContextProperty as v };
1534
+
1535
+ //# sourceMappingURL=mcp-config-jL9w70It.mjs.map