@ekanos/cli 0.1.6 → 0.1.7

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 (40) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/init.d.ts +36 -0
  3. package/dist/commands/init.js +126 -10
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/upgrade.js +108 -7
  6. package/dist/commands/upgrade.js.map +1 -1
  7. package/dist/copy-tree.d.ts +48 -0
  8. package/dist/copy-tree.js +55 -0
  9. package/dist/copy-tree.js.map +1 -0
  10. package/dist/index.js +5 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/parse-argv.d.ts +7 -1
  13. package/dist/parse-argv.js +10 -2
  14. package/dist/parse-argv.js.map +1 -1
  15. package/dist/project-checks.d.ts +9 -0
  16. package/dist/project-checks.js +29 -0
  17. package/dist/project-checks.js.map +1 -1
  18. package/dist/project.d.ts +23 -0
  19. package/dist/project.js +66 -1
  20. package/dist/project.js.map +1 -1
  21. package/dist/publish-api.d.ts +1 -1
  22. package/dist/publish-api.js +2 -2
  23. package/dist/publish-api.js.map +1 -1
  24. package/dist/safe-fs.d.ts +1 -1
  25. package/dist/safe-fs.js.map +1 -1
  26. package/dist/sources-api.d.ts +1 -1
  27. package/dist/sources-api.js.map +1 -1
  28. package/dist/validate-findings.js +2 -1
  29. package/dist/validate-findings.js.map +1 -1
  30. package/package.json +1 -1
  31. package/templates/AGENTS.md.tmpl +57 -0
  32. package/templates/claude-skill.md.tmpl +7 -0
  33. package/templates/docs/api-notes.md.tmpl +21 -0
  34. package/templates/mcp.json.tmpl +8 -0
  35. package/templates/skills/build-integration/SKILL.md +178 -0
  36. package/templates/skills/build-integration/references/api-docs-research.md +93 -0
  37. package/templates/skills/build-integration/references/api-wiring.md +94 -0
  38. package/templates/skills/build-integration/references/widget-design.md +132 -0
  39. package/templates/skills/polish-widgets/SKILL.md +139 -0
  40. package/templates/skills/polish-widgets/references/patterns.md +269 -0
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: polish-widgets
3
+ description: Review-and-fix pass over existing widget styling. Use when the user asks to review widget styling, wants widgets or the marketplace tile to look better, says the tile design is bad or the widgets look plain/generic/flat, reports overflow or responsiveness problems, or before publishing as a final styling pass.
4
+ ---
5
+
6
+ # Polish Widgets
7
+
8
+ A widget that renders the right data can still be a bad widget: every row the
9
+ same weight, no element answering the widget's question, a grid of identical
10
+ stat chips. This skill is the fix pass — render each widget in the harness,
11
+ find the worst offenses, fix them, show the user before and after. The
12
+ creation-time design rules live in
13
+ `../build-integration/references/widget-design.md`; this skill assumes they
14
+ exist and enforces them on code already written.
15
+
16
+ ## The key question
17
+
18
+ Every widget exists to answer 1–2 questions. Name the question FIRST. Its
19
+ answer must be the most prominent element — size, weight, position — and
20
+ everything else demotes to supporting context. If you cannot name the
21
+ question, that is the finding: ask the user what the widget is for before
22
+ touching its classes.
23
+
24
+ | Widget type | Key question | Most prominent element |
25
+ | ------------------- | ---------------------------------- | --------------------------------------------------------------------------- |
26
+ | KPI / stat | "How are we doing?" | The hero number: `text-3xl font-semibold tracking-tight tabular-nums` |
27
+ | List / feed | "What needs my attention?" | Item name `text-base font-medium` + its value, right-aligned `tabular-nums` |
28
+ | Chart | "Which way is it going?" | The chart plus the current value; axes and legends muted |
29
+ | Action card | "What can I do here?" | One clear CTA; supporting copy secondary |
30
+ | Status / connection | "Is it working? What's connected?" | Badge or status dot + the identity it describes |
31
+
32
+ The most common failure is uniform weight: everything `text-sm`, values styled
33
+ like their labels. Squint at the rendered widget — if nothing reads first,
34
+ there is no hierarchy, and that fix comes before any padding nit.
35
+
36
+ ## Review procedure
37
+
38
+ 1. **Enumerate** the widgets from `components.widgets` in `src/integration.ts`
39
+ — id, name, component file. Review all of them unless the user scoped it.
40
+ 2. **Render**: `npx ekanos dev`, then the Widgets surface at
41
+ `http://localhost:3100/<slug>/widgets`. Drive the review from the dev
42
+ toolbar: Render state (loading / error / inactive / disabled / active),
43
+ Viewport presets (check sm, not just lg), Collapsed, Unhealthy, and the
44
+ theme toggle — dark mode is part of the review, not an extra.
45
+ 3. **Assess** each widget against the checklist below and write findings per
46
+ widget, worst-first. Missing hierarchy outranks spacing; a broken sm layout
47
+ outranks a badge color.
48
+ 4. **Fix** worst-first, one widget at a time. After each: `npx tsc --noEmit`,
49
+ re-render in the harness.
50
+ 5. **Show the user** before/after for each widget — screenshots or ask them to
51
+ look — and get their read before the next one. Never claim a widget looks
52
+ better without one of the two.
53
+
54
+ ## Checklist
55
+
56
+ **Hierarchy**
57
+
58
+ - The key question's answer is the visually dominant element
59
+ - One prominent element per row; supporting info is `text-muted-foreground`
60
+ - Hero/stat values match the recipe: `text-3xl font-semibold tracking-tight
61
+ tabular-nums` (never below `text-2xl`, and never `text-sm` on a number
62
+ that matters)
63
+ - List row titles `text-base font-medium`; subtitles `text-sm text-muted-foreground`
64
+ - `text-xs` only on tertiary metadata (timestamps, header badge counts)
65
+
66
+ **Tokens & color**
67
+
68
+ - Semantic tokens only — no `bg-white`, `text-gray-500`, hex, raw palette shades
69
+ - Primary accent on ≤10% of the widget; status through `Badge` variants
70
+ (`success` / `warning` / `info` / `destructive`) or a semantic status dot
71
+ - No two colored elements competing in one row
72
+ - Both themes checked with the toolbar toggle
73
+
74
+ **Layout & overflow**
75
+
76
+ - Lists cap at 5–7 visible items; more goes behind inner scroll
77
+ (`min-h-0 flex-1 overflow-y-auto` on the list, never the card) or "View all"
78
+ - `truncate` + `min-w-0` on user-supplied strings; values `shrink-0` and right-aligned
79
+ - The sm viewport holds: cut columns, keep meaning
80
+ - Sizing floors hold: rows `px-4 py-3`+, list icons `h-5 w-5`+, gaps `gap-3`+,
81
+ body text `text-sm`+
82
+
83
+ **States & numbers**
84
+
85
+ - All four states render (the toolbar flips loading/error/inactive; empty needs
86
+ a data mock) and empty is a designed state, not a blank card
87
+ - Digits that align use `tabular-nums`; money is right-aligned with fixed width
88
+
89
+ The canonical fix for the flat-row failure, since it is the one you will apply
90
+ most:
91
+
92
+ ```tsx
93
+ // Before: three spans, one size, no first read
94
+ <div className="flex justify-between text-sm">
95
+ <span>{item.name}</span>
96
+ <span>{item.amount}</span>
97
+ </div>
98
+
99
+ // After: name leads, value anchors, metadata recedes
100
+ <div className="flex items-center gap-3 px-4 py-3">
101
+ <div className="min-w-0 flex-1">
102
+ <p className="truncate text-base font-medium">{item.name}</p>
103
+ <p className="text-muted-foreground text-sm">{item.detail}</p>
104
+ </div>
105
+ <span className="shrink-0 text-base font-semibold tabular-nums">
106
+ {formatCurrency(item.amount)}
107
+ </span>
108
+ </div>
109
+ ```
110
+
111
+ Full before→after gallery — hero KPI, status treatment, sizing floors, inner
112
+ scroll, row actions, designed empty state — in `references/patterns.md`. Read
113
+ it before rewriting any widget body.
114
+
115
+ ## The marketplace tile
116
+
117
+ "The tile looks bad" is usually not a styling bug — it is missing branding.
118
+ The tile is `components.marketplaceTile` in the definition, typically built on
119
+ `BaseMarketplaceTile` from `@ekanos/sdk/components`, and it renders from the
120
+ props you pass: `name`, `summary` (1–2 sentences shown on the card),
121
+ `brandName`, `logoPath`, `categories`, `productColor`, `ctaText`. Omit
122
+ `logoPath` and the card falls back to a single-letter monogram; omit `summary`
123
+ and the body sits blank next to its neighbours. Check the props before
124
+ touching CSS.
125
+
126
+ Review it at `http://localhost:3100/<slug>/tile` — the harness renders it
127
+ inside the real catalogue grid with two inert neighbour cards, which is the
128
+ only honest test: does the title wrap at column width, does the summary clamp
129
+ like the neighbours', is the card the same height? (Register the component as
130
+ `tile:` in `harness.config.ts` if the surface says none is registered.) The
131
+ harness also notes this surface is lightly used by the live marketplace today
132
+ — keep it correct and branded, don't gold-plate it.
133
+
134
+ ## Reporting
135
+
136
+ Finish with a per-widget summary: the key question you assigned it, the
137
+ findings fixed (worst-first), anything deliberately left, and confirmation the
138
+ user saw the result. Then run the standard gate: `npx tsc --noEmit` and
139
+ `npx ekanos validate` clean.
@@ -0,0 +1,269 @@
1
+ # Widget fix patterns — before → after
2
+
3
+ Each pattern names the failure, then shows the smallest honest fix. All
4
+ imports are `@ekanos/ui/*`; icons go through `@ekanos/ui/icon` (Font Awesome
5
+ names — verify against the Free set). Everything sits inside the standard
6
+ `Widget.Active` body from the SDK's compound API; these are the bodies, not
7
+ the wrapper.
8
+
9
+ ## 1. Flat list row → row with a first read
10
+
11
+ **Failure:** every field the same size, so nothing answers "what needs my
12
+ attention?". Values styled like labels, metadata styled like values.
13
+
14
+ ```tsx
15
+ // BEFORE — three spans, one weight
16
+ {
17
+ invoices.map((inv) => (
18
+ <div key={inv.id} className="flex justify-between p-2 text-sm">
19
+ <span>{inv.customer}</span>
20
+ <span className="text-xs">{inv.dueDate}</span>
21
+ <span>{inv.amount}</span>
22
+ </div>
23
+ ));
24
+ }
25
+ ```
26
+
27
+ ```tsx
28
+ // AFTER — name leads, value anchors right, metadata recedes
29
+ <div className="divide-border divide-y">
30
+ {invoices.map((inv) => (
31
+ <div key={inv.id} className="flex items-center gap-3 px-4 py-3">
32
+ <div className="min-w-0 flex-1">
33
+ <p className="truncate text-base font-medium">{inv.customer}</p>
34
+ <p className="text-muted-foreground text-sm">
35
+ Due {formatDate(inv.dueDate)}
36
+ </p>
37
+ </div>
38
+ <span className="shrink-0 text-right text-base font-semibold tabular-nums">
39
+ {formatCurrency(inv.amount)}
40
+ </span>
41
+ </div>
42
+ ))}
43
+ </div>
44
+ ```
45
+
46
+ Rules at work: `min-w-0 flex-1` + `truncate` so long names never push the
47
+ value out; `shrink-0` + `tabular-nums` so amounts hold a column; rows own
48
+ their padding (`px-4 py-3`), the container owns none.
49
+
50
+ ## 2. Bare stat grid → hero KPI with supporting stats
51
+
52
+ **Failure:** four equal chips. The widget's one question ("how are we
53
+ doing?") has no answer — every number claims the same importance.
54
+
55
+ ```tsx
56
+ // BEFORE — nothing is the headline
57
+ <div className="grid grid-cols-2 gap-2 p-4">
58
+ <div className="text-sm">Revenue: {revenue}</div>
59
+ <div className="text-sm">Orders: {orders}</div>
60
+ <div className="text-sm">Avg: {avg}</div>
61
+ <div className="text-sm">Refunds: {refunds}</div>
62
+ </div>
63
+ ```
64
+
65
+ ```tsx
66
+ // AFTER — one hero, a delta, the rest demoted to a footer strip
67
+ <div className="flex flex-1 flex-col items-center justify-center px-4 py-6">
68
+ <span className="text-muted-foreground text-sm">Revenue this month</span>
69
+ <span className="text-3xl font-semibold tracking-tight tabular-nums">
70
+ {formatCurrency(revenue)}
71
+ </span>
72
+ <span
73
+ className={cn(
74
+ 'mt-1 text-sm font-medium',
75
+ delta >= 0 ? 'text-success-strong' : 'text-destructive',
76
+ )}
77
+ >
78
+ {delta >= 0 ? '+' : ''}
79
+ {delta.toFixed(1)}% vs last month
80
+ </span>
81
+ </div>
82
+
83
+ <div className="flex shrink-0 items-center border-t">
84
+ {[
85
+ { label: 'Orders', value: orders },
86
+ { label: 'Avg order', value: formatCurrency(avg) },
87
+ { label: 'Refunds', value: refunds },
88
+ ].map((stat, i) => (
89
+ <Fragment key={stat.label}>
90
+ {i > 0 && <div className="bg-border h-8 w-px" />}
91
+ <div className="flex flex-1 flex-col items-center py-3">
92
+ <span className="text-lg font-semibold tabular-nums">{stat.value}</span>
93
+ <span className="text-muted-foreground text-xs">{stat.label}</span>
94
+ </div>
95
+ </Fragment>
96
+ ))}
97
+ </div>
98
+ ```
99
+
100
+ (`cn` from `@ekanos/ui/utils`.) Pick the hero by the interview's SEE-outcome,
101
+ not by which number is largest. One `text-3xl` element per widget — two heroes
102
+ is zero heroes.
103
+
104
+ ## 3. Unstyled status → semantic badge or dot
105
+
106
+ **Failure:** status as bare text, or hardcoded palette (`bg-green-500`) that
107
+ breaks in dark mode and off-brand tenants.
108
+
109
+ ```tsx
110
+ // BEFORE
111
+ <span className="text-xs">{sub.status}</span>
112
+ // or
113
+ <span className="rounded bg-green-500 px-1 text-white">{sub.status}</span>
114
+ ```
115
+
116
+ ```tsx
117
+ // AFTER — Badge variants carry the semantics; the map is the single source
118
+ import { Badge } from '@ekanos/ui/badge';
119
+
120
+ const STATUS_VARIANT = {
121
+ active: 'success',
122
+ trialing: 'info',
123
+ past_due: 'warning',
124
+ canceled: 'destructive',
125
+ } as const;
126
+
127
+ <Badge variant={STATUS_VARIANT[sub.status] ?? 'outline'}>
128
+ {STATUS_LABEL[sub.status] ?? sub.status}
129
+ </Badge>;
130
+ ```
131
+
132
+ When a badge is too loud (dense lists), use a dot plus muted text instead:
133
+
134
+ ```tsx
135
+ <span
136
+ className={cn(
137
+ 'h-2.5 w-2.5 shrink-0 rounded-full',
138
+ sub.status === 'active' ? 'bg-success' : 'bg-muted-foreground/40',
139
+ )}
140
+ />
141
+ <span className="text-muted-foreground text-sm">{STATUS_LABEL[sub.status]}</span>
142
+ ```
143
+
144
+ One status treatment per row. A row with a colored badge AND a colored icon
145
+ AND colored money is signalling nothing.
146
+
147
+ ## 4. Cramped card → sizing floors applied
148
+
149
+ **Failure:** `p-2`, `space-y-1`, `text-xs` everywhere, `h-3 w-3` icons. Dense
150
+ is not information-rich; it is unreadable.
151
+
152
+ ```tsx
153
+ // BEFORE
154
+ <div className="space-y-1 p-2">
155
+ {events.map((e) => (
156
+ <div key={e.id} className="flex gap-1 text-xs">
157
+ <Icon name="fa-light fa-bolt" className="h-3 w-3" />
158
+ <span>{e.title}</span>
159
+ </div>
160
+ ))}
161
+ </div>
162
+ ```
163
+
164
+ ```tsx
165
+ // AFTER — floors: rows px-4 py-3, icons h-5 w-5, body text-sm+
166
+ <div className="divide-border divide-y">
167
+ {events.slice(0, 6).map((e) => (
168
+ <div key={e.id} className="flex items-start gap-3 px-4 py-3">
169
+ <span className="bg-muted text-muted-foreground shrink-0 rounded-full p-2">
170
+ <Icon name={eventIcon(e.type)} className="h-5 w-5" />
171
+ </span>
172
+ <div className="min-w-0 flex-1">
173
+ <p className="truncate text-base font-medium">{e.title}</p>
174
+ <p className="text-muted-foreground text-sm">{e.summary}</p>
175
+ </div>
176
+ <span className="text-muted-foreground shrink-0 text-xs">
177
+ {formatTimeAgo(e.at)}
178
+ </span>
179
+ </div>
180
+ ))}
181
+ </div>
182
+ ```
183
+
184
+ If the content no longer fits after applying floors, show fewer items — the
185
+ floors are not negotiable, the item count is.
186
+
187
+ ## 5. Overflowing card → inner scroll + "View all"
188
+
189
+ **Failure:** the list grows the card past its grid slot, or a fixed
190
+ `max-h-[180px]` caps it arbitrarily and clips the last row.
191
+
192
+ ```tsx
193
+ // BEFORE — the card scrolls (or grows); a magic pixel height fights the grid
194
+ <div className="max-h-[180px] overflow-y-auto">{rows}</div>
195
+ ```
196
+
197
+ ```tsx
198
+ // AFTER — the LIST scrolls inside the flex column; the card holds its size
199
+ import { Button } from '@ekanos/ui/button';
200
+
201
+ <div className="flex h-full min-h-0 flex-col">
202
+ <div className="min-h-0 flex-1 overflow-y-auto">
203
+ <div className="divide-border divide-y">{rows}</div>
204
+ </div>
205
+ {items.length > 7 && (
206
+ <div className="shrink-0 border-t px-4 py-2">
207
+ <Button variant="ghost" size="sm" className="text-muted-foreground">
208
+ View all {items.length}
209
+ </Button>
210
+ </div>
211
+ )}
212
+ </div>;
213
+ ```
214
+
215
+ `min-h-0` is the load-bearing class — without it the flex child refuses to
216
+ shrink and nothing scrolls. Test at the sm viewport preset, where the failure
217
+ actually shows.
218
+
219
+ ## 6. Button pile-up → one action + menu
220
+
221
+ **Failure:** two or three buttons per row; the row becomes a toolbar.
222
+
223
+ ```tsx
224
+ // AFTER — one visible action, the rest behind a menu
225
+ import {
226
+ DropdownMenu,
227
+ DropdownMenuContent,
228
+ DropdownMenuItem,
229
+ DropdownMenuTrigger,
230
+ } from '@ekanos/ui/dropdown-menu';
231
+
232
+ <div className="flex shrink-0 items-center gap-2">
233
+ <Button size="sm">Pay now</Button>
234
+ <DropdownMenu>
235
+ <DropdownMenuTrigger
236
+ render={<Button variant="ghost" size="icon" className="h-8 w-8" />}
237
+ >
238
+ <Icon name="fa-light fa-ellipsis-vertical" className="h-4 w-4" />
239
+ </DropdownMenuTrigger>
240
+ <DropdownMenuContent align="end">
241
+ <DropdownMenuItem>View details</DropdownMenuItem>
242
+ <DropdownMenuItem>Payment history</DropdownMenuItem>
243
+ </DropdownMenuContent>
244
+ </DropdownMenu>
245
+ </div>;
246
+ ```
247
+
248
+ The one visible action should be the urgent one; if no action is urgent, all
249
+ of them belong in the menu.
250
+
251
+ ## 7. Blank empty state → designed empty state
252
+
253
+ **Failure:** an empty array renders an empty card, indistinguishable from a
254
+ bug. Empty is a state you design, distinct from error.
255
+
256
+ ```tsx
257
+ // AFTER — icon, one sentence of what appears here, what action produces it
258
+ <div className="flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center">
259
+ <div className="bg-muted flex h-16 w-16 items-center justify-center rounded-full">
260
+ <Icon name="fa-light fa-inbox" className="text-muted-foreground h-8 w-8" />
261
+ </div>
262
+ <p className="text-base font-medium">No invoices yet</p>
263
+ <p className="text-muted-foreground max-w-xs text-sm">
264
+ Invoices appear here as soon as your first one is issued.
265
+ </p>
266
+ </div>
267
+ ```
268
+
269
+ No CTA unless there is a real action the user can take from the dashboard.