@paigy/harness 0.1.7 → 0.2.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/README.md +10 -9
- package/dist/cli.js +30 -3
- package/dist/main.js +27 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -157,12 +157,13 @@ re-voiceable, revocable.
|
|
|
157
157
|
## Install anywhere (the curl path)
|
|
158
158
|
|
|
159
159
|
```sh
|
|
160
|
-
curl -fsSL https://paigy.ai/install
|
|
160
|
+
curl -fsSL https://paigy.ai/install | sh
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
Installs the npm CLI (one self-contained file — the workspace libs are bundled)
|
|
164
|
-
runs `paigy-harness
|
|
165
|
-
|
|
163
|
+
Installs the npm CLI (one self-contained file — the workspace libs are bundled) and
|
|
164
|
+
runs `paigy-harness setup`: QR-pairs the machine, hatches each detected agent's
|
|
165
|
+
identity (no codes), registers the MCP + statusline, grants a workspace, installs
|
|
166
|
+
the host service. `paigy-harness service` installs a
|
|
166
167
|
macOS launchd agent so hosting survives reboots — the host loop IS the service,
|
|
167
168
|
launchd just keeps it alive (the old "no daemon" note was about the WAKE channel,
|
|
168
169
|
which still belongs to paigy-listen).
|
|
@@ -191,7 +192,7 @@ import Electron — a machine with only the CLI is exactly as launchable as one
|
|
|
191
192
|
the window.
|
|
192
193
|
|
|
193
194
|
Pairing is one-time and lives in `~/.paigy` — the app reads it, never mints it. If
|
|
194
|
-
you've never paired: `
|
|
195
|
+
you've never paired: `paigy-harness setup`.
|
|
195
196
|
|
|
196
197
|
## Uninstall
|
|
197
198
|
|
|
@@ -224,7 +225,7 @@ LAST, after the hooks are gone.
|
|
|
224
225
|
token — per-run identity is threaded through every network touch (`run.ts` binds
|
|
225
226
|
submit/check/ack once per session), so parallel sessions never share a
|
|
226
227
|
conversation. "Stop sessions" stops them all; per-session stop is UI away.
|
|
227
|
-
- **
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
228
|
+
- **The host service IS the daemon** (`paigy-harness service`, launchd, KeepAlive) —
|
|
229
|
+
it claims phone launches and runs sessions. The WAKE channel for terminal-agent
|
|
230
|
+
replies still belongs to `paigy-listen` + `PAIGY_ON_WAKE` (#486); spawn-on-wake
|
|
231
|
+
needs the slot-lease design in companion.md before it auto-wires.
|
package/dist/cli.js
CHANGED
|
@@ -30665,6 +30665,9 @@ function readToken(agent2 = AGENT_NAME) {
|
|
|
30665
30665
|
if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
|
|
30666
30666
|
return readTokenFile()[agent2]?.access_token ?? "";
|
|
30667
30667
|
}
|
|
30668
|
+
function listSlots() {
|
|
30669
|
+
return Object.keys(readTokenFile());
|
|
30670
|
+
}
|
|
30668
30671
|
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
30669
30672
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
30670
30673
|
method: "POST",
|
|
@@ -31740,7 +31743,8 @@ async function drainInput(state, deps = {}) {
|
|
|
31740
31743
|
const claimed = [];
|
|
31741
31744
|
for (const item of mine) {
|
|
31742
31745
|
try {
|
|
31743
|
-
await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
31746
|
+
const ack = await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
31747
|
+
if (ack?.won === false) continue;
|
|
31744
31748
|
claimed.push(item);
|
|
31745
31749
|
} catch {
|
|
31746
31750
|
}
|
|
@@ -32366,9 +32370,30 @@ function startHost(opts) {
|
|
|
32366
32370
|
opts.log(`\u25B6 phone-launched ${label} (${spec.harness}) in ${spec.workspace}`);
|
|
32367
32371
|
}
|
|
32368
32372
|
}
|
|
32373
|
+
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
|
|
32374
|
+
async function sweepSlots() {
|
|
32375
|
+
const workspace = listWorkspaces(opts.wsDeps)[0];
|
|
32376
|
+
if (!workspace) return;
|
|
32377
|
+
for (const slot of listSlots()) {
|
|
32378
|
+
const harness = SLOT_HARNESS[slot];
|
|
32379
|
+
const key = `slot:${slot}`;
|
|
32380
|
+
if (!harness || runs.has(key)) continue;
|
|
32381
|
+
const token = readToken(slot);
|
|
32382
|
+
if (!token) continue;
|
|
32383
|
+
const work = await checkReplies({ token }).catch(() => null);
|
|
32384
|
+
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
32385
|
+
const log = (line) => opts.log(`[${slot}] ${line}`);
|
|
32386
|
+
const run = runHarness({ harness, cwd: workspace, mode: "bypass", prompt: "", exclusive: true, token, log });
|
|
32387
|
+
runs.set(key, { run, label: slot });
|
|
32388
|
+
opts.log(`\u25B6 woke ${slot} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
32389
|
+
}
|
|
32390
|
+
}
|
|
32369
32391
|
beat();
|
|
32370
32392
|
const pulse = setInterval(beat, 6e4);
|
|
32371
|
-
const spawnPoll = setInterval(() =>
|
|
32393
|
+
const spawnPoll = setInterval(() => {
|
|
32394
|
+
void claimAndSpawn();
|
|
32395
|
+
void sweepSlots();
|
|
32396
|
+
}, 5e3);
|
|
32372
32397
|
const stopSessions = () => {
|
|
32373
32398
|
for (const { run, label } of runs.values()) {
|
|
32374
32399
|
run.stop();
|
|
@@ -32510,7 +32535,9 @@ async function main() {
|
|
|
32510
32535
|
}
|
|
32511
32536
|
} else console.log("start hosting with: paigy-harness host (keep it running under your init system)");
|
|
32512
32537
|
const TERMINAL_AGENTS = [
|
|
32513
|
-
|
|
32538
|
+
// Wire = MCP registration + the plugin (skills, hooks, idle-escalation — the
|
|
32539
|
+
// piece that makes a LIVE session notice your requests without polling).
|
|
32540
|
+
{ cli: "claude", name: "Claude Code", slot: "mcp-agent", wire: "claude mcp add --scope user paigy -- npx -y @paigy/mcp@latest && claude plugin marketplace add paigy-ai/mcp; claude plugin install paigy@paigy" },
|
|
32514
32541
|
{ cli: "codex", name: "Codex", slot: "codex", wire: "codex mcp add paigy -- npx -y @paigy/mcp@latest" },
|
|
32515
32542
|
{ cli: "antigravity", name: "Antigravity", slot: "antigravity" }
|
|
32516
32543
|
];
|
package/dist/main.js
CHANGED
|
@@ -12087,6 +12087,9 @@ function readToken(agent2 = AGENT_NAME) {
|
|
|
12087
12087
|
if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
|
|
12088
12088
|
return readTokenFile()[agent2]?.access_token ?? "";
|
|
12089
12089
|
}
|
|
12090
|
+
function listSlots() {
|
|
12091
|
+
return Object.keys(readTokenFile());
|
|
12092
|
+
}
|
|
12090
12093
|
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
12091
12094
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
12092
12095
|
method: "POST",
|
|
@@ -13522,7 +13525,8 @@ async function drainInput(state, deps = {}) {
|
|
|
13522
13525
|
const claimed = [];
|
|
13523
13526
|
for (const item of mine) {
|
|
13524
13527
|
try {
|
|
13525
|
-
await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
13528
|
+
const ack = await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
13529
|
+
if (ack?.won === false) continue;
|
|
13526
13530
|
claimed.push(item);
|
|
13527
13531
|
} catch {
|
|
13528
13532
|
}
|
|
@@ -13837,9 +13841,30 @@ function startHost(opts) {
|
|
|
13837
13841
|
opts.log(`\u25B6 phone-launched ${label} (${spec.harness}) in ${spec.workspace}`);
|
|
13838
13842
|
}
|
|
13839
13843
|
}
|
|
13844
|
+
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
|
|
13845
|
+
async function sweepSlots() {
|
|
13846
|
+
const workspace = listWorkspaces(opts.wsDeps)[0];
|
|
13847
|
+
if (!workspace) return;
|
|
13848
|
+
for (const slot of listSlots()) {
|
|
13849
|
+
const harness = SLOT_HARNESS[slot];
|
|
13850
|
+
const key = `slot:${slot}`;
|
|
13851
|
+
if (!harness || runs.has(key)) continue;
|
|
13852
|
+
const token = readToken(slot);
|
|
13853
|
+
if (!token) continue;
|
|
13854
|
+
const work = await checkReplies({ token }).catch(() => null);
|
|
13855
|
+
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
13856
|
+
const log2 = (line) => opts.log(`[${slot}] ${line}`);
|
|
13857
|
+
const run = runHarness({ harness, cwd: workspace, mode: "bypass", prompt: "", exclusive: true, token, log: log2 });
|
|
13858
|
+
runs.set(key, { run, label: slot });
|
|
13859
|
+
opts.log(`\u25B6 woke ${slot} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
13860
|
+
}
|
|
13861
|
+
}
|
|
13840
13862
|
beat();
|
|
13841
13863
|
const pulse = setInterval(beat, 6e4);
|
|
13842
|
-
const spawnPoll = setInterval(() =>
|
|
13864
|
+
const spawnPoll = setInterval(() => {
|
|
13865
|
+
void claimAndSpawn();
|
|
13866
|
+
void sweepSlots();
|
|
13867
|
+
}, 5e3);
|
|
13843
13868
|
const stopSessions = () => {
|
|
13844
13869
|
for (const { run, label } of runs.values()) {
|
|
13845
13870
|
run.stop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paigy/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Run Claude Code / Codex on this machine, bridged to Paigy — launchable from your phone.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"vitest": "^2.1.8",
|
|
19
19
|
"qrcode": "^1.5.4",
|
|
20
20
|
"zod": "^3.24.1",
|
|
21
|
-
"@paigy/
|
|
22
|
-
"@paigy/
|
|
21
|
+
"@paigy/sdk": "0.1.0",
|
|
22
|
+
"@paigy/schema": "0.0.0"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist/cli.js",
|