@kamilio/wire 0.1.2 → 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.
- package/README.md +1 -1
- package/bin/wire-mcp.mjs +1 -1
- package/dist/adapters/root.d.ts +5 -2
- package/dist/adapters/root.d.ts.map +1 -1
- package/dist/adapters/root.js +58 -30
- package/dist/adapters/root.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +6 -26
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/executable.d.ts +3 -1
- package/dist/executable.d.ts.map +1 -1
- package/dist/executable.js +5 -4
- package/dist/executable.js.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +4 -0
- package/dist/hooks.js.map +1 -1
- package/node_modules/provider-asana/dist/asana-project.d.ts.map +1 -1
- package/node_modules/provider-asana/dist/asana-project.js +3 -0
- package/node_modules/provider-asana/dist/asana-project.js.map +1 -1
- package/node_modules/provider-asana/src/asana-project.ts +2 -0
- package/node_modules/provider-chatgpt/dist/chatgpt.d.ts.map +1 -1
- package/node_modules/provider-chatgpt/dist/chatgpt.js +2 -0
- package/node_modules/provider-chatgpt/dist/chatgpt.js.map +1 -1
- package/node_modules/provider-chatgpt/src/chatgpt.ts +1 -0
- package/node_modules/provider-gmail/dist/gmail.d.ts.map +1 -1
- package/node_modules/provider-gmail/dist/gmail.js +6 -2
- package/node_modules/provider-gmail/dist/gmail.js.map +1 -1
- package/node_modules/provider-gmail/src/gmail.ts +7 -2
- package/node_modules/provider-google-docs/dist/google-docs.d.ts.map +1 -1
- package/node_modules/provider-google-docs/dist/google-docs.js +370 -9
- package/node_modules/provider-google-docs/dist/google-docs.js.map +1 -1
- package/node_modules/provider-google-docs/dist/google-forms.d.ts +28 -0
- package/node_modules/provider-google-docs/dist/google-forms.d.ts.map +1 -0
- package/node_modules/provider-google-docs/dist/google-forms.js +191 -0
- package/node_modules/provider-google-docs/dist/google-forms.js.map +1 -0
- package/node_modules/provider-google-docs/dist/index.d.ts +26 -0
- package/node_modules/provider-google-docs/dist/index.d.ts.map +1 -1
- package/node_modules/provider-google-docs/dist/index.js +3 -1
- package/node_modules/provider-google-docs/dist/index.js.map +1 -1
- package/node_modules/provider-google-docs/src/google-docs.ts +339 -10
- package/node_modules/provider-google-docs/src/google-forms.ts +179 -0
- package/node_modules/provider-google-docs/src/index.ts +3 -1
- package/node_modules/provider-notion/dist/notion-sync.d.ts +1 -1
- package/node_modules/provider-notion/dist/notion-sync.d.ts.map +1 -1
- package/node_modules/provider-notion/dist/notion-sync.js +51 -29
- package/node_modules/provider-notion/dist/notion-sync.js.map +1 -1
- package/node_modules/provider-notion/src/notion-sync.ts +52 -26
- package/node_modules/provider-slack/dist/slack.d.ts.map +1 -1
- package/node_modules/provider-slack/dist/slack.js +14 -7
- package/node_modules/provider-slack/dist/slack.js.map +1 -1
- package/node_modules/provider-slack/src/slack.ts +14 -5
- package/node_modules/provider-zoom/dist/zoom-hub.d.ts.map +1 -1
- package/node_modules/provider-zoom/dist/zoom-hub.js +42 -35
- package/node_modules/provider-zoom/dist/zoom-hub.js.map +1 -1
- package/node_modules/provider-zoom/src/zoom-hub.ts +40 -35
- package/node_modules/wire-core/dist/core/model.d.ts +1 -1
- package/node_modules/wire-core/dist/core/model.d.ts.map +1 -1
- package/node_modules/wire-core/dist/index.d.ts +3 -2
- package/node_modules/wire-core/dist/index.d.ts.map +1 -1
- package/node_modules/wire-core/dist/index.js +2 -2
- package/node_modules/wire-core/dist/index.js.map +1 -1
- package/node_modules/wire-core/dist/operations.d.ts +3 -0
- package/node_modules/wire-core/dist/operations.d.ts.map +1 -1
- package/node_modules/wire-core/dist/operations.js +46 -7
- package/node_modules/wire-core/dist/operations.js.map +1 -1
- package/node_modules/wire-core/dist/ports.d.ts +1 -0
- package/node_modules/wire-core/dist/ports.d.ts.map +1 -1
- package/node_modules/wire-core/dist/runtime/chrome.d.ts.map +1 -1
- package/node_modules/wire-core/dist/runtime/chrome.js +27 -4
- package/node_modules/wire-core/dist/runtime/chrome.js.map +1 -1
- package/node_modules/wire-core/dist/runtime/cookies.d.ts.map +1 -1
- package/node_modules/wire-core/dist/runtime/cookies.js +7 -2
- package/node_modules/wire-core/dist/runtime/cookies.js.map +1 -1
- package/node_modules/wire-core/dist/runtime/node.d.ts.map +1 -1
- package/node_modules/wire-core/dist/runtime/node.js +4 -0
- package/node_modules/wire-core/dist/runtime/node.js.map +1 -1
- package/node_modules/wire-core/dist/storage/registry.d.ts.map +1 -1
- package/node_modules/wire-core/dist/storage/registry.js +95 -208
- package/node_modules/wire-core/dist/storage/registry.js.map +1 -1
- package/node_modules/wire-core/dist/storage/workspace.d.ts +3 -0
- package/node_modules/wire-core/dist/storage/workspace.d.ts.map +1 -1
- package/node_modules/wire-core/dist/storage/workspace.js +10 -1
- package/node_modules/wire-core/dist/storage/workspace.js.map +1 -1
- package/node_modules/wire-core/package.json +4 -0
- package/node_modules/wire-core/src/core/model.ts +1 -1
- package/node_modules/wire-core/src/index.ts +3 -2
- package/node_modules/wire-core/src/operations.ts +52 -8
- package/node_modules/wire-core/src/ports.ts +1 -0
- package/node_modules/wire-core/src/runtime/chrome.ts +30 -5
- package/node_modules/wire-core/src/runtime/cookies.ts +4 -2
- package/node_modules/wire-core/src/runtime/node.ts +4 -0
- package/node_modules/wire-core/src/storage/registry.ts +48 -15
- package/node_modules/wire-core/src/storage/workspace.ts +10 -1
- package/package.json +1 -1
- package/src/adapters/root.ts +51 -31
- package/src/auth.ts +7 -20
- package/src/cli.ts +2 -2
- package/src/executable.ts +5 -4
- package/src/hooks.ts +5 -0
package/src/adapters/root.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { UserError, defineCommand, defineGroup, S, type Group } from "toolcraft";
|
|
2
2
|
|
|
3
|
-
import type
|
|
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
|
-
|
|
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
|
|
353
|
-
const login = async (paste: boolean | undefined) =>
|
|
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(["
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
@@ -133,8 +133,8 @@ const removedCommandNames = Object.freeze({
|
|
|
133
133
|
const outputFormatNames = new Set(["rich", "md", "markdown", "json"]);
|
|
134
134
|
const outputFormatList = "md, markdown, json, rich";
|
|
135
135
|
const debugModeNames = new Set(["raw"]);
|
|
136
|
-
const backendNames = new Set(["
|
|
137
|
-
const backendList = "
|
|
136
|
+
const backendNames = new Set(["files", "sqlite"]);
|
|
137
|
+
const backendList = "files, sqlite";
|
|
138
138
|
|
|
139
139
|
function isSourceUrl(value: string): boolean {
|
|
140
140
|
return /^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(value);
|
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:
|
|
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
|
}
|