memorix 1.2.2 → 1.2.4
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 +27 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/TEAM.md +86 -86
- package/dist/cli/index.js +5199 -4726
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +428 -49
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +97 -18
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +27 -0
- package/dist/sdk.js +428 -49
- package/dist/sdk.js.map +1 -1
- package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +86 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +13 -1
- package/docs/API_REFERENCE.md +13 -3
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/dev-log/progress.txt +60 -9
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/capability-map.ts +1 -1
- package/src/cli/command-guide.ts +4 -1
- package/src/cli/commands/agent-integrations.ts +5 -1
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/codegraph.ts +1 -1
- package/src/cli/commands/context.ts +9 -1
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +54 -54
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +66 -66
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +82 -82
- package/src/cli/commands/message.ts +104 -104
- package/src/cli/commands/poll.ts +58 -58
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +118 -118
- package/src/cli/commands/resume.ts +31 -0
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +15 -7
- package/src/cli/commands/skills.ts +114 -114
- package/src/cli/commands/task.ts +167 -167
- package/src/cli/commands/transfer.ts +47 -47
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/session-service.ts +3 -2
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +54 -1
- package/src/codegraph/task-lens.ts +29 -0
- package/src/compact/token-budget.ts +89 -74
- package/src/config/toml-loader.ts +9 -5
- package/src/dashboard/project-classification.ts +64 -64
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/handler.ts +127 -66
- package/src/hooks/installers/index.ts +5 -4
- package/src/hooks/official-skills.ts +6 -4
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/rules/memorix-agent-rules.md +9 -7
- package/src/hooks/significance-filter.ts +250 -250
- package/src/knowledge/context-assembly.ts +4 -1
- package/src/knowledge/workset.ts +89 -1
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/disclosure-policy.ts +135 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/refs.ts +94 -94
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +158 -9
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +343 -343
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/sdk.ts +299 -299
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +144 -10
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/bun-sqlite-compat.ts +118 -15
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +3 -3
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +161 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
package/src/cli/tui/Sidebar.tsx
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sidebar right panel with quick action hints and health snapshot.
|
|
3
|
-
*
|
|
4
|
-
* Modern design: Unicode separators, status dots, active indicator
|
|
5
|
-
* with arrow symbol, brand-colored section headers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { Box, Text, useInput } from 'ink';
|
|
10
|
-
import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
|
|
11
|
-
import type { HealthInfo, BackgroundInfo } from './data.js';
|
|
12
|
-
import type { ViewType } from './theme.js';
|
|
13
|
-
|
|
14
|
-
interface SidebarProps {
|
|
15
|
-
health: HealthInfo;
|
|
16
|
-
background: BackgroundInfo;
|
|
17
|
-
onAction: (cmd: string) => void;
|
|
18
|
-
activeView: ViewType;
|
|
19
|
-
/** When true, Sidebar captures shortcut keys and drives navigation. */
|
|
20
|
-
isFocused?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ACTIONS = [
|
|
24
|
-
{ key: 's', label: 'Search', cmd: '/search' },
|
|
25
|
-
{ key: 'r', label: 'Remember', cmd: '/remember' },
|
|
26
|
-
{ key: 'v', label: 'Recent', cmd: '/recent' },
|
|
27
|
-
{ key: 'd', label: 'Doctor', cmd: '/doctor' },
|
|
28
|
-
{ key: 'b', label: 'Background', cmd: '/background' },
|
|
29
|
-
{ key: 'w', label: 'Dashboard', cmd: '/dashboard' },
|
|
30
|
-
{ key: 'p', label: 'Project', cmd: '/project' },
|
|
31
|
-
{ key: 'c', label: 'Configure', cmd: '/configure' },
|
|
32
|
-
{ key: 'i', label: 'Integrate', cmd: '/integrate' },
|
|
33
|
-
{ key: 'h', label: 'Home', cmd: '/home' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const SIDEBAR_WIDTH = 32;
|
|
37
|
-
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
38
|
-
const ACTION_LABEL_WIDTH = INNER_WIDTH - 2;
|
|
39
|
-
const VALUE_WIDTH = INNER_WIDTH - 8;
|
|
40
|
-
|
|
41
|
-
function separator(width: number): string {
|
|
42
|
-
return SEP.thin.repeat(width);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function colorForMode(mode: string): string {
|
|
46
|
-
const normalized = mode.toLowerCase();
|
|
47
|
-
if (normalized.includes('hybrid')) return COLORS.success;
|
|
48
|
-
if (normalized.includes('vector')) return COLORS.accent;
|
|
49
|
-
return COLORS.warning;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function charWidth(ch: string): number {
|
|
53
|
-
const cp = ch.codePointAt(0)!;
|
|
54
|
-
return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
|
|
55
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
56
|
-
(cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fit(text: string, max: number): string {
|
|
60
|
-
if (max <= 0) return '';
|
|
61
|
-
let width = 0;
|
|
62
|
-
for (const ch of text) {
|
|
63
|
-
width += charWidth(ch);
|
|
64
|
-
if (width > max) break;
|
|
65
|
-
}
|
|
66
|
-
if (width <= max) return text.padEnd(text.length + (max - width));
|
|
67
|
-
if (max === 1) return '…';
|
|
68
|
-
let result = '';
|
|
69
|
-
let used = 0;
|
|
70
|
-
for (const ch of text) {
|
|
71
|
-
const cw = charWidth(ch);
|
|
72
|
-
if (used + cw > max - 1) break;
|
|
73
|
-
result += ch;
|
|
74
|
-
used += cw;
|
|
75
|
-
}
|
|
76
|
-
const truncated = `${result}…`;
|
|
77
|
-
return truncated.padEnd(truncated.length + Math.max(0, max - (used + 1)));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Build a key→cmd lookup from ACTIONS for O(1) dispatch
|
|
81
|
-
const KEY_TO_CMD: Record<string, string> = {};
|
|
82
|
-
for (const a of ACTIONS) KEY_TO_CMD[a.key] = a.cmd;
|
|
83
|
-
|
|
84
|
-
export function Sidebar({ health, background, onAction, activeView, isFocused = false }: SidebarProps): React.ReactElement {
|
|
85
|
-
// ── Interactive navigation: Sidebar owns shortcut key dispatch ──
|
|
86
|
-
useInput((ch, key) => {
|
|
87
|
-
// Esc: return home from any secondary view
|
|
88
|
-
if (key.escape && activeView !== 'home') {
|
|
89
|
-
onAction('/home');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const cmd = KEY_TO_CMD[ch];
|
|
93
|
-
if (cmd) {
|
|
94
|
-
onAction(cmd);
|
|
95
|
-
}
|
|
96
|
-
}, { isActive: isFocused });
|
|
97
|
-
|
|
98
|
-
// Map view types to sidebar action commands for highlight
|
|
99
|
-
const activeCmd = ACTIONS.find(a => {
|
|
100
|
-
const viewMap: Record<string, string> = {
|
|
101
|
-
'/search': 'search', '/recent': 'recent', '/doctor': 'doctor',
|
|
102
|
-
'/background': 'background', '/dashboard': 'dashboard',
|
|
103
|
-
'/project': 'project', '/configure': 'configure',
|
|
104
|
-
'/integrate': 'integrate', '/home': 'home',
|
|
105
|
-
};
|
|
106
|
-
return viewMap[a.cmd] === activeView;
|
|
107
|
-
})?.cmd;
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<Box
|
|
111
|
-
flexDirection="column"
|
|
112
|
-
width={SIDEBAR_WIDTH}
|
|
113
|
-
borderStyle="single"
|
|
114
|
-
borderColor={COLORS.border}
|
|
115
|
-
paddingX={1}
|
|
116
|
-
>
|
|
117
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
118
|
-
<Text color={COLORS.brand} bold>Quick Actions</Text>
|
|
119
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
120
|
-
{ACTIONS.map((action) => {
|
|
121
|
-
const isActive = action.cmd === activeCmd;
|
|
122
|
-
return (
|
|
123
|
-
<Box key={action.key}>
|
|
124
|
-
<Text color={isActive ? COLORS.brand : COLORS.muted}>
|
|
125
|
-
{`${isActive ? SYMBOLS.arrow : action.key} `}
|
|
126
|
-
</Text>
|
|
127
|
-
<Text color={isActive ? COLORS.accent : COLORS.text} bold={isActive}>{fit(action.label, ACTION_LABEL_WIDTH)}</Text>
|
|
128
|
-
</Box>
|
|
129
|
-
);
|
|
130
|
-
})}
|
|
131
|
-
</Box>
|
|
132
|
-
|
|
133
|
-
<Box flexDirection="column">
|
|
134
|
-
<Text color={COLORS.brand} bold>Health</Text>
|
|
135
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
136
|
-
|
|
137
|
-
<Box>
|
|
138
|
-
<Text color={COLORS.muted}>{'Embed'.padEnd(8)}</Text>
|
|
139
|
-
<Text color={
|
|
140
|
-
health.embeddingProvider === 'ready' ? COLORS.success
|
|
141
|
-
: health.embeddingProvider === 'unavailable' ? COLORS.warning
|
|
142
|
-
: COLORS.muted
|
|
143
|
-
}>
|
|
144
|
-
{fit(`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : health.embeddingProvider === 'unavailable' ? STATUS_DOTS.warn : STATUS_DOTS.off} ${health.embeddingLabel}`, VALUE_WIDTH)}
|
|
145
|
-
</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
<Box>
|
|
148
|
-
<Text color={COLORS.muted}>{'Search'.padEnd(8)}</Text>
|
|
149
|
-
<Text color={colorForMode(health.searchModeLabel)}>{fit(`${STATUS_DOTS.ok} ${health.searchModeLabel}`, VALUE_WIDTH)}</Text>
|
|
150
|
-
</Box>
|
|
151
|
-
<Box>
|
|
152
|
-
<Text color={COLORS.muted}>{'Sess'.padEnd(8)}</Text>
|
|
153
|
-
<Text color={COLORS.text}>{fit(`${health.sessions}`, VALUE_WIDTH)}</Text>
|
|
154
|
-
</Box>
|
|
155
|
-
|
|
156
|
-
<Box marginTop={1} flexDirection="column">
|
|
157
|
-
<Text color={COLORS.brand} bold>Background</Text>
|
|
158
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
159
|
-
<Box>
|
|
160
|
-
<Text color={COLORS.muted}>{'Status'.padEnd(8)}</Text>
|
|
161
|
-
<Text color={background.healthy ? COLORS.success : background.running ? COLORS.warning : COLORS.muted}>
|
|
162
|
-
{fit(`${background.healthy ? STATUS_DOTS.running : background.running ? STATUS_DOTS.warn : STATUS_DOTS.stopped} ${background.healthy ? 'Running' : background.running ? 'Warning' : 'Stopped'}`, VALUE_WIDTH)}
|
|
163
|
-
</Text>
|
|
164
|
-
</Box>
|
|
165
|
-
{background.port && (
|
|
166
|
-
<Box>
|
|
167
|
-
<Text color={COLORS.muted}>{'Port'.padEnd(8)}</Text>
|
|
168
|
-
<Text color={COLORS.text}>{fit(`${background.port}`, VALUE_WIDTH)}</Text>
|
|
169
|
-
</Box>
|
|
170
|
-
)}
|
|
171
|
-
</Box>
|
|
172
|
-
</Box>
|
|
173
|
-
|
|
174
|
-
<Box marginTop={1}>
|
|
175
|
-
<Text color={COLORS.muted} italic>Keys or /cmd</Text>
|
|
176
|
-
</Box>
|
|
177
|
-
</Box>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar right panel with quick action hints and health snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Modern design: Unicode separators, status dots, active indicator
|
|
5
|
+
* with arrow symbol, brand-colored section headers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Box, Text, useInput } from 'ink';
|
|
10
|
+
import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
|
|
11
|
+
import type { HealthInfo, BackgroundInfo } from './data.js';
|
|
12
|
+
import type { ViewType } from './theme.js';
|
|
13
|
+
|
|
14
|
+
interface SidebarProps {
|
|
15
|
+
health: HealthInfo;
|
|
16
|
+
background: BackgroundInfo;
|
|
17
|
+
onAction: (cmd: string) => void;
|
|
18
|
+
activeView: ViewType;
|
|
19
|
+
/** When true, Sidebar captures shortcut keys and drives navigation. */
|
|
20
|
+
isFocused?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ACTIONS = [
|
|
24
|
+
{ key: 's', label: 'Search', cmd: '/search' },
|
|
25
|
+
{ key: 'r', label: 'Remember', cmd: '/remember' },
|
|
26
|
+
{ key: 'v', label: 'Recent', cmd: '/recent' },
|
|
27
|
+
{ key: 'd', label: 'Doctor', cmd: '/doctor' },
|
|
28
|
+
{ key: 'b', label: 'Background', cmd: '/background' },
|
|
29
|
+
{ key: 'w', label: 'Dashboard', cmd: '/dashboard' },
|
|
30
|
+
{ key: 'p', label: 'Project', cmd: '/project' },
|
|
31
|
+
{ key: 'c', label: 'Configure', cmd: '/configure' },
|
|
32
|
+
{ key: 'i', label: 'Integrate', cmd: '/integrate' },
|
|
33
|
+
{ key: 'h', label: 'Home', cmd: '/home' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const SIDEBAR_WIDTH = 32;
|
|
37
|
+
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
38
|
+
const ACTION_LABEL_WIDTH = INNER_WIDTH - 2;
|
|
39
|
+
const VALUE_WIDTH = INNER_WIDTH - 8;
|
|
40
|
+
|
|
41
|
+
function separator(width: number): string {
|
|
42
|
+
return SEP.thin.repeat(width);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function colorForMode(mode: string): string {
|
|
46
|
+
const normalized = mode.toLowerCase();
|
|
47
|
+
if (normalized.includes('hybrid')) return COLORS.success;
|
|
48
|
+
if (normalized.includes('vector')) return COLORS.accent;
|
|
49
|
+
return COLORS.warning;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function charWidth(ch: string): number {
|
|
53
|
+
const cp = ch.codePointAt(0)!;
|
|
54
|
+
return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
|
|
55
|
+
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
56
|
+
(cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fit(text: string, max: number): string {
|
|
60
|
+
if (max <= 0) return '';
|
|
61
|
+
let width = 0;
|
|
62
|
+
for (const ch of text) {
|
|
63
|
+
width += charWidth(ch);
|
|
64
|
+
if (width > max) break;
|
|
65
|
+
}
|
|
66
|
+
if (width <= max) return text.padEnd(text.length + (max - width));
|
|
67
|
+
if (max === 1) return '…';
|
|
68
|
+
let result = '';
|
|
69
|
+
let used = 0;
|
|
70
|
+
for (const ch of text) {
|
|
71
|
+
const cw = charWidth(ch);
|
|
72
|
+
if (used + cw > max - 1) break;
|
|
73
|
+
result += ch;
|
|
74
|
+
used += cw;
|
|
75
|
+
}
|
|
76
|
+
const truncated = `${result}…`;
|
|
77
|
+
return truncated.padEnd(truncated.length + Math.max(0, max - (used + 1)));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build a key→cmd lookup from ACTIONS for O(1) dispatch
|
|
81
|
+
const KEY_TO_CMD: Record<string, string> = {};
|
|
82
|
+
for (const a of ACTIONS) KEY_TO_CMD[a.key] = a.cmd;
|
|
83
|
+
|
|
84
|
+
export function Sidebar({ health, background, onAction, activeView, isFocused = false }: SidebarProps): React.ReactElement {
|
|
85
|
+
// ── Interactive navigation: Sidebar owns shortcut key dispatch ──
|
|
86
|
+
useInput((ch, key) => {
|
|
87
|
+
// Esc: return home from any secondary view
|
|
88
|
+
if (key.escape && activeView !== 'home') {
|
|
89
|
+
onAction('/home');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const cmd = KEY_TO_CMD[ch];
|
|
93
|
+
if (cmd) {
|
|
94
|
+
onAction(cmd);
|
|
95
|
+
}
|
|
96
|
+
}, { isActive: isFocused });
|
|
97
|
+
|
|
98
|
+
// Map view types to sidebar action commands for highlight
|
|
99
|
+
const activeCmd = ACTIONS.find(a => {
|
|
100
|
+
const viewMap: Record<string, string> = {
|
|
101
|
+
'/search': 'search', '/recent': 'recent', '/doctor': 'doctor',
|
|
102
|
+
'/background': 'background', '/dashboard': 'dashboard',
|
|
103
|
+
'/project': 'project', '/configure': 'configure',
|
|
104
|
+
'/integrate': 'integrate', '/home': 'home',
|
|
105
|
+
};
|
|
106
|
+
return viewMap[a.cmd] === activeView;
|
|
107
|
+
})?.cmd;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Box
|
|
111
|
+
flexDirection="column"
|
|
112
|
+
width={SIDEBAR_WIDTH}
|
|
113
|
+
borderStyle="single"
|
|
114
|
+
borderColor={COLORS.border}
|
|
115
|
+
paddingX={1}
|
|
116
|
+
>
|
|
117
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
118
|
+
<Text color={COLORS.brand} bold>Quick Actions</Text>
|
|
119
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
120
|
+
{ACTIONS.map((action) => {
|
|
121
|
+
const isActive = action.cmd === activeCmd;
|
|
122
|
+
return (
|
|
123
|
+
<Box key={action.key}>
|
|
124
|
+
<Text color={isActive ? COLORS.brand : COLORS.muted}>
|
|
125
|
+
{`${isActive ? SYMBOLS.arrow : action.key} `}
|
|
126
|
+
</Text>
|
|
127
|
+
<Text color={isActive ? COLORS.accent : COLORS.text} bold={isActive}>{fit(action.label, ACTION_LABEL_WIDTH)}</Text>
|
|
128
|
+
</Box>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</Box>
|
|
132
|
+
|
|
133
|
+
<Box flexDirection="column">
|
|
134
|
+
<Text color={COLORS.brand} bold>Health</Text>
|
|
135
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
136
|
+
|
|
137
|
+
<Box>
|
|
138
|
+
<Text color={COLORS.muted}>{'Embed'.padEnd(8)}</Text>
|
|
139
|
+
<Text color={
|
|
140
|
+
health.embeddingProvider === 'ready' ? COLORS.success
|
|
141
|
+
: health.embeddingProvider === 'unavailable' ? COLORS.warning
|
|
142
|
+
: COLORS.muted
|
|
143
|
+
}>
|
|
144
|
+
{fit(`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : health.embeddingProvider === 'unavailable' ? STATUS_DOTS.warn : STATUS_DOTS.off} ${health.embeddingLabel}`, VALUE_WIDTH)}
|
|
145
|
+
</Text>
|
|
146
|
+
</Box>
|
|
147
|
+
<Box>
|
|
148
|
+
<Text color={COLORS.muted}>{'Search'.padEnd(8)}</Text>
|
|
149
|
+
<Text color={colorForMode(health.searchModeLabel)}>{fit(`${STATUS_DOTS.ok} ${health.searchModeLabel}`, VALUE_WIDTH)}</Text>
|
|
150
|
+
</Box>
|
|
151
|
+
<Box>
|
|
152
|
+
<Text color={COLORS.muted}>{'Sess'.padEnd(8)}</Text>
|
|
153
|
+
<Text color={COLORS.text}>{fit(`${health.sessions}`, VALUE_WIDTH)}</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
|
|
156
|
+
<Box marginTop={1} flexDirection="column">
|
|
157
|
+
<Text color={COLORS.brand} bold>Background</Text>
|
|
158
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
159
|
+
<Box>
|
|
160
|
+
<Text color={COLORS.muted}>{'Status'.padEnd(8)}</Text>
|
|
161
|
+
<Text color={background.healthy ? COLORS.success : background.running ? COLORS.warning : COLORS.muted}>
|
|
162
|
+
{fit(`${background.healthy ? STATUS_DOTS.running : background.running ? STATUS_DOTS.warn : STATUS_DOTS.stopped} ${background.healthy ? 'Running' : background.running ? 'Warning' : 'Stopped'}`, VALUE_WIDTH)}
|
|
163
|
+
</Text>
|
|
164
|
+
</Box>
|
|
165
|
+
{background.port && (
|
|
166
|
+
<Box>
|
|
167
|
+
<Text color={COLORS.muted}>{'Port'.padEnd(8)}</Text>
|
|
168
|
+
<Text color={COLORS.text}>{fit(`${background.port}`, VALUE_WIDTH)}</Text>
|
|
169
|
+
</Box>
|
|
170
|
+
)}
|
|
171
|
+
</Box>
|
|
172
|
+
</Box>
|
|
173
|
+
|
|
174
|
+
<Box marginTop={1}>
|
|
175
|
+
<Text color={COLORS.muted} italic>Keys or /cmd</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
</Box>
|
|
178
|
+
);
|
|
179
|
+
}
|
package/src/cli/tui/index.ts
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memorix TUI entry point.
|
|
3
|
-
*
|
|
4
|
-
* Renders the Ink-based workbench in alternate screen buffer.
|
|
5
|
-
* All commands are now Ink-native — no interactive fallback needed.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { getCliVersion } from '../version.js';
|
|
9
|
-
|
|
10
|
-
const ALT_ON = '\x1b[?1049h\x1b[?25l';
|
|
11
|
-
const ALT_OFF = '\x1b[?25h\x1b[?1049l';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Start the fullscreen Ink-based workbench.
|
|
15
|
-
* All views are Ink-native. No alternate-screen exit/re-enter loop.
|
|
16
|
-
*/
|
|
17
|
-
export async function startWorkbench(): Promise<void> {
|
|
18
|
-
const { render } = await import('ink');
|
|
19
|
-
const React = await import('react');
|
|
20
|
-
const { WorkbenchApp } = await import('./App.js');
|
|
21
|
-
|
|
22
|
-
process.stdout.write(ALT_ON);
|
|
23
|
-
|
|
24
|
-
const app = React.createElement(WorkbenchApp, {
|
|
25
|
-
version: getCliVersion(),
|
|
26
|
-
onExitForInteractive: () => {
|
|
27
|
-
// No-op: all commands are now Ink-native.
|
|
28
|
-
// Kept for interface compatibility but never called.
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const instance = render(app, { exitOnCtrlC: false });
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
await instance.waitUntilExit();
|
|
36
|
-
} catch {
|
|
37
|
-
// Ignore unmount-triggered exits.
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
process.stdout.write(ALT_OFF);
|
|
41
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Memorix TUI entry point.
|
|
3
|
+
*
|
|
4
|
+
* Renders the Ink-based workbench in alternate screen buffer.
|
|
5
|
+
* All commands are now Ink-native — no interactive fallback needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getCliVersion } from '../version.js';
|
|
9
|
+
|
|
10
|
+
const ALT_ON = '\x1b[?1049h\x1b[?25l';
|
|
11
|
+
const ALT_OFF = '\x1b[?25h\x1b[?1049l';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Start the fullscreen Ink-based workbench.
|
|
15
|
+
* All views are Ink-native. No alternate-screen exit/re-enter loop.
|
|
16
|
+
*/
|
|
17
|
+
export async function startWorkbench(): Promise<void> {
|
|
18
|
+
const { render } = await import('ink');
|
|
19
|
+
const React = await import('react');
|
|
20
|
+
const { WorkbenchApp } = await import('./App.js');
|
|
21
|
+
|
|
22
|
+
process.stdout.write(ALT_ON);
|
|
23
|
+
|
|
24
|
+
const app = React.createElement(WorkbenchApp, {
|
|
25
|
+
version: getCliVersion(),
|
|
26
|
+
onExitForInteractive: () => {
|
|
27
|
+
// No-op: all commands are now Ink-native.
|
|
28
|
+
// Kept for interface compatibility but never called.
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const instance = render(app, { exitOnCtrlC: false });
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await instance.waitUntilExit();
|
|
36
|
+
} catch {
|
|
37
|
+
// Ignore unmount-triggered exits.
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.stdout.write(ALT_OFF);
|
|
41
|
+
}
|