workweave 0.2.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -0
  3. package/dist/ai/local.d.ts +9 -0
  4. package/dist/ai/local.d.ts.map +1 -0
  5. package/dist/ai/local.js +148 -0
  6. package/dist/ai/local.js.map +1 -0
  7. package/dist/ai/provider.d.ts +43 -0
  8. package/dist/ai/provider.d.ts.map +1 -0
  9. package/dist/ai/provider.js +191 -0
  10. package/dist/ai/provider.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +193 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/connectors/github.d.ts +13 -0
  16. package/dist/connectors/github.d.ts.map +1 -0
  17. package/dist/connectors/github.js +212 -0
  18. package/dist/connectors/github.js.map +1 -0
  19. package/dist/connectors/linear.d.ts +12 -0
  20. package/dist/connectors/linear.d.ts.map +1 -0
  21. package/dist/connectors/linear.js +160 -0
  22. package/dist/connectors/linear.js.map +1 -0
  23. package/dist/connectors/registry.d.ts +10 -0
  24. package/dist/connectors/registry.d.ts.map +1 -0
  25. package/dist/connectors/registry.js +36 -0
  26. package/dist/connectors/registry.js.map +1 -0
  27. package/dist/connectors/slack.d.ts +11 -0
  28. package/dist/connectors/slack.d.ts.map +1 -0
  29. package/dist/connectors/slack.js +212 -0
  30. package/dist/connectors/slack.js.map +1 -0
  31. package/dist/connectors/types.d.ts +2 -0
  32. package/dist/connectors/types.d.ts.map +1 -0
  33. package/dist/connectors/types.js +3 -0
  34. package/dist/connectors/types.js.map +1 -0
  35. package/dist/display.d.ts +9 -0
  36. package/dist/display.d.ts.map +1 -0
  37. package/dist/display.js +216 -0
  38. package/dist/display.js.map +1 -0
  39. package/dist/env.d.ts +4 -0
  40. package/dist/env.d.ts.map +1 -0
  41. package/dist/env.js +64 -0
  42. package/dist/env.js.map +1 -0
  43. package/dist/pipeline/ai-synthesize.d.ts +11 -0
  44. package/dist/pipeline/ai-synthesize.d.ts.map +1 -0
  45. package/dist/pipeline/ai-synthesize.js +264 -0
  46. package/dist/pipeline/ai-synthesize.js.map +1 -0
  47. package/dist/pipeline/cluster-id.d.ts +5 -0
  48. package/dist/pipeline/cluster-id.d.ts.map +1 -0
  49. package/dist/pipeline/cluster-id.js +13 -0
  50. package/dist/pipeline/cluster-id.js.map +1 -0
  51. package/dist/pipeline/correlate.d.ts +7 -0
  52. package/dist/pipeline/correlate.d.ts.map +1 -0
  53. package/dist/pipeline/correlate.js +105 -0
  54. package/dist/pipeline/correlate.js.map +1 -0
  55. package/dist/pipeline/ingest.d.ts +17 -0
  56. package/dist/pipeline/ingest.d.ts.map +1 -0
  57. package/dist/pipeline/ingest.js +56 -0
  58. package/dist/pipeline/ingest.js.map +1 -0
  59. package/dist/pipeline/merge-plan.d.ts +14 -0
  60. package/dist/pipeline/merge-plan.d.ts.map +1 -0
  61. package/dist/pipeline/merge-plan.js +70 -0
  62. package/dist/pipeline/merge-plan.js.map +1 -0
  63. package/dist/pipeline/normalize.d.ts +3 -0
  64. package/dist/pipeline/normalize.d.ts.map +1 -0
  65. package/dist/pipeline/normalize.js +169 -0
  66. package/dist/pipeline/normalize.js.map +1 -0
  67. package/dist/pipeline/prioritize.d.ts +6 -0
  68. package/dist/pipeline/prioritize.d.ts.map +1 -0
  69. package/dist/pipeline/prioritize.js +453 -0
  70. package/dist/pipeline/prioritize.js.map +1 -0
  71. package/dist/pipeline/schedule.d.ts +11 -0
  72. package/dist/pipeline/schedule.d.ts.map +1 -0
  73. package/dist/pipeline/schedule.js +98 -0
  74. package/dist/pipeline/schedule.js.map +1 -0
  75. package/dist/setup.d.ts +2 -0
  76. package/dist/setup.d.ts.map +1 -0
  77. package/dist/setup.js +276 -0
  78. package/dist/setup.js.map +1 -0
  79. package/dist/types/index.d.ts +90 -0
  80. package/dist/types/index.d.ts.map +1 -0
  81. package/dist/types/index.js +3 -0
  82. package/dist/types/index.js.map +1 -0
  83. package/package.json +51 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # Workweave
2
+
3
+ > Turn scattered developer signals into a focused, prioritized plan for your day.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![npm version](https://img.shields.io/badge/version-0.2.0-blue.svg)](package.json)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+
9
+ Workweave is a CLI tool that ingests activity from **GitHub**, **Linear**, and **Slack**, then synthesizes a time-boxed workday plan — either using deterministic prioritization rules or an AI model of your choice (local, Anthropic, or OpenAI).
10
+
11
+ ---
12
+
13
+ ## How it works
14
+
15
+ ```
16
+ GitHub ─┐
17
+ Linear ─┼──▶ ingest ──▶ normalize ──▶ correlate ──▶ prioritize ──▶ schedule ──▶ plan
18
+ Slack ─┘ ▲
19
+ (optional AI)
20
+ ```
21
+
22
+ 1. **Ingest** — pull open PRs, assigned issues, mentions, and messages
23
+ 2. **Normalize** — map everything to a common `Artifact` model
24
+ 3. **Correlate** — link related items across sources
25
+ 4. **Prioritize** — score by urgency, importance, social pressure, staleness, and blocking factors
26
+ 5. **Schedule** — fit clusters into your available workday budget
27
+ 6. **Display** — rich terminal output or machine-readable JSON
28
+
29
+ ---
30
+
31
+ ## Features
32
+
33
+ - **Three AI providers** — local model (no key required), Anthropic Claude, or OpenAI
34
+ - **Rules mode** — works fully offline, no API key needed
35
+ - **JSON output** — pipe into scripts, dashboards, or other tools with `--json`
36
+ - **Interactive setup** — guided `workweave setup` wizard configures everything
37
+ - **Connector detection** — `workweave detect` checks which sources are ready before you run
38
+ - **Configurable workday budget** — schedule work to fit your actual available hours
39
+
40
+ ---
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ git clone https://github.com/emin93/workweave.git
46
+ cd workweave
47
+ npm install
48
+ npm run build
49
+ ```
50
+
51
+ To use the `workweave` command globally:
52
+
53
+ ```bash
54
+ npm link
55
+ ```
56
+
57
+ Or run directly without linking:
58
+
59
+ ```bash
60
+ node dist/cli.js <command>
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Quick start
66
+
67
+ **1. Run the interactive setup wizard:**
68
+
69
+ ```bash
70
+ workweave setup
71
+ ```
72
+
73
+ The wizard walks you through:
74
+ - GitHub personal access token (repo scope)
75
+ - AI provider — downloads a local model (~4 GB, runs on CPU) or configures an API key
76
+ - Workday hours (default: 8 h)
77
+ - Linear API key (optional)
78
+ - Slack user token (optional)
79
+
80
+ All credentials are stored in a local `.env` file that is never committed.
81
+
82
+ **2. Check that your connectors are ready:**
83
+
84
+ ```bash
85
+ workweave detect --connectors github,linear,slack
86
+ ```
87
+
88
+ **3. Synthesize your day:**
89
+
90
+ ```bash
91
+ workweave synth --connectors github,linear,slack
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Commands
97
+
98
+ ### `workweave setup`
99
+
100
+ Interactive wizard to configure connectors and AI.
101
+
102
+ ```bash
103
+ workweave setup
104
+ ```
105
+
106
+ ### `workweave detect`
107
+
108
+ Check which connectors are configured and reachable.
109
+
110
+ ```bash
111
+ workweave detect --connectors github,linear,slack
112
+ ```
113
+
114
+ ```
115
+ github ✓ authenticated as emin93
116
+ linear ✓ workspace: Acme Corp
117
+ slack ✗ SLACK_USER_TOKEN not set
118
+ ```
119
+
120
+ ### `workweave synth`
121
+
122
+ Fetch signals and produce a workday plan.
123
+
124
+ ```bash
125
+ # Rules-based (no AI, works offline)
126
+ workweave synth --connectors github,linear
127
+
128
+ # With AI — auto-detects provider (local → Anthropic → OpenAI)
129
+ workweave synth --connectors github,linear,slack --ai
130
+
131
+ # Force a specific provider
132
+ workweave synth --connectors github --ai --provider anthropic
133
+
134
+ # Adjust available time
135
+ workweave synth --connectors github --workday-minutes 360
136
+
137
+ # Machine-readable output
138
+ workweave synth --connectors github --ai --json
139
+ ```
140
+
141
+ ---
142
+
143
+ ## AI providers
144
+
145
+ Workweave supports three AI backends. When `--ai` is set and no `--provider` is specified, it auto-detects in this order:
146
+
147
+ | Priority | Provider | How to configure |
148
+ |----------|----------|-----------------|
149
+ | 1 | **Local model** (Llama, via `node-llama-cpp`) | Run `workweave setup` to download (~4 GB, CPU-only, no key) |
150
+ | 2 | **Anthropic** (`claude-haiku-4-5`) | Set `ANTHROPIC_API_KEY` |
151
+ | 3 | **OpenAI** (`gpt-4o-mini`) | Set `OPENAI_API_KEY` |
152
+
153
+ You can force a provider with `--provider local|anthropic|openai`.
154
+
155
+ ---
156
+
157
+ ## Environment variables
158
+
159
+ | Variable | Description |
160
+ |----------|-------------|
161
+ | `GITHUB_TOKEN` | GitHub personal access token (repo scope) |
162
+ | `LINEAR_API_KEY` | Linear personal API key |
163
+ | `SLACK_USER_TOKEN` | Slack user token (`xoxp-…`) |
164
+ | `ANTHROPIC_API_KEY` | Anthropic API key |
165
+ | `ANTHROPIC_MODEL` | Model override (default: `claude-haiku-4-5`) |
166
+ | `OPENAI_API_KEY` | OpenAI API key |
167
+ | `OPENAI_MODEL` | Model override (default: `gpt-4o-mini`) |
168
+ | `WORKDAY_MINUTES` | Available minutes per day (default: `480`) |
169
+
170
+ Variables can be set in a local `.env` file at the project root (created by `workweave setup`) or exported in your shell.
171
+
172
+ ---
173
+
174
+ ## JSON output
175
+
176
+ Add `--json` to any command for machine-readable output. Progress logs are suppressed and only the result is written to stdout.
177
+
178
+ ```bash
179
+ workweave synth --connectors github --json | jq '.plan.blocks[].title'
180
+ ```
181
+
182
+ **`synth` output shape:**
183
+
184
+ ```jsonc
185
+ {
186
+ "plan": {
187
+ "blocks": [...], // scheduled work blocks
188
+ "synthesisMode": "ai", // "ai" | "rules"
189
+ "synthesisProvider": "local"
190
+ },
191
+ "meta": {
192
+ "connectors": ["github"],
193
+ "rawEvents": 42, // raw events ingested
194
+ "artifacts": 18, // normalized artifacts
195
+ "connectorErrors": [] // non-fatal errors
196
+ }
197
+ }
198
+ ```
199
+
200
+ **`detect` output shape:**
201
+
202
+ ```jsonc
203
+ {
204
+ "connectors": [
205
+ { "id": "github", "ready": true, "detail": "authenticated as emin93" },
206
+ { "id": "linear", "ready": false, "detail": "LINEAR_API_KEY not set" }
207
+ ]
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Project structure
214
+
215
+ ```
216
+ src/
217
+ cli.ts # CLI entry point, argument parsing
218
+ setup.ts # Interactive setup wizard
219
+ display.ts # Terminal rendering (plan, detect, spinner)
220
+ env.ts # .env file loading and writing
221
+ connectors/
222
+ github.ts # GitHub REST API connector
223
+ linear.ts # Linear GraphQL connector
224
+ slack.ts # Slack Web API connector
225
+ registry.ts # Connector registry + detect-all
226
+ pipeline/
227
+ ingest.ts # Orchestrate connector fetching
228
+ normalize.ts # Map raw events → Artifact
229
+ correlate.ts # Link related artifacts across sources
230
+ prioritize.ts # Score and rank clusters
231
+ schedule.ts # Fit clusters into workday budget
232
+ ai-synthesize.ts # AI-powered clustering and titling
233
+ ai/
234
+ provider.ts # LLMProvider interface + OpenAI/Anthropic/LlamaCpp impls
235
+ local.ts # Local model path helpers and download
236
+ types/ # Shared TypeScript models
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Contributing
242
+
243
+ Contributions are welcome. Please open an issue to discuss significant changes before submitting a PR.
244
+
245
+ ```bash
246
+ npm run build # compile TypeScript
247
+ npm run lint # ESLint
248
+ ```
249
+
250
+ ---
251
+
252
+ ## License
253
+
254
+ [MIT](LICENSE)
@@ -0,0 +1,9 @@
1
+ export declare const MODEL_NAME = "Qwen2.5-1.5B-Instruct";
2
+ export declare const MODEL_FILE = "qwen2.5-1.5b-instruct-q4_k_m.gguf";
3
+ export declare const MODEL_SIZE_MB = 986;
4
+ export declare function modelsDir(): string;
5
+ export declare function modelPath(): string;
6
+ export declare function modelExists(): boolean;
7
+ export type ProgressCallback = (downloadedMB: number, totalMB: number, pct: number) => void;
8
+ export declare function downloadModel(onProgress: ProgressCallback): Promise<void>;
9
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/ai/local.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,UAAU,0BAA0B,CAAC;AAClD,eAAO,MAAM,UAAU,sCAAsC,CAAC;AAC9D,eAAO,MAAM,aAAa,MAAM,CAAC;AAMjC,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,MAAM,MAAM,gBAAgB,GAAG,CAC7B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,KACR,IAAI,CAAC;AAEV,wBAAgB,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmGzE"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MODEL_SIZE_MB = exports.MODEL_FILE = exports.MODEL_NAME = void 0;
37
+ exports.modelsDir = modelsDir;
38
+ exports.modelPath = modelPath;
39
+ exports.modelExists = modelExists;
40
+ exports.downloadModel = downloadModel;
41
+ const os_1 = require("os");
42
+ const path_1 = require("path");
43
+ const fs_1 = require("fs");
44
+ const https = __importStar(require("https"));
45
+ const http = __importStar(require("http"));
46
+ exports.MODEL_NAME = "Qwen2.5-1.5B-Instruct";
47
+ exports.MODEL_FILE = "qwen2.5-1.5b-instruct-q4_k_m.gguf";
48
+ exports.MODEL_SIZE_MB = 986;
49
+ const HF_URL = "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/" +
50
+ exports.MODEL_FILE;
51
+ function modelsDir() {
52
+ return (0, path_1.join)((0, os_1.homedir)(), ".workweave", "models");
53
+ }
54
+ function modelPath() {
55
+ return (0, path_1.join)(modelsDir(), exports.MODEL_FILE);
56
+ }
57
+ function modelExists() {
58
+ return (0, fs_1.existsSync)(modelPath());
59
+ }
60
+ function downloadModel(onProgress) {
61
+ return new Promise((resolve, reject) => {
62
+ const dir = modelsDir();
63
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
64
+ const dest = modelPath();
65
+ const tmp = dest + ".part";
66
+ function doRequest(url, redirects = 0) {
67
+ if (redirects > 10) {
68
+ reject(new Error("Too many redirects"));
69
+ return;
70
+ }
71
+ const parsed = new URL(url);
72
+ const transport = parsed.protocol === "https:" ? https : http;
73
+ transport
74
+ .get(url, { headers: { "User-Agent": "workweave/1.0" } }, (res) => {
75
+ const { statusCode, headers: resHeaders } = res;
76
+ if (statusCode === 301 ||
77
+ statusCode === 302 ||
78
+ statusCode === 307 ||
79
+ statusCode === 308) {
80
+ res.resume();
81
+ doRequest(resHeaders.location, redirects + 1);
82
+ return;
83
+ }
84
+ if (statusCode !== 200) {
85
+ res.resume();
86
+ reject(new Error(`Download failed: HTTP ${statusCode}`));
87
+ return;
88
+ }
89
+ const totalBytes = parseInt(resHeaders["content-length"] ?? "0", 10);
90
+ const totalMB = totalBytes
91
+ ? Math.round(totalBytes / 1024 / 1024)
92
+ : exports.MODEL_SIZE_MB;
93
+ let downloaded = 0;
94
+ const ws = (0, fs_1.createWriteStream)(tmp);
95
+ res.on("data", (chunk) => {
96
+ downloaded += chunk.length;
97
+ ws.write(chunk);
98
+ const downloadedMB = Math.round(downloaded / 1024 / 1024);
99
+ const pct = totalBytes
100
+ ? Math.round((downloaded / totalBytes) * 100)
101
+ : 0;
102
+ onProgress(downloadedMB, totalMB, pct);
103
+ });
104
+ res.on("end", () => {
105
+ ws.end(() => {
106
+ // rename .part → final
107
+ const { renameSync } = require("fs");
108
+ try {
109
+ renameSync(tmp, dest);
110
+ }
111
+ catch {
112
+ // cross-device fallback: copy then delete
113
+ const { copyFileSync } = require("fs");
114
+ copyFileSync(tmp, dest);
115
+ (0, fs_1.unlinkSync)(tmp);
116
+ }
117
+ resolve();
118
+ });
119
+ });
120
+ res.on("error", (err) => {
121
+ ws.destroy();
122
+ try {
123
+ (0, fs_1.unlinkSync)(tmp);
124
+ }
125
+ catch { /* ignore */ }
126
+ reject(err);
127
+ });
128
+ ws.on("error", (err) => {
129
+ res.destroy();
130
+ try {
131
+ (0, fs_1.unlinkSync)(tmp);
132
+ }
133
+ catch { /* ignore */ }
134
+ reject(err);
135
+ });
136
+ })
137
+ .on("error", (err) => {
138
+ try {
139
+ (0, fs_1.unlinkSync)(tmp);
140
+ }
141
+ catch { /* ignore */ }
142
+ reject(err);
143
+ });
144
+ }
145
+ doRequest(HF_URL);
146
+ });
147
+ }
148
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/ai/local.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ export interface LLMProvider {
2
+ id: string;
3
+ name: string;
4
+ complete(prompt: string): Promise<string>;
5
+ isAvailable(): Promise<boolean>;
6
+ }
7
+ export interface OpenAIConfig {
8
+ apiKey: string;
9
+ model?: string;
10
+ }
11
+ export interface AnthropicConfig {
12
+ apiKey: string;
13
+ model?: string;
14
+ }
15
+ export declare class OpenAIProvider implements LLMProvider {
16
+ private config;
17
+ id: string;
18
+ name: string;
19
+ constructor(config: OpenAIConfig);
20
+ isAvailable(): Promise<boolean>;
21
+ complete(prompt: string): Promise<string>;
22
+ }
23
+ export declare class AnthropicProvider implements LLMProvider {
24
+ private config;
25
+ id: string;
26
+ name: string;
27
+ constructor(config: AnthropicConfig);
28
+ isAvailable(): Promise<boolean>;
29
+ complete(prompt: string): Promise<string>;
30
+ }
31
+ export declare class LlamaCppProvider implements LLMProvider {
32
+ private readonly _modelPath;
33
+ id: string;
34
+ name: string;
35
+ private _llama;
36
+ private _model;
37
+ constructor(_modelPath: string);
38
+ isAvailable(): Promise<boolean>;
39
+ private static _esmImport;
40
+ private _load;
41
+ complete(prompt: string): Promise<string>;
42
+ }
43
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,cAAe,YAAW,WAAW;IAIpC,OAAO,CAAC,MAAM;IAH1B,EAAE,SAAY;IACd,IAAI,SAAgB;gBAEA,MAAM,EAAE,YAAY;IAElC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA4BhD;AAED,qBAAa,iBAAkB,YAAW,WAAW;IAIvC,OAAO,CAAC,MAAM;IAH1B,EAAE,SAAe;IACjB,IAAI,SAAmB;gBAEH,MAAM,EAAE,eAAe;IAErC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA8BhD;AAED,qBAAa,gBAAiB,YAAW,WAAW;IAStC,OAAO,CAAC,QAAQ,CAAC,UAAU;IARvC,EAAE,SAAW;IACb,IAAI,SAAgC;IAGpC,OAAO,CAAC,MAAM,CAAa;IAE3B,OAAO,CAAC,MAAM,CAAa;gBAEE,UAAU,EAAE,MAAM;IAEzC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAQrC,OAAO,CAAC,MAAM,CAAC,UAAU,CACiC;YAE5C,KAAK;IAOb,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAWhD"}
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LlamaCppProvider = exports.AnthropicProvider = exports.OpenAIProvider = void 0;
37
+ const https = __importStar(require("https"));
38
+ const http = __importStar(require("http"));
39
+ const fs_1 = require("fs");
40
+ class OpenAIProvider {
41
+ config;
42
+ id = "openai";
43
+ name = "OpenAI API";
44
+ constructor(config) {
45
+ this.config = config;
46
+ }
47
+ async isAvailable() {
48
+ return !!this.config.apiKey;
49
+ }
50
+ async complete(prompt) {
51
+ const model = this.config.model || "gpt-4o-mini";
52
+ const url = new URL("/v1/chat/completions", "https://api.openai.com");
53
+ const body = JSON.stringify({
54
+ model,
55
+ messages: [{ role: "user", content: prompt }],
56
+ temperature: 0.3,
57
+ response_format: { type: "json_object" },
58
+ });
59
+ return httpRequest({
60
+ hostname: url.hostname,
61
+ port: url.port || (url.protocol === "https:" ? 443 : 80),
62
+ path: url.pathname,
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ Authorization: `Bearer ${this.config.apiKey}`,
67
+ },
68
+ protocol: url.protocol,
69
+ body,
70
+ timeoutMs: 30_000,
71
+ }).then((raw) => {
72
+ const parsed = JSON.parse(raw);
73
+ return parsed.choices?.[0]?.message?.content ?? "";
74
+ });
75
+ }
76
+ }
77
+ exports.OpenAIProvider = OpenAIProvider;
78
+ class AnthropicProvider {
79
+ config;
80
+ id = "anthropic";
81
+ name = "Anthropic API";
82
+ constructor(config) {
83
+ this.config = config;
84
+ }
85
+ async isAvailable() {
86
+ return !!this.config.apiKey;
87
+ }
88
+ async complete(prompt) {
89
+ const model = this.config.model || "claude-haiku-4-5";
90
+ const url = new URL("/v1/messages", "https://api.anthropic.com");
91
+ const body = JSON.stringify({
92
+ model,
93
+ max_tokens: 4096,
94
+ messages: [{ role: "user", content: prompt }],
95
+ });
96
+ return httpRequest({
97
+ hostname: url.hostname,
98
+ port: 443,
99
+ path: url.pathname,
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ "x-api-key": this.config.apiKey,
104
+ "anthropic-version": "2023-06-01",
105
+ },
106
+ protocol: "https:",
107
+ body,
108
+ timeoutMs: 60_000,
109
+ }).then((raw) => {
110
+ const parsed = JSON.parse(raw);
111
+ const textBlock = parsed.content
112
+ ?.find((b) => b.type === "text");
113
+ return textBlock?.text ?? "";
114
+ });
115
+ }
116
+ }
117
+ exports.AnthropicProvider = AnthropicProvider;
118
+ class LlamaCppProvider {
119
+ _modelPath;
120
+ id = "local";
121
+ name = "Local model (Qwen2.5-1.5B)";
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ _llama = null;
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ _model = null;
126
+ constructor(_modelPath) {
127
+ this._modelPath = _modelPath;
128
+ }
129
+ async isAvailable() {
130
+ return (0, fs_1.existsSync)(this._modelPath);
131
+ }
132
+ // node-llama-cpp is ESM-only with top-level await, so require() cannot load
133
+ // it. This function bypasses TypeScript's import()->require() transform and
134
+ // calls the real ESM dynamic import loader instead.
135
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
136
+ static _esmImport = new Function("m", "return import(m)");
137
+ async _load() {
138
+ if (this._model)
139
+ return;
140
+ const { getLlama } = await LlamaCppProvider._esmImport("node-llama-cpp");
141
+ this._llama = await getLlama({ gpu: false });
142
+ this._model = await this._llama.loadModel({ modelPath: this._modelPath });
143
+ }
144
+ async complete(prompt) {
145
+ await this._load();
146
+ const { LlamaChatSession } = await LlamaCppProvider._esmImport("node-llama-cpp");
147
+ const context = await this._model.createContext({ contextSize: 4096 });
148
+ const session = new LlamaChatSession({
149
+ contextSequence: context.getSequence(),
150
+ });
151
+ const response = await session.prompt(prompt);
152
+ await context.dispose();
153
+ return response;
154
+ }
155
+ }
156
+ exports.LlamaCppProvider = LlamaCppProvider;
157
+ function httpRequest(opts) {
158
+ const transport = opts.protocol === "https:" ? https : http;
159
+ return new Promise((resolve, reject) => {
160
+ const req = transport.request({
161
+ hostname: opts.hostname,
162
+ port: opts.port,
163
+ path: opts.path,
164
+ method: opts.method,
165
+ headers: {
166
+ ...opts.headers,
167
+ ...(opts.body ? { "Content-Length": Buffer.byteLength(opts.body) } : {}),
168
+ },
169
+ }, (res) => {
170
+ let data = "";
171
+ res.on("data", (chunk) => (data += chunk));
172
+ res.on("end", () => {
173
+ if (res.statusCode && res.statusCode >= 400) {
174
+ reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
175
+ }
176
+ else {
177
+ resolve(data);
178
+ }
179
+ });
180
+ });
181
+ req.on("error", reject);
182
+ req.setTimeout(opts.timeoutMs, () => {
183
+ req.destroy();
184
+ reject(new Error(`Request timed out after ${opts.timeoutMs}ms`));
185
+ });
186
+ if (opts.body)
187
+ req.write(opts.body);
188
+ req.end();
189
+ });
190
+ }
191
+ //# sourceMappingURL=provider.js.map