@ghcrawl/api-core 0.1.0 → 0.1.1

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 (63) hide show
  1. package/README.md +25 -0
  2. package/dist/api/server.d.ts +4 -0
  3. package/dist/api/server.d.ts.map +1 -0
  4. package/dist/api/server.js +142 -0
  5. package/dist/api/server.js.map +1 -0
  6. package/dist/cluster/build.d.ts +16 -0
  7. package/dist/cluster/build.d.ts.map +1 -0
  8. package/dist/cluster/build.js +62 -0
  9. package/dist/cluster/build.js.map +1 -0
  10. package/dist/config.d.ts +83 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +257 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/db/migrate.d.ts +3 -0
  15. package/dist/db/migrate.d.ts.map +1 -0
  16. package/{src/db/migrate.ts → dist/db/migrate.js} +30 -36
  17. package/dist/db/migrate.js.map +1 -0
  18. package/dist/db/sqlite.d.ts +4 -0
  19. package/dist/db/sqlite.d.ts.map +1 -0
  20. package/dist/db/sqlite.js +11 -0
  21. package/dist/db/sqlite.js.map +1 -0
  22. package/dist/documents/normalize.d.ts +23 -0
  23. package/dist/documents/normalize.d.ts.map +1 -0
  24. package/dist/documents/normalize.js +36 -0
  25. package/dist/documents/normalize.js.map +1 -0
  26. package/dist/github/client.d.ts +24 -0
  27. package/dist/github/client.d.ts.map +1 -0
  28. package/dist/github/client.js +170 -0
  29. package/dist/github/client.js.map +1 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/{src/index.ts → dist/index.js} +1 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/openai/provider.d.ts +44 -0
  35. package/dist/openai/provider.d.ts.map +1 -0
  36. package/dist/openai/provider.js +107 -0
  37. package/dist/openai/provider.js.map +1 -0
  38. package/dist/search/exact.d.ts +14 -0
  39. package/dist/search/exact.d.ts.map +1 -0
  40. package/dist/search/exact.js +26 -0
  41. package/dist/search/exact.js.map +1 -0
  42. package/dist/service.d.ts +247 -0
  43. package/dist/service.d.ts.map +1 -0
  44. package/dist/service.js +1735 -0
  45. package/dist/service.js.map +1 -0
  46. package/package.json +6 -5
  47. package/src/api/server.test.ts +0 -296
  48. package/src/api/server.ts +0 -171
  49. package/src/cluster/build.test.ts +0 -18
  50. package/src/cluster/build.ts +0 -74
  51. package/src/config.test.ts +0 -247
  52. package/src/config.ts +0 -421
  53. package/src/db/migrate.test.ts +0 -30
  54. package/src/db/sqlite.ts +0 -14
  55. package/src/documents/normalize.test.ts +0 -25
  56. package/src/documents/normalize.ts +0 -52
  57. package/src/github/client.ts +0 -241
  58. package/src/openai/provider.ts +0 -141
  59. package/src/search/exact.test.ts +0 -22
  60. package/src/search/exact.ts +0 -28
  61. package/src/service.test.ts +0 -2036
  62. package/src/service.ts +0 -2497
  63. package/src/types/better-sqlite3.d.ts +0 -1
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @ghcrawl/api-core
2
+
3
+ Internal core library for `ghcrawl`.
4
+
5
+ This package contains the Node.js runtime for:
6
+
7
+ - GitHub sync
8
+ - SQLite storage
9
+ - OpenAI embeddings
10
+ - search
11
+ - clustering
12
+ - HTTP route adapters
13
+
14
+ Most users should not install this package directly.
15
+
16
+ Use the main CLI package instead:
17
+
18
+ ```bash
19
+ npm install -g ghcrawl
20
+ ```
21
+
22
+ Project docs:
23
+
24
+ - [GitHub repository](https://github.com/pwrdrvr/ghcrawl)
25
+ - [Project README](https://github.com/pwrdrvr/ghcrawl#readme)
@@ -0,0 +1,4 @@
1
+ import http from 'node:http';
2
+ import { GHCrawlService } from '../service.js';
3
+ export declare function createApiServer(service: GHCrawlService): http.Server;
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAK7B,OAAO,EAAE,cAAc,EAAmB,MAAM,eAAe,CAAC;AAgBhE,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC,MAAM,CA4IpE"}
@@ -0,0 +1,142 @@
1
+ import http from 'node:http';
2
+ import { actionRequestSchema, refreshRequestSchema } from '@ghcrawl/api-contract';
3
+ import { ZodError } from 'zod';
4
+ import { GHCrawlService, parseRepoParams } from '../service.js';
5
+ function sendJson(res, status, payload) {
6
+ res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
7
+ res.end(JSON.stringify(payload));
8
+ }
9
+ async function readBody(req) {
10
+ const chunks = [];
11
+ for await (const chunk of req) {
12
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
13
+ }
14
+ if (chunks.length === 0)
15
+ return null;
16
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
17
+ }
18
+ export function createApiServer(service) {
19
+ return http.createServer(async (req, res) => {
20
+ try {
21
+ if (!req.url || !req.method) {
22
+ sendJson(res, 400, { error: 'Missing request metadata' });
23
+ return;
24
+ }
25
+ const url = new URL(req.url, 'http://127.0.0.1');
26
+ if (req.method === 'GET' && url.pathname === '/health') {
27
+ sendJson(res, 200, service.init());
28
+ return;
29
+ }
30
+ if (req.method === 'GET' && url.pathname === '/repositories') {
31
+ sendJson(res, 200, service.listRepositories());
32
+ return;
33
+ }
34
+ if (req.method === 'GET' && url.pathname === '/threads') {
35
+ const params = parseRepoParams(url);
36
+ const kindParam = url.searchParams.get('kind');
37
+ const kind = kindParam === 'issue' || kindParam === 'pull_request' ? kindParam : undefined;
38
+ sendJson(res, 200, service.listThreads({ ...params, kind }));
39
+ return;
40
+ }
41
+ if (req.method === 'GET' && url.pathname === '/search') {
42
+ const params = parseRepoParams(url);
43
+ const query = url.searchParams.get('query');
44
+ if (!query) {
45
+ sendJson(res, 400, { error: 'Missing query parameter' });
46
+ return;
47
+ }
48
+ const modeParam = url.searchParams.get('mode');
49
+ const mode = modeParam === 'keyword' || modeParam === 'semantic' || modeParam === 'hybrid' ? modeParam : undefined;
50
+ sendJson(res, 200, await service.searchRepository({ ...params, query, mode }));
51
+ return;
52
+ }
53
+ if (req.method === 'GET' && url.pathname === '/neighbors') {
54
+ const params = parseRepoParams(url);
55
+ const numberValue = url.searchParams.get('number');
56
+ if (!numberValue) {
57
+ sendJson(res, 400, { error: 'Missing number parameter' });
58
+ return;
59
+ }
60
+ const threadNumber = Number(numberValue);
61
+ if (!Number.isInteger(threadNumber) || threadNumber <= 0) {
62
+ sendJson(res, 400, { error: 'Invalid number parameter' });
63
+ return;
64
+ }
65
+ const limitValue = url.searchParams.get('limit');
66
+ const minScoreValue = url.searchParams.get('minScore');
67
+ sendJson(res, 200, service.listNeighbors({
68
+ ...params,
69
+ threadNumber,
70
+ limit: limitValue ? Number(limitValue) : undefined,
71
+ minScore: minScoreValue ? Number(minScoreValue) : undefined,
72
+ }));
73
+ return;
74
+ }
75
+ if (req.method === 'GET' && url.pathname === '/clusters') {
76
+ const params = parseRepoParams(url);
77
+ sendJson(res, 200, service.listClusters(params));
78
+ return;
79
+ }
80
+ if (req.method === 'GET' && url.pathname === '/cluster-summaries') {
81
+ const params = parseRepoParams(url);
82
+ const sortParam = url.searchParams.get('sort');
83
+ const sort = sortParam === 'recent' || sortParam === 'size' ? sortParam : undefined;
84
+ const minSizeValue = url.searchParams.get('minSize');
85
+ const limitValue = url.searchParams.get('limit');
86
+ const search = url.searchParams.get('search') ?? undefined;
87
+ sendJson(res, 200, service.listClusterSummaries({
88
+ ...params,
89
+ minSize: minSizeValue ? Number(minSizeValue) : undefined,
90
+ limit: limitValue ? Number(limitValue) : undefined,
91
+ sort,
92
+ search,
93
+ }));
94
+ return;
95
+ }
96
+ if (req.method === 'GET' && url.pathname === '/cluster-detail') {
97
+ const params = parseRepoParams(url);
98
+ const clusterIdValue = url.searchParams.get('clusterId');
99
+ if (!clusterIdValue) {
100
+ sendJson(res, 400, { error: 'Missing clusterId parameter' });
101
+ return;
102
+ }
103
+ const clusterId = Number(clusterIdValue);
104
+ if (!Number.isInteger(clusterId) || clusterId <= 0) {
105
+ sendJson(res, 400, { error: 'Invalid clusterId parameter' });
106
+ return;
107
+ }
108
+ const memberLimitValue = url.searchParams.get('memberLimit');
109
+ const bodyCharsValue = url.searchParams.get('bodyChars');
110
+ sendJson(res, 200, service.getClusterDetailDump({
111
+ ...params,
112
+ clusterId,
113
+ memberLimit: memberLimitValue ? Number(memberLimitValue) : undefined,
114
+ bodyChars: bodyCharsValue ? Number(bodyCharsValue) : undefined,
115
+ }));
116
+ return;
117
+ }
118
+ if (req.method === 'POST' && url.pathname === '/actions/rerun') {
119
+ const body = actionRequestSchema.parse(await readBody(req));
120
+ sendJson(res, 200, await service.rerunAction(body));
121
+ return;
122
+ }
123
+ if (req.method === 'POST' && url.pathname === '/actions/refresh') {
124
+ const body = refreshRequestSchema.parse(await readBody(req));
125
+ sendJson(res, 200, await service.refreshRepository(body));
126
+ return;
127
+ }
128
+ sendJson(res, 404, { error: 'Not found' });
129
+ }
130
+ catch (error) {
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ sendJson(res, isBadRequestError(error, message) ? 400 : 500, { error: message });
133
+ }
134
+ });
135
+ }
136
+ function isBadRequestError(error, message) {
137
+ return (error instanceof SyntaxError ||
138
+ error instanceof ZodError ||
139
+ message.startsWith('Missing ') ||
140
+ message.startsWith('Invalid '));
141
+ }
142
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAE/B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhE,SAAS,QAAQ,CAAC,GAAwB,EAAE,MAAc,EAAE,OAAgB;IAC1E,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE,CAAC,CAAC;IAC7E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,GAAyB;IAC/C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAY,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAuB;IACrD,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1C,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;gBAC5B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;YAEjD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACvD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;gBACnC,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;gBAC7D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;gBACxD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBACpC,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC/C,MAAM,IAAI,GAAG,SAAS,KAAK,OAAO,IAAI,SAAS,KAAK,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC3F,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC7D,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACvD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBACpC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;oBACzD,OAAO;gBACT,CAAC;gBACD,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC/C,MAAM,IAAI,GAAG,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,UAAU,IAAI,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;gBACnH,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC/E,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC1D,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBACpC,MAAM,WAAW,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACnD,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;oBAC1D,OAAO;gBACT,CAAC;gBACD,MAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;oBACzD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;oBAC1D,OAAO;gBACT,CAAC;gBACD,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACjD,MAAM,aAAa,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACvD,QAAQ,CACN,GAAG,EACH,GAAG,EACH,OAAO,CAAC,aAAa,CAAC;oBACpB,GAAG,MAAM;oBACT,YAAY;oBACZ,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;oBAClD,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS;iBAC5D,CAAC,CACH,CAAC;gBACF,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBACzD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBACpC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;gBACjD,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,oBAAoB,EAAE,CAAC;gBAClE,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBACpC,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC/C,MAAM,IAAI,GAAG,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;gBACpF,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACrD,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACjD,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC;gBAC3D,QAAQ,CACN,GAAG,EACH,GAAG,EACH,OAAO,CAAC,oBAAoB,CAAC;oBAC3B,GAAG,MAAM;oBACT,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS;oBACxD,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;oBAClD,IAAI;oBACJ,MAAM;iBACP,CAAC,CACH,CAAC;gBACF,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,iBAAiB,EAAE,CAAC;gBAC/D,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;gBACpC,MAAM,cAAc,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACzD,IAAI,CAAC,cAAc,EAAE,CAAC;oBACpB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;oBAC7D,OAAO;gBACT,CAAC;gBACD,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;oBACnD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;oBAC7D,OAAO;gBACT,CAAC;gBACD,MAAM,gBAAgB,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBAC7D,MAAM,cAAc,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACzD,QAAQ,CACN,GAAG,EACH,GAAG,EACH,OAAO,CAAC,oBAAoB,CAAC;oBAC3B,GAAG,MAAM;oBACT,SAAS;oBACT,WAAW,EAAE,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;oBACpE,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS;iBAC/D,CAAC,CACH,CAAC;gBACF,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,gBAAgB,EAAE,CAAC;gBAC/D,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,kBAAkB,EAAE,CAAC;gBACjE,MAAM,IAAI,GAAG,oBAAoB,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,OAAe;IACxD,OAAO,CACL,KAAK,YAAY,WAAW;QAC5B,KAAK,YAAY,QAAQ;QACzB,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC;QAC9B,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ export type SimilarityEdge = {
2
+ leftThreadId: number;
3
+ rightThreadId: number;
4
+ score: number;
5
+ };
6
+ type Node = {
7
+ threadId: number;
8
+ number: number;
9
+ title: string;
10
+ };
11
+ export declare function buildClusters(nodes: Node[], edges: SimilarityEdge[]): Array<{
12
+ representativeThreadId: number;
13
+ members: number[];
14
+ }>;
15
+ export {};
16
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cluster/build.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,IAAI,GAAG;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA8BF,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAiClI"}
@@ -0,0 +1,62 @@
1
+ class UnionFind {
2
+ parent = new Map();
3
+ add(value) {
4
+ if (!this.parent.has(value))
5
+ this.parent.set(value, value);
6
+ }
7
+ find(value) {
8
+ const parent = this.parent.get(value);
9
+ if (parent === undefined) {
10
+ this.parent.set(value, value);
11
+ return value;
12
+ }
13
+ if (parent === value)
14
+ return value;
15
+ const root = this.find(parent);
16
+ this.parent.set(value, root);
17
+ return root;
18
+ }
19
+ union(left, right) {
20
+ const leftRoot = this.find(left);
21
+ const rightRoot = this.find(right);
22
+ if (leftRoot !== rightRoot) {
23
+ this.parent.set(rightRoot, leftRoot);
24
+ }
25
+ }
26
+ }
27
+ export function buildClusters(nodes, edges) {
28
+ const uf = new UnionFind();
29
+ for (const node of nodes)
30
+ uf.add(node.threadId);
31
+ for (const edge of edges)
32
+ uf.union(edge.leftThreadId, edge.rightThreadId);
33
+ const byRoot = new Map();
34
+ for (const node of nodes) {
35
+ const root = uf.find(node.threadId);
36
+ const list = byRoot.get(root) ?? [];
37
+ list.push(node.threadId);
38
+ byRoot.set(root, list);
39
+ }
40
+ const edgeCounts = new Map();
41
+ for (const edge of edges) {
42
+ edgeCounts.set(edge.leftThreadId, (edgeCounts.get(edge.leftThreadId) ?? 0) + 1);
43
+ edgeCounts.set(edge.rightThreadId, (edgeCounts.get(edge.rightThreadId) ?? 0) + 1);
44
+ }
45
+ const nodesById = new Map(nodes.map((node) => [node.threadId, node]));
46
+ return Array.from(byRoot.values())
47
+ .map((members) => {
48
+ const representative = [...members].sort((leftId, rightId) => {
49
+ const left = nodesById.get(leftId);
50
+ const right = nodesById.get(rightId);
51
+ const edgeDelta = (edgeCounts.get(rightId) ?? 0) - (edgeCounts.get(leftId) ?? 0);
52
+ if (edgeDelta !== 0)
53
+ return edgeDelta;
54
+ if (!left || !right)
55
+ return leftId - rightId;
56
+ return left.number - right.number;
57
+ })[0];
58
+ return { representativeThreadId: representative, members: members.sort((left, right) => left - right) };
59
+ })
60
+ .sort((left, right) => right.members.length - left.members.length);
61
+ }
62
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/cluster/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ export type ConfigValueSource = 'env' | 'config' | 'dotenv' | 'default' | 'none';
2
+ export type SecretProvider = 'plaintext' | 'op';
3
+ export type TuiSortPreference = 'recent' | 'size';
4
+ export type TuiMinClusterSize = 0 | 1 | 10 | 20 | 50;
5
+ export type TuiRepositoryPreference = {
6
+ minClusterSize: TuiMinClusterSize;
7
+ sortMode: TuiSortPreference;
8
+ };
9
+ export type PersistedGitcrawlConfig = {
10
+ githubToken?: string;
11
+ openaiApiKey?: string;
12
+ secretProvider?: SecretProvider;
13
+ opVaultName?: string;
14
+ opItemName?: string;
15
+ dbPath?: string;
16
+ apiPort?: number;
17
+ summaryModel?: string;
18
+ embedModel?: string;
19
+ embedBatchSize?: number;
20
+ embedConcurrency?: number;
21
+ embedMaxUnread?: number;
22
+ openSearchUrl?: string;
23
+ openSearchIndex?: string;
24
+ tuiPreferences?: Record<string, TuiRepositoryPreference>;
25
+ };
26
+ export type GitcrawlConfig = {
27
+ workspaceRoot: string;
28
+ configDir: string;
29
+ configPath: string;
30
+ configFileExists: boolean;
31
+ dbPath: string;
32
+ dbPathSource: ConfigValueSource;
33
+ apiPort: number;
34
+ githubToken?: string;
35
+ githubTokenSource: ConfigValueSource;
36
+ openaiApiKey?: string;
37
+ openaiApiKeySource: ConfigValueSource;
38
+ secretProvider: SecretProvider;
39
+ opVaultName?: string;
40
+ opItemName?: string;
41
+ summaryModel: string;
42
+ embedModel: string;
43
+ embedBatchSize: number;
44
+ embedConcurrency: number;
45
+ embedMaxUnread: number;
46
+ openSearchUrl?: string;
47
+ openSearchIndex: string;
48
+ tuiPreferences: Record<string, TuiRepositoryPreference>;
49
+ };
50
+ type LoadedStoredConfig = {
51
+ configDir: string;
52
+ configPath: string;
53
+ exists: boolean;
54
+ data: PersistedGitcrawlConfig;
55
+ };
56
+ type LoadConfigOptions = {
57
+ cwd?: string;
58
+ env?: NodeJS.ProcessEnv;
59
+ platform?: NodeJS.Platform;
60
+ };
61
+ export declare function getConfigDir(options?: LoadConfigOptions): string;
62
+ export declare function getConfigPath(options?: LoadConfigOptions): string;
63
+ export declare function readPersistedConfig(options?: LoadConfigOptions): LoadedStoredConfig;
64
+ export declare function writePersistedConfig(values: PersistedGitcrawlConfig, options?: LoadConfigOptions): {
65
+ configPath: string;
66
+ };
67
+ export declare function isLikelyGitHubToken(value: string): boolean;
68
+ export declare function isLikelyOpenAiApiKey(value: string): boolean;
69
+ export declare function loadConfig(options?: LoadConfigOptions): GitcrawlConfig;
70
+ export declare function ensureRuntimeDirs(config: GitcrawlConfig): void;
71
+ export declare function getTuiRepositoryPreference(config: GitcrawlConfig, owner: string, repo: string): TuiRepositoryPreference;
72
+ export declare function writeTuiRepositoryPreference(config: GitcrawlConfig, params: {
73
+ owner: string;
74
+ repo: string;
75
+ minClusterSize: TuiMinClusterSize;
76
+ sortMode: TuiSortPreference;
77
+ }): {
78
+ configPath: string;
79
+ };
80
+ export declare function requireGithubToken(config: GitcrawlConfig): string;
81
+ export declare function requireOpenAiKey(config: GitcrawlConfig): string;
82
+ export {};
83
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,257 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import dotenv from 'dotenv';
5
+ function pathModuleForPlatform(platform) {
6
+ return platform === 'win32' ? path.win32 : path;
7
+ }
8
+ function findWorkspaceRoot(start) {
9
+ let current = path.resolve(start);
10
+ while (true) {
11
+ if (fs.existsSync(path.join(current, 'pnpm-workspace.yaml'))) {
12
+ return current;
13
+ }
14
+ const parent = path.dirname(current);
15
+ if (parent === current)
16
+ return path.resolve(start);
17
+ current = parent;
18
+ }
19
+ }
20
+ function resolveHomeDirectory(env) {
21
+ const home = env.HOME ?? env.USERPROFILE ?? os.homedir();
22
+ return path.resolve(home);
23
+ }
24
+ export function getConfigDir(options = {}) {
25
+ const env = options.env ?? process.env;
26
+ const platform = options.platform ?? process.platform;
27
+ const pathModule = pathModuleForPlatform(platform);
28
+ if (env.XDG_CONFIG_HOME) {
29
+ return pathModule.resolve(env.XDG_CONFIG_HOME, 'ghcrawl');
30
+ }
31
+ if (platform === 'win32' && env.APPDATA) {
32
+ return pathModule.resolve(env.APPDATA, 'ghcrawl');
33
+ }
34
+ return pathModule.join(resolveHomeDirectory(env), '.config', 'ghcrawl');
35
+ }
36
+ export function getConfigPath(options = {}) {
37
+ const platform = options.platform ?? process.platform;
38
+ const pathModule = pathModuleForPlatform(platform);
39
+ return pathModule.join(getConfigDir(options), 'config.json');
40
+ }
41
+ function readDotenvFile(workspaceRoot) {
42
+ const dotenvPath = path.join(workspaceRoot, '.env.local');
43
+ if (!fs.existsSync(dotenvPath)) {
44
+ return {};
45
+ }
46
+ return dotenv.parse(fs.readFileSync(dotenvPath, 'utf8'));
47
+ }
48
+ function pickDefined(...values) {
49
+ for (const entry of values) {
50
+ if (entry.value !== undefined && entry.value !== null) {
51
+ return entry;
52
+ }
53
+ }
54
+ return { source: 'none', value: undefined };
55
+ }
56
+ function getString(value) {
57
+ return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
58
+ }
59
+ function getEnvString(env, primary, legacy) {
60
+ return getString(env[primary]) ?? (legacy ? getString(env[legacy]) : undefined);
61
+ }
62
+ function getDotenvString(values, primary, legacy) {
63
+ return getString(values[primary]) ?? (legacy ? getString(values[legacy]) : undefined);
64
+ }
65
+ function getNumber(value) {
66
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
67
+ }
68
+ function getSecretProvider(value) {
69
+ return value === 'plaintext' || value === 'op' ? value : undefined;
70
+ }
71
+ function getTuiSortPreference(value) {
72
+ return value === 'recent' || value === 'size' ? value : undefined;
73
+ }
74
+ function getTuiMinClusterSize(value) {
75
+ return value === 0 || value === 1 || value === 10 || value === 20 || value === 50 ? value : undefined;
76
+ }
77
+ function getTuiPreferences(value) {
78
+ if (!value || typeof value !== 'object') {
79
+ return undefined;
80
+ }
81
+ const preferences = {};
82
+ for (const [fullName, preference] of Object.entries(value)) {
83
+ if (!preference || typeof preference !== 'object') {
84
+ continue;
85
+ }
86
+ const record = preference;
87
+ const minClusterSize = getTuiMinClusterSize(record.minClusterSize);
88
+ const sortMode = getTuiSortPreference(record.sortMode);
89
+ if (minClusterSize === undefined || sortMode === undefined) {
90
+ continue;
91
+ }
92
+ preferences[fullName] = { minClusterSize, sortMode };
93
+ }
94
+ return preferences;
95
+ }
96
+ export function readPersistedConfig(options = {}) {
97
+ const configDir = getConfigDir(options);
98
+ const configPath = getConfigPath(options);
99
+ if (!fs.existsSync(configPath)) {
100
+ return { configDir, configPath, exists: false, data: {} };
101
+ }
102
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
103
+ return {
104
+ configDir,
105
+ configPath,
106
+ exists: true,
107
+ data: {
108
+ githubToken: getString(raw.githubToken),
109
+ openaiApiKey: getString(raw.openaiApiKey),
110
+ secretProvider: getSecretProvider(raw.secretProvider),
111
+ opVaultName: getString(raw.opVaultName),
112
+ opItemName: getString(raw.opItemName),
113
+ dbPath: getString(raw.dbPath),
114
+ apiPort: getNumber(raw.apiPort),
115
+ summaryModel: getString(raw.summaryModel),
116
+ embedModel: getString(raw.embedModel),
117
+ embedBatchSize: getNumber(raw.embedBatchSize),
118
+ embedConcurrency: getNumber(raw.embedConcurrency),
119
+ embedMaxUnread: getNumber(raw.embedMaxUnread),
120
+ openSearchUrl: getString(raw.openSearchUrl),
121
+ openSearchIndex: getString(raw.openSearchIndex),
122
+ tuiPreferences: getTuiPreferences(raw.tuiPreferences),
123
+ },
124
+ };
125
+ }
126
+ export function writePersistedConfig(values, options = {}) {
127
+ const current = readPersistedConfig(options);
128
+ fs.mkdirSync(current.configDir, { recursive: true });
129
+ const next = {
130
+ ...current.data,
131
+ ...values,
132
+ };
133
+ fs.writeFileSync(current.configPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
134
+ return { configPath: current.configPath };
135
+ }
136
+ function resolveConfiguredPath(configDir, value) {
137
+ return path.isAbsolute(value) ? value : path.resolve(configDir, value);
138
+ }
139
+ function getWorkspaceDbPath(workspaceRoot) {
140
+ const workspacePath = path.join(workspaceRoot, 'data', 'ghcrawl.db');
141
+ return fs.existsSync(workspacePath) ? workspacePath : null;
142
+ }
143
+ function parseIntegerSetting(name, raw) {
144
+ const parsed = Number(raw);
145
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
146
+ throw new Error(`Invalid ${name}: ${raw}`);
147
+ }
148
+ return parsed;
149
+ }
150
+ export function isLikelyGitHubToken(value) {
151
+ return /^(gh[pousr]_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+)$/.test(value.trim());
152
+ }
153
+ export function isLikelyOpenAiApiKey(value) {
154
+ return /^sk-[A-Za-z0-9._-]+$/.test(value.trim());
155
+ }
156
+ export function loadConfig(options = {}) {
157
+ const cwd = options.cwd ?? process.cwd();
158
+ const env = options.env ?? process.env;
159
+ const platform = options.platform ?? process.platform;
160
+ const workspaceRoot = findWorkspaceRoot(cwd);
161
+ const stored = readPersistedConfig({ cwd, env, platform });
162
+ const dotenvValues = readDotenvFile(workspaceRoot);
163
+ const githubToken = pickDefined({ source: 'env', value: getString(env.GITHUB_TOKEN) }, { source: 'config', value: stored.data.githubToken }, { source: 'dotenv', value: getString(dotenvValues.GITHUB_TOKEN) });
164
+ const openaiApiKey = pickDefined({ source: 'env', value: getString(env.OPENAI_API_KEY) }, { source: 'config', value: stored.data.openaiApiKey }, { source: 'dotenv', value: getString(dotenvValues.OPENAI_API_KEY) });
165
+ const configuredDbPath = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_DB_PATH', 'GHCRAWL_DB_PATH') }, { source: 'config', value: stored.data.dbPath }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_DB_PATH', 'GHCRAWL_DB_PATH') });
166
+ const workspaceDbPath = configuredDbPath.value === undefined ? getWorkspaceDbPath(workspaceRoot) : null;
167
+ const dbPathValue = workspaceDbPath !== null
168
+ ? { source: 'default', value: workspaceDbPath }
169
+ : pickDefined(configuredDbPath, { source: 'default', value: 'ghcrawl.db' });
170
+ const apiPortValue = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_API_PORT', 'GHCRAWL_API_PORT') }, { source: 'config', value: stored.data.apiPort }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_API_PORT', 'GHCRAWL_API_PORT') }, { source: 'default', value: '5179' });
171
+ const embedBatchSizeValue = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_EMBED_BATCH_SIZE', 'GHCRAWL_EMBED_BATCH_SIZE') }, { source: 'config', value: stored.data.embedBatchSize }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_EMBED_BATCH_SIZE', 'GHCRAWL_EMBED_BATCH_SIZE') }, { source: 'default', value: '8' });
172
+ const embedConcurrencyValue = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_EMBED_CONCURRENCY', 'GHCRAWL_EMBED_CONCURRENCY') }, { source: 'config', value: stored.data.embedConcurrency }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_EMBED_CONCURRENCY', 'GHCRAWL_EMBED_CONCURRENCY') }, { source: 'default', value: '10' });
173
+ const embedMaxUnreadValue = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_EMBED_MAX_UNREAD', 'GHCRAWL_EMBED_MAX_UNREAD') }, { source: 'config', value: stored.data.embedMaxUnread }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_EMBED_MAX_UNREAD', 'GHCRAWL_EMBED_MAX_UNREAD') }, { source: 'default', value: '20' });
174
+ const summaryModel = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_SUMMARY_MODEL', 'GHCRAWL_SUMMARY_MODEL') }, { source: 'config', value: stored.data.summaryModel }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_SUMMARY_MODEL', 'GHCRAWL_SUMMARY_MODEL') }, { source: 'default', value: 'gpt-5-mini' });
175
+ const embedModel = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_EMBED_MODEL', 'GHCRAWL_EMBED_MODEL') }, { source: 'config', value: stored.data.embedModel }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_EMBED_MODEL', 'GHCRAWL_EMBED_MODEL') }, { source: 'default', value: 'text-embedding-3-large' });
176
+ const openSearchUrl = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_OPENSEARCH_URL', 'GHCRAWL_OPENSEARCH_URL') }, { source: 'config', value: stored.data.openSearchUrl }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_OPENSEARCH_URL', 'GHCRAWL_OPENSEARCH_URL') });
177
+ const openSearchIndex = pickDefined({ source: 'env', value: getEnvString(env, 'GHCRAWL_OPENSEARCH_INDEX', 'GHCRAWL_OPENSEARCH_INDEX') }, { source: 'config', value: stored.data.openSearchIndex }, { source: 'dotenv', value: getDotenvString(dotenvValues, 'GHCRAWL_OPENSEARCH_INDEX', 'GHCRAWL_OPENSEARCH_INDEX') }, { source: 'default', value: 'ghcrawl-threads' });
178
+ const dbPath = dbPathValue.value && path.isAbsolute(dbPathValue.value)
179
+ ? dbPathValue.value
180
+ : resolveConfiguredPath(stored.configDir, dbPathValue.value ?? 'ghcrawl.db');
181
+ const apiPort = parseIntegerSetting('GHCRAWL_API_PORT', String(apiPortValue.value ?? '5179'));
182
+ const embedBatchSize = parseIntegerSetting('GHCRAWL_EMBED_BATCH_SIZE', String(embedBatchSizeValue.value ?? '8'));
183
+ const embedConcurrency = parseIntegerSetting('GHCRAWL_EMBED_CONCURRENCY', String(embedConcurrencyValue.value ?? '10'));
184
+ const embedMaxUnread = parseIntegerSetting('GHCRAWL_EMBED_MAX_UNREAD', String(embedMaxUnreadValue.value ?? '20'));
185
+ return {
186
+ workspaceRoot,
187
+ configDir: stored.configDir,
188
+ configPath: stored.configPath,
189
+ configFileExists: stored.exists,
190
+ dbPath,
191
+ dbPathSource: dbPathValue.source,
192
+ apiPort,
193
+ githubToken: githubToken.value,
194
+ githubTokenSource: githubToken.source,
195
+ openaiApiKey: openaiApiKey.value,
196
+ openaiApiKeySource: openaiApiKey.source,
197
+ secretProvider: stored.data.secretProvider ?? 'plaintext',
198
+ opVaultName: stored.data.opVaultName,
199
+ opItemName: stored.data.opItemName,
200
+ summaryModel: summaryModel.value ?? 'gpt-5-mini',
201
+ embedModel: embedModel.value ?? 'text-embedding-3-large',
202
+ embedBatchSize,
203
+ embedConcurrency,
204
+ embedMaxUnread,
205
+ openSearchUrl: openSearchUrl.value,
206
+ openSearchIndex: openSearchIndex.value ?? 'ghcrawl-threads',
207
+ tuiPreferences: stored.data.tuiPreferences ?? {},
208
+ };
209
+ }
210
+ export function ensureRuntimeDirs(config) {
211
+ fs.mkdirSync(config.configDir, { recursive: true });
212
+ fs.mkdirSync(path.dirname(config.dbPath), { recursive: true });
213
+ }
214
+ export function getTuiRepositoryPreference(config, owner, repo) {
215
+ return config.tuiPreferences[`${owner}/${repo}`] ?? { minClusterSize: 10, sortMode: 'recent' };
216
+ }
217
+ export function writeTuiRepositoryPreference(config, params) {
218
+ const fullName = `${params.owner}/${params.repo}`;
219
+ const nextPreferences = {
220
+ ...config.tuiPreferences,
221
+ [fullName]: {
222
+ minClusterSize: params.minClusterSize,
223
+ sortMode: params.sortMode,
224
+ },
225
+ };
226
+ config.tuiPreferences = nextPreferences;
227
+ const next = fs.existsSync(config.configPath)
228
+ ? {
229
+ ...JSON.parse(fs.readFileSync(config.configPath, 'utf8')),
230
+ tuiPreferences: nextPreferences,
231
+ }
232
+ : {
233
+ tuiPreferences: nextPreferences,
234
+ };
235
+ fs.mkdirSync(config.configDir, { recursive: true });
236
+ fs.writeFileSync(config.configPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
237
+ return { configPath: config.configPath };
238
+ }
239
+ export function requireGithubToken(config) {
240
+ if (!config.githubToken) {
241
+ if (config.secretProvider === 'op' && config.opVaultName && config.opItemName) {
242
+ throw new Error(`Missing GitHub token in the environment. This config is set to use 1Password CLI via ${config.opVaultName}/${config.opItemName}; run ghcrawl through your op wrapper or set GITHUB_TOKEN. Expected config at ${config.configPath}`);
243
+ }
244
+ throw new Error(`Missing GitHub token. Run ghcrawl init or set GITHUB_TOKEN. Expected config at ${config.configPath}`);
245
+ }
246
+ return config.githubToken;
247
+ }
248
+ export function requireOpenAiKey(config) {
249
+ if (!config.openaiApiKey) {
250
+ if (config.secretProvider === 'op' && config.opVaultName && config.opItemName) {
251
+ throw new Error(`Missing OpenAI API key in the environment. This config is set to use 1Password CLI via ${config.opVaultName}/${config.opItemName}; run ghcrawl through your op wrapper or set OPENAI_API_KEY. Expected config at ${config.configPath}`);
252
+ }
253
+ throw new Error(`Missing OpenAI API key. Run ghcrawl init or set OPENAI_API_KEY. Expected config at ${config.configPath}`);
254
+ }
255
+ return config.openaiApiKey;
256
+ }
257
+ //# sourceMappingURL=config.js.map