@tiens.nguyen/gonext-local-worker 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,120 +1,3 @@
1
- # gonext-local-worker
2
-
3
- Runs on **your Mac** next to **Ollama** or any **OpenAI-compatible** local server. It:
4
-
5
- 1. Polls **`POST /api/worker/jobs/next`** on your GoNext cloud API (Lambda).
6
- 2. Runs the job against **`payload.baseURL`** (your LAN `http://127.0.0.1:11434/v1` etc.).
7
- 3. **`PATCH`**es **`running`** → **`completed`** / **`failed`** so DynamoDB and the web app update.
8
-
9
- Set your Firebase **worker user id** in the worker env so it claims your own queued jobs.
10
-
11
- ## Install
12
-
13
- ```bash
14
- npm install -g @tiens.nguyen/gonext-local-worker
15
- ```
16
-
17
- Or from source:
18
-
19
- ```bash
20
- cd tools/gonext-local-worker
21
- npm install
22
- npm link
23
- ```
24
-
25
- Requires **Node.js 18+**.
26
-
27
- ## Configure
28
-
29
- Create `~/.gonext/worker.env` (optional):
30
-
31
- ```bash
32
- mkdir -p ~/.gonext
33
- cat > ~/.gonext/worker.env << 'EOF'
34
- GONEXT_API_BASE=https://YOUR_API.execute-api.ap-southeast-1.amazonaws.com
35
- GONEXT_WORKER_USER_ID=paste-your-firebase-user-id
36
- GONEXT_POLL_MS=1500
37
- EOF
38
- chmod 600 ~/.gonext/worker.env
39
- ```
40
-
41
- Or export in the shell:
42
-
43
- ```bash
44
- export GONEXT_API_BASE=https://....execute-api....amazonaws.com
45
- export GONEXT_WORKER_USER_ID=...
46
- ```
47
-
48
- ## Run
49
-
50
- ```bash
51
- gonext-local-worker
52
- ```
53
-
54
- Options:
55
-
56
- ```bash
57
- gonext-local-worker --poll-ms 2000
58
- gonext-local-worker --api-base https://other-host.example
59
- gonext-local-worker --mode webhook --webhook-port 5001
60
- gonext-local-worker --mode both
61
- gonext-local-worker --help
62
- ```
63
-
64
- Leave this process **running** while you use async local models from the web app.
65
-
66
- ## Push-dispatch mode (new option)
67
-
68
- You now have 2 ways to update job status:
69
-
70
- 1. **Polling mode** (existing): worker polls `/api/worker/jobs/next`.
71
- 2. **Webhook mode** (new): API push-dispatches job payloads to your local worker endpoint.
72
-
73
- Run local webhook endpoint:
74
-
75
- ```bash
76
- gonext-local-worker --mode webhook --webhook-port 5001
77
- ```
78
-
79
- or keep both (recommended transition):
80
-
81
- ```bash
82
- gonext-local-worker --mode both --webhook-port 5001
83
- ```
84
-
85
- Expose webhook with ngrok:
86
-
87
- ```bash
88
- ngrok http 5001
89
- ```
90
-
91
- Then in web app Settings set **Worker webhook URL** to:
92
-
93
- `https://<your-ngrok-domain>/api/dispatch-job`
94
-
95
- Local worker API endpoints on port 5001:
96
-
97
- - `POST /api/chat` -> proxies to `http://localhost:11434/api/chat`
98
- - `POST /api/generate` -> proxies to `http://localhost:11434/api/generate`
99
- - `GET /api/tags` -> proxies to `http://localhost:11434/api/tags`
100
- - `POST /api/dispatch-job` -> cloud dispatch entrypoint
101
- - `GET /api/health`
102
-
103
- Legacy non-prefixed routes (`/chat`, `/generate`, `/tags`, `/dispatch-job`, `/health`) are also available for compatibility.
104
-
105
- ## Run at login (macOS LaunchAgent)
106
-
107
- 1. Copy `launchd/com.gonext.worker.plist.example` to `~/Library/LaunchAgents/com.gonext.worker.plist`.
108
- 2. Edit paths: set **full path** to `gonext-local-worker` (`which gonext-local-worker` after `npm link`).
109
- 3. `launchctl load ~/Library/LaunchAgents/com.gonext.worker.plist`
110
-
111
- Unload:
112
-
113
- ```bash
114
- launchctl unload ~/Library/LaunchAgents/com.gonext.worker.plist
115
- ```
116
-
117
- ## Troubleshooting
118
-
119
- - **`400` / missing workerUserId** — Set `GONEXT_WORKER_USER_ID` correctly.
120
- - **Job never claimed** — Ensure DynamoDB + worker routes work; Settings must include **Ollama base URL** so the queued payload points at a reachable host from this Mac (`127.0.0.1` is fine).
1
+ # @tiens.nguyen/gonext-local-worker
2
+ Run:
3
+ GONEXT_API_BASE=... GONEXT_WORKER_KEY=... npx -y --package @tiens.nguyen/gonext-local-worker gonext-local-worker
@@ -1,115 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * GoNext local LLM worker runs on the Mac where Ollama / MLX HTTP server lives.
4
- * Polls your cloud API for jobs, calls the model locally, PATCHes status back.
3
+ * Polls GoNext Cloud API for pending local-LLM chat jobs, runs them against the
4
+ * URLs embedded in the job payload (your LAN Ollama / OpenAI-compatible server),
5
+ * then PATCHes completion back to the API.
5
6
  *
6
- * Env:
7
- * GONEXT_API_BASE HTTPS origin only (no trailing slash), e.g. https://xxx.execute-api....amazonaws.com
8
- * GONEXT_WORKER_USER_ID Firebase user id (owner of queued jobs)
9
- * GONEXT_POLL_MS Poll interval when idle (default 1500)
7
+ * Usage (from api/):
8
+ * export GONEXT_API_BASE=https://xxxx.execute-api....amazonaws.com
9
+ * export GONEXT_WORKER_KEY=<plaintext secret from Settings Worker API key>
10
+ * node scripts/local-llm-worker.mjs
10
11
  *
11
- * Optional env files (loaded in order; shell exports win if set before launch):
12
- * ~/.gonext/worker.env
13
- * ./.env (cwd)
12
+ * Requires Node 18+ (global fetch). Uses the OpenAI SDK from this package.
14
13
  */
15
- import { homedir } from "node:os";
16
- import os from "node:os";
17
- import { join } from "node:path";
18
- import dotenv from "dotenv";
19
- import express from "express";
20
14
  import OpenAI from "openai";
21
15
 
22
- dotenv.config({ path: join(homedir(), ".gonext", "worker.env") });
23
- dotenv.config();
16
+ const apiBase = (process.env.GONEXT_API_BASE ?? "").replace(/\/+$/, "");
17
+ const workerKey = process.env.GONEXT_WORKER_KEY ?? "";
18
+ const pollMs = Number(process.env.GONEXT_POLL_MS ?? "1500") || 1500;
24
19
 
25
- function parseArgs(argv) {
26
- const out = {
27
- help: false,
28
- pollMs: undefined,
29
- apiBase: undefined,
30
- mode: undefined,
31
- webhookPort: undefined,
32
- };
33
- for (let i = 2; i < argv.length; i++) {
34
- const a = argv[i];
35
- if (a === "--help" || a === "-h") out.help = true;
36
- else if (a === "--poll-ms" && argv[i + 1])
37
- out.pollMs = Number(argv[++i]);
38
- else if (a === "--api-base" && argv[i + 1]) out.apiBase = argv[++i];
39
- else if (a === "--mode" && argv[i + 1]) out.mode = argv[++i];
40
- else if (a === "--webhook-port" && argv[i + 1])
41
- out.webhookPort = Number(argv[++i]);
42
- }
43
- return out;
44
- }
45
-
46
- function printHelp() {
47
- console.log(`
48
- gonext-local-worker — bridge cloud GoNext jobs ↔ local LLM (Ollama / OpenAI-compatible)
49
-
50
- Usage:
51
- export GONEXT_API_BASE=https://....amazonaws.com
52
- export GONEXT_WORKER_USER_ID=your-firebase-user-id
53
- gonext-local-worker
54
-
55
- Options:
56
- --poll-ms <ms> Idle poll interval (default 1500 or GONEXT_POLL_MS)
57
- --api-base <url> Override GONEXT_API_BASE
58
- --mode <poll|webhook|both> Worker mode (default: poll)
59
- --webhook-port <n> Local webhook port (default: 5001)
60
-
61
- Config files (optional):
62
- ~/.gonext/worker.env
63
- .env in current directory
64
-
65
- Install:
66
- npm install -g @tiens.nguyen/gonext-local-worker
67
-
68
- Then keep this running while you use the web app with local models.
69
- `);
70
- }
71
-
72
- const args = parseArgs(process.argv);
73
- if (args.help) {
74
- printHelp();
75
- process.exit(0);
76
- }
77
-
78
- const apiBase = (
79
- args.apiBase ??
80
- process.env.GONEXT_API_BASE ??
81
- ""
82
- ).replace(/\/+$/, "");
83
- const workerUserId = (process.env.GONEXT_WORKER_USER_ID ?? "").trim();
84
- const pollMs =
85
- (Number.isFinite(args.pollMs) && args.pollMs > 0
86
- ? args.pollMs
87
- : Number(process.env.GONEXT_POLL_MS ?? "1500")) || 1500;
88
- const modeRaw = String(args.mode ?? process.env.GONEXT_WORKER_MODE ?? "poll");
89
- const mode = ["poll", "webhook", "both"].includes(modeRaw)
90
- ? modeRaw
91
- : "poll";
92
- const webhookPort =
93
- (Number.isFinite(args.webhookPort) && args.webhookPort > 0
94
- ? args.webhookPort
95
- : Number(process.env.GONEXT_WEBHOOK_PORT ?? "5001")) || 5001;
96
- const ollamaBase = (
97
- process.env.GONEXT_LOCAL_OLLAMA_BASE ?? "http://127.0.0.1:11434"
98
- ).replace(/\/+$/, "");
99
-
100
- if (!apiBase || !workerUserId) {
20
+ if (!apiBase || !workerKey) {
101
21
  console.error(
102
- "Missing GONEXT_API_BASE or GONEXT_WORKER_USER_ID.\nSet them or put them in ~/.gonext/worker.env run with --help."
22
+ "Set GONEXT_API_BASE (HTTP API origin, no /api suffix) and GONEXT_WORKER_KEY."
103
23
  );
104
24
  process.exit(1);
105
25
  }
106
26
 
107
- const WORKER_VERSION = "1.0.0";
108
- const WORKER_HOST = os.hostname();
109
- function ts() {
110
- return new Date().toISOString();
111
- }
112
-
113
27
  function toOpenAIMessages(messages) {
114
28
  return messages.map((m) => {
115
29
  if (m.role === "user" && m.attachments?.length) {
@@ -132,54 +46,19 @@ async function workerFetch(path, init = {}) {
132
46
  const url = `${apiBase}${path.startsWith("/") ? path : `/${path}`}`;
133
47
  const headers = {
134
48
  "Content-Type": "application/json",
135
- "X-Worker-User-Id": workerUserId,
49
+ "X-Worker-Key": workerKey,
136
50
  ...(init.headers ?? {}),
137
51
  };
138
52
  return fetch(url, { ...init, headers });
139
53
  }
140
54
 
141
- let shuttingDown = false;
142
- let activeJobId = "";
143
- let lastError = "";
144
- let webhookServer = null;
145
-
146
- async function postHeartbeat(payload) {
147
- try {
148
- await workerFetch("/api/worker/heartbeat", {
149
- method: "POST",
150
- body: JSON.stringify({
151
- workerVersion: WORKER_VERSION,
152
- host: WORKER_HOST,
153
- ...payload,
154
- }),
155
- });
156
- } catch {
157
- /* keep worker loop alive */
158
- }
159
- }
160
-
161
55
  async function runChatJob(job) {
162
56
  const { jobId, payload } = job;
163
57
  const start = Date.now();
164
- activeJobId = jobId;
165
- lastError = "";
166
- await postHeartbeat({ state: "running", currentJobId: jobId });
167
- const patchRunning = await workerFetch(`/api/worker/jobs/${jobId}`, {
58
+ await workerFetch(`/api/worker/jobs/${jobId}`, {
168
59
  method: "PATCH",
169
60
  body: JSON.stringify({ jobStatus: "running" }),
170
61
  });
171
- if (!patchRunning.ok) {
172
- const t = await patchRunning.text().catch(() => "");
173
- console.error(`[${ts()}] PATCH running failed ${patchRunning.status}`, t);
174
- await postHeartbeat({
175
- state: "error",
176
- currentJobId: jobId,
177
- lastError: `PATCH running failed ${patchRunning.status}`,
178
- });
179
- lastError = `PATCH running failed ${patchRunning.status}`;
180
- activeJobId = "";
181
- return;
182
- }
183
62
 
184
63
  const client = new OpenAI({
185
64
  baseURL: payload.baseURL,
@@ -199,19 +78,11 @@ async function runChatJob(job) {
199
78
  body: JSON.stringify({
200
79
  jobStatus: "completed",
201
80
  resultText: text,
202
- tokenCount: Math.max(1, completion.usage?.total_tokens ?? 1),
81
+ tokenCount: 1,
203
82
  totalTimeSeconds,
204
83
  }),
205
84
  });
206
- console.log(
207
- `[${ts()}] completed job ${jobId} (${totalTimeSeconds.toFixed(1)}s)`
208
- );
209
- await postHeartbeat({
210
- state: "idle",
211
- currentJobId: "",
212
- lastJobCompletedAt: new Date().toISOString(),
213
- });
214
- activeJobId = "";
85
+ console.log(`[gonext-worker] completed ${jobId} (${totalTimeSeconds.toFixed(1)}s)`);
215
86
  } catch (e) {
216
87
  const message = e instanceof Error ? e.message : String(e);
217
88
  await workerFetch(`/api/worker/jobs/${jobId}`, {
@@ -222,14 +93,7 @@ async function runChatJob(job) {
222
93
  totalTimeSeconds: (Date.now() - start) / 1000,
223
94
  }),
224
95
  });
225
- console.error(`[${ts()}] failed job ${jobId}:`, message);
226
- await postHeartbeat({
227
- state: "error",
228
- currentJobId: jobId,
229
- lastError: message,
230
- });
231
- lastError = message;
232
- activeJobId = "";
96
+ console.error(`[gonext-worker] failed ${jobId}:`, message);
233
97
  }
234
98
  }
235
99
 
@@ -238,268 +102,24 @@ async function pollOnce() {
238
102
  if (res.status === 204) return;
239
103
  if (!res.ok) {
240
104
  const t = await res.text().catch(() => "");
241
- throw new Error(`POST /api/worker/jobs/next ${res.status}: ${t}`);
105
+ throw new Error(`next failed ${res.status}: ${t}`);
242
106
  }
243
107
  const job = await res.json();
244
- if (job?.jobId && job.payload) {
108
+ if (job?.jobId) {
245
109
  await runChatJob(job);
246
110
  }
247
111
  }
248
112
 
249
- function sleep(ms) {
250
- return new Promise((r) => setTimeout(r, ms));
251
- }
252
-
253
- function toOllamaChatMessages(messages) {
254
- return (messages ?? []).map((m) => ({
255
- role: m.role,
256
- content: m.content,
257
- ...(Array.isArray(m.attachments) && m.attachments.length > 0
258
- ? { images: m.attachments.map((a) => a.data) }
259
- : {}),
260
- }));
261
- }
262
-
263
- async function runOllamaAndMaybeUpdateJob(params) {
264
- const { endpoint, requestBody, jobId, extractResultText } = params;
265
- const hasJob = typeof jobId === "string" && jobId.length > 0;
266
- const start = Date.now();
267
- if (hasJob) {
268
- activeJobId = jobId;
269
- lastError = "";
270
- await postHeartbeat({ state: "running", currentJobId: jobId });
271
- await workerFetch(`/api/worker/jobs/${jobId}`, {
272
- method: "PATCH",
273
- body: JSON.stringify({ jobStatus: "running" }),
274
- });
275
- }
276
- try {
277
- const res = await fetch(`${ollamaBase}${endpoint}`, {
278
- method: "POST",
279
- headers: { "Content-Type": "application/json" },
280
- body: JSON.stringify({
281
- stream: false,
282
- ...requestBody,
283
- }),
284
- });
285
- const raw = await res.text();
286
- if (!res.ok) {
287
- throw new Error(`Ollama ${endpoint} failed ${res.status}: ${raw}`);
288
- }
289
- const parsed = raw ? JSON.parse(raw) : {};
290
- if (hasJob) {
291
- await workerFetch(`/api/worker/jobs/${jobId}`, {
292
- method: "PATCH",
293
- body: JSON.stringify({
294
- jobStatus: "completed",
295
- resultText: extractResultText(parsed),
296
- tokenCount: 1,
297
- totalTimeSeconds: (Date.now() - start) / 1000,
298
- }),
299
- });
300
- await postHeartbeat({
301
- state: "idle",
302
- currentJobId: "",
303
- lastJobCompletedAt: new Date().toISOString(),
304
- });
305
- activeJobId = "";
306
- }
307
- return parsed;
308
- } catch (e) {
309
- if (hasJob) {
310
- const message = e instanceof Error ? e.message : String(e);
311
- await workerFetch(`/api/worker/jobs/${jobId}`, {
312
- method: "PATCH",
313
- body: JSON.stringify({
314
- jobStatus: "failed",
315
- errorMessage: message,
316
- totalTimeSeconds: (Date.now() - start) / 1000,
317
- }),
318
- });
319
- await postHeartbeat({
320
- state: "error",
321
- currentJobId: jobId,
322
- lastError: message,
323
- });
324
- lastError = message;
325
- activeJobId = "";
326
- }
327
- throw e;
328
- }
329
- }
330
-
331
- function startWebhookServer() {
332
- const app = express();
333
- app.use(express.json({ limit: "10mb" }));
334
-
335
- const healthHandler = (_req, res) => {
336
- const state =
337
- activeJobId.length > 0 ? "running" : lastError ? "error" : "idle";
338
- res.json({
339
- ok: true,
340
- workerVersion: WORKER_VERSION,
341
- host: WORKER_HOST,
342
- mode,
343
- state,
344
- activeJobId: activeJobId || undefined,
345
- lastError: lastError || undefined,
346
- dispatchPath: "/dispatch-job",
347
- chatPath: "/chat",
348
- generatePath: "/generate",
349
- tagsPath: "/tags",
350
- ollamaBase,
351
- });
352
- };
353
- app.get("/health", healthHandler);
354
- app.get("/api/health", healthHandler);
355
-
356
- const tagsHandler = async (_req, res) => {
357
- try {
358
- const r = await fetch(`${ollamaBase}/api/tags`, { method: "GET" });
359
- const raw = await r.text();
360
- if (!r.ok) {
361
- res
362
- .status(r.status)
363
- .json({ error: `Ollama /api/tags failed ${r.status}`, raw });
364
- return;
365
- }
366
- res.type("application/json").send(raw || "{}");
367
- } catch (e) {
368
- res.status(500).json({ error: e instanceof Error ? e.message : "error" });
369
- }
370
- };
371
- app.get("/tags", tagsHandler);
372
- app.get("/api/tags", tagsHandler);
373
-
374
- const dispatchJobHandler = async (req, res) => {
375
- const body = req.body ?? {};
376
- if (!body?.jobId || !body?.payload) {
377
- res.status(400).json({ error: "Expected { jobId, payload }." });
378
- return;
379
- }
380
- if (activeJobId) {
381
- res.status(409).json({ error: "Worker busy.", activeJobId });
382
- return;
383
- }
384
- const payload = body.payload;
385
- const chatBody = {
386
- model: payload.modelId,
387
- messages: toOllamaChatMessages(payload.messages),
388
- };
389
- void runOllamaAndMaybeUpdateJob({
390
- endpoint: "/api/chat",
391
- requestBody: chatBody,
392
- jobId: String(body.jobId),
393
- extractResultText: (j) => j?.message?.content ?? "",
394
- }).catch((e) => {
395
- console.error("[dispatch-job] error:", e instanceof Error ? e.message : e);
396
- });
397
- res.status(202).json({ ok: true, accepted: true, jobId: body.jobId });
398
- };
399
- app.post("/dispatch-job", dispatchJobHandler);
400
- app.post("/api/dispatch-job", dispatchJobHandler);
401
-
402
- const chatHandler = async (req, res) => {
403
- try {
404
- const body = req.body ?? {};
405
- const jobId = typeof body.jobId === "string" ? body.jobId : "";
406
- const requestBody =
407
- body.request && typeof body.request === "object" ? body.request : body;
408
- const parsed = await runOllamaAndMaybeUpdateJob({
409
- endpoint: "/api/chat",
410
- requestBody,
411
- jobId,
412
- extractResultText: (j) => j?.message?.content ?? "",
413
- });
414
- res.json(parsed);
415
- } catch (e) {
416
- res.status(500).json({ error: e instanceof Error ? e.message : "error" });
417
- }
418
- };
419
- app.post("/chat", chatHandler);
420
- app.post("/api/chat", chatHandler);
421
-
422
- const generateHandler = async (req, res) => {
113
+ async function main() {
114
+ console.log(`[gonext-worker] polling ${apiBase} every ${pollMs}ms`);
115
+ for (;;) {
423
116
  try {
424
- const body = req.body ?? {};
425
- const jobId = typeof body.jobId === "string" ? body.jobId : "";
426
- const requestBody =
427
- body.request && typeof body.request === "object" ? body.request : body;
428
- const parsed = await runOllamaAndMaybeUpdateJob({
429
- endpoint: "/api/generate",
430
- requestBody,
431
- jobId,
432
- extractResultText: (j) => j?.response ?? "",
433
- });
434
- res.json(parsed);
117
+ await pollOnce();
435
118
  } catch (e) {
436
- res.status(500).json({ error: e instanceof Error ? e.message : "error" });
119
+ console.error("[gonext-worker] poll error:", e);
437
120
  }
438
- };
439
- app.post("/generate", generateHandler);
440
- app.post("/api/generate", generateHandler);
441
-
442
- webhookServer = app.listen(webhookPort, "127.0.0.1", () => {
443
- console.log(
444
- `[${ts()}] local worker API on http://127.0.0.1:${webhookPort} (/chat, /generate, /tags, /dispatch-job, /health)`
445
- );
446
- });
447
- }
448
-
449
- async function main() {
450
- console.log(`[${ts()}] gonext-local-worker`);
451
- console.log(` API ${apiBase}`);
452
- console.log(` mode ${mode}`);
453
- if (mode === "poll" || mode === "both") {
454
- console.log(` poll every ${pollMs}ms (idle)`);
455
- }
456
- if (mode === "webhook" || mode === "both") {
457
- console.log(` hook http://127.0.0.1:${webhookPort}/dispatch-job`);
458
- }
459
- console.log(` stop Ctrl+C`);
460
- await postHeartbeat({ state: "idle", currentJobId: "" });
461
-
462
- const loop = async () => {
463
- while (!shuttingDown) {
464
- try {
465
- await pollOnce();
466
- await postHeartbeat({ state: "idle", currentJobId: "" });
467
- } catch (e) {
468
- console.error(`[${ts()}] poll error:`, e instanceof Error ? e.message : e);
469
- await postHeartbeat({
470
- state: "error",
471
- currentJobId: "",
472
- lastError: e instanceof Error ? e.message : String(e),
473
- });
474
- }
475
- if (shuttingDown) break;
476
- await sleep(pollMs);
477
- }
478
- };
479
-
480
- const stop = () => {
481
- if (shuttingDown) return;
482
- shuttingDown = true;
483
- console.log(`\n[${ts()}] shutting down…`);
484
- if (webhookServer) {
485
- webhookServer.close();
486
- }
487
- process.exit(0);
488
- };
489
- process.on("SIGINT", stop);
490
- process.on("SIGTERM", stop);
491
-
492
- if (mode === "webhook" || mode === "both") {
493
- startWebhookServer();
494
- }
495
- if (mode === "poll" || mode === "both") {
496
- await loop();
497
- } else {
498
- await new Promise(() => {});
121
+ await new Promise((r) => setTimeout(r, pollMs));
499
122
  }
500
123
  }
501
124
 
502
- main().catch((e) => {
503
- console.error(e);
504
- process.exit(1);
505
- });
125
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,6 +15,11 @@
15
15
  "bin": {
16
16
  "gonext-local-worker": "./gonext-local-worker.mjs"
17
17
  },
18
+ "scripts": {
19
+ "deploy:local": "npm install -g .",
20
+ "run": "gonext-local-worker",
21
+ "publish:org": "npm publish --access public"
22
+ },
18
23
  "files": [
19
24
  "gonext-local-worker.mjs",
20
25
  "README.md",
@@ -1,34 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>Label</key>
6
- <string>com.gonext.worker</string>
7
- <key>ProgramArguments</key>
8
- <array>
9
- <!-- Replace with output of: which gonext-local-worker -->
10
- <string>/usr/local/bin/gonext-local-worker</string>
11
- </array>
12
- <!-- Optional: uncomment to force Node path -->
13
- <!--
14
- <array>
15
- <string>/usr/local/bin/node</string>
16
- <string>/ABSOLUTE/PATH/TO/gonext/tools/gonext-local-worker/gonext-local-worker.mjs</string>
17
- </array>
18
- -->
19
- <key>RunAtLoad</key>
20
- <true/>
21
- <key>KeepAlive</key>
22
- <true/>
23
- <key>StandardOutPath</key>
24
- <string>/tmp/gonext-worker.log</string>
25
- <key>StandardErrorPath</key>
26
- <string>/tmp/gonext-worker.err</string>
27
- <key>EnvironmentVariables</key>
28
- <dict>
29
- <!-- Or rely on ~/.gonext/worker.env (loaded by the worker) -->
30
- <key>PATH</key>
31
- <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
32
- </dict>
33
- </dict>
34
- </plist>