@ouraihub/aether-ui 0.0.0-stage → 0.1.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 (179) hide show
  1. package/LICENSE +789 -0
  2. package/fixtures/model-classify.json +42 -0
  3. package/fixtures/pricing-cost.json +56 -0
  4. package/fixtures/reasoning-infer.json +44 -0
  5. package/fixtures/sse/anthropic-messages/chinese.sse +24 -0
  6. package/fixtures/sse/anthropic-messages/multi-tool.sse +33 -0
  7. package/fixtures/sse/anthropic-messages/single-tool.sse +39 -0
  8. package/fixtures/sse/anthropic-messages/text.sse +27 -0
  9. package/fixtures/sse/anthropic-messages/truncated.sse +21 -0
  10. package/fixtures/sse/anthropic-messages/upstream-error.sse +12 -0
  11. package/fixtures/sse/expect.json +20 -0
  12. package/fixtures/sse/openai-chat/chinese.sse +14 -0
  13. package/fixtures/sse/openai-chat/multi-tool.sse +18 -0
  14. package/fixtures/sse/openai-chat/single-tool.sse +22 -0
  15. package/fixtures/sse/openai-chat/text.sse +14 -0
  16. package/fixtures/sse/openai-chat/truncated.sse +12 -0
  17. package/fixtures/sse/openai-chat/upstream-error.sse +6 -0
  18. package/fixtures/sse/openai-responses/chinese.sse +33 -0
  19. package/fixtures/sse/openai-responses/multi-tool.sse +36 -0
  20. package/fixtures/sse/openai-responses/single-tool.sse +27 -0
  21. package/fixtures/sse/openai-responses/text.sse +33 -0
  22. package/fixtures/sse/openai-responses/truncated.sse +30 -0
  23. package/fixtures/sse/openai-responses/upstream-error.sse +9 -0
  24. package/package.json +71 -3
  25. package/src/CopyInput.svelte +33 -0
  26. package/src/KitNotifier.svelte +33 -0
  27. package/src/chat/ApprovalCard.svelte +23 -0
  28. package/src/chat/ChatPanel.svelte +2097 -0
  29. package/src/chat/MessageItem.svelte +583 -0
  30. package/src/chat/SkillPicker.svelte +126 -0
  31. package/src/chat/ToolCallItem.svelte +23 -0
  32. package/src/chat/UsageDetails.svelte +138 -0
  33. package/src/chat/index.ts +2 -0
  34. package/src/chat/run-session.svelte.ts +556 -0
  35. package/src/components/ui/alert/alert-action.svelte +20 -0
  36. package/src/components/ui/alert/alert-description.svelte +23 -0
  37. package/src/components/ui/alert/alert-title.svelte +23 -0
  38. package/src/components/ui/alert/alert.svelte +43 -0
  39. package/src/components/ui/alert/index.ts +17 -0
  40. package/src/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  41. package/src/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  42. package/src/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
  43. package/src/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  44. package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  45. package/src/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  46. package/src/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
  47. package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
  48. package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  49. package/src/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  50. package/src/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  51. package/src/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  52. package/src/components/ui/alert-dialog/index.ts +40 -0
  53. package/src/components/ui/button/button.svelte +81 -0
  54. package/src/components/ui/button/index.ts +17 -0
  55. package/src/components/ui/dialog/dialog-close.svelte +11 -0
  56. package/src/components/ui/dialog/dialog-content.svelte +48 -0
  57. package/src/components/ui/dialog/dialog-description.svelte +17 -0
  58. package/src/components/ui/dialog/dialog-footer.svelte +32 -0
  59. package/src/components/ui/dialog/dialog-header.svelte +20 -0
  60. package/src/components/ui/dialog/dialog-overlay.svelte +17 -0
  61. package/src/components/ui/dialog/dialog-portal.svelte +7 -0
  62. package/src/components/ui/dialog/dialog-title.svelte +17 -0
  63. package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
  64. package/src/components/ui/dialog/dialog.svelte +7 -0
  65. package/src/components/ui/dialog/index.ts +34 -0
  66. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  67. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  68. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  69. package/src/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  70. package/src/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  71. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  72. package/src/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  73. package/src/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  74. package/src/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +17 -0
  75. package/src/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  76. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  77. package/src/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  78. package/src/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  79. package/src/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  80. package/src/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  81. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  82. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  83. package/src/components/ui/dropdown-menu/index.ts +54 -0
  84. package/src/components/ui/empty/empty-content.svelte +23 -0
  85. package/src/components/ui/empty/empty-description.svelte +23 -0
  86. package/src/components/ui/empty/empty-header.svelte +20 -0
  87. package/src/components/ui/empty/empty-media.svelte +41 -0
  88. package/src/components/ui/empty/empty-title.svelte +20 -0
  89. package/src/components/ui/empty/empty.svelte +23 -0
  90. package/src/components/ui/empty/index.ts +22 -0
  91. package/src/components/ui/field/field-content.svelte +20 -0
  92. package/src/components/ui/field/field-description.svelte +25 -0
  93. package/src/components/ui/field/field-error.svelte +58 -0
  94. package/src/components/ui/field/field-group.svelte +23 -0
  95. package/src/components/ui/field/field-label.svelte +25 -0
  96. package/src/components/ui/field/field-legend.svelte +24 -0
  97. package/src/components/ui/field/field-separator.svelte +35 -0
  98. package/src/components/ui/field/field-set.svelte +20 -0
  99. package/src/components/ui/field/field-title.svelte +20 -0
  100. package/src/components/ui/field/field.svelte +47 -0
  101. package/src/components/ui/field/index.ts +33 -0
  102. package/src/components/ui/input/index.ts +7 -0
  103. package/src/components/ui/input/input.svelte +48 -0
  104. package/src/components/ui/label/index.ts +7 -0
  105. package/src/components/ui/label/label.svelte +20 -0
  106. package/src/components/ui/select/index.ts +40 -0
  107. package/src/components/ui/select/select-content.svelte +45 -0
  108. package/src/components/ui/select/select-group-heading.svelte +21 -0
  109. package/src/components/ui/select/select-group.svelte +17 -0
  110. package/src/components/ui/select/select-item.svelte +43 -0
  111. package/src/components/ui/select/select-label.svelte +20 -0
  112. package/src/components/ui/select/select-portal.svelte +7 -0
  113. package/src/components/ui/select/select-scroll-down-button.svelte +20 -0
  114. package/src/components/ui/select/select-scroll-up-button.svelte +20 -0
  115. package/src/components/ui/select/select-separator.svelte +18 -0
  116. package/src/components/ui/select/select-trigger.svelte +29 -0
  117. package/src/components/ui/select/select-value.svelte +7 -0
  118. package/src/components/ui/select/select.svelte +11 -0
  119. package/src/components/ui/separator/index.ts +7 -0
  120. package/src/components/ui/separator/separator.svelte +21 -0
  121. package/src/components/ui/sonner/index.ts +1 -0
  122. package/src/components/ui/sonner/sonner.svelte +40 -0
  123. package/src/components/ui/switch/index.ts +7 -0
  124. package/src/components/ui/switch/switch.svelte +34 -0
  125. package/src/components/ui/textarea/index.ts +7 -0
  126. package/src/components/ui/textarea/textarea.svelte +22 -0
  127. package/src/context.ts +34 -0
  128. package/src/dev/Demo.svelte +45 -0
  129. package/src/dev/main.ts +7 -0
  130. package/src/i18n/en.ts +617 -0
  131. package/src/i18n/index.ts +23 -0
  132. package/src/i18n/zh-CN.ts +599 -0
  133. package/src/index.ts +10 -0
  134. package/src/logs/LogPanel.svelte +97 -0
  135. package/src/logs/LogSettings.svelte +56 -0
  136. package/src/logs/index.ts +5 -0
  137. package/src/logs/logger.ts +96 -0
  138. package/src/markdown.ts +8 -0
  139. package/src/mcp/McpClientSettings.svelte +491 -0
  140. package/src/mcp/McpServerSettings.svelte +327 -0
  141. package/src/mcp/config.ts +26 -0
  142. package/src/mcp/index.ts +3 -0
  143. package/src/mcp/input.ts +73 -0
  144. package/src/notify.svelte.ts +43 -0
  145. package/src/ports/contract.ts +165 -0
  146. package/src/ports/fake.ts +484 -0
  147. package/src/ports/index.ts +7 -0
  148. package/src/ports/search.ts +47 -0
  149. package/src/ports/skill-sources.ts +45 -0
  150. package/src/providers/EndpointKeysDialog.svelte +343 -0
  151. package/src/providers/EndpointPanel.svelte +491 -0
  152. package/src/providers/ModelPickerDialog.svelte +187 -0
  153. package/src/providers/ModelSelect.svelte +434 -0
  154. package/src/providers/ModelSettingsDialog.svelte +453 -0
  155. package/src/providers/ProvidersSettings.svelte +631 -0
  156. package/src/providers/RequestConfigDialog.svelte +250 -0
  157. package/src/providers/SecretInput.svelte +276 -0
  158. package/src/providers/draft.ts +329 -0
  159. package/src/providers/edit-session.ts +146 -0
  160. package/src/providers/index.ts +5 -0
  161. package/src/providers/reasoning.ts +41 -0
  162. package/src/providers/revision.svelte.ts +4 -0
  163. package/src/search/SearchSettings.svelte +703 -0
  164. package/src/search/index.ts +13 -0
  165. package/src/search/settings-flow.ts +62 -0
  166. package/src/session.ts +20 -0
  167. package/src/skills/SkillAdd.svelte +347 -0
  168. package/src/skills/SkillAuthoring.svelte +381 -0
  169. package/src/skills/SkillEditor.svelte +94 -0
  170. package/src/skills/SkillPreviewDialog.svelte +122 -0
  171. package/src/skills/SkillsSettings.svelte +140 -0
  172. package/src/skills/error.ts +32 -0
  173. package/src/skills/index.ts +4 -0
  174. package/src/styles/kit.css +137 -0
  175. package/src/styles/tokens.css +22 -0
  176. package/src/tauri/index.ts +282 -0
  177. package/src/utils.ts +22 -0
  178. package/src/web/index.ts +376 -0
  179. package/README.md +0 -3
@@ -0,0 +1,140 @@
1
+ <script lang="ts">
2
+ // Skill list, import → full-text preview → confirm, view, edit, duplicate, reorder, delete (docs/16 §4.3).
3
+ import { Button } from '#lib/components/ui/button/index.js'
4
+ import * as Empty from '#lib/components/ui/empty/index.js'
5
+ import { getKitContext } from '../context'
6
+ import type { SkillDto, SkillMeta } from '../ports'
7
+ import { errorText } from './error'
8
+ import SkillEditor from './SkillEditor.svelte'
9
+ import SkillPreviewDialog from './SkillPreviewDialog.svelte'
10
+ import SkillAdd from './SkillAdd.svelte'
11
+
12
+ const { ports, i18n, notify } = getKitContext()
13
+ const { t } = i18n
14
+ const port = ports.skills
15
+
16
+ let skills = $state<SkillMeta[]>([])
17
+ let busy = $state(false)
18
+ let viewing = $state<SkillDto | null>(null)
19
+ let viewOpen = $state(false)
20
+ let editing = $state<SkillDto | null>(null)
21
+ let editOpen = $state(false)
22
+
23
+ async function act(run: () => Promise<void>) {
24
+ busy = true
25
+ try {
26
+ await run()
27
+ } catch (error) {
28
+ notify.toast('error', errorText(t, error))
29
+ } finally {
30
+ busy = false
31
+ }
32
+ }
33
+
34
+ if (port) void act(async () => void (skills = await port.list()))
35
+
36
+ function view(id: string) {
37
+ if (!port) return
38
+ void act(async () => {
39
+ viewing = await port.get(id)
40
+ viewOpen = true
41
+ })
42
+ }
43
+
44
+ function edit(id: string | null) {
45
+ if (!port) return
46
+ void act(async () => {
47
+ editing = id === null ? null : await port.get(id)
48
+ editOpen = true
49
+ })
50
+ }
51
+
52
+ function duplicate(skill: SkillMeta) {
53
+ if (!port) return
54
+ void act(async () => void (skills = await port.duplicate(skill.id, `${skill.name}-copy`)))
55
+ }
56
+
57
+ function move(index: number, by: number) {
58
+ if (!port) return
59
+ const ids = skills.map((s) => s.id)
60
+ const [id] = ids.splice(index, 1)
61
+ if (id === undefined) return
62
+ ids.splice(index + by, 0, id)
63
+ void act(async () => void (skills = await port.reorder(ids)))
64
+ }
65
+
66
+ async function remove(skill: SkillMeta) {
67
+ if (!port) return
68
+ const ok = await notify.confirm({
69
+ title: t('skills.delete'),
70
+ body: t('skills.delete_confirm', { name: skill.name }),
71
+ danger: true,
72
+ })
73
+ if (ok) void act(async () => void (skills = await port.remove(skill.id)))
74
+ }
75
+ </script>
76
+
77
+ {#if port}
78
+ <section class="flex flex-col gap-4" aria-label={t('skills.title')}>
79
+ <SkillAdd disabled={busy} oninstalled={(next) => (skills = next)} onmanual={() => edit(null)} />
80
+ {#if skills.length === 0}
81
+ <Empty.Root>
82
+ <Empty.Header>
83
+ <Empty.Title>{t('skills.empty')}</Empty.Title>
84
+ <Empty.Description>{t('skills.empty_hint')}</Empty.Description>
85
+ </Empty.Header>
86
+ </Empty.Root>
87
+ {:else}
88
+ <ul class="flex flex-col gap-2" aria-label={t('skills.list')}>
89
+ {#each skills as skill, index (skill.id)}
90
+ <li class="flex items-center gap-3 rounded-md border p-3">
91
+ <div class="flex min-w-0 flex-1 flex-col">
92
+ <span class="flex min-w-0 items-center gap-2">
93
+ <span class="truncate font-mono text-sm">{skill.name}</span>
94
+ {#if skill.hasScripts}
95
+ <span class="shrink-0 rounded border px-1.5 text-xs">{t('skills.has_scripts')}</span>
96
+ {/if}
97
+ </span>
98
+ <span class="text-muted-foreground truncate text-xs">
99
+ {skill.version ?? t('skills.unversioned')} · {t(`skills.origin.${skill.origin}`)} ·
100
+ {t('skills.revision', { revision: skill.revision })} · {t('skills.chars', { count: skill.bodyChars })}
101
+ {#if skill.fileCount > 0}· {t('skills.file_count', { count: skill.fileCount })}{/if}
102
+ </span>
103
+ </div>
104
+ <Button
105
+ size="sm"
106
+ variant="ghost"
107
+ disabled={busy || index === 0}
108
+ aria-label={t('skills.up', { name: skill.name })}
109
+ onclick={() => move(index, -1)}>↑</Button
110
+ >
111
+ <Button
112
+ size="sm"
113
+ variant="ghost"
114
+ disabled={busy || index === skills.length - 1}
115
+ aria-label={t('skills.down', { name: skill.name })}
116
+ onclick={() => move(index, 1)}>↓</Button
117
+ >
118
+ <Button size="sm" variant="outline" disabled={busy} onclick={() => view(skill.id)}
119
+ >{t('skills.view')}</Button
120
+ >
121
+ {#if skill.origin === 'builtin'}
122
+ <Button size="sm" variant="outline" disabled={busy} onclick={() => duplicate(skill)}
123
+ >{t('skills.duplicate')}</Button
124
+ >
125
+ {:else}
126
+ <Button size="sm" variant="outline" disabled={busy} onclick={() => edit(skill.id)}
127
+ >{t('skills.edit')}</Button
128
+ >
129
+ <Button size="sm" variant="destructive" disabled={busy} onclick={() => void remove(skill)}
130
+ >{t('skills.delete')}</Button
131
+ >
132
+ {/if}
133
+ </li>
134
+ {/each}
135
+ </ul>
136
+ {/if}
137
+ </section>
138
+ <SkillPreviewDialog bind:open={viewOpen} skill={viewing} title={viewing?.name ?? ''} />
139
+ <SkillEditor bind:open={editOpen} {port} skill={editing} onsaved={(next) => (skills = next)} />
140
+ {/if}
@@ -0,0 +1,32 @@
1
+ // Error text for skill commands: the docs/14 reasons the user can act on, else the generic code text.
2
+ import { isKitClientError } from '../ports'
3
+ import type { KitI18n } from '../i18n'
4
+
5
+ export function errorText(t: KitI18n['t'], error: unknown): string {
6
+ if (!isKitClientError(error)) return t('errors.internal')
7
+ const { reason, field, limit } = error.details
8
+ switch (reason) {
9
+ case 'exists':
10
+ return t('skills.err.exists')
11
+ case 'builtin_name':
12
+ return t('skills.err.builtin_name')
13
+ case 'revision_changed':
14
+ return t('skills.err.revision_changed')
15
+ case 'secret_detected':
16
+ return t('skills.err.secret_detected', { field: field ?? '' })
17
+ case 'unsupported_files':
18
+ return t('skills.err.unsupported_files', { field: field ?? '' })
19
+ case 'too_large':
20
+ return t('skills.err.too_large', { field: field ?? '', limit: limit ?? '' })
21
+ case 'frontmatter_unsupported':
22
+ return t('skills.err.frontmatter', { field: field ?? '' })
23
+ case 'invalid_name':
24
+ return t('skills.err.invalid_name', { field: field ?? '' })
25
+ case 'too_deep':
26
+ return t('skills.err.too_deep', { field: field ?? '', limit: limit ?? '' })
27
+ case 'not_utf8':
28
+ return t('skills.err.not_utf8', { field: field ?? '' })
29
+ default:
30
+ return field ? `${t(`errors.${error.code}`)} (${field})` : t(`errors.${error.code}`)
31
+ }
32
+ }
@@ -0,0 +1,4 @@
1
+ // SkillsSettings, SkillPreviewDialog, SkillEditor (docs/16 §4.3). SkillPicker lives in ChatPanel.
2
+ export { default as SkillEditor } from './SkillEditor.svelte'
3
+ export { default as SkillPreviewDialog } from './SkillPreviewDialog.svelte'
4
+ export { default as SkillsSettings } from './SkillsSettings.svelte'
@@ -0,0 +1,137 @@
1
+ @import 'tailwindcss';
2
+ @import 'tw-animate-css';
3
+ @import 'shadcn-svelte/tailwind.css';
4
+ @import './tokens.css';
5
+ /* Generated by shadcn-svelte init; font import removed (fonts belong to the host). Hosts without shadcn import this file (docs/16 §3). */
6
+ @source "../";
7
+
8
+ @custom-variant dark (&:is(.dark *, [data-theme="dark"] *));
9
+
10
+ :root {
11
+ --background: oklch(0.995 0.002 265);
12
+ --foreground: oklch(0.21 0.01 265);
13
+ --card: oklch(0.995 0.002 265);
14
+ --card-foreground: oklch(0.21 0.01 265);
15
+ --popover: oklch(0.995 0.002 265);
16
+ --popover-foreground: oklch(0.21 0.01 265);
17
+ --primary: oklch(0.25 0.012 265);
18
+ --primary-foreground: oklch(0.985 0.002 265);
19
+ --secondary: oklch(0.965 0.004 265);
20
+ --secondary-foreground: oklch(0.25 0.012 265);
21
+ --muted: oklch(0.965 0.004 265);
22
+ --muted-foreground: oklch(0.5 0.01 265);
23
+ --accent: oklch(0.965 0.004 265);
24
+ --accent-foreground: oklch(0.25 0.012 265);
25
+ --destructive: oklch(0.577 0.245 27.325);
26
+ --border: oklch(0.915 0.005 265);
27
+ --input: oklch(0.915 0.005 265);
28
+ --ring: oklch(0.7 0.01 265);
29
+ --chart-1: oklch(0.87 0.005 265);
30
+ --chart-2: oklch(0.556 0.01 265);
31
+ --chart-3: oklch(0.439 0.01 265);
32
+ --chart-4: oklch(0.371 0.01 265);
33
+ --chart-5: oklch(0.269 0.01 265);
34
+ --success: oklch(0.6 0.15 150);
35
+ --warning: oklch(0.7 0.15 75);
36
+ --info: oklch(0.58 0.14 250);
37
+ --radius: var(--kit-radius);
38
+ --sidebar: oklch(0.98 0.003 265);
39
+ --sidebar-foreground: oklch(0.21 0.01 265);
40
+ --sidebar-primary: oklch(0.25 0.012 265);
41
+ --sidebar-primary-foreground: oklch(0.985 0.002 265);
42
+ --sidebar-accent: oklch(0.965 0.004 265);
43
+ --sidebar-accent-foreground: oklch(0.25 0.012 265);
44
+ --sidebar-border: oklch(0.915 0.005 265);
45
+ --sidebar-ring: oklch(0.7 0.01 265);
46
+ }
47
+
48
+ .dark,
49
+ [data-theme='dark'] {
50
+ --background: oklch(0.17 0.008 265);
51
+ --foreground: oklch(0.93 0.004 265);
52
+ --card: oklch(0.215 0.01 265);
53
+ --card-foreground: oklch(0.93 0.004 265);
54
+ --popover: oklch(0.215 0.01 265);
55
+ --popover-foreground: oklch(0.93 0.004 265);
56
+ --primary: oklch(0.9 0.006 265);
57
+ --primary-foreground: oklch(0.21 0.01 265);
58
+ --secondary: oklch(0.27 0.011 265);
59
+ --secondary-foreground: oklch(0.93 0.004 265);
60
+ --muted: oklch(0.27 0.011 265);
61
+ --muted-foreground: oklch(0.7 0.008 265);
62
+ --accent: oklch(0.27 0.011 265);
63
+ --accent-foreground: oklch(0.93 0.004 265);
64
+ --destructive: oklch(0.704 0.191 22.216);
65
+ --border: oklch(1 0 0 / 10%);
66
+ --input: oklch(1 0 0 / 15%);
67
+ --ring: oklch(0.556 0.01 265);
68
+ --chart-1: oklch(0.87 0.005 265);
69
+ --chart-2: oklch(0.556 0.01 265);
70
+ --chart-3: oklch(0.439 0.01 265);
71
+ --chart-4: oklch(0.371 0.01 265);
72
+ --chart-5: oklch(0.269 0.01 265);
73
+ --sidebar: oklch(0.215 0.01 265);
74
+ --sidebar-foreground: oklch(0.93 0.004 265);
75
+ --sidebar-primary: oklch(0.488 0.243 264.376);
76
+ --sidebar-primary-foreground: oklch(0.985 0.002 265);
77
+ --sidebar-accent: oklch(0.27 0.011 265);
78
+ --sidebar-accent-foreground: oklch(0.93 0.004 265);
79
+ --sidebar-border: oklch(1 0 0 / 10%);
80
+ --sidebar-ring: oklch(0.556 0.01 265);
81
+ --success: oklch(0.72 0.15 150);
82
+ --warning: oklch(0.8 0.15 80);
83
+ --info: oklch(0.72 0.13 250);
84
+ }
85
+
86
+ @theme static inline {
87
+ --color-success: var(--success);
88
+ --color-warning: var(--warning);
89
+ --color-info: var(--info);
90
+ --color-sidebar-ring: var(--sidebar-ring);
91
+ --color-sidebar-border: var(--sidebar-border);
92
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
93
+ --color-sidebar-accent: var(--sidebar-accent);
94
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
95
+ --color-sidebar-primary: var(--sidebar-primary);
96
+ --color-sidebar-foreground: var(--sidebar-foreground);
97
+ --color-sidebar: var(--sidebar);
98
+ --color-chart-5: var(--chart-5);
99
+ --color-chart-4: var(--chart-4);
100
+ --color-chart-3: var(--chart-3);
101
+ --color-chart-2: var(--chart-2);
102
+ --color-chart-1: var(--chart-1);
103
+ --color-ring: var(--ring);
104
+ --color-input: var(--input);
105
+ --color-border: var(--border);
106
+ --color-destructive: var(--destructive);
107
+ --color-accent-foreground: var(--accent-foreground);
108
+ --color-accent: var(--accent);
109
+ --color-muted-foreground: var(--muted-foreground);
110
+ --color-muted: var(--muted);
111
+ --color-secondary-foreground: var(--secondary-foreground);
112
+ --color-secondary: var(--secondary);
113
+ --color-primary-foreground: var(--primary-foreground);
114
+ --color-primary: var(--primary);
115
+ --color-popover-foreground: var(--popover-foreground);
116
+ --color-popover: var(--popover);
117
+ --color-card-foreground: var(--card-foreground);
118
+ --color-card: var(--card);
119
+ --color-foreground: var(--foreground);
120
+ --color-background: var(--background);
121
+ --radius-sm: calc(var(--radius) * 0.6);
122
+ --radius-md: calc(var(--radius) * 0.8);
123
+ --radius-lg: var(--radius);
124
+ --radius-xl: calc(var(--radius) * 1.4);
125
+ --radius-2xl: calc(var(--radius) * 1.8);
126
+ --radius-3xl: calc(var(--radius) * 2.2);
127
+ --radius-4xl: calc(var(--radius) * 2.6);
128
+ }
129
+
130
+ @layer base {
131
+ * {
132
+ @apply border-border outline-ring/50;
133
+ }
134
+ body {
135
+ @apply bg-background text-foreground;
136
+ }
137
+ }
@@ -0,0 +1,22 @@
1
+ /* Size tokens and their Tailwind utilities (docs/16 §3). Hosts with their own Tailwind entry import this after tailwindcss and override the --kit-* values. */
2
+ :root {
3
+ --kit-text-xs: 11.5px;
4
+ --kit-text-sm: 12px;
5
+ --kit-text-base: 13px;
6
+ --kit-text-lg: 13.5px;
7
+ --kit-control-h: 30px;
8
+ --kit-control-h-sm: 28px;
9
+ --kit-radius: 8px;
10
+ --kit-radius-sm: 6px;
11
+ }
12
+
13
+ @theme inline {
14
+ --text-kit-xs: var(--kit-text-xs);
15
+ --text-kit-sm: var(--kit-text-sm);
16
+ --text-kit-base: var(--kit-text-base);
17
+ --text-kit-lg: var(--kit-text-lg);
18
+ --spacing-kit-control: var(--kit-control-h);
19
+ --spacing-kit-control-sm: var(--kit-control-h-sm);
20
+ --radius-kit: var(--kit-radius);
21
+ --radius-kit-sm: var(--kit-radius-sm);
22
+ }
@@ -0,0 +1,282 @@
1
+ // The only directory allowed to import @tauri-apps/* (ESLint enforces): createTauriPorts() (docs/16 §2).
2
+ // Argument names are the camelCase of the Rust command parameters (docs/15 §5).
3
+ import { Channel, invoke } from '@tauri-apps/api/core'
4
+ import { KitClientError } from '../ports'
5
+ import type {
6
+ DesktopActions,
7
+ KitError,
8
+ KitPorts,
9
+ LogsPort,
10
+ PreparedSkill,
11
+ RunAccepted,
12
+ RunEvent,
13
+ SearchPort,
14
+ } from '../ports'
15
+
16
+ /** Which feature ports the host enabled; the rest stay absent so their UI areas are hidden. */
17
+ export interface TauriPortsOptions {
18
+ providers?: boolean
19
+ search?: boolean
20
+ chat?: boolean
21
+ /** Independent conversation storage capability (`aether:chat-history`). */
22
+ conversations?: boolean
23
+ skills?: boolean
24
+ /** Read-only discovery/pickers; generation additionally requires chat and providers. */
25
+ skillSources?: boolean
26
+ mcpClient?: boolean
27
+ mcpServer?: boolean
28
+ }
29
+
30
+ const CANCELLED: KitError = { code: 'cancelled', message: 'cancelled' }
31
+
32
+ /** Invokes `plugin:aether|<command>` and turns any rejection into a KitClientError at the boundary. */
33
+ async function call<T>(command: string, args: Record<string, unknown> = {}): Promise<T> {
34
+ try {
35
+ return await invoke<T>(`plugin:aether|${command}`, args)
36
+ } catch (error) {
37
+ const isKitError =
38
+ typeof error === 'object' &&
39
+ error !== null &&
40
+ typeof (error as Partial<KitError>).code === 'string' &&
41
+ typeof (error as Partial<KitError>).message === 'string'
42
+ throw new KitClientError(isKitError ? (error as KitError) : { code: 'internal', message: String(error) })
43
+ }
44
+ }
45
+
46
+ /** The plugin always registers the log commands, so desktop hosts always get `logs`. */
47
+ export function createTauriPorts(
48
+ options: TauriPortsOptions = {},
49
+ ): KitPorts & { logs: LogsPort; desktop: DesktopActions } {
50
+ const ports: KitPorts & { logs: LogsPort; desktop: DesktopActions } = {
51
+ logs: {
52
+ tail: (limit) => call('logs_tail', { limit }),
53
+ ingest: (batch) => call('logs_ingest', { batch }),
54
+ getLevel: () => call('logs_get_level'),
55
+ setLevel: (level) => call('logs_set_level', { level }),
56
+ },
57
+ desktop: { openLogsDirectory: () => call('logs_open_dir') },
58
+ }
59
+ if (options.providers) {
60
+ ports.providers = {
61
+ list: () => call('providers_list'),
62
+ setEnabled: (id, enabled) => call('providers_set_enabled', { id, enabled }),
63
+ save: (input) => call('providers_save', { input }),
64
+ remove: (id) => call('providers_delete', { id }),
65
+ modelOptions: (filter = {}) => call('providers_model_options', { filter }),
66
+ // Aborting calls providers_cancel; the pending probe then rejects with `cancelled`.
67
+ probe: async (target, callOptions) => {
68
+ const signal = callOptions?.signal
69
+ if (signal?.aborted) throw new KitClientError(CANCELLED)
70
+ const jobId = crypto.randomUUID()
71
+ const cancel = () => void call('providers_cancel', { jobId }).catch(() => undefined)
72
+ signal?.addEventListener('abort', cancel, { once: true })
73
+ try {
74
+ return await call('providers_probe', { jobId, target })
75
+ } finally {
76
+ signal?.removeEventListener('abort', cancel)
77
+ }
78
+ },
79
+ revealNeedsPassword: false,
80
+ keyStrategies: false,
81
+ saveKey: (input) => call('providers_key_save', { input }),
82
+ removeKey: (endpointId, keyId) => call('providers_key_delete', { endpointId, keyId }),
83
+ revealKey: (keyId) => call('providers_key_reveal', { keyId }),
84
+ }
85
+ }
86
+ if (options.search) {
87
+ const searchCall = async <T>(
88
+ command: string,
89
+ args: Record<string, unknown> = {},
90
+ callOptions?: { signal?: AbortSignal },
91
+ ): Promise<T> => {
92
+ const signal = callOptions?.signal
93
+ if (signal?.aborted) throw new KitClientError(CANCELLED)
94
+ const jobId = crypto.randomUUID()
95
+ const cancel = () => void call('search_cancel', { jobId }).catch(() => undefined)
96
+ signal?.addEventListener('abort', cancel, { once: true })
97
+ try {
98
+ return await call<T>(command, { ...args, jobId })
99
+ } finally {
100
+ signal?.removeEventListener('abort', cancel)
101
+ }
102
+ }
103
+ const search: SearchPort = {
104
+ settings: (capability, o) => searchCall('search_settings', { capability }, o),
105
+ save: (input, o) => searchCall('search_save', { input }, o),
106
+ setDefault: (capability, provider, o) => searchCall('search_set_default', { capability, provider }, o),
107
+ savePreferences: (input, o) => searchCall('search_save_preferences', { input }, o),
108
+ saveKey: (capability, provider, value, keyId, o) =>
109
+ searchCall('search_save_key', { capability, provider, value, keyId: keyId ?? null }, o),
110
+ removeKey: (capability, provider, keyId, o) =>
111
+ searchCall('search_remove_key', { capability, provider, keyId }, o),
112
+ reveal: (capability, provider, keyId, currentPassword, o) =>
113
+ searchCall('search_reveal', { capability, provider, keyId, currentPassword: currentPassword ?? null }, o),
114
+ check: (capability, provider, o) => searchCall('search_check', { capability, provider }, o),
115
+ search: (query, o) => searchCall('search_query', { query }, o),
116
+ fetch: (url, o) => searchCall('search_fetch', { url }, o),
117
+ }
118
+ ports.search = search
119
+ }
120
+ if (options.chat) {
121
+ ports.chat = {
122
+ start: async (request, onEvent, callOptions) => {
123
+ const signal = callOptions?.signal
124
+ if (signal?.aborted) throw new KitClientError(CANCELLED)
125
+ let accepted: RunAccepted | null = null
126
+ let ended = false
127
+ let requested = false
128
+ let dispatched = false
129
+ const cleanup = () => signal?.removeEventListener('abort', cancel)
130
+ const dispatchCancel = () => {
131
+ if (!accepted || ended || dispatched) return
132
+ dispatched = true
133
+ void call('chat_cancel', { runId: accepted.runId }).catch(() => undefined)
134
+ }
135
+ const cancel = () => {
136
+ requested = true
137
+ dispatchCancel()
138
+ }
139
+ signal?.addEventListener('abort', cancel, { once: true })
140
+ const channel = new Channel<RunEvent>((event) => {
141
+ if (ended) return
142
+ if (event.kind === 'end') {
143
+ ended = true
144
+ cleanup()
145
+ }
146
+ onEvent(event)
147
+ })
148
+ try {
149
+ accepted = await call<RunAccepted>('chat_run', { request, onEvent: channel })
150
+ if (requested || signal?.aborted) dispatchCancel()
151
+ return accepted
152
+ } catch (error) {
153
+ ended = true
154
+ cleanup()
155
+ throw error
156
+ }
157
+ },
158
+ cancel: (runId) => call('chat_cancel', { runId }),
159
+ getSnapshot: (runId) => call('chat_snapshot', { runId }),
160
+ approve: async (runId, callId, allow) => {
161
+ await call('chat_approve', { runId, callId, allow })
162
+ },
163
+ }
164
+ }
165
+ if (options.conversations) {
166
+ ports.conversations = {
167
+ list: (scope) => call('chat_conversations', { scope: scope ?? null }),
168
+ get: (id) => call('chat_conversation', { id }),
169
+ remove: async (id) => {
170
+ await call('chat_delete', { id })
171
+ },
172
+ rename: (id, title) => call('chat_rename', { id, title }),
173
+ editTurn: (conversationId, turnId, text, expectedText) =>
174
+ call('chat_edit_turn', { conversationId, turnId, text, expectedText }),
175
+ removeTurn: (conversationId, turnId) => call('chat_remove_turn', { conversationId, turnId }),
176
+ fork: (conversationId, turnId) => call('chat_fork', { conversationId, turnId }),
177
+ selectTurn: (conversationId, turnId) => call('chat_select_turn', { conversationId, turnId }),
178
+ }
179
+ }
180
+ if (options.skills) {
181
+ ports.skills = {
182
+ list: () => call('skills_list'),
183
+ get: (id) => call('skills_get', { id }),
184
+ save: (input) => call('skills_save', { input }),
185
+ preview: (source) => call('skills_preview', { source }),
186
+ install: (source, confirmedDigest, replace) => call('skills_install', { source, confirmedDigest, replace }),
187
+ duplicate: (id, newName) => call('skills_duplicate', { id, newName }),
188
+ reorder: (ids) => call('skills_reorder', { ids }),
189
+ remove: (id) => call('skills_delete', { id }),
190
+ }
191
+ }
192
+ if (options.skillSources) {
193
+ const sourceCall = async <T>(
194
+ command: string,
195
+ args: Record<string, unknown>,
196
+ o?: { signal?: AbortSignal },
197
+ ): Promise<T> => {
198
+ if (o?.signal?.aborted) throw new KitClientError(CANCELLED)
199
+ const result = await call<T>(command, args)
200
+ if (o?.signal?.aborted) throw new KitClientError(CANCELLED)
201
+ return result
202
+ }
203
+ ports.skillSources = {
204
+ search: (query, o) => sourceCall('skills_source_search', { query }, o),
205
+ prepare: (url, o) => sourceCall('skills_source_prepare', { url }, o),
206
+ discover: (o) => sourceCall('skills_system_discover', {}, o),
207
+ prepareSystem: (path, o) => sourceCall('skills_system_prepare', { path }, o),
208
+ pickDirectory: (o) => sourceCall('skills_pick_directory', {}, o),
209
+ pickSearchDirectory: (o) => sourceCall('skills_pick_search_directory', {}, o),
210
+ }
211
+ if (options.chat && options.providers) {
212
+ ports.skillSources.authoring = {
213
+ create: (o) => sourceCall<string>('skills_authoring_create', {}, o),
214
+ send: async (sessionId, input, model, onEvent, o) => {
215
+ const signal = o?.signal
216
+ if (signal?.aborted) throw new KitClientError(CANCELLED)
217
+ let accepted: RunAccepted | null = null
218
+ let ended = false
219
+ const cancel = () => {
220
+ if (accepted && !ended)
221
+ void call('skills_authoring_cancel', { sessionId, runId: accepted.runId }).catch(() => undefined)
222
+ }
223
+ signal?.addEventListener('abort', cancel, { once: true })
224
+ const channel = new Channel<RunEvent>((event) => {
225
+ if (ended) return
226
+ if (event.kind === 'end') {
227
+ ended = true
228
+ signal?.removeEventListener('abort', cancel)
229
+ }
230
+ onEvent(event)
231
+ })
232
+ try {
233
+ accepted = await call<RunAccepted>('skills_authoring_send', { sessionId, input, model, onEvent: channel })
234
+ if (signal?.aborted) cancel()
235
+ return accepted
236
+ } catch (error) {
237
+ ended = true
238
+ signal?.removeEventListener('abort', cancel)
239
+ throw error
240
+ }
241
+ },
242
+ approve: async (sessionId, runId, callId, allow) => {
243
+ await call('skills_authoring_approve', { sessionId, runId, callId, allow })
244
+ },
245
+ cancel: async (sessionId, runId) => {
246
+ await call('skills_authoring_cancel', { sessionId, runId })
247
+ },
248
+ prepare: (sessionId, o) => sourceCall<PreparedSkill>('skills_authoring_prepare', { sessionId }, o),
249
+ close: async (sessionId) => {
250
+ await call('skills_authoring_close', { sessionId })
251
+ },
252
+ }
253
+ }
254
+ }
255
+ if (options.mcpClient) {
256
+ ports.mcpClient = {
257
+ list: () => call('mcp_client_list'),
258
+ save: (input) => call('mcp_client_save', { input }),
259
+ remove: (id) => call('mcp_client_delete', { id }),
260
+ refresh: (serverId) => call('mcp_client_refresh', { serverId }),
261
+ setToolApproval: async (serverId, tool, approval) => {
262
+ await call('mcp_client_set_tool_approval', { serverId, tool, approval })
263
+ },
264
+ }
265
+ }
266
+ if (options.mcpServer) {
267
+ ports.mcpServer = {
268
+ status: () => call('mcp_server_status'),
269
+ tokens: () => call('mcp_server_tokens'),
270
+ start: () => call('mcp_server_start'),
271
+ stop: () => call('mcp_server_stop'),
272
+ setPort: (port) => call('mcp_server_set_port', { port }),
273
+ issueToken: (name, scopes) => call('mcp_server_issue_token', { name, scopes }),
274
+ revokeToken: async (id) => {
275
+ await call('mcp_server_revoke_token', { id })
276
+ },
277
+ scopes: () => call('mcp_server_scopes'),
278
+ setScopes: (ids) => call('mcp_server_set_scopes', { ids }),
279
+ }
280
+ }
281
+ return ports
282
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { createCn } from 'cn/config'
2
+ import { createTV } from 'tailwind-variants'
3
+ export type { VariantProps } from 'tailwind-variants'
4
+
5
+ const twMergeConfig = {
6
+ extend: {
7
+ theme: {
8
+ text: ['kit-xs', 'kit-sm', 'kit-base', 'kit-lg'],
9
+ spacing: ['kit-control', 'kit-control-sm'],
10
+ radius: ['kit', 'kit-sm'],
11
+ },
12
+ },
13
+ }
14
+ export const cn = createCn(twMergeConfig)
15
+ export const tv = createTV({ twMergeConfig })
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T
21
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>
22
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }