spine-framework-cortex 0.1.19 → 0.2.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 (46) hide show
  1. package/{functions/custom_cortex-handler.ts → api/cortex-handler.ts} +9 -9
  2. package/components/CliInstancesCard.tsx +144 -0
  3. package/components/CortexSidebar.tsx +27 -53
  4. package/hooks/useTypeRegistry.ts +74 -0
  5. package/index.tsx +13 -24
  6. package/manifest.json +1 -13
  7. package/package.json +11 -20
  8. package/pages/courses/CoursesPage.tsx +14 -4
  9. package/pages/crm/AccountDetailPage.tsx +149 -194
  10. package/pages/crm/ContactsPage.tsx +7 -7
  11. package/pages/intelligence/IntelligencePage.tsx +24 -31
  12. package/pages/kb/KBEditorPage.tsx +9 -2
  13. package/pages/operations/AuditFunnelPage.tsx +378 -0
  14. package/pages/operations/InstallFunnelPage.tsx +410 -0
  15. package/pages/operations/OperationsDashboard.tsx +275 -0
  16. package/pages/support/RedactionReview.tsx +11 -2
  17. package/seed/link-types.json +8 -42
  18. package/seed/package.json +27 -0
  19. package/seed/roles.json +1 -1
  20. package/seed/types.json +2711 -596
  21. package/CHANGELOG.md +0 -46
  22. package/LICENSE.md +0 -223
  23. package/README.md +0 -69
  24. package/functions/custom_anonymous-sessions.ts +0 -356
  25. package/functions/custom_case_analysis.ts +0 -507
  26. package/functions/custom_community-escalation.ts +0 -234
  27. package/functions/custom_cortex-chunks.ts +0 -52
  28. package/functions/custom_funnel-scoring.ts +0 -256
  29. package/functions/custom_funnel-signal.ts +0 -446
  30. package/functions/custom_funnel-timers.ts +0 -449
  31. package/functions/custom_kb-chunker-test.ts +0 -364
  32. package/functions/custom_kb-chunker.ts +0 -576
  33. package/functions/custom_kb-embeddings.ts +0 -481
  34. package/functions/custom_kb-ingestion.ts +0 -448
  35. package/functions/custom_support-triage.ts +0 -649
  36. package/functions/custom_tag_management.ts +0 -314
  37. package/functions/webhook-handlers.ts +0 -29
  38. package/lib/resolveTypeId.ts +0 -16
  39. package/pages/crm/ContactDetailPage.tsx +0 -184
  40. package/pages/ops/AuditFunnelPage.tsx +0 -191
  41. package/pages/ops/CommandCenterPage.tsx +0 -377
  42. package/pages/ops/InstallFunnelPage.tsx +0 -226
  43. package/seed/accounts.json +0 -9
  44. package/seed/integrations.json +0 -24
  45. package/seed/pipelines.json +0 -59
  46. package/seed/triggers.json +0 -125
@@ -1,226 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { useNavigate } from 'react-router-dom'
3
- import { apiFetch } from '@core/lib/api'
4
- import { Badge } from '@core/components/ui/badge'
5
- import { Button } from '@core/components/ui/button'
6
- import { Input } from '@core/components/ui/input'
7
- import { Skeleton } from '@core/components/ui/skeleton'
8
- import { Download, Search, ChevronRight } from 'lucide-react'
9
-
10
- type FilterTab = 'all' | 'unclaimed' | 'claimed' | 'hot'
11
-
12
- interface InstallAccount {
13
- id: string
14
- slug: string
15
- display_name?: string
16
- created_at: string
17
- data?: {
18
- temperature?: 'cold' | 'warm' | 'hot'
19
- lifecycle_stage?: string
20
- lead_score?: number
21
- last_signal_at?: string
22
- claim_status?: string
23
- instance_id?: string
24
- ratings?: Record<string, { rating: number; raw_score: number; calculated_at?: string }>
25
- }
26
- }
27
-
28
- function scoreBar(rating: number) {
29
- const val = Math.round((rating / 5) * 100)
30
- const color = val >= 80 ? 'bg-green-500' : val >= 50 ? 'bg-yellow-500' : 'bg-gray-300'
31
- return (
32
- <div className="flex items-center gap-2">
33
- <div className="w-24 h-1.5 bg-muted rounded-full overflow-hidden">
34
- <div className={`h-full rounded-full ${color}`} style={{ width: `${val}%` }} />
35
- </div>
36
- <span className="text-xs font-medium tabular-nums">{val}</span>
37
- </div>
38
- )
39
- }
40
-
41
- function claimBadge(status?: string) {
42
- if (status === 'claimed') return <Badge className="text-xs bg-green-100 text-green-700 border-green-200">Claimed</Badge>
43
- return <Badge variant="outline" className="text-xs">Unclaimed</Badge>
44
- }
45
-
46
- function stageBadge(stage?: string) {
47
- return <Badge variant="secondary" className="text-xs capitalize">{stage || '—'}</Badge>
48
- }
49
-
50
- function maskInstanceId(id?: string): string {
51
- if (!id) return '—'
52
- if (id.length <= 12) return id
53
- return id.slice(0, 10) + '…'
54
- }
55
-
56
- function daysSince(dateStr?: string): number | null {
57
- if (!dateStr) return null
58
- return Math.floor((Date.now() - new Date(dateStr).getTime()) / (1000 * 60 * 60 * 24))
59
- }
60
-
61
- export default function InstallFunnelPage() {
62
- const navigate = useNavigate()
63
- const [accounts, setAccounts] = useState<InstallAccount[]>([])
64
- const [loading, setLoading] = useState(true)
65
- const [search, setSearch] = useState('')
66
- const [tab, setTab] = useState<FilterTab>('all')
67
-
68
- useEffect(() => {
69
- apiFetch('/api/admin-data?action=list&entity=accounts&limit=500')
70
- .then(r => r.json())
71
- .then(json => {
72
- const all: InstallAccount[] = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : []
73
- // Keep accounts in the technical funnel
74
- const techAccounts = all.filter(a =>
75
- a.data?.ratings?.['funnel-technical'] ||
76
- a.data?.ratings?.['funnel-technical-installed'] ||
77
- a.data?.ratings?.['funnel-technical-claimed'] ||
78
- a.data?.lifecycle_stage === 'installed'
79
- )
80
- // Sort by technical rating desc, then by first signal date
81
- techAccounts.sort((a, b) => {
82
- const ra = a.data?.ratings?.['funnel-technical']?.rating || a.data?.ratings?.installed?.rating || 0
83
- const rb = b.data?.ratings?.['funnel-technical']?.rating || b.data?.ratings?.installed?.rating || 0
84
- if (rb !== ra) return rb - ra
85
- return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
86
- })
87
- setAccounts(techAccounts)
88
- })
89
- .catch(() => setAccounts([]))
90
- .finally(() => setLoading(false))
91
- }, [])
92
-
93
- const tabFiltered = accounts.filter(a => {
94
- if (tab === 'unclaimed') return a.data?.claim_status !== 'claimed'
95
- if (tab === 'claimed') return a.data?.claim_status === 'claimed'
96
- if (tab === 'hot') return a.data?.temperature === 'hot'
97
- return true
98
- })
99
-
100
- const filtered = tabFiltered.filter(a => {
101
- const q = search.toLowerCase()
102
- return !q || (a.display_name || a.slug || a.data?.instance_id || '').toLowerCase().includes(q)
103
- })
104
-
105
- const counts = {
106
- all: accounts.length,
107
- unclaimed: accounts.filter(a => a.data?.claim_status !== 'claimed').length,
108
- claimed: accounts.filter(a => a.data?.claim_status === 'claimed').length,
109
- hot: accounts.filter(a => a.data?.temperature === 'hot').length,
110
- }
111
-
112
- return (
113
- <div className="p-6 space-y-6">
114
- {/* Header */}
115
- <div className="flex items-center justify-between">
116
- <div>
117
- <div className="flex items-center gap-2">
118
- <Download className="h-5 w-5 text-muted-foreground" />
119
- <h1 className="text-2xl font-bold">Install Funnel</h1>
120
- </div>
121
- <p className="text-sm text-muted-foreground mt-0.5">
122
- Developer accounts progressing from npm install through claim — your technical pipeline.
123
- </p>
124
- </div>
125
- <Button size="sm" onClick={() => navigate('/cortex/ops/command-center')}>
126
- Command Center <ChevronRight className="h-3.5 w-3.5 ml-1" />
127
- </Button>
128
- </div>
129
-
130
- {/* Summary stats */}
131
- <div className="grid grid-cols-4 gap-4">
132
- {[
133
- { label: 'Total Installs', value: loading ? '…' : accounts.length },
134
- { label: 'Claimed', value: loading ? '…' : counts.claimed },
135
- { label: 'Unclaimed', value: loading ? '…' : counts.unclaimed },
136
- { label: 'Hot', value: loading ? '…' : counts.hot },
137
- ].map(s => (
138
- <div key={s.label} className="border rounded-lg p-4">
139
- <div className="text-xs text-muted-foreground mb-1">{s.label}</div>
140
- {loading ? <Skeleton className="h-7 w-12" /> : <div className="text-2xl font-bold">{s.value}</div>}
141
- </div>
142
- ))}
143
- </div>
144
-
145
- {/* Table */}
146
- <div className="border rounded-lg">
147
- <div className="flex items-center justify-between px-4 py-3 border-b">
148
- {/* Filter tabs */}
149
- <div className="flex gap-0.5">
150
- {(['all', 'unclaimed', 'claimed', 'hot'] as FilterTab[]).map(t => (
151
- <button
152
- key={t}
153
- onClick={() => setTab(t)}
154
- className={`px-3 py-1 text-xs rounded-md capitalize transition-colors ${
155
- tab === t ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'
156
- }`}
157
- >
158
- {t} ({counts[t]})
159
- </button>
160
- ))}
161
- </div>
162
- <div className="relative w-56">
163
- <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
164
- <Input
165
- className="h-7 pl-8 text-xs"
166
- placeholder="Search installs…"
167
- value={search}
168
- onChange={e => setSearch(e.target.value)}
169
- />
170
- </div>
171
- </div>
172
-
173
- {loading ? (
174
- <div className="p-4 space-y-3">
175
- {Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-10 w-full" />)}
176
- </div>
177
- ) : filtered.length === 0 ? (
178
- <div className="p-10 text-center text-sm text-muted-foreground">
179
- <Download className="h-8 w-8 mx-auto mb-2 opacity-20" />
180
- {search ? 'No installs match your search.' : 'No technical funnel activity yet. npm install signals will appear here.'}
181
- </div>
182
- ) : (
183
- <>
184
- <div className="grid grid-cols-[2fr_1fr_1fr_1fr_1fr_1fr] gap-4 px-4 py-2 text-xs text-muted-foreground font-medium border-b bg-muted/30">
185
- <span>Instance / Account</span>
186
- <span>Signal Score</span>
187
- <span>Stage</span>
188
- <span>Last Action</span>
189
- <span>Claim Status</span>
190
- <span>Days Active</span>
191
- </div>
192
- <div className="divide-y">
193
- {filtered.map(a => {
194
- const rating = a.data?.ratings?.['funnel-technical']?.rating
195
- || a.data?.ratings?.['funnel-technical-claimed']?.rating
196
- || a.data?.ratings?.installed?.rating
197
- || 0
198
- const days = daysSince(a.created_at)
199
- const instanceId = a.data?.instance_id || a.slug
200
- return (
201
- <div
202
- key={a.id}
203
- className="grid grid-cols-[2fr_1fr_1fr_1fr_1fr_1fr] gap-4 px-4 py-3 items-center hover:bg-accent/50 cursor-pointer transition-colors text-sm"
204
- onClick={() => navigate(`/cortex/crm/accounts/${a.id}`)}
205
- >
206
- <div className="min-w-0">
207
- <div className="font-medium font-mono text-xs truncate">{maskInstanceId(instanceId)}</div>
208
- {a.display_name && <div className="text-xs text-muted-foreground truncate">{a.display_name}</div>}
209
- </div>
210
- <div>{scoreBar(rating)}</div>
211
- <div>{stageBadge(a.data?.lifecycle_stage)}</div>
212
- <div className="text-xs text-muted-foreground">
213
- {a.data?.last_signal_at ? new Date(a.data.last_signal_at).toLocaleDateString() : '—'}
214
- </div>
215
- <div>{claimBadge(a.data?.claim_status)}</div>
216
- <div className="text-xs text-muted-foreground">{days !== null ? `${days}d` : '—'}</div>
217
- </div>
218
- )
219
- })}
220
- </div>
221
- </>
222
- )}
223
- </div>
224
- </div>
225
- )
226
- }
@@ -1,9 +0,0 @@
1
- [
2
- {
3
- "slug": "unidentified-visitors",
4
- "display_name": "Unidentified Visitors",
5
- "description": "Container account for anonymous funnel signals and sessions before identity stitching",
6
- "type_slug": "account",
7
- "is_active": true
8
- }
9
- ]
@@ -1,24 +0,0 @@
1
- [
2
- {
3
- "name": "Funnel Signal: Marketing",
4
- "slug": "funnel-signal-mar",
5
- "provider": "webhook",
6
- "status": "active",
7
- "is_active": true,
8
- "config": {
9
- "handler": { "path": "funnel-signal" },
10
- "description": "Marketing signal ingest via integration-routes"
11
- }
12
- },
13
- {
14
- "name": "Funnel Signal: Usage",
15
- "slug": "funnel-signal-use",
16
- "provider": "webhook",
17
- "status": "active",
18
- "is_active": true,
19
- "config": {
20
- "handler": { "path": "funnel-signal" },
21
- "description": "Usage signal ingest via integration-routes"
22
- }
23
- }
24
- ]
@@ -1,59 +0,0 @@
1
- [
2
- {
3
- "slug": "case-analysis",
4
- "name": "Case Analysis Pipeline",
5
- "description": "Automatically analyzes resolved support tickets to extract insights and create tags",
6
- "stages": [
7
- {
8
- "stage_type": "agent_inference",
9
- "config": {
10
- "function": "case_analysis.analyze"
11
- }
12
- },
13
- {
14
- "stage_type": "agent_inference",
15
- "config": {
16
- "function": "tag_management.generate"
17
- }
18
- }
19
- ],
20
- "config": {
21
- "debounce_ms": 5000,
22
- "retry_count": 3,
23
- "retry_delay_ms": 1000
24
- },
25
- "ownership": "tenant",
26
- "is_system": false,
27
- "is_active": true
28
- },
29
- {
30
- "slug": "funnel-general",
31
- "name": "Funnel: General",
32
- "description": "Default funnel pipeline — fires for all signals without a specific pipeline_slug. Add stages to notify, create deals, or trigger outreach.",
33
- "stages": [],
34
- "config": {},
35
- "ownership": "tenant",
36
- "is_system": false,
37
- "is_active": true
38
- },
39
- {
40
- "slug": "funnel-ai-audit",
41
- "name": "Funnel: AI Audit",
42
- "description": "Pipeline for the AI Audit funnel. Fires when a signal arrives with pipeline_slug='funnel-ai-audit'. Add stages to run your sales sequence.",
43
- "stages": [],
44
- "config": {},
45
- "ownership": "tenant",
46
- "is_system": false,
47
- "is_active": true
48
- },
49
- {
50
- "slug": "funnel-implementation",
51
- "name": "Funnel: Implementation",
52
- "description": "Pipeline for the Implementation Contract funnel. Fires when a signal arrives with pipeline_slug='funnel-implementation'.",
53
- "stages": [],
54
- "config": {},
55
- "ownership": "tenant",
56
- "is_system": false,
57
- "is_active": true
58
- }
59
- ]
@@ -1,125 +0,0 @@
1
- [
2
- {
3
- "name": "Case Resolution Analysis",
4
- "description": "Automatically triggers case analysis when support tickets are resolved",
5
- "trigger_type": "event",
6
- "event_type": "item_updated",
7
- "pipeline_slug": "case-analysis",
8
- "config": {
9
- "scope": "account",
10
- "filters": [
11
- { "field": "status", "value": "resolved", "operator": "$eq" },
12
- { "field": "type_id", "type_slug": "support_ticket", "operator": "$eq" }
13
- ],
14
- "debounce_ms": 5000,
15
- "entity_type": "items",
16
- "retry_count": 3,
17
- "retry_delay_ms": 1000
18
- },
19
- "ownership": "tenant",
20
- "is_system": false,
21
- "is_active": true
22
- },
23
- {
24
- "name": "Funnel: General Signal Routing",
25
- "description": "Fires the general funnel pipeline for signals with no specific pipeline_slug",
26
- "trigger_type": "event",
27
- "event_type": "item_created",
28
- "pipeline_slug": "funnel-general",
29
- "config": {
30
- "scope": "account",
31
- "type_slug": "funnel_signal",
32
- "entity_type": "item",
33
- "filters": {
34
- "data.classification.pipeline_slug": { "$exists": false }
35
- }
36
- },
37
- "ownership": "tenant",
38
- "is_system": false,
39
- "is_active": true
40
- },
41
- {
42
- "name": "Funnel: AI Audit Routing",
43
- "description": "Fires the AI Audit pipeline when a signal arrives tagged with pipeline_slug='funnel-ai-audit'",
44
- "trigger_type": "event",
45
- "event_type": "item_created",
46
- "pipeline_slug": "funnel-ai-audit",
47
- "config": {
48
- "scope": "account",
49
- "type_slug": "funnel_signal",
50
- "entity_type": "item",
51
- "filters": {
52
- "data.classification.pipeline_slug": "funnel-ai-audit"
53
- }
54
- },
55
- "ownership": "tenant",
56
- "is_system": false,
57
- "is_active": true
58
- },
59
- {
60
- "name": "Funnel: Implementation Routing",
61
- "description": "Fires the Implementation pipeline when a signal arrives tagged with pipeline_slug='funnel-implementation'",
62
- "trigger_type": "event",
63
- "event_type": "item_created",
64
- "pipeline_slug": "funnel-implementation",
65
- "config": {
66
- "scope": "account",
67
- "type_slug": "funnel_signal",
68
- "entity_type": "item",
69
- "filters": {
70
- "data.classification.pipeline_slug": "funnel-implementation"
71
- }
72
- },
73
- "ownership": "tenant",
74
- "is_system": false,
75
- "is_active": true
76
- },
77
- {
78
- "name": "Funnel: Aggregation",
79
- "description": "Hourly dashboard metric aggregation",
80
- "trigger_type": "cron",
81
- "event_type": null,
82
- "config": {
83
- "scope": "account",
84
- "function": "funnel-timers.aggregation",
85
- "schedule": "0 * * * *",
86
- "timezone": "UTC",
87
- "description": "Hourly dashboard metric aggregation"
88
- },
89
- "ownership": "tenant",
90
- "is_system": false,
91
- "is_active": true
92
- },
93
- {
94
- "name": "Funnel: Score Decay",
95
- "description": "Daily recalculation of account ratings for score decay",
96
- "trigger_type": "cron",
97
- "event_type": null,
98
- "config": {
99
- "scope": "account",
100
- "function": "funnel-timers.scoreDecay",
101
- "schedule": "59 23 * * *",
102
- "timezone": "UTC",
103
- "description": "Daily recalculation of account ratings for score decay"
104
- },
105
- "ownership": "tenant",
106
- "is_system": false,
107
- "is_active": true
108
- },
109
- {
110
- "name": "Funnel: Session Cleanup",
111
- "description": "Daily purge of expired anonymous sessions",
112
- "trigger_type": "cron",
113
- "event_type": null,
114
- "config": {
115
- "scope": "account",
116
- "function": "funnel-timers.sessionCleanup",
117
- "schedule": "0 2 * * *",
118
- "timezone": "UTC",
119
- "description": "Daily purge of expired anonymous sessions"
120
- },
121
- "ownership": "tenant",
122
- "is_system": false,
123
- "is_active": true
124
- }
125
- ]