qiksy 1.2.1 → 1.3.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.
package/bin/qiksy.mjs CHANGED
@@ -13,7 +13,7 @@
13
13
  * Everything runs on the user's machine, on their own Claude. Nothing is uploaded.
14
14
  */
15
15
  import { spawn, spawnSync } from 'node:child_process';
16
- import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
16
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
17
17
  import { homedir } from 'node:os';
18
18
  import { dirname, join, resolve } from 'node:path';
19
19
  import { platform } from 'node:process';
@@ -40,6 +40,15 @@ try {
40
40
  /* unwritable home: each backend falls back to its own folder */
41
41
  }
42
42
 
43
+ /* The supervisor's own pid. `stop` used to kill the three tools by port — and the
44
+ supervisor, doing exactly what it was built for, brought them back two seconds later,
45
+ so stopping never actually stopped (found 2026-07-26 while upgrading a live install).
46
+ It records its pid here and `stop` ends the supervisor first. */
47
+ const PID_FILE = join(DATA_HOME, 'engine.pid');
48
+ const writePid = () => { try { writeFileSync(PID_FILE, String(process.pid)); } catch { /* unwritable home */ } };
49
+ const readPid = () => { try { return Number(readFileSync(PID_FILE, 'utf8').trim()) || 0; } catch { return 0; } };
50
+ const clearPid = () => { try { if (existsSync(PID_FILE)) rmSync(PID_FILE); } catch { /* ignore */ } };
51
+
43
52
  const TOOLS = [
44
53
  {
45
54
  id: 'work-finder',
@@ -180,14 +189,20 @@ async function cmdStatus() {
180
189
  function cmdStop() {
181
190
  console.log(`\n${c.blue(c.b(' ◆ Qiksy'))}${c.dim(' — stopping')}\n`);
182
191
  let killed = 0;
192
+ // Who was actually up BEFORE we touch anything — killing the supervisor takes its
193
+ // children with it, and reporting "was not running" about something we just stopped
194
+ // is the kind of small lie that makes people distrust the rest of the output.
195
+ const wasUp = new Map(TOOLS.map((t) => [t.id, pidsOn(t.port).length > 0]));
196
+ // The supervisor first, or it restarts every tool we are about to stop.
197
+ const sup = readPid();
198
+ if (sup && sup !== process.pid) { if (killPid(sup)) killed++; clearPid(); }
183
199
  for (const t of TOOLS) {
184
- const pids = pidsOn(t.port);
185
- if (!pids.length) {
186
- console.log(c.dim(` · ${t.name.padEnd(18)} was not running`));
187
- continue;
188
- }
189
- for (const p of pids) if (killPid(p)) killed++;
190
- console.log(c.green(` ✓ ${t.name.padEnd(18)} stopped`));
200
+ for (const p of pidsOn(t.port)) if (killPid(p)) killed++;
201
+ console.log(
202
+ wasUp.get(t.id)
203
+ ? c.green(` ✓ ${t.name.padEnd(18)} stopped`)
204
+ : c.dim(` · ${t.name.padEnd(18)} was not running`)
205
+ );
191
206
  }
192
207
  console.log(c.dim(`\n ${killed ? 'Everything is off.' : 'Nothing was running.'} Start again: npx qiksy\n`));
193
208
  }
@@ -448,10 +463,12 @@ function stopAll() {
448
463
  process.on('SIGINT', () => {
449
464
  console.log(c.dim('\n Stopping…'));
450
465
  stopAll();
466
+ clearPid();
451
467
  process.exit(0);
452
468
  });
453
469
  process.on('SIGTERM', () => {
454
470
  stopAll();
471
+ clearPid();
455
472
  process.exit(0);
456
473
  });
457
474
 
@@ -546,6 +563,7 @@ process.on('SIGTERM', () => {
546
563
  if (ok) up.push(tool);
547
564
  }
548
565
 
566
+ writePid();
549
567
  startWatchdog();
550
568
 
551
569
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qiksy",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Qiksy \u2014 one local engine for every Qiksy tool (Work Finder, Travel Assistant, Client Finder). Runs on your own Claude; nothing leaves your machine.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,11 @@ function defaults() {
33
33
  re-read every pass. Three days was aggressive enough to hide most of the
34
34
  market; two weeks is still "current" and leaves something to find. */
35
35
  freshnessFrom: 0,
36
- freshnessDays: 14,
36
+ /* Seven days, not fourteen (owner decision 2026-07-26). A wider window mostly
37
+ buys older postings that nobody answers any more, at the cost of the agent
38
+ re-reading them — slower AND more of the person's Claude quota for less. The
39
+ window is a setting, so "dig deeper" stays one control away. */
40
+ freshnessDays: 7,
37
41
  /* How many sweeps at most. Unbounded would spend the user's whole Claude
38
42
  quota in one night; two barren sweeps in a row also end it early. */
39
43
  maxSweeps: 6,
@@ -212,8 +212,21 @@ function startSearch(position, run = null) {
212
212
  const sweep = async (wave) => {
213
213
  const exclude = db.vacancies.filter(v => v.positionId === position.id).map(v => v.company);
214
214
  const before = added;
215
+ /* The FIRST wave is deliberately smaller. Measured on a live run 2026-07-26: the
216
+ first finds landed after ~7 minutes and the next 20 minutes added two more —
217
+ so the person was made to wait for a tail they could have been working through.
218
+ A short first wave gets them a usable list fast; the rest keeps arriving behind
219
+ them, and the UI now says so. Later waves go back to full size. */
220
+ const perWave = wave === 1
221
+ ? Math.min(12, db.settings.resultsPerSearch ?? 20)
222
+ : (db.settings.resultsPerSearch ?? 20);
223
+ const waveSettings = { ...db.settings, resultsPerSearch: perWave };
224
+ // The app shows "wave 2 of 6" from these — a log line in Russian could not be
225
+ // translated, and a run with no visible progress reads as a hang.
226
+ job.wave = wave;
227
+ job.waves = maxSweeps;
215
228
  const r = await runClaude({
216
- prompt: searchPrompt(position, profile, exclude, db.settings, { wave, waves: maxSweeps, canNarrow: effectivePlanId() !== 'free' }),
229
+ prompt: searchPrompt(position, profile, exclude, waveSettings, { wave, waves: maxSweeps, canNarrow: effectivePlanId() !== 'free' }),
217
230
  allowedTools: ['WebSearch', 'WebFetch'],
218
231
  withBrowser: !!db.settings.searchViaBrowser,
219
232
  model: db.settings.model,