@cortexmemory/cli 0.26.0 → 0.27.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 (71) hide show
  1. package/dist/commands/db.d.ts.map +1 -1
  2. package/dist/commands/db.js +2 -18
  3. package/dist/commands/db.js.map +1 -1
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +153 -17
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +373 -70
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/setup.d.ts.map +1 -1
  11. package/dist/commands/setup.js +102 -46
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +94 -7
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/types.d.ts +23 -0
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/utils/config.d.ts +11 -0
  20. package/dist/utils/config.d.ts.map +1 -1
  21. package/dist/utils/config.js +20 -0
  22. package/dist/utils/config.js.map +1 -1
  23. package/dist/utils/init/convex-setup.d.ts +58 -6
  24. package/dist/utils/init/convex-setup.d.ts.map +1 -1
  25. package/dist/utils/init/convex-setup.js +261 -57
  26. package/dist/utils/init/convex-setup.js.map +1 -1
  27. package/dist/utils/init/env-generator.d.ts.map +1 -1
  28. package/dist/utils/init/env-generator.js +12 -2
  29. package/dist/utils/init/env-generator.js.map +1 -1
  30. package/dist/utils/init/graph-setup.d.ts.map +1 -1
  31. package/dist/utils/init/graph-setup.js +12 -0
  32. package/dist/utils/init/graph-setup.js.map +1 -1
  33. package/dist/utils/init/quickstart-setup.d.ts +87 -0
  34. package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
  35. package/dist/utils/init/quickstart-setup.js +462 -0
  36. package/dist/utils/init/quickstart-setup.js.map +1 -0
  37. package/dist/utils/init/types.d.ts +4 -0
  38. package/dist/utils/init/types.d.ts.map +1 -1
  39. package/dist/utils/schema-sync.d.ts.map +1 -1
  40. package/dist/utils/schema-sync.js +27 -21
  41. package/dist/utils/schema-sync.js.map +1 -1
  42. package/package.json +3 -2
  43. package/templates/vercel-ai-quickstart/.env.local.example +45 -0
  44. package/templates/vercel-ai-quickstart/README.md +280 -0
  45. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +196 -0
  46. package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
  47. package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
  48. package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
  49. package/templates/vercel-ai-quickstart/app/globals.css +114 -0
  50. package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
  51. package/templates/vercel-ai-quickstart/app/page.tsx +131 -0
  52. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +237 -0
  53. package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
  54. package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
  55. package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
  56. package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
  57. package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
  58. package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
  59. package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
  60. package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
  61. package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
  62. package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
  63. package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
  64. package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
  65. package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
  66. package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
  67. package/templates/vercel-ai-quickstart/next.config.js +7 -0
  68. package/templates/vercel-ai-quickstart/package.json +41 -0
  69. package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
  70. package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
  71. package/templates/vercel-ai-quickstart/tsconfig.json +33 -0
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ interface DataPreviewProps {
4
+ data: {
5
+ id?: string;
6
+ preview?: string;
7
+ metadata?: Record<string, unknown>;
8
+ };
9
+ }
10
+
11
+ export function DataPreview({ data }: DataPreviewProps) {
12
+ return (
13
+ <div className="space-y-2 text-sm">
14
+ {/* ID */}
15
+ {data.id && (
16
+ <div className="flex items-center gap-2">
17
+ <span className="text-gray-500">ID:</span>
18
+ <code className="px-1.5 py-0.5 bg-black/30 rounded text-xs font-mono text-cortex-400">
19
+ {data.id}
20
+ </code>
21
+ </div>
22
+ )}
23
+
24
+ {/* Preview */}
25
+ {data.preview && (
26
+ <div className="mt-2">
27
+ <span className="text-gray-500 text-xs">Data:</span>
28
+ <pre className="mt-1 p-2 bg-black/30 rounded text-xs font-mono text-gray-300 overflow-x-auto whitespace-pre-wrap">
29
+ {data.preview}
30
+ </pre>
31
+ </div>
32
+ )}
33
+
34
+ {/* Metadata */}
35
+ {data.metadata && Object.keys(data.metadata).length > 0 && (
36
+ <div className="mt-2">
37
+ <span className="text-gray-500 text-xs">Metadata:</span>
38
+ <div className="mt-1 flex flex-wrap gap-1.5">
39
+ {Object.entries(data.metadata).map(([key, value]) => (
40
+ <span
41
+ key={key}
42
+ className="px-1.5 py-0.5 bg-white/5 rounded text-xs"
43
+ >
44
+ <span className="text-gray-500">{key}:</span>{" "}
45
+ <span className="text-gray-300">
46
+ {typeof value === "object"
47
+ ? JSON.stringify(value)
48
+ : String(value)}
49
+ </span>
50
+ </span>
51
+ ))}
52
+ </div>
53
+ </div>
54
+ )}
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,214 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+
5
+ interface HealthCheck {
6
+ status: string;
7
+ latencyMs?: number;
8
+ error?: string;
9
+ }
10
+
11
+ interface HealthResponse {
12
+ status: "healthy" | "degraded" | "unhealthy";
13
+ timestamp: string;
14
+ checks: Record<string, HealthCheck>;
15
+ config: {
16
+ convexUrl: string;
17
+ publicConvexUrl: string;
18
+ openaiKey: string;
19
+ graphSync: string;
20
+ graphBackend: string;
21
+ };
22
+ }
23
+
24
+ export function HealthStatus() {
25
+ const [health, setHealth] = useState<HealthResponse | null>(null);
26
+ const [loading, setLoading] = useState(true);
27
+ const [error, setError] = useState<string | null>(null);
28
+ const [expanded, setExpanded] = useState(false);
29
+
30
+ const checkHealth = useCallback(async () => {
31
+ try {
32
+ setLoading(true);
33
+ setError(null);
34
+ const response = await fetch("/api/health");
35
+ if (!response.ok) {
36
+ throw new Error(`HTTP ${response.status}`);
37
+ }
38
+ const data = await response.json();
39
+ setHealth(data);
40
+ } catch (err) {
41
+ setError(err instanceof Error ? err.message : "Health check failed");
42
+ } finally {
43
+ setLoading(false);
44
+ }
45
+ }, []);
46
+
47
+ useEffect(() => {
48
+ checkHealth();
49
+ // Re-check every 30 seconds
50
+ const interval = setInterval(checkHealth, 30000);
51
+ return () => clearInterval(interval);
52
+ }, [checkHealth]);
53
+
54
+ const statusColor = {
55
+ healthy: "bg-green-500",
56
+ degraded: "bg-yellow-500",
57
+ unhealthy: "bg-red-500",
58
+ };
59
+
60
+ const statusIcon = {
61
+ healthy: "✓",
62
+ degraded: "⚠",
63
+ unhealthy: "✗",
64
+ };
65
+
66
+ if (loading && !health) {
67
+ return (
68
+ <div className="flex items-center gap-2 text-sm text-gray-400">
69
+ <div className="w-2 h-2 rounded-full bg-gray-500 animate-pulse" />
70
+ <span>Checking...</span>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ if (error && !health) {
76
+ return (
77
+ <button
78
+ onClick={checkHealth}
79
+ className="flex items-center gap-2 text-sm text-red-400 hover:text-red-300"
80
+ >
81
+ <div className="w-2 h-2 rounded-full bg-red-500" />
82
+ <span>Connection Error</span>
83
+ </button>
84
+ );
85
+ }
86
+
87
+ if (!health) return null;
88
+
89
+ return (
90
+ <div className="relative">
91
+ <button
92
+ onClick={() => setExpanded(!expanded)}
93
+ className="flex items-center gap-2 text-sm hover:opacity-80 transition-opacity"
94
+ >
95
+ <div
96
+ className={`w-2 h-2 rounded-full ${statusColor[health.status]} ${
97
+ loading ? "animate-pulse" : ""
98
+ }`}
99
+ />
100
+ <span
101
+ className={
102
+ health.status === "healthy"
103
+ ? "text-green-400"
104
+ : health.status === "degraded"
105
+ ? "text-yellow-400"
106
+ : "text-red-400"
107
+ }
108
+ >
109
+ {statusIcon[health.status]} Backend{" "}
110
+ {health.status.charAt(0).toUpperCase() + health.status.slice(1)}
111
+ </span>
112
+ </button>
113
+
114
+ {expanded && (
115
+ <div className="absolute right-0 top-full mt-2 w-80 bg-gray-900 border border-white/10 rounded-lg shadow-xl z-50 p-4">
116
+ <div className="flex items-center justify-between mb-3">
117
+ <h3 className="font-semibold">System Health</h3>
118
+ <button
119
+ onClick={checkHealth}
120
+ disabled={loading}
121
+ className="text-xs text-gray-400 hover:text-white disabled:opacity-50"
122
+ >
123
+ {loading ? "Checking..." : "Refresh"}
124
+ </button>
125
+ </div>
126
+
127
+ <div className="space-y-2 text-sm">
128
+ {/* Individual checks */}
129
+ {Object.entries(health.checks).map(([name, check]) => (
130
+ <div
131
+ key={name}
132
+ className="flex items-center justify-between py-1 border-b border-white/5"
133
+ >
134
+ <span className="text-gray-400 capitalize">
135
+ {name.replace(/([A-Z])/g, " $1").trim()}
136
+ </span>
137
+ <span
138
+ className={`flex items-center gap-1 ${
139
+ check.status === "ok"
140
+ ? "text-green-400"
141
+ : check.status === "warning"
142
+ ? "text-yellow-400"
143
+ : "text-red-400"
144
+ }`}
145
+ >
146
+ {check.status === "ok"
147
+ ? "✓"
148
+ : check.status === "warning"
149
+ ? "⚠"
150
+ : "✗"}
151
+ {check.latencyMs !== undefined && (
152
+ <span className="text-gray-500 text-xs ml-1">
153
+ {check.latencyMs}ms
154
+ </span>
155
+ )}
156
+ </span>
157
+ </div>
158
+ ))}
159
+
160
+ {/* Config summary */}
161
+ <div className="pt-2 mt-2 border-t border-white/10">
162
+ <div className="text-xs text-gray-500 mb-1">Configuration</div>
163
+ <div className="flex flex-wrap gap-1">
164
+ <span
165
+ className={`px-2 py-0.5 rounded text-xs ${
166
+ health.config.convexUrl === "configured"
167
+ ? "bg-green-900/30 text-green-400"
168
+ : "bg-red-900/30 text-red-400"
169
+ }`}
170
+ >
171
+ Convex
172
+ </span>
173
+ <span
174
+ className={`px-2 py-0.5 rounded text-xs ${
175
+ health.config.openaiKey === "configured"
176
+ ? "bg-green-900/30 text-green-400"
177
+ : "bg-red-900/30 text-red-400"
178
+ }`}
179
+ >
180
+ OpenAI
181
+ </span>
182
+ <span
183
+ className={`px-2 py-0.5 rounded text-xs ${
184
+ health.config.graphSync === "enabled"
185
+ ? "bg-blue-900/30 text-blue-400"
186
+ : "bg-gray-900/30 text-gray-500"
187
+ }`}
188
+ >
189
+ Graph: {health.config.graphBackend}
190
+ </span>
191
+ </div>
192
+ </div>
193
+
194
+ {/* Errors */}
195
+ {Object.entries(health.checks)
196
+ .filter(([, check]) => check.error)
197
+ .map(([name, check]) => (
198
+ <div
199
+ key={`error-${name}`}
200
+ className="mt-2 p-2 bg-red-900/20 rounded text-xs text-red-400"
201
+ >
202
+ <strong className="capitalize">{name}:</strong> {check.error}
203
+ </div>
204
+ ))}
205
+ </div>
206
+
207
+ <div className="mt-3 pt-2 border-t border-white/10 text-xs text-gray-500">
208
+ Last checked: {new Date(health.timestamp).toLocaleTimeString()}
209
+ </div>
210
+ </div>
211
+ )}
212
+ </div>
213
+ );
214
+ }
@@ -0,0 +1,263 @@
1
+ "use client";
2
+
3
+ import { motion, AnimatePresence } from "framer-motion";
4
+ import { useState } from "react";
5
+ import { DataPreview } from "./DataPreview";
6
+
7
+ type LayerStatus = "pending" | "in_progress" | "complete" | "error" | "skipped";
8
+ type RevisionAction = "ADD" | "UPDATE" | "SUPERSEDE" | "NONE";
9
+
10
+ interface LayerCardProps {
11
+ name: string;
12
+ icon: string;
13
+ status: LayerStatus;
14
+ latencyMs?: number;
15
+ data?: {
16
+ id?: string;
17
+ preview?: string;
18
+ metadata?: Record<string, unknown>;
19
+ };
20
+ compact?: boolean;
21
+ optional?: boolean;
22
+ /** Revision action taken by belief revision system (v0.24.0+) */
23
+ revisionAction?: RevisionAction;
24
+ /** Facts that were superseded (when revisionAction is "SUPERSEDE") */
25
+ supersededFacts?: string[];
26
+ }
27
+
28
+ /**
29
+ * Get badge styling for revision action
30
+ */
31
+ const revisionBadgeConfig: Record<
32
+ RevisionAction,
33
+ { color: string; bgColor: string; label: string }
34
+ > = {
35
+ ADD: {
36
+ color: "text-green-400",
37
+ bgColor: "bg-green-500/20",
38
+ label: "NEW",
39
+ },
40
+ UPDATE: {
41
+ color: "text-blue-400",
42
+ bgColor: "bg-blue-500/20",
43
+ label: "UPDATED",
44
+ },
45
+ SUPERSEDE: {
46
+ color: "text-orange-400",
47
+ bgColor: "bg-orange-500/20",
48
+ label: "SUPERSEDED",
49
+ },
50
+ NONE: {
51
+ color: "text-gray-400",
52
+ bgColor: "bg-gray-500/20",
53
+ label: "UNCHANGED",
54
+ },
55
+ };
56
+
57
+ const statusConfig: Record<
58
+ LayerStatus,
59
+ { color: string; bgColor: string; text: string }
60
+ > = {
61
+ pending: {
62
+ color: "text-gray-400",
63
+ bgColor: "bg-gray-500/20",
64
+ text: "○",
65
+ },
66
+ in_progress: {
67
+ color: "text-yellow-400",
68
+ bgColor: "bg-yellow-500/20",
69
+ text: "◐",
70
+ },
71
+ complete: {
72
+ color: "text-green-400",
73
+ bgColor: "bg-green-500/20",
74
+ text: "●",
75
+ },
76
+ error: {
77
+ color: "text-red-400",
78
+ bgColor: "bg-red-500/20",
79
+ text: "✕",
80
+ },
81
+ skipped: {
82
+ color: "text-gray-600",
83
+ bgColor: "bg-gray-800/50",
84
+ text: "○",
85
+ },
86
+ };
87
+
88
+ export function LayerCard({
89
+ name,
90
+ icon,
91
+ status,
92
+ latencyMs,
93
+ data,
94
+ compact = false,
95
+ optional = false,
96
+ revisionAction,
97
+ supersededFacts,
98
+ }: LayerCardProps) {
99
+ const [isExpanded, setIsExpanded] = useState(false);
100
+ const config = statusConfig[status];
101
+ const revisionConfig = revisionAction
102
+ ? revisionBadgeConfig[revisionAction]
103
+ : null;
104
+
105
+ if (compact) {
106
+ return (
107
+ <motion.div
108
+ className={`px-3 py-2 rounded-lg border border-white/10 ${config.bgColor} min-w-[100px]`}
109
+ animate={{
110
+ scale: status === "in_progress" ? [1, 1.02, 1] : 1,
111
+ boxShadow:
112
+ status === "complete"
113
+ ? [
114
+ "0 0 0 0 rgba(34, 197, 94, 0.4)",
115
+ "0 0 0 4px rgba(34, 197, 94, 0)",
116
+ ]
117
+ : "none",
118
+ }}
119
+ transition={{
120
+ duration: status === "in_progress" ? 1 : 0.5,
121
+ repeat: status === "in_progress" ? Infinity : 0,
122
+ }}
123
+ >
124
+ <div className="flex items-center gap-2">
125
+ <span className={`text-sm ${config.color}`}>{config.text}</span>
126
+ <span>{icon}</span>
127
+ <span className="text-xs font-medium truncate">{name}</span>
128
+ </div>
129
+ <div className="flex items-center justify-center gap-2 mt-0.5">
130
+ {latencyMs !== undefined && (
131
+ <span className="text-[10px] text-gray-500">{latencyMs}ms</span>
132
+ )}
133
+ {/* Revision action badge (v0.24.0+) */}
134
+ {revisionConfig && revisionAction !== "NONE" && (
135
+ <motion.span
136
+ initial={{ scale: 0.8, opacity: 0 }}
137
+ animate={{ scale: 1, opacity: 1 }}
138
+ className={`text-[9px] px-1.5 py-0.5 rounded ${revisionConfig.bgColor} ${revisionConfig.color} font-medium`}
139
+ >
140
+ {revisionConfig.label}
141
+ </motion.span>
142
+ )}
143
+ </div>
144
+ </motion.div>
145
+ );
146
+ }
147
+
148
+ return (
149
+ <motion.div
150
+ className={`rounded-xl border border-white/10 ${config.bgColor} overflow-hidden ${
151
+ optional && status === "skipped" ? "opacity-50" : ""
152
+ }`}
153
+ animate={{
154
+ scale: status === "in_progress" ? [1, 1.01, 1] : 1,
155
+ }}
156
+ transition={{
157
+ duration: 1,
158
+ repeat: status === "in_progress" ? Infinity : 0,
159
+ }}
160
+ layout
161
+ >
162
+ <button
163
+ onClick={() => data && setIsExpanded(!isExpanded)}
164
+ className={`w-full px-4 py-3 flex items-center gap-3 ${
165
+ data ? "cursor-pointer hover:bg-white/5" : "cursor-default"
166
+ } transition-colors`}
167
+ >
168
+ {/* Status indicator */}
169
+ <motion.div
170
+ className={`w-3 h-3 rounded-full flex-shrink-0 ${
171
+ status === "complete"
172
+ ? "bg-green-500"
173
+ : status === "in_progress"
174
+ ? "bg-yellow-500"
175
+ : status === "error"
176
+ ? "bg-red-500"
177
+ : "bg-gray-500"
178
+ }`}
179
+ animate={{
180
+ opacity: status === "in_progress" ? [0.5, 1, 0.5] : 1,
181
+ scale: status === "complete" ? [1, 1.2, 1] : 1,
182
+ }}
183
+ transition={{
184
+ duration: status === "in_progress" ? 1 : 0.3,
185
+ repeat: status === "in_progress" ? Infinity : 0,
186
+ }}
187
+ />
188
+
189
+ {/* Icon and name */}
190
+ <span className="text-lg">{icon}</span>
191
+ <span className="font-medium flex-1 text-left">{name}</span>
192
+
193
+ {/* Latency */}
194
+ {latencyMs !== undefined && (
195
+ <span className="text-sm text-gray-400">{latencyMs}ms</span>
196
+ )}
197
+
198
+ {/* Revision action badge (v0.24.0+) */}
199
+ {revisionConfig && revisionAction !== "NONE" && (
200
+ <motion.span
201
+ initial={{ scale: 0.8, opacity: 0 }}
202
+ animate={{ scale: 1, opacity: 1 }}
203
+ className={`text-[10px] px-1.5 py-0.5 rounded ${revisionConfig.bgColor} ${revisionConfig.color} font-medium`}
204
+ >
205
+ {revisionConfig.label}
206
+ </motion.span>
207
+ )}
208
+
209
+ {/* Optional badge */}
210
+ {optional && (
211
+ <span className="text-[10px] px-1.5 py-0.5 bg-white/10 rounded text-gray-400">
212
+ optional
213
+ </span>
214
+ )}
215
+
216
+ {/* Expand indicator */}
217
+ {data && (
218
+ <motion.span
219
+ className="text-gray-400"
220
+ animate={{ rotate: isExpanded ? 180 : 0 }}
221
+ >
222
+
223
+ </motion.span>
224
+ )}
225
+ </button>
226
+
227
+ {/* Expandable data preview */}
228
+ <AnimatePresence>
229
+ {isExpanded && data && (
230
+ <motion.div
231
+ initial={{ height: 0, opacity: 0 }}
232
+ animate={{ height: "auto", opacity: 1 }}
233
+ exit={{ height: 0, opacity: 0 }}
234
+ transition={{ duration: 0.2 }}
235
+ className="overflow-hidden"
236
+ >
237
+ <div className="px-4 pb-3 pt-1 border-t border-white/10">
238
+ <DataPreview data={data} />
239
+ {/* Show superseded facts when revisionAction is SUPERSEDE */}
240
+ {revisionAction === "SUPERSEDE" &&
241
+ supersededFacts &&
242
+ supersededFacts.length > 0 && (
243
+ <div className="mt-2 pt-2 border-t border-white/5">
244
+ <div className="text-[10px] text-orange-400 font-medium mb-1">
245
+ Superseded Facts:
246
+ </div>
247
+ {supersededFacts.map((fact, i) => (
248
+ <div
249
+ key={i}
250
+ className="text-[10px] text-gray-500 line-through"
251
+ >
252
+ {fact}
253
+ </div>
254
+ ))}
255
+ </div>
256
+ )}
257
+ </div>
258
+ </motion.div>
259
+ )}
260
+ </AnimatePresence>
261
+ </motion.div>
262
+ );
263
+ }