@sickr/cli 0.9.13 → 0.9.15

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 (2) hide show
  1. package/dist/run.js +51 -4
  2. package/package.json +1 -1
package/dist/run.js CHANGED
@@ -24,7 +24,7 @@ import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, rea
24
24
  import { homedir } from 'node:os';
25
25
  import { join } from 'node:path';
26
26
  import { randomUUID } from 'node:crypto';
27
- import { execFileSync } from 'node:child_process';
27
+ import { execFileSync, spawn } from 'node:child_process';
28
28
  import { setTimeout as sleep } from 'node:timers/promises';
29
29
  import { readCredentials } from './auth.js';
30
30
  import { runsDir } from './recorder.js';
@@ -160,6 +160,10 @@ export function cleanPtyResponse(input, lastPrompt = '') {
160
160
  .join('\n')
161
161
  .trim();
162
162
  }
163
+ function hasPtyPromptMarker(input) {
164
+ const clean = stripAnsi(input).replace(/[\u2800-\u28ff]/g, '');
165
+ return clean.includes('>>> Send a message (/? for help)') || /(?:^|\n)\s*>>>\s*$/.test(clean);
166
+ }
163
167
  export class HooklessPtyEventSynth {
164
168
  identity;
165
169
  send;
@@ -169,7 +173,7 @@ export class HooklessPtyEventSynth {
169
173
  lastPrompt = '';
170
174
  awaitingResponse = false;
171
175
  responseTimer = null;
172
- constructor(identity, send, responseDelayMs = 700) {
176
+ constructor(identity, send, responseDelayMs = 2500) {
173
177
  this.identity = identity;
174
178
  this.send = send;
175
179
  this.responseDelayMs = responseDelayMs;
@@ -191,18 +195,25 @@ export class HooklessPtyEventSynth {
191
195
  this.recordPrompt(submitted);
192
196
  }
193
197
  recordPrompt(text) {
198
+ this.prepareResponse(text);
199
+ this.sendEvent('prompt', 'Prompt', this.lastPrompt);
200
+ }
201
+ prepareResponse(text) {
194
202
  const prompt = stripAnsi(text).replace(/\r/g, '\n').trim();
195
203
  if (!prompt)
196
204
  return;
197
205
  this.lastPrompt = prompt;
198
206
  this.outputBuffer = '';
199
207
  this.awaitingResponse = true;
200
- this.sendEvent('prompt', 'Prompt', prompt);
201
208
  }
202
209
  observeOutput(chunk) {
203
210
  if (!this.awaitingResponse)
204
211
  return;
205
212
  this.outputBuffer += chunk;
213
+ if (hasPtyPromptMarker(this.outputBuffer)) {
214
+ this.flushResponse();
215
+ return;
216
+ }
206
217
  if (this.responseTimer)
207
218
  clearTimeout(this.responseTimer);
208
219
  this.responseTimer = setTimeout(() => this.flushResponse(), this.responseDelayMs);
@@ -403,6 +414,40 @@ export function providerArgsFor(agent, existingArgs) {
403
414
  return ['run', model, ...existingArgs];
404
415
  return existingArgs;
405
416
  }
417
+ function ollamaHost() {
418
+ return process.env.OLLAMA_HOST || 'http://127.0.0.1:11434';
419
+ }
420
+ async function ollamaServerReady() {
421
+ try {
422
+ const res = await fetch(`${ollamaHost().replace(/\/$/, '')}/api/tags`, { signal: AbortSignal.timeout(1000) });
423
+ return res.ok;
424
+ }
425
+ catch {
426
+ return false;
427
+ }
428
+ }
429
+ export async function ensureOllamaServer(agentBin, opts = {}) {
430
+ if (await ollamaServerReady())
431
+ return;
432
+ const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(agentBin);
433
+ const child = spawn(agentBin, ['serve'], {
434
+ cwd: process.cwd(),
435
+ env: process.env,
436
+ detached: true,
437
+ stdio: 'ignore',
438
+ windowsHide: true,
439
+ shell: needsShell,
440
+ });
441
+ child.unref();
442
+ const attempts = opts.attempts ?? 20;
443
+ const delayMs = opts.delayMs ?? 250;
444
+ for (let i = 0; i < attempts; i++) {
445
+ await sleep(delayMs);
446
+ if (await ollamaServerReady())
447
+ return;
448
+ }
449
+ throw new Error('Ollama server did not start. Try running `ollama serve` in another terminal, then retry `sickr run ollama`.');
450
+ }
406
451
  export async function startRun(opts) {
407
452
  const creds = readCredentials();
408
453
  if (!creds) {
@@ -424,6 +469,8 @@ export async function startRun(opts) {
424
469
  ensureRecordingHooks(opts.agent);
425
470
  const agentBin = resolveAgent(opts.agent);
426
471
  const provider = providerForAgent(opts.agent);
472
+ if (provider === 'ollama')
473
+ await ensureOllamaServer(agentBin);
427
474
  const agentLabel = provider ? PROVIDERS[provider].recordLabel : opts.agent;
428
475
  const runnerId = randomUUID();
429
476
  const runnerIdentity = { agent: agentLabel, runner: runnerId, sessions: new Set() };
@@ -604,7 +651,7 @@ export async function startRun(opts) {
604
651
  pty.write('\r');
605
652
  }
606
653
  catch { /* pty exited */ } }, 80);
607
- hooklessSynth?.recordPrompt(m.text);
654
+ hooklessSynth?.prepareResponse(m.text);
608
655
  }
609
656
  catch { /* PTY may have exited */ }
610
657
  if (opts.verbose)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sickr/cli",
3
- "version": "0.9.13",
3
+ "version": "0.9.15",
4
4
  "type": "module",
5
5
  "description": "npx @sickr/cli - replay, live look, and workflow orchestration for AI coding agents.",
6
6
  "bin": {