@groeponline/pi-wishcraft 1.1.0 → 1.3.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 +15 -0
- package/README.md +16 -7
- package/ROADMAP.md +144 -425
- package/docs/commands.md +6 -2
- package/docs/configuration.md +21 -0
- package/docs/design/accessibility.md +71 -0
- package/docs/design/deck-layout.md +69 -0
- package/docs/design/motion-gallery.md +80 -0
- package/docs/design/motion-system.md +119 -0
- package/docs/design/presets.md +196 -0
- package/docs/design/regression-testing.md +54 -0
- package/docs/design/responsive.md +53 -0
- package/docs/design/signal.md +71 -0
- package/docs/design/theme-contract.md +132 -0
- package/docs/design/vnext-overview.md +55 -0
- package/docs/design/vnext-release-plan.md +212 -0
- package/docs/index.md +19 -1
- package/docs/segments.md +1 -1
- package/package.json +2 -1
- package/src/config/appearance.ts +229 -0
- package/src/config/parse.ts +41 -0
- package/src/config/presets.ts +184 -0
- package/src/config/structural-presets.ts +555 -0
- package/src/config/tokens.ts +154 -0
- package/src/config/types.ts +210 -2
- package/src/extension/commands/commands.ts +36 -12
- package/src/extension/commands/powerline-completions.ts +4 -0
- package/src/extension/commands/queue-commands.ts +6 -0
- package/src/extension/core/segment-context.ts +27 -21
- package/src/extension/core/state.ts +16 -1
- package/src/extension/core/types.ts +11 -1
- package/src/extension/session/session-lifecycle.ts +29 -65
- package/src/extension/session/session-notifications.ts +91 -0
- package/src/extension/settings/appearance-write.ts +135 -0
- package/src/extension/settings/wishcraft-config-items.ts +119 -0
- package/src/extension/settings/wishcraft-config.ts +28 -114
- package/src/extension/skills/skill-manager.ts +5 -0
- package/src/extension/ui/deck/component.ts +371 -0
- package/src/extension/ui/deck/index.ts +34 -0
- package/src/extension/ui/deck/render.ts +260 -0
- package/src/extension/ui/deck/route-bodies.ts +209 -0
- package/src/extension/ui/deck/routes.ts +37 -0
- package/src/extension/ui/deck/session-snapshot.ts +122 -0
- package/src/extension/ui/deck/types.ts +97 -0
- package/src/extension/ui/powerline-menu-view.ts +19 -15
- package/src/extension/ui/signal-layout.ts +76 -0
- package/src/extension/ui/status-line-renderers.ts +20 -2
- package/src/motion/accessibility.ts +59 -0
- package/src/motion/catalog-extra.ts +429 -0
- package/src/motion/catalog.ts +336 -0
- package/src/motion/composer.ts +147 -0
- package/src/motion/frames.ts +84 -0
- package/src/motion/gallery.ts +77 -0
- package/src/motion/index.ts +78 -0
- package/src/motion/policy.ts +128 -0
- package/src/motion/scheduler.ts +159 -0
- package/src/motion/types.ts +132 -0
- package/src/render/timer.ts +1 -0
- package/src/signal/controller.ts +135 -0
- package/src/signal/integration.ts +46 -0
- package/src/signal/render.ts +178 -0
- package/src/theme/detect.ts +48 -0
- package/src/theme/tokens/index.ts +9 -0
- package/src/theme/tokens/mapping.ts +21 -0
- package/src/theme/tokens/types.ts +6 -0
- package/src/usage/token-budget.ts +23 -0
|
@@ -3,6 +3,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { BashTranscriptStore } from "../../../bash-mode/transcript.ts";
|
|
4
4
|
import { BashCompletionEngine } from "../../../bash-mode/completion.ts";
|
|
5
5
|
import { parsePowerlineConfig } from "../../config/powerline-config.ts";
|
|
6
|
+
import { policyFromEnvironment } from "../../motion/accessibility.ts";
|
|
6
7
|
import { registerCustomSegments } from "../../segments/index.ts";
|
|
7
8
|
import { registerCustomPresets } from "../../config/presets.ts";
|
|
8
9
|
import { invalidateGitStatus } from "../../git/status.ts";
|
|
@@ -15,26 +16,10 @@ import {
|
|
|
15
16
|
onVibeToolCall,
|
|
16
17
|
} from "../../working-vibes/index.ts";
|
|
17
18
|
import {
|
|
18
|
-
getSessionTotalCost,
|
|
19
19
|
getUsageTokenTotal,
|
|
20
20
|
isSessionAssistantMessage,
|
|
21
21
|
} from "../../usage/ledger.ts";
|
|
22
|
-
import {
|
|
23
|
-
formatCostAlertMessage,
|
|
24
|
-
shouldTriggerCostAlert,
|
|
25
|
-
} from "./cost-alert.ts";
|
|
26
|
-
import {
|
|
27
|
-
formatTokenBudgetWarning,
|
|
28
|
-
parseTokenBudget,
|
|
29
|
-
tokenBudgetLevel,
|
|
30
|
-
} from "../../usage/token-budget.ts";
|
|
31
|
-
import {
|
|
32
|
-
recordUsageEvent,
|
|
33
|
-
loadUsageFileFromDisk,
|
|
34
|
-
tokenTotal,
|
|
35
|
-
totalsForRange,
|
|
36
|
-
dayKey,
|
|
37
|
-
} from "../../usage/usage-store.ts";
|
|
22
|
+
import { recordUsageEvent } from "../../usage/usage-store.ts";
|
|
38
23
|
import {
|
|
39
24
|
detectCustomCompactionEnabled,
|
|
40
25
|
readSettings,
|
|
@@ -76,52 +61,14 @@ import {
|
|
|
76
61
|
clearSkillsCountPublisher,
|
|
77
62
|
} from "../skills/skill-status.ts";
|
|
78
63
|
import { maybeAppendReadHint } from "./read-hints.ts";
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const threshold = config.costAlert;
|
|
88
|
-
const sessionEvents = rt.sessionBranchCache.get(ctx.sessionManager);
|
|
89
|
-
const totalCost = getSessionTotalCost(rt.tokenStatsCache.get(sessionEvents));
|
|
90
|
-
if (
|
|
91
|
-
!shouldTriggerCostAlert({
|
|
92
|
-
totalCost,
|
|
93
|
-
threshold,
|
|
94
|
-
alreadyNotified: rt.costAlertNotified,
|
|
95
|
-
})
|
|
96
|
-
) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
rt.costAlertNotified = true;
|
|
100
|
-
ctx.ui.notify(
|
|
101
|
-
formatCostAlertMessage(
|
|
102
|
-
totalCost,
|
|
103
|
-
threshold as number,
|
|
104
|
-
config.segmentOptions?.cost?.currency ?? "USD",
|
|
105
|
-
),
|
|
106
|
-
"warning",
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function maybeNotifyTokenBudget(rt: RuntimeState, ctx: any): void {
|
|
111
|
-
if (!ctx?.hasUI) return;
|
|
112
|
-
const daily = parseTokenBudget(readSettings(ctx.cwd ?? process.cwd()).wishcraft)
|
|
113
|
-
.daily;
|
|
114
|
-
if (!daily) return;
|
|
115
|
-
const now = Date.now();
|
|
116
|
-
const todayStart = Date.parse(`${dayKey(now)}T00:00:00`);
|
|
117
|
-
const used = tokenTotal(
|
|
118
|
-
totalsForRange(loadUsageFileFromDisk(), todayStart, now + 1),
|
|
119
|
-
);
|
|
120
|
-
const { level } = tokenBudgetLevel(used, daily);
|
|
121
|
-
if (level === 0 || level <= rt.tokenBudgetNotifiedLevel) return;
|
|
122
|
-
rt.tokenBudgetNotifiedLevel = level;
|
|
123
|
-
ctx.ui.notify(formatTokenBudgetWarning(used, daily, level), "warning");
|
|
124
|
-
}
|
|
64
|
+
import {
|
|
65
|
+
dispatchSignalEvent,
|
|
66
|
+
settleSignal,
|
|
67
|
+
} from "../../signal/integration.ts";
|
|
68
|
+
import {
|
|
69
|
+
maybeNotifyCostAlert,
|
|
70
|
+
maybeNotifyTokenBudget,
|
|
71
|
+
} from "./session-notifications.ts";
|
|
125
72
|
|
|
126
73
|
// Helper to extract recent agent response text (skipping thinking blocks)
|
|
127
74
|
function getRecentAgentContext(ctx: any): string | undefined {
|
|
@@ -162,6 +109,7 @@ export function registerSessionLifecycle(
|
|
|
162
109
|
): void {
|
|
163
110
|
// Track session start
|
|
164
111
|
pi.on("session_start", async (event, ctx) => {
|
|
112
|
+
settleSignal(rt);
|
|
165
113
|
clearSkillsCountPublisher();
|
|
166
114
|
rt.shellSession?.dispose();
|
|
167
115
|
rt.shellSession = null;
|
|
@@ -174,6 +122,7 @@ export function registerSessionLifecycle(
|
|
|
174
122
|
rt.liveAssistantUsage = null;
|
|
175
123
|
rt.costAlertNotified = false;
|
|
176
124
|
rt.tokenBudgetNotifiedLevel = 0;
|
|
125
|
+
rt.tokenBudgetSnapshot = { day: "", dailyLimit: null, dailyUsed: 0 };
|
|
177
126
|
rt.powerlineCompacting = false;
|
|
178
127
|
rt.deliverAfterRetrySettles = false;
|
|
179
128
|
rt.stashedEditorText = null;
|
|
@@ -183,6 +132,7 @@ export function registerSessionLifecycle(
|
|
|
183
132
|
rt.bashModeSettings = parseBashModeSettings(settings, rt.resolvedShortcuts);
|
|
184
133
|
rt.showLastPrompt = settings.showLastPrompt !== false;
|
|
185
134
|
setConfig(parsePowerlineConfig(settings.powerline, PRESET_NAMES));
|
|
135
|
+
rt.motionPolicy = policyFromEnvironment(process.env, config.motionLevel);
|
|
186
136
|
rt.queueStore.setSentRetentionMs(
|
|
187
137
|
config.queue.retentionHours * 60 * 60 * 1000,
|
|
188
138
|
);
|
|
@@ -225,7 +175,7 @@ export function registerSessionLifecycle(
|
|
|
225
175
|
} else {
|
|
226
176
|
dismissWelcome(rt, ctx);
|
|
227
177
|
}
|
|
228
|
-
maybeNotifyTokenBudget(rt, ctx);
|
|
178
|
+
maybeNotifyTokenBudget(rt, ctx, settings);
|
|
229
179
|
}
|
|
230
180
|
});
|
|
231
181
|
|
|
@@ -238,6 +188,7 @@ export function registerSessionLifecycle(
|
|
|
238
188
|
rt.welcomeOverlayShouldDismiss = false;
|
|
239
189
|
rt.welcomeDismissScheduler.cancel();
|
|
240
190
|
rt.statusRenderScheduler.cancel();
|
|
191
|
+
rt.motionScheduler.dispose();
|
|
241
192
|
rt.restoreFooterStatusRepaintHook?.();
|
|
242
193
|
rt.restoreFooterStatusRepaintHook = null;
|
|
243
194
|
rt.stashShortcutInputUnsubscribe?.();
|
|
@@ -256,6 +207,7 @@ export function registerSessionLifecycle(
|
|
|
256
207
|
rt.getThinkingLevelFn = null;
|
|
257
208
|
rt.currentThinkingLevel = null;
|
|
258
209
|
rt.liveAssistantUsage = null;
|
|
210
|
+
rt.tokenBudgetSnapshot = { day: "", dailyLimit: null, dailyUsed: 0 };
|
|
259
211
|
rt.tuiRef = null;
|
|
260
212
|
rt.currentEditor = null;
|
|
261
213
|
resetLayoutCache(rt);
|
|
@@ -320,6 +272,7 @@ export function registerSessionLifecycle(
|
|
|
320
272
|
onVibeAgentStart();
|
|
321
273
|
dismissWelcome(rt, ctx);
|
|
322
274
|
rt.currentCtx = ctx;
|
|
275
|
+
dispatchSignalEvent(rt, config.appearance, "thinking");
|
|
323
276
|
});
|
|
324
277
|
|
|
325
278
|
pi.on("message_update", async (event, ctx) => {
|
|
@@ -333,6 +286,9 @@ export function registerSessionLifecycle(
|
|
|
333
286
|
rt.currentCtx = ctx;
|
|
334
287
|
rt.layoutDirty = true;
|
|
335
288
|
rt.statusRenderScheduler.schedule(CONTEXT_STATUS_RENDER_MS);
|
|
289
|
+
if (rt.signal.event !== "streaming") {
|
|
290
|
+
dispatchSignalEvent(rt, config.appearance, "streaming");
|
|
291
|
+
}
|
|
336
292
|
}
|
|
337
293
|
});
|
|
338
294
|
|
|
@@ -374,6 +330,7 @@ export function registerSessionLifecycle(
|
|
|
374
330
|
rt.powerlineCompacting = true;
|
|
375
331
|
rt.currentCtx = ctx;
|
|
376
332
|
requestQueueRender(rt);
|
|
333
|
+
dispatchSignalEvent(rt, config.appearance, "compact");
|
|
377
334
|
});
|
|
378
335
|
|
|
379
336
|
pi.on("session_compact", async (event, ctx) => {
|
|
@@ -406,6 +363,12 @@ export function registerSessionLifecycle(
|
|
|
406
363
|
// Also dismiss on tool calls (agent is working) + refresh vibe if rate limit allows
|
|
407
364
|
pi.on("tool_call", async (event, ctx) => {
|
|
408
365
|
dismissWelcome(rt, ctx);
|
|
366
|
+
dispatchSignalEvent(
|
|
367
|
+
rt,
|
|
368
|
+
config.appearance,
|
|
369
|
+
"tool.start",
|
|
370
|
+
`tool ${event.toolName}`,
|
|
371
|
+
);
|
|
409
372
|
if (ctx.hasUI) {
|
|
410
373
|
// Extract recent agent context from session for richer vibe generation
|
|
411
374
|
const agentContext = getRecentAgentContext(ctx);
|
|
@@ -422,6 +385,7 @@ export function registerSessionLifecycle(
|
|
|
422
385
|
rt.isStreaming = false;
|
|
423
386
|
rt.liveAssistantUsage = null;
|
|
424
387
|
rt.coreContextUsageCache.reset();
|
|
388
|
+
dispatchSignalEvent(rt, config.appearance, "success");
|
|
425
389
|
|
|
426
390
|
let hasUI = false;
|
|
427
391
|
try {
|
|
@@ -462,4 +426,4 @@ export function registerSessionLifecycle(
|
|
|
462
426
|
schedulePostCompactionDelivery(pi, rt, ctx);
|
|
463
427
|
}
|
|
464
428
|
});
|
|
465
|
-
}
|
|
429
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getSessionTotalCost } from "../../usage/ledger.ts";
|
|
2
|
+
import {
|
|
3
|
+
formatTokenBudgetWarning,
|
|
4
|
+
parseTokenBudget,
|
|
5
|
+
tokenBudgetLevel,
|
|
6
|
+
} from "../../usage/token-budget.ts";
|
|
7
|
+
import {
|
|
8
|
+
dayKey,
|
|
9
|
+
loadUsageFileFromDisk,
|
|
10
|
+
tokenTotal,
|
|
11
|
+
totalsForRange,
|
|
12
|
+
} from "../../usage/usage-store.ts";
|
|
13
|
+
import { config } from "../core/state.ts";
|
|
14
|
+
import type { RuntimeState } from "../core/types.ts";
|
|
15
|
+
import { readSettings } from "../settings/settings-io.ts";
|
|
16
|
+
import {
|
|
17
|
+
formatCostAlertMessage,
|
|
18
|
+
shouldTriggerCostAlert,
|
|
19
|
+
} from "./cost-alert.ts";
|
|
20
|
+
|
|
21
|
+
/** Notify once when the configured per-session cost threshold is crossed. */
|
|
22
|
+
export function maybeNotifyCostAlert(rt: RuntimeState, ctx: any): void {
|
|
23
|
+
if (!ctx?.hasUI || rt.costAlertNotified) return;
|
|
24
|
+
const threshold = config.costAlert;
|
|
25
|
+
const sessionEvents = rt.sessionBranchCache.get(ctx.sessionManager);
|
|
26
|
+
const totalCost = getSessionTotalCost(rt.tokenStatsCache.get(sessionEvents));
|
|
27
|
+
if (
|
|
28
|
+
!shouldTriggerCostAlert({
|
|
29
|
+
totalCost,
|
|
30
|
+
threshold,
|
|
31
|
+
alreadyNotified: rt.costAlertNotified,
|
|
32
|
+
})
|
|
33
|
+
) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rt.costAlertNotified = true;
|
|
38
|
+
ctx.ui.notify(
|
|
39
|
+
formatCostAlertMessage(
|
|
40
|
+
totalCost,
|
|
41
|
+
threshold as number,
|
|
42
|
+
config.segmentOptions?.cost?.currency ?? "USD",
|
|
43
|
+
),
|
|
44
|
+
"warning",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Refresh disk-backed budget data at lifecycle boundaries, never during paint.
|
|
50
|
+
* A local-day rollover also resets the per-day warning latch.
|
|
51
|
+
*/
|
|
52
|
+
export function refreshTokenBudgetSnapshot(
|
|
53
|
+
rt: RuntimeState,
|
|
54
|
+
ctx: any,
|
|
55
|
+
settings?: ReturnType<typeof readSettings>,
|
|
56
|
+
): { daily: number | null; used: number; level: 0 | 80 | 100 } {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const day = dayKey(now);
|
|
59
|
+
if (rt.tokenBudgetSnapshot.day !== day) {
|
|
60
|
+
rt.tokenBudgetNotifiedLevel = 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resolvedSettings = settings ?? readSettings(ctx.cwd ?? process.cwd());
|
|
64
|
+
const daily = parseTokenBudget(resolvedSettings.wishcraft).daily;
|
|
65
|
+
if (!daily) {
|
|
66
|
+
rt.tokenBudgetSnapshot = { day, dailyLimit: null, dailyUsed: 0 };
|
|
67
|
+
return { daily: null, used: 0, level: 0 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const todayStart = Date.parse(`${day}T00:00:00`);
|
|
71
|
+
const used = tokenTotal(
|
|
72
|
+
totalsForRange(loadUsageFileFromDisk(), todayStart, now + 1),
|
|
73
|
+
);
|
|
74
|
+
rt.tokenBudgetSnapshot = { day, dailyLimit: daily, dailyUsed: used };
|
|
75
|
+
const { level } = tokenBudgetLevel(used, daily);
|
|
76
|
+
return { daily, used, level };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Refresh the daily budget and emit its 80%/100% warning at most once per day. */
|
|
80
|
+
export function maybeNotifyTokenBudget(
|
|
81
|
+
rt: RuntimeState,
|
|
82
|
+
ctx: any,
|
|
83
|
+
settings?: ReturnType<typeof readSettings>,
|
|
84
|
+
): void {
|
|
85
|
+
if (!ctx?.hasUI) return;
|
|
86
|
+
const { daily, used, level } = refreshTokenBudgetSnapshot(rt, ctx, settings);
|
|
87
|
+
if (!daily || level === 0 || level <= rt.tokenBudgetNotifiedLevel) return;
|
|
88
|
+
|
|
89
|
+
rt.tokenBudgetNotifiedLevel = level;
|
|
90
|
+
ctx.ui.notify(formatTokenBudgetWarning(used, daily, level), "warning");
|
|
91
|
+
}
|
|
@@ -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
|
+
}
|