@groeponline/pi-wishcraft 1.6.0 → 1.8.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.
Files changed (46) hide show
  1. package/AGENTS.md +315 -97
  2. package/CHANGELOG.md +94 -9
  3. package/README.md +33 -33
  4. package/bash-mode/editor.ts +82 -10
  5. package/bash-mode/pty-session.ts +64 -16
  6. package/bash-mode/transcript.ts +67 -2
  7. package/docs/architecture/codebase-graph.md +660 -0
  8. package/docs/bash-mode.md +4 -0
  9. package/docs/commands.md +1 -1
  10. package/docs/segments.md +2 -2
  11. package/package.json +2 -2
  12. package/src/config/presets.ts +59 -1
  13. package/src/config/segment-options.ts +98 -83
  14. package/src/config/structural-preset-data.ts +69 -0
  15. package/src/config/structural-preset-table.ts +445 -0
  16. package/src/config/structural-presets.ts +9 -499
  17. package/src/config/types.ts +8 -1
  18. package/src/extension/skills/global-registry.ts +63 -0
  19. package/src/extension/skills/skill-registry.ts +56 -4
  20. package/src/extension/ui/custom-editor.ts +22 -1
  21. package/src/extension/ui/deck/component.ts +17 -5
  22. package/src/extension/ui/deck/render.ts +26 -7
  23. package/src/extension/ui/status-line-renderers.ts +6 -2
  24. package/src/motion/catalog-extra.ts +24 -0
  25. package/src/motion/catalog.ts +4 -0
  26. package/src/motion/index.ts +1 -0
  27. package/src/motion/policy.ts +28 -1
  28. package/src/motion/scheduler.ts +7 -0
  29. package/src/render/layout.ts +44 -2
  30. package/src/render/motion-candidates.ts +52 -0
  31. package/src/render/motion-rail.ts +80 -50
  32. package/src/render/paint.ts +32 -8
  33. package/src/render/v2-adapter.ts +2 -0
  34. package/src/render/v2-entry.ts +1 -1
  35. package/src/segments/system.ts +13 -6
  36. package/src/signal/controller.ts +45 -29
  37. package/src/studio/component.ts +213 -37
  38. package/src/studio/deepwiki/cache.ts +80 -6
  39. package/src/studio/list.ts +11 -1
  40. package/src/studio/open.ts +207 -15
  41. package/src/theme/colors.ts +2 -6
  42. package/src/theme/icons.ts +21 -0
  43. package/src/theme/separators.ts +20 -0
  44. package/tsconfig.test.json +7 -0
  45. package/src/tools/patch.ts +0 -179
  46. package/src/tools/ripgrep.ts +0 -104
@@ -31,6 +31,18 @@ const CHEF_COLORS: ColorScheme = {
31
31
  queue: "dim",
32
32
  };
33
33
 
34
+ // chef-calm: same operational essentials, visually quieter. Accent stays
35
+ // reserved for state (context/token states), everything else blends into
36
+ // text/dim so the line reads as one row instead of a rainbow.
37
+ const CHEF_CALM_COLORS: ColorScheme = {
38
+ ...DEFAULT_COLORS,
39
+ model: "text",
40
+ path: "text",
41
+ gitClean: "dim",
42
+ queue: "dim",
43
+ context: "accent",
44
+ };
45
+
34
46
  // ---------------------------------------------------------------------------
35
47
  // 10 vNext Structural Signature Presets
36
48
  // ---------------------------------------------------------------------------
@@ -346,9 +358,19 @@ export const PRESETS: Record<StatusLinePreset, PresetDef> = {
346
358
  "shell_mode",
347
359
  "path",
348
360
  "git",
361
+ "session",
349
362
  "queue",
350
363
  ],
351
- rightSegments: ["tps", "open_ports", "subagents", "cost", "context_pct", "time"],
364
+ rightSegments: [
365
+ "tps",
366
+ "open_ports",
367
+ "token_out",
368
+ "cache_read",
369
+ "subagents",
370
+ "cost",
371
+ "context_pct",
372
+ "time",
373
+ ],
352
374
  separator: "slash",
353
375
  colors: CHEF_COLORS,
354
376
  segmentOptions: {
@@ -372,6 +394,42 @@ export const PRESETS: Record<StatusLinePreset, PresetDef> = {
372
394
  },
373
395
  },
374
396
 
397
+ // chef-calm: five segments, dot separator, quiet motion. The operational
398
+ // row without the noise — tps/open_ports/subagents/cost/hostname dropped,
399
+ // git commit subjects off (ahead/behind stays). Colours: text/dim with a
400
+ // single accent for context so the line reads as one calm row.
401
+ "chef-calm": {
402
+ leftSegments: ["model", "path", "git"],
403
+ rightSegments: ["context_pct", "time"],
404
+ secondarySegments: [],
405
+ separator: "dot",
406
+ colors: CHEF_CALM_COLORS,
407
+ segmentOptions: {
408
+ model: { showThinkingLevel: false },
409
+ path: { mode: "basename" },
410
+ git: {
411
+ showBranch: true,
412
+ showStaged: false,
413
+ showUnstaged: false,
414
+ showUntracked: false,
415
+ polling: "branch",
416
+ hostIcon: false,
417
+ showAheadBehind: true,
418
+ showCommit: false,
419
+ },
420
+ context: { format: "percent" },
421
+ time: { format: "24h", showSeconds: false },
422
+ },
423
+ motion: {
424
+ idle: "nimbus",
425
+ thinking: "nimbus",
426
+ streaming: "nimbus",
427
+ "tool.end": "copper-switch",
428
+ success: "copper-switch",
429
+ error: "copper-switch",
430
+ },
431
+ },
432
+
375
433
  // 10 vNext Structural Presets
376
434
  lanternwake: {
377
435
  leftSegments: ["model", "thinking", "shell_mode", "path", "git", "session", "queue"],
@@ -2,132 +2,147 @@ import { normalizeCostCurrency } from "../usage/rates.ts";
2
2
  import { isRecord } from "./primitives.ts";
3
3
  import type { StatusLineSegmentOptions } from "./types.ts";
4
4
 
5
+ /**
6
+ * Normalizes raw segment configuration into supported status-line segment options.
7
+ *
8
+ * Invalid values are ignored, numeric limits are normalized, string values are trimmed,
9
+ * and non-empty template overrides are preserved for supported option groups.
10
+ *
11
+ * @param raw - Raw segment configuration to normalize
12
+ * @returns The validated and normalized segment options
13
+ */
5
14
  export function normalizeSegmentOptions(
6
15
  raw: Record<string, unknown>,
7
16
  ): StatusLineSegmentOptions {
8
17
  const options: StatusLineSegmentOptions = {};
9
18
 
10
19
  if (isRecord(raw.model)) {
11
- options.model = {
12
- ...(typeof raw.model.showThinkingLevel === "boolean"
13
- ? { showThinkingLevel: raw.model.showThinkingLevel }
14
- : {}),
15
- ...(raw.model.display === "name" || raw.model.display === "qualified"
16
- ? { display: raw.model.display }
17
- : {}),
18
- };
20
+ const model: NonNullable<StatusLineSegmentOptions["model"]> = {};
21
+ if (typeof raw.model.showThinkingLevel === "boolean") {
22
+ model.showThinkingLevel = raw.model.showThinkingLevel;
23
+ }
24
+ if (raw.model.display === "name" || raw.model.display === "qualified") {
25
+ model.display = raw.model.display;
26
+ }
27
+ options.model = model;
19
28
  }
20
29
 
21
30
  if (isRecord(raw.path)) {
22
- options.path = {
23
- ...(raw.path.mode === "basename" ||
31
+ const path: NonNullable<StatusLineSegmentOptions["path"]> = {};
32
+ if (
33
+ raw.path.mode === "basename" ||
24
34
  raw.path.mode === "abbreviated" ||
25
35
  raw.path.mode === "full"
26
- ? { mode: raw.path.mode }
27
- : {}),
28
- ...(typeof raw.path.maxLength === "number" &&
36
+ ) {
37
+ path.mode = raw.path.mode;
38
+ }
39
+ if (
40
+ typeof raw.path.maxLength === "number" &&
29
41
  Number.isFinite(raw.path.maxLength) &&
30
42
  raw.path.maxLength > 0
31
- ? { maxLength: Math.floor(raw.path.maxLength) }
32
- : {}),
33
- };
43
+ ) {
44
+ path.maxLength = Math.floor(raw.path.maxLength);
45
+ }
46
+ options.path = path;
34
47
  }
35
48
 
36
49
  if (isRecord(raw.git)) {
37
- options.git = {
38
- ...(typeof raw.git.showBranch === "boolean"
39
- ? { showBranch: raw.git.showBranch }
40
- : {}),
41
- ...(typeof raw.git.showStaged === "boolean"
42
- ? { showStaged: raw.git.showStaged }
43
- : {}),
44
- ...(typeof raw.git.showUnstaged === "boolean"
45
- ? { showUnstaged: raw.git.showUnstaged }
46
- : {}),
47
- ...(typeof raw.git.showUntracked === "boolean"
48
- ? { showUntracked: raw.git.showUntracked }
49
- : {}),
50
- ...(raw.git.polling === "full" ||
50
+ const git: NonNullable<StatusLineSegmentOptions["git"]> = {};
51
+ if (typeof raw.git.showBranch === "boolean") git.showBranch = raw.git.showBranch;
52
+ if (typeof raw.git.showStaged === "boolean") git.showStaged = raw.git.showStaged;
53
+ if (typeof raw.git.showUnstaged === "boolean") {
54
+ git.showUnstaged = raw.git.showUnstaged;
55
+ }
56
+ if (typeof raw.git.showUntracked === "boolean") {
57
+ git.showUntracked = raw.git.showUntracked;
58
+ }
59
+ if (
60
+ raw.git.polling === "full" ||
51
61
  raw.git.polling === "branch" ||
52
62
  raw.git.polling === "off"
53
- ? { polling: raw.git.polling }
54
- : {}),
55
- ...(typeof raw.git.hostIcon === "boolean"
56
- ? { hostIcon: raw.git.hostIcon }
57
- : {}),
58
- ...(typeof raw.git.showAheadBehind === "boolean"
59
- ? { showAheadBehind: raw.git.showAheadBehind }
60
- : {}),
61
- ...(typeof raw.git.showCommit === "boolean"
62
- ? { showCommit: raw.git.showCommit }
63
- : {}),
64
- ...(typeof raw.git.maxCommitSubjectLength === "number" &&
63
+ ) {
64
+ git.polling = raw.git.polling;
65
+ }
66
+ if (typeof raw.git.hostIcon === "boolean") git.hostIcon = raw.git.hostIcon;
67
+ if (typeof raw.git.showAheadBehind === "boolean") {
68
+ git.showAheadBehind = raw.git.showAheadBehind;
69
+ }
70
+ if (typeof raw.git.showCommit === "boolean") git.showCommit = raw.git.showCommit;
71
+ if (
72
+ typeof raw.git.maxCommitSubjectLength === "number" &&
65
73
  Number.isFinite(raw.git.maxCommitSubjectLength) &&
66
74
  raw.git.maxCommitSubjectLength > 0
67
- ? { maxCommitSubjectLength: Math.floor(raw.git.maxCommitSubjectLength) }
68
- : {}),
69
- };
75
+ ) {
76
+ git.maxCommitSubjectLength = Math.floor(raw.git.maxCommitSubjectLength);
77
+ }
78
+ options.git = git;
70
79
  }
71
80
 
72
81
  if (isRecord(raw.time)) {
73
- options.time = {
74
- ...(raw.time.format === "12h" || raw.time.format === "24h"
75
- ? { format: raw.time.format }
76
- : {}),
77
- ...(typeof raw.time.showSeconds === "boolean"
78
- ? { showSeconds: raw.time.showSeconds }
79
- : {}),
80
- };
82
+ const time: NonNullable<StatusLineSegmentOptions["time"]> = {};
83
+ if (raw.time.format === "12h" || raw.time.format === "24h") {
84
+ time.format = raw.time.format;
85
+ }
86
+ if (typeof raw.time.showSeconds === "boolean") {
87
+ time.showSeconds = raw.time.showSeconds;
88
+ }
89
+ options.time = time;
81
90
  }
82
91
 
83
92
  if (isRecord(raw.cost)) {
84
93
  const currency = normalizeCostCurrency(raw.cost.currency);
85
- options.cost = {
86
- ...(raw.cost.subscriptionDisplay === "subscription" ||
94
+ const cost: NonNullable<StatusLineSegmentOptions["cost"]> = {};
95
+ if (
96
+ raw.cost.subscriptionDisplay === "subscription" ||
87
97
  raw.cost.subscriptionDisplay === "reported-cost" ||
88
98
  raw.cost.subscriptionDisplay === "both"
89
- ? { subscriptionDisplay: raw.cost.subscriptionDisplay }
90
- : {}),
91
- ...(currency ? { currency } : {}),
92
- };
99
+ ) {
100
+ cost.subscriptionDisplay = raw.cost.subscriptionDisplay;
101
+ }
102
+ if (currency) cost.currency = currency;
103
+ options.cost = cost;
93
104
  }
94
105
 
95
106
  if (isRecord(raw.context)) {
96
- options.context = {
97
- ...(raw.context.format === "full" || raw.context.format === "percent"
98
- ? { format: raw.context.format }
99
- : {}),
100
- };
107
+ const context: NonNullable<StatusLineSegmentOptions["context"]> = {};
108
+ if (raw.context.format === "full" || raw.context.format === "percent") {
109
+ context.format = raw.context.format;
110
+ }
111
+ options.context = context;
101
112
  }
102
113
 
103
114
  if (isRecord(raw.cache_read)) {
104
- options.cache_read = {
105
- ...(raw.cache_read.format === "tokens" ||
115
+ const cacheRead: NonNullable<StatusLineSegmentOptions["cache_read"]> = {};
116
+ if (
117
+ raw.cache_read.format === "tokens" ||
106
118
  raw.cache_read.format === "percent" ||
107
119
  raw.cache_read.format === "both"
108
- ? { format: raw.cache_read.format }
109
- : {}),
110
- };
120
+ ) {
121
+ cacheRead.format = raw.cache_read.format;
122
+ }
123
+ options.cache_read = cacheRead;
111
124
  }
112
125
 
113
126
  if (isRecord(raw.openPorts)) {
114
- options.openPorts = {
115
- ...(typeof raw.openPorts.includeUdp === "boolean"
116
- ? { includeUdp: raw.openPorts.includeUdp }
117
- : {}),
118
- ...(typeof raw.openPorts.host === "string" && raw.openPorts.host.trim()
119
- ? { host: raw.openPorts.host.trim() }
120
- : {}),
121
- };
127
+ const openPorts: NonNullable<StatusLineSegmentOptions["openPorts"]> = {};
128
+ if (typeof raw.openPorts.includeUdp === "boolean") {
129
+ openPorts.includeUdp = raw.openPorts.includeUdp;
130
+ }
131
+ if (typeof raw.openPorts.host === "string" && raw.openPorts.host.trim()) {
132
+ openPorts.host = raw.openPorts.host.trim();
133
+ }
134
+ options.openPorts = openPorts;
122
135
  }
123
136
 
124
137
  if (isRecord(raw.tps)) {
125
- options.tps = {
126
- ...(typeof raw.tps.windowMs === "number" &&
127
- Number.isFinite(raw.tps.windowMs)
128
- ? { windowMs: Math.min(5000, Math.max(500, Math.floor(raw.tps.windowMs))) }
129
- : {}),
130
- };
138
+ const tps: NonNullable<StatusLineSegmentOptions["tps"]> = {};
139
+ if (typeof raw.tps.windowMs === "number" && Number.isFinite(raw.tps.windowMs)) {
140
+ tps.windowMs = Math.min(5000, Math.max(500, Math.floor(raw.tps.windowMs)));
141
+ }
142
+ if (raw.tps.mode === "both" || raw.tps.mode === "out" || raw.tps.mode === "in") {
143
+ tps.mode = raw.tps.mode;
144
+ }
145
+ options.tps = tps;
131
146
  }
132
147
 
133
148
  // Generic `template` override for every segment option group:
@@ -0,0 +1,69 @@
1
+ /**
2
+ * structural-preset-data.ts
3
+ * ---------------------------------------------------------------------------
4
+ * Shared helpers and chrome/deck/welcome constants for the structural presets.
5
+ * The preset definitions live in structural-preset-table.ts; the public API
6
+ * (getStructuralPreset, etc.) stays in structural-presets.ts.
7
+ * ---------------------------------------------------------------------------
8
+ */
9
+
10
+ import type { ChromeSpec, DeckSpec, MotionRef, WelcomeSpec, WishcraftTokens } from "./types.ts";
11
+ import type { MotionEvent } from "../motion/types.ts";
12
+
13
+ export function tokens(partial: Partial<WishcraftTokens>): Partial<WishcraftTokens> {
14
+ return partial;
15
+ }
16
+
17
+ export function motion(
18
+ signature: MotionRef,
19
+ overrides?: Partial<Record<MotionEvent, MotionRef>>,
20
+ ): Partial<Record<MotionEvent, MotionRef>> {
21
+ const base: Partial<Record<MotionEvent, MotionRef>> = {
22
+ idle: "wisp",
23
+ thinking: signature,
24
+ streaming: signature,
25
+ "tool.start": signature,
26
+ "tool.end": "rune-bloom",
27
+ "idea.capture": "rune-bloom",
28
+ "skill.insert": "rune-bloom",
29
+ "policy.deny": "rune-bloom",
30
+ repair: signature,
31
+ compact: "bar",
32
+ success: "rune-bloom",
33
+ warning: "rune-bloom",
34
+ error: "rune-bloom",
35
+ };
36
+ return { ...base, ...overrides };
37
+ }
38
+
39
+ export const ROUNDED_CHROME: ChromeSpec = {
40
+ frame: "rounded",
41
+ corners: { tl: "╭", tr: "╮", bl: "╰", br: "╯" },
42
+ dividers: { horizontal: "─", vertical: "│", cross: "┼" },
43
+ density: "medium",
44
+ };
45
+
46
+ export const MINIMAL_CHROME: ChromeSpec = {
47
+ frame: "minimal",
48
+ corners: { tl: " ", tr: " ", bl: " ", br: " " },
49
+ dividers: { horizontal: "─", vertical: " ", cross: " " },
50
+ density: "spacious",
51
+ };
52
+
53
+ export const BORDERLESS_CHROME: ChromeSpec = {
54
+ frame: "borderless",
55
+ corners: { tl: "", tr: "", bl: "", br: "" },
56
+ dividers: { horizontal: "─", vertical: " ", cross: " " },
57
+ density: "spacious",
58
+ };
59
+
60
+ export const DEFAULT_DECK: DeckSpec = {
61
+ navigation: "tabs",
62
+ panelStyle: "framed",
63
+ activityStyle: "pulse",
64
+ };
65
+
66
+ export const DEFAULT_WELCOME: WelcomeSpec = {
67
+ lantern: true,
68
+ ambient: true,
69
+ };