@astrofoundry/pi-astro 0.20.2 → 0.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/pi-astro",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
4
4
  "description": "Personal pi customizations (extensions, subagents, skills, prompts, themes) for the pi coding agent.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -31,13 +31,14 @@ A model load after restart takes up to a minute; `health=503` during that time i
31
31
  | `hermes-api-health` | `/health` (expect 200) and `/v1/models` (expect 401: the endpoint is up and requires the backend key) |
32
32
  | `hermes-version` | installed version |
33
33
  | `hermes-guest-status`, `hermes-snapshots` | LXC 101 state and snapshots (Pulsar) |
34
- | `hermes-releases [n]` | upstream releases of `NousResearch/hermes-agent` with notes |
34
+ | `hermes-releases [n]` | upstream releases of `NousResearch/hermes-agent`, notes cut at 6000 characters |
35
+ | `hermes-release <tag>` | one release with its complete notes |
35
36
  | `hermes-upgrade --confirm` | Pulsar wrapper: snapshot `preupgrade<timestamp>`, `hermes update`, `hermes config migrate`, restart both gateways, wait for a Discord connection and API 401, keep the two newest snapshots |
36
37
 
37
38
  ## Hermes upgrade procedure
38
39
 
39
40
  1. `hermes-version` and `hermes-releases 10`.
40
- 2. Read every release from the installed version up to the target. Stop and report when a note mentions a manual step, a configuration change beyond `hermes config migrate`, a breaking change, a new Python or system dependency, or a required intermediate version. Quote the sentence.
41
+ 2. Read every release from the installed version up to the target; use `hermes-release <tag>` for any note the list cut off. Stop and report when a note mentions a manual step, a configuration change beyond `hermes config migrate`, a breaking change, a new Python or system dependency, or a required intermediate version. Quote the sentence.
41
42
  3. `hermes-status` and `hermes-api-health` must be healthy before the upgrade; `hermes-snapshots` shows the existing snapshots.
42
43
  4. `hermes-upgrade --confirm` (up to 20 minutes). The wrapper prints the rollback command when a step fails; repeat it verbatim in your report and do not roll back yourself.
43
44
  5. `hermes-version`, `hermes-status`, `hermes-api-health`. Report old and new version, the snapshot name, and the health results.
@@ -14,7 +14,7 @@ port() { case "$1" in embed) echo 8081 ;; rerank) echo 8082 ;; esac; }
14
14
  agent_state() {
15
15
  l="$(label "$1")"
16
16
  printf '%-8s ' "$1"
17
- launchctl print "gui/$uid/$l" 2>/dev/null | grep -E '^\s*(state|pid) = ' | tr -s ' ' | tr '\n' ' ' || printf 'not loaded '
17
+ launchctl print "gui/$uid/$l" 2>/dev/null | grep -E '^[[:space:]](state|pid) = ' | tr -s ' ' | tr '\n' ' ' || printf 'not loaded '
18
18
  printf 'health=%s\n' "$(curl -s -o /dev/null -w '%{http_code}' -m 5 "http://127.0.0.1:$(port "$1")/health" || echo 000)"
19
19
  }
20
20
 
@@ -75,7 +75,8 @@ ${Object.entries(INFERENCE_COMMANDS)
75
75
  .map(([name, c]) => ` ${name.padEnd(22)} ${c.help}`)
76
76
  .join("\n")}
77
77
  europa-health GET the Europa embedding /health over HTTP (Europa is usually off by design)
78
- hermes-releases [n] latest n upstream Hermes releases with notes (default 5); read them before hermes-upgrade
78
+ hermes-releases [n] latest n upstream Hermes releases, notes cut at 6000 chars (default 5)
79
+ hermes-release <tag> one release with its full notes; read every release between the installed version and the target before hermes-upgrade
79
80
 
80
81
  Europa control and Hermes configuration are operator tasks.`;
81
82
 
@@ -121,16 +122,29 @@ export function compactReleases(releases: GithubRelease[]): { tag: string; name:
121
122
  }));
122
123
  }
123
124
 
124
- async function hermesReleases(config: InferenceConfig, countArg: string | undefined): Promise<number> {
125
- const n = countArg === undefined ? 5 : Number(countArg);
126
- if (!Number.isInteger(n) || n < 1 || n > 30) throw new UsageError("hermes-releases [n]: n must be 1..30");
125
+ const RELEASE_TAG = /^[A-Za-z0-9._-]{1,60}$/;
126
+
127
+ async function github(config: InferenceConfig, path: string): Promise<unknown> {
127
128
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(config.hermesRepo)) throw new ServiceError("hermesRepo must be owner/name");
128
- const response = await fetch(`https://api.github.com/repos/${config.hermesRepo}/releases?per_page=${n}`, {
129
+ const response = await fetch(`https://api.github.com/repos/${config.hermesRepo}${path}`, {
129
130
  headers: { Accept: "application/vnd.github+json", "User-Agent": "pi-astro-inference-specialist" },
130
131
  signal: AbortSignal.timeout(30_000),
131
132
  });
132
- if (!response.ok) throw new ServiceError(`GitHub answered ${response.status} for ${config.hermesRepo} releases`);
133
- printJson({ repository: config.hermesRepo, releases: compactReleases((await response.json()) as GithubRelease[]) });
133
+ if (!response.ok) throw new ServiceError(`GitHub answered ${response.status} for ${config.hermesRepo}${path}`);
134
+ return response.json();
135
+ }
136
+
137
+ async function hermesReleases(config: InferenceConfig, countArg: string | undefined): Promise<number> {
138
+ const n = countArg === undefined ? 5 : Number(countArg);
139
+ if (!Number.isInteger(n) || n < 1 || n > 30) throw new UsageError("hermes-releases [n]: n must be 1..30");
140
+ printJson({ repository: config.hermesRepo, releases: compactReleases((await github(config, `/releases?per_page=${n}`)) as GithubRelease[]) });
141
+ return 0;
142
+ }
143
+
144
+ async function hermesRelease(config: InferenceConfig, tag: string | undefined): Promise<number> {
145
+ if (tag === undefined || !RELEASE_TAG.test(tag)) throw new UsageError("hermes-release <tag>, e.g. v2026.9.14");
146
+ const release = (await github(config, `/releases/tags/${encodeURIComponent(tag)}`)) as GithubRelease;
147
+ printJson({ repository: config.hermesRepo, ...compactReleases([release])[0], notes: release.body ?? "" });
134
148
  return 0;
135
149
  }
136
150
 
@@ -150,6 +164,7 @@ export async function command(args: string[]): Promise<number> {
150
164
  const config = readConfig<InferenceConfig>(SERVICE, SHAPE);
151
165
  if (args[0] === "europa-health") return europaHealth(config);
152
166
  if (args[0] === "hermes-releases") return hermesReleases(config, args[1]);
167
+ if (args[0] === "hermes-release") return hermesRelease(config, args[1]);
153
168
  const { remote, spec } = buildInferenceRemote(args);
154
169
  const targets: Record<Target, { host: string; user: string; keyFile: string }> = {
155
170
  nexus: { host: config.nexusHost, user: config.nexusUser, keyFile: secretPath(SERVICE, "SSH_KEY_NEXUS") },