@kamilio/wire 0.1.1 → 0.1.3

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 (106) hide show
  1. package/README.md +1 -1
  2. package/bin/wire-mcp.mjs +1 -1
  3. package/dist/adapters/root.d.ts +5 -2
  4. package/dist/adapters/root.d.ts.map +1 -1
  5. package/dist/adapters/root.js +58 -30
  6. package/dist/adapters/root.js.map +1 -1
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +6 -26
  9. package/dist/auth.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +5 -3
  12. package/dist/cli.js.map +1 -1
  13. package/dist/executable.d.ts +3 -1
  14. package/dist/executable.d.ts.map +1 -1
  15. package/dist/executable.js +5 -4
  16. package/dist/executable.js.map +1 -1
  17. package/dist/hooks.d.ts.map +1 -1
  18. package/dist/hooks.js +4 -0
  19. package/dist/hooks.js.map +1 -1
  20. package/dist/mcp.d.ts.map +1 -1
  21. package/dist/mcp.js +5 -2
  22. package/dist/mcp.js.map +1 -1
  23. package/node_modules/provider-asana/dist/asana-project.d.ts.map +1 -1
  24. package/node_modules/provider-asana/dist/asana-project.js +3 -0
  25. package/node_modules/provider-asana/dist/asana-project.js.map +1 -1
  26. package/node_modules/provider-asana/src/asana-project.ts +2 -0
  27. package/node_modules/provider-chatgpt/dist/chatgpt.d.ts.map +1 -1
  28. package/node_modules/provider-chatgpt/dist/chatgpt.js +2 -0
  29. package/node_modules/provider-chatgpt/dist/chatgpt.js.map +1 -1
  30. package/node_modules/provider-chatgpt/src/chatgpt.ts +1 -0
  31. package/node_modules/provider-gmail/dist/gmail.d.ts.map +1 -1
  32. package/node_modules/provider-gmail/dist/gmail.js +6 -2
  33. package/node_modules/provider-gmail/dist/gmail.js.map +1 -1
  34. package/node_modules/provider-gmail/src/gmail.ts +7 -2
  35. package/node_modules/provider-google-docs/dist/google-docs.d.ts.map +1 -1
  36. package/node_modules/provider-google-docs/dist/google-docs.js +370 -9
  37. package/node_modules/provider-google-docs/dist/google-docs.js.map +1 -1
  38. package/node_modules/provider-google-docs/dist/google-forms.d.ts +28 -0
  39. package/node_modules/provider-google-docs/dist/google-forms.d.ts.map +1 -0
  40. package/node_modules/provider-google-docs/dist/google-forms.js +191 -0
  41. package/node_modules/provider-google-docs/dist/google-forms.js.map +1 -0
  42. package/node_modules/provider-google-docs/dist/index.d.ts +26 -0
  43. package/node_modules/provider-google-docs/dist/index.d.ts.map +1 -1
  44. package/node_modules/provider-google-docs/dist/index.js +3 -1
  45. package/node_modules/provider-google-docs/dist/index.js.map +1 -1
  46. package/node_modules/provider-google-docs/src/google-docs.ts +339 -10
  47. package/node_modules/provider-google-docs/src/google-forms.ts +179 -0
  48. package/node_modules/provider-google-docs/src/index.ts +3 -1
  49. package/node_modules/provider-notion/dist/notion-sync.d.ts +1 -1
  50. package/node_modules/provider-notion/dist/notion-sync.d.ts.map +1 -1
  51. package/node_modules/provider-notion/dist/notion-sync.js +51 -29
  52. package/node_modules/provider-notion/dist/notion-sync.js.map +1 -1
  53. package/node_modules/provider-notion/src/notion-sync.ts +52 -26
  54. package/node_modules/provider-slack/dist/slack.d.ts.map +1 -1
  55. package/node_modules/provider-slack/dist/slack.js +14 -7
  56. package/node_modules/provider-slack/dist/slack.js.map +1 -1
  57. package/node_modules/provider-slack/src/slack.ts +14 -5
  58. package/node_modules/provider-zoom/dist/zoom-hub.d.ts.map +1 -1
  59. package/node_modules/provider-zoom/dist/zoom-hub.js +42 -35
  60. package/node_modules/provider-zoom/dist/zoom-hub.js.map +1 -1
  61. package/node_modules/provider-zoom/src/zoom-hub.ts +40 -35
  62. package/node_modules/wire-core/dist/core/model.d.ts +1 -1
  63. package/node_modules/wire-core/dist/core/model.d.ts.map +1 -1
  64. package/node_modules/wire-core/dist/index.d.ts +3 -2
  65. package/node_modules/wire-core/dist/index.d.ts.map +1 -1
  66. package/node_modules/wire-core/dist/index.js +2 -2
  67. package/node_modules/wire-core/dist/index.js.map +1 -1
  68. package/node_modules/wire-core/dist/operations.d.ts +3 -0
  69. package/node_modules/wire-core/dist/operations.d.ts.map +1 -1
  70. package/node_modules/wire-core/dist/operations.js +46 -7
  71. package/node_modules/wire-core/dist/operations.js.map +1 -1
  72. package/node_modules/wire-core/dist/ports.d.ts +1 -0
  73. package/node_modules/wire-core/dist/ports.d.ts.map +1 -1
  74. package/node_modules/wire-core/dist/runtime/chrome.d.ts.map +1 -1
  75. package/node_modules/wire-core/dist/runtime/chrome.js +27 -4
  76. package/node_modules/wire-core/dist/runtime/chrome.js.map +1 -1
  77. package/node_modules/wire-core/dist/runtime/cookies.d.ts.map +1 -1
  78. package/node_modules/wire-core/dist/runtime/cookies.js +7 -2
  79. package/node_modules/wire-core/dist/runtime/cookies.js.map +1 -1
  80. package/node_modules/wire-core/dist/runtime/node.d.ts.map +1 -1
  81. package/node_modules/wire-core/dist/runtime/node.js +4 -0
  82. package/node_modules/wire-core/dist/runtime/node.js.map +1 -1
  83. package/node_modules/wire-core/dist/storage/registry.d.ts.map +1 -1
  84. package/node_modules/wire-core/dist/storage/registry.js +95 -208
  85. package/node_modules/wire-core/dist/storage/registry.js.map +1 -1
  86. package/node_modules/wire-core/dist/storage/workspace.d.ts +3 -0
  87. package/node_modules/wire-core/dist/storage/workspace.d.ts.map +1 -1
  88. package/node_modules/wire-core/dist/storage/workspace.js +10 -1
  89. package/node_modules/wire-core/dist/storage/workspace.js.map +1 -1
  90. package/node_modules/wire-core/package.json +4 -0
  91. package/node_modules/wire-core/src/core/model.ts +1 -1
  92. package/node_modules/wire-core/src/index.ts +3 -2
  93. package/node_modules/wire-core/src/operations.ts +52 -8
  94. package/node_modules/wire-core/src/ports.ts +1 -0
  95. package/node_modules/wire-core/src/runtime/chrome.ts +30 -5
  96. package/node_modules/wire-core/src/runtime/cookies.ts +4 -2
  97. package/node_modules/wire-core/src/runtime/node.ts +4 -0
  98. package/node_modules/wire-core/src/storage/registry.ts +48 -15
  99. package/node_modules/wire-core/src/storage/workspace.ts +10 -1
  100. package/package.json +1 -1
  101. package/src/adapters/root.ts +51 -31
  102. package/src/auth.ts +7 -20
  103. package/src/cli.ts +6 -3
  104. package/src/executable.ts +5 -4
  105. package/src/hooks.ts +5 -0
  106. package/src/mcp.ts +6 -2
@@ -1,10 +1,11 @@
1
1
  import { UserError, defineCommand, defineGroup, S, type Group } from "toolcraft";
2
2
 
3
- import type { FetchedDocument, InitializedWire, Resource, SwitchedWireBackend, Wire, WireBackend, WireResult, WireWatchSession } from "wire-core";
3
+ import { defaultWireBackend, registryPathForBackend, type FetchedDocument, type InitializedWire, type Resource, type SwitchedWireBackend, type Wire, type WireBackend, type WireResult, type WireWatchSession } from "wire-core";
4
4
  import type { Auth, AuthResult, AuthService } from "../auth.js";
5
5
 
6
6
  export type WireRoot = Group;
7
7
  export type TextInputReader = () => Promise<string>;
8
+ export type WireRootOptions = Readonly<{ allowPaste: boolean }>;
8
9
 
9
10
  const authServices = ["asana", "chatgpt", "gmail", "google-docs", "notion", "slack", "zoom"] as const;
10
11
  const removedCliCommandNames = Object.freeze({
@@ -116,20 +117,22 @@ function formatWireCountsRich(result: WireResult, primitives: any): string {
116
117
 
117
118
  function formatWireResult(result: WireResult): string {
118
119
  const local = lineText(primaryFilesystemLink(result.resource).path);
120
+ const remote = lineText(result.summary.remote);
119
121
  const titleText = lineText(wireTitle(result.resource));
120
122
  if (result.summary.action === "failed") return [`failed ${titleText}`, `local: ${local}`, formatMultilineField("error", result.summary.error!)].join("\n");
121
- const changed = result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0;
123
+ const changed = result.summary.action !== "detached" && (result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0);
122
124
  const title = changed ? `${result.summary.action} ${formatWireCounts(result)} ${titleText}` : `${result.summary.action} ${titleText}`;
123
- return [title, `local: ${local}`].join("\n");
125
+ return [title, ...(result.summary.action === "uploaded" ? [`remote: ${remote}`] : []), `local: ${local}`].join("\n");
124
126
  }
125
127
 
126
128
  function formatWireResultRich(result: WireResult, primitives: any): string {
127
129
  const local = lineText(primaryFilesystemLink(result.resource).path);
130
+ const remote = lineText(result.summary.remote);
128
131
  const titleText = lineText(wireTitle(result.resource));
129
132
  if (result.summary.action === "failed") return [`failed ${titleText}`, `local: ${local}`, formatMultilineField("error", result.summary.error!)].join("\n");
130
- const changed = result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0;
133
+ const changed = result.summary.action !== "detached" && (result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0);
131
134
  const title = changed ? `${result.summary.action} ${formatWireCountsRich(result, primitives)} ${titleText}` : `${result.summary.action} ${titleText}`;
132
- return [title, `local: ${local}`].join("\n");
135
+ return [title, ...(result.summary.action === "uploaded" ? [`remote: ${remote}`] : []), `local: ${local}`].join("\n");
133
136
  }
134
137
 
135
138
  function formatMultilineField(label: string, value: string): string {
@@ -189,16 +192,16 @@ function formatInitializedWire(value: InitializedWire): string {
189
192
 
190
193
  function watchSessionJson(value: WireWatchSession) {
191
194
  value.close();
192
- return { resource_id: value.resource.id, title: wireTitle(value.resource), remote: value.resource.urls[0], local: primaryFilesystemLink(value.resource).path, path: value.path, mode: value.mode, debounceMs: value.debounceMs, pollMs: value.pollMs };
195
+ throw new UserError("wire watch --json is not supported. Use markdown output for watch.");
193
196
  }
194
197
 
195
198
  function wireUserErrorMessage(message: string): boolean {
196
- return /^Unsupported source URL: /.test(message) || /^Wire workspace not initialized\. Run `wire init` or `wire <url>` first\.$/.test(message) || /^Wire workspace already initialized with (sqlite|files) registry at .+\. Existing registries are not overwritten\.$/.test(message) || /^Resource path is not registered: /.test(message) || /^Resource (path |identifier |URL )?not found: /.test(message) || /^Ambiguous resource path /.test(message) || /^Login not saved$/.test(message) || /authentication (is missing|is missing or expired|failed|expired).*Run `wire (asana|chatgpt|gmail|google-docs|notion|slack|zoom) login`/i.test(message) || /^Asana API /.test(message) || /^(GET|POST|PUT|DELETE) \/.* failed: \d+ /.test(message) || /^(Invalid Asana project heading|Asana section appears|Asana milestone appears|Asana task appears|Asana subtask appears|Unsupported Asana Markdown|Duplicate Asana identity|Missing Asana project heading|Asana project identity changed|Unknown Asana identity|Conflicting Asana edits)/.test(message) || /^ChatGPT conversation download failed\. Run `wire chatgpt login`\./.test(message) || /^Gmail API .+ failed: HTTP \d+ /.test(message) || /^Slack API [\w.]+ failed: /.test(message) || /^Zoom Hub (.+ failed: HTTP \d+|file .+ was not returned by batch_get)/.test(message) || /^(Markdown document requires a first heading|Markdown and Notion changed since last sync|lossless Markdown headings deeper than level 3 are not supported)$/.test(message) || /^Indented Notion Markdown block has no parent at indent \d+$/.test(message) || /^POST [A-Za-z0-9]+ failed: \d+ /.test(message) || /^Google (Docs|Sheets) changed remotely and locally\./.test(message) || /^Google Docs local edit cannot be mapped to the live document text$/.test(message) || /^Google Docs sync cannot upload formatting-only Markdown edits$/.test(message) || /^Google (Docs|Sheets) editor did not include save metadata$/.test(message) || /^Google (Docs Markdown|Sheets CSV) export (did not include a filename|failed: HTTP \d+)$/.test(message) || /^Google (Docs|Sheets) save (failed: |verification failed$)/.test(message) || /^Google sync base must /.test(message) || /^Google Sheets sync base /.test(message) || /^Google Sheets sync cannot upload formula-like cell text at row \d+, column \d+\nPrefix it with an apostrophe or rewrite it as plain text before syncing\.$/.test(message) || /^Google Sheets sync requires a Markdown table/.test(message) || /^Google Sheets sync requires every Markdown table row to have the same number of cells$/.test(message) || /^Google Sheets sync requires every Markdown table row to have \d+ cells: line \d+ has \d+$/.test(message);
199
+ return /^Unsupported source URL: /.test(message) || /^Wire workspace not initialized\. Run `wire init` or `wire <url>` first\.$/.test(message) || /^Wire workspace already initialized with (sqlite|files) registry at .+\. Existing registries are not overwritten\.$/.test(message) || /^Resource path is not registered: /.test(message) || /^Resource (path |identifier |URL )?not found: /.test(message) || /^Ambiguous resource path /.test(message) || /^Login not saved$/.test(message) || /authentication (is missing|is missing or expired|failed|expired).*Run `wire (asana|chatgpt|gmail|google-docs|notion|slack|zoom) login`/i.test(message) || /^Asana API /.test(message) || /^(GET|POST|PUT|DELETE) \/.* failed: \d+ /.test(message) || /^(Invalid Asana project heading|Asana section appears|Asana milestone appears|Asana task appears|Asana subtask appears|Unsupported Asana Markdown|Duplicate Asana identity|Missing Asana project heading|Asana project identity changed|Unknown Asana identity|Conflicting Asana edits)/.test(message) || /^ChatGPT conversation download failed\. Run `wire chatgpt login`\./.test(message) || /^Gmail API .+ failed: HTTP \d+ /.test(message) || /^Slack API [\w.]+ failed: /.test(message) || /^Zoom Hub (.+ failed: HTTP \d+|file .+ was not returned by batch_get)/.test(message) || /^(Markdown document requires a first heading|Markdown and Notion changed since last sync|lossless Markdown headings deeper than level 3 are not supported)$/.test(message) || /^Indented Notion Markdown block has no parent at indent \d+$/.test(message) || /^POST [A-Za-z0-9]+ failed: \d+ /.test(message) || /^Google (Docs|Sheets|Slides) changed remotely and locally\./.test(message) || /^Google Docs local edit cannot be mapped to the live document text$/.test(message) || /^Google Docs sync cannot upload formatting-only Markdown edits$/.test(message) || /^Google Docs sync cannot preserve formatting in edited text$/.test(message) || /^Google Slides sync is download-only\./.test(message) || /^Google Forms sync is download-only\./.test(message) || /^Google Forms API is disabled\. Enable it at /.test(message) || /^Google Forms API token is missing required scopes\./.test(message) || /^Google Forms API authentication is missing or expired\./.test(message) || /^Google Forms API .+ failed: HTTP \d+/.test(message) || /^Google (Docs|Sheets) editor did not include save metadata$/.test(message) || /^Google (Docs Markdown|Sheets CSV|Slides PPTX) export (did not include a filename|failed: HTTP \d+)$/.test(message) || /^Google (Docs|Sheets) save (failed: |verification failed$)/.test(message) || /^Google sync base must /.test(message) || /^Google Sheets sync base /.test(message) || /^Google Sheets sync cannot upload formula-like cell text at row \d+, column \d+\nPrefix it with an apostrophe or rewrite it as plain text before syncing\.$/.test(message) || /^Google Sheets sync requires a Markdown table/.test(message) || /^Google Sheets sync requires every Markdown table row to have the same number of cells$/.test(message) || /^Google Sheets sync requires every Markdown table row to have \d+ cells: line \d+ has \d+$/.test(message);
197
200
  }
198
201
 
199
202
  function wireUserErrorDisplayMessage(message: string): string {
200
203
  const unsupportedSource = /^Unsupported source URL: ([\s\S]+)$/.exec(message);
201
- if (unsupportedSource !== null) return `unsupported source\nurl: ${lineText(unsupportedSource[1]!)}\nsupported: Asana, ChatGPT, Gmail, Google Docs/Sheets, Notion, Slack, Zoom`;
204
+ if (unsupportedSource !== null) return `unsupported source\nurl: ${lineText(unsupportedSource[1]!)}\nsupported: Asana, ChatGPT, Gmail, Google Docs/Sheets/Slides/Forms, Notion, Slack, Zoom`;
202
205
  const missingWorkspace = /^Wire workspace not initialized\. Run `wire init` or `wire <url>` first\.$/.exec(message);
203
206
  if (missingWorkspace !== null) return "workspace not initialized\nrun: wire init\nattach: wire <url>";
204
207
  const existingWorkspace = /^Wire workspace already initialized with (sqlite|files) registry at ([\s\S]+)\. Existing registries are not overwritten\.$/.exec(message);
@@ -235,30 +238,44 @@ function wireUserErrorDisplayMessage(message: string): string {
235
238
  if (notionIndentedBlock !== null) return `local markdown invalid\nservice: Notion\nindent: ${notionIndentedBlock[1]!}\ndetail: indented block has no parent`;
236
239
  const chatgptDownload = /^ChatGPT conversation download failed\. Run `wire chatgpt login`\.([\s\S]*)$/.exec(message);
237
240
  if (chatgptDownload !== null) return `download failed\nservice: ChatGPT\nlogin: wire chatgpt login\ndetail: ${lineText(chatgptDownload[1]!)}`;
238
- const googleExportMissingFilename = /^Google (Docs Markdown|Sheets CSV) export did not include a filename$/.exec(message);
239
- if (googleExportMissingFilename !== null) return `export failed\nservice: Google Docs/Sheets\nsource: Google ${googleExportMissingFilename[1]!}\ndetail: missing filename`;
240
- const googleExportHttp = /^Google (Docs Markdown|Sheets CSV) export failed: HTTP (\d+)(?: ([\s\S]+))?$/.exec(message);
241
- if (googleExportHttp !== null) return `export failed\nservice: Google Docs/Sheets\nsource: Google ${googleExportHttp[1]!}\nstatus: HTTP ${googleExportHttp[2]!}${googleExportHttp[3] === undefined ? "" : `\ndetail: ${lineText(googleExportHttp[3]!)}`}`;
241
+ const googleExportMissingFilename = /^Google (Docs Markdown|Sheets CSV|Slides PPTX) export did not include a filename$/.exec(message);
242
+ if (googleExportMissingFilename !== null) return `export failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleExportMissingFilename[1]!}\ndetail: missing filename`;
243
+ const googleExportHttp = /^Google (Docs Markdown|Sheets CSV|Slides PPTX) export failed: HTTP (\d+)(?: ([\s\S]+))?$/.exec(message);
244
+ if (googleExportHttp !== null) return `export failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleExportHttp[1]!}\nstatus: HTTP ${googleExportHttp[2]!}${googleExportHttp[3] === undefined ? "" : `\ndetail: ${lineText(googleExportHttp[3]!)}`}`;
242
245
  const googleEditorMetadata = /^Google (Docs|Sheets) editor did not include save metadata$/.exec(message);
243
- if (googleEditorMetadata !== null) return `save metadata missing\nservice: Google Docs/Sheets\nsource: Google ${googleEditorMetadata[1]!} editor`;
246
+ if (googleEditorMetadata !== null) return `save metadata missing\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleEditorMetadata[1]!} editor`;
244
247
  const googleSaveFailed = /^Google (Docs|Sheets) save failed: ([\s\S]+)$/.exec(message);
245
- if (googleSaveFailed !== null) return `save failed\nservice: Google Docs/Sheets\nsource: Google ${googleSaveFailed[1]!}\ndetail: ${lineText(googleSaveFailed[2]!)}`;
248
+ if (googleSaveFailed !== null) return `save failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleSaveFailed[1]!}\ndetail: ${lineText(googleSaveFailed[2]!)}`;
246
249
  const googleSaveVerification = /^Google (Docs|Sheets) save verification failed$/.exec(message);
247
- if (googleSaveVerification !== null) return `save verification failed\nservice: Google Docs/Sheets\nsource: Google ${googleSaveVerification[1]!}`;
248
- const googleConflict = /^Google (Docs|Sheets) changed remotely and locally\. Resolve the conflict in Google (Docs|Sheets) or the local Markdown file before syncing again\.$/.exec(message);
249
- if (googleConflict !== null) return `sync conflict\nservice: Google Docs/Sheets\nsource: Google ${googleConflict[1]!}\nresolve: edit Google ${googleConflict[2]!} or local Markdown, then sync again`;
250
+ if (googleSaveVerification !== null) return `save verification failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleSaveVerification[1]!}`;
251
+ const googleConflict = /^Google (Docs|Sheets|Slides) changed remotely and locally\. Resolve the conflict in Google (Docs|Sheets|Slides) or the local Markdown file before syncing again\.$/.exec(message);
252
+ if (googleConflict !== null) return `sync conflict\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleConflict[1]!}\nresolve: edit Google ${googleConflict[2]!} or local Markdown, then sync again`;
250
253
  const googleDocsMapping = /^Google Docs local edit cannot be mapped to the live document text$/.exec(message);
251
- if (googleDocsMapping !== null) return "local edit not mappable\nservice: Google Docs/Sheets\nsource: Google Docs\nresolve: edit Google Docs or simplify local Markdown";
254
+ if (googleDocsMapping !== null) return "local edit not mappable\nservice: Google Docs/Sheets/Slides\nsource: Google Docs\nresolve: edit Google Docs or simplify local Markdown";
252
255
  const googleFormattingOnly = /^Google Docs sync cannot upload formatting-only Markdown edits$/.exec(message);
253
- if (googleFormattingOnly !== null) return "local edit not uploadable\nservice: Google Docs/Sheets\nsource: Google Docs\ndetail: formatting-only Markdown edit";
256
+ if (googleFormattingOnly !== null) return "local edit not uploadable\nservice: Google Docs/Sheets/Slides\nsource: Google Docs\ndetail: formatting-only Markdown edit";
257
+ const googleFormattingSpan = /^Google Docs sync cannot preserve formatting in edited text$/.exec(message);
258
+ if (googleFormattingSpan !== null) return "local edit not uploadable\nservice: Google Docs/Sheets/Slides\nsource: Google Docs\ndetail: edited text contains formatting";
259
+ const googleSlidesDownloadOnly = /^Google Slides sync is download-only\. Revert local edits or use `wire download <url>` for a fresh copy\.$/.exec(message);
260
+ if (googleSlidesDownloadOnly !== null) return "local edit not uploadable\nservice: Google Docs/Sheets/Slides\nsource: Google Slides\ndetail: download-only";
261
+ const googleFormsDownloadOnly = /^Google Forms sync is download-only\. Revert local edits or use `wire download <url>` for a fresh copy\.$/.exec(message);
262
+ if (googleFormsDownloadOnly !== null) return "local edit not uploadable\nservice: Google Forms\ndetail: download-only";
263
+ const googleFormsDisabled = /^Google Forms API is disabled\. Enable it at ([^\s]+) then retry\.$/.exec(message);
264
+ if (googleFormsDisabled !== null) return `api disabled\nservice: Google Forms\nenable: ${lineText(googleFormsDisabled[1]!)}\nretry: wire <form-url>`;
265
+ const googleFormsScopes = /^Google Forms API token is missing required scopes\. Regenerate GOOGLE_FORMS_TOKEN_FILE with forms\.body and forms\.responses\.readonly scopes\.$/.exec(message);
266
+ if (googleFormsScopes !== null) return "login invalid\nservice: Google Forms\ntoken: GOOGLE_FORMS_TOKEN_FILE\nscopes: forms.body, forms.responses.readonly";
267
+ const googleFormsAuth = /^Google Forms API authentication is missing or expired\. Set GOOGLE_FORMS_TOKEN_FILE to an OAuth token with Forms scopes, then retry\.$/.exec(message);
268
+ if (googleFormsAuth !== null) return "login required\nservice: Google Forms\ntoken: GOOGLE_FORMS_TOKEN_FILE\nscopes: forms.body, forms.responses.readonly";
269
+ const googleFormsApi = /^Google Forms API ([\s\S]+?) failed: HTTP (\d+)(?: ([\s\S]+))?$/.exec(message);
270
+ if (googleFormsApi !== null) return `api failed\nservice: Google Forms\noperation: ${lineText(googleFormsApi[1]!)}\nstatus: HTTP ${googleFormsApi[2]!}${googleFormsApi[3] === undefined ? "" : `\ndetail: ${lineText(googleFormsApi[3]!)}`}`;
254
271
  const googleSyncBase = /^Google sync base must ([\s\S]+)$/.exec(message);
255
- if (googleSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets\ndetail: must ${lineText(googleSyncBase[1]!)}`;
272
+ if (googleSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets/Slides\ndetail: must ${lineText(googleSyncBase[1]!)}`;
256
273
  const googleSheetsSyncBase = /^Google Sheets sync base ([\s\S]+)$/.exec(message);
257
- if (googleSheetsSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets\nsource: Google Sheets\ndetail: ${lineText(googleSheetsSyncBase[1]!)}`;
274
+ if (googleSheetsSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets/Slides\nsource: Google Sheets\ndetail: ${lineText(googleSheetsSyncBase[1]!)}`;
258
275
  const googleSheetsFormulaCell = /^Google Sheets sync cannot upload formula-like cell text at row (\d+), column (\d+)\nPrefix it with an apostrophe or rewrite it as plain text before syncing\.$/.exec(message);
259
- if (googleSheetsFormulaCell !== null) return `formula-like cell blocked\nservice: Google Docs/Sheets\nsource: Google Sheets\ncell: row ${googleSheetsFormulaCell[1]!}, column ${googleSheetsFormulaCell[2]!}\nresolve: prefix with an apostrophe or rewrite as plain text`;
276
+ if (googleSheetsFormulaCell !== null) return `formula-like cell blocked\nservice: Google Docs/Sheets/Slides\nsource: Google Sheets\ncell: row ${googleSheetsFormulaCell[1]!}, column ${googleSheetsFormulaCell[2]!}\nresolve: prefix with an apostrophe or rewrite as plain text`;
260
277
  const googleSheetsTable = /^Google Sheets sync requires (a Markdown table(?: separator row)?|every Markdown table row to have the same number of cells|every Markdown table row to have \d+ cells: line \d+ has \d+)$/.exec(message);
261
- if (googleSheetsTable !== null) return `local table invalid\nservice: Google Docs/Sheets\nsource: Google Sheets\ndetail: requires ${lineText(googleSheetsTable[1]!)}`;
278
+ if (googleSheetsTable !== null) return `local table invalid\nservice: Google Docs/Sheets/Slides\nsource: Google Sheets\ndetail: requires ${lineText(googleSheetsTable[1]!)}`;
262
279
  const missingPath = /^Resource path (not found|is not registered): ([\s\S]+)$/.exec(message);
263
280
  if (missingPath !== null) return `${missingPath[1] === "not found" ? "resource not found" : "resource not registered"}\npath: ${lineText(missingPath[2]!)}`;
264
281
  const missingResource = /^Resource (identifier |URL )?not found: ([\s\S]+)$/.exec(message);
@@ -290,7 +307,7 @@ function serviceTitle(service: AuthService): string {
290
307
  asana: "Asana",
291
308
  chatgpt: "ChatGPT",
292
309
  gmail: "Gmail",
293
- "google-docs": "Google Docs/Sheets",
310
+ "google-docs": "Google Docs/Sheets/Slides",
294
311
  notion: "Notion",
295
312
  slack: "Slack",
296
313
  zoom: "Zoom",
@@ -349,17 +366,20 @@ export const wirePresentation: WirePresentation = Object.freeze({
349
366
  authLogout: render<{ readonly service: AuthService; readonly deleted: true }>((value) => `${serviceTitle(value.service)} logged out`),
350
367
  });
351
368
 
352
- function authGroup(service: AuthService, description: string, loginDescription: string, auth: Auth, readInput?: TextInputReader): Group {
353
- const login = async (paste: boolean | undefined) => paste === true ? auth.pasteCookies(service, await readInput!()) : service === "asana" ? auth.extractAsana() : service === "chatgpt" ? auth.extractChatgpt() : service === "gmail" ? auth.extractGmail() : service === "google-docs" ? auth.extractGoogleDocs() : service === "notion" ? auth.extractNotion() : service === "slack" ? auth.extractSlack() : auth.extractZoom();
369
+ function authGroup(service: AuthService, description: string, loginDescription: string, auth: Auth, readInput: TextInputReader | undefined, options: WireRootOptions): Group {
370
+ const login = async (paste: boolean | undefined) => {
371
+ if (paste === true && !options.allowPaste) throw new UserError("Cookie paste is CLI-only.");
372
+ return paste === true ? auth.pasteCookies(service, await readInput!()) : service === "asana" ? auth.extractAsana() : service === "chatgpt" ? auth.extractChatgpt() : service === "gmail" ? auth.extractGmail() : service === "google-docs" ? auth.extractGoogleDocs() : service === "notion" ? auth.extractNotion() : service === "slack" ? auth.extractSlack() : auth.extractZoom();
373
+ };
354
374
  const title = serviceTitle(service);
355
375
  return defineGroup({ name: service, description, children: [
356
376
  defineCommand({ name: "status", description: `Check saved ${title} login.`, params: S.Object({}), handler: () => userFacing(() => auth.status(service)), render: wirePresentation.authStatus }),
357
- defineCommand({ name: "login", description: loginDescription, params: S.Object({ paste: S.Optional(S.Boolean({ description: "Read cookie text from stdin." })) }), handler: ({ params }) => userFacing(() => login(params.paste)), render: wirePresentation.authStatus }),
377
+ defineCommand({ name: "login", description: loginDescription, params: S.Object({ paste: S.Optional(S.Boolean({ description: "Read cookie text from stdin.", scope: ["cli", "sdk"] })) }), handler: ({ params }) => userFacing(() => login(params.paste)), render: wirePresentation.authStatus }),
358
378
  defineCommand({ name: "logout", description: `Delete saved ${title} cookies.`, params: S.Object({}), handler: () => auth.logout(service), render: wirePresentation.authLogout }),
359
379
  ] });
360
380
  }
361
381
 
362
- export function createRoot(wire: Wire, currentDirectory: string, auth?: Auth, readInput?: TextInputReader): WireRoot {
382
+ export function createRoot(wire: Wire, currentDirectory: string, auth?: Auth, readInput?: TextInputReader, options: WireRootOptions = { allowPaste: true }): WireRoot {
363
383
  const attachFromCliUrl = (url: string) => {
364
384
  const removedCommand = removedCliCommandNames[url as keyof typeof removedCliCommandNames];
365
385
  if (removedCommand !== undefined) throw new UserError(`Unknown command: ${url}. ${removedCommand}`);
@@ -376,7 +396,7 @@ export function createRoot(wire: Wire, currentDirectory: string, auth?: Auth, re
376
396
  });
377
397
  const authCommand = (service: (typeof authServices)[number]) => {
378
398
  const title = serviceTitle(service);
379
- return authGroup(service, `Manage ${title} login.`, "Capture cookies once; normal commands reuse saved cookies.", auth!, readInput);
399
+ return authGroup(service, `Manage ${title} login.`, "Capture cookies once; normal commands reuse saved cookies.", auth!, readInput, options);
380
400
  };
381
401
  return defineGroup({
382
402
  name: "",
@@ -388,9 +408,9 @@ export function createRoot(wire: Wire, currentDirectory: string, auth?: Auth, re
388
408
  name: "init",
389
409
  description: "Initialize a wire workspace in the current directory.",
390
410
  params: S.Object({
391
- backend: S.Optional(S.Enum(["sqlite", "files"], { default: "sqlite", description: "Registry backend: sqlite or files." })),
411
+ backend: S.Optional(S.Enum(["files", "sqlite"], { default: defaultWireBackend, description: "Registry backend: files or sqlite." })),
392
412
  }),
393
- handler: ({ params }) => userFacing(() => wire.init(currentDirectory, params.backend as WireBackend, params.backend === "files" ? "records" : "registry.sqlite3")),
413
+ handler: ({ params }) => userFacing(() => wire.init(currentDirectory, params.backend as WireBackend, registryPathForBackend(params.backend as WireBackend))),
394
414
  render: wirePresentation.init,
395
415
  }),
396
416
  defineCommand({
package/src/auth.ts CHANGED
@@ -45,24 +45,19 @@ async function verifySlack(runtime: RuntimeCapabilities, cookies: readonly Cooki
45
45
 
46
46
  async function verifyZoom(runtime: RuntimeCapabilities, cookies: readonly Cookie[]): Promise<AuthResult | null> {
47
47
  const cookie = cookieHeader(cookies);
48
- const csrfResponse = await runtime.http.request("https://zoom.us/csrf_js", { method: "POST", headers: { cookie, "user-agent": "Mozilla/5.0", "fetch-csrf-token": "1", referer: "https://hub.zoom.us/" }, body: "" });
49
- if (!csrfResponse.ok) return null;
50
- const csrfText = await csrfResponse.text();
51
- const csrfIndex = csrfText.indexOf(":");
52
- if (csrfIndex === -1) return null;
53
- const csrf = csrfText.slice(csrfIndex + 1).trim();
54
- if (csrf === "") return null;
55
- const jwtResponse = await runtime.http.request("https://hub.zoom.us/nws/common/2.0/nak?pms=Hub%2CUser%3ABase%2CAICW&src=aicw", { headers: { cookie, "user-agent": "Mozilla/5.0", "zoom-csrftoken": csrf, "x-requested-with": "XMLHttpRequest", referer: "https://hub.zoom.us/" } });
48
+ const jwtResponse = await runtime.http.request("https://hub.zoom.us/nws/common/2.0/nak?pms=Hub%2CUser%3ABase%2CAICW&src=aicw", { headers: { cookie, "user-agent": zoomUserAgent, "x-requested-with": "XMLHttpRequest", referer: "https://hub.zoom.us/" } });
56
49
  const jwt = (await jwtResponse.text()).trim();
57
50
  const accountId = cookies.find((value) => value.name === "zm_aid")?.value;
58
- if (jwt.split(".").length !== 3 || accountId === undefined) return null;
51
+ if (!jwtResponse.ok || jwt.split(".").length !== 3 || accountId === undefined) return null;
59
52
  return Object.freeze({ service: "zoom", identity: Object.freeze({ account_id: accountId }) });
60
53
  }
61
54
 
62
55
  type CookieJar = Map<string, Cookie>;
63
56
 
57
+ const zoomUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36";
58
+
64
59
  function zoomCookieKey(cookie: Cookie): string {
65
- return `${cookie.domain}\t${cookie.path}\t${cookie.name}`;
60
+ return `${cookie.domain.startsWith(".") ? cookie.domain.slice(1) : cookie.domain}\t${cookie.path}\t${cookie.name}`;
66
61
  }
67
62
 
68
63
  function zoomCookieJar(cookies: readonly Cookie[]): CookieJar {
@@ -173,15 +168,7 @@ async function zoomAuthRequest(runtime: RuntimeCapabilities, jar: CookieJar, url
173
168
  async function verifyZoomCookieState(runtime: RuntimeCapabilities, cookies: readonly Cookie[], metadata: Readonly<Record<string, string>>): Promise<Readonly<{ result: AuthResult | null; cookies: readonly Cookie[]; metadata: Readonly<Record<string, string>>; changed: boolean }>> {
174
169
  const jar = zoomCookieJar(cookies);
175
170
  let changed = zoomPruneExpiredCookies(jar, runtime.clock.now());
176
- const csrf = await zoomAuthRequest(runtime, jar, "https://zoom.us/csrf_js", { method: "POST", headers: { "user-agent": "Mozilla/5.0", "fetch-csrf-token": "1", referer: "https://hub.zoom.us/" }, body: "" });
177
- changed = csrf.changed || changed;
178
- if (!csrf.response.ok) return Object.freeze({ result: null, cookies: Object.freeze([...jar.values()]), metadata, changed });
179
- const csrfText = await csrf.response.text();
180
- const csrfIndex = csrfText.indexOf(":");
181
- if (csrfIndex === -1) return Object.freeze({ result: null, cookies: Object.freeze([...jar.values()]), metadata, changed });
182
- const token = csrfText.slice(csrfIndex + 1).trim();
183
- if (token === "") return Object.freeze({ result: null, cookies: Object.freeze([...jar.values()]), metadata, changed });
184
- const jwt = await zoomAuthRequest(runtime, jar, "https://hub.zoom.us/nws/common/2.0/nak?pms=Hub%2CUser%3ABase%2CAICW&src=aicw", { headers: { "user-agent": "Mozilla/5.0", "zoom-csrftoken": token, "x-requested-with": "XMLHttpRequest", referer: "https://hub.zoom.us/" } });
171
+ const jwt = await zoomAuthRequest(runtime, jar, "https://hub.zoom.us/nws/common/2.0/nak?pms=Hub%2CUser%3ABase%2CAICW&src=aicw", { headers: { "user-agent": zoomUserAgent, "x-requested-with": "XMLHttpRequest", referer: "https://hub.zoom.us/" } });
185
172
  changed = jwt.changed || changed;
186
173
  const jwtText = (await jwt.response.text()).trim();
187
174
  const accountId = [...jar.values()].find((value) => value.name === "zm_aid")?.value;
@@ -264,7 +251,7 @@ export function composeAuth(runtime: RuntimeCapabilities, environment: NodeEnvir
264
251
  const status = async (service: AuthService) => {
265
252
  const cookies = await runtime.cookies.loadSaved(service);
266
253
  if (cookies === null) throw cookieAuthError(service);
267
- const state = await verifyCookieState(service, cookies, service === "slack" ? await runtime.cookies.metadata(service) : Object.freeze({}));
254
+ const state = await verifyCookieState(service, cookies, await runtime.cookies.metadata(service));
268
255
  if (state.changed) await saveCookies(service, state.cookies, state.metadata);
269
256
  if (state.result !== null) return state.result;
270
257
  throw cookieAuthError(service);
package/src/cli.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from "node:fs";
2
+
1
3
  import { runCLI } from "toolcraft/cli";
2
4
 
3
5
  import type { WireRoot } from "./adapters/root.js";
@@ -131,8 +133,8 @@ const removedCommandNames = Object.freeze({
131
133
  const outputFormatNames = new Set(["rich", "md", "markdown", "json"]);
132
134
  const outputFormatList = "md, markdown, json, rich";
133
135
  const debugModeNames = new Set(["raw"]);
134
- const backendNames = new Set(["sqlite", "files"]);
135
- const backendList = "sqlite, files";
136
+ const backendNames = new Set(["files", "sqlite"]);
137
+ const backendList = "files, sqlite";
136
138
 
137
139
  function isSourceUrl(value: string): boolean {
138
140
  return /^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(value);
@@ -501,5 +503,6 @@ export async function runWireCli(root: WireRoot | WireRootFactory, argv: readonl
501
503
  if (!version && renderExcessPositionals(normalized)) {
502
504
  return;
503
505
  }
504
- await runCLI(typeof root === "function" ? root(currentDirectory) : root, { version: "0.1.0", rootUsageName: "wire", presets: false, approvals: false, controls: { output: true, debug: true }, argv: normalized });
506
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string };
507
+ await runCLI(typeof root === "function" ? root(currentDirectory) : root, { version: packageJson.version, rootUsageName: "wire", presets: false, approvals: false, controls: { output: true, debug: true }, argv: normalized });
505
508
  }
package/src/executable.ts CHANGED
@@ -6,7 +6,7 @@ import { createRoot } from "./adapters/root.js";
6
6
  import { composeAuth } from "./auth.js";
7
7
  import { serviceCatalog } from "./adapters/services/catalog.js";
8
8
  import { withWireHooks } from "./hooks.js";
9
- import { composeWire, configuredWireRoot, createCookiesCapability, createGoogleTokensCapability, createNodeClock, createNodeConfiguration, createNodeFilesystem, createNodeHttp, createNodeOpenFiles, createNodeProcess, createNodeSecrets, createNodeWatch, extractChromeCookies, initializeWire, loadWireConfig, openWireRegistry, switchWireBackend, wireRelativePath, type NodeEnvironment } from "wire-core";
9
+ import { composeWire, configuredWireRoot, createCookiesCapability, createGoogleTokensCapability, createNodeClock, createNodeConfiguration, createNodeFilesystem, createNodeHttp, createNodeOpenFiles, createNodeProcess, createNodeSecrets, createNodeWatch, defaultWireBackend, defaultWireRegistryPath, extractChromeCookies, initializeWire, loadWireConfig, openWireRegistry, switchWireBackend, wireRelativePath, type NodeEnvironment } from "wire-core";
10
10
  import type { WireConfig } from "wire-core";
11
11
 
12
12
  function readStandardInput(): Promise<string> {
@@ -52,7 +52,7 @@ function loadConfiguredEnvironment(environment: NodeEnvironment, currentDirector
52
52
  return config.env === undefined ? environment : Object.freeze({ ...environment, ...config.env });
53
53
  }
54
54
 
55
- export function createExecutableRoot(environment: NodeEnvironment, currentDirectory: string) {
55
+ export function createExecutableRoot(environment: NodeEnvironment, currentDirectory: string, options = { allowPaste: true }) {
56
56
  const repositoryRoot = environment["WIRE_REPOSITORY_ROOT"] ?? discoverRepositoryRoot(currentDirectory);
57
57
  const baseEnvironment = repositoryRoot === undefined ? environment : Object.freeze({ ...environment, WIRE_REPOSITORY_ROOT: repositoryRoot });
58
58
  const resolvedEnvironment = loadConfiguredEnvironment(baseEnvironment, currentDirectory);
@@ -71,6 +71,7 @@ export function createExecutableRoot(environment: NodeEnvironment, currentDirect
71
71
  configuration,
72
72
  secrets: createNodeSecrets(filesystem, resolvedEnvironment),
73
73
  gmailTokens: Object.freeze({ load: () => createGoogleTokensCapability(filesystem, http, clock, configuration.get("GOOGLE_CREDENTIALS_FILE"), configuration.get("GOOGLE_TOKEN_FILE")).load(), refresh: () => createGoogleTokensCapability(filesystem, http, clock, configuration.get("GOOGLE_CREDENTIALS_FILE"), configuration.get("GOOGLE_TOKEN_FILE")).refresh() }),
74
+ googleFormsTokens: Object.freeze({ load: () => createGoogleTokensCapability(filesystem, http, clock, configuration.get("GOOGLE_CREDENTIALS_FILE"), configuration.get("GOOGLE_FORMS_TOKEN_FILE")).load(), refresh: () => createGoogleTokensCapability(filesystem, http, clock, configuration.get("GOOGLE_CREDENTIALS_FILE"), configuration.get("GOOGLE_FORMS_TOKEN_FILE")).refresh() }),
74
75
  };
75
76
  let auth: ReturnType<typeof composeAuth>;
76
77
  const runtime = Object.freeze({ ...runtimeBase, cookies: createCookiesCapability(filesystem, () => home, () => resolvedEnvironment["WIRE_REPOSITORY_ROOT"]) });
@@ -93,10 +94,10 @@ export function createExecutableRoot(environment: NodeEnvironment, currentDirect
93
94
  relativePath: wireRelativePath,
94
95
  switchBackend: switchWireBackend,
95
96
  },
96
- initialization: { backend: "sqlite", registryPath: "registry.sqlite3" },
97
+ initialization: { backend: defaultWireBackend, registryPath: defaultWireRegistryPath },
97
98
  watch: createNodeWatch(),
98
99
  now: runtime.clock.now,
99
100
  open: runtime.openFiles.open,
100
101
  }), { currentDirectory, home, environment: resolvedEnvironment });
101
- return createRoot(wire, currentDirectory, auth, readStandardInput);
102
+ return createRoot(wire, currentDirectory, auth, readStandardInput, options);
102
103
  }
package/src/hooks.ts CHANGED
@@ -6,6 +6,8 @@ import { promisify } from "node:util";
6
6
 
7
7
  import type { Resource, Wire, WireResult } from "wire-core";
8
8
  import { configuredWireRoot, openWireRegistry, wireRelativePath } from "wire-core";
9
+ import type { WireWatchSyncHook } from "wire-core/internal/operations";
10
+ import { wireWatchHooks } from "wire-core/internal/operations";
9
11
 
10
12
  const execFileAsync = promisify(execFile);
11
13
 
@@ -183,14 +185,17 @@ async function runBatchHooks(options: WireHookOptions, command: string, results:
183
185
  }
184
186
 
185
187
  export function withWireHooks(wire: Wire, options: WireHookOptions): Wire {
188
+ const watchSyncHook: WireWatchSyncHook = (command, result) => runSingleResultHooks(options, command, result);
186
189
  return Object.freeze({
187
190
  ...wire,
188
191
  attach: async (url, path) => runSingleResultHooks(options, "attach", await wire.attach(url, path)),
189
192
  create: async (url, path) => runSingleResultHooks(options, "attach", await wire.create(url, path)),
190
193
  sync: async (value, path) => runSingleResultHooks(options, "sync", await wire.sync(value, path)),
194
+ downloadSource: async (url, path) => runSingleResultHooks(options, "download", await wire.downloadSource(url, path)),
191
195
  download: async (value, path) => runSingleResultHooks(options, "download", await wire.download(value, path)),
192
196
  detach: async (value, path) => runSingleResultHooks(options, "detach", await wire.detach(value, path)),
193
197
  unlink: async (value, path) => runSingleResultHooks(options, "detach", await wire.unlink(value, path)),
198
+ watch: async (value, path) => (wire as Wire & { [wireWatchHooks](value: string, path: string, hooks: readonly WireWatchSyncHook[]): ReturnType<Wire["watch"]> })[wireWatchHooks](value, path, [watchSyncHook]),
194
199
  syncAll: async (path) => runBatchHooks(options, "sync-all", await wire.syncAll(path)),
195
200
  });
196
201
  }
package/src/mcp.ts CHANGED
@@ -1,11 +1,15 @@
1
+ import { readFileSync } from "node:fs";
2
+
1
3
  import { createMCPServer, runMCP } from "toolcraft/mcp";
2
4
 
3
5
  import type { WireRoot } from "./adapters/root.js";
4
6
 
5
7
  export function createWireMcpServer(root: WireRoot) {
6
- return createMCPServer(root, { name: "wire", version: "0.1.0", omitRootToolNamePrefix: true, approvals: false });
8
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string };
9
+ return createMCPServer(root, { name: "wire", version: packageJson.version, omitRootToolNamePrefix: true, approvals: false });
7
10
  }
8
11
 
9
12
  export function runWireMcp(root: WireRoot): Promise<void> {
10
- return runMCP(root, { name: "wire", version: "0.1.0", omitRootToolNamePrefix: true, approvals: false });
13
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string };
14
+ return runMCP(root, { name: "wire", version: packageJson.version, omitRootToolNamePrefix: true, approvals: false });
11
15
  }