@pellux/goodvibes-tui 0.27.0 → 0.29.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 +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -12,13 +12,10 @@ export type { InspectableDomain } from '@/runtime/index.ts';
|
|
|
12
12
|
export { HealthPanel } from '@/runtime/index.ts';
|
|
13
13
|
export { DivergencePanel } from '@/runtime/index.ts';
|
|
14
14
|
export { ReplayPanel } from '@/runtime/index.ts';
|
|
15
|
-
export { PolicyPanel } from './policy.ts';
|
|
16
|
-
export type { PolicyPanelSnapshot } from './policy.ts';
|
|
17
15
|
export { ToolContractsPanel } from '@/runtime/index.ts';
|
|
18
16
|
export { TransportPanel } from '@/runtime/index.ts';
|
|
19
17
|
export type { TransportPanelSnapshot } from '@/runtime/index.ts';
|
|
20
18
|
export { OpsPanel } from './ops.ts';
|
|
21
19
|
export type { OpsAuditEntry } from './ops.ts';
|
|
22
|
-
export { PanelResourcesPanel } from './panel-resources.ts';
|
|
23
20
|
export { SecurityPanel } from '@/runtime/index.ts';
|
|
24
21
|
export type { SecurityPanelSnapshot } from '@/runtime/index.ts';
|
|
@@ -154,6 +154,9 @@ export function enrichModelEntries(
|
|
|
154
154
|
// Determine source: custom/local providers carry provenance on ModelDefinition;
|
|
155
155
|
// for catalog models, if getContextWindowForModel returned more than the
|
|
156
156
|
// static contextWindow it came from OpenRouter, else it's the registry value.
|
|
157
|
+
// NOTE: This is a best-effort heuristic. When the OpenRouter live value equals
|
|
158
|
+
// the static registry value the badge shows 'registry' even though OpenRouter
|
|
159
|
+
// was consulted. The contextWindow value itself is always accurate.
|
|
157
160
|
let contextWindowSource: ModelPickerEntry['contextWindowSource'];
|
|
158
161
|
if (model.contextWindowProvenance) {
|
|
159
162
|
contextWindowSource = model.contextWindowProvenance;
|
|
@@ -97,6 +97,10 @@ export interface ModelPickerEntry {
|
|
|
97
97
|
* - `fallback` — default constant (no config or API source)
|
|
98
98
|
* - `openrouter` — sourced from OpenRouter model data (built-in catalog models)
|
|
99
99
|
* - `registry` — static value in the built-in model registry
|
|
100
|
+
*
|
|
101
|
+
* For built-in catalog models this badge is best-effort: when the OpenRouter
|
|
102
|
+
* resolved value equals the static registry value the source shows 'registry'
|
|
103
|
+
* even if OpenRouter was consulted at runtime.
|
|
100
104
|
*/
|
|
101
105
|
readonly contextWindowSource:
|
|
102
106
|
| 'provider_api'
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -253,6 +253,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
253
253
|
if (!input.helpOverlayActive) input.modalOpened('help');
|
|
254
254
|
input.helpOverlayActive = !input.helpOverlayActive;
|
|
255
255
|
input.helpScrollOffset = 0;
|
|
256
|
+
render();
|
|
256
257
|
};
|
|
257
258
|
|
|
258
259
|
commandContext.openShortcutsOverlay = () => {
|
|
@@ -292,19 +293,16 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
292
293
|
};
|
|
293
294
|
|
|
294
295
|
commandContext.openPanelPicker = () => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
panelManager.show();
|
|
304
|
-
input.panelFocused = true;
|
|
305
|
-
conversation.setSplashSuppressed(true);
|
|
296
|
+
// Focus ownership lives in PanelManager (focusTarget); read it there rather
|
|
297
|
+
// than tracking a parallel input.panelFocused flag. Toggle semantics: if the
|
|
298
|
+
// workspace is visible AND already focused, hide it; otherwise reveal and
|
|
299
|
+
// focus it (opening the panel list when nothing is open yet).
|
|
300
|
+
if (panelManager.isVisible() && panelManager.getFocusTarget() === 'panel') {
|
|
301
|
+
panelManager.hide();
|
|
302
|
+
panelManager.focusPrompt();
|
|
303
|
+
conversation.setSplashSuppressed(false);
|
|
306
304
|
conversation.rebuildHistory();
|
|
307
|
-
} else
|
|
305
|
+
} else {
|
|
308
306
|
if (panelManager.getAllOpen().length === 0) {
|
|
309
307
|
try {
|
|
310
308
|
panelManager.open('panel-list');
|
|
@@ -313,26 +311,20 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
313
311
|
}
|
|
314
312
|
}
|
|
315
313
|
panelManager.show();
|
|
316
|
-
|
|
314
|
+
panelManager.focusPanels();
|
|
317
315
|
conversation.setSplashSuppressed(true);
|
|
318
316
|
conversation.rebuildHistory();
|
|
319
|
-
} else {
|
|
320
|
-
panelManager.hide();
|
|
321
|
-
input.panelFocused = false;
|
|
322
|
-
conversation.setSplashSuppressed(false);
|
|
323
|
-
conversation.rebuildHistory();
|
|
324
317
|
}
|
|
325
318
|
render();
|
|
326
319
|
};
|
|
327
320
|
|
|
328
321
|
commandContext.focusPanels = () => {
|
|
329
|
-
|
|
330
|
-
input.panelFocused = true;
|
|
322
|
+
panelManager.focusPanels();
|
|
331
323
|
render();
|
|
332
324
|
};
|
|
333
325
|
|
|
334
326
|
commandContext.focusPrompt = () => {
|
|
335
|
-
|
|
327
|
+
panelManager.focusPrompt();
|
|
336
328
|
input.indicatorFocused = false;
|
|
337
329
|
render();
|
|
338
330
|
};
|
|
@@ -340,7 +332,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
340
332
|
commandContext.showPanel = (panelId, pane) => {
|
|
341
333
|
panelManager.open(panelId, pane);
|
|
342
334
|
panelManager.show();
|
|
343
|
-
|
|
335
|
+
panelManager.focusPanels();
|
|
344
336
|
conversation.setSplashSuppressed(true);
|
|
345
337
|
conversation.rebuildHistory();
|
|
346
338
|
render();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ms-duration formatters for the panel layer.
|
|
3
|
+
*
|
|
4
|
+
* Three families are exported, each matching a distinct behavior cluster.
|
|
5
|
+
* DO NOT replace these with formatElapsed from utils/format-elapsed.ts —
|
|
6
|
+
* formatElapsed floors to integer seconds and lacks the null/'?ms' guards
|
|
7
|
+
* that the latency panels require.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format a latency value in milliseconds with sub-second precision.
|
|
12
|
+
*
|
|
13
|
+
* Used by: debug-panel, provider-health-panel
|
|
14
|
+
*
|
|
15
|
+
* ms <= 0 → 'n/a'
|
|
16
|
+
* ms >= 10000 → '12.3s' (one decimal)
|
|
17
|
+
* ms >= 1000 → '1.23s' (two decimals)
|
|
18
|
+
* else → '500ms' (integer ms)
|
|
19
|
+
*/
|
|
20
|
+
export function formatLatencyMs(ms: number): string {
|
|
21
|
+
if (ms <= 0) return 'n/a';
|
|
22
|
+
if (ms >= 10_000) return `${(ms / 1000).toFixed(1)}s`;
|
|
23
|
+
if (ms >= 1_000) return `${(ms / 1000).toFixed(2)}s`;
|
|
24
|
+
return `${Math.round(ms)}ms`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format a task/agent duration with minute-rolling notation.
|
|
29
|
+
*
|
|
30
|
+
* Used by: tool-inspector-panel, agent-inspector-shared
|
|
31
|
+
*
|
|
32
|
+
* ms < 1000 → '500ms'
|
|
33
|
+
* ms < 60000 → '3.5s'
|
|
34
|
+
* else → '1m30s'
|
|
35
|
+
*/
|
|
36
|
+
export function formatDuration(ms: number): string {
|
|
37
|
+
if (ms < 1000) return `${ms}ms`;
|
|
38
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
39
|
+
return `${Math.floor(ms / 60000)}m${Math.floor((ms % 60000) / 1000)}s`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Format a short eval/forensics duration; treats undefined as '?ms'.
|
|
44
|
+
*
|
|
45
|
+
* Used by: incident-review-panel, eval-panel
|
|
46
|
+
*
|
|
47
|
+
* undefined → '?ms'
|
|
48
|
+
* ms < 1000 → '500ms'
|
|
49
|
+
* else → '1.5s'
|
|
50
|
+
*/
|
|
51
|
+
export function formatShortDuration(ms: number | undefined): string {
|
|
52
|
+
if (ms === undefined) return '?ms';
|
|
53
|
+
if (ms < 1000) return `${ms}ms`;
|
|
54
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
55
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parametrised number abbreviation helper.
|
|
3
|
+
*
|
|
4
|
+
* Each call site can reproduce its exact current output by passing the
|
|
5
|
+
* appropriate options rather than a single "agreed" format being imposed.
|
|
6
|
+
*
|
|
7
|
+
* Ranges (default opts: guard=1_000, decimals=1, rounding='fixed'):
|
|
8
|
+
* n < guard → String(n) e.g. 999 → '999'
|
|
9
|
+
* n < 1_000_000 → X.Xk e.g. 1500 → '1.5k'
|
|
10
|
+
* n < 1_000_000_000 → X.XM e.g. 1.5M → '1.5M'
|
|
11
|
+
* (with bSuffix) → X.XB / X.XT
|
|
12
|
+
* (with noM) → always k, never M
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface AbbreviateCountOpts {
|
|
16
|
+
/**
|
|
17
|
+
* Values strictly below this are returned as-is via String(n).
|
|
18
|
+
* Default: 1_000
|
|
19
|
+
*/
|
|
20
|
+
guard?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Decimal places used for the k suffix (and for M when mDecimals is not set).
|
|
23
|
+
* Default: 1
|
|
24
|
+
*/
|
|
25
|
+
decimals?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Override decimal places for the M suffix only.
|
|
28
|
+
* Default: same as decimals.
|
|
29
|
+
*/
|
|
30
|
+
mDecimals?: number;
|
|
31
|
+
/**
|
|
32
|
+
* 'fixed' → value.toFixed(decimals) (default)
|
|
33
|
+
* 'round' → String(Math.round(value)) — always integer, decimals ignored
|
|
34
|
+
*/
|
|
35
|
+
rounding?: 'fixed' | 'round';
|
|
36
|
+
/**
|
|
37
|
+
* When true, extend formatting to B (billions) and T (trillions).
|
|
38
|
+
* Default: false
|
|
39
|
+
*/
|
|
40
|
+
bSuffix?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* When true, never produce M/B/T — format as k even for millions.
|
|
43
|
+
* Default: false
|
|
44
|
+
*/
|
|
45
|
+
noM?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Format a non-negative integer with k/M/B/T magnitude suffix.
|
|
50
|
+
*
|
|
51
|
+
* Preserves each call site's exact current format by accepting
|
|
52
|
+
* per-site option overrides (guard, decimals, rounding, etc.).
|
|
53
|
+
*/
|
|
54
|
+
export function abbreviateCount(n: number, opts?: AbbreviateCountOpts): string {
|
|
55
|
+
const guard = opts?.guard ?? 1_000;
|
|
56
|
+
const decimals = opts?.decimals ?? 1;
|
|
57
|
+
const mDecimals = opts?.mDecimals ?? decimals;
|
|
58
|
+
const rounding = opts?.rounding ?? 'fixed';
|
|
59
|
+
const noM = opts?.noM ?? false;
|
|
60
|
+
const bSuffix = opts?.bSuffix ?? false;
|
|
61
|
+
|
|
62
|
+
/** Render a scaled value according to the configured rounding mode. */
|
|
63
|
+
const fmt = (val: number, d: number): string =>
|
|
64
|
+
rounding === 'round' ? String(Math.round(val)) : val.toFixed(d);
|
|
65
|
+
|
|
66
|
+
if (n < guard) return String(n);
|
|
67
|
+
if (noM || n < 1_000_000) return fmt(n / 1_000, decimals) + 'k';
|
|
68
|
+
if (!bSuffix || n < 1_000_000_000) return fmt(n / 1_000_000, mDecimals) + 'M';
|
|
69
|
+
if (n < 1_000_000_000_000) return fmt(n / 1_000_000_000, decimals) + 'B';
|
|
70
|
+
return fmt(n / 1_000_000_000_000, decimals) + 'T';
|
|
71
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { center, getDisplayWidth } from './terminal-width.ts';
|
|
1
|
+
import { center, getDisplayWidth, truncateDisplay } from './terminal-width.ts';
|
|
2
2
|
import { VERSION } from '../version.ts';
|
|
3
3
|
|
|
4
4
|
const ART_LINES = [
|
|
@@ -22,15 +22,33 @@ const TAGLINE = '[ good vibes ・ A I ・ いい雰囲気
|
|
|
22
22
|
|
|
23
23
|
const VERSION_LINE = ` ✦ v${VERSION} █ terminal AI assistant █ 自動コード ✦`;
|
|
24
24
|
|
|
25
|
+
/** Fixed hint line — the three primary shell entry points. */
|
|
26
|
+
const HINT_LINE = 'Ctrl+P panels / ? help / F2 processes';
|
|
27
|
+
|
|
25
28
|
export interface SplashOptions {
|
|
26
29
|
workingDir?: string;
|
|
27
30
|
model?: string;
|
|
28
31
|
provider?: string;
|
|
29
32
|
toolCount?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Session id of the most recent session, as resolved by readLastSessionPointer.
|
|
35
|
+
* When present, the splash advertises a resume affordance.
|
|
36
|
+
*/
|
|
37
|
+
lastSessionId?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Collapse a $HOME-prefixed working directory to a leading `~`. */
|
|
41
|
+
function collapseHome(dir: string): string {
|
|
42
|
+
const home = typeof process !== 'undefined' ? process.env.HOME ?? '' : '';
|
|
43
|
+
return home && dir.startsWith(home) ? '~' + dir.slice(home.length) : dir;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Center a meta line, truncating first so it never overflows the terminal width. */
|
|
47
|
+
function metaLine(text: string, columns: number): string {
|
|
48
|
+
return center(truncateDisplay(text, Math.max(0, columns)), columns);
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
export function getSplashLines(columns: number, opts: SplashOptions = {}): string[] {
|
|
33
|
-
const splashHint = 'start chatting or type /help for commands';
|
|
34
52
|
const lines: string[] = [
|
|
35
53
|
center(TOP_BORDER, columns),
|
|
36
54
|
...ART_LINES.map((line) => center(line, columns)),
|
|
@@ -40,7 +58,30 @@ export function getSplashLines(columns: number, opts: SplashOptions = {}): strin
|
|
|
40
58
|
'',
|
|
41
59
|
];
|
|
42
60
|
|
|
43
|
-
|
|
61
|
+
// Live session context — real state pulled from SplashOptions rather than a
|
|
62
|
+
// static "/help" signpost: model (provider) and tool count on one line, cwd
|
|
63
|
+
// on the next.
|
|
64
|
+
const contextBits: string[] = [];
|
|
65
|
+
if (opts.model) {
|
|
66
|
+
contextBits.push(opts.provider ? `${opts.model} (${opts.provider})` : opts.model);
|
|
67
|
+
}
|
|
68
|
+
if (typeof opts.toolCount === 'number') {
|
|
69
|
+
contextBits.push(`${opts.toolCount} tool${opts.toolCount === 1 ? '' : 's'}`);
|
|
70
|
+
}
|
|
71
|
+
if (contextBits.length > 0) {
|
|
72
|
+
lines.push(metaLine(contextBits.join(' · '), columns));
|
|
73
|
+
}
|
|
74
|
+
if (opts.workingDir) {
|
|
75
|
+
lines.push(metaLine(collapseHome(opts.workingDir), columns));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Last-session resume pointer (readLastSessionPointer result), when recorded.
|
|
79
|
+
if (opts.lastSessionId) {
|
|
80
|
+
lines.push(metaLine(`↩ resume last session — /sessions resume ${opts.lastSessionId}`, columns));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push(metaLine(HINT_LINE, columns));
|
|
44
85
|
|
|
45
86
|
return lines;
|
|
46
87
|
}
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.
|
|
9
|
+
let _version = '0.29.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|
|
@@ -267,6 +267,19 @@ export class WorkPlanStore {
|
|
|
267
267
|
return removed;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Writes the current plan's `toMarkdown()` output to a sibling `.md` file
|
|
272
|
+
* next to the JSON store file, so the checklist can be opened outside the
|
|
273
|
+
* TUI. Returns the written path alongside the markdown that was written.
|
|
274
|
+
*/
|
|
275
|
+
exportMarkdown(): { readonly path: string; readonly markdown: string } {
|
|
276
|
+
const plan = this.readPlan();
|
|
277
|
+
const markdown = this.toMarkdown(plan);
|
|
278
|
+
const path = this.filePath.replace(/\.json$/, '.md');
|
|
279
|
+
atomicWriteFileSync(path, `${markdown}\n`, { mkdirp: true });
|
|
280
|
+
return { path, markdown };
|
|
281
|
+
}
|
|
282
|
+
|
|
270
283
|
toMarkdown(plan: WorkPlan = this.readPlan()): string {
|
|
271
284
|
const lines = [
|
|
272
285
|
`# ${plan.title}`,
|