coaia-visualizer 1.4.2 → 1.5.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.
Files changed (47) hide show
  1. package/.dockerignore +9 -0
  2. package/Dockerfile.app +50 -0
  3. package/Dockerfile.test +24 -0
  4. package/LIVE_MODE_DESIGN.md +435 -0
  5. package/MCP_TESTING_COMPLETE.md +302 -0
  6. package/MCP_TESTING_IMPLEMENTATION_SUMMARY.md +317 -0
  7. package/MCP_TESTING_SETUP.md +268 -0
  8. package/NAMING.md +218 -0
  9. package/QUICK_START_MCP_TESTING.md +236 -0
  10. package/WS__issue_8__coaia-visualizer__260207.code-workspace +45 -0
  11. package/app/api/audio/[filename]/route.ts +37 -0
  12. package/app/api/charts/[id]/route.ts +48 -35
  13. package/app/api/watch/route.ts +42 -0
  14. package/app/page.tsx +103 -53
  15. package/cli.ts +56 -3
  16. package/components/add-master-chart.tsx +230 -0
  17. package/components/chart-detail-editable.tsx +27 -16
  18. package/components/chart-list.tsx +13 -1
  19. package/components/create-chart-form.tsx +248 -0
  20. package/components/data-stats.tsx +9 -7
  21. package/components/live-indicator.tsx +14 -0
  22. package/components/ui/dialog.tsx +143 -0
  23. package/components/ui/label.tsx +24 -0
  24. package/direct-test.sh +180 -0
  25. package/dist/cli.js +52 -3
  26. package/docker-compose.test.yml +69 -0
  27. package/hooks/use-live-polling.ts +45 -0
  28. package/jgwill.coaia-visualizer-8--496dca71-d476-4ac9-ba9f-376add118dd8--260208.txt +2612 -0
  29. package/lib/chart-editor.ts +281 -68
  30. package/mcp/Dockerfile +21 -0
  31. package/mcp/README.md +25 -6
  32. package/mcp/src/api-client.ts +15 -3
  33. package/mcp/src/index.ts +17 -2
  34. package/mcp/src/tools/index.ts +21 -1
  35. package/mcp/test_mcp/.gemini/settings.json +18 -0
  36. package/mcp-config.json +14 -0
  37. package/package.json +2 -2
  38. package/run-mcp-tests.sh +99 -0
  39. package/samples/tradingchart.jsonl +31 -0
  40. package/test-data/test-master.jsonl +11 -0
  41. package/test-run.log +101 -0
  42. package/test-scripts/README.md +239 -0
  43. package/test-scripts/run-all-tests.sh +38 -0
  44. package/test-scripts/test-01-basic-operations.sh +87 -0
  45. package/test-scripts/test-02-telescope-creation.sh +91 -0
  46. package/test-scripts/test-03-navigation.sh +87 -0
  47. package/validate-mcp.sh +136 -0
package/cli.ts CHANGED
@@ -21,13 +21,21 @@ interface Config {
21
21
  memoryPath: string;
22
22
  port: number;
23
23
  noOpen: boolean;
24
+ live: boolean;
25
+ pollInterval: number;
26
+ autoPlay: boolean;
27
+ audioDir: string;
24
28
  }
25
29
 
26
30
  function loadConfig(args: minimist.ParsedArgs): Config {
27
31
  let config: Config = {
28
32
  memoryPath: path.join(process.cwd(), 'memory.jsonl'),
29
33
  port: 3000,
30
- noOpen: false
34
+ noOpen: false,
35
+ live: false,
36
+ pollInterval: 2000,
37
+ autoPlay: false,
38
+ audioDir: path.join(process.cwd(), 'audio')
31
39
  };
32
40
 
33
41
  // Load .env files
@@ -50,6 +58,18 @@ function loadConfig(args: minimist.ParsedArgs): Config {
50
58
  if (process.env.COAIAV_PORT) {
51
59
  config.port = parseInt(process.env.COAIAV_PORT, 10);
52
60
  }
61
+ if (process.env.COAIAV_LIVE === 'true') {
62
+ config.live = true;
63
+ }
64
+ if (process.env.COAIAV_POLL_INTERVAL) {
65
+ config.pollInterval = parseInt(process.env.COAIAV_POLL_INTERVAL, 10);
66
+ }
67
+ if (process.env.COAIAV_AUTO_PLAY === 'true') {
68
+ config.autoPlay = true;
69
+ }
70
+ if (process.env.COAIAV_AUDIO_DIR) {
71
+ config.audioDir = process.env.COAIAV_AUDIO_DIR;
72
+ }
53
73
 
54
74
  // Command-line flags override everything
55
75
  if (args['memory-path'] || args['M']) {
@@ -61,6 +81,18 @@ function loadConfig(args: minimist.ParsedArgs): Config {
61
81
  if (args['no-open']) {
62
82
  config.noOpen = true;
63
83
  }
84
+ if (args['live']) {
85
+ config.live = true;
86
+ }
87
+ if (args['poll-interval']) {
88
+ config.pollInterval = parseInt(args['poll-interval'], 10);
89
+ }
90
+ if (args['auto-play']) {
91
+ config.autoPlay = true;
92
+ }
93
+ if (args['audio-dir']) {
94
+ config.audioDir = args['audio-dir'];
95
+ }
64
96
 
65
97
  return config;
66
98
  }
@@ -83,12 +115,20 @@ USAGE:
83
115
  OPTIONS:
84
116
  --memory-path PATH, -M PATH Path to memory.jsonl file (default: ./memory.jsonl)
85
117
  --port PORT, -p PORT Server port (default: 3000)
118
+ --live Enable live monitoring mode
119
+ --poll-interval MS Polling interval in ms (default: 2000)
120
+ --auto-play Auto-play audio for new beats
121
+ --audio-dir PATH Audio directory path (default: ./audio)
86
122
  --no-open Don't auto-open browser
87
123
  --help, -h Show this help message
88
124
 
89
125
  ENVIRONMENT VARIABLES:
90
126
  COAIAN_MF Default memory file path
91
127
  COAIAV_PORT Default server port
128
+ COAIAV_LIVE Enable live mode (true/false)
129
+ COAIAV_POLL_INTERVAL Polling interval in ms
130
+ COAIAV_AUTO_PLAY Enable auto-play (true/false)
131
+ COAIAV_AUDIO_DIR Audio directory path
92
132
 
93
133
  EXAMPLES:
94
134
  # Launch with default memory.jsonl
@@ -117,14 +157,23 @@ EXAMPLES:
117
157
  process.exit(1);
118
158
  }
119
159
 
120
- console.log(`🎨 COAIA Visualizer`);
160
+ console.log(`🎨 COAIA Visualizer${config.live ? ' [LIVE MODE]' : ''}`);
121
161
  console.log(`📁 Memory file: ${config.memoryPath}`);
122
162
  console.log(`🌐 Port: ${config.port}`);
163
+ if (config.live) {
164
+ console.log(`🔄 Polling: ${config.pollInterval}ms`);
165
+ console.log(`🎵 Audio: ${config.audioDir}`);
166
+ console.log(`🔊 Auto-play: ${config.autoPlay ? 'enabled' : 'disabled'}`);
167
+ }
123
168
  console.log();
124
169
 
125
170
  // Set environment variables for Next.js
126
171
  process.env.COAIAV_MEMORY_PATH = path.resolve(config.memoryPath);
127
172
  process.env.PORT = config.port.toString();
173
+ process.env.NEXT_PUBLIC_LIVE_MODE = config.live.toString();
174
+ process.env.NEXT_PUBLIC_POLL_INTERVAL = config.pollInterval.toString();
175
+ process.env.NEXT_PUBLIC_AUTO_PLAY = config.autoPlay.toString();
176
+ process.env.COAIAV_AUDIO_DIR = path.resolve(config.audioDir);
128
177
 
129
178
  // Navigate to visualizer root
130
179
  const visualizerRoot = path.resolve(__dirname, '..');
@@ -136,7 +185,11 @@ EXAMPLES:
136
185
  env: {
137
186
  ...process.env,
138
187
  COAIAV_MEMORY_PATH: path.resolve(config.memoryPath),
139
- PORT: config.port.toString()
188
+ PORT: config.port.toString(),
189
+ NEXT_PUBLIC_LIVE_MODE: config.live.toString(),
190
+ NEXT_PUBLIC_POLL_INTERVAL: config.pollInterval.toString(),
191
+ NEXT_PUBLIC_AUTO_PLAY: config.autoPlay.toString(),
192
+ COAIAV_AUDIO_DIR: path.resolve(config.audioDir)
140
193
  }
141
194
  });
142
195
 
@@ -0,0 +1,230 @@
1
+ "use client"
2
+
3
+ import type React from "react"
4
+
5
+ import { useState } from "react"
6
+ import { Button } from "@/components/ui/button"
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ DialogTrigger,
14
+ } from "@/components/ui/dialog"
15
+ import { Input } from "@/components/ui/input"
16
+ import { Label } from "@/components/ui/label"
17
+ import { Textarea } from "@/components/ui/textarea"
18
+ import { Plus, X } from "lucide-react"
19
+ import type { ParsedData, EntityRecord, RelationRecord } from "@/lib/types"
20
+
21
+ interface AddMasterChartProps {
22
+ data: ParsedData
23
+ onChartAdded: (updatedData: ParsedData) => void
24
+ }
25
+
26
+ export function AddMasterChart({ data, onChartAdded }: AddMasterChartProps) {
27
+ const [open, setOpen] = useState(false)
28
+ const [desiredOutcome, setDesiredOutcome] = useState("")
29
+ const [currentReality, setCurrentReality] = useState<string[]>([""])
30
+ const [dueDate, setDueDate] = useState("")
31
+
32
+ const handleAddObservation = () => {
33
+ setCurrentReality([...currentReality, ""])
34
+ }
35
+
36
+ const handleRemoveObservation = (index: number) => {
37
+ setCurrentReality(currentReality.filter((_, i) => i !== index))
38
+ }
39
+
40
+ const handleObservationChange = (index: number, value: string) => {
41
+ const updated = [...currentReality]
42
+ updated[index] = value
43
+ setCurrentReality(updated)
44
+ }
45
+
46
+ const handleSubmit = (e: React.FormEvent) => {
47
+ e.preventDefault()
48
+
49
+ if (!desiredOutcome.trim()) {
50
+ return
51
+ }
52
+
53
+ const filteredReality = currentReality.filter((obs) => obs.trim())
54
+ if (filteredReality.length === 0) {
55
+ return
56
+ }
57
+
58
+ // Generate new chart ID
59
+ const existingCharts = Array.from(data.entities.values()).filter((e) => e.entityType === "structural_tension_chart")
60
+ const maxId = existingCharts.reduce((max, e) => {
61
+ const id = Number.parseInt(e.metadata?.chartId?.replace("chart_", "") || "0")
62
+ return Math.max(max, id)
63
+ }, 0)
64
+
65
+ const newChartId = `chart_${maxId + 1}`
66
+ const timestamp = new Date().toISOString()
67
+
68
+ // Clone existing data
69
+ const newEntities = new Map(data.entities)
70
+ const newRelations = [...data.relations]
71
+
72
+ const chartEntity: EntityRecord = {
73
+ type: "entity",
74
+ name: newChartId,
75
+ entityType: "structural_tension_chart",
76
+ observations: [`Master chart: ${desiredOutcome}`],
77
+ metadata: {
78
+ chartId: newChartId,
79
+ level: 0,
80
+ completionStatus: false,
81
+ createdAt: timestamp,
82
+ updatedAt: timestamp,
83
+ ...(dueDate && { dueDate: new Date(dueDate).toISOString() }),
84
+ },
85
+ }
86
+ newEntities.set(newChartId, chartEntity)
87
+
88
+ const desiredOutcomeName = `${newChartId}_desired_outcome`
89
+ const desiredOutcomeEntity: EntityRecord = {
90
+ type: "entity",
91
+ name: desiredOutcomeName,
92
+ entityType: "desired_outcome",
93
+ observations: [desiredOutcome.trim()],
94
+ metadata: {
95
+ chartId: newChartId,
96
+ createdAt: timestamp,
97
+ updatedAt: timestamp,
98
+ },
99
+ }
100
+ newEntities.set(desiredOutcomeName, desiredOutcomeEntity)
101
+
102
+ const currentRealityName = `${newChartId}_current_reality`
103
+ const currentRealityEntity: EntityRecord = {
104
+ type: "entity",
105
+ name: currentRealityName,
106
+ entityType: "current_reality",
107
+ observations: filteredReality,
108
+ metadata: {
109
+ chartId: newChartId,
110
+ createdAt: timestamp,
111
+ updatedAt: timestamp,
112
+ },
113
+ }
114
+ newEntities.set(currentRealityName, currentRealityEntity)
115
+
116
+ const containsDesiredOutcome: RelationRecord = {
117
+ type: "relation",
118
+ from: newChartId,
119
+ to: desiredOutcomeName,
120
+ relationType: "contains",
121
+ metadata: { createdAt: timestamp },
122
+ }
123
+ newRelations.push(containsDesiredOutcome)
124
+
125
+ const containsCurrentReality: RelationRecord = {
126
+ type: "relation",
127
+ from: newChartId,
128
+ to: currentRealityName,
129
+ relationType: "contains",
130
+ metadata: { createdAt: timestamp },
131
+ }
132
+ newRelations.push(containsCurrentReality)
133
+
134
+ const tensionRelation: RelationRecord = {
135
+ type: "relation",
136
+ from: currentRealityName,
137
+ to: desiredOutcomeName,
138
+ relationType: "creates_tension_with",
139
+ metadata: { createdAt: timestamp },
140
+ }
141
+ newRelations.push(tensionRelation)
142
+
143
+ // Re-organize data to update chart hierarchy
144
+ const { organizeData } = require("@/lib/jsonl-parser")
145
+ const records = [...Array.from(newEntities.values()), ...newRelations]
146
+ const updatedData = organizeData(records)
147
+
148
+ onChartAdded(updatedData)
149
+
150
+ // Reset form
151
+ setDesiredOutcome("")
152
+ setCurrentReality([""])
153
+ setDueDate("")
154
+ setOpen(false)
155
+ }
156
+
157
+ return (
158
+ <Dialog open={open} onOpenChange={setOpen}>
159
+ <DialogTrigger asChild>
160
+ <Button variant="outline" size="sm" className="w-full bg-transparent">
161
+ <Plus className="w-4 h-4 mr-2" />
162
+ Add Master Chart
163
+ </Button>
164
+ </DialogTrigger>
165
+ <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
166
+ <DialogHeader>
167
+ <DialogTitle>Add New Master Chart</DialogTitle>
168
+ <DialogDescription>Create a new top-level structural tension chart</DialogDescription>
169
+ </DialogHeader>
170
+ <form onSubmit={handleSubmit} className="space-y-6">
171
+ <div className="space-y-2">
172
+ <Label htmlFor="desired-outcome">Desired Outcome *</Label>
173
+ <Textarea
174
+ id="desired-outcome"
175
+ placeholder="What do you want to create or achieve?"
176
+ value={desiredOutcome}
177
+ onChange={(e) => setDesiredOutcome(e.target.value)}
178
+ required
179
+ rows={3}
180
+ />
181
+ </div>
182
+
183
+ <div className="space-y-3">
184
+ <div className="flex items-center justify-between">
185
+ <Label>Current Reality Observations *</Label>
186
+ <Button type="button" variant="ghost" size="sm" onClick={handleAddObservation}>
187
+ <Plus className="w-4 h-4 mr-1" />
188
+ Add Observation
189
+ </Button>
190
+ </div>
191
+ {currentReality.map((obs, index) => (
192
+ <div key={index} className="flex gap-2">
193
+ <Textarea
194
+ placeholder={`Observation ${index + 1}: What's actually true right now?`}
195
+ value={obs}
196
+ onChange={(e) => handleObservationChange(index, e.target.value)}
197
+ rows={2}
198
+ className="flex-1"
199
+ />
200
+ {currentReality.length > 1 && (
201
+ <Button
202
+ type="button"
203
+ variant="ghost"
204
+ size="sm"
205
+ onClick={() => handleRemoveObservation(index)}
206
+ className="flex-shrink-0"
207
+ >
208
+ <X className="w-4 h-4" />
209
+ </Button>
210
+ )}
211
+ </div>
212
+ ))}
213
+ </div>
214
+
215
+ <div className="space-y-2">
216
+ <Label htmlFor="due-date">Due Date (Optional)</Label>
217
+ <Input id="due-date" type="date" value={dueDate} onChange={(e) => setDueDate(e.target.value)} />
218
+ </div>
219
+
220
+ <div className="flex gap-3 justify-end">
221
+ <Button type="button" variant="outline" onClick={() => setOpen(false)}>
222
+ Cancel
223
+ </Button>
224
+ <Button type="submit">Create Master Chart</Button>
225
+ </div>
226
+ </form>
227
+ </DialogContent>
228
+ </Dialog>
229
+ )
230
+ }
@@ -41,6 +41,30 @@ export function ChartDetailEditable({
41
41
  onUpdate(updatedData)
42
42
  }
43
43
 
44
+ const handleCreateTelescopedChart = (actionName: string) => {
45
+ console.log("[v0] Creating telescoped chart from action:", actionName)
46
+ const editor = new ChartEditor(data)
47
+
48
+ try {
49
+ const newChartId = editor.createTelescopedChartFromAction(chart.chartEntity.name, actionName)
50
+ console.log("[v0] Created new chart:", newChartId)
51
+
52
+ const updatedData = editor.getUpdatedData()
53
+ onUpdate(updatedData)
54
+
55
+ // Navigate to the newly created chart
56
+ if (onNavigateToSubChart) {
57
+ const newChart = updatedData.charts.find((c) => c.id === newChartId)
58
+ if (newChart) {
59
+ console.log("[v0] Navigating to new chart:", newChartId)
60
+ setTimeout(() => onNavigateToSubChart(newChart), 150)
61
+ }
62
+ }
63
+ } catch (error) {
64
+ console.error("[v0] Error creating telescoped chart:", error)
65
+ }
66
+ }
67
+
44
68
  return (
45
69
  <div className="space-y-6">
46
70
  {/* Back button for sub-charts */}
@@ -157,7 +181,7 @@ export function ChartDetailEditable({
157
181
  <CardContent className="space-y-3">
158
182
  {chart.actions.map((action, idx) => {
159
183
  const subChart = action.metadata?.isTelescopedChart
160
- ? chart.subCharts.find((sc) => sc.desiredOutcome?.name === action.name)
184
+ ? chart.subCharts.find((sc) => sc.desiredOutcome?.observations[0] === action.observations[0])
161
185
  : undefined
162
186
 
163
187
  return (
@@ -179,27 +203,14 @@ export function ChartDetailEditable({
179
203
  onNavigateToChart={
180
204
  subChart && onNavigateToSubChart ? () => onNavigateToSubChart(subChart) : undefined
181
205
  }
182
- onCreateTelescopedChart={() => {
183
- applyEdit((editor) => editor.createTelescopedChart(chart.id, action.name))
184
- // After creating, find and navigate to the new subchart
185
- const editor = new ChartEditor(data)
186
- editor.createTelescopedChart(chart.id, action.name)
187
- const updatedData = editor.getUpdatedData()
188
- const newSubChart = updatedData.charts
189
- .find((c) => c.id === chart.id)
190
- ?.subCharts.find((sc) => sc.desiredOutcome?.name === action.name)
191
- if (newSubChart && onNavigateToSubChart) {
192
- onUpdate(updatedData)
193
- setTimeout(() => onNavigateToSubChart(newSubChart), 100)
194
- }
195
- }}
206
+ onCreateTelescopedChart={() => handleCreateTelescopedChart(action.name)}
196
207
  />
197
208
  )
198
209
  })}
199
210
 
200
211
  <AddActionStep
201
212
  onAdd={(description, currentReality, dueDate) =>
202
- applyEdit((editor) => editor.addActionStep(chart.id, description, currentReality, dueDate))
213
+ applyEdit((editor) => editor.addActionStep(chart.chartEntity.name, description, currentReality, dueDate))
203
214
  }
204
215
  />
205
216
  </CardContent>
@@ -8,15 +8,17 @@ import { getChartSummary, getChartProgress } from "@/lib/jsonl-parser"
8
8
  import { ChevronRight, ChevronDown, Target, Calendar, BookOpen } from "lucide-react"
9
9
  import { useState } from "react"
10
10
  import { cn } from "@/lib/utils"
11
+ import { AddMasterChart } from "@/components/add-master-chart"
11
12
 
12
13
  interface ChartListProps {
13
14
  data: ParsedData
14
15
  selectedChart: Chart | null
15
16
  onSelectChart: (chart: Chart) => void
16
17
  mode: "hierarchy" | "list"
18
+ onDataUpdate?: (updatedData: ParsedData) => void
17
19
  }
18
20
 
19
- export function ChartList({ data, selectedChart, onSelectChart, mode }: ChartListProps) {
21
+ export function ChartList({ data, selectedChart, onSelectChart, mode, onDataUpdate }: ChartListProps) {
20
22
  if (mode === "list") {
21
23
  return (
22
24
  <Card>
@@ -25,6 +27,11 @@ export function ChartList({ data, selectedChart, onSelectChart, mode }: ChartLis
25
27
  <CardDescription className="text-xs md:text-sm">{data.charts.length} total charts</CardDescription>
26
28
  </CardHeader>
27
29
  <CardContent>
30
+ {onDataUpdate && (
31
+ <div className="mb-4">
32
+ <AddMasterChart data={data} onChartAdded={onDataUpdate} />
33
+ </div>
34
+ )}
28
35
  <ScrollArea className="h-[400px] md:h-[600px] pr-2 md:pr-4">
29
36
  <div className="space-y-2">
30
37
  {data.charts.map((chart) => (
@@ -50,6 +57,11 @@ export function ChartList({ data, selectedChart, onSelectChart, mode }: ChartLis
50
57
  <CardDescription className="text-xs md:text-sm">{data.rootCharts.length} root charts</CardDescription>
51
58
  </CardHeader>
52
59
  <CardContent>
60
+ {onDataUpdate && (
61
+ <div className="mb-4">
62
+ <AddMasterChart data={data} onChartAdded={onDataUpdate} />
63
+ </div>
64
+ )}
53
65
  <ScrollArea className="h-[400px] md:h-[600px] pr-2 md:pr-4">
54
66
  <div className="space-y-2">
55
67
  {data.rootCharts.map((chart) => (