pidge-cli 0.15.1 → 0.15.3
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/CHANGELOG.md +37 -0
- package/bin/pidge.js +76 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.3 — #33 fix: the self-heal marker no longer corrupts the skill
|
|
4
|
+
|
|
5
|
+
The 0.15.2 marker was written as the FIRST line of `SKILL.md`, ABOVE the opening `---`. A
|
|
6
|
+
SKILL.md whose first line isn't `---` fails Claude Code's YAML frontmatter parse: the skill
|
|
7
|
+
still appears, but with a GARBAGE description (the HTML comment leaks in as the description and
|
|
8
|
+
the real `name`/`description` are lost) — so the agent's Claude Code never learns WHEN to use
|
|
9
|
+
Pidge. Verified on a live headless `claude` run: a marker-first probe skill loaded with its
|
|
10
|
+
description showing `<!-- pidge-skill … -->` instead of its real text, while an identical
|
|
11
|
+
`---`-first control loaded correctly.
|
|
12
|
+
|
|
13
|
+
- **fix:** the marker now rides a `# pidge-skill rev=R manifest=N` YAML COMMENT INSIDE the
|
|
14
|
+
frontmatter (a `#` comment is valid YAML and invisible to `name`/`description`), so the
|
|
15
|
+
frontmatter opens on line 1 and parses cleanly while the marker still travels with the file.
|
|
16
|
+
- **fix:** `ensureSkillFresh()` reads the marker from its new in-frontmatter position and still
|
|
17
|
+
tolerates the old line-1 `<!-- … -->` marker, so a 0.15.2 install is detected as stale.
|
|
18
|
+
- **fix:** `SKILL_REVISION` bumped 1 → 2, so every 0.15.2 install (all rev=1, all broken) is
|
|
19
|
+
seen as stale and self-heals into the corrected format on the next command — zero human action.
|
|
20
|
+
|
|
21
|
+
## 0.15.2 — #280 the skill self-heals
|
|
22
|
+
|
|
23
|
+
#280 — the local skill self-heals: any pidge command silently refreshes
|
|
24
|
+
`.claude/skills/pidge/SKILL.md` when the skill revision or manifest version is newer than
|
|
25
|
+
what's installed, so onboarded agents always run the latest skill.
|
|
26
|
+
|
|
27
|
+
- **feat:** a bumpable `SKILL_REVISION` constant + the live manifest version are baked into a
|
|
28
|
+
first-line marker (`<!-- pidge-skill rev=R manifest=N -->`) of every generated `SKILL.md`.
|
|
29
|
+
`ensureSkillFresh()` (run from `checkManifestNews`, which already fires on every command via
|
|
30
|
+
the `x-pidge-manifest-version` header) compares the installed marker against this CLI's spine
|
|
31
|
+
and the server's manifest; when either is newer it silently regenerates the skill and prints
|
|
32
|
+
one stderr note. Only an EXISTING skill is refreshed (never auto-created), at most once per
|
|
33
|
+
process, fully best-effort (a refresh failure never breaks your command), and `QUIET_NAG`
|
|
34
|
+
suppresses the note (the regenerate still happens).
|
|
35
|
+
- **note:** this composes with `@latest` — an agent on `npx pidge-cli@latest` picks up a new
|
|
36
|
+
`SKILL_REVISION` and self-heals the spine on its next command. A PINNED CLI still self-heals
|
|
37
|
+
on a server manifest bump (via the header) but can't gain a newer hand-authored spine without
|
|
38
|
+
updating the CLI. BUMP `SKILL_REVISION` in any future sprint that edits the SKILL.md spine.
|
|
39
|
+
|
|
3
40
|
## 0.15.1 — #274-D skill polish
|
|
4
41
|
|
|
5
42
|
skill polish — catalog-first actions, write-for-the-lock-screen (banner = title+body; body_markdown is detail-only), good-report guidance; gold examples now set a plain --body.
|
package/bin/pidge.js
CHANGED
|
@@ -562,8 +562,20 @@ function fetchT(url, opts = {}, timeoutMs = 30000) {
|
|
|
562
562
|
// than what this CLI shipped knowing, nudge on stderr — the agent re-reads the
|
|
563
563
|
// manifest (whats_new) and learns the new capabilities without polling.
|
|
564
564
|
const KNOWN_MANIFEST_VERSION = 46;
|
|
565
|
+
// #280: the hand-authored skill SPINE version. BUMP whenever the SKILL.md spine
|
|
566
|
+
// (the non-generated prose in installSkill) changes — an existing install whose
|
|
567
|
+
// baked marker is older than this self-heals on its next pidge command, so an
|
|
568
|
+
// onboarded agent always runs the latest skill without any human action. Start at 1.
|
|
569
|
+
// Bumped to 2 in 0.15.3 so every 0.15.2 install (which baked the marker ABOVE the `---`,
|
|
570
|
+
// corrupting the skill's description) is detected as stale and self-heals into the fixed
|
|
571
|
+
// in-frontmatter format on the next command. Bump this whenever the hand-authored spine moves.
|
|
572
|
+
const SKILL_REVISION = 2;
|
|
565
573
|
const NAG_TTL_MS = 24 * 60 * 60 * 1000; // #241: at most one nag per 24 h
|
|
566
574
|
let newsWarned = false;
|
|
575
|
+
// #280: the self-heal runs at most ONCE per process (one regeneration, even when
|
|
576
|
+
// many commands/poll-ticks call checkManifestNews). Non-stale checks stay cheap +
|
|
577
|
+
// repeatable; this only latches once an actual heal is attempted.
|
|
578
|
+
let skillHealed = false;
|
|
567
579
|
|
|
568
580
|
// #241: a tiny per-install state cache (~/.config/pidge/state.json, per-agent
|
|
569
581
|
// when PIDGE_AGENT is set — same dir as the env file). Best-effort: a read-only
|
|
@@ -581,9 +593,14 @@ function writeState(patch) {
|
|
|
581
593
|
} catch { /* best-effort — the nag just won't persist its throttle */ }
|
|
582
594
|
}
|
|
583
595
|
|
|
584
|
-
function checkManifestNews(res) {
|
|
585
|
-
if (QUIET_NAG || newsWarned) return;
|
|
596
|
+
async function checkManifestNews(res) {
|
|
586
597
|
const ver = parseInt(res.headers.get('x-pidge-manifest-version') || '0', 10);
|
|
598
|
+
// #280: the self-heal runs on EVERY command (its own once-guard + cheap
|
|
599
|
+
// first-line read), BEFORE the nag throttle below — it must fire even when the
|
|
600
|
+
// server isn't ahead of KNOWN_MANIFEST_VERSION (a pure spine bump) and even
|
|
601
|
+
// under QUIET_NAG (which only silences the stderr note, never the regenerate).
|
|
602
|
+
await ensureSkillFresh(ver);
|
|
603
|
+
if (QUIET_NAG || newsWarned) return;
|
|
587
604
|
// (c) only when the server is ahead of what THIS CLI knows.
|
|
588
605
|
if (!(ver > KNOWN_MANIFEST_VERSION)) return;
|
|
589
606
|
// #241 throttle: nag at most once per 24 h, and after that window only when the
|
|
@@ -607,6 +624,44 @@ function checkManifestNews(res) {
|
|
|
607
624
|
console.error(`pidge: the server has NEW capabilities (manifest v${ver}; this CLI knows v${KNOWN_MANIFEST_VERSION}) — pidge is a thin pipe, so you can use any new /notify field RIGHT NOW via --param KEY=VALUE. Read the catalog (whats_new) in the public manifest: curl $PIDGE_URL/api/v1/manifest (public; add -H "Authorization: Bearer $PIDGE_TOKEN" to also see your channel's config). Updating the CLI only matters to gain native flags: npx pidge-cli@latest (a pinned ref never self-updates). Silence this with --quiet-nag or PIDGE_QUIET_NAG=1.`);
|
|
608
625
|
}
|
|
609
626
|
|
|
627
|
+
// #280: STRUCTURAL self-heal — keep the LOCAL skill current with zero human action.
|
|
628
|
+
// The installed .claude/skills/pidge/SKILL.md is written once at onboarding and then
|
|
629
|
+
// goes stale silently (a CLI/skill improvement gives an onboarded agent no signal, so
|
|
630
|
+
// it keeps running the old skill). This silently regenerates it when EITHER trigger
|
|
631
|
+
// fires: this CLI's hand-authored spine moved (SKILL_REVISION > the baked rev) OR the
|
|
632
|
+
// server's manifest moved (serverManifestVersion > the baked manifest) — caught from
|
|
633
|
+
// the x-pidge-manifest-version header that already rides every response. So the agent's
|
|
634
|
+
// NEXT session is always current. Only REFRESHES an existing skill (creating one is
|
|
635
|
+
// onboarding's job, never a side effect of an unrelated command), runs at most once per
|
|
636
|
+
// process, and is wholly best-effort: any failure is swallowed — a skill refresh must
|
|
637
|
+
// NEVER break the user's actual command.
|
|
638
|
+
async function ensureSkillFresh(serverManifestVersion) {
|
|
639
|
+
if (skillHealed) return;
|
|
640
|
+
try {
|
|
641
|
+
// Resolve the path the SAME way installSkill does (cwd-relative).
|
|
642
|
+
const file = path.join(process.cwd(), '.claude', 'skills', 'pidge', 'SKILL.md');
|
|
643
|
+
if (!fs.existsSync(file)) return; // don't auto-create — only refresh an existing skill
|
|
644
|
+
// #33 fix: the marker now rides a `# pidge-skill rev=N manifest=M` YAML comment INSIDE
|
|
645
|
+
// the frontmatter (0.15.3+); pre-0.15.3 installs put `<!-- pidge-skill … -->` as line 1.
|
|
646
|
+
// Scan for the marker line either way — the token `pidge-skill` appears ONLY there in a
|
|
647
|
+
// generated skill — so a stale OLD-format install is still detected and healed into the
|
|
648
|
+
// corrected format (its garbage-description bug repaired) on the next command.
|
|
649
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
650
|
+
const markerLine = content.split('\n').find((l) => l.includes('pidge-skill')) || '';
|
|
651
|
+
const revM = markerLine.match(/rev=(\d+)/);
|
|
652
|
+
const manM = markerLine.match(/manifest=(\d+)/);
|
|
653
|
+
const installedRev = revM ? parseInt(revM[1], 10) : 0;
|
|
654
|
+
const installedManifest = manM ? parseInt(manM[1], 10) : 0;
|
|
655
|
+
const stale = SKILL_REVISION > installedRev || (serverManifestVersion || 0) > installedManifest;
|
|
656
|
+
if (!stale) return;
|
|
657
|
+
skillHealed = true; // latch BEFORE the network write — attempt the heal at most once
|
|
658
|
+
const r = await installSkill(BASE, TOKEN); // silent: it already writes the file
|
|
659
|
+
// Respect QUIET_NAG/PIDGE_QUIET_NAG for the note only — we STILL regenerated.
|
|
660
|
+
if (!QUIET_NAG)
|
|
661
|
+
console.error(`pidge: refreshed your local Pidge skill (rev ${SKILL_REVISION}, manifest v${r.manifest_version}) — your next session will use it.`);
|
|
662
|
+
} catch { /* best-effort — a skill refresh must never break the user's command */ }
|
|
663
|
+
}
|
|
664
|
+
|
|
610
665
|
// ---------------------------------------------------------------------------
|
|
611
666
|
// #119: the health ledger of one blocking session (wait/ask/listen). Drives
|
|
612
667
|
// (a) automatic DEGRADE from held ?wait= polls to plain GETs after
|
|
@@ -976,7 +1031,7 @@ async function doNotify(extra = {}) {
|
|
|
976
1031
|
} catch (e) {
|
|
977
1032
|
die(`pidge: send failed (network): ${e.message}`, 2);
|
|
978
1033
|
}
|
|
979
|
-
checkManifestNews(res);
|
|
1034
|
+
await checkManifestNews(res);
|
|
980
1035
|
const ok = res.status >= 200 && res.status < 300;
|
|
981
1036
|
let info = {};
|
|
982
1037
|
try { info = JSON.parse(raw); } catch { /* leave {} */ }
|
|
@@ -1100,7 +1155,7 @@ async function doWait(cid, { timeout, interval }) {
|
|
|
1100
1155
|
const askedAt = Date.now();
|
|
1101
1156
|
try {
|
|
1102
1157
|
const res = await fetchT(url, { headers }, (waitS + 10) * 1000);
|
|
1103
|
-
checkManifestNews(res);
|
|
1158
|
+
await checkManifestNews(res);
|
|
1104
1159
|
if (res.status === 200) {
|
|
1105
1160
|
health.ok();
|
|
1106
1161
|
const data = await res.json().catch(() => ({}));
|
|
@@ -1414,7 +1469,7 @@ async function runContract() {
|
|
|
1414
1469
|
} catch (e) {
|
|
1415
1470
|
die(`pidge: contract set failed (network): ${e.message}`, 2);
|
|
1416
1471
|
}
|
|
1417
|
-
checkManifestNews(res);
|
|
1472
|
+
await checkManifestNews(res);
|
|
1418
1473
|
if (!(res.status >= 200 && res.status < 300)) die(`pidge: contract set failed (${res.status}): ${body}`, 2);
|
|
1419
1474
|
// stdout = ONLY the operating_contract, never the raw channel JSON. The
|
|
1420
1475
|
// /channels PATCH echoes the whole channel — INCLUDING "key":"hld_…" — and
|
|
@@ -1464,7 +1519,7 @@ async function doSelftest() {
|
|
|
1464
1519
|
const res = await fetchT(`${BASE}/api/v1/selftest`, {
|
|
1465
1520
|
method: 'POST', headers, body: JSON.stringify({ window_seconds: windowS }),
|
|
1466
1521
|
});
|
|
1467
|
-
checkManifestNews(res);
|
|
1522
|
+
await checkManifestNews(res);
|
|
1468
1523
|
if (res.status < 200 || res.status >= 300) die(`pidge: selftest: the server refused (${res.status}) — is your key valid? try \`pidge doctor\``, 2);
|
|
1469
1524
|
fired = await res.json();
|
|
1470
1525
|
} catch (e) {
|
|
@@ -1537,7 +1592,7 @@ async function runDoctor(base = BASE, token = TOKEN, sourceLabel = null) {
|
|
|
1537
1592
|
process.exit(2);
|
|
1538
1593
|
}
|
|
1539
1594
|
const { res, data } = out;
|
|
1540
|
-
checkManifestNews(res);
|
|
1595
|
+
await checkManifestNews(res);
|
|
1541
1596
|
if (res.status === 401) {
|
|
1542
1597
|
console.error('pidge doctor: server reachable but the key is INVALID/REVOKED — re-onboard: ask your human for a fresh claim code (Pidge app → Canais → o canal → copiar prompt de setup)');
|
|
1543
1598
|
process.exit(2);
|
|
@@ -1719,9 +1774,17 @@ async function installSkill(base = BASE, token = TOKEN) {
|
|
|
1719
1774
|
const profileTable = (m.profiles && m.profiles.decision_table) || [];
|
|
1720
1775
|
const notes = m.notes || [];
|
|
1721
1776
|
const exits = (m.cli && m.cli.output) || '';
|
|
1777
|
+
// #33 fix (0.15.3): the self-heal marker rides a `# pidge-skill …` YAML COMMENT INSIDE
|
|
1778
|
+
// the frontmatter — it MUST NOT precede the opening `---`. A SKILL.md whose first line
|
|
1779
|
+
// isn't `---` fails the YAML frontmatter parse, so Claude Code loads the skill with a
|
|
1780
|
+
// GARBAGE description (the HTML comment leaked in as the description, the real one lost)
|
|
1781
|
+
// — proven on a live headless run. A `#` comment line is valid YAML and invisible to
|
|
1782
|
+
// name/description, so the marker survives without corrupting the load. ensureSkillFresh
|
|
1783
|
+
// reads it from this position (and still tolerates the old line-1 marker to heal it).
|
|
1722
1784
|
const skill = `---
|
|
1723
1785
|
name: pidge
|
|
1724
1786
|
description: Send rich, actionable iPhone notifications to your human and get their decision back (Pidge). Every send is a TYPE (message/important/urgent/event/live) plus an OPTIONAL response (buttons + send-and-go vs wait). Use when finishing long tasks, needing a decision/approval, sending updates with substance, or anything time-anchored. Also covers reading the human's replies back.
|
|
1787
|
+
# pidge-skill rev=${SKILL_REVISION} manifest=${m.manifest_version}
|
|
1725
1788
|
---
|
|
1726
1789
|
|
|
1727
1790
|
# Pidge — notify your human, get answers back
|
|
@@ -1881,7 +1944,7 @@ A turn-based agent (Claude Code, anything invoked on demand) stays COMMANDABLE w
|
|
|
1881
1944
|
}
|
|
1882
1945
|
case 'whoami': {
|
|
1883
1946
|
const { res, data } = await fetchWhoami().catch((e) => { die(`pidge: whoami failed (network): ${e.message}`, 2); });
|
|
1884
|
-
checkManifestNews(res);
|
|
1947
|
+
await checkManifestNews(res);
|
|
1885
1948
|
if (res.status !== 200) die(`pidge: whoami failed (${res.status}): ${JSON.stringify(data)}`, 2);
|
|
1886
1949
|
console.log(JSON.stringify(data, null, 2));
|
|
1887
1950
|
console.error(`pidge: you are canal "${data.channel && data.channel.name}" · ${data.devices ?? '?'} device(s)`);
|
|
@@ -2017,7 +2080,7 @@ A turn-based agent (Claude Code, anything invoked on demand) stays COMMANDABLE w
|
|
|
2017
2080
|
} catch (e) {
|
|
2018
2081
|
die(`pidge: cancel failed (network): ${e.message}`, 2);
|
|
2019
2082
|
}
|
|
2020
|
-
checkManifestNews(res);
|
|
2083
|
+
await checkManifestNews(res);
|
|
2021
2084
|
console.log(raw);
|
|
2022
2085
|
if (res.status >= 200 && res.status < 300) {
|
|
2023
2086
|
console.error(`pidge: cancelled ${cid} — nothing will fire`);
|
|
@@ -2046,7 +2109,7 @@ A turn-based agent (Claude Code, anything invoked on demand) stays COMMANDABLE w
|
|
|
2046
2109
|
} catch (e) {
|
|
2047
2110
|
die(`pidge: ack failed (network): ${e.message}`, 2);
|
|
2048
2111
|
}
|
|
2049
|
-
checkManifestNews(res);
|
|
2112
|
+
await checkManifestNews(res);
|
|
2050
2113
|
console.log(raw);
|
|
2051
2114
|
if (!(res.status >= 200 && res.status < 300)) die(`pidge: ack failed (${res.status}): ${raw}`, 2);
|
|
2052
2115
|
let adata = {};
|
|
@@ -2085,7 +2148,7 @@ A turn-based agent (Claude Code, anything invoked on demand) stays COMMANDABLE w
|
|
|
2085
2148
|
} catch (e) {
|
|
2086
2149
|
die(`pidge: inbox failed (network): ${e.message}`, 2);
|
|
2087
2150
|
}
|
|
2088
|
-
checkManifestNews(res);
|
|
2151
|
+
await checkManifestNews(res);
|
|
2089
2152
|
console.log(raw);
|
|
2090
2153
|
if (!(res.status >= 200 && res.status < 300)) die(`pidge: inbox failed (${res.status})`, 2);
|
|
2091
2154
|
let data = {};
|
|
@@ -2194,7 +2257,7 @@ A turn-based agent (Claude Code, anything invoked on demand) stays COMMANDABLE w
|
|
|
2194
2257
|
draining = true;
|
|
2195
2258
|
try {
|
|
2196
2259
|
const res = await fetchT(`${BASE}/api/v1/messages${queueQs}`, { headers });
|
|
2197
|
-
checkManifestNews(res);
|
|
2260
|
+
await checkManifestNews(res);
|
|
2198
2261
|
if (res.status === 200) {
|
|
2199
2262
|
health.ok();
|
|
2200
2263
|
const msgs = (await res.json().catch(() => ({}))).messages || [];
|
|
@@ -2253,7 +2316,7 @@ A turn-based agent (Claude Code, anything invoked on demand) stays COMMANDABLE w
|
|
|
2253
2316
|
if (waitS > 0) qs.set('wait', String(waitS));
|
|
2254
2317
|
if (v.all) qs.set('all', 'true');
|
|
2255
2318
|
const res = await fetchT(`${BASE}/api/v1/messages${qs.size ? `?${qs}` : ''}`, { headers }, (waitS + 10) * 1000);
|
|
2256
|
-
checkManifestNews(res);
|
|
2319
|
+
await checkManifestNews(res);
|
|
2257
2320
|
if (res.status === 200) {
|
|
2258
2321
|
health.ok();
|
|
2259
2322
|
const data = await res.json().catch(() => ({}));
|