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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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,
|