@docyrus/docyrus 0.0.20 → 0.0.21

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 (65) hide show
  1. package/agent-loader.js +32 -1
  2. package/agent-loader.js.map +2 -2
  3. package/main.js +321 -70
  4. package/main.js.map +4 -4
  5. package/package.json +12 -2
  6. package/resources/chrome-tools/browser-content.js +103 -0
  7. package/resources/chrome-tools/browser-cookies.js +35 -0
  8. package/resources/chrome-tools/browser-eval.js +53 -0
  9. package/resources/chrome-tools/browser-hn-scraper.js +108 -0
  10. package/resources/chrome-tools/browser-nav.js +44 -0
  11. package/resources/chrome-tools/browser-pick.js +162 -0
  12. package/resources/chrome-tools/browser-screenshot.js +34 -0
  13. package/resources/chrome-tools/browser-start.js +86 -0
  14. package/resources/pi-agent/extensions/answer.ts +532 -0
  15. package/resources/pi-agent/extensions/context.ts +578 -0
  16. package/resources/pi-agent/extensions/control.ts +1779 -0
  17. package/resources/pi-agent/extensions/diff.ts +218 -0
  18. package/resources/pi-agent/extensions/files.ts +199 -0
  19. package/resources/pi-agent/extensions/loop.ts +446 -0
  20. package/resources/pi-agent/extensions/multi-edit.ts +835 -0
  21. package/resources/pi-agent/extensions/notify.ts +88 -0
  22. package/resources/pi-agent/extensions/pi-mcp-adapter/CHANGELOG.md +192 -0
  23. package/resources/pi-agent/extensions/pi-mcp-adapter/LICENSE +21 -0
  24. package/resources/pi-agent/extensions/pi-mcp-adapter/README.md +296 -0
  25. package/resources/pi-agent/extensions/pi-mcp-adapter/app-bridge.bundle.js +67 -0
  26. package/resources/pi-agent/extensions/pi-mcp-adapter/cli.js +108 -0
  27. package/resources/pi-agent/extensions/pi-mcp-adapter/commands.ts +211 -0
  28. package/resources/pi-agent/extensions/pi-mcp-adapter/config.ts +227 -0
  29. package/resources/pi-agent/extensions/pi-mcp-adapter/consent-manager.ts +64 -0
  30. package/resources/pi-agent/extensions/pi-mcp-adapter/direct-tools.ts +301 -0
  31. package/resources/pi-agent/extensions/pi-mcp-adapter/errors.ts +219 -0
  32. package/resources/pi-agent/extensions/pi-mcp-adapter/glimpse-ui.ts +80 -0
  33. package/resources/pi-agent/extensions/pi-mcp-adapter/host-html-template.ts +427 -0
  34. package/resources/pi-agent/extensions/pi-mcp-adapter/index.ts +232 -0
  35. package/resources/pi-agent/extensions/pi-mcp-adapter/init.ts +319 -0
  36. package/resources/pi-agent/extensions/pi-mcp-adapter/lifecycle.ts +93 -0
  37. package/resources/pi-agent/extensions/pi-mcp-adapter/logger.ts +169 -0
  38. package/resources/pi-agent/extensions/pi-mcp-adapter/mcp-panel.ts +713 -0
  39. package/resources/pi-agent/extensions/pi-mcp-adapter/metadata-cache.ts +191 -0
  40. package/resources/pi-agent/extensions/pi-mcp-adapter/npx-resolver.ts +419 -0
  41. package/resources/pi-agent/extensions/pi-mcp-adapter/oauth-handler.ts +56 -0
  42. package/resources/pi-agent/extensions/pi-mcp-adapter/package.json +85 -0
  43. package/resources/pi-agent/extensions/pi-mcp-adapter/paths.ts +29 -0
  44. package/resources/pi-agent/extensions/pi-mcp-adapter/proxy-modes.ts +635 -0
  45. package/resources/pi-agent/extensions/pi-mcp-adapter/resource-tools.ts +17 -0
  46. package/resources/pi-agent/extensions/pi-mcp-adapter/server-manager.ts +330 -0
  47. package/resources/pi-agent/extensions/pi-mcp-adapter/state.ts +41 -0
  48. package/resources/pi-agent/extensions/pi-mcp-adapter/tool-metadata.ts +144 -0
  49. package/resources/pi-agent/extensions/pi-mcp-adapter/tool-registrar.ts +46 -0
  50. package/resources/pi-agent/extensions/pi-mcp-adapter/types.ts +367 -0
  51. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-resource-handler.ts +145 -0
  52. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-server.ts +623 -0
  53. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-session.ts +384 -0
  54. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-stream-types.ts +89 -0
  55. package/resources/pi-agent/extensions/pi-mcp-adapter/utils.ts +75 -0
  56. package/resources/pi-agent/extensions/prompt-editor.ts +1315 -0
  57. package/resources/pi-agent/extensions/prompt-url-widget.ts +158 -0
  58. package/resources/pi-agent/extensions/redraws.ts +24 -0
  59. package/resources/pi-agent/extensions/review.ts +2160 -0
  60. package/resources/pi-agent/extensions/todos.ts +2076 -0
  61. package/resources/pi-agent/extensions/tps.ts +47 -0
  62. package/resources/pi-agent/extensions/whimsical.ts +474 -0
  63. package/resources/pi-agent/skills/changelog-generator/SKILL.md +425 -0
  64. package/resources/pi-agent/skills/docyrus-chrome-devtools-cli/SKILL.md +80 -0
  65. package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +51 -0
@@ -0,0 +1,367 @@
1
+ // types.ts - Core type definitions
2
+ import type { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import type { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
4
+ import type { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import type { TextContent, ImageContent } from "@mariozechner/pi-ai";
6
+
7
+ // Transport type (stdio + HTTP)
8
+ export type Transport =
9
+ | StdioClientTransport
10
+ | SSEClientTransport
11
+ | StreamableHTTPClientTransport;
12
+
13
+ // Import sources for config
14
+ export type ImportKind =
15
+ | "cursor"
16
+ | "claude-code"
17
+ | "claude-desktop"
18
+ | "codex"
19
+ | "windsurf"
20
+ | "vscode";
21
+
22
+ // Tool definition from MCP server
23
+ export interface McpTool {
24
+ name: string;
25
+ title?: string;
26
+ description?: string;
27
+ inputSchema?: unknown; // JSON Schema
28
+ _meta?: Record<string, unknown>;
29
+ }
30
+
31
+ // Resource definition from MCP server
32
+ export interface McpResource {
33
+ uri: string;
34
+ name: string;
35
+ description?: string;
36
+ mimeType?: string;
37
+ _meta?: Record<string, unknown>;
38
+ }
39
+
40
+ export interface UiResourceMeta {
41
+ csp?: UiResourceCsp;
42
+ permissions?: UiResourcePermissions;
43
+ domain?: string;
44
+ prefersBorder?: boolean;
45
+ }
46
+
47
+ export interface UiResourceContent {
48
+ uri: string;
49
+ html: string;
50
+ mimeType?: string;
51
+ meta: UiResourceMeta;
52
+ }
53
+
54
+ export interface UiProxyRequestBody<TParams> {
55
+ token: string;
56
+ params: TParams;
57
+ }
58
+
59
+ export interface UiProxyResult<T = Record<string, unknown>> {
60
+ ok: boolean;
61
+ result?: T;
62
+ error?: string;
63
+ }
64
+
65
+ export interface UiResourceCsp {
66
+ connectDomains?: string[];
67
+ scriptDomains?: string[];
68
+ styleDomains?: string[];
69
+ fontDomains?: string[];
70
+ imgDomains?: string[];
71
+ mediaDomains?: string[];
72
+ frameDomains?: string[];
73
+ workerDomains?: string[];
74
+ baseUriDomains?: string[];
75
+ }
76
+
77
+ export interface UiResourcePermissions {
78
+ camera?: {};
79
+ microphone?: {};
80
+ geolocation?: {};
81
+ clipboardWrite?: {};
82
+ }
83
+
84
+ export interface UiToolInfo {
85
+ id?: string | number;
86
+ tool: {
87
+ name: string;
88
+ description?: string;
89
+ inputSchema?: unknown;
90
+ };
91
+ }
92
+
93
+ export interface UiHostContext {
94
+ toolInfo?: UiToolInfo;
95
+ theme?: "light" | "dark";
96
+ styles?: Record<string, unknown>;
97
+ displayMode?: UiDisplayMode;
98
+ availableDisplayModes?: UiDisplayMode[];
99
+ containerDimensions?: {
100
+ width?: number;
101
+ maxWidth?: number;
102
+ height?: number;
103
+ maxHeight?: number;
104
+ };
105
+ [key: string]: unknown;
106
+ }
107
+
108
+ export type UiDisplayMode = "inline" | "fullscreen" | "pip";
109
+
110
+ // Re-export stream types from the shared lightweight module.
111
+ // This allows the example package to import stream schemas without pulling the full types.ts dependency graph.
112
+ export {
113
+ UI_STREAM_HOST_CONTEXT_KEY,
114
+ UI_STREAM_REQUEST_META_KEY,
115
+ UI_STREAM_RESULT_PATCH_METHOD,
116
+ SERVER_STREAM_RESULT_PATCH_METHOD,
117
+ UI_STREAM_STRUCTURED_CONTENT_KEY,
118
+ uiStreamModeSchema,
119
+ visualizationStreamPhaseSchema,
120
+ visualizationStreamFrameTypeSchema,
121
+ visualizationStreamStatusSchema,
122
+ uiStreamHostContextSchema,
123
+ visualizationStreamEnvelopeSchema,
124
+ uiStreamCallToolResultSchema,
125
+ uiStreamResultPatchNotificationSchema,
126
+ serverStreamResultPatchNotificationSchema,
127
+ getUiStreamHostContext,
128
+ getVisualizationStreamEnvelope,
129
+ type UiStreamMode,
130
+ type VisualizationStreamPhase,
131
+ type VisualizationStreamFrameType,
132
+ type VisualizationStreamStatus,
133
+ type UiStreamHostContext,
134
+ type VisualizationStreamEnvelope,
135
+ type UiStreamCallToolResult,
136
+ type UiStreamResultPatchNotification,
137
+ type ServerStreamResultPatchNotification,
138
+ type UiStreamSummary,
139
+ } from "./ui-stream-types.js";
140
+
141
+ export interface UiMessageParams {
142
+ role?: string;
143
+ content?: unknown[];
144
+ type?: "prompt" | "notify" | "intent" | "message";
145
+ message?: string;
146
+ prompt?: string;
147
+ intent?: string;
148
+ params?: Record<string, unknown>;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ /**
153
+ * Extract prompt text from either legacy MCP UI message shapes or native AppBridge user messages.
154
+ */
155
+ export function extractUiPromptText(params: UiMessageParams): string | undefined {
156
+ if (params.type === "prompt" || params.prompt) {
157
+ const prompt = params.prompt ?? String(params.message ?? "");
158
+ return prompt || undefined;
159
+ }
160
+
161
+ if (params.role === "user" && Array.isArray(params.content)) {
162
+ const text = params.content
163
+ .map((block) => (block && typeof block === "object" && "text" in block ? String((block as { text?: unknown }).text ?? "") : ""))
164
+ .filter(Boolean)
165
+ .join("\n\n");
166
+ return text || undefined;
167
+ }
168
+
169
+ return undefined;
170
+ }
171
+
172
+ /**
173
+ * Structured UI handoff recovered from a canonical prompt envelope.
174
+ */
175
+ export interface UiPromptHandoff {
176
+ intent: string;
177
+ params: Record<string, unknown>;
178
+ raw: string;
179
+ }
180
+
181
+ /**
182
+ * Parse a canonical named UI handoff encoded as `intent\n{json}`.
183
+ */
184
+ export function parseUiPromptHandoff(prompt: string): UiPromptHandoff | undefined {
185
+ const newlineIndex = prompt.indexOf("\n");
186
+ if (newlineIndex <= 0) {
187
+ return undefined;
188
+ }
189
+
190
+ const intent = prompt.slice(0, newlineIndex).trim();
191
+ const payloadText = prompt.slice(newlineIndex + 1).trim();
192
+ if (!intent || !payloadText) {
193
+ return undefined;
194
+ }
195
+
196
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(intent)) {
197
+ return undefined;
198
+ }
199
+
200
+ try {
201
+ const parsed = JSON.parse(payloadText);
202
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
203
+ return undefined;
204
+ }
205
+ return {
206
+ intent,
207
+ params: parsed as Record<string, unknown>,
208
+ raw: prompt,
209
+ };
210
+ } catch {
211
+ return undefined;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Accumulated messages from a UI session.
217
+ * Collected during the session and available when it ends.
218
+ */
219
+ export interface UiSessionMessages {
220
+ prompts: string[];
221
+ notifications: string[];
222
+ intents: Array<{ intent: string; params?: Record<string, unknown> }>;
223
+ }
224
+
225
+ export interface UiModelContextParams {
226
+ content?: unknown[];
227
+ structuredContent?: Record<string, unknown>;
228
+ [key: string]: unknown;
229
+ }
230
+
231
+ export interface UiOpenLinkResult {
232
+ isError?: boolean;
233
+ [key: string]: unknown;
234
+ }
235
+
236
+ export interface UiDisplayModeRequest {
237
+ mode?: UiDisplayMode;
238
+ }
239
+
240
+ export interface UiDisplayModeResult {
241
+ mode: UiDisplayMode;
242
+ [key: string]: unknown;
243
+ }
244
+
245
+ // Content types from MCP
246
+ export interface McpContent {
247
+ type: "text" | "image" | "audio" | "resource" | "resource_link";
248
+ text?: string;
249
+ data?: string;
250
+ mimeType?: string;
251
+ resource?: {
252
+ uri: string;
253
+ text?: string;
254
+ blob?: string;
255
+ };
256
+ uri?: string;
257
+ name?: string;
258
+ description?: string;
259
+ }
260
+
261
+ // Pi content block type
262
+ export type ContentBlock = TextContent | ImageContent;
263
+
264
+ // Server configuration
265
+ export interface ServerEntry {
266
+ command?: string;
267
+ args?: string[];
268
+ env?: Record<string, string>;
269
+ cwd?: string;
270
+ // HTTP fields
271
+ url?: string;
272
+ headers?: Record<string, string>;
273
+ auth?: "oauth" | "bearer";
274
+ bearerToken?: string;
275
+ bearerTokenEnv?: string;
276
+ lifecycle?: "keep-alive" | "lazy" | "eager";
277
+ idleTimeout?: number; // minutes, overrides global setting
278
+ // Resource handling
279
+ exposeResources?: boolean;
280
+ // Direct tool registration
281
+ directTools?: boolean | string[];
282
+ // Debug
283
+ debug?: boolean; // Show server stderr (default: false)
284
+ }
285
+
286
+ // Settings
287
+ export interface McpSettings {
288
+ toolPrefix?: "server" | "none" | "short";
289
+ idleTimeout?: number; // minutes, default 10, 0 to disable
290
+ directTools?: boolean;
291
+ }
292
+
293
+ // Root config
294
+ export interface McpConfig {
295
+ mcpServers: Record<string, ServerEntry>;
296
+ imports?: ImportKind[];
297
+ settings?: McpSettings;
298
+ }
299
+
300
+ // Alias for clarity
301
+ export type ServerDefinition = ServerEntry;
302
+
303
+ export interface ToolMetadata {
304
+ name: string; // Prefixed tool name (e.g., "xcodebuild_list_sims")
305
+ originalName: string; // Original MCP tool name (e.g., "list_sims")
306
+ description: string;
307
+ resourceUri?: string; // For resource tools: the URI to read
308
+ uiResourceUri?: string; // For app-enabled tools: the UI resource URI
309
+ inputSchema?: unknown; // JSON Schema for parameters (stored for describe/errors)
310
+ uiStreamMode?: UiStreamMode;
311
+ }
312
+
313
+ export interface DirectToolSpec {
314
+ serverName: string;
315
+ originalName: string;
316
+ prefixedName: string;
317
+ description: string;
318
+ inputSchema?: unknown;
319
+ resourceUri?: string;
320
+ uiResourceUri?: string;
321
+ uiStreamMode?: UiStreamMode;
322
+ }
323
+
324
+ export interface ServerProvenance {
325
+ path: string;
326
+ kind: "user" | "project" | "import";
327
+ importKind?: string;
328
+ }
329
+
330
+ export interface McpPanelCallbacks {
331
+ reconnect: (serverName: string) => Promise<boolean>;
332
+ getConnectionStatus: (serverName: string) => "connected" | "idle" | "failed" | "needs-auth";
333
+ refreshCacheAfterReconnect: (serverName: string) => import("./metadata-cache.js").ServerCacheEntry | null;
334
+ }
335
+
336
+ export interface McpPanelResult {
337
+ changes: Map<string, true | string[] | false>;
338
+ cancelled: boolean;
339
+ }
340
+
341
+ /**
342
+ * Get server prefix based on tool prefix mode.
343
+ */
344
+ export function getServerPrefix(
345
+ serverName: string,
346
+ mode: "server" | "none" | "short"
347
+ ): string {
348
+ if (mode === "none") return "";
349
+ if (mode === "short") {
350
+ let short = serverName.replace(/-?mcp$/i, "").replace(/-/g, "_");
351
+ if (!short) short = "mcp";
352
+ return short;
353
+ }
354
+ return serverName.replace(/-/g, "_");
355
+ }
356
+
357
+ /**
358
+ * Format a tool name with server prefix.
359
+ */
360
+ export function formatToolName(
361
+ toolName: string,
362
+ serverName: string,
363
+ prefix: "server" | "none" | "short"
364
+ ): string {
365
+ const p = getServerPrefix(serverName, prefix);
366
+ return p ? `${p}_${toolName}` : toolName;
367
+ }
@@ -0,0 +1,145 @@
1
+ import { RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/app-bridge";
2
+ import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { ResourceFetchError, ResourceParseError } from "./errors.js";
4
+ import { logger } from "./logger.js";
5
+ import type { McpServerManager } from "./server-manager.js";
6
+ import type { UiResourceContent, UiResourceMeta } from "./types.js";
7
+
8
+ interface ResourceContentRecord {
9
+ uri?: string;
10
+ mimeType?: string;
11
+ text?: string;
12
+ blob?: string;
13
+ _meta?: Record<string, unknown>;
14
+ }
15
+
16
+ export class UiResourceHandler {
17
+ private log = logger.child({ component: "UiResourceHandler" });
18
+
19
+ constructor(private manager: McpServerManager) {}
20
+
21
+ async readUiResource(serverName: string, uri: string): Promise<UiResourceContent> {
22
+ const log = this.log.child({ server: serverName, uri });
23
+
24
+ if (!uri.startsWith("ui://")) {
25
+ throw new ResourceParseError(uri, "URI must start with ui://", { server: serverName });
26
+ }
27
+
28
+ log.debug("Fetching UI resource");
29
+
30
+ let result: ReadResourceResult;
31
+ try {
32
+ result = await this.manager.readResource(serverName, uri);
33
+ } catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ log.error("Failed to read resource", error instanceof Error ? error : undefined);
36
+ throw new ResourceFetchError(uri, message, {
37
+ server: serverName,
38
+ cause: error instanceof Error ? error : undefined,
39
+ });
40
+ }
41
+
42
+ const content = selectContent(result, uri);
43
+ const mimeType = content.mimeType;
44
+
45
+ if (mimeType && !isHtmlMimeType(mimeType)) {
46
+ log.warn("Unsupported MIME type", { mimeType });
47
+ throw new ResourceParseError(
48
+ uri,
49
+ `unsupported MIME type "${mimeType}" (expected text/html or ${RESOURCE_MIME_TYPE})`,
50
+ { server: serverName, mimeType }
51
+ );
52
+ }
53
+
54
+ const html = toHtml(content);
55
+ if (!html.trim()) {
56
+ log.warn("Resource content is empty");
57
+ throw new ResourceParseError(uri, "content is empty", { server: serverName });
58
+ }
59
+
60
+ const contentMeta = extractUiMeta(content._meta);
61
+ const listMeta = extractUiMeta(this.getListResourceMeta(serverName, uri));
62
+
63
+ log.debug("Resource loaded successfully", {
64
+ contentLength: html.length,
65
+ hasCsp: !!contentMeta.csp || !!listMeta.csp,
66
+ });
67
+
68
+ return {
69
+ uri: content.uri ?? uri,
70
+ html,
71
+ mimeType: mimeType ?? RESOURCE_MIME_TYPE,
72
+ meta: {
73
+ csp: contentMeta.csp ?? listMeta.csp,
74
+ permissions: contentMeta.permissions ?? listMeta.permissions,
75
+ domain: contentMeta.domain ?? listMeta.domain,
76
+ prefersBorder: contentMeta.prefersBorder ?? listMeta.prefersBorder,
77
+ },
78
+ };
79
+ }
80
+
81
+ private getListResourceMeta(serverName: string, uri: string): Record<string, unknown> | undefined {
82
+ const connection = this.manager.getConnection(serverName);
83
+ if (!connection?.resources?.length) return undefined;
84
+ const resource = connection.resources.find((entry) => entry.uri === uri);
85
+ if (!resource || !resource._meta || typeof resource._meta !== "object") return undefined;
86
+ return resource._meta;
87
+ }
88
+ }
89
+
90
+ function selectContent(result: ReadResourceResult, preferredUri: string): ResourceContentRecord {
91
+ const contents = (result.contents ?? []) as ResourceContentRecord[];
92
+ if (contents.length === 0) {
93
+ throw new Error(`No contents returned for UI resource: ${preferredUri}`);
94
+ }
95
+
96
+ const byUri = contents.find((content) => content.uri === preferredUri);
97
+ if (byUri) return byUri;
98
+
99
+ const byHtmlMime = contents.find(
100
+ (content) => content.mimeType && isHtmlMimeType(content.mimeType)
101
+ );
102
+ if (byHtmlMime) return byHtmlMime;
103
+
104
+ return contents[0];
105
+ }
106
+
107
+ function isHtmlMimeType(mimeType: string): boolean {
108
+ const normalized = mimeType.toLowerCase();
109
+ return normalized.startsWith("text/html") || normalized === RESOURCE_MIME_TYPE.toLowerCase();
110
+ }
111
+
112
+ function toHtml(content: ResourceContentRecord): string {
113
+ if (typeof content.text === "string") {
114
+ return content.text;
115
+ }
116
+
117
+ if (typeof content.blob === "string") {
118
+ return Buffer.from(content.blob, "base64").toString("utf-8");
119
+ }
120
+
121
+ throw new Error(`UI resource ${content.uri ?? "(unknown)"} did not include text or blob content`);
122
+ }
123
+
124
+ function extractUiMeta(meta: Record<string, unknown> | undefined): UiResourceMeta {
125
+ if (!meta || typeof meta !== "object") return {};
126
+ const ui = meta.ui as Record<string, unknown> | undefined;
127
+ if (!ui || typeof ui !== "object") return {};
128
+
129
+ const out: UiResourceMeta = {};
130
+
131
+ if (ui.csp && typeof ui.csp === "object") {
132
+ out.csp = ui.csp as UiResourceMeta["csp"];
133
+ }
134
+ if (ui.permissions && typeof ui.permissions === "object") {
135
+ out.permissions = ui.permissions as UiResourceMeta["permissions"];
136
+ }
137
+ if (typeof ui.domain === "string") {
138
+ out.domain = ui.domain;
139
+ }
140
+ if (typeof ui.prefersBorder === "boolean") {
141
+ out.prefersBorder = ui.prefersBorder;
142
+ }
143
+
144
+ return out;
145
+ }