@kamilio/wire 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +50 -0
  2. package/dist/adapters/root.d.ts +1 -1
  3. package/dist/adapters/root.d.ts.map +1 -1
  4. package/dist/adapters/root.js +24 -30
  5. package/dist/adapters/root.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +5 -3
  8. package/dist/cli.js.map +1 -1
  9. package/dist/executable.d.ts.map +1 -1
  10. package/dist/executable.js +2 -2
  11. package/dist/executable.js.map +1 -1
  12. package/dist/mcp.d.ts.map +1 -1
  13. package/dist/mcp.js +5 -2
  14. package/dist/mcp.js.map +1 -1
  15. package/node_modules/provider-google-docs/dist/google-docs.d.ts.map +1 -1
  16. package/node_modules/provider-google-docs/dist/google-docs.js +3 -331
  17. package/node_modules/provider-google-docs/dist/google-docs.js.map +1 -1
  18. package/node_modules/provider-google-docs/src/google-docs.ts +3 -294
  19. package/node_modules/wire-core/dist/core/model.d.ts +1 -1
  20. package/node_modules/wire-core/dist/core/model.d.ts.map +1 -1
  21. package/node_modules/wire-core/dist/index.d.ts +1 -1
  22. package/node_modules/wire-core/dist/index.d.ts.map +1 -1
  23. package/node_modules/wire-core/dist/index.js +1 -1
  24. package/node_modules/wire-core/dist/index.js.map +1 -1
  25. package/node_modules/wire-core/dist/operations.d.ts.map +1 -1
  26. package/node_modules/wire-core/dist/operations.js +2 -3
  27. package/node_modules/wire-core/dist/operations.js.map +1 -1
  28. package/node_modules/wire-core/dist/storage/registry.d.ts.map +1 -1
  29. package/node_modules/wire-core/dist/storage/registry.js +208 -95
  30. package/node_modules/wire-core/dist/storage/registry.js.map +1 -1
  31. package/node_modules/wire-core/dist/storage/workspace.d.ts +0 -3
  32. package/node_modules/wire-core/dist/storage/workspace.d.ts.map +1 -1
  33. package/node_modules/wire-core/dist/storage/workspace.js +1 -10
  34. package/node_modules/wire-core/dist/storage/workspace.js.map +1 -1
  35. package/node_modules/wire-core/src/core/model.ts +1 -1
  36. package/node_modules/wire-core/src/index.ts +1 -1
  37. package/node_modules/wire-core/src/operations.ts +2 -3
  38. package/node_modules/wire-core/src/storage/registry.ts +15 -48
  39. package/node_modules/wire-core/src/storage/workspace.ts +1 -10
  40. package/package.json +16 -1
  41. package/src/adapters/root.ts +25 -29
  42. package/src/cli.ts +6 -3
  43. package/src/executable.ts +2 -2
  44. package/src/mcp.ts +6 -2
@@ -19,7 +19,7 @@ type ResourceIdRow = Readonly<{ resource_id: string }>;
19
19
  type IdRow = Readonly<{ id: string }>;
20
20
  type SqliteModule = Readonly<{ DatabaseSync: new (path: string) => DatabaseSync }>;
21
21
 
22
- const require = createRequire("/wire-core/storage/registry.js");
22
+ const require = createRequire(import.meta.url);
23
23
 
24
24
  function compareStrings(left: string, right: string): number {
25
25
  const leftCodePoints = Array.from(left, (character) => character.codePointAt(0)!);
@@ -77,7 +77,7 @@ export class SqliteRegistry implements Registry {
77
77
 
78
78
  constructor(path: string) {
79
79
  this.path = realpathSyncParent(path);
80
- const database = this.connect();
80
+ using database = this.connect();
81
81
  database.exec(`
82
82
  PRAGMA journal_mode=DELETE;
83
83
  PRAGMA foreign_keys=ON;
@@ -121,27 +121,11 @@ export class SqliteRegistry implements Registry {
121
121
  );
122
122
  CREATE INDEX IF NOT EXISTS resource_relationships_target ON resource_relationships(target_id);
123
123
  `);
124
- database.close();
125
124
  }
126
125
 
127
126
  async put(resource: Resource): Promise<Resource> {
128
127
  const normalized = storageResource(resource);
129
- assertUniqueFileResource(normalized);
130
- const database = this.connect();
131
- for (const identifier of normalized.identifiers) {
132
- const row = database.prepare("SELECT resource_id FROM resource_identifiers WHERE service = ? AND identifier = ? AND resource_id != ?").get(identifier.service, identifier.identifier, normalized.id) as ResourceIdRow | undefined;
133
- if (row !== undefined) {
134
- database.close();
135
- throw new Error(`${identifier.service}/${identifier.identifier}`);
136
- }
137
- }
138
- for (const url of normalized.urls) {
139
- const row = database.prepare("SELECT resource_id FROM resource_urls WHERE url = ? AND resource_id != ?").get(url, normalized.id) as ResourceIdRow | undefined;
140
- if (row !== undefined) {
141
- database.close();
142
- throw new Error(url);
143
- }
144
- }
128
+ using database = this.connect();
145
129
  database.exec("BEGIN");
146
130
  database.prepare("INSERT INTO resources (id, type) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET type=excluded.type").run(normalized.id, normalized.type);
147
131
  database.prepare("DELETE FROM resource_identifiers WHERE resource_id = ?").run(normalized.id);
@@ -160,76 +144,58 @@ export class SqliteRegistry implements Registry {
160
144
  const insertRelationship = database.prepare("INSERT INTO resource_relationships (source_id, target_id, type, data_json) VALUES (?, ?, ?, ?)");
161
145
  for (const relationship of normalized.relationships) insertRelationship.run(normalized.id, relationship.target_id, relationship.type, stableJsonCompact(relationship.data));
162
146
  database.exec("COMMIT");
163
- database.close();
164
147
  return normalized;
165
148
  }
166
149
 
167
150
  async get(resourceId: string): Promise<Resource> {
168
- const database = this.connect();
169
- const row = database.prepare("SELECT id FROM resources WHERE id = ?").get(resourceId) as IdRow | undefined;
170
- if (row === undefined) {
171
- database.close();
172
- throw missingResource(resourceId);
173
- }
151
+ using database = this.connect();
174
152
  database.exec("BEGIN");
175
153
  const resource = this.getResource(database, resourceId);
176
154
  database.exec("COMMIT");
177
- database.close();
178
155
  return resource;
179
156
  }
180
157
 
181
158
  async findByIdentifier(service: string, identifier: string): Promise<Resource> {
182
- const database = this.connect();
159
+ using database = this.connect();
183
160
  database.exec("BEGIN");
184
161
  const row = database.prepare("SELECT resource_id FROM resource_identifiers WHERE service = ? AND identifier = ?").get(service, identifier) as ResourceIdRow | undefined;
185
- if (row === undefined) {
186
- database.close();
187
- throw missingIdentifier(service, identifier);
188
- }
162
+ if (row === undefined) throw missingIdentifier(service, identifier);
189
163
  const resource = this.getResource(database, row.resource_id);
190
164
  database.exec("COMMIT");
191
- database.close();
192
165
  return resource;
193
166
  }
194
167
 
195
168
  async findByUrl(url: string): Promise<Resource> {
196
- const database = this.connect();
169
+ using database = this.connect();
197
170
  database.exec("BEGIN");
198
171
  const row = database.prepare("SELECT resource_id FROM resource_urls WHERE url = ?").get(url) as ResourceIdRow | undefined;
199
- if (row === undefined) {
200
- database.close();
201
- throw missingUrl(url);
202
- }
172
+ if (row === undefined) throw missingUrl(url);
203
173
  const resource = this.getResource(database, row.resource_id);
204
174
  database.exec("COMMIT");
205
- database.close();
206
175
  return resource;
207
176
  }
208
177
 
209
178
  async findByPath(path: string): Promise<readonly Resource[]> {
210
- const database = this.connect();
179
+ using database = this.connect();
211
180
  database.exec("BEGIN");
212
181
  const rows = database.prepare("SELECT DISTINCT resource_id FROM filesystem_links WHERE path = ? ORDER BY resource_id").all(path.replaceAll(sep, "/")) as ResourceIdRow[];
213
182
  const resources = rows.map((row) => this.getResource(database, row.resource_id));
214
183
  database.exec("COMMIT");
215
- database.close();
216
184
  return resources;
217
185
  }
218
186
 
219
187
  async listResources(): Promise<readonly Resource[]> {
220
- const database = this.connect();
188
+ using database = this.connect();
221
189
  database.exec("BEGIN");
222
190
  const rows = database.prepare("SELECT id FROM resources ORDER BY id").all() as IdRow[];
223
191
  const resources = rows.map((row) => this.getResource(database, row.id));
224
192
  database.exec("COMMIT");
225
- database.close();
226
193
  return resources;
227
194
  }
228
195
 
229
196
  async delete(resourceId: string): Promise<void> {
230
- const database = this.connect();
197
+ using database = this.connect();
231
198
  database.prepare("DELETE FROM resources WHERE id = ?").run(resourceId);
232
- database.close();
233
199
  }
234
200
 
235
201
  private getResource(database: DatabaseSync, resourceId: string): Resource {
@@ -279,9 +245,10 @@ export class FileRegistry implements Registry {
279
245
  if (existingUrls.has(url)) throw new Error(url);
280
246
  }
281
247
  const tempPath = join(this.path, randomUUID());
282
- const handle = await open(tempPath, "wx", 0o600);
283
- await handle.writeFile(`${stableJsonPretty(normalized)}\n`, "utf8");
284
- await handle.close();
248
+ {
249
+ await using handle = await open(tempPath, "wx", 0o600);
250
+ await handle.writeFile(`${stableJsonPretty(normalized)}\n`, "utf8");
251
+ }
285
252
  await rename(tempPath, this.resourcePath(normalized.id));
286
253
  return normalized;
287
254
  }
@@ -9,15 +9,6 @@ import { FileRegistry, SqliteRegistry } from "./registry.js";
9
9
 
10
10
  export type { InitializedWire, WireBackend, WireConfig } from "../ports.js";
11
11
 
12
- export const defaultWireBackend = "files" satisfies WireBackend;
13
- export const defaultWireRegistryPath = "records";
14
-
15
- export function registryPathForBackend(backend: WireBackend): string {
16
- if (backend === "files") return "records";
17
- if (backend === "sqlite") return "registry.sqlite3";
18
- throw new Error(backend);
19
- }
20
-
21
12
  function canonicalPath(path: string): string {
22
13
  const resolved = resolve(path);
23
14
  if (existsSync(resolved)) return realpathSync(resolved);
@@ -85,7 +76,7 @@ export async function switchWireBackend(path: string, home: string): Promise<Swi
85
76
  const source = config.backend === "sqlite" ? new SqliteRegistry(fromPath) : new FileRegistry(fromPath);
86
77
  const resources = await source.listResources();
87
78
  const to = config.backend === "sqlite" ? "files" : "sqlite";
88
- const targetRelativePath = registryPathForBackend(to);
79
+ const targetRelativePath = to === "sqlite" ? "registry.sqlite3" : "records";
89
80
  const toPath = join(wireRoot, targetRelativePath);
90
81
  await rm(toPath, { recursive: true, force: true });
91
82
  const target = to === "sqlite" ? new SqliteRegistry(toPath) : new FileRegistry(toPath);
package/package.json CHANGED
@@ -1,7 +1,21 @@
1
1
  {
2
2
  "name": "@kamilio/wire",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Attach Notion, Google Docs, Slack, Gmail, Asana, ChatGPT, and Zoom resources as local Markdown you can sync.",
5
+ "keywords": [
6
+ "markdown",
7
+ "sync",
8
+ "notion",
9
+ "google-docs",
10
+ "slack",
11
+ "gmail",
12
+ "asana",
13
+ "chatgpt",
14
+ "zoom",
15
+ "mcp",
16
+ "cli",
17
+ "agent-tools"
18
+ ],
5
19
  "type": "module",
6
20
  "bin": {
7
21
  "wire": "bin/wire.mjs",
@@ -34,6 +48,7 @@
34
48
  }
35
49
  },
36
50
  "files": [
51
+ "README.md",
37
52
  "bin",
38
53
  "dist",
39
54
  "src"
@@ -1,6 +1,6 @@
1
1
  import { UserError, defineCommand, defineGroup, S, type Group } from "toolcraft";
2
2
 
3
- import { defaultWireBackend, registryPathForBackend, type FetchedDocument, type InitializedWire, type Resource, type SwitchedWireBackend, type Wire, type WireBackend, type WireResult, type WireWatchSession } from "wire-core";
3
+ import type { FetchedDocument, InitializedWire, Resource, SwitchedWireBackend, Wire, WireBackend, WireResult, WireWatchSession } from "wire-core";
4
4
  import type { Auth, AuthResult, AuthService } from "../auth.js";
5
5
 
6
6
  export type WireRoot = Group;
@@ -116,22 +116,20 @@ function formatWireCountsRich(result: WireResult, primitives: any): string {
116
116
 
117
117
  function formatWireResult(result: WireResult): string {
118
118
  const local = lineText(primaryFilesystemLink(result.resource).path);
119
- const remote = lineText(result.summary.remote);
120
119
  const titleText = lineText(wireTitle(result.resource));
121
120
  if (result.summary.action === "failed") return [`failed ${titleText}`, `local: ${local}`, formatMultilineField("error", result.summary.error!)].join("\n");
122
- const changed = result.summary.action !== "detached" && (result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0);
121
+ const changed = result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0;
123
122
  const title = changed ? `${result.summary.action} ${formatWireCounts(result)} ${titleText}` : `${result.summary.action} ${titleText}`;
124
- return [title, ...(result.summary.action === "uploaded" ? [`remote: ${remote}`] : []), `local: ${local}`].join("\n");
123
+ return [title, `local: ${local}`].join("\n");
125
124
  }
126
125
 
127
126
  function formatWireResultRich(result: WireResult, primitives: any): string {
128
127
  const local = lineText(primaryFilesystemLink(result.resource).path);
129
- const remote = lineText(result.summary.remote);
130
128
  const titleText = lineText(wireTitle(result.resource));
131
129
  if (result.summary.action === "failed") return [`failed ${titleText}`, `local: ${local}`, formatMultilineField("error", result.summary.error!)].join("\n");
132
- const changed = result.summary.action !== "detached" && (result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0);
130
+ const changed = result.summary.added !== 0 || result.summary.modified !== 0 || result.summary.removed !== 0;
133
131
  const title = changed ? `${result.summary.action} ${formatWireCountsRich(result, primitives)} ${titleText}` : `${result.summary.action} ${titleText}`;
134
- return [title, ...(result.summary.action === "uploaded" ? [`remote: ${remote}`] : []), `local: ${local}`].join("\n");
132
+ return [title, `local: ${local}`].join("\n");
135
133
  }
136
134
 
137
135
  function formatMultilineField(label: string, value: string): string {
@@ -195,12 +193,12 @@ function watchSessionJson(value: WireWatchSession) {
195
193
  }
196
194
 
197
195
  function wireUserErrorMessage(message: string): boolean {
198
- 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 Slides sync is download-only\./.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);
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
197
  }
200
198
 
201
199
  function wireUserErrorDisplayMessage(message: string): string {
202
200
  const unsupportedSource = /^Unsupported source URL: ([\s\S]+)$/.exec(message);
203
- if (unsupportedSource !== null) return `unsupported source\nurl: ${lineText(unsupportedSource[1]!)}\nsupported: Asana, ChatGPT, Gmail, Google Docs/Sheets/Slides, Notion, Slack, Zoom`;
201
+ if (unsupportedSource !== null) return `unsupported source\nurl: ${lineText(unsupportedSource[1]!)}\nsupported: Asana, ChatGPT, Gmail, Google Docs/Sheets, Notion, Slack, Zoom`;
204
202
  const missingWorkspace = /^Wire workspace not initialized\. Run `wire init` or `wire <url>` first\.$/.exec(message);
205
203
  if (missingWorkspace !== null) return "workspace not initialized\nrun: wire init\nattach: wire <url>";
206
204
  const existingWorkspace = /^Wire workspace already initialized with (sqlite|files) registry at ([\s\S]+)\. Existing registries are not overwritten\.$/.exec(message);
@@ -237,32 +235,30 @@ function wireUserErrorDisplayMessage(message: string): string {
237
235
  if (notionIndentedBlock !== null) return `local markdown invalid\nservice: Notion\nindent: ${notionIndentedBlock[1]!}\ndetail: indented block has no parent`;
238
236
  const chatgptDownload = /^ChatGPT conversation download failed\. Run `wire chatgpt login`\.([\s\S]*)$/.exec(message);
239
237
  if (chatgptDownload !== null) return `download failed\nservice: ChatGPT\nlogin: wire chatgpt login\ndetail: ${lineText(chatgptDownload[1]!)}`;
240
- const googleExportMissingFilename = /^Google (Docs Markdown|Sheets CSV|Slides PPTX) export did not include a filename$/.exec(message);
241
- if (googleExportMissingFilename !== null) return `export failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleExportMissingFilename[1]!}\ndetail: missing filename`;
242
- const googleExportHttp = /^Google (Docs Markdown|Sheets CSV|Slides PPTX) export failed: HTTP (\d+)(?: ([\s\S]+))?$/.exec(message);
243
- 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]!)}`}`;
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]!)}`}`;
244
242
  const googleEditorMetadata = /^Google (Docs|Sheets) editor did not include save metadata$/.exec(message);
245
- if (googleEditorMetadata !== null) return `save metadata missing\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleEditorMetadata[1]!} editor`;
243
+ if (googleEditorMetadata !== null) return `save metadata missing\nservice: Google Docs/Sheets\nsource: Google ${googleEditorMetadata[1]!} editor`;
246
244
  const googleSaveFailed = /^Google (Docs|Sheets) save failed: ([\s\S]+)$/.exec(message);
247
- if (googleSaveFailed !== null) return `save failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleSaveFailed[1]!}\ndetail: ${lineText(googleSaveFailed[2]!)}`;
245
+ if (googleSaveFailed !== null) return `save failed\nservice: Google Docs/Sheets\nsource: Google ${googleSaveFailed[1]!}\ndetail: ${lineText(googleSaveFailed[2]!)}`;
248
246
  const googleSaveVerification = /^Google (Docs|Sheets) save verification failed$/.exec(message);
249
- if (googleSaveVerification !== null) return `save verification failed\nservice: Google Docs/Sheets/Slides\nsource: Google ${googleSaveVerification[1]!}`;
250
- 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);
251
- 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`;
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`;
252
250
  const googleDocsMapping = /^Google Docs local edit cannot be mapped to the live document text$/.exec(message);
253
- if (googleDocsMapping !== null) return "local edit not mappable\nservice: Google Docs/Sheets/Slides\nsource: Google Docs\nresolve: edit Google Docs or simplify local Markdown";
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
252
  const googleFormattingOnly = /^Google Docs sync cannot upload formatting-only Markdown edits$/.exec(message);
255
- if (googleFormattingOnly !== null) return "local edit not uploadable\nservice: Google Docs/Sheets/Slides\nsource: Google Docs\ndetail: formatting-only Markdown edit";
256
- const googleSlidesDownloadOnly = /^Google Slides sync is download-only\. Revert local edits or use `wire download <url>` for a fresh copy\.$/.exec(message);
257
- if (googleSlidesDownloadOnly !== null) return "local edit not uploadable\nservice: Google Docs/Sheets/Slides\nsource: Google Slides\ndetail: download-only";
253
+ if (googleFormattingOnly !== null) return "local edit not uploadable\nservice: Google Docs/Sheets\nsource: Google Docs\ndetail: formatting-only Markdown edit";
258
254
  const googleSyncBase = /^Google sync base must ([\s\S]+)$/.exec(message);
259
- if (googleSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets/Slides\ndetail: must ${lineText(googleSyncBase[1]!)}`;
255
+ if (googleSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets\ndetail: must ${lineText(googleSyncBase[1]!)}`;
260
256
  const googleSheetsSyncBase = /^Google Sheets sync base ([\s\S]+)$/.exec(message);
261
- if (googleSheetsSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets/Slides\nsource: Google Sheets\ndetail: ${lineText(googleSheetsSyncBase[1]!)}`;
257
+ if (googleSheetsSyncBase !== null) return `sync base invalid\nservice: Google Docs/Sheets\nsource: Google Sheets\ndetail: ${lineText(googleSheetsSyncBase[1]!)}`;
262
258
  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);
263
- 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`;
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`;
264
260
  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);
265
- if (googleSheetsTable !== null) return `local table invalid\nservice: Google Docs/Sheets/Slides\nsource: Google Sheets\ndetail: requires ${lineText(googleSheetsTable[1]!)}`;
261
+ if (googleSheetsTable !== null) return `local table invalid\nservice: Google Docs/Sheets\nsource: Google Sheets\ndetail: requires ${lineText(googleSheetsTable[1]!)}`;
266
262
  const missingPath = /^Resource path (not found|is not registered): ([\s\S]+)$/.exec(message);
267
263
  if (missingPath !== null) return `${missingPath[1] === "not found" ? "resource not found" : "resource not registered"}\npath: ${lineText(missingPath[2]!)}`;
268
264
  const missingResource = /^Resource (identifier |URL )?not found: ([\s\S]+)$/.exec(message);
@@ -294,7 +290,7 @@ function serviceTitle(service: AuthService): string {
294
290
  asana: "Asana",
295
291
  chatgpt: "ChatGPT",
296
292
  gmail: "Gmail",
297
- "google-docs": "Google Docs/Sheets/Slides",
293
+ "google-docs": "Google Docs/Sheets",
298
294
  notion: "Notion",
299
295
  slack: "Slack",
300
296
  zoom: "Zoom",
@@ -392,9 +388,9 @@ export function createRoot(wire: Wire, currentDirectory: string, auth?: Auth, re
392
388
  name: "init",
393
389
  description: "Initialize a wire workspace in the current directory.",
394
390
  params: S.Object({
395
- backend: S.Optional(S.Enum(["files", "sqlite"], { default: defaultWireBackend, description: "Registry backend: files or sqlite." })),
391
+ backend: S.Optional(S.Enum(["sqlite", "files"], { default: "sqlite", description: "Registry backend: sqlite or files." })),
396
392
  }),
397
- handler: ({ params }) => userFacing(() => wire.init(currentDirectory, params.backend as WireBackend, registryPathForBackend(params.backend as WireBackend))),
393
+ handler: ({ params }) => userFacing(() => wire.init(currentDirectory, params.backend as WireBackend, params.backend === "files" ? "records" : "registry.sqlite3")),
398
394
  render: wirePresentation.init,
399
395
  }),
400
396
  defineCommand({
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(["files", "sqlite"]);
135
- const backendList = "files, sqlite";
136
+ const backendNames = new Set(["sqlite", "files"]);
137
+ const backendList = "sqlite, files";
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, defaultWireBackend, defaultWireRegistryPath, 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, 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> {
@@ -93,7 +93,7 @@ export function createExecutableRoot(environment: NodeEnvironment, currentDirect
93
93
  relativePath: wireRelativePath,
94
94
  switchBackend: switchWireBackend,
95
95
  },
96
- initialization: { backend: defaultWireBackend, registryPath: defaultWireRegistryPath },
96
+ initialization: { backend: "sqlite", registryPath: "registry.sqlite3" },
97
97
  watch: createNodeWatch(),
98
98
  now: runtime.clock.now,
99
99
  open: runtime.openFiles.open,
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
  }