jowork 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{chunk-ROIINI33.js → chunk-4PIT2GZ4.js} +13 -1
  2. package/dist/{chunk-XLYRHKG6.js → chunk-54SD5GBF.js} +1 -1
  3. package/dist/chunk-63AMINQC.js +156 -0
  4. package/dist/{chunk-XAEGXSEO.js → chunk-74AHY7X6.js} +4 -0
  5. package/dist/{chunk-7U3SXINY.js → chunk-ATAUWJYD.js} +320 -50
  6. package/dist/chunk-DQW74UCN.js +671 -0
  7. package/dist/chunk-EYP6WMFF.js +153 -0
  8. package/dist/{chunk-JSTXMDXI.js → chunk-FCFZCZHR.js} +1 -1
  9. package/dist/chunk-FX6Z3QHV.js +34 -0
  10. package/dist/chunk-HENAABEL.js +419 -0
  11. package/dist/chunk-OXWWOKC7.js +201 -0
  12. package/dist/chunk-QGHJ45PL.js +661 -0
  13. package/dist/chunk-RO3KK5RC.js +132 -0
  14. package/dist/{chunk-JE6TOU7W.js → chunk-TFMF3EXE.js} +2 -7
  15. package/dist/{chunk-TN327MDF.js → chunk-VX662YLA.js} +3 -3
  16. package/dist/cli.js +338 -149
  17. package/dist/{config-AI6UIJJN.js → config-FH2XLN7A.js} +2 -2
  18. package/dist/content-reader-VPGTR2SF.js +10 -0
  19. package/dist/context-ZNI3WOB7.js +10 -0
  20. package/dist/{credential-store-ZRZCSRPC.js → credential-store-OS5ZY4OW.js} +2 -2
  21. package/dist/{feishu-A6YVFKEN.js → feishu-XW5T6ER2.js} +8 -3
  22. package/dist/{git-manager-N35XSG4Y.js → git-manager-RVWV2GSV.js} +2 -1
  23. package/dist/github-PQKAYTLO.js +11 -0
  24. package/dist/{paths-JXOMBYIT.js → paths-FFRET6F7.js} +7 -3
  25. package/dist/{server-5GVWN2NB.js → server-WEADPUST.js} +59 -66
  26. package/dist/{setup-IDQDPCEJ.js → setup-S2S2CHB2.js} +91 -32
  27. package/dist/sync-SRLFR5NA.js +21 -0
  28. package/dist/transport.js +6 -4
  29. package/package.json +1 -1
  30. package/src/dashboard/public/app.js +34 -8
  31. package/src/dashboard/public/style.css +14 -0
  32. package/dist/chunk-AIXKXEYS.js +0 -547
  33. package/dist/chunk-L5ZR7TSK.js +0 -82
  34. package/dist/chunk-LS2AJM5A.js +0 -163
  35. package/dist/chunk-QMOFQX7X.js +0 -612
  36. package/dist/chunk-YJWTKFWX.js +0 -451
  37. package/dist/github-SHWUFNYB.js +0 -10
  38. package/dist/sync-7V54N62M.js +0 -18
@@ -1,451 +0,0 @@
1
- import {
2
- contentHash,
3
- formatIssue,
4
- formatPullRequest
5
- } from "./chunk-QMOFQX7X.js";
6
- import {
7
- createId
8
- } from "./chunk-JE6TOU7W.js";
9
- import {
10
- logError,
11
- logInfo
12
- } from "./chunk-MYDK7MWB.js";
13
-
14
- // src/sync/linker.ts
15
- var PATTERNS = [
16
- // GitHub/GitLab PR/Issue references
17
- { type: "pr", regex: /(?:PR|pr|Pull Request|pull request)\s*#?(\d+)/g, confidence: "high" },
18
- { type: "issue", regex: /(?:issue|Issue|ISSUE)\s*#?(\d+)/g, confidence: "high" },
19
- { type: "issue", regex: /#(\d{2,6})\b/g, confidence: "medium" },
20
- // bare #123
21
- // Linear-style issue keys (e.g. LIN-234, PROJ-56)
22
- // Requires 2+ digit number to reduce false positives (GPT-5, GLP-1 are NOT issues)
23
- { type: "issue", regex: /\b([A-Z]{2,10}-\d{2,6})\b/g, confidence: "high" },
24
- // Git commit SHA
25
- { type: "commit", regex: /\b([0-9a-f]{7,40})\b/g, confidence: "low" },
26
- // URLs
27
- { type: "url", regex: /https?:\/\/[^\s<>"{}|\\^`\[\]]+/g, confidence: "high" },
28
- // @mentions (feishu user_id format)
29
- { type: "mention", regex: /@([a-zA-Z0-9_]+)/g, confidence: "medium" },
30
- // Action items (Chinese + English)
31
- { type: "action_item", regex: /(?:需要|TODO|FIXME|待办|截止|deadline|action item|任务)[::\s]+([^\n。.]{5,80})/gi, confidence: "medium" }
32
- ];
33
- function extractLinks(content) {
34
- const links = [];
35
- const seen = /* @__PURE__ */ new Set();
36
- for (const pattern of PATTERNS) {
37
- if (pattern.type === "commit" && content.length < 100) continue;
38
- const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
39
- let match;
40
- while ((match = regex.exec(content)) !== null) {
41
- const identifier = match[1] ?? match[0];
42
- const key = `${pattern.type}:${identifier}`;
43
- if (seen.has(key)) continue;
44
- seen.add(key);
45
- if (identifier.length < 3 && pattern.type !== "pr") continue;
46
- if (pattern.type === "commit" && identifier.length < 7) continue;
47
- let metadata;
48
- if (pattern.type === "action_item") {
49
- const surroundingText = content.slice(
50
- Math.max(0, (match.index ?? 0) - 50),
51
- Math.min(content.length, (match.index ?? 0) + match[0].length + 50)
52
- );
53
- const mentionMatch = surroundingText.match(/@([a-zA-Z0-9_]+)/);
54
- const dateMatch = surroundingText.match(
55
- /(\d{4}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}月\d{1,2}[日号]|\d{1,2}\/\d{1,2})/
56
- );
57
- if (mentionMatch || dateMatch) {
58
- metadata = {};
59
- if (mentionMatch) metadata.assignee = mentionMatch[1];
60
- if (dateMatch) metadata.dueDate = dateMatch[1];
61
- }
62
- }
63
- links.push({
64
- linkType: pattern.type,
65
- identifier,
66
- confidence: pattern.confidence,
67
- ...metadata ? { metadata } : {}
68
- });
69
- }
70
- }
71
- return links;
72
- }
73
- function processObjectLinks(sqlite, objectId, content) {
74
- const links = extractLinks(content);
75
- if (links.length === 0) return 0;
76
- const insert = sqlite.prepare(`
77
- INSERT OR IGNORE INTO object_links (id, source_object_id, target_object_id, link_type, identifier, metadata, confidence, created_at)
78
- VALUES (?, ?, NULL, ?, ?, ?, ?, ?)
79
- `);
80
- const now = Date.now();
81
- let count = 0;
82
- const batch = sqlite.transaction(() => {
83
- for (const link of links) {
84
- const id = `${objectId}:${link.linkType}:${link.identifier}`.slice(0, 64);
85
- insert.run(
86
- id,
87
- objectId,
88
- link.linkType,
89
- link.identifier,
90
- link.metadata ? JSON.stringify(link.metadata) : null,
91
- link.confidence,
92
- now
93
- );
94
- count++;
95
- }
96
- });
97
- batch();
98
- return count;
99
- }
100
- function linkAllUnprocessed(sqlite) {
101
- const unprocessed = sqlite.prepare(`
102
- SELECT o.id, ob.content FROM objects o
103
- JOIN object_bodies ob ON ob.object_id = o.id
104
- WHERE o.links_processed = 0
105
- LIMIT 1000
106
- `).all();
107
- if (unprocessed.length === 0) return { processed: 0, linksCreated: 0 };
108
- let linksCreated = 0;
109
- const markProcessed = sqlite.prepare("UPDATE objects SET links_processed = 1 WHERE id = ?");
110
- const batch = sqlite.transaction(() => {
111
- for (const obj of unprocessed) {
112
- linksCreated += processObjectLinks(sqlite, obj.id, obj.content);
113
- markProcessed.run(obj.id);
114
- }
115
- });
116
- batch();
117
- logInfo("linker", `Processed ${unprocessed.length} objects, created ${linksCreated} links`);
118
- return { processed: unprocessed.length, linksCreated };
119
- }
120
-
121
- // src/sync/gitlab.ts
122
- var defaultLogger = {
123
- info: (msg, ctx) => logInfo("sync", msg, ctx),
124
- warn: (msg, ctx) => logError("sync", msg, ctx),
125
- error: (msg, ctx) => logError("sync", msg, ctx)
126
- };
127
- var MAX_PROJECTS = 20;
128
- var RATE_LIMIT_DELAY_MS = 200;
129
- async function syncGitLab(sqlite, data, logger = defaultLogger, fileWriter) {
130
- const token = data.token;
131
- if (!token) throw new Error("Missing GitLab token");
132
- const baseUrl = data.apiUrl || "https://gitlab.com";
133
- const headers = {
134
- "PRIVATE-TOKEN": token
135
- };
136
- let projects = 0;
137
- let issues = 0;
138
- let mrs = 0;
139
- let newObjects = 0;
140
- const projectsRes = await fetch(
141
- `${baseUrl}/api/v4/projects?membership=true&per_page=20&order_by=last_activity_at`,
142
- { headers }
143
- );
144
- if (!projectsRes.ok) {
145
- if (projectsRes.status === 401) throw new Error("GitLab token expired or invalid");
146
- throw new Error(`GitLab API error: ${projectsRes.status}`);
147
- }
148
- const projectList = await projectsRes.json();
149
- projects = projectList.length;
150
- logger.info(`Found ${projects} GitLab projects`);
151
- const checkExists = sqlite.prepare("SELECT id FROM objects WHERE uri = ?");
152
- const insertObj = sqlite.prepare(`
153
- INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, last_synced_at, created_at)
154
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
155
- `);
156
- const insertBody = sqlite.prepare(`
157
- INSERT OR REPLACE INTO object_bodies (object_id, content, content_type, fetched_at)
158
- VALUES (?, ?, ?, ?)
159
- `);
160
- const insertFts = sqlite.prepare(`
161
- INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
162
- VALUES (?, ?, ?, ?, ?, ?, ?)
163
- `);
164
- const getRowid = sqlite.prepare("SELECT rowid FROM objects WHERE id = ?");
165
- for (const project of projectList.slice(0, MAX_PROJECTS)) {
166
- const encodedPath = encodeURIComponent(project.path_with_namespace);
167
- try {
168
- const issuesRes = await fetch(
169
- `${baseUrl}/api/v4/projects/${encodedPath}/issues?state=all&per_page=30&order_by=updated_at`,
170
- { headers }
171
- );
172
- if (issuesRes.ok) {
173
- const issueList = await issuesRes.json();
174
- const batchInsert = sqlite.transaction((items) => {
175
- for (const item of items) {
176
- const uri = `gitlab://${project.path_with_namespace}/issue/${item.iid}`;
177
- if (checkExists.get(uri)) continue;
178
- const now = Date.now();
179
- const id = createId("obj");
180
- const title = `${project.path_with_namespace}#${item.iid}: ${item.title}`;
181
- const summary = item.description ? item.description.length > 200 ? item.description.slice(0, 200) + "..." : item.description : item.title;
182
- const tags = JSON.stringify(["gitlab", "issue", item.state, ...item.labels]);
183
- const body = formatGitLabIssueBody(item, project.path_with_namespace);
184
- const hash = contentHash(title + (item.description ?? ""));
185
- insertObj.run(id, "gitlab", "issue", uri, title, summary, tags, hash, now, new Date(item.created_at).getTime());
186
- insertBody.run(id, body, "text/plain", now);
187
- try {
188
- const rowid = getRowid.get(id);
189
- if (rowid) {
190
- const excerpt = body.length > 500 ? body.slice(0, 500) : body;
191
- insertFts.run(rowid.rowid, title, summary ?? "", tags, "gitlab", "issue", excerpt);
192
- }
193
- } catch {
194
- }
195
- if (fileWriter) {
196
- try {
197
- const fileContent = formatIssue({
198
- source: "gitlab",
199
- repo: project.path_with_namespace,
200
- number: item.iid,
201
- title: item.title,
202
- state: item.state,
203
- author: item.author?.username ?? "unknown",
204
- labels: item.labels,
205
- created: item.created_at,
206
- uri,
207
- body: item.description ?? ""
208
- });
209
- const filePath = fileWriter.writeObject("gitlab", "issue", {
210
- id,
211
- repo: project.path_with_namespace,
212
- number: item.iid,
213
- title: item.title
214
- }, fileContent);
215
- sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
216
- } catch {
217
- }
218
- }
219
- newObjects++;
220
- issues++;
221
- }
222
- });
223
- batchInsert(issueList);
224
- } else {
225
- logger.warn(`Failed to fetch issues for ${project.path_with_namespace}: ${issuesRes.status}`);
226
- }
227
- } catch (err) {
228
- logger.warn(`Error fetching issues for ${project.path_with_namespace}: ${err}`);
229
- }
230
- try {
231
- const mrsRes = await fetch(
232
- `${baseUrl}/api/v4/projects/${encodedPath}/merge_requests?state=all&per_page=30&order_by=updated_at`,
233
- { headers }
234
- );
235
- if (mrsRes.ok) {
236
- const mrList = await mrsRes.json();
237
- const batchInsert = sqlite.transaction((items) => {
238
- for (const item of items) {
239
- const uri = `gitlab://${project.path_with_namespace}/merge_request/${item.iid}`;
240
- if (checkExists.get(uri)) continue;
241
- const now = Date.now();
242
- const id = createId("obj");
243
- const title = `${project.path_with_namespace}!${item.iid}: ${item.title}`;
244
- const summary = item.description ? item.description.length > 200 ? item.description.slice(0, 200) + "..." : item.description : item.title;
245
- const tags = JSON.stringify(["gitlab", "merge_request", item.state, ...item.labels]);
246
- const body = formatGitLabMRBody(item, project.path_with_namespace);
247
- const hash = contentHash(title + (item.description ?? ""));
248
- insertObj.run(id, "gitlab", "merge_request", uri, title, summary, tags, hash, now, new Date(item.created_at).getTime());
249
- insertBody.run(id, body, "text/plain", now);
250
- try {
251
- const rowid = getRowid.get(id);
252
- if (rowid) {
253
- const excerpt = body.length > 500 ? body.slice(0, 500) : body;
254
- insertFts.run(rowid.rowid, title, summary ?? "", tags, "gitlab", "merge_request", excerpt);
255
- }
256
- } catch {
257
- }
258
- if (fileWriter) {
259
- try {
260
- const fileContent = formatPullRequest({
261
- source: "gitlab",
262
- repo: project.path_with_namespace,
263
- number: item.iid,
264
- title: item.title,
265
- state: item.state,
266
- author: item.author?.username ?? "unknown",
267
- labels: item.labels,
268
- created: item.created_at,
269
- uri,
270
- body: item.description ?? "",
271
- sourceBranch: item.source_branch,
272
- targetBranch: item.target_branch
273
- });
274
- const filePath = fileWriter.writeObject("gitlab", "merge_request", {
275
- id,
276
- repo: project.path_with_namespace,
277
- number: item.iid,
278
- title: item.title
279
- }, fileContent);
280
- sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
281
- } catch {
282
- }
283
- }
284
- newObjects++;
285
- mrs++;
286
- }
287
- });
288
- batchInsert(mrList);
289
- } else {
290
- logger.warn(`Failed to fetch MRs for ${project.path_with_namespace}: ${mrsRes.status}`);
291
- }
292
- } catch (err) {
293
- logger.warn(`Error fetching MRs for ${project.path_with_namespace}: ${err}`);
294
- }
295
- await new Promise((r) => setTimeout(r, RATE_LIMIT_DELAY_MS));
296
- }
297
- logger.info("GitLab sync complete", { projects, issues, mrs, newObjects });
298
- return { projects, issues, mrs, newObjects };
299
- }
300
- function formatGitLabIssueBody(item, project) {
301
- return [
302
- `${project}#${item.iid}: ${item.title}`,
303
- `State: ${item.state} | Author: ${item.author?.username ?? "unknown"} | Created: ${item.created_at}`,
304
- `Labels: ${item.labels.join(", ") || "none"}`,
305
- "",
306
- item.description ?? "(no description)"
307
- ].join("\n");
308
- }
309
- function formatGitLabMRBody(item, project) {
310
- return [
311
- `${project}!${item.iid}: ${item.title}`,
312
- `State: ${item.state} | Author: ${item.author?.username ?? "unknown"} | Created: ${item.created_at}`,
313
- `Branch: ${item.source_branch} \u2192 ${item.target_branch}`,
314
- `Labels: ${item.labels.join(", ") || "none"}`,
315
- "",
316
- item.description ?? "(no description)"
317
- ].join("\n");
318
- }
319
-
320
- // src/sync/linear.ts
321
- var defaultLogger2 = {
322
- info: (msg, ctx) => logInfo("sync", msg, ctx),
323
- warn: (msg, ctx) => logError("sync", msg, ctx),
324
- error: (msg, ctx) => logError("sync", msg, ctx)
325
- };
326
- var LINEAR_API = "https://api.linear.app/graphql";
327
- var ISSUES_QUERY = `
328
- query {
329
- issues(first: 50, orderBy: updatedAt) {
330
- nodes {
331
- id
332
- identifier
333
- title
334
- description
335
- url
336
- state { name }
337
- assignee { name }
338
- labels { nodes { name } }
339
- createdAt
340
- updatedAt
341
- }
342
- }
343
- }
344
- `;
345
- async function syncLinear(sqlite, data, logger = defaultLogger2, fileWriter) {
346
- const apiKey = data.apiKey;
347
- if (!apiKey) throw new Error("Missing Linear API key");
348
- const headers = {
349
- "Content-Type": "application/json",
350
- Authorization: apiKey
351
- };
352
- let issues = 0;
353
- let newObjects = 0;
354
- const res = await fetch(LINEAR_API, {
355
- method: "POST",
356
- headers,
357
- body: JSON.stringify({ query: ISSUES_QUERY })
358
- });
359
- if (!res.ok) {
360
- if (res.status === 401) throw new Error("Linear API key expired or invalid");
361
- throw new Error(`Linear API error: ${res.status}`);
362
- }
363
- const body = await res.json();
364
- if (body.errors?.length) {
365
- throw new Error(`Linear GraphQL error: ${body.errors[0].message}`);
366
- }
367
- const issueList = body.data?.issues?.nodes ?? [];
368
- issues = issueList.length;
369
- logger.info(`Found ${issues} Linear issues`);
370
- const checkExists = sqlite.prepare("SELECT id FROM objects WHERE uri = ?");
371
- const insertObj = sqlite.prepare(`
372
- INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, last_synced_at, created_at)
373
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
374
- `);
375
- const insertBody = sqlite.prepare(`
376
- INSERT OR REPLACE INTO object_bodies (object_id, content, content_type, fetched_at)
377
- VALUES (?, ?, ?, ?)
378
- `);
379
- const insertFts = sqlite.prepare(`
380
- INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
381
- VALUES (?, ?, ?, ?, ?, ?, ?)
382
- `);
383
- const getRowid = sqlite.prepare("SELECT rowid FROM objects WHERE id = ?");
384
- const batchInsert = sqlite.transaction((items) => {
385
- for (const item of items) {
386
- const uri = `linear://${item.identifier}`;
387
- if (checkExists.get(uri)) continue;
388
- const now = Date.now();
389
- const id = createId("obj");
390
- const title = `${item.identifier}: ${item.title}`;
391
- const summary = item.description ? item.description.length > 200 ? item.description.slice(0, 200) + "..." : item.description : item.title;
392
- const labelNames = item.labels.nodes.map((l) => l.name);
393
- const tags = JSON.stringify(["linear", "issue", item.state.name, ...labelNames]);
394
- const bodyText = formatLinearIssueBody(item);
395
- const hash = contentHash(title + (item.description ?? ""));
396
- insertObj.run(id, "linear", "issue", uri, title, summary, tags, hash, now, new Date(item.createdAt).getTime());
397
- insertBody.run(id, bodyText, "text/plain", now);
398
- try {
399
- const rowid = getRowid.get(id);
400
- if (rowid) {
401
- const excerpt = bodyText.length > 500 ? bodyText.slice(0, 500) : bodyText;
402
- insertFts.run(rowid.rowid, title, summary ?? "", tags, "linear", "issue", excerpt);
403
- }
404
- } catch {
405
- }
406
- if (fileWriter) {
407
- try {
408
- const labelNames2 = item.labels.nodes.map((l) => l.name);
409
- const fileContent = formatIssue({
410
- source: "linear",
411
- repo: item.identifier.split("-")[0] ?? "linear",
412
- number: parseInt(item.identifier.split("-")[1] ?? "0"),
413
- title: item.title,
414
- state: item.state.name,
415
- author: item.assignee?.name ?? "unassigned",
416
- labels: labelNames2,
417
- created: item.createdAt,
418
- uri: `linear://${item.identifier}`,
419
- body: item.description ?? ""
420
- });
421
- const filePath = fileWriter.writeObject("linear", "issue", {
422
- id,
423
- identifier: item.identifier,
424
- title: item.title
425
- }, fileContent);
426
- sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
427
- } catch {
428
- }
429
- }
430
- newObjects++;
431
- }
432
- });
433
- batchInsert(issueList);
434
- logger.info("Linear sync complete", { issues, newObjects });
435
- return { issues, newObjects };
436
- }
437
- function formatLinearIssueBody(item) {
438
- return [
439
- `${item.identifier}: ${item.title}`,
440
- `State: ${item.state.name} | Assignee: ${item.assignee?.name ?? "unassigned"} | Created: ${item.createdAt}`,
441
- `Labels: ${item.labels.nodes.map((l) => l.name).join(", ") || "none"}`,
442
- "",
443
- item.description ?? "(no description)"
444
- ].join("\n");
445
- }
446
-
447
- export {
448
- linkAllUnprocessed,
449
- syncGitLab,
450
- syncLinear
451
- };
@@ -1,10 +0,0 @@
1
- import {
2
- syncGitHub
3
- } from "./chunk-LS2AJM5A.js";
4
- import "./chunk-QMOFQX7X.js";
5
- import "./chunk-JE6TOU7W.js";
6
- import "./chunk-MYDK7MWB.js";
7
- import "./chunk-UJ4KEHGZ.js";
8
- export {
9
- syncGitHub
10
- };
@@ -1,18 +0,0 @@
1
- import {
2
- runSync,
3
- syncCommand
4
- } from "./chunk-AIXKXEYS.js";
5
- import "./chunk-YJWTKFWX.js";
6
- import "./chunk-XAEGXSEO.js";
7
- import "./chunk-XLYRHKG6.js";
8
- import "./chunk-ROIINI33.js";
9
- import "./chunk-LS2AJM5A.js";
10
- import "./chunk-QMOFQX7X.js";
11
- import "./chunk-JE6TOU7W.js";
12
- import "./chunk-L5ZR7TSK.js";
13
- import "./chunk-MYDK7MWB.js";
14
- import "./chunk-UJ4KEHGZ.js";
15
- export {
16
- runSync,
17
- syncCommand
18
- };