selftune 0.2.2 → 0.2.6

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 (53) hide show
  1. package/README.md +11 -0
  2. package/apps/local-dashboard/dist/assets/index-C75H1Q3n.css +1 -0
  3. package/apps/local-dashboard/dist/assets/index-axE4kz3Q.js +15 -0
  4. package/apps/local-dashboard/dist/assets/vendor-ui-r2k_Ku_V.js +346 -0
  5. package/apps/local-dashboard/dist/index.html +3 -3
  6. package/cli/selftune/analytics.ts +354 -0
  7. package/cli/selftune/badge/badge.ts +2 -2
  8. package/cli/selftune/dashboard-server.ts +3 -3
  9. package/cli/selftune/evolution/evolve-body.ts +1 -1
  10. package/cli/selftune/evolution/evolve.ts +1 -1
  11. package/cli/selftune/index.ts +15 -1
  12. package/cli/selftune/init.ts +5 -1
  13. package/cli/selftune/observability.ts +63 -2
  14. package/cli/selftune/orchestrate.ts +1 -1
  15. package/cli/selftune/quickstart.ts +1 -1
  16. package/cli/selftune/status.ts +2 -2
  17. package/cli/selftune/types.ts +1 -0
  18. package/cli/selftune/utils/llm-call.ts +2 -1
  19. package/package.json +6 -4
  20. package/packages/ui/README.md +113 -0
  21. package/packages/ui/index.ts +10 -0
  22. package/packages/ui/package.json +62 -0
  23. package/packages/ui/src/components/ActivityTimeline.tsx +171 -0
  24. package/packages/ui/src/components/EvidenceViewer.tsx +718 -0
  25. package/packages/ui/src/components/EvolutionTimeline.tsx +252 -0
  26. package/packages/ui/src/components/InfoTip.tsx +19 -0
  27. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +164 -0
  28. package/packages/ui/src/components/index.ts +7 -0
  29. package/packages/ui/src/components/section-cards.tsx +155 -0
  30. package/packages/ui/src/components/skill-health-grid.tsx +686 -0
  31. package/packages/ui/src/lib/constants.tsx +43 -0
  32. package/packages/ui/src/lib/format.ts +37 -0
  33. package/packages/ui/src/lib/index.ts +3 -0
  34. package/packages/ui/src/lib/utils.ts +6 -0
  35. package/packages/ui/src/primitives/badge.tsx +52 -0
  36. package/packages/ui/src/primitives/button.tsx +58 -0
  37. package/packages/ui/src/primitives/card.tsx +103 -0
  38. package/packages/ui/src/primitives/checkbox.tsx +27 -0
  39. package/packages/ui/src/primitives/collapsible.tsx +7 -0
  40. package/packages/ui/src/primitives/dropdown-menu.tsx +266 -0
  41. package/packages/ui/src/primitives/index.ts +55 -0
  42. package/packages/ui/src/primitives/label.tsx +20 -0
  43. package/packages/ui/src/primitives/select.tsx +197 -0
  44. package/packages/ui/src/primitives/table.tsx +114 -0
  45. package/packages/ui/src/primitives/tabs.tsx +82 -0
  46. package/packages/ui/src/primitives/tooltip.tsx +64 -0
  47. package/packages/ui/src/types.ts +87 -0
  48. package/packages/ui/tsconfig.json +17 -0
  49. package/skill/SKILL.md +3 -0
  50. package/skill/Workflows/Telemetry.md +59 -0
  51. package/apps/local-dashboard/dist/assets/index-C4EOTFZ2.js +0 -15
  52. package/apps/local-dashboard/dist/assets/index-bl-Webyd.css +0 -1
  53. package/apps/local-dashboard/dist/assets/vendor-ui-D7_zX_qy.js +0 -346
@@ -0,0 +1,197 @@
1
+ import * as React from "react"
2
+ import { Select as SelectPrimitive } from "@base-ui/react/select"
3
+
4
+ import { cn } from "../lib/utils"
5
+ import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
10
+ return (
11
+ <SelectPrimitive.Group
12
+ data-slot="select-group"
13
+ className={cn("scroll-my-1 p-1", className)}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
20
+ return (
21
+ <SelectPrimitive.Value
22
+ data-slot="select-value"
23
+ className={cn("flex flex-1 text-left", className)}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ function SelectTrigger({
30
+ className,
31
+ size = "default",
32
+ children,
33
+ ...props
34
+ }: SelectPrimitive.Trigger.Props & {
35
+ size?: "sm" | "default"
36
+ }) {
37
+ return (
38
+ <SelectPrimitive.Trigger
39
+ data-slot="select-trigger"
40
+ data-size={size}
41
+ className={cn(
42
+ "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ <SelectPrimitive.Icon
49
+ render={
50
+ <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
51
+ }
52
+ />
53
+ </SelectPrimitive.Trigger>
54
+ )
55
+ }
56
+
57
+ function SelectContent({
58
+ className,
59
+ children,
60
+ side = "bottom",
61
+ sideOffset = 4,
62
+ align = "center",
63
+ alignOffset = 0,
64
+ alignItemWithTrigger = true,
65
+ ...props
66
+ }: SelectPrimitive.Popup.Props &
67
+ Pick<
68
+ SelectPrimitive.Positioner.Props,
69
+ "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
70
+ >) {
71
+ return (
72
+ <SelectPrimitive.Portal>
73
+ <SelectPrimitive.Positioner
74
+ side={side}
75
+ sideOffset={sideOffset}
76
+ align={align}
77
+ alignOffset={alignOffset}
78
+ alignItemWithTrigger={alignItemWithTrigger}
79
+ className="isolate z-50"
80
+ >
81
+ <SelectPrimitive.Popup
82
+ data-slot="select-content"
83
+ data-align-trigger={alignItemWithTrigger}
84
+ className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
85
+ {...props}
86
+ >
87
+ <SelectScrollUpButton />
88
+ <SelectPrimitive.List>{children}</SelectPrimitive.List>
89
+ <SelectScrollDownButton />
90
+ </SelectPrimitive.Popup>
91
+ </SelectPrimitive.Positioner>
92
+ </SelectPrimitive.Portal>
93
+ )
94
+ }
95
+
96
+ function SelectLabel({
97
+ className,
98
+ ...props
99
+ }: SelectPrimitive.GroupLabel.Props) {
100
+ return (
101
+ <SelectPrimitive.GroupLabel
102
+ data-slot="select-label"
103
+ className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
104
+ {...props}
105
+ />
106
+ )
107
+ }
108
+
109
+ function SelectItem({
110
+ className,
111
+ children,
112
+ ...props
113
+ }: SelectPrimitive.Item.Props) {
114
+ return (
115
+ <SelectPrimitive.Item
116
+ data-slot="select-item"
117
+ className={cn(
118
+ "relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
119
+ className
120
+ )}
121
+ {...props}
122
+ >
123
+ <SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
124
+ {children}
125
+ </SelectPrimitive.ItemText>
126
+ <SelectPrimitive.ItemIndicator
127
+ render={
128
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
129
+ }
130
+ >
131
+ <CheckIcon className="pointer-events-none" />
132
+ </SelectPrimitive.ItemIndicator>
133
+ </SelectPrimitive.Item>
134
+ )
135
+ }
136
+
137
+ function SelectSeparator({
138
+ className,
139
+ ...props
140
+ }: SelectPrimitive.Separator.Props) {
141
+ return (
142
+ <SelectPrimitive.Separator
143
+ data-slot="select-separator"
144
+ className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
145
+ {...props}
146
+ />
147
+ )
148
+ }
149
+
150
+ function SelectScrollUpButton({
151
+ className,
152
+ ...props
153
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
154
+ return (
155
+ <SelectPrimitive.ScrollUpArrow
156
+ data-slot="select-scroll-up-button"
157
+ className={cn(
158
+ "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ <ChevronUpIcon />
164
+ </SelectPrimitive.ScrollUpArrow>
165
+ )
166
+ }
167
+
168
+ function SelectScrollDownButton({
169
+ className,
170
+ ...props
171
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
172
+ return (
173
+ <SelectPrimitive.ScrollDownArrow
174
+ data-slot="select-scroll-down-button"
175
+ className={cn(
176
+ "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
177
+ className
178
+ )}
179
+ {...props}
180
+ >
181
+ <ChevronDownIcon />
182
+ </SelectPrimitive.ScrollDownArrow>
183
+ )
184
+ }
185
+
186
+ export {
187
+ Select,
188
+ SelectContent,
189
+ SelectGroup,
190
+ SelectItem,
191
+ SelectLabel,
192
+ SelectScrollDownButton,
193
+ SelectScrollUpButton,
194
+ SelectSeparator,
195
+ SelectTrigger,
196
+ SelectValue,
197
+ }
@@ -0,0 +1,114 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
6
+ return (
7
+ <div
8
+ data-slot="table-container"
9
+ className="relative w-full overflow-x-auto"
10
+ >
11
+ <table
12
+ data-slot="table"
13
+ className={cn("w-full caption-bottom text-sm", className)}
14
+ {...props}
15
+ />
16
+ </div>
17
+ )
18
+ }
19
+
20
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
21
+ return (
22
+ <thead
23
+ data-slot="table-header"
24
+ className={cn("[&_tr]:border-b", className)}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+
30
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
31
+ return (
32
+ <tbody
33
+ data-slot="table-body"
34
+ className={cn("[&_tr:last-child]:border-0", className)}
35
+ {...props}
36
+ />
37
+ )
38
+ }
39
+
40
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
41
+ return (
42
+ <tfoot
43
+ data-slot="table-footer"
44
+ className={cn(
45
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
54
+ return (
55
+ <tr
56
+ data-slot="table-row"
57
+ className={cn(
58
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
67
+ return (
68
+ <th
69
+ data-slot="table-head"
70
+ className={cn(
71
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
72
+ className
73
+ )}
74
+ {...props}
75
+ />
76
+ )
77
+ }
78
+
79
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
80
+ return (
81
+ <td
82
+ data-slot="table-cell"
83
+ className={cn(
84
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
85
+ className
86
+ )}
87
+ {...props}
88
+ />
89
+ )
90
+ }
91
+
92
+ function TableCaption({
93
+ className,
94
+ ...props
95
+ }: React.ComponentProps<"caption">) {
96
+ return (
97
+ <caption
98
+ data-slot="table-caption"
99
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
100
+ {...props}
101
+ />
102
+ )
103
+ }
104
+
105
+ export {
106
+ Table,
107
+ TableHeader,
108
+ TableBody,
109
+ TableFooter,
110
+ TableHead,
111
+ TableRow,
112
+ TableCell,
113
+ TableCaption,
114
+ }
@@ -0,0 +1,82 @@
1
+ "use client"
2
+
3
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ function Tabs({
9
+ className,
10
+ orientation = "horizontal",
11
+ ...props
12
+ }: TabsPrimitive.Root.Props) {
13
+ return (
14
+ <TabsPrimitive.Root
15
+ data-slot="tabs"
16
+ data-orientation={orientation}
17
+ className={cn(
18
+ "group/tabs flex gap-2 data-horizontal:flex-col",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ const tabsListVariants = cva(
27
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
28
+ {
29
+ variants: {
30
+ variant: {
31
+ default: "bg-muted",
32
+ line: "gap-1 bg-transparent",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ },
38
+ }
39
+ )
40
+
41
+ function TabsList({
42
+ className,
43
+ variant = "default",
44
+ ...props
45
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
46
+ return (
47
+ <TabsPrimitive.List
48
+ data-slot="tabs-list"
49
+ data-variant={variant}
50
+ className={cn(tabsListVariants({ variant }), className)}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
57
+ return (
58
+ <TabsPrimitive.Tab
59
+ data-slot="tabs-trigger"
60
+ className={cn(
61
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
62
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
63
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
64
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
73
+ return (
74
+ <TabsPrimitive.Panel
75
+ data-slot="tabs-content"
76
+ className={cn("flex-1 text-sm outline-none", className)}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
@@ -0,0 +1,64 @@
1
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ function TooltipProvider({
6
+ delay = 0,
7
+ ...props
8
+ }: TooltipPrimitive.Provider.Props) {
9
+ return (
10
+ <TooltipPrimitive.Provider
11
+ data-slot="tooltip-provider"
12
+ delay={delay}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
19
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
20
+ }
21
+
22
+ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
23
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
24
+ }
25
+
26
+ function TooltipContent({
27
+ className,
28
+ side = "top",
29
+ sideOffset = 4,
30
+ align = "center",
31
+ alignOffset = 0,
32
+ children,
33
+ ...props
34
+ }: TooltipPrimitive.Popup.Props &
35
+ Pick<
36
+ TooltipPrimitive.Positioner.Props,
37
+ "align" | "alignOffset" | "side" | "sideOffset"
38
+ >) {
39
+ return (
40
+ <TooltipPrimitive.Portal>
41
+ <TooltipPrimitive.Positioner
42
+ align={align}
43
+ alignOffset={alignOffset}
44
+ side={side}
45
+ sideOffset={sideOffset}
46
+ className="isolate z-50"
47
+ >
48
+ <TooltipPrimitive.Popup
49
+ data-slot="tooltip-content"
50
+ className={cn(
51
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
52
+ className
53
+ )}
54
+ {...props}
55
+ >
56
+ {children}
57
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
58
+ </TooltipPrimitive.Popup>
59
+ </TooltipPrimitive.Positioner>
60
+ </TooltipPrimitive.Portal>
61
+ )
62
+ }
63
+
64
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,87 @@
1
+ // -- UI-only types -----------------------------------------------------------
2
+
3
+ export type SkillHealthStatus = "HEALTHY" | "WARNING" | "CRITICAL" | "UNGRADED" | "UNKNOWN";
4
+
5
+ export interface SkillCard {
6
+ name: string;
7
+ scope: string | null;
8
+ passRate: number | null;
9
+ checks: number;
10
+ status: SkillHealthStatus;
11
+ hasEvidence: boolean;
12
+ uniqueSessions: number;
13
+ lastSeen: string | null;
14
+ }
15
+
16
+ // -- Dashboard contract types (re-declared for package independence) ----------
17
+
18
+ export interface EvalSnapshot {
19
+ before_pass_rate?: number;
20
+ after_pass_rate?: number;
21
+ net_change?: number;
22
+ improved?: boolean;
23
+ regressions?: Array<Record<string, unknown>>;
24
+ new_passes?: Array<Record<string, unknown>>;
25
+ }
26
+
27
+ export interface EvolutionEntry {
28
+ timestamp: string;
29
+ proposal_id: string;
30
+ action: string;
31
+ details: string;
32
+ eval_snapshot?: EvalSnapshot | null;
33
+ }
34
+
35
+ export interface UnmatchedQuery {
36
+ timestamp: string;
37
+ session_id: string;
38
+ query: string;
39
+ }
40
+
41
+ export interface PendingProposal {
42
+ proposal_id: string;
43
+ action: string;
44
+ timestamp: string;
45
+ details: string;
46
+ skill_name?: string;
47
+ }
48
+
49
+ export interface EvidenceEntry {
50
+ proposal_id: string;
51
+ target: string;
52
+ stage: string;
53
+ timestamp: string;
54
+ rationale: string | null;
55
+ confidence: number | null;
56
+ original_text: string | null;
57
+ proposed_text: string | null;
58
+ validation: Record<string, unknown> | null;
59
+ details: string | null;
60
+ eval_set: Array<Record<string, unknown>>;
61
+ }
62
+
63
+ export interface OrchestrateRunSkillAction {
64
+ skill: string;
65
+ action: "evolve" | "watch" | "skip";
66
+ reason: string;
67
+ deployed?: boolean;
68
+ rolledBack?: boolean;
69
+ alert?: string | null;
70
+ elapsed_ms?: number;
71
+ llm_calls?: number;
72
+ }
73
+
74
+ export interface OrchestrateRunReport {
75
+ run_id: string;
76
+ timestamp: string;
77
+ elapsed_ms: number;
78
+ dry_run: boolean;
79
+ approval_mode: "auto" | "review";
80
+ total_skills: number;
81
+ evaluated: number;
82
+ evolved: number;
83
+ deployed: number;
84
+ watched: number;
85
+ skipped: number;
86
+ skill_actions: OrchestrateRunSkillAction[];
87
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "sourceMap": true,
13
+ "outDir": "dist",
14
+ "rootDir": "."
15
+ },
16
+ "include": ["src/**/*.ts", "src/**/*.tsx", "index.ts"]
17
+ }
package/skill/SKILL.md CHANGED
@@ -77,6 +77,7 @@ selftune cron setup [--dry-run] # auto-detect platform (
77
77
  selftune cron setup --platform openclaw [--dry-run] [--tz <timezone>] # OpenClaw-specific
78
78
  selftune cron list
79
79
  selftune cron remove [--dry-run]
80
+ selftune telemetry [status|enable|disable]
80
81
  ```
81
82
 
82
83
  ## Workflow Routing
@@ -102,6 +103,7 @@ selftune cron remove [--dry-run]
102
103
  | eval unit-test, skill test, test skill, generate tests, run tests, assertions | UnitTest | Workflows/UnitTest.md |
103
104
  | eval composability, co-occurrence, skill conflicts, skills together, conflict score | Composability | Workflows/Composability.md |
104
105
  | eval import, skillsbench, external evals, benchmark tasks, import corpus | ImportSkillsBench | Workflows/ImportSkillsBench.md |
106
+ | telemetry, analytics, disable analytics, opt out, usage data, tracking, privacy | Telemetry | Workflows/Telemetry.md |
105
107
  | status, health summary, skill health, pass rates, how are skills, skills working, skills doing, run selftune, start selftune | Status | *(direct command — no workflow file)* |
106
108
  | last, last session, recent session, what happened, what changed, what did selftune do | Last | *(direct command — no workflow file)* |
107
109
 
@@ -191,6 +193,7 @@ Observe --> Detect --> Diagnose --> Propose --> Validate --> Audit --> Deploy --
191
193
  | `Workflows/UnitTest.md` | Skill-level unit test runner and generator |
192
194
  | `Workflows/Composability.md` | Multi-skill co-occurrence conflict analysis |
193
195
  | `Workflows/ImportSkillsBench.md` | SkillsBench task corpus importer |
196
+ | `Workflows/Telemetry.md` | Telemetry status, opt-in/opt-out, and privacy |
194
197
 
195
198
  ## Specialized Agents
196
199
 
@@ -0,0 +1,59 @@
1
+ # selftune Telemetry Workflow
2
+
3
+ ## When to Use
4
+
5
+ When the user asks about telemetry, analytics, usage tracking, privacy,
6
+ opting out of data collection, or wants to check/change their telemetry settings.
7
+
8
+ ## Overview
9
+
10
+ selftune collects anonymous, non-identifying usage analytics to help prioritize
11
+ features. No PII is ever collected — only command names, OS/arch, and selftune
12
+ version. Users can opt out at any time.
13
+
14
+ ## Default Commands
15
+
16
+ ```bash
17
+ selftune telemetry # Show current telemetry status
18
+ selftune telemetry status # Same as above
19
+ selftune telemetry enable # Enable anonymous usage analytics
20
+ selftune telemetry disable # Disable anonymous usage analytics
21
+ ```
22
+
23
+ ## Environment Override
24
+
25
+ ```bash
26
+ export SELFTUNE_NO_ANALYTICS=1 # Disable via env var (highest priority)
27
+ ```
28
+
29
+ Analytics is also automatically disabled in CI environments (`CI=true`).
30
+
31
+ ## What Is Collected
32
+
33
+ - Command name (e.g., "status", "evolve")
34
+ - OS, architecture, selftune version, node version
35
+ - Agent type (claude/codex/opencode)
36
+ - Random anonymous ID (not derived from any user data)
37
+
38
+ ## What IS Collected (linkable)
39
+
40
+ - `anonymous_id` — stable random ID (persisted locally, not derived from user data)
41
+ - `sent_at` — ISO timestamp of when the event was sent
42
+
43
+ These fields can correlate events from the same machine. They contain no PII.
44
+
45
+ ## What Is NOT Collected
46
+
47
+ - No usernames, emails, IPs, or hostnames
48
+ - No file paths or repo names
49
+ - No session IDs
50
+ - No skill names or content
51
+
52
+ ## Common Patterns
53
+
54
+ - "Is selftune tracking me?"
55
+ → `selftune telemetry status`
56
+ - "Turn off analytics"
57
+ → `selftune telemetry disable`
58
+ - "I want to help improve selftune"
59
+ → `selftune telemetry enable`