pidge-cli 0.11.0 → 0.11.1
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 +8 -1
- package/bin/pidge.js +57 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,13 @@ then gets the answer as JSON — no webhook, no polling loop to write.
|
|
|
33
33
|
> stdout now carries only the `operating_contract`, so the key never lands in an agent's
|
|
34
34
|
> transcript/logs.
|
|
35
35
|
>
|
|
36
|
+
> **v0.11.1** (Pidge manifest v31): **`pidge doctor` now probes the realtime path** (#171) —
|
|
37
|
+
> after the HTTP checks it opens a quick `/cable` subscription and reports `realtime: ok` or
|
|
38
|
+
> `realtime: INDISPONÍVEL` (the #119 failure class an HTTP-only doctor couldn't see: an edge
|
|
39
|
+
> killing held responses, a proxy refusing the WebSocket). Exit stays `0` — an unavailable WS
|
|
40
|
+
> just degrades `listen` to polling — but you learn it BEFORE the first deaf listen. The doctor
|
|
41
|
+
> hint now leads with `pidge hello`, and the version nudge knows v31 (#229).
|
|
42
|
+
>
|
|
36
43
|
> **v0.11.0** (Pidge manifest v30): the **first-contact WOW** (#217). New **`pidge hello`** —
|
|
37
44
|
> your channel's debut handshake, narrated LIVE on the lock screen by a server-driven 3-stage
|
|
38
45
|
> Live Activity (Conectando → toque para confirmar → Concluído ✓) so your human *sees* the
|
|
@@ -138,7 +145,7 @@ npx pidge-cli notify --title "Relatório" --file ./relatorio.xlsx
|
|
|
138
145
|
| `contract set <k>=<v>` / `contract show` | **v0.9.0:** DECLARE how you operate (`keep_connection_alive`, `mirror_in_origin_session`, `listen_mode=turn_based\|persistent\|external_daemon`, `quiet_when_idle`). **Advisory, never policy** — you declare, the human registers their expectation and *sees* if you honor it; Pidge enforces nothing. An unknown key/bad value is rejected locally (exit 1). |
|
|
139
146
|
| `selftest [--window N]` | **v0.10.0 (#205):** prove your listener works by ROUND-TRIP — fire a nonce, run the listener, confirm it picks it up + acks in time. PASS exit `0` / FAIL exit `2` with the likely cause (timeout / orphan / transport). Run it as the last onboarding step + whenever sends seem to go unheard. |
|
|
140
147
|
| `setup --claim <code>` | One-shot onboarding (v0.7.0): exchange the single-use code for the key, store it in `~/.config/pidge/env` (600), run doctor. **v0.9.0** also claims channel ownership so `doctor` can warn on a silent key swap. **v0.9.1+** declares your `operating_contract` (default `listen_mode=turn_based`; `--listen-mode persistent\|external_daemon` for a supervisor/daemon). |
|
|
141
|
-
| `doctor` | Validate the setup **without exposing secrets**: env source, server reachable, key valid, **honest device reach**, channel ownership. Exit 0/2. |
|
|
148
|
+
| `doctor` | Validate the setup **without exposing secrets**: env source, server reachable, key valid, **honest device reach**, channel ownership, and (**v0.11.1, #171**) a **realtime probe** (`realtime: ok / INDISPONÍVEL` — exit stays 0; an unavailable WS just degrades `listen` to polling). Exit 0/2. |
|
|
142
149
|
| `whoami` | Which channel does this key speak for (JSON). |
|
|
143
150
|
| `skill install` | Write `.claude/skills/pidge/SKILL.md` generated from the live manifest — persistent Pidge knowledge for Claude Code agents; re-run to update. |
|
|
144
151
|
| `--version` | Print the CLI version. |
|
package/bin/pidge.js
CHANGED
|
@@ -302,7 +302,7 @@ function fetchT(url, opts = {}, timeoutMs = 30000) {
|
|
|
302
302
|
// The server advertises its manifest version on every response. When it's newer
|
|
303
303
|
// than what this CLI shipped knowing, nudge ONCE on stderr — the agent re-reads
|
|
304
304
|
// the manifest (whats_new) and learns the new capabilities without polling.
|
|
305
|
-
const KNOWN_MANIFEST_VERSION =
|
|
305
|
+
const KNOWN_MANIFEST_VERSION = 31;
|
|
306
306
|
let newsWarned = false;
|
|
307
307
|
function checkManifestNews(res) {
|
|
308
308
|
const v = parseInt(res.headers.get('x-pidge-manifest-version') || '0', 10);
|
|
@@ -381,10 +381,10 @@ function wantRealtime() {
|
|
|
381
381
|
// The server pings every ~3 s — that heartbeat is the liveness check (silence
|
|
382
382
|
// >15 s ⇒ the socket is dead even if TCP hasn't noticed; close → caller
|
|
383
383
|
// reconnects). Returns {close()} or null if the constructor itself failed.
|
|
384
|
-
function cableSubscribe({ channel, onUp, onFrame, onDown }) {
|
|
384
|
+
function cableSubscribe({ channel, onUp, onFrame, onDown, base = BASE, token = TOKEN }) {
|
|
385
385
|
let ws;
|
|
386
386
|
try {
|
|
387
|
-
ws = new WebSocket(
|
|
387
|
+
ws = new WebSocket(base.replace(/^http/, 'ws') + '/cable', ['actioncable-v1-json', token]);
|
|
388
388
|
} catch (e) { onDown(e.message); return null; }
|
|
389
389
|
const identifier = JSON.stringify({ channel });
|
|
390
390
|
let lastBeat = Date.now();
|
|
@@ -452,6 +452,41 @@ async function cableSession({ channel, deadline, onUp, onFrame }) {
|
|
|
452
452
|
return 'deadline';
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// #171: doctor's realtime probe — the failure class an HTTP-only doctor can't
|
|
456
|
+
// see (#119: an edge killing held responses, a proxy refusing the upgrade). A
|
|
457
|
+
// green HTTP doctor can coexist with a `listen` that's deaf over the socket.
|
|
458
|
+
// Open ONE ConversationChannel subscription on /cable (reusing cableSubscribe —
|
|
459
|
+
// the same client `listen` holds), wait for confirm_subscription, close — all
|
|
460
|
+
// within ≤5 s. Degrade is the CONTRACT, not a failure: an unavailable WS just
|
|
461
|
+
// means `listen` polls (works, less instant), so this NEVER changes the exit
|
|
462
|
+
// code — it only lets the agent KNOW before the first deaf listen. Resolves
|
|
463
|
+
// {ok, ms} | {ok:false, reason} | {skipped:true} (Node <22 has no native
|
|
464
|
+
// WebSocket — same gate as wantRealtime, :373).
|
|
465
|
+
function probeRealtime(base, token) {
|
|
466
|
+
if (typeof WebSocket !== 'function') return Promise.resolve({ skipped: true });
|
|
467
|
+
return new Promise((resolve) => {
|
|
468
|
+
const started = Date.now();
|
|
469
|
+
let settled = false;
|
|
470
|
+
let sub = null;
|
|
471
|
+
const done = (result) => {
|
|
472
|
+
if (settled) return; settled = true;
|
|
473
|
+
clearTimeout(guard);
|
|
474
|
+
if (sub) sub.close();
|
|
475
|
+
resolve(result);
|
|
476
|
+
};
|
|
477
|
+
const guard = setTimeout(() => done({ ok: false, reason: 'no confirm_subscription within 5s' }), 5000);
|
|
478
|
+
sub = cableSubscribe({
|
|
479
|
+
channel: 'ConversationChannel',
|
|
480
|
+
base,
|
|
481
|
+
token,
|
|
482
|
+
onUp: () => done({ ok: true, ms: Date.now() - started }),
|
|
483
|
+
onFrame: () => { /* a stray frame during the probe is irrelevant */ },
|
|
484
|
+
onDown: (why) => done({ ok: false, reason: why }),
|
|
485
|
+
});
|
|
486
|
+
if (!sub) done({ ok: false, reason: 'WebSocket constructor failed' });
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
455
490
|
// Map CLI flags → the /notify JSON body, including only what was provided.
|
|
456
491
|
function buildBody() {
|
|
457
492
|
if (!v.title) die('pidge: --title is required', 1);
|
|
@@ -1093,8 +1128,25 @@ async function runDoctor(base = BASE, token = TOKEN, sourceLabel = null) {
|
|
|
1093
1128
|
console.error('pidge doctor: BROKEN (exit 2) — devices exist but 0 are reachable (all disabled or on the wrong APNs environment): a send reaches nobody.');
|
|
1094
1129
|
process.exit(2);
|
|
1095
1130
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1131
|
+
// #171: probe the realtime path (the #119 failure class an HTTP-only doctor
|
|
1132
|
+
// misses). Exit stays 0 either way — an unavailable WS degrades to polling.
|
|
1133
|
+
const rt = await probeRealtime(base, token);
|
|
1134
|
+
let realtime;
|
|
1135
|
+
if (rt.skipped) {
|
|
1136
|
+
realtime = 'skipped';
|
|
1137
|
+
console.error('pidge doctor: realtime: skipped — this Node lacks a native WebSocket (need Node ≥22); `listen` will poll. Upgrade Node for instant delivery.');
|
|
1138
|
+
} else if (rt.ok) {
|
|
1139
|
+
realtime = 'ok';
|
|
1140
|
+
console.error(`pidge doctor: realtime: ok (ws connect + subscribe em ${rt.ms}ms)`);
|
|
1141
|
+
} else {
|
|
1142
|
+
realtime = 'unavailable';
|
|
1143
|
+
console.error(`pidge doctor: realtime: INDISPONÍVEL — ${rt.reason}. O \`listen\` degrada pra polling (funciona, menos instantâneo); use --no-realtime pra fixar o piso.`);
|
|
1144
|
+
}
|
|
1145
|
+
// #229: lead with `pidge hello` — the first-contact WOW (send + wait in one),
|
|
1146
|
+
// the same debut the /agent-setup guide leads with. It's a thin wrapper over
|
|
1147
|
+
// `ask --template onboarding` (the underlying mechanism, if you need it raw).
|
|
1148
|
+
console.error('pidge doctor: all good — try: pidge hello (first-contact WOW — send + wait in one; equivalent: pidge ask --template onboarding)');
|
|
1149
|
+
console.log(JSON.stringify({ ok: true, base_url: base, channel: data.channel, devices, manifest_version: data.manifest_version, realtime }));
|
|
1098
1150
|
process.exit(0);
|
|
1099
1151
|
}
|
|
1100
1152
|
|