Skip to content

Commit 4832246

Browse files
committed
fix: reduce lines
1 parent 82af8fb commit 4832246

2 files changed

Lines changed: 118 additions & 99 deletions

File tree

skills/local-ai-app-integration/SKILL.md

Lines changed: 9 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -260,107 +260,17 @@ The launcher is a thin process supervisor. Its only jobs:
260260
> silently breaking any in-flight transcription. Add `vendor/` (or the
261261
> equivalent) to the watcher's ignore list before testing.
262262
263-
**Python reference launcher** (adapt to the app's language):
263+
The launcher logic in pseudocode (full Python and Node.js implementations in [reference.md](reference.md#reference-launchers)):
264264
265-
```python
266-
import os, secrets, socket, subprocess, sys, time, urllib.request
267-
from pathlib import Path
268-
269-
LEMOND_DIR = Path(__file__).parent / "vendor" / "lemonade"
270-
LEMOND_BIN = LEMOND_DIR / ("lemond.exe" if sys.platform == "win32" else "lemond")
271-
272-
def _free_port() -> int:
273-
with socket.socket() as s:
274-
s.bind(("127.0.0.1", 0))
275-
return s.getsockname()[1]
276-
277-
def start_lemond(retries: int = 3) -> tuple[subprocess.Popen, str, int]:
278-
# _free_port releases the socket before lemond binds — another process
279-
# can grab the port in that window. Retry with a fresh port on failure.
280-
last_err: Exception | None = None
281-
for _ in range(retries):
282-
port = _free_port()
283-
key = secrets.token_urlsafe(32)
284-
env = {**os.environ, "LEMONADE_API_KEY": key}
285-
proc = subprocess.Popen(
286-
[str(LEMOND_BIN), str(LEMOND_DIR), "--port", str(port)],
287-
env=env,
288-
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
289-
)
290-
try:
291-
_wait_for_health(port, key, timeout_s=30)
292-
return proc, key, port
293-
except RuntimeError as e:
294-
proc.kill()
295-
proc.wait()
296-
last_err = e
297-
raise RuntimeError(f"lemond failed to start after {retries} attempts") from last_err
298-
299-
def _wait_for_health(port: int, key: str, timeout_s: int) -> None:
300-
url = f"http://127.0.0.1:{port}/api/v1/health"
301-
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {key}"})
302-
deadline = time.monotonic() + timeout_s
303-
while time.monotonic() < deadline:
304-
try:
305-
with urllib.request.urlopen(req, timeout=1) as r:
306-
if r.status == 200:
307-
return
308-
except Exception:
309-
time.sleep(0.25)
310-
raise RuntimeError(f"lemond on port {port} did not become healthy within {timeout_s}s")
311265
```
312-
313-
**Node.js reference launcher:**
314-
315-
```js
316-
import { spawn } from "node:child_process";
317-
import { randomBytes } from "node:crypto";
318-
import { createServer } from "node:net";
319-
import path from "node:path";
320-
321-
const LEMOND_DIR = path.join(import.meta.dirname, "vendor", "lemonade");
322-
const LEMOND_BIN = path.join(LEMOND_DIR, process.platform === "win32" ? "lemond.exe" : "lemond");
323-
324-
const freePort = () => new Promise((res) => {
325-
const s = createServer().listen(0, "127.0.0.1", () => {
326-
const { port } = s.address(); s.close(() => res(port));
327-
});
328-
});
329-
330-
// freePort releases the socket before lemond binds — retry with a fresh port on failure.
331-
export async function startLemond(retries = 3) {
332-
let lastErr;
333-
for (let i = 0; i < retries; i++) {
334-
const port = await freePort();
335-
const key = randomBytes(32).toString("base64url");
336-
const proc = spawn(LEMOND_BIN, [LEMOND_DIR, "--port", String(port)], {
337-
env: { ...process.env, LEMONADE_API_KEY: key },
338-
stdio: ["ignore", "pipe", "pipe"],
339-
});
340-
try {
341-
await waitForHealth(port, key, 30_000);
342-
return { proc, key, port };
343-
} catch (e) {
344-
proc.kill();
345-
lastErr = e;
346-
}
347-
}
348-
throw new Error(`lemond failed to start after ${retries} attempts: ${lastErr?.message}`);
349-
}
350-
351-
async function waitForHealth(port, key, timeoutMs) {
352-
const url = `http://127.0.0.1:${port}/api/v1/health`;
353-
const headers = { Authorization: `Bearer ${key}` };
354-
const deadline = Date.now() + timeoutMs;
355-
while (Date.now() < deadline) {
356-
try {
357-
const r = await fetch(url, { headers });
358-
if (r.ok) return;
359-
} catch {}
360-
await new Promise((r) => setTimeout(r, 250));
361-
}
362-
throw new Error(`lemond on port ${port} did not become healthy within ${timeoutMs}ms`);
363-
}
266+
port = bind("127.0.0.1:0"), read port, close socket
267+
key = random_bytes(32)
268+
proc = spawn(lemond_bin, [lemond_dir, "--port", port], env={LEMONADE_API_KEY: key})
269+
poll GET /api/v1/health with Bearer key, retry for 90s, 250ms interval
270+
return proc, key, port
271+
272+
# On failure: kill proc, pick new port, retry up to 3 times
273+
# On app exit: proc.kill() (Windows) / proc.terminate() (Unix), then wait()
364274
```
365275
366276
## Step 5: Re-point the existing client at `lemond`

skills/local-ai-app-integration/reference.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,112 @@ Two backend limitations on Linux as of this writing:
271271

272272
When building from source for an unusual Linux distro, see the upstream
273273
`docs/embeddable/building.md` in the lemonade-sdk/lemonade repo.
274+
275+
---
276+
277+
## Reference launchers
278+
279+
Full implementations for Step 4. Adapt to the app's language; the key
280+
constraints are: retry with a fresh port on spawn failure (the socket is
281+
released before lemond binds), poll `/api/v1/health` with the Bearer key,
282+
and kill the process on app exit.
283+
284+
**Python:**
285+
286+
```python
287+
import os, secrets, socket, subprocess, sys, time, urllib.request
288+
from pathlib import Path
289+
290+
LEMOND_DIR = Path(__file__).parent / "vendor" / "lemonade"
291+
LEMOND_BIN = LEMOND_DIR / ("lemond.exe" if sys.platform == "win32" else "lemond")
292+
293+
def _free_port() -> int:
294+
with socket.socket() as s:
295+
s.bind(("127.0.0.1", 0))
296+
return s.getsockname()[1]
297+
298+
def start_lemond(retries: int = 3) -> tuple[subprocess.Popen, str, int]:
299+
last_err: Exception | None = None
300+
for _ in range(retries):
301+
port = _free_port()
302+
key = secrets.token_urlsafe(32)
303+
env = {**os.environ, "LEMONADE_API_KEY": key}
304+
proc = subprocess.Popen(
305+
[str(LEMOND_BIN), str(LEMOND_DIR), "--port", str(port)],
306+
env=env,
307+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
308+
)
309+
try:
310+
_wait_for_health(port, key, timeout_s=30)
311+
return proc, key, port
312+
except RuntimeError as e:
313+
proc.kill()
314+
proc.wait()
315+
last_err = e
316+
raise RuntimeError(f"lemond failed to start after {retries} attempts") from last_err
317+
318+
def _wait_for_health(port: int, key: str, timeout_s: int) -> None:
319+
url = f"http://127.0.0.1:{port}/api/v1/health"
320+
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {key}"})
321+
deadline = time.monotonic() + timeout_s
322+
while time.monotonic() < deadline:
323+
try:
324+
with urllib.request.urlopen(req, timeout=1) as r:
325+
if r.status == 200:
326+
return
327+
except Exception:
328+
time.sleep(0.25)
329+
raise RuntimeError(f"lemond on port {port} did not become healthy within {timeout_s}s")
330+
```
331+
332+
**Node.js:**
333+
334+
```js
335+
import { spawn } from "node:child_process";
336+
import { randomBytes } from "node:crypto";
337+
import { createServer } from "node:net";
338+
import path from "node:path";
339+
340+
const LEMOND_DIR = path.join(import.meta.dirname, "vendor", "lemonade");
341+
const LEMOND_BIN = path.join(LEMOND_DIR, process.platform === "win32" ? "lemond.exe" : "lemond");
342+
343+
const freePort = () => new Promise((res) => {
344+
const s = createServer().listen(0, "127.0.0.1", () => {
345+
const { port } = s.address(); s.close(() => res(port));
346+
});
347+
});
348+
349+
export async function startLemond(retries = 3) {
350+
let lastErr;
351+
for (let i = 0; i < retries; i++) {
352+
const port = await freePort();
353+
const key = randomBytes(32).toString("base64url");
354+
const proc = spawn(LEMOND_BIN, [LEMOND_DIR, "--port", String(port)], {
355+
env: { ...process.env, LEMONADE_API_KEY: key },
356+
stdio: ["ignore", "pipe", "pipe"],
357+
});
358+
try {
359+
await waitForHealth(port, key, 30_000);
360+
return { proc, key, port };
361+
} catch (e) {
362+
proc.kill();
363+
lastErr = e;
364+
}
365+
}
366+
throw new Error(`lemond failed to start after ${retries} attempts: ${lastErr?.message}`);
367+
}
368+
369+
async function waitForHealth(port, key, timeoutMs) {
370+
const url = `http://127.0.0.1:${port}/api/v1/health`;
371+
const headers = { Authorization: `Bearer ${key}` };
372+
const deadline = Date.now() + timeoutMs;
373+
while (Date.now() < deadline) {
374+
try {
375+
const r = await fetch(url, { headers });
376+
if (r.ok) return;
377+
} catch {}
378+
await new Promise((r) => setTimeout(r, 250));
379+
}
380+
throw new Error(`lemond on port ${port} did not become healthy within ${timeoutMs}ms`);
381+
}
382+
```

0 commit comments

Comments
 (0)