memorix 1.4.6 → 1.5.1

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 (119) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +225 -70
  4. package/dist/cli/memcode.js +0 -0
  5. package/dist/index.js +225 -70
  6. package/dist/maintenance-runner.js +183 -38
  7. package/dist/media-audio-runner.js +183 -38
  8. package/dist/media-video-runner.js +183 -38
  9. package/dist/memcode-runtime/CHANGELOG.md +38 -0
  10. package/dist/sdk.js +224 -69
  11. package/dist/types.d.ts +2 -0
  12. package/dist/vector-backfill-runner.js +183 -38
  13. package/docs/CONFIGURATION.md +12 -0
  14. package/docs/DESIGN_DECISIONS.md +357 -357
  15. package/package.json +1 -1
  16. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  17. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  18. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  19. package/plugins/copilot/memorix/plugin.json +1 -1
  20. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  21. package/plugins/omp/memorix/package.json +1 -1
  22. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  23. package/plugins/pi/memorix/package.json +1 -1
  24. package/server.json +2 -2
  25. package/src/audit/index.ts +156 -156
  26. package/src/cli/commands/formation.ts +48 -48
  27. package/src/cli/commands/git-hook-install.ts +111 -111
  28. package/src/cli/commands/handoff.ts +54 -54
  29. package/src/cli/commands/hooks-status.ts +63 -63
  30. package/src/cli/commands/ingest.ts +44 -44
  31. package/src/cli/commands/integrate-shared.ts +15 -15
  32. package/src/cli/commands/lock.ts +82 -82
  33. package/src/cli/commands/message.ts +104 -104
  34. package/src/cli/commands/poll.ts +58 -58
  35. package/src/cli/commands/reasoning.ts +118 -118
  36. package/src/cli/commands/skills.ts +114 -114
  37. package/src/cli/commands/task.ts +167 -167
  38. package/src/cli/commands/transfer.ts +47 -47
  39. package/src/cli/index.ts +406 -406
  40. package/src/cli/tui/ChatView.tsx +234 -234
  41. package/src/cli/tui/CommandBar.tsx +312 -312
  42. package/src/cli/tui/ContextRail.tsx +118 -118
  43. package/src/cli/tui/HeaderBar.tsx +72 -72
  44. package/src/cli/tui/LogoBanner.tsx +51 -51
  45. package/src/cli/tui/index.ts +41 -41
  46. package/src/cli/version.ts +7 -7
  47. package/src/cli/workbench.ts +1 -1
  48. package/src/config.ts +23 -2
  49. package/src/dashboard/project-classification.ts +64 -64
  50. package/src/embedding/api-provider.ts +105 -22
  51. package/src/embedding/provider.ts +2 -0
  52. package/src/git/hooks-path.ts +85 -85
  53. package/src/hooks/pattern-detector.ts +173 -173
  54. package/src/hooks/significance-filter.ts +250 -250
  55. package/src/llm/memory-manager.ts +328 -328
  56. package/src/llm/provider.ts +17 -8
  57. package/src/llm/quality.ts +248 -248
  58. package/src/memory/attribution-guard.ts +249 -249
  59. package/src/memory/disclosure-policy.ts +135 -135
  60. package/src/memory/entity-extractor.ts +197 -197
  61. package/src/memory/formation/evaluate.ts +217 -217
  62. package/src/memory/formation/extract.ts +6 -3
  63. package/src/memory/formation/index.ts +2 -1
  64. package/src/memory/formation/resolve.ts +10 -6
  65. package/src/memory/formation/types.ts +3 -1
  66. package/src/memory/freshness.ts +122 -122
  67. package/src/memory/graph.ts +197 -197
  68. package/src/memory/long-term.ts +7 -1
  69. package/src/memory/refs.ts +94 -94
  70. package/src/memory/secret-filter.ts +79 -79
  71. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  72. package/src/orchestrate/adapters/claude.ts +111 -111
  73. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  74. package/src/orchestrate/adapters/codex.ts +41 -41
  75. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  76. package/src/orchestrate/adapters/gemini.ts +42 -42
  77. package/src/orchestrate/adapters/index.ts +73 -73
  78. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  79. package/src/orchestrate/adapters/opencode.ts +47 -47
  80. package/src/orchestrate/adapters/types.ts +77 -77
  81. package/src/orchestrate/capability-router.ts +284 -284
  82. package/src/orchestrate/cost-tracker.ts +219 -219
  83. package/src/orchestrate/error-recovery.ts +191 -191
  84. package/src/orchestrate/evidence.ts +140 -140
  85. package/src/orchestrate/ledger.ts +110 -110
  86. package/src/orchestrate/memorix-bridge.ts +343 -343
  87. package/src/orchestrate/output-budget.ts +80 -80
  88. package/src/orchestrate/permission.ts +152 -152
  89. package/src/orchestrate/pipeline-trace.ts +131 -131
  90. package/src/orchestrate/prompt-builder.ts +155 -155
  91. package/src/orchestrate/ring-buffer.ts +37 -37
  92. package/src/orchestrate/task-graph.ts +389 -389
  93. package/src/project/aliases.ts +374 -374
  94. package/src/rules/adapters/trae.ts +56 -56
  95. package/src/rules/syncer.ts +235 -235
  96. package/src/search/query-expansion.ts +52 -52
  97. package/src/server/formation-timeout.ts +27 -27
  98. package/src/server.ts +14 -10
  99. package/src/skills/mini-skills.ts +386 -386
  100. package/src/store/chat-store.ts +119 -119
  101. package/src/store/graph-store.ts +249 -249
  102. package/src/store/mini-skill-store.ts +349 -349
  103. package/src/store/orama-store.ts +6 -4
  104. package/src/store/persistence-json.ts +212 -212
  105. package/src/store/persistence.ts +291 -291
  106. package/src/store/project-affinity.ts +195 -195
  107. package/src/team/event-bus.ts +76 -76
  108. package/src/team/handoff.ts +161 -161
  109. package/src/team/poll.ts +132 -132
  110. package/src/timeout.ts +67 -0
  111. package/src/types.ts +2 -0
  112. package/src/workspace/mcp-adapters/codex.ts +191 -191
  113. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  114. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  115. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  116. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  117. package/src/workspace/mcp-adapters/trae.ts +134 -134
  118. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  119. package/src/workspace/sanitizer.ts +60 -60
@@ -1,118 +1,118 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
4
- import type { ProjectInfo, HealthInfo, BackgroundInfo } from './data.js';
5
- import type { ChatAnswer } from './chat-service.js';
6
- import type { ViewType } from './theme.js';
7
-
8
- interface ContextRailProps {
9
- project: ProjectInfo | null;
10
- health: HealthInfo;
11
- background: BackgroundInfo;
12
- activeView: ViewType;
13
- lastChat: ChatAnswer | null;
14
- transcriptCount: number;
15
- /** Rail width in columns (default 34). Parent should calculate based on terminal size. */
16
- width?: number;
17
- }
18
-
19
- function separator(width: number): string {
20
- return SEP.thin.repeat(width);
21
- }
22
-
23
- function charWidth(ch: string): number {
24
- const cp = ch.codePointAt(0)!;
25
- return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
26
- (cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
27
- (cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
28
- }
29
-
30
- function fit(text: string, max: number): string {
31
- if (max <= 0) return '';
32
- // Measure display width (CJK chars = 2 columns)
33
- let w = 0;
34
- for (const ch of text) {
35
- w += charWidth(ch);
36
- if (w > max) break;
37
- }
38
- if (w <= max) return text.padEnd(text.length + (max - w));
39
- // Truncate by display width
40
- if (max === 1) return '…';
41
- let result = '';
42
- let rw = 0;
43
- for (const ch of text) {
44
- const cw = charWidth(ch);
45
- if (rw + cw > max - 1) break;
46
- result += ch;
47
- rw += cw;
48
- }
49
- const truncated = `${result}…`;
50
- return truncated.padEnd(truncated.length + Math.max(0, max - (rw + 1)));
51
- }
52
-
53
- export function ContextRail({ project, health, background, activeView, lastChat, transcriptCount, width: railWidth = 34 }: ContextRailProps): React.ReactElement {
54
- const width = railWidth;
55
- const inner = width - 4;
56
-
57
- return (
58
- <Box
59
- flexDirection="column"
60
- width={width}
61
- flexShrink={0}
62
- paddingLeft={1}
63
- borderStyle="single"
64
- borderColor={COLORS.border}
65
- borderLeft={true}
66
- borderTop={false}
67
- borderRight={false}
68
- borderBottom={false}
69
- >
70
- <Text color={COLORS.brand} bold wrap="truncate-end">Context</Text>
71
- <Text color={COLORS.border} wrap="truncate-end">{separator(inner)}</Text>
72
-
73
- <Box flexDirection="column" marginBottom={1}>
74
- <Text color={COLORS.muted} wrap="truncate-end">View</Text>
75
- <Text color={COLORS.text} wrap="truncate-end">{fit(activeView, inner)}</Text>
76
- </Box>
77
-
78
- <Box flexDirection="column" marginBottom={1}>
79
- <Text color={COLORS.muted} wrap="truncate-end">Project</Text>
80
- <Text color={project ? COLORS.text : COLORS.warning} wrap="truncate-end">{project ? fit(project.name, inner) : 'No project'}</Text>
81
- </Box>
82
-
83
- <Box flexDirection="column" marginBottom={1}>
84
- <Text color={COLORS.muted} wrap="truncate-end">Retrieval</Text>
85
- <Text color={health.embeddingProvider === 'ready' ? COLORS.success : COLORS.muted} wrap="truncate-end">
86
- {`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : STATUS_DOTS.off} ${fit(health.searchModeLabel, inner - 2)}`}
87
- </Text>
88
- <Text color={COLORS.textDim} wrap="truncate-end">{fit(health.searchDiagnostic, inner)}</Text>
89
- </Box>
90
-
91
- <Box flexDirection="column" marginBottom={1}>
92
- <Text color={COLORS.muted} wrap="truncate-end">Session</Text>
93
- <Text color={COLORS.text} wrap="truncate-end">{fit(`${transcriptCount} messages`, inner)}</Text>
94
- <Text color={background.healthy ? COLORS.success : COLORS.muted} wrap="truncate-end">
95
- {fit(`${background.healthy ? STATUS_DOTS.running : STATUS_DOTS.off} background${background.port ? ` :${background.port}` : ''}`, inner)}
96
- </Text>
97
- </Box>
98
-
99
- {lastChat && lastChat.sources.length > 0 && (
100
- <Box flexDirection="column" marginBottom={1}>
101
- <Text color={COLORS.muted} wrap="truncate-end">Last sources</Text>
102
- {lastChat.sources.slice(0, 3).map((source) => (
103
- <Text key={source.id} color={COLORS.textDim} wrap="truncate-end">{fit(`[obs:${source.id}] ${source.title}`, inner)}</Text>
104
- ))}
105
- </Box>
106
- )}
107
-
108
- <Box flexDirection="column">
109
- <Text color={COLORS.muted} wrap="truncate-end">Quick actions</Text>
110
- <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /chat ask with memory`, inner)}</Text>
111
- <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /search inspect hits`, inner)}</Text>
112
- <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /recent recent activity`, inner)}</Text>
113
- <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /remember store a note`, inner)}</Text>
114
- <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /doctor diagnostics`, inner)}</Text>
115
- </Box>
116
- </Box>
117
- );
118
- }
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
4
+ import type { ProjectInfo, HealthInfo, BackgroundInfo } from './data.js';
5
+ import type { ChatAnswer } from './chat-service.js';
6
+ import type { ViewType } from './theme.js';
7
+
8
+ interface ContextRailProps {
9
+ project: ProjectInfo | null;
10
+ health: HealthInfo;
11
+ background: BackgroundInfo;
12
+ activeView: ViewType;
13
+ lastChat: ChatAnswer | null;
14
+ transcriptCount: number;
15
+ /** Rail width in columns (default 34). Parent should calculate based on terminal size. */
16
+ width?: number;
17
+ }
18
+
19
+ function separator(width: number): string {
20
+ return SEP.thin.repeat(width);
21
+ }
22
+
23
+ function charWidth(ch: string): number {
24
+ const cp = ch.codePointAt(0)!;
25
+ return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
26
+ (cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
27
+ (cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
28
+ }
29
+
30
+ function fit(text: string, max: number): string {
31
+ if (max <= 0) return '';
32
+ // Measure display width (CJK chars = 2 columns)
33
+ let w = 0;
34
+ for (const ch of text) {
35
+ w += charWidth(ch);
36
+ if (w > max) break;
37
+ }
38
+ if (w <= max) return text.padEnd(text.length + (max - w));
39
+ // Truncate by display width
40
+ if (max === 1) return '…';
41
+ let result = '';
42
+ let rw = 0;
43
+ for (const ch of text) {
44
+ const cw = charWidth(ch);
45
+ if (rw + cw > max - 1) break;
46
+ result += ch;
47
+ rw += cw;
48
+ }
49
+ const truncated = `${result}…`;
50
+ return truncated.padEnd(truncated.length + Math.max(0, max - (rw + 1)));
51
+ }
52
+
53
+ export function ContextRail({ project, health, background, activeView, lastChat, transcriptCount, width: railWidth = 34 }: ContextRailProps): React.ReactElement {
54
+ const width = railWidth;
55
+ const inner = width - 4;
56
+
57
+ return (
58
+ <Box
59
+ flexDirection="column"
60
+ width={width}
61
+ flexShrink={0}
62
+ paddingLeft={1}
63
+ borderStyle="single"
64
+ borderColor={COLORS.border}
65
+ borderLeft={true}
66
+ borderTop={false}
67
+ borderRight={false}
68
+ borderBottom={false}
69
+ >
70
+ <Text color={COLORS.brand} bold wrap="truncate-end">Context</Text>
71
+ <Text color={COLORS.border} wrap="truncate-end">{separator(inner)}</Text>
72
+
73
+ <Box flexDirection="column" marginBottom={1}>
74
+ <Text color={COLORS.muted} wrap="truncate-end">View</Text>
75
+ <Text color={COLORS.text} wrap="truncate-end">{fit(activeView, inner)}</Text>
76
+ </Box>
77
+
78
+ <Box flexDirection="column" marginBottom={1}>
79
+ <Text color={COLORS.muted} wrap="truncate-end">Project</Text>
80
+ <Text color={project ? COLORS.text : COLORS.warning} wrap="truncate-end">{project ? fit(project.name, inner) : 'No project'}</Text>
81
+ </Box>
82
+
83
+ <Box flexDirection="column" marginBottom={1}>
84
+ <Text color={COLORS.muted} wrap="truncate-end">Retrieval</Text>
85
+ <Text color={health.embeddingProvider === 'ready' ? COLORS.success : COLORS.muted} wrap="truncate-end">
86
+ {`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : STATUS_DOTS.off} ${fit(health.searchModeLabel, inner - 2)}`}
87
+ </Text>
88
+ <Text color={COLORS.textDim} wrap="truncate-end">{fit(health.searchDiagnostic, inner)}</Text>
89
+ </Box>
90
+
91
+ <Box flexDirection="column" marginBottom={1}>
92
+ <Text color={COLORS.muted} wrap="truncate-end">Session</Text>
93
+ <Text color={COLORS.text} wrap="truncate-end">{fit(`${transcriptCount} messages`, inner)}</Text>
94
+ <Text color={background.healthy ? COLORS.success : COLORS.muted} wrap="truncate-end">
95
+ {fit(`${background.healthy ? STATUS_DOTS.running : STATUS_DOTS.off} background${background.port ? ` :${background.port}` : ''}`, inner)}
96
+ </Text>
97
+ </Box>
98
+
99
+ {lastChat && lastChat.sources.length > 0 && (
100
+ <Box flexDirection="column" marginBottom={1}>
101
+ <Text color={COLORS.muted} wrap="truncate-end">Last sources</Text>
102
+ {lastChat.sources.slice(0, 3).map((source) => (
103
+ <Text key={source.id} color={COLORS.textDim} wrap="truncate-end">{fit(`[obs:${source.id}] ${source.title}`, inner)}</Text>
104
+ ))}
105
+ </Box>
106
+ )}
107
+
108
+ <Box flexDirection="column">
109
+ <Text color={COLORS.muted} wrap="truncate-end">Quick actions</Text>
110
+ <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /chat ask with memory`, inner)}</Text>
111
+ <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /search inspect hits`, inner)}</Text>
112
+ <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /recent recent activity`, inner)}</Text>
113
+ <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /remember store a note`, inner)}</Text>
114
+ <Text color={COLORS.textDim} wrap="truncate-end">{fit(`${SYMBOLS.arrow} /doctor diagnostics`, inner)}</Text>
115
+ </Box>
116
+ </Box>
117
+ );
118
+ }
@@ -1,72 +1,72 @@
1
- /**
2
- * Top status bar for the Memorix workbench.
3
- *
4
- * Modern design: brand symbol, pill-style badges, status dots,
5
- * Unicode separators.
6
- */
7
-
8
- import React from 'react';
9
- import { Box, Text } from 'ink';
10
- import { COLORS, STATUS_DOTS, SYMBOLS } from './theme.js';
11
- import type { ProjectInfo, HealthInfo } from './data.js';
12
-
13
- interface HeaderBarProps {
14
- version: string;
15
- project: ProjectInfo | null;
16
- health: HealthInfo;
17
- mode: string;
18
- }
19
-
20
- function colorForMode(mode: string): string {
21
- const normalized = mode.toLowerCase();
22
- if (normalized.includes('hybrid')) return COLORS.success;
23
- if (normalized.includes('vector')) return COLORS.accent;
24
- return COLORS.warning;
25
- }
26
-
27
- export function HeaderBar({ version, project, health, mode }: HeaderBarProps): React.ReactElement {
28
- return (
29
- <Box
30
- flexDirection="row"
31
- justifyContent="space-between"
32
- flexShrink={0}
33
- paddingX={1}
34
- borderStyle="single"
35
- borderColor={COLORS.border}
36
- borderBottom={true}
37
- borderTop={false}
38
- borderLeft={false}
39
- borderRight={false}
40
- >
41
- {/* Brand */}
42
- <Box gap={1}>
43
- <Text color={COLORS.brand} bold>{SYMBOLS.bullet} Memorix</Text>
44
- <Text color={COLORS.muted}>v{version}</Text>
45
- </Box>
46
-
47
- {/* Project name */}
48
- <Box>
49
- {project ? (
50
- <Text color={COLORS.text}>{project.name}</Text>
51
- ) : (
52
- <Text color={COLORS.warning}>{STATUS_DOTS.warn} no project</Text>
53
- )}
54
- </Box>
55
-
56
- {/* Status badges */}
57
- <Box gap={1}>
58
- {project ? (
59
- <>
60
- <Text color={COLORS.muted}>{SYMBOLS.pill(mode.toLowerCase())}</Text>
61
- <Text color={COLORS.border}>|</Text>
62
- <Text color={colorForMode(health.searchModeLabel)}>{STATUS_DOTS.ok} {health.searchModeLabel}</Text>
63
- <Text color={COLORS.border}>|</Text>
64
- <Text color={COLORS.text}>{health.activeMemories} mem</Text>
65
- </>
66
- ) : (
67
- <Text color={COLORS.muted}>/configure to get started</Text>
68
- )}
69
- </Box>
70
- </Box>
71
- );
72
- }
1
+ /**
2
+ * Top status bar for the Memorix workbench.
3
+ *
4
+ * Modern design: brand symbol, pill-style badges, status dots,
5
+ * Unicode separators.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { Box, Text } from 'ink';
10
+ import { COLORS, STATUS_DOTS, SYMBOLS } from './theme.js';
11
+ import type { ProjectInfo, HealthInfo } from './data.js';
12
+
13
+ interface HeaderBarProps {
14
+ version: string;
15
+ project: ProjectInfo | null;
16
+ health: HealthInfo;
17
+ mode: string;
18
+ }
19
+
20
+ function colorForMode(mode: string): string {
21
+ const normalized = mode.toLowerCase();
22
+ if (normalized.includes('hybrid')) return COLORS.success;
23
+ if (normalized.includes('vector')) return COLORS.accent;
24
+ return COLORS.warning;
25
+ }
26
+
27
+ export function HeaderBar({ version, project, health, mode }: HeaderBarProps): React.ReactElement {
28
+ return (
29
+ <Box
30
+ flexDirection="row"
31
+ justifyContent="space-between"
32
+ flexShrink={0}
33
+ paddingX={1}
34
+ borderStyle="single"
35
+ borderColor={COLORS.border}
36
+ borderBottom={true}
37
+ borderTop={false}
38
+ borderLeft={false}
39
+ borderRight={false}
40
+ >
41
+ {/* Brand */}
42
+ <Box gap={1}>
43
+ <Text color={COLORS.brand} bold>{SYMBOLS.bullet} Memorix</Text>
44
+ <Text color={COLORS.muted}>v{version}</Text>
45
+ </Box>
46
+
47
+ {/* Project name */}
48
+ <Box>
49
+ {project ? (
50
+ <Text color={COLORS.text}>{project.name}</Text>
51
+ ) : (
52
+ <Text color={COLORS.warning}>{STATUS_DOTS.warn} no project</Text>
53
+ )}
54
+ </Box>
55
+
56
+ {/* Status badges */}
57
+ <Box gap={1}>
58
+ {project ? (
59
+ <>
60
+ <Text color={COLORS.muted}>{SYMBOLS.pill(mode.toLowerCase())}</Text>
61
+ <Text color={COLORS.border}>|</Text>
62
+ <Text color={colorForMode(health.searchModeLabel)}>{STATUS_DOTS.ok} {health.searchModeLabel}</Text>
63
+ <Text color={COLORS.border}>|</Text>
64
+ <Text color={COLORS.text}>{health.activeMemories} mem</Text>
65
+ </>
66
+ ) : (
67
+ <Text color={COLORS.muted}>/configure to get started</Text>
68
+ )}
69
+ </Box>
70
+ </Box>
71
+ );
72
+ }
@@ -1,51 +1,51 @@
1
- /**
2
- * LogoBanner — ASCII art Memorix-Bridge logo with gradient coloring.
3
- *
4
- * Uses ink-gradient for the brand gradient effect.
5
- * Falls back to a compact single-line on narrow terminals (< 50 cols).
6
- */
7
-
8
- import React from 'react';
9
- import { Box, Text } from 'ink';
10
- import BigText from 'ink-big-text';
11
- import Gradient from 'ink-gradient';
12
- import { COLORS } from './theme.js';
13
-
14
- const SUBTITLE = 'Memory workbench for coding agents';
15
-
16
- interface LogoBannerProps {
17
- /** Terminal width — used to decide full vs compact mode */
18
- width?: number;
19
- /** App version string, e.g. "1.0.8" */
20
- version?: string;
21
- }
22
-
23
- export function LogoBanner({ width = 80, version }: LogoBannerProps): React.ReactElement {
24
- const compact = width < 54;
25
- const full = width >= 72;
26
- const brandColors = [COLORS.brandBright, COLORS.brand, COLORS.brandDim];
27
-
28
- if (compact) {
29
- return (
30
- <Box paddingX={1} flexDirection="column" alignItems="center">
31
- <Gradient colors={brandColors}>
32
- <Text bold>Memorix</Text>
33
- </Gradient>
34
- <Text color={COLORS.textDim}>Memory workbench</Text>
35
- {version && <Text color={COLORS.muted}>v{version}</Text>}
36
- </Box>
37
- );
38
- }
39
-
40
- return (
41
- <Box flexDirection="column" alignItems="center" paddingX={1}>
42
- <Gradient colors={brandColors}>
43
- {full ? <BigText text="MEMORIX" font="block" /> : <Text bold>MEMORIX</Text>}
44
- </Gradient>
45
- <Box>
46
- <Text color={COLORS.textDim}>{SUBTITLE}</Text>
47
- {version && <Text color={COLORS.muted}> v{version}</Text>}
48
- </Box>
49
- </Box>
50
- );
51
- }
1
+ /**
2
+ * LogoBanner — ASCII art Memorix-Bridge logo with gradient coloring.
3
+ *
4
+ * Uses ink-gradient for the brand gradient effect.
5
+ * Falls back to a compact single-line on narrow terminals (< 50 cols).
6
+ */
7
+
8
+ import React from 'react';
9
+ import { Box, Text } from 'ink';
10
+ import BigText from 'ink-big-text';
11
+ import Gradient from 'ink-gradient';
12
+ import { COLORS } from './theme.js';
13
+
14
+ const SUBTITLE = 'Memory workbench for coding agents';
15
+
16
+ interface LogoBannerProps {
17
+ /** Terminal width — used to decide full vs compact mode */
18
+ width?: number;
19
+ /** App version string, e.g. "1.0.8" */
20
+ version?: string;
21
+ }
22
+
23
+ export function LogoBanner({ width = 80, version }: LogoBannerProps): React.ReactElement {
24
+ const compact = width < 54;
25
+ const full = width >= 72;
26
+ const brandColors = [COLORS.brandBright, COLORS.brand, COLORS.brandDim];
27
+
28
+ if (compact) {
29
+ return (
30
+ <Box paddingX={1} flexDirection="column" alignItems="center">
31
+ <Gradient colors={brandColors}>
32
+ <Text bold>Memorix</Text>
33
+ </Gradient>
34
+ <Text color={COLORS.textDim}>Memory workbench</Text>
35
+ {version && <Text color={COLORS.muted}>v{version}</Text>}
36
+ </Box>
37
+ );
38
+ }
39
+
40
+ return (
41
+ <Box flexDirection="column" alignItems="center" paddingX={1}>
42
+ <Gradient colors={brandColors}>
43
+ {full ? <BigText text="MEMORIX" font="block" /> : <Text bold>MEMORIX</Text>}
44
+ </Gradient>
45
+ <Box>
46
+ <Text color={COLORS.textDim}>{SUBTITLE}</Text>
47
+ {version && <Text color={COLORS.muted}> v{version}</Text>}
48
+ </Box>
49
+ </Box>
50
+ );
51
+ }
@@ -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
+ }
@@ -1,7 +1,7 @@
1
- import pkg from '../../package.json';
2
-
3
- export function getCliVersion(): string {
4
- return typeof __MEMORIX_VERSION__ !== 'undefined'
5
- ? __MEMORIX_VERSION__
6
- : pkg.version;
7
- }
1
+ import pkg from '../../package.json';
2
+
3
+ export function getCliVersion(): string {
4
+ return typeof __MEMORIX_VERSION__ !== 'undefined'
5
+ ? __MEMORIX_VERSION__
6
+ : pkg.version;
7
+ }
@@ -5,4 +5,4 @@
5
5
  * Kept for backward-compat: `import { startWorkbench } from './workbench.js'`
6
6
  */
7
7
 
8
- export { startWorkbench } from './tui/index.js';
8
+ export { startWorkbench } from './tui/index.js';
package/src/config.ts CHANGED
@@ -84,9 +84,30 @@ export function getEmbeddingBaseUrl(): string {
84
84
  return getResolvedEmbeddingLane().baseUrl || 'https://api.openai.com/v1';
85
85
  }
86
86
 
87
- /** Embedding model: env > memorix.yml > config.json > default */
87
+ /** Embedding model: env > memorix.yml > config.json > provider-aware default */
88
88
  export function getEmbeddingModel(): string {
89
- return getResolvedEmbeddingLane().model || 'text-embedding-3-small';
89
+ const lane = getResolvedEmbeddingLane();
90
+ return defaultEmbeddingModelFor(lane.baseUrl, lane.model);
91
+ }
92
+
93
+ /**
94
+ * Provider-aware embedding model default: OpenRouter serves many providers,
95
+ * and the OpenAI-only default does not fit it. Qwen3-Embedding-8B is a
96
+ * strong multilingual default there; everywhere else keep the OpenAI model.
97
+ * An explicitly configured model always wins.
98
+ */
99
+ export function defaultEmbeddingModelFor(
100
+ baseUrl: string | undefined,
101
+ explicitModel: string | undefined,
102
+ ): string {
103
+ if (explicitModel) return explicitModel;
104
+ try {
105
+ const url = baseUrl ? new URL(baseUrl) : null;
106
+ if (url && (url.hostname === 'openrouter.ai' || url.hostname.endsWith('.openrouter.ai'))) {
107
+ return 'qwen/qwen3-embedding-8b';
108
+ }
109
+ } catch { /* treat unparsable base URLs as non-OpenRouter */ }
110
+ return 'text-embedding-3-small';
90
111
  }
91
112
 
92
113
  /** Embedding dimensions override: env > memorix.yml > config.json > null (auto-detect) */