@groeponline/pi-wishcraft 1.1.0 → 1.2.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 (64) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +16 -7
  3. package/ROADMAP.md +41 -260
  4. package/docs/commands.md +6 -2
  5. package/docs/configuration.md +21 -0
  6. package/docs/design/accessibility.md +71 -0
  7. package/docs/design/deck-layout.md +69 -0
  8. package/docs/design/motion-gallery.md +80 -0
  9. package/docs/design/motion-system.md +119 -0
  10. package/docs/design/presets.md +196 -0
  11. package/docs/design/regression-testing.md +54 -0
  12. package/docs/design/responsive.md +53 -0
  13. package/docs/design/signal.md +68 -0
  14. package/docs/design/theme-contract.md +132 -0
  15. package/docs/design/vnext-overview.md +55 -0
  16. package/docs/design/vnext-release-plan.md +212 -0
  17. package/docs/index.md +19 -1
  18. package/docs/segments.md +1 -1
  19. package/package.json +2 -1
  20. package/src/config/appearance.ts +230 -0
  21. package/src/config/parse.ts +41 -0
  22. package/src/config/presets.ts +184 -0
  23. package/src/config/structural-presets.ts +555 -0
  24. package/src/config/tokens.ts +154 -0
  25. package/src/config/types.ts +210 -2
  26. package/src/extension/commands/commands.ts +36 -12
  27. package/src/extension/commands/powerline-completions.ts +4 -0
  28. package/src/extension/commands/queue-commands.ts +6 -0
  29. package/src/extension/core/segment-context.ts +7 -1
  30. package/src/extension/core/state.ts +14 -0
  31. package/src/extension/core/types.ts +7 -0
  32. package/src/extension/session/session-lifecycle.ts +20 -0
  33. package/src/extension/settings/appearance-write.ts +135 -0
  34. package/src/extension/settings/wishcraft-config-items.ts +119 -0
  35. package/src/extension/settings/wishcraft-config.ts +28 -114
  36. package/src/extension/skills/skill-manager.ts +5 -0
  37. package/src/extension/ui/deck/component.ts +358 -0
  38. package/src/extension/ui/deck/index.ts +34 -0
  39. package/src/extension/ui/deck/render.ts +260 -0
  40. package/src/extension/ui/deck/route-bodies.ts +209 -0
  41. package/src/extension/ui/deck/routes.ts +37 -0
  42. package/src/extension/ui/deck/session-snapshot.ts +104 -0
  43. package/src/extension/ui/deck/types.ts +92 -0
  44. package/src/extension/ui/powerline-menu-view.ts +19 -15
  45. package/src/extension/ui/signal-layout.ts +77 -0
  46. package/src/extension/ui/status-line-renderers.ts +20 -2
  47. package/src/motion/accessibility.ts +59 -0
  48. package/src/motion/catalog-extra.ts +429 -0
  49. package/src/motion/catalog.ts +336 -0
  50. package/src/motion/composer.ts +147 -0
  51. package/src/motion/frames.ts +84 -0
  52. package/src/motion/gallery.ts +77 -0
  53. package/src/motion/index.ts +78 -0
  54. package/src/motion/policy.ts +128 -0
  55. package/src/motion/scheduler.ts +159 -0
  56. package/src/motion/types.ts +132 -0
  57. package/src/render/timer.ts +1 -0
  58. package/src/signal/controller.ts +123 -0
  59. package/src/signal/integration.ts +42 -0
  60. package/src/signal/render.ts +178 -0
  61. package/src/theme/detect.ts +48 -0
  62. package/src/theme/tokens/index.ts +9 -0
  63. package/src/theme/tokens/mapping.ts +62 -0
  64. package/src/theme/tokens/types.ts +39 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * appearance-write.ts
3
+ * ---------------------------------------------------------------------------
4
+ * Persist `powerline.appearance.base` and refresh the live Signal.
5
+ * ---------------------------------------------------------------------------
6
+ */
7
+
8
+ import type { StructuralPresetName } from "../../config/types.ts";
9
+ import type { MotionEvent } from "../../motion/types.ts";
10
+ import { isStructuralPresetName } from "../../config/structural-presets.ts";
11
+ import {
12
+ effectiveAppearanceMix,
13
+ resolveAppearanceMix,
14
+ } from "../../config/appearance.ts";
15
+ import {
16
+ isMotionLevel,
17
+ policyFromEnvironment,
18
+ type MotionLevel,
19
+ } from "../../motion/accessibility.ts";
20
+ import { parsePowerlineConfig } from "../../config/powerline-config.ts";
21
+ import { isRecord, writePowerlineSetting } from "./settings-io.ts";
22
+ import { config, PRESET_NAMES, setConfig } from "../core/state.ts";
23
+ import {
24
+ requestImmediateStatusRender,
25
+ resetLayoutCache,
26
+ } from "../core/segment-context.ts";
27
+ import type { RuntimeState } from "../core/types.ts";
28
+
29
+ export function writeAppearanceBase(
30
+ cwd: string,
31
+ name: StructuralPresetName,
32
+ ): boolean {
33
+ return writePowerlineSetting(cwd, (existing) => {
34
+ const node: Record<string, unknown> = isRecord(existing)
35
+ ? { ...existing }
36
+ : typeof existing === "string"
37
+ ? { preset: existing }
38
+ : {};
39
+ const appearance = isRecord(node.appearance) ? { ...node.appearance } : {};
40
+ appearance.base = name;
41
+ node.appearance = appearance;
42
+ return node;
43
+ });
44
+ }
45
+
46
+ /** Write the structural base, keep session config in sync, and repaint Signal. */
47
+ export function applyAppearanceBase(
48
+ rt: RuntimeState,
49
+ cwd: string,
50
+ name: StructuralPresetName,
51
+ ): boolean {
52
+ config.appearance = { ...config.appearance, base: name };
53
+ const ok = writeAppearanceBase(cwd, name);
54
+ resetLayoutCache(rt);
55
+ requestImmediateStatusRender(rt);
56
+ return ok;
57
+ }
58
+
59
+ /** `/signal hexforge` also sets appearance.base so colors match the layout name. */
60
+ export function syncAppearanceForLayoutPreset(
61
+ rt: RuntimeState,
62
+ cwd: string,
63
+ preset: string,
64
+ ): void {
65
+ if (!isStructuralPresetName(preset)) return;
66
+ applyAppearanceBase(rt, cwd, preset);
67
+ }
68
+
69
+ export function applyPersistedMotionPolicy(rt: RuntimeState): void {
70
+ rt.motionPolicy = policyFromEnvironment(process.env, config.motionLevel);
71
+ }
72
+
73
+ /** Re-parse `settings.powerline` into session config and repaint Signal. */
74
+ export function reloadPowerlineFromSettings(
75
+ rt: RuntimeState,
76
+ settings: Record<string, unknown>,
77
+ ): void {
78
+ setConfig({
79
+ ...config,
80
+ ...parsePowerlineConfig(settings.powerline, PRESET_NAMES),
81
+ });
82
+ applyPersistedMotionPolicy(rt);
83
+ resetLayoutCache(rt);
84
+ requestImmediateStatusRender(rt);
85
+ }
86
+
87
+ export function applyMotionLevel(
88
+ rt: RuntimeState,
89
+ cwd: string,
90
+ level: MotionLevel,
91
+ ): boolean {
92
+ if (!isMotionLevel(level)) return false;
93
+ config.motionLevel = level;
94
+ applyPersistedMotionPolicy(rt);
95
+ const ok = writePowerlineSetting(cwd, (existing) => {
96
+ const node: Record<string, unknown> = isRecord(existing)
97
+ ? { ...existing }
98
+ : typeof existing === "string"
99
+ ? { preset: existing }
100
+ : {};
101
+ node.motionLevel = level;
102
+ return node;
103
+ });
104
+ resetLayoutCache(rt);
105
+ requestImmediateStatusRender(rt);
106
+ return ok;
107
+ }
108
+
109
+ export function applyMotionAssignment(
110
+ rt: RuntimeState,
111
+ cwd: string,
112
+ event: MotionEvent,
113
+ motionId: string,
114
+ ): boolean {
115
+ const current = {
116
+ ...resolveAppearanceMix(effectiveAppearanceMix(config.appearance, config.preset))
117
+ .motion,
118
+ [event]: motionId,
119
+ };
120
+ config.appearance = { ...config.appearance, motion: current };
121
+ const ok = writePowerlineSetting(cwd, (existing) => {
122
+ const node: Record<string, unknown> = isRecord(existing)
123
+ ? { ...existing }
124
+ : typeof existing === "string"
125
+ ? { preset: existing }
126
+ : {};
127
+ const appearance = isRecord(node.appearance) ? { ...node.appearance } : {};
128
+ appearance.motion = current;
129
+ node.appearance = appearance;
130
+ return node;
131
+ });
132
+ resetLayoutCache(rt);
133
+ requestImmediateStatusRender(rt);
134
+ return ok;
135
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Declarative groups for `/wishcraft settings`.
3
+ * Kept beside the overlay so the TUI file stays under the size budget.
4
+ */
5
+
6
+ import { STRUCTURAL_PRESET_NAMES } from "../../config/types.ts";
7
+ import { MOTION_LEVELS } from "../../motion/accessibility.ts";
8
+
9
+ export type ConfigValue = boolean | string | number | null;
10
+
11
+ export interface ConfigItem {
12
+ /** Label in the list. */
13
+ label: string;
14
+ /** Path inside settings ("powerline.placement", "wishcraft.hooksEnabled", ...). */
15
+ path: string;
16
+ /** Edit kind. */
17
+ kind: "toggle" | "select" | "text" | "number";
18
+ /** For select: the choices. */
19
+ choices?: string[];
20
+ /** Hint under the group. */
21
+ hint?: string;
22
+ /** Detail explanation (optional). */
23
+ description?: string;
24
+ /** Effective value when the setting is absent (toggles only; defaults to false). */
25
+ default?: boolean;
26
+ }
27
+
28
+ export interface ConfigGroup {
29
+ title: string;
30
+ items: ConfigItem[];
31
+ }
32
+
33
+ const SEPARATORS = [
34
+ "powerline",
35
+ "powerline-thin",
36
+ "slash",
37
+ "pipe",
38
+ "block",
39
+ "none",
40
+ "ascii",
41
+ "dot",
42
+ "chevron",
43
+ "star",
44
+ ];
45
+
46
+ /** Build groups from current settings (values shown live). */
47
+ export function buildConfigGroups(_settings: Record<string, unknown>): ConfigGroup[] {
48
+ return [
49
+ {
50
+ title: "Status bar",
51
+ items: [
52
+ { label: "Preset", path: "powerline.preset", kind: "select", choices: ["default", "minimal", "compact", "full", "nerd", "ascii", "chef"] },
53
+ {
54
+ label: "Structural base",
55
+ path: "powerline.appearance.base",
56
+ kind: "select",
57
+ choices: [...STRUCTURAL_PRESET_NAMES],
58
+ hint: "colors and motion for Signal; layout preset stays separate",
59
+ },
60
+ {
61
+ label: "Motion level",
62
+ path: "powerline.motionLevel",
63
+ kind: "select",
64
+ choices: [...MOTION_LEVELS],
65
+ hint: "full · reduced · functional · off",
66
+ },
67
+ { label: "Separator", path: "powerline.separator", kind: "select", choices: SEPARATORS },
68
+ { label: "Placement", path: "powerline.placement", kind: "select", choices: ["above", "below"] },
69
+ { label: "Path mode", path: "powerline.segmentOptions.path.mode", kind: "select", choices: ["basename", "abbreviated", "full"] },
70
+ { label: "Path max length", path: "powerline.segmentOptions.path.maxLength", kind: "number", hint: "0 = unlimited" },
71
+ { label: "Time format", path: "powerline.segmentOptions.time.format", kind: "select", choices: ["12h", "24h"] },
72
+ { label: "Time seconds", path: "powerline.segmentOptions.time.showSeconds", kind: "toggle" },
73
+ { label: "Git host icons", path: "powerline.segmentOptions.git.hostIcon", kind: "toggle" },
74
+ { label: "Git ahead/behind", path: "powerline.segmentOptions.git.showAheadBehind", kind: "toggle" },
75
+ { label: "Git latest commit", path: "powerline.segmentOptions.git.showCommit", kind: "toggle" },
76
+ { label: "Context format", path: "powerline.segmentOptions.context.format", kind: "select", choices: ["full", "percent"] },
77
+ { label: "Cache-read format", path: "powerline.segmentOptions.cache_read.format", kind: "select", choices: ["tokens", "percent", "both"] },
78
+ { label: "Cost display", path: "powerline.segmentOptions.cost.subscriptionDisplay", kind: "select", choices: ["subscription", "reported-cost", "both"] },
79
+ { label: "Currency", path: "powerline.segmentOptions.cost.currency", kind: "text" },
80
+ { label: "Ports include UDP", path: "powerline.segmentOptions.openPorts.includeUdp", kind: "toggle" },
81
+ { label: "TPS window (ms)", path: "powerline.segmentOptions.tps.windowMs", kind: "number", hint: "default 1000" },
82
+ { label: "TPS mode", path: "powerline.segmentOptions.tps.mode", kind: "select", choices: ["both", "out", "in"] },
83
+ { label: "TPS label", path: "powerline.segmentLabels.tps", kind: "text", hint: "empty = no label" },
84
+ ],
85
+ },
86
+ {
87
+ title: "Welcome & vibes",
88
+ items: [
89
+ { label: "Welcome overlay", path: "powerline.welcome", kind: "toggle", hint: "on = overlay at startup, off = no welcome" },
90
+ { label: "Animate wishcraft lantern", path: "wishcraft.welcome.animateLantern", kind: "toggle", hint: "flicker on the lantern" },
91
+ ],
92
+ },
93
+ {
94
+ title: "Skills",
95
+ items: [
96
+ { label: "Inline expand /command and $skill", path: "wishcraft.inlineSkills", kind: "toggle", hint: "needs a restart to take effect" },
97
+ { label: "Read hints", path: "wishcraft.readHints", kind: "toggle", default: true, hint: "off = no continuation hint after partial reads" },
98
+ ],
99
+ },
100
+ {
101
+ title: "Hooks & repairs (harness)",
102
+ items: [
103
+ { label: "Hooks enabled", path: "wishcraft.hooksEnabled", kind: "toggle", hint: "preToolUse / postToolUse / sessionStart command hooks" },
104
+ { label: "Tool-input repairs", path: "wishcraft.repairsEnabled", kind: "toggle", hint: "null-for-optional, auto-link, json-array, path aliases" },
105
+ { label: "Daily token budget", path: "wishcraft.tokenBudget.daily", kind: "number", hint: "colours the cost segment; never blocks. 0 = off" },
106
+ ],
107
+ },
108
+ {
109
+ title: "Shortcuts",
110
+ items: [
111
+ { label: "Menu", path: "powerlineShortcuts.menu", kind: "text", hint: "e.g. alt+p" },
112
+ { label: "Info", path: "powerlineShortcuts.info", kind: "text" },
113
+ { label: "Stash", path: "powerlineShortcuts.stashHistory", kind: "text" },
114
+ { label: "Idea", path: "powerlineShortcuts.ideaCapture", kind: "text" },
115
+ { label: "Queue", path: "powerlineShortcuts.queueOpen", kind: "text" },
116
+ ],
117
+ },
118
+ ];
119
+ }
@@ -11,38 +11,20 @@
11
11
  import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
12
12
  import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
13
13
  import type { RuntimeState } from "../core/types.ts";
14
+ import { openWishcraftDeck } from "../ui/deck/index.ts";
15
+ import { parseDeckRouteArg } from "../ui/deck/routes.ts";
14
16
  import { readSettings, writeSettingKey } from "../settings/settings-io.ts";
15
17
  import { isRecord } from "../settings/settings-io.ts";
16
- import { config as stateConfig, setConfig, PRESET_NAMES } from "../core/state.ts";
17
- import { parsePowerlineConfig } from "../../config/powerline-config.ts";
18
+ import { reloadPowerlineFromSettings } from "./appearance-write.ts";
19
+ import {
20
+ buildConfigGroups,
21
+ type ConfigGroup,
22
+ type ConfigItem,
23
+ type ConfigValue,
24
+ } from "./wishcraft-config-items.ts";
18
25
 
19
- // ---------------------------------------------------------------------------
20
- // Config-item declaraties
21
- // ---------------------------------------------------------------------------
22
-
23
- export type ConfigValue = boolean | string | number | null;
24
-
25
- export interface ConfigItem {
26
- /** Label in the list. */
27
- label: string;
28
- /** Path inside settings ("powerline.placement", "wishcraft.hooksEnabled", ...). */
29
- path: string;
30
- /** Edit kind. */
31
- kind: "toggle" | "select" | "text" | "number";
32
- /** For select: the choices. */
33
- choices?: string[];
34
- /** Hint under the group. */
35
- hint?: string;
36
- /** Detail explanation (optional). */
37
- description?: string;
38
- /** Effective value when the setting is absent (toggles only; defaults to false). */
39
- default?: boolean;
40
- }
41
-
42
- export interface ConfigGroup {
43
- title: string;
44
- items: ConfigItem[];
45
- }
26
+ export type { ConfigGroup, ConfigItem, ConfigValue };
27
+ export { buildConfigGroups };
46
28
 
47
29
  /** Nested read: "wishcraft.hooksEnabled" → settings.wishcraft.hooksEnabled. */
48
30
  export function readConfigPath(settings: Record<string, unknown>, path: string): ConfigValue {
@@ -118,80 +100,6 @@ export function writeConfigPath(
118
100
  });
119
101
  }
120
102
 
121
- const SEPARATORS = [
122
- "powerline",
123
- "powerline-thin",
124
- "slash",
125
- "pipe",
126
- "block",
127
- "none",
128
- "ascii",
129
- "dot",
130
- "chevron",
131
- "star",
132
- ];
133
-
134
- /** Build groups from current settings (values shown live). */
135
- export function buildConfigGroups(settings: Record<string, unknown>): ConfigGroup[] {
136
- return [
137
- {
138
- title: "Status bar",
139
- items: [
140
- { label: "Preset", path: "powerline.preset", kind: "select", choices: ["default", "minimal", "compact", "full", "nerd", "ascii", "chef"] },
141
- { label: "Separator", path: "powerline.separator", kind: "select", choices: SEPARATORS },
142
- { label: "Placement", path: "powerline.placement", kind: "select", choices: ["above", "below"] },
143
- { label: "Path mode", path: "powerline.segmentOptions.path.mode", kind: "select", choices: ["basename", "abbreviated", "full"] },
144
- { label: "Path max length", path: "powerline.segmentOptions.path.maxLength", kind: "number", hint: "0 = unlimited" },
145
- { label: "Time format", path: "powerline.segmentOptions.time.format", kind: "select", choices: ["12h", "24h"] },
146
- { label: "Time seconds", path: "powerline.segmentOptions.time.showSeconds", kind: "toggle" },
147
- { label: "Git host icons", path: "powerline.segmentOptions.git.hostIcon", kind: "toggle" },
148
- { label: "Git ahead/behind", path: "powerline.segmentOptions.git.showAheadBehind", kind: "toggle" },
149
- { label: "Git latest commit", path: "powerline.segmentOptions.git.showCommit", kind: "toggle" },
150
- { label: "Context format", path: "powerline.segmentOptions.context.format", kind: "select", choices: ["full", "percent"] },
151
- { label: "Cache-read format", path: "powerline.segmentOptions.cache_read.format", kind: "select", choices: ["tokens", "percent", "both"] },
152
- { label: "Cost display", path: "powerline.segmentOptions.cost.subscriptionDisplay", kind: "select", choices: ["subscription", "reported-cost", "both"] },
153
- { label: "Currency", path: "powerline.segmentOptions.cost.currency", kind: "text" },
154
- { label: "Ports include UDP", path: "powerline.segmentOptions.openPorts.includeUdp", kind: "toggle" },
155
- { label: "TPS window (ms)", path: "powerline.segmentOptions.tps.windowMs", kind: "number", hint: "default 1000" },
156
- { label: "TPS mode", path: "powerline.segmentOptions.tps.mode", kind: "select", choices: ["both", "out", "in"] },
157
- { label: "TPS label", path: "powerline.segmentLabels.tps", kind: "text", hint: "empty = no label" },
158
- ],
159
- },
160
- {
161
- title: "Welcome & vibes",
162
- items: [
163
- { label: "Welcome overlay", path: "powerline.welcome", kind: "toggle", hint: "on = overlay at startup, off = no welcome" },
164
- { label: "Animate wishcraft lantern", path: "wishcraft.welcome.animateLantern", kind: "toggle", hint: "flicker on the lantern" },
165
- ],
166
- },
167
- {
168
- title: "Skills",
169
- items: [
170
- { label: "Inline expand /command and $skill", path: "wishcraft.inlineSkills", kind: "toggle", hint: "needs a restart to take effect" },
171
- { label: "Read hints", path: "wishcraft.readHints", kind: "toggle", default: true, hint: "off = no continuation hint after partial reads" },
172
- ],
173
- },
174
- {
175
- title: "Hooks & repairs (harness)",
176
- items: [
177
- { label: "Hooks enabled", path: "wishcraft.hooksEnabled", kind: "toggle", hint: "preToolUse / postToolUse / sessionStart command hooks" },
178
- { label: "Tool-input repairs", path: "wishcraft.repairsEnabled", kind: "toggle", hint: "null-for-optional, auto-link, json-array, path aliases" },
179
- { label: "Daily token budget", path: "wishcraft.tokenBudget.daily", kind: "number", hint: "colours the cost segment; never blocks. 0 = off" },
180
- ],
181
- },
182
- {
183
- title: "Shortcuts",
184
- items: [
185
- { label: "Menu", path: "powerlineShortcuts.menu", kind: "text", hint: "e.g. alt+p" },
186
- { label: "Info", path: "powerlineShortcuts.info", kind: "text" },
187
- { label: "Stash", path: "powerlineShortcuts.stashHistory", kind: "text" },
188
- { label: "Idea", path: "powerlineShortcuts.ideaCapture", kind: "text" },
189
- { label: "Queue", path: "powerlineShortcuts.queueOpen", kind: "text" },
190
- ],
191
- },
192
- ];
193
- }
194
-
195
103
  // ---------------------------------------------------------------------------
196
104
  // Overlay
197
105
  // ---------------------------------------------------------------------------
@@ -272,13 +180,8 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
272
180
  const ok = writeConfigPath(cwd, item.path, value);
273
181
  settings = readSettings(cwd);
274
182
  groups = buildConfigGroups(settings);
275
- // live-reload powerline config + status bar
276
183
  if (item.path.startsWith("powerline")) {
277
- setConfig({
278
- ...stateConfig,
279
- ...parsePowerlineConfig(settings.powerline, PRESET_NAMES),
280
- });
281
- rt.tuiRef?.requestRender?.();
184
+ reloadPowerlineFromSettings(rt, settings);
282
185
  }
283
186
  ctx.ui.notify(
284
187
  ok ? `${item.label}: ${displayValue(item, value)} (saved)` : `${item.label} not saved (settings.json?)`,
@@ -294,6 +197,9 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
294
197
  const ok = writeConfigPath(cwd, item.path, next);
295
198
  settings = readSettings(cwd);
296
199
  groups = buildConfigGroups(settings);
200
+ if (item.path.startsWith("powerline")) {
201
+ reloadPowerlineFromSettings(rt, settings);
202
+ }
297
203
  ctx.ui.notify(
298
204
  ok ? `${item.label}: ${next} (saved)` : `${item.label} not saved`,
299
205
  ok ? "info" : "warning",
@@ -305,6 +211,9 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
305
211
  const ok = writeConfigPath(cwd, item.path, nextToggleValue(item, cur));
306
212
  settings = readSettings(cwd);
307
213
  groups = buildConfigGroups(settings);
214
+ if (item.path.startsWith("powerline")) {
215
+ reloadPowerlineFromSettings(rt, settings);
216
+ }
308
217
  ctx.ui.notify(
309
218
  ok ? `${item.label}: ${!(cur === true) ? "on" : "off"} (saved)` : `${item.label} not saved`,
310
219
  ok ? "info" : "warning",
@@ -437,17 +346,22 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
437
346
  );
438
347
  }
439
348
 
440
- /** Register /wishcraft. */
349
+ /** Register /wishcraft — opens the Deck; `settings`/`config` open the flat list. */
441
350
  export function registerWishcraftConfigCommand(pi: ExtensionAPI, rt: RuntimeState): void {
442
351
  pi.registerCommand("wishcraft", {
443
- description: "Configure all wishcraft settings in one overlay (status bar, welcome, hooks, shortcuts)",
444
- handler: async (_args: string, ctx: any) => {
352
+ description: "Open the Wishcraft Deck, or settings/config for the flat list",
353
+ handler: async (args: string, ctx: any) => {
445
354
  if (!rt.enabled || !ctx.hasUI) {
446
- ctx.ui.notify("Powerline UI is disabled", "info");
355
+ ctx.ui.notify("Signal UI is disabled", "info");
447
356
  return;
448
357
  }
449
358
  rt.currentCtx = ctx;
450
- await showWishcraftConfig(rt, ctx);
359
+ const trimmed = args?.trim() ?? "";
360
+ if (trimmed === "config" || trimmed === "settings") {
361
+ await showWishcraftConfig(rt, ctx);
362
+ return;
363
+ }
364
+ await openWishcraftDeck(rt, ctx, parseDeckRouteArg(trimmed));
451
365
  },
452
366
  });
453
367
  }
@@ -17,6 +17,7 @@ import { realpathSync, rmSync } from "node:fs";
17
17
  import { join, relative } from "node:path";
18
18
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
19
  import { getAgentDir } from "../../paths/agent-dirs.ts";
20
+ import { openWishcraftDeck } from "../ui/deck/index.ts";
20
21
  import type { RuntimeState } from "../core/types.ts";
21
22
  import {
22
23
  applySkillFilter,
@@ -528,6 +529,10 @@ export function registerSkillManagerCommand(
528
529
  return;
529
530
  }
530
531
  const sub = args?.trim().split(/\s+/)[0]?.toLowerCase();
532
+ if (sub === "deck") {
533
+ await openWishcraftDeck(rt, ctx, "skills");
534
+ return;
535
+ }
531
536
  if (sub === "doctor") {
532
537
  await runDoctor(ctx);
533
538
  return;