@interactive-inc/claude-funnel 0.10.0 → 0.15.1
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.
- package/README.md +106 -56
- package/dist/bin.js +557 -530
- package/dist/connectors/schedule.d.ts +2 -49
- package/dist/connectors/schedule.js +1 -1
- package/dist/connectors/slack.d.ts +4 -48
- package/dist/connectors/slack.js +1 -1
- package/dist/gateway/daemon.js +213 -211
- package/dist/index.d.ts +465 -173
- package/dist/index.js +692 -154
- package/dist/{schedule-connector-schema-CkuIQ0JQ.js → schedule-connector-schema-FxP7LPlx.js} +11 -0
- package/dist/{file-system-Co60LrmR.d.ts → schedule-listener-BPodvbld.d.ts} +56 -1
- package/dist/{slack-connector-schema-Cd22WiHB.js → slack-connector-schema-B4hsf3AY.js} +10 -1
- package/dist/slack-listener-CHj6uMY-.d.ts +74 -0
- package/package.json +2 -6
- package/schemas/funnel.schema.json +144 -0
- package/dist/slack-connector-schema-D7zAHN8k.d.ts +0 -15
- package/lib/bin.ts +0 -3
- package/lib/cli/factory.ts +0 -10
- package/lib/cli/index.ts +0 -85
- package/lib/cli/router/query-to-cli-args.ts +0 -20
- package/lib/cli/router/to-request.ts +0 -113
- package/lib/cli/router/validator.ts +0 -27
- package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +0 -27
- package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +0 -40
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +0 -41
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +0 -22
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +0 -23
- package/lib/cli/routes/channels.$channel.connectors.$connector.ts +0 -26
- package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +0 -92
- package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +0 -22
- package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +0 -63
- package/lib/cli/routes/channels.$channel.connectors.ts +0 -26
- package/lib/cli/routes/channels.$channel.publish.ts +0 -52
- package/lib/cli/routes/channels.$channel.rename.$newName.ts +0 -22
- package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +0 -34
- package/lib/cli/routes/channels.$channel.ts +0 -34
- package/lib/cli/routes/channels.add.$channel.ts +0 -33
- package/lib/cli/routes/channels.remove.$channel.ts +0 -20
- package/lib/cli/routes/channels.ts +0 -39
- package/lib/cli/routes/claude.ts +0 -70
- package/lib/cli/routes/gateway.listeners.ts +0 -41
- package/lib/cli/routes/gateway.logs.ts +0 -123
- package/lib/cli/routes/gateway.restart.ts +0 -50
- package/lib/cli/routes/gateway.run.ts +0 -41
- package/lib/cli/routes/gateway.start.ts +0 -50
- package/lib/cli/routes/gateway.status.ts +0 -19
- package/lib/cli/routes/gateway.stop.ts +0 -32
- package/lib/cli/routes/gateway.ts +0 -55
- package/lib/cli/routes/index.ts +0 -219
- package/lib/cli/routes/profiles.$profile.as-default.ts +0 -22
- package/lib/cli/routes/profiles.$profile.rename.$newName.ts +0 -22
- package/lib/cli/routes/profiles.$profile.run.ts +0 -36
- package/lib/cli/routes/profiles.add.$profile.ts +0 -49
- package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
- package/lib/cli/routes/profiles.set.$profile.ts +0 -45
- package/lib/cli/routes/profiles.ts +0 -40
- package/lib/cli/routes/status.ts +0 -93
- package/lib/cli/routes/update.ts +0 -27
- package/lib/connectors/connector-adapter.ts +0 -9
- package/lib/connectors/connector-config-schema.ts +0 -16
- package/lib/connectors/connector-factory.ts +0 -94
- package/lib/connectors/connector-listener.ts +0 -20
- package/lib/connectors/discord-adapter.ts +0 -51
- package/lib/connectors/discord-connector-schema.ts +0 -12
- package/lib/connectors/discord-event-processor.ts +0 -48
- package/lib/connectors/discord-listener.ts +0 -111
- package/lib/connectors/discord.ts +0 -4
- package/lib/connectors/gh-adapter.ts +0 -48
- package/lib/connectors/gh-connector-schema.ts +0 -12
- package/lib/connectors/gh-listener.ts +0 -137
- package/lib/connectors/gh.ts +0 -3
- package/lib/connectors/match-cron.ts +0 -78
- package/lib/connectors/schedule-connector-schema.ts +0 -33
- package/lib/connectors/schedule-listener.ts +0 -207
- package/lib/connectors/schedule-state-store.ts +0 -54
- package/lib/connectors/schedule.ts +0 -4
- package/lib/connectors/slack-adapter.ts +0 -36
- package/lib/connectors/slack-connector-schema.ts +0 -13
- package/lib/connectors/slack-event-processor.ts +0 -97
- package/lib/connectors/slack-listener.ts +0 -97
- package/lib/connectors/slack.ts +0 -4
- package/lib/engine/channels/channels.ts +0 -520
- package/lib/engine/claude/claude.ts +0 -205
- package/lib/engine/claude/gateway-controller.ts +0 -4
- package/lib/engine/fs/file-system.ts +0 -23
- package/lib/engine/fs/memory-file-system.ts +0 -102
- package/lib/engine/fs/node-file-system.ts +0 -68
- package/lib/engine/http/http-client.ts +0 -17
- package/lib/engine/http/memory-http-client.ts +0 -36
- package/lib/engine/http/node-http-client.ts +0 -23
- package/lib/engine/id/id-generator.ts +0 -7
- package/lib/engine/id/memory-id-generator.ts +0 -20
- package/lib/engine/id/node-id-generator.ts +0 -7
- package/lib/engine/logger/logger.ts +0 -11
- package/lib/engine/logger/memory-logger.ts +0 -28
- package/lib/engine/logger/node-logger.ts +0 -49
- package/lib/engine/logger/noop-logger.ts +0 -9
- package/lib/engine/mcp/channel-server.ts +0 -123
- package/lib/engine/mcp/channel-subscriber.ts +0 -82
- package/lib/engine/mcp/mcp.ts +0 -126
- package/lib/engine/mcp/read-channel-connectors.ts +0 -34
- package/lib/engine/mcp/read-gateway-token.ts +0 -16
- package/lib/engine/mcp/usage-hint-for-type.ts +0 -15
- package/lib/engine/process/memory-process-runner.ts +0 -88
- package/lib/engine/process/node-process-runner.ts +0 -91
- package/lib/engine/process/process-runner.ts +0 -33
- package/lib/engine/profiles/profile-channel-checker.ts +0 -7
- package/lib/engine/profiles/profiles.ts +0 -126
- package/lib/engine/settings/mock-settings-reader.ts +0 -27
- package/lib/engine/settings/settings-reader.ts +0 -6
- package/lib/engine/settings/settings-schema.ts +0 -48
- package/lib/engine/settings/settings-store.ts +0 -110
- package/lib/engine/time/clock.ts +0 -15
- package/lib/engine/time/memory-clock.ts +0 -26
- package/lib/engine/time/node-clock.ts +0 -7
- package/lib/funnel.ts +0 -294
- package/lib/gateway/auth-middleware.ts +0 -44
- package/lib/gateway/broadcaster.ts +0 -319
- package/lib/gateway/channel-publisher.ts +0 -67
- package/lib/gateway/daemon.ts +0 -47
- package/lib/gateway/factory.ts +0 -10
- package/lib/gateway/funnel-event-store.ts +0 -155
- package/lib/gateway/gateway-server.ts +0 -426
- package/lib/gateway/gateway-token.ts +0 -79
- package/lib/gateway/gateway.ts +0 -209
- package/lib/gateway/kill-competing-slack-gateways.ts +0 -56
- package/lib/gateway/listener-supervisor.ts +0 -339
- package/lib/gateway/listeners-client.ts +0 -128
- package/lib/gateway/publish-schema.ts +0 -27
- package/lib/gateway/resolve-daemon-script.ts +0 -26
- package/lib/gateway/routes/channels.connectors.call.ts +0 -39
- package/lib/gateway/routes/channels.publish.ts +0 -44
- package/lib/gateway/routes/health.ts +0 -13
- package/lib/gateway/routes/index.ts +0 -26
- package/lib/gateway/routes/listeners.list.ts +0 -6
- package/lib/gateway/routes/listeners.restart.ts +0 -15
- package/lib/gateway/routes/listeners.start.ts +0 -15
- package/lib/gateway/routes/listeners.stop.ts +0 -15
- package/lib/gateway/routes/route-deps.ts +0 -19
- package/lib/gateway/routes/status.ts +0 -15
- package/lib/gateway/routes/validator.ts +0 -17
- package/lib/index.ts +0 -67
- package/lib/logger/leuco-human-file-writer.ts +0 -65
- package/lib/logger/leuco-human-logger.ts +0 -98
- package/lib/logger/leuco-human-record.ts +0 -16
- package/lib/logger/leuco-human-stdout-writer.ts +0 -26
- package/lib/logger/leuco-human-writer.ts +0 -14
- package/lib/logger/leuco-logger-memory-sink.ts +0 -67
- package/lib/logger/leuco-logger-record.ts +0 -13
- package/lib/logger/leuco-logger-sink.ts +0 -33
- package/lib/logger/leuco-logger-sqlite-sink.ts +0 -355
- package/lib/logger/leuco-logger.ts +0 -135
- package/lib/tui/app.tsx +0 -357
- package/lib/tui/components/add-row.tsx +0 -18
- package/lib/tui/components/brand.tsx +0 -27
- package/lib/tui/components/card.tsx +0 -44
- package/lib/tui/components/detail-bar.tsx +0 -46
- package/lib/tui/components/editable-field.tsx +0 -33
- package/lib/tui/components/empty-state.tsx +0 -11
- package/lib/tui/components/gateway-status.tsx +0 -66
- package/lib/tui/components/keymap.tsx +0 -29
- package/lib/tui/components/menu-item.tsx +0 -73
- package/lib/tui/components/menu.tsx +0 -26
- package/lib/tui/components/panel-header.tsx +0 -22
- package/lib/tui/components/readonly-field.tsx +0 -18
- package/lib/tui/components/section-header.tsx +0 -25
- package/lib/tui/components/selection-accent.tsx +0 -32
- package/lib/tui/components/session-item.tsx +0 -33
- package/lib/tui/components/session-list.tsx +0 -33
- package/lib/tui/components/ui/hascii/accordion-item.tsx +0 -88
- package/lib/tui/components/ui/hascii/accordion.tsx +0 -96
- package/lib/tui/components/ui/hascii/alert-dialog.tsx +0 -43
- package/lib/tui/components/ui/hascii/badge.tsx +0 -51
- package/lib/tui/components/ui/hascii/breadcrumb.tsx +0 -58
- package/lib/tui/components/ui/hascii/button.tsx +0 -194
- package/lib/tui/components/ui/hascii/card-content.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-description.tsx +0 -13
- package/lib/tui/components/ui/hascii/card-footer.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-title.tsx +0 -13
- package/lib/tui/components/ui/hascii/card.tsx +0 -27
- package/lib/tui/components/ui/hascii/checkbox.tsx +0 -65
- package/lib/tui/components/ui/hascii/command.tsx +0 -159
- package/lib/tui/components/ui/hascii/dialog-content.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-description.tsx +0 -13
- package/lib/tui/components/ui/hascii/dialog-footer.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-title.tsx +0 -13
- package/lib/tui/components/ui/hascii/dialog.tsx +0 -27
- package/lib/tui/components/ui/hascii/file-tree.tsx +0 -142
- package/lib/tui/components/ui/hascii/focus-group.tsx +0 -62
- package/lib/tui/components/ui/hascii/form-item.tsx +0 -43
- package/lib/tui/components/ui/hascii/input-otp.tsx +0 -86
- package/lib/tui/components/ui/hascii/input.tsx +0 -130
- package/lib/tui/components/ui/hascii/pagination.tsx +0 -105
- package/lib/tui/components/ui/hascii/progress.tsx +0 -28
- package/lib/tui/components/ui/hascii/select.tsx +0 -131
- package/lib/tui/components/ui/hascii/separator.tsx +0 -35
- package/lib/tui/components/ui/hascii/sidebar-content.tsx +0 -23
- package/lib/tui/components/ui/hascii/sidebar-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +0 -67
- package/lib/tui/components/ui/hascii/sidebar.tsx +0 -24
- package/lib/tui/components/ui/hascii/skeleton.tsx +0 -60
- package/lib/tui/components/ui/hascii/slider.tsx +0 -91
- package/lib/tui/components/ui/hascii/snackbar.tsx +0 -75
- package/lib/tui/components/ui/hascii/sparkline.tsx +0 -53
- package/lib/tui/components/ui/hascii/spinner.tsx +0 -47
- package/lib/tui/components/ui/hascii/stepper.tsx +0 -54
- package/lib/tui/components/ui/hascii/switch.tsx +0 -66
- package/lib/tui/components/ui/hascii/table.tsx +0 -95
- package/lib/tui/components/ui/hascii/tabs.tsx +0 -59
- package/lib/tui/components/ui/hascii/toggle-group-item.tsx +0 -45
- package/lib/tui/components/ui/hascii/toggle-group.tsx +0 -99
- package/lib/tui/components/ui/hascii/tree.tsx +0 -104
- package/lib/tui/components/view-shell.tsx +0 -44
- package/lib/tui/filter-input.tsx +0 -33
- package/lib/tui/hooks/hascii/use-pressable.ts +0 -54
- package/lib/tui/parse-comma-list.ts +0 -14
- package/lib/tui/profile-launcher.tsx +0 -61
- package/lib/tui/scrollbar-options.ts +0 -19
- package/lib/tui/sidebar.tsx +0 -50
- package/lib/tui/theme.ts +0 -40
- package/lib/tui/tui.tsx +0 -20
- package/lib/tui/types.ts +0 -38
- package/lib/tui/unique-name.ts +0 -18
- package/lib/tui/use-event-stream.ts +0 -133
- package/lib/tui/use-snapshot.ts +0 -99
- package/lib/tui/utils/hascii/form-item-context.tsx +0 -23
- package/lib/tui/utils/hascii/input-focus-context.tsx +0 -31
- package/lib/tui/utils/hascii/theme-context.tsx +0 -26
- package/lib/tui/utils/hascii/theme.ts +0 -176
- package/lib/tui/views/channels-view.tsx +0 -108
- package/lib/tui/views/connectors-view.tsx +0 -164
- package/lib/tui/views/events-view.tsx +0 -160
- package/lib/tui/views/listeners-view.tsx +0 -80
- package/lib/tui/views/profiles-view.tsx +0 -152
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { useKeyboard } from "@opentui/react"
|
|
2
|
-
import { useState } from "react"
|
|
3
|
-
import type { ReactNode } from "react"
|
|
4
|
-
import { useHasciiFocus } from "@/tui/components/ui/hascii/focus-group"
|
|
5
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
6
|
-
import { usePressable } from "@/tui/hooks/hascii/use-pressable"
|
|
7
|
-
|
|
8
|
-
type Variant = "default" | "secondary" | "outline" | "ghost" | "destructive"
|
|
9
|
-
type Size = "default" | "sm" | "md" | "lg"
|
|
10
|
-
|
|
11
|
-
export type Props = {
|
|
12
|
-
variant?: Variant
|
|
13
|
-
size?: Size
|
|
14
|
-
focusId?: string
|
|
15
|
-
isFocused?: boolean
|
|
16
|
-
isDisabled?: boolean
|
|
17
|
-
onPress?: () => void
|
|
18
|
-
children?: ReactNode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type ResolvedSize = "sm" | "md" | "lg"
|
|
22
|
-
|
|
23
|
-
const sizeDims: Record<ResolvedSize, { paddingX: number; height: number }> = {
|
|
24
|
-
sm: { paddingX: 1, height: 1 },
|
|
25
|
-
md: { paddingX: 2, height: 1 },
|
|
26
|
-
lg: { paddingX: 2, height: 3 },
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const resolveSize = (size: Size): ResolvedSize => (size === "default" ? "md" : size)
|
|
30
|
-
|
|
31
|
-
const pickBg = (
|
|
32
|
-
rest: string | undefined,
|
|
33
|
-
hover: string,
|
|
34
|
-
active: string,
|
|
35
|
-
isHover: boolean,
|
|
36
|
-
isActive: boolean,
|
|
37
|
-
): string | undefined => {
|
|
38
|
-
if (isActive) return active
|
|
39
|
-
if (isHover) return hover
|
|
40
|
-
return rest
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** A focusable terminal button. Background cycles through rest, hover, and active states. */
|
|
44
|
-
export function HasciiButton(props: Props) {
|
|
45
|
-
const variant = props.variant ?? "default"
|
|
46
|
-
const size = props.size ?? "default"
|
|
47
|
-
const groupFocused = useHasciiFocus(props.focusId)
|
|
48
|
-
const isFocused = props.isFocused ?? groupFocused
|
|
49
|
-
const isDisabled = props.isDisabled ?? false
|
|
50
|
-
|
|
51
|
-
const theme = useHasciiTheme()
|
|
52
|
-
const resolvedSize = resolveSize(size)
|
|
53
|
-
const dims = sizeDims[resolvedSize]
|
|
54
|
-
|
|
55
|
-
const press = usePressable({ isDisabled, onPress: props.onPress })
|
|
56
|
-
|
|
57
|
-
const flashState = useState(false)
|
|
58
|
-
const flashed = flashState[0]
|
|
59
|
-
const setFlashed = flashState[1]
|
|
60
|
-
|
|
61
|
-
const isHover = press.isHovered && !press.isPressed && !flashed
|
|
62
|
-
const isActive = press.isPressed || flashed
|
|
63
|
-
|
|
64
|
-
useKeyboard((key) => {
|
|
65
|
-
if (!isFocused || isDisabled) return
|
|
66
|
-
|
|
67
|
-
if (key.name === "return" || key.name === "space") {
|
|
68
|
-
setFlashed(true)
|
|
69
|
-
props.onPress?.()
|
|
70
|
-
setTimeout(() => setFlashed(false), 120)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
if (variant === "outline") {
|
|
75
|
-
const tone = isDisabled
|
|
76
|
-
? theme.color.border
|
|
77
|
-
: isActive
|
|
78
|
-
? theme.color.primaryActive
|
|
79
|
-
: isHover
|
|
80
|
-
? theme.color.primaryHover
|
|
81
|
-
: theme.color.primary
|
|
82
|
-
|
|
83
|
-
const outlinePaddingX = resolvedSize === "sm" ? 0 : resolvedSize === "md" ? 1 : 2
|
|
84
|
-
const outlineHeight = 3
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<box
|
|
88
|
-
paddingLeft={outlinePaddingX}
|
|
89
|
-
paddingRight={outlinePaddingX}
|
|
90
|
-
height={outlineHeight}
|
|
91
|
-
border={outlineHeight >= 3}
|
|
92
|
-
borderStyle="rounded"
|
|
93
|
-
borderColor={tone}
|
|
94
|
-
alignItems="center"
|
|
95
|
-
justifyContent="center"
|
|
96
|
-
{...press.bind}
|
|
97
|
-
>
|
|
98
|
-
<text fg={tone}>{props.children}</text>
|
|
99
|
-
</box>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (variant === "ghost") {
|
|
104
|
-
const fg = isDisabled ? theme.color.mutedForeground : theme.color.foreground
|
|
105
|
-
const bg = pickBg(undefined, theme.color.accentHover, theme.color.accent, isHover, isActive)
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<box
|
|
109
|
-
paddingLeft={dims.paddingX}
|
|
110
|
-
paddingRight={dims.paddingX}
|
|
111
|
-
height={dims.height}
|
|
112
|
-
backgroundColor={bg}
|
|
113
|
-
alignItems="center"
|
|
114
|
-
justifyContent="center"
|
|
115
|
-
{...press.bind}
|
|
116
|
-
>
|
|
117
|
-
<text fg={fg}>{props.children}</text>
|
|
118
|
-
</box>
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (variant === "secondary") {
|
|
123
|
-
const fg = isDisabled ? theme.color.mutedForeground : theme.color.secondaryForeground
|
|
124
|
-
const bg = pickBg(
|
|
125
|
-
theme.color.secondary,
|
|
126
|
-
theme.color.secondaryHover,
|
|
127
|
-
theme.color.secondaryActive,
|
|
128
|
-
isHover,
|
|
129
|
-
isActive,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<box
|
|
134
|
-
paddingLeft={dims.paddingX}
|
|
135
|
-
paddingRight={dims.paddingX}
|
|
136
|
-
height={dims.height}
|
|
137
|
-
backgroundColor={bg}
|
|
138
|
-
alignItems="center"
|
|
139
|
-
justifyContent="center"
|
|
140
|
-
{...press.bind}
|
|
141
|
-
>
|
|
142
|
-
<text fg={fg}>{props.children}</text>
|
|
143
|
-
</box>
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (variant === "destructive") {
|
|
148
|
-
const fg = isDisabled ? theme.color.mutedForeground : theme.color.destructiveForeground
|
|
149
|
-
const bg = pickBg(
|
|
150
|
-
theme.color.destructive,
|
|
151
|
-
theme.color.destructiveHover,
|
|
152
|
-
theme.color.destructiveActive,
|
|
153
|
-
isHover,
|
|
154
|
-
isActive,
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
return (
|
|
158
|
-
<box
|
|
159
|
-
paddingLeft={dims.paddingX}
|
|
160
|
-
paddingRight={dims.paddingX}
|
|
161
|
-
height={dims.height}
|
|
162
|
-
backgroundColor={bg}
|
|
163
|
-
alignItems="center"
|
|
164
|
-
justifyContent="center"
|
|
165
|
-
{...press.bind}
|
|
166
|
-
>
|
|
167
|
-
<text fg={fg}>{props.children}</text>
|
|
168
|
-
</box>
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const fg = isDisabled ? theme.color.mutedForeground : theme.color.primaryForeground
|
|
173
|
-
const bg = pickBg(
|
|
174
|
-
theme.color.primary,
|
|
175
|
-
theme.color.primaryHover,
|
|
176
|
-
theme.color.primaryActive,
|
|
177
|
-
isHover,
|
|
178
|
-
isActive,
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<box
|
|
183
|
-
paddingLeft={dims.paddingX}
|
|
184
|
-
paddingRight={dims.paddingX}
|
|
185
|
-
height={dims.height}
|
|
186
|
-
backgroundColor={bg}
|
|
187
|
-
alignItems="center"
|
|
188
|
-
justifyContent="center"
|
|
189
|
-
{...press.bind}
|
|
190
|
-
>
|
|
191
|
-
<text fg={fg}>{props.children}</text>
|
|
192
|
-
</box>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
children?: ReactNode
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Vertical content region inside a HasciiCard. paddingY=1 keeps the body separated from neighbouring header and footer. */
|
|
8
|
-
export function HasciiCardContent(props: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<box flexDirection="column" gap={1} paddingTop={1} paddingBottom={1}>
|
|
11
|
-
{props.children}
|
|
12
|
-
</box>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
3
|
-
|
|
4
|
-
export type Props = {
|
|
5
|
-
children?: ReactNode
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Secondary descriptive text rendered under a HasciiCardTitle. */
|
|
9
|
-
export function HasciiCardDescription(props: Props) {
|
|
10
|
-
const theme = useHasciiTheme()
|
|
11
|
-
|
|
12
|
-
return <text fg={theme.color.mutedForeground}>{props.children}</text>
|
|
13
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
children?: ReactNode
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Horizontal footer region for action rows inside a HasciiCard. */
|
|
8
|
-
export function HasciiCardFooter(props: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<box flexDirection="row" justifyContent="flex-end" gap={1}>
|
|
11
|
-
{props.children}
|
|
12
|
-
</box>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
children?: ReactNode
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Vertical heading group typically holding HasciiCardTitle and HasciiCardDescription. */
|
|
8
|
-
export function HasciiCardHeader(props: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<box flexDirection="column" gap={0}>
|
|
11
|
-
{props.children}
|
|
12
|
-
</box>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
3
|
-
|
|
4
|
-
export type Props = {
|
|
5
|
-
children?: ReactNode
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Primary title text inside a card header. */
|
|
9
|
-
export function HasciiCardTitle(props: Props) {
|
|
10
|
-
const theme = useHasciiTheme()
|
|
11
|
-
|
|
12
|
-
return <text fg={theme.color.foreground}>{props.children}</text>
|
|
13
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
3
|
-
|
|
4
|
-
export type Props = {
|
|
5
|
-
width?: number
|
|
6
|
-
children?: ReactNode
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Filled container that frames its children. Use with HasciiCardHeader, HasciiCardContent, and HasciiCardFooter. */
|
|
10
|
-
export function HasciiCard(props: Props) {
|
|
11
|
-
const theme = useHasciiTheme()
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<box
|
|
15
|
-
backgroundColor={theme.color.card}
|
|
16
|
-
flexDirection="column"
|
|
17
|
-
paddingTop={1}
|
|
18
|
-
paddingBottom={1}
|
|
19
|
-
paddingLeft={2}
|
|
20
|
-
paddingRight={2}
|
|
21
|
-
gap={0}
|
|
22
|
-
width={props.width}
|
|
23
|
-
>
|
|
24
|
-
{props.children}
|
|
25
|
-
</box>
|
|
26
|
-
)
|
|
27
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { useState } from "react"
|
|
3
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
4
|
-
import { usePressable } from "@/tui/hooks/hascii/use-pressable"
|
|
5
|
-
|
|
6
|
-
type Type = "ballot" | "square"
|
|
7
|
-
|
|
8
|
-
export type Props = {
|
|
9
|
-
type?: Type
|
|
10
|
-
isChecked?: boolean
|
|
11
|
-
defaultChecked?: boolean
|
|
12
|
-
isDisabled?: boolean
|
|
13
|
-
onChange?: (next: boolean) => void
|
|
14
|
-
children?: ReactNode
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const GLYPHS: Record<Type, { checked: string; unchecked: string }> = {
|
|
18
|
-
ballot: { checked: "☑", unchecked: "☐" },
|
|
19
|
-
square: { checked: "■", unchecked: "□" },
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Single checkbox row. type="ballot" (default) uses ☐/☑; type="square" matches the stepper's ■/□ glyphs. */
|
|
23
|
-
export function HasciiCheckbox(props: Props) {
|
|
24
|
-
const type = props.type ?? "ballot"
|
|
25
|
-
const isDisabled = props.isDisabled ?? false
|
|
26
|
-
const theme = useHasciiTheme()
|
|
27
|
-
|
|
28
|
-
const internalState = useState(props.defaultChecked ?? false)
|
|
29
|
-
const internal = internalState[0]
|
|
30
|
-
const setInternal = internalState[1]
|
|
31
|
-
|
|
32
|
-
const isChecked = props.isChecked ?? internal
|
|
33
|
-
|
|
34
|
-
const toggle = () => {
|
|
35
|
-
const next = !isChecked
|
|
36
|
-
|
|
37
|
-
if (props.isChecked === undefined) setInternal(next)
|
|
38
|
-
props.onChange?.(next)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const press = usePressable({ isDisabled, onPress: toggle })
|
|
42
|
-
|
|
43
|
-
const glyphFg = isDisabled
|
|
44
|
-
? theme.color.mutedForeground
|
|
45
|
-
: isChecked
|
|
46
|
-
? theme.color.primary
|
|
47
|
-
: press.isHovered
|
|
48
|
-
? theme.color.foreground
|
|
49
|
-
: theme.color.mutedForeground
|
|
50
|
-
|
|
51
|
-
const labelFg = isDisabled ? theme.color.mutedForeground : theme.color.foreground
|
|
52
|
-
|
|
53
|
-
const glyphs = GLYPHS[type]
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<box flexDirection="row" alignItems="center" {...press.bind}>
|
|
57
|
-
<text fg={glyphFg}>{isChecked ? glyphs.checked : glyphs.unchecked}</text>
|
|
58
|
-
{props.children !== undefined ? (
|
|
59
|
-
<box paddingLeft={2}>
|
|
60
|
-
<text fg={labelFg}>{props.children}</text>
|
|
61
|
-
</box>
|
|
62
|
-
) : null}
|
|
63
|
-
</box>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { useKeyboard } from "@opentui/react"
|
|
2
|
-
import { useState } from "react"
|
|
3
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
4
|
-
import { usePressable } from "@/tui/hooks/hascii/use-pressable"
|
|
5
|
-
import { HasciiInput } from "@/tui/components/ui/hascii/input"
|
|
6
|
-
|
|
7
|
-
export type CommandItem = {
|
|
8
|
-
id: string
|
|
9
|
-
label: string
|
|
10
|
-
hint?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type Props = {
|
|
14
|
-
items: CommandItem[]
|
|
15
|
-
placeholder?: string
|
|
16
|
-
width?: number
|
|
17
|
-
maxRows?: number
|
|
18
|
-
isFocused?: boolean
|
|
19
|
-
onRun?: (id: string) => void
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type RowProps = {
|
|
23
|
-
item: CommandItem
|
|
24
|
-
isActive: boolean
|
|
25
|
-
onHover: () => void
|
|
26
|
-
onPress: () => void
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const ROW_HEIGHT = 1
|
|
30
|
-
|
|
31
|
-
/** Internal row used by HasciiCommand. Hover moves the active cursor; only the active row gets a bg + ▏ left rule. */
|
|
32
|
-
function HasciiCommandRow(props: RowProps) {
|
|
33
|
-
const theme = useHasciiTheme()
|
|
34
|
-
const press = usePressable({ onPress: props.onPress })
|
|
35
|
-
|
|
36
|
-
const bg = props.isActive ? theme.color.secondaryActive : undefined
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<box
|
|
40
|
-
flexDirection="row"
|
|
41
|
-
alignItems="center"
|
|
42
|
-
justifyContent="space-between"
|
|
43
|
-
paddingLeft={2}
|
|
44
|
-
paddingRight={2}
|
|
45
|
-
height={ROW_HEIGHT}
|
|
46
|
-
backgroundColor={bg}
|
|
47
|
-
onMouseOver={() => {
|
|
48
|
-
press.bind.onMouseOver()
|
|
49
|
-
props.onHover()
|
|
50
|
-
}}
|
|
51
|
-
onMouseOut={press.bind.onMouseOut}
|
|
52
|
-
onMouseDown={press.bind.onMouseDown}
|
|
53
|
-
onMouseUp={press.bind.onMouseUp}
|
|
54
|
-
>
|
|
55
|
-
{props.isActive ? (
|
|
56
|
-
<box position="absolute" left={0} top={0} bottom={0} flexDirection="column">
|
|
57
|
-
{Array.from({ length: ROW_HEIGHT }, (_, index) => (
|
|
58
|
-
<text key={index} fg={theme.color.primary}>
|
|
59
|
-
▏
|
|
60
|
-
</text>
|
|
61
|
-
))}
|
|
62
|
-
</box>
|
|
63
|
-
) : null}
|
|
64
|
-
<text fg={theme.color.foreground}>{props.item.label}</text>
|
|
65
|
-
{props.item.hint !== undefined ? (
|
|
66
|
-
<text fg={theme.color.mutedForeground}>{props.item.hint}</text>
|
|
67
|
-
) : null}
|
|
68
|
-
</box>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const matches = (item: CommandItem, query: string): boolean => {
|
|
73
|
-
if (query.length === 0) return true
|
|
74
|
-
|
|
75
|
-
const needle = query.toLowerCase()
|
|
76
|
-
return item.label.toLowerCase().includes(needle)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Keyboard-driven palette: type to filter, ↑/↓ moves the cursor (also moved by hover), Enter runs. The input bar is HasciiInput. */
|
|
80
|
-
export function HasciiCommand(props: Props) {
|
|
81
|
-
const width = props.width ?? 48
|
|
82
|
-
const maxRows = props.maxRows ?? 8
|
|
83
|
-
const isFocused = props.isFocused ?? true
|
|
84
|
-
const placeholder = props.placeholder ?? "type a command…"
|
|
85
|
-
const theme = useHasciiTheme()
|
|
86
|
-
|
|
87
|
-
const queryState = useState("")
|
|
88
|
-
const query = queryState[0]
|
|
89
|
-
const setQuery = queryState[1]
|
|
90
|
-
|
|
91
|
-
const indexState = useState(0)
|
|
92
|
-
const activeIndex = indexState[0]
|
|
93
|
-
const setActiveIndex = indexState[1]
|
|
94
|
-
|
|
95
|
-
const filtered = props.items.filter((item) => matches(item, query))
|
|
96
|
-
const safeIndex = filtered.length === 0 ? 0 : Math.min(activeIndex, filtered.length - 1)
|
|
97
|
-
|
|
98
|
-
useKeyboard((key) => {
|
|
99
|
-
if (!isFocused) return
|
|
100
|
-
|
|
101
|
-
if (key.name === "up") {
|
|
102
|
-
setActiveIndex(Math.max(0, safeIndex - 1))
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (key.name === "down") {
|
|
107
|
-
setActiveIndex(Math.min(filtered.length - 1, safeIndex + 1))
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (key.name === "return") {
|
|
112
|
-
const item = filtered[safeIndex]
|
|
113
|
-
if (item) props.onRun?.(item.id)
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<box width={width} backgroundColor={theme.color.card} flexDirection="column">
|
|
120
|
-
<HasciiInput
|
|
121
|
-
defaultFocused={isFocused}
|
|
122
|
-
width={width}
|
|
123
|
-
value={query}
|
|
124
|
-
placeholder={placeholder}
|
|
125
|
-
onInput={(value) => {
|
|
126
|
-
setQuery(value)
|
|
127
|
-
setActiveIndex(0)
|
|
128
|
-
}}
|
|
129
|
-
/>
|
|
130
|
-
<box height={maxRows}>
|
|
131
|
-
{filtered.length === 0 ? (
|
|
132
|
-
<box paddingLeft={2} paddingRight={2} height={1}>
|
|
133
|
-
<text fg={theme.color.mutedForeground}>no matches</text>
|
|
134
|
-
</box>
|
|
135
|
-
) : (
|
|
136
|
-
<scrollbox
|
|
137
|
-
flexGrow={1}
|
|
138
|
-
scrollY
|
|
139
|
-
stickyScroll={false}
|
|
140
|
-
contentOptions={{ flexDirection: "column", gap: 0 }}
|
|
141
|
-
>
|
|
142
|
-
{filtered.map((item, index) => (
|
|
143
|
-
<HasciiCommandRow
|
|
144
|
-
key={item.id}
|
|
145
|
-
item={item}
|
|
146
|
-
isActive={index === safeIndex}
|
|
147
|
-
onHover={() => setActiveIndex(index)}
|
|
148
|
-
onPress={() => {
|
|
149
|
-
setActiveIndex(index)
|
|
150
|
-
props.onRun?.(item.id)
|
|
151
|
-
}}
|
|
152
|
-
/>
|
|
153
|
-
))}
|
|
154
|
-
</scrollbox>
|
|
155
|
-
)}
|
|
156
|
-
</box>
|
|
157
|
-
</box>
|
|
158
|
-
)
|
|
159
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
children?: ReactNode
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Vertical body region inside a HasciiDialog. */
|
|
8
|
-
export function HasciiDialogContent(props: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<box flexDirection="column" gap={1}>
|
|
11
|
-
{props.children}
|
|
12
|
-
</box>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
3
|
-
|
|
4
|
-
export type Props = {
|
|
5
|
-
children?: ReactNode
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Secondary descriptive text rendered under a HasciiDialogTitle. */
|
|
9
|
-
export function HasciiDialogDescription(props: Props) {
|
|
10
|
-
const theme = useHasciiTheme()
|
|
11
|
-
|
|
12
|
-
return <text fg={theme.color.mutedForeground}>{props.children}</text>
|
|
13
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
children?: ReactNode
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Horizontal action row, right-aligned, at the bottom of a HasciiDialog. */
|
|
8
|
-
export function HasciiDialogFooter(props: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<box flexDirection="row" gap={1} justifyContent="flex-end" marginTop={1}>
|
|
11
|
-
{props.children}
|
|
12
|
-
</box>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
children?: ReactNode
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Dialog header. Stacks the children vertically with the standard 1-cell gap. */
|
|
8
|
-
export function HasciiDialogHeader(props: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<box flexDirection="column" gap={1}>
|
|
11
|
-
{props.children}
|
|
12
|
-
</box>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
3
|
-
|
|
4
|
-
export type Props = {
|
|
5
|
-
children?: ReactNode
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Primary title text inside a HasciiDialogHeader. */
|
|
9
|
-
export function HasciiDialogTitle(props: Props) {
|
|
10
|
-
const theme = useHasciiTheme()
|
|
11
|
-
|
|
12
|
-
return <text fg={theme.color.foreground}>{props.children}</text>
|
|
13
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { HasciiButton } from "@/tui/components/ui/hascii/button"
|
|
3
|
-
import { HasciiCard } from "@/tui/components/ui/hascii/card"
|
|
4
|
-
|
|
5
|
-
export type Props = {
|
|
6
|
-
width?: number
|
|
7
|
-
onClose?: () => void
|
|
8
|
-
children?: ReactNode
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Floating dialog. Wraps a HasciiCard for layout and renders a default-variant x button just above the card's top-right corner when onClose is provided. */
|
|
12
|
-
export function HasciiDialog(props: Props) {
|
|
13
|
-
const width = props.width ?? 48
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<box width={width} paddingTop={1}>
|
|
17
|
-
<HasciiCard width={width}>{props.children}</HasciiCard>
|
|
18
|
-
{props.onClose ? (
|
|
19
|
-
<box position="absolute" top={0} right={0}>
|
|
20
|
-
<HasciiButton variant="secondary" size="default" onPress={props.onClose}>
|
|
21
|
-
x
|
|
22
|
-
</HasciiButton>
|
|
23
|
-
</box>
|
|
24
|
-
) : null}
|
|
25
|
-
</box>
|
|
26
|
-
)
|
|
27
|
-
}
|