@groeponline/pi-wishcraft 1.23.0 → 1.24.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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.24.0] - 2026-11-12
6
+
7
+ ### Fixed
8
+ - **Git probes no longer race interactive git.** Every read-only `git` spawn (`src/git/status.ts`, `bash-mode/completion.ts`) now runs with `GIT_OPTIONAL_LOCKS=0` and `windowsHide: true`, so status polling can't refresh `.git/index` under a real checkout and orphaned `index.lock` files stop bricking the repo.
9
+ - **Think levels high/xhigh/max got their rainbow back.** The thinking segment collapsed all three into the plain `thinking` color; it now uses dedicated `thinkingHigh`/`thinkingXhigh`/`thinkingMax` semantic colors (default `rainbow`, overridable per preset — the `rainbow` ColorValue itself is new).
10
+ - **Token totals prefer the provider-reported `totalTokens`.** The four-field sum undercounted providers that report reasoning/other tokens only in the total.
11
+
12
+
5
13
  ## [1.23.0] - 2026-11-08
6
14
 
7
15
  ### Fixed
package/RELEASE.md CHANGED
@@ -113,11 +113,34 @@ Measured from this repository on 2026-11-08:
113
113
  | `npm whoami` with `secrets.NPM_TOKEN` | `chefgroeponline` — valid |
114
114
  | `ACTIONS_ID_TOKEN_REQUEST_URL` in a job with `id-token: write` | present |
115
115
  | `POST /-/npm/v1/oidc/token/exchange/package/@groeponline%2fpi-wishcraft` | **401** |
116
-
117
- Two consequences: publishes carry no provenance attestation, and the token has
118
- to be replaced by hand when it expires. Switching to trusted publishing is a
119
- one-time npmjs.com change, after which the `NODE_AUTH_TOKEN` env comes out of
120
- the job:
116
+ | `npm publish` with that token (v1.23.0, run 42464125440) | **EOTP** — one-time password required |
117
+
118
+ **The token authenticates but cannot write.** It is a *Publish* token on an
119
+ account with 2FA enabled, so npm demands an OTP for `npm publish` and a CI
120
+ runner can never answer one.
121
+
122
+ **Resolved 2026-11-09 03:58 UTC.** The org `NPM_TOKEN` secret was replaced with a
123
+ granular token that has *Bypass 2FA*; run 42559311821 (dispatch on `v1.23.0`)
124
+ then published `1.23.0` and created its GitHub Release. No workflow change was
125
+ needed — `NODE_AUTH_TOKEN` still publishes. `v1.18.0`–`v1.22.0` stay unpublished:
126
+ their tag refs carry unpinned `uses: actions/...@v7` and the org policy rejects
127
+ them, so they cannot be replayed without rewriting tags. `1.23.0` supersedes them.
128
+
129
+ The two routes that were available, for the record:
130
+
131
+ - **Token route (unblocks v1.23.0 as tagged):** Access Tokens → Generate New
132
+ Token → **Granular**, read+write on `@groeponline` (or a classic
133
+ **Automation** token), with **Bypass 2FA** enabled; store it as the org
134
+ `NPM_TOKEN` secret. Then replay the tag:
135
+ `gh workflow run release.yml --ref v1.23.0` — that same job also creates the
136
+ missing GitHub Release.
137
+ - **OIDC route (no credential at all):** configure the trusted publisher below
138
+ and drop the `NODE_AUTH_TOKEN` env from the `publish` job. A tag cut *before*
139
+ that change still carries the token env and cannot be replayed, so this route
140
+ publishes the next version and leaves the hole.
141
+
142
+ Switching to trusted publishing is a one-time npmjs.com change, after which the
143
+ `NODE_AUTH_TOKEN` env comes out of the job:
121
144
 
122
145
  1. Sign in as the maintainer of `@groeponline/pi-wishcraft` (`chefgroeponline
123
146
  <joep@chefgroep.online>` — `npm view @groeponline/pi-wishcraft maintainers`).
@@ -149,9 +172,15 @@ Rules that follow from the current (token) configuration:
149
172
 
150
173
  For a scoped package npm answers a PUT with **404, not 403**, when the caller
151
174
  may not publish. That means the credential is missing, revoked, read-only or
152
- (`EOTP`) 2FA-blocked, or does not own the scope. It is not "package not found",
153
- and it is not a retryable transient. `ENEEDAUTH` instead means npm had no
154
- credential at all: `NODE_AUTH_TOKEN` was empty and the OIDC exchange refused.
175
+ does not own the scope. It is not "package not found", and it is not a
176
+ retryable transient. Two neighbours are distinct and have their own fix:
177
+
178
+ - `ENEEDAUTH` — npm had no credential at all: `NODE_AUTH_TOKEN` was empty and
179
+ the OIDC exchange refused.
180
+ - `EOTP` — the credential authenticates but the account requires a one-time
181
+ password for writes, which CI cannot supply. This is what v1.23.0 hit. The
182
+ token needs 2FA bypass (granular token with *Bypass 2FA*, or a classic
183
+ Automation token); retrying with the same token only burns another run.
155
184
 
156
185
  `scripts/npm-publish.sh` prints that diagnosis instead of the raw 404. Recovery:
157
186
 
@@ -246,6 +246,8 @@ function runGit(args: string[], cwd: string): string[] {
246
246
  cwd,
247
247
  encoding: "utf8",
248
248
  stdio: ["ignore", "pipe", "ignore"],
249
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
250
+ windowsHide: true,
249
251
  });
250
252
  if (result.status !== 0 || !result.stdout) return [];
251
253
  return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
package/build-stamp.json CHANGED
@@ -1 +1 @@
1
- {"source_sha":"a12a4900dbbf32823e1c2c6261c850eb21834d38"}
1
+ {"source_sha":"1e85fa41738909a1dd3d098ad8ef0986ae76a298"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groeponline/pi-wishcraft",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "Pi coding agent extension: operator cockpit with a live powerline status bar, idea queue, skill search, sticky Bash, hooks, policy guardrails, and TUI session UX.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.4.2",
@@ -29,11 +29,20 @@ export type { TokenRole, WishcraftTokens };
29
29
  /**
30
30
  * Which token each legacy semantic color reads from.
31
31
  *
32
- * `thinking*` is deliberately absent: those four are Pi's own thinking-level
32
+ * `thinking*` is deliberately absent: those seven are Pi's own thinking-level
33
33
  * colors, not part of the Wishcraft palette, so they pass through untouched.
34
34
  */
35
35
  export const SEMANTIC_TOKEN_ROLE: Record<
36
- Exclude<SemanticColor, "thinking" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium">,
36
+ Exclude<
37
+ SemanticColor,
38
+ | "thinking"
39
+ | "thinkingMinimal"
40
+ | "thinkingLow"
41
+ | "thinkingMedium"
42
+ | "thinkingHigh"
43
+ | "thinkingXhigh"
44
+ | "thinkingMax"
45
+ >,
37
46
  TokenRole
38
47
  > = {
39
48
  model: "primary",
@@ -54,12 +63,21 @@ export const SEMANTIC_TOKEN_ROLE: Record<
54
63
  /** Pi thinking-level colors, passed through rather than tokenised. */
55
64
  export const THINKING_COLORS: Pick<
56
65
  Required<ColorScheme>,
57
- "thinking" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium"
66
+ | "thinking"
67
+ | "thinkingMinimal"
68
+ | "thinkingLow"
69
+ | "thinkingMedium"
70
+ | "thinkingHigh"
71
+ | "thinkingXhigh"
72
+ | "thinkingMax"
58
73
  > = {
59
74
  thinking: "thinkingOff",
60
75
  thinkingMinimal: "thinkingMinimal",
61
76
  thinkingLow: "thinkingLow",
62
77
  thinkingMedium: "thinkingMedium",
78
+ thinkingHigh: "rainbow",
79
+ thinkingXhigh: "rainbow",
80
+ thinkingMax: "rainbow",
63
81
  };
64
82
 
65
83
  /**
@@ -3,7 +3,7 @@ import type { MotionEvent } from "../motion/types.ts";
3
3
  import type { CostCurrencyCode } from "../usage/rates.ts";
4
4
 
5
5
  // Theme color - either a pi theme color name or a custom hex color
6
- export type ColorValue = ThemeColor | `#${string}`;
6
+ export type ColorValue = ThemeColor | `#${string}` | "rainbow";
7
7
  export type ThemeLike = Pick<Theme, "fg">;
8
8
 
9
9
  // Semantic color names for segments
@@ -17,6 +17,9 @@ export type SemanticColor =
17
17
  | "thinkingMinimal"
18
18
  | "thinkingLow"
19
19
  | "thinkingMedium"
20
+ | "thinkingHigh"
21
+ | "thinkingXhigh"
22
+ | "thinkingMax"
20
23
  | "context"
21
24
  | "contextWarn"
22
25
  | "contextError"
@@ -370,6 +370,16 @@ export function createDeckComponent(
370
370
  if (state.route === "ideas") {
371
371
  const ideas = filteredIdeas(liveSnapshot(), state);
372
372
  if (!state.navMode && handleList(data, "selectedIdea", ideas.length)) return;
373
+ // Nav ↑↓ must reach the route-column handlers below, like every other
374
+ // list route; the body below used to swallow them (trailing return),
375
+ // stranding the cursor on Ideas forever.
376
+ if (matchesKey(data, "up") || matchesKey(data, "down")) {
377
+ if (state.navMode || ideas.length === 0) {
378
+ // fall through to the nav handlers at the bottom
379
+ } else {
380
+ return;
381
+ }
382
+ } else {
373
383
  const cursor = Math.min(state.selectedIdea, Math.max(0, ideas.length - 1));
374
384
  const idea = ideas[cursor];
375
385
  if (!idea?.id) return;
@@ -404,6 +414,7 @@ export function createDeckComponent(
404
414
  return;
405
415
  }
406
416
  return;
417
+ }
407
418
  }
408
419
 
409
420
  if (state.route === "ports") {
package/src/git/status.ts CHANGED
@@ -94,6 +94,10 @@ function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
94
94
  return new Promise((resolve) => {
95
95
  const proc = spawn("git", args, {
96
96
  stdio: ["ignore", "pipe", "pipe"],
97
+ // Read-only probes must not refresh .git/index — a stray index.lock
98
+ // there races interactive git and bricks the repo (upstream #156/#180).
99
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
100
+ windowsHide: true,
97
101
  });
98
102
 
99
103
  let stdout = "";
@@ -32,7 +32,13 @@ export const thinkingSegment: StatusLineSegment = {
32
32
  const content = `think:${label}`;
33
33
 
34
34
  if (level === "high" || level === "xhigh" || level === "max") {
35
- return { content: color(ctx, "thinking", content), visible: true };
35
+ const highKey =
36
+ level === "high"
37
+ ? "thinkingHigh"
38
+ : level === "xhigh"
39
+ ? "thinkingXhigh"
40
+ : "thinkingMax";
41
+ return { content: color(ctx, highKey, content), visible: true };
36
42
  }
37
43
 
38
44
  if (level === "minimal") {
@@ -25,6 +25,9 @@ const DEFAULT_COLORS: Required<ColorScheme> = {
25
25
  thinkingMinimal: "thinkingMinimal",
26
26
  thinkingLow: "thinkingLow",
27
27
  thinkingMedium: "thinkingMedium",
28
+ thinkingHigh: "rainbow",
29
+ thinkingXhigh: "rainbow",
30
+ thinkingMax: "rainbow",
28
31
  context: "dim",
29
32
  contextWarn: "warning",
30
33
  contextError: "error",
@@ -189,6 +192,9 @@ export function applyColor(
189
192
  color: ColorValue,
190
193
  text: string,
191
194
  ): string {
195
+ if (color === "rainbow") {
196
+ return rainbow(text);
197
+ }
192
198
  if (typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color)) {
193
199
  return `${hexToRgbAnsi(color)}${text}\x1b[0m`;
194
200
  }
@@ -36,6 +36,11 @@ export function isSessionAssistantMessage(value: unknown): value is AssistantMes
36
36
  }
37
37
 
38
38
  export function getUsageTokenTotal(usage: SessionAssistantUsage): number {
39
+ // Prefer the provider-reported total: it can cover token kinds the four
40
+ // fields miss (e.g. reasoning on providers that don't itemize).
41
+ if (typeof usage.totalTokens === "number" && usage.totalTokens > 0) {
42
+ return usage.totalTokens;
43
+ }
39
44
  return usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
40
45
  }
41
46