@interactive-inc/claude-funnel 0.10.0 → 0.10.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/dist/bin.js +448 -448
- package/dist/connectors/slack.d.ts +1 -29
- package/dist/gateway/daemon.js +166 -166
- package/dist/index.d.ts +4 -11
- package/dist/index.js +133 -120
- package/dist/slack-event-processor-CS-bAit9.d.ts +43 -0
- package/package.json +1 -6
- 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,108 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/react */
|
|
2
|
-
import { AddRow } from "@/tui/components/add-row"
|
|
3
|
-
import { Card } from "@/tui/components/card"
|
|
4
|
-
import { EditableField } from "@/tui/components/editable-field"
|
|
5
|
-
import { EmptyState } from "@/tui/components/empty-state"
|
|
6
|
-
import { PanelHeader } from "@/tui/components/panel-header"
|
|
7
|
-
import { ReadonlyField } from "@/tui/components/readonly-field"
|
|
8
|
-
import { ViewShell } from "@/tui/components/view-shell"
|
|
9
|
-
import type { Snapshot } from "@/tui/types"
|
|
10
|
-
import { uniqueName } from "@/tui/unique-name"
|
|
11
|
-
import type { Funnel } from "@/funnel"
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
snapshot: Snapshot
|
|
15
|
-
funnel: Funnel
|
|
16
|
-
refresh: () => void
|
|
17
|
-
focusedKey: string | null
|
|
18
|
-
setFocusedKey: (key: string | null) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type Channel = Snapshot["channels"][number]
|
|
22
|
-
|
|
23
|
-
const fieldKey = (name: string, field: string): string => `channels::${name}::${field}`
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Channel inspector — one Card per channel. Connectors live nested inside the
|
|
27
|
-
* channel and are managed in the connectors view; here only the channel's
|
|
28
|
-
* name and id (read-only) are shown along with a count of nested connectors.
|
|
29
|
-
*/
|
|
30
|
-
export function ChannelsView(props: Props) {
|
|
31
|
-
const channels = props.snapshot.channels
|
|
32
|
-
|
|
33
|
-
const commit = (channel: Channel, field: string, raw: string): void => {
|
|
34
|
-
try {
|
|
35
|
-
if (field === "name") {
|
|
36
|
-
const next = raw.trim()
|
|
37
|
-
|
|
38
|
-
if (next && next !== channel.name) props.funnel.channels.rename(channel.name, next)
|
|
39
|
-
}
|
|
40
|
-
} catch (error) {
|
|
41
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
props.setFocusedKey(null)
|
|
45
|
-
props.refresh()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const removeChannel = (name: string): void => {
|
|
49
|
-
try {
|
|
50
|
-
props.funnel.channels.remove(name)
|
|
51
|
-
} catch (error) {
|
|
52
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
props.setFocusedKey(null)
|
|
56
|
-
props.refresh()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const addChannel = (): void => {
|
|
60
|
-
const name = uniqueName(
|
|
61
|
-
channels.map((c) => c.name),
|
|
62
|
-
"channel",
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const created = props.funnel.channels.add({ name })
|
|
67
|
-
props.setFocusedKey(fieldKey(created.name, "name"))
|
|
68
|
-
} catch (error) {
|
|
69
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
props.refresh()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<ViewShell>
|
|
77
|
-
<PanelHeader label="channels" count={channels.length} />
|
|
78
|
-
|
|
79
|
-
{channels.length === 0 ? (
|
|
80
|
-
<EmptyState message="(none — use the button below to add one)" />
|
|
81
|
-
) : (
|
|
82
|
-
channels.map((channel) => (
|
|
83
|
-
<Card key={channel.id} title={channel.name} onDelete={() => removeChannel(channel.name)}>
|
|
84
|
-
<EditableField
|
|
85
|
-
label="name"
|
|
86
|
-
initialValue={channel.name}
|
|
87
|
-
focused={props.focusedKey === fieldKey(channel.name, "name")}
|
|
88
|
-
onFocus={() => props.setFocusedKey(fieldKey(channel.name, "name"))}
|
|
89
|
-
onCommit={(raw) => commit(channel, "name", raw)}
|
|
90
|
-
/>
|
|
91
|
-
<ReadonlyField label="id" value={channel.id} />
|
|
92
|
-
<ReadonlyField label="delivery" value={channel.delivery} />
|
|
93
|
-
<ReadonlyField
|
|
94
|
-
label="connectors"
|
|
95
|
-
value={
|
|
96
|
-
channel.connectors.length > 0
|
|
97
|
-
? channel.connectors.map((c) => `${c.name}:${c.type}`).join(", ")
|
|
98
|
-
: "(none)"
|
|
99
|
-
}
|
|
100
|
-
/>
|
|
101
|
-
</Card>
|
|
102
|
-
))
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
<AddRow label="add channel" onClick={addChannel} />
|
|
106
|
-
</ViewShell>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { AddRow } from "@/tui/components/add-row"
|
|
2
|
-
import { Card } from "@/tui/components/card"
|
|
3
|
-
import { EmptyState } from "@/tui/components/empty-state"
|
|
4
|
-
import { PanelHeader } from "@/tui/components/panel-header"
|
|
5
|
-
import { ReadonlyField } from "@/tui/components/readonly-field"
|
|
6
|
-
import { HasciiButton } from "@/tui/components/ui/hascii/button"
|
|
7
|
-
import { ViewShell } from "@/tui/components/view-shell"
|
|
8
|
-
import { funnel } from "@/tui/theme"
|
|
9
|
-
import type { Snapshot } from "@/tui/types"
|
|
10
|
-
import { uniqueName } from "@/tui/unique-name"
|
|
11
|
-
import type { Funnel } from "@/funnel"
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
snapshot: Snapshot
|
|
15
|
-
funnel: Funnel
|
|
16
|
-
refresh: () => void
|
|
17
|
-
focusedKey: string | null
|
|
18
|
-
setFocusedKey: (key: string | null) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type Connector = Snapshot["connectors"][number]
|
|
22
|
-
type ConnectorType = Connector["type"]
|
|
23
|
-
|
|
24
|
-
const formatTimestamp = (iso: string | undefined): string => {
|
|
25
|
-
if (!iso) return "—"
|
|
26
|
-
|
|
27
|
-
const d = new Date(iso)
|
|
28
|
-
|
|
29
|
-
if (Number.isNaN(d.getTime())) return "—"
|
|
30
|
-
|
|
31
|
-
const yyyy = d.getFullYear()
|
|
32
|
-
const mm = String(d.getMonth() + 1).padStart(2, "0")
|
|
33
|
-
const dd = String(d.getDate()).padStart(2, "0")
|
|
34
|
-
const hh = String(d.getHours()).padStart(2, "0")
|
|
35
|
-
const mn = String(d.getMinutes()).padStart(2, "0")
|
|
36
|
-
|
|
37
|
-
return `${yyyy}-${mm}-${dd} ${hh}:${mn}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Channel-scoped connector inspector. Reads `funnel.channels.listAllConnectors()`
|
|
42
|
-
* (already flattened with channelName / channelId tags) and lets the user delete
|
|
43
|
-
* each connector or quickly add a new one to the first available channel via the
|
|
44
|
-
* AddRow buttons. Editing values is intentionally read-only — token / pollInterval
|
|
45
|
-
* mutation belongs to `fnl channels <ch> connectors set <conn> ...` because the
|
|
46
|
-
* same connector name can exist in multiple channels and inline edits would have
|
|
47
|
-
* to disambiguate.
|
|
48
|
-
*/
|
|
49
|
-
export function ConnectorsView(props: Props) {
|
|
50
|
-
const connectors = props.snapshot.connectors
|
|
51
|
-
const channels = props.snapshot.channels
|
|
52
|
-
const targetChannel = channels[0] ?? null
|
|
53
|
-
|
|
54
|
-
const logError = (error: unknown): void => {
|
|
55
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const removeConnector = (connector: Connector): void => {
|
|
59
|
-
props.funnel.listeners.stop(connector.channelName, connector.name).catch(logError)
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
props.funnel.channels.removeConnector(connector.channelName, connector.name)
|
|
63
|
-
} catch (error) {
|
|
64
|
-
logError(error)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
props.refresh()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const addConnector = (type: ConnectorType): void => {
|
|
71
|
-
if (!targetChannel) {
|
|
72
|
-
logError(new Error("add a channel first before creating a connector"))
|
|
73
|
-
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const existingNames = connectors
|
|
78
|
-
.filter((c) => c.channelId === targetChannel.id)
|
|
79
|
-
.map((c) => c.name)
|
|
80
|
-
const name = uniqueName(existingNames, type)
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
if (type === "slack") {
|
|
84
|
-
props.funnel.channels.addConnector(targetChannel.name, {
|
|
85
|
-
type: "slack",
|
|
86
|
-
name,
|
|
87
|
-
botToken: "xoxb-PLACEHOLDER",
|
|
88
|
-
appToken: "xapp-PLACEHOLDER",
|
|
89
|
-
})
|
|
90
|
-
} else if (type === "gh") {
|
|
91
|
-
props.funnel.channels.addConnector(targetChannel.name, { type: "gh", name })
|
|
92
|
-
} else if (type === "discord") {
|
|
93
|
-
props.funnel.channels.addConnector(targetChannel.name, {
|
|
94
|
-
type: "discord",
|
|
95
|
-
name,
|
|
96
|
-
botToken: "PLACEHOLDER-PLACEHOLDER",
|
|
97
|
-
})
|
|
98
|
-
} else {
|
|
99
|
-
props.funnel.channels.addConnector(targetChannel.name, { type: "schedule", name })
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
props.funnel.listeners.start(targetChannel.name, name).catch(logError)
|
|
103
|
-
} catch (error) {
|
|
104
|
-
logError(error)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
props.refresh()
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<ViewShell>
|
|
112
|
-
<PanelHeader label="connectors" count={connectors.length} />
|
|
113
|
-
|
|
114
|
-
{connectors.length === 0 ? (
|
|
115
|
-
<EmptyState message="(none — add via the buttons below or `fnl channels <ch> connectors add ...`)" />
|
|
116
|
-
) : (
|
|
117
|
-
connectors.map((connector) => (
|
|
118
|
-
<Card key={`${connector.channelId}::${connector.id}`} title={connector.name}>
|
|
119
|
-
<ReadonlyField label="channel" value={connector.channelName} />
|
|
120
|
-
<ReadonlyField label="type" value={connector.type} />
|
|
121
|
-
<ReadonlyField label="id" value={connector.id} />
|
|
122
|
-
{connector.type === "slack" ? (
|
|
123
|
-
<>
|
|
124
|
-
<ReadonlyField label="bot-token" value={connector.botToken} />
|
|
125
|
-
<ReadonlyField label="app-token" value={connector.appToken} />
|
|
126
|
-
</>
|
|
127
|
-
) : null}
|
|
128
|
-
{connector.type === "gh" ? (
|
|
129
|
-
<ReadonlyField label="poll" value={String(connector.pollInterval ?? 60)} />
|
|
130
|
-
) : null}
|
|
131
|
-
{connector.type === "discord" ? (
|
|
132
|
-
<ReadonlyField label="bot-token" value={connector.botToken} />
|
|
133
|
-
) : null}
|
|
134
|
-
{connector.type === "schedule" ? (
|
|
135
|
-
<ReadonlyField label="entries" value={String(connector.entries.length)} />
|
|
136
|
-
) : null}
|
|
137
|
-
<text fg={funnel.faint}>{`created ${formatTimestamp(connector.createdAt)}`}</text>
|
|
138
|
-
<box style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
|
139
|
-
<text fg={funnel.faint}>{`updated ${formatTimestamp(connector.updatedAt)}`}</text>
|
|
140
|
-
<HasciiButton
|
|
141
|
-
variant="destructive"
|
|
142
|
-
size="sm"
|
|
143
|
-
onPress={() => removeConnector(connector)}
|
|
144
|
-
>
|
|
145
|
-
delete
|
|
146
|
-
</HasciiButton>
|
|
147
|
-
</box>
|
|
148
|
-
</Card>
|
|
149
|
-
))
|
|
150
|
-
)}
|
|
151
|
-
|
|
152
|
-
{targetChannel ? (
|
|
153
|
-
<text fg={funnel.faint}>{`add target channel: ${targetChannel.name}`}</text>
|
|
154
|
-
) : (
|
|
155
|
-
<text fg={funnel.warn}>add a channel first to enable the buttons below</text>
|
|
156
|
-
)}
|
|
157
|
-
|
|
158
|
-
<AddRow label="add slack" onClick={() => addConnector("slack")} />
|
|
159
|
-
<AddRow label="add gh" onClick={() => addConnector("gh")} />
|
|
160
|
-
<AddRow label="add discord" onClick={() => addConnector("discord")} />
|
|
161
|
-
<AddRow label="add schedule" onClick={() => addConnector("schedule")} />
|
|
162
|
-
</ViewShell>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/react */
|
|
2
|
-
import { DetailBar } from "@/tui/components/detail-bar"
|
|
3
|
-
import { HasciiSeparator } from "@/tui/components/ui/hascii/separator"
|
|
4
|
-
import { EmptyState } from "@/tui/components/empty-state"
|
|
5
|
-
import { Keymap } from "@/tui/components/keymap"
|
|
6
|
-
import { PanelHeader } from "@/tui/components/panel-header"
|
|
7
|
-
import { ViewShell } from "@/tui/components/view-shell"
|
|
8
|
-
import { funnel } from "@/tui/theme"
|
|
9
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
10
|
-
import type { StreamEvent, StreamStatus } from "@/tui/types"
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
events: StreamEvent[]
|
|
14
|
-
filter: string
|
|
15
|
-
selectedIndex: number
|
|
16
|
-
streamStatus: StreamStatus
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const streamLabel = (status: StreamStatus): string => {
|
|
20
|
-
if (status === "open") return "live"
|
|
21
|
-
if (status === "connecting") return "connecting…"
|
|
22
|
-
if (status === "closed") return "reconnecting…"
|
|
23
|
-
|
|
24
|
-
return "offline"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const formatTime = (ms: number): string => {
|
|
28
|
-
const date = new Date(ms)
|
|
29
|
-
const hh = String(date.getHours()).padStart(2, "0")
|
|
30
|
-
const mm = String(date.getMinutes()).padStart(2, "0")
|
|
31
|
-
const ss = String(date.getSeconds()).padStart(2, "0")
|
|
32
|
-
|
|
33
|
-
return `${hh}:${mm}:${ss}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const truncate = (value: string, max: number): string => {
|
|
37
|
-
const flat = value.replace(/\s+/g, " ").trim()
|
|
38
|
-
|
|
39
|
-
if (flat.length <= max) return flat
|
|
40
|
-
|
|
41
|
-
return `${flat.slice(0, max - 1)}…`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const matches = (event: StreamEvent, filter: string): boolean => {
|
|
45
|
-
if (!filter) return true
|
|
46
|
-
|
|
47
|
-
const needle = filter.toLowerCase()
|
|
48
|
-
const haystack = [
|
|
49
|
-
event.content,
|
|
50
|
-
event.meta.connector ?? "",
|
|
51
|
-
event.meta.event_type ?? "",
|
|
52
|
-
event.meta.channel ?? "",
|
|
53
|
-
]
|
|
54
|
-
.join(" ")
|
|
55
|
-
.toLowerCase()
|
|
56
|
-
|
|
57
|
-
return haystack.includes(needle)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const tryParseJson = (value: string): unknown => {
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse(value)
|
|
63
|
-
} catch {
|
|
64
|
-
return value
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const formatJson = (value: unknown): string => {
|
|
69
|
-
try {
|
|
70
|
-
return JSON.stringify(value, null, 2)
|
|
71
|
-
} catch {
|
|
72
|
-
return String(value)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Live event stream + detail of the selected event.
|
|
78
|
-
*
|
|
79
|
-
* The events list lives inside `ViewShell` (padded canvas) while the
|
|
80
|
-
* detail strip is a sibling `DetailBar` so its background spans the
|
|
81
|
-
* full main column edge-to-edge and reads as a distinct elevated
|
|
82
|
-
* stratum below the list.
|
|
83
|
-
*/
|
|
84
|
-
export function EventsView(props: Props) {
|
|
85
|
-
const theme = useHasciiTheme()
|
|
86
|
-
const visible = props.events.filter((event) => matches(event, props.filter))
|
|
87
|
-
const selected = visible[props.selectedIndex] ?? null
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<box style={{ flexDirection: "column", flexGrow: 1 }}>
|
|
91
|
-
<ViewShell>
|
|
92
|
-
<PanelHeader
|
|
93
|
-
label="events"
|
|
94
|
-
count={visible.length}
|
|
95
|
-
hint={[
|
|
96
|
-
streamLabel(props.streamStatus),
|
|
97
|
-
`${props.events.length} total`,
|
|
98
|
-
props.filter ? `/${props.filter}/` : null,
|
|
99
|
-
]
|
|
100
|
-
.filter((part): part is string => part !== null)
|
|
101
|
-
.join(" · ")}
|
|
102
|
-
/>
|
|
103
|
-
|
|
104
|
-
{visible.length === 0 ? (
|
|
105
|
-
<EmptyState message="(no events yet — waiting for the first one)" />
|
|
106
|
-
) : (
|
|
107
|
-
visible.map((event, index) => {
|
|
108
|
-
const isSelected = index === props.selectedIndex
|
|
109
|
-
const connector = event.meta.connector ?? "system"
|
|
110
|
-
const eventType = event.meta.event_type ?? "?"
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<text key={event.id} bg={isSelected ? theme.color.muted : undefined}>
|
|
114
|
-
<span fg={theme.color.mutedForeground}>{formatTime(event.receivedAt)}</span>
|
|
115
|
-
<span fg={funnel.faint}> </span>
|
|
116
|
-
<span fg={theme.color.mutedForeground}>{eventType.padEnd(8)}</span>
|
|
117
|
-
<span fg={funnel.faint}>{" · "}</span>
|
|
118
|
-
<span fg={isSelected ? theme.color.foreground : theme.color.foreground}>
|
|
119
|
-
{connector.padEnd(14)}
|
|
120
|
-
</span>
|
|
121
|
-
<span fg={funnel.faint}> </span>
|
|
122
|
-
<span fg={isSelected ? theme.color.foreground : theme.color.mutedForeground}>
|
|
123
|
-
{truncate(event.content, 80)}
|
|
124
|
-
</span>
|
|
125
|
-
</text>
|
|
126
|
-
)
|
|
127
|
-
})
|
|
128
|
-
)}
|
|
129
|
-
|
|
130
|
-
<Keymap
|
|
131
|
-
hints={[
|
|
132
|
-
{ key: "j/k", label: "select" },
|
|
133
|
-
{ key: "/", label: "filter" },
|
|
134
|
-
]}
|
|
135
|
-
/>
|
|
136
|
-
</ViewShell>
|
|
137
|
-
|
|
138
|
-
<DetailBar>
|
|
139
|
-
<PanelHeader label="detail" />
|
|
140
|
-
|
|
141
|
-
{!selected ? (
|
|
142
|
-
<EmptyState message="(select an event with j/k to inspect)" />
|
|
143
|
-
) : (
|
|
144
|
-
<>
|
|
145
|
-
<text>
|
|
146
|
-
<span fg={theme.color.mutedForeground}>meta: </span>
|
|
147
|
-
<span fg={theme.color.foreground}>
|
|
148
|
-
{Object.entries(selected.meta)
|
|
149
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
150
|
-
.join(" ")}
|
|
151
|
-
</span>
|
|
152
|
-
</text>
|
|
153
|
-
<HasciiSeparator />
|
|
154
|
-
<text fg={theme.color.foreground}>{formatJson(tryParseJson(selected.content))}</text>
|
|
155
|
-
</>
|
|
156
|
-
)}
|
|
157
|
-
</DetailBar>
|
|
158
|
-
</box>
|
|
159
|
-
)
|
|
160
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/react */
|
|
2
|
-
import { Card } from "@/tui/components/card"
|
|
3
|
-
import { EmptyState } from "@/tui/components/empty-state"
|
|
4
|
-
import { Keymap } from "@/tui/components/keymap"
|
|
5
|
-
import { PanelHeader } from "@/tui/components/panel-header"
|
|
6
|
-
import { ViewShell } from "@/tui/components/view-shell"
|
|
7
|
-
import { funnel } from "@/tui/theme"
|
|
8
|
-
import { useHasciiTheme } from "@/tui/utils/hascii/theme-context"
|
|
9
|
-
import type { Snapshot, StreamEvent } from "@/tui/types"
|
|
10
|
-
|
|
11
|
-
type Props = {
|
|
12
|
-
snapshot: Snapshot
|
|
13
|
-
events: StreamEvent[]
|
|
14
|
-
selectedIndex: number
|
|
15
|
-
busy: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const eventCountBy = (events: StreamEvent[], connectorName: string): number => {
|
|
19
|
-
let count = 0
|
|
20
|
-
|
|
21
|
-
for (const event of events) {
|
|
22
|
-
if (event.meta.connector === connectorName) count += 1
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return count
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Listener registry — one Card per listener. The Card title shows the
|
|
30
|
-
* listener's name; inside, a single status line carries the alive
|
|
31
|
-
* dot, the connector type, and the event count. Cursor selection is
|
|
32
|
-
* shown via the Card's `selected` accent. Listeners are runtime
|
|
33
|
-
* entities derived from connectors, so there is no add path here —
|
|
34
|
-
* register / remove a connector instead.
|
|
35
|
-
*/
|
|
36
|
-
export function ListenersView(props: Props) {
|
|
37
|
-
const theme = useHasciiTheme()
|
|
38
|
-
const listeners = props.snapshot.listeners
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<ViewShell>
|
|
42
|
-
<PanelHeader
|
|
43
|
-
label="listeners"
|
|
44
|
-
count={listeners.length}
|
|
45
|
-
hint={props.busy ? "working…" : undefined}
|
|
46
|
-
/>
|
|
47
|
-
|
|
48
|
-
{!props.snapshot.daemonReachable ? (
|
|
49
|
-
<EmptyState message="(gateway daemon offline — press G to start it)" />
|
|
50
|
-
) : listeners.length === 0 ? (
|
|
51
|
-
<EmptyState message="(no listeners — register a connector first)" />
|
|
52
|
-
) : (
|
|
53
|
-
listeners.map((entry, index) => {
|
|
54
|
-
const aliveColor = entry.alive ? funnel.alive : funnel.dead
|
|
55
|
-
const count = eventCountBy(props.events, entry.name)
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<Card key={entry.name} title={entry.name} selected={index === props.selectedIndex}>
|
|
59
|
-
<text>
|
|
60
|
-
<span fg={aliveColor}>{entry.alive ? "●" : "○"}</span>
|
|
61
|
-
<span fg={funnel.faint}> </span>
|
|
62
|
-
<span fg={theme.color.mutedForeground}>{entry.type}</span>
|
|
63
|
-
{count > 0 ? <span fg={theme.color.mutedForeground}>{` ${count}↓`}</span> : null}
|
|
64
|
-
</text>
|
|
65
|
-
</Card>
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
)}
|
|
69
|
-
|
|
70
|
-
<Keymap
|
|
71
|
-
hints={[
|
|
72
|
-
{ key: "j/k", label: "select" },
|
|
73
|
-
{ key: "s", label: "start" },
|
|
74
|
-
{ key: "x", label: "stop" },
|
|
75
|
-
{ key: "R", label: "restart" },
|
|
76
|
-
]}
|
|
77
|
-
/>
|
|
78
|
-
</ViewShell>
|
|
79
|
-
)
|
|
80
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/react */
|
|
2
|
-
import { AddRow } from "@/tui/components/add-row"
|
|
3
|
-
import { Card } from "@/tui/components/card"
|
|
4
|
-
import { EditableField } from "@/tui/components/editable-field"
|
|
5
|
-
import { EmptyState } from "@/tui/components/empty-state"
|
|
6
|
-
import { Keymap } from "@/tui/components/keymap"
|
|
7
|
-
import { PanelHeader } from "@/tui/components/panel-header"
|
|
8
|
-
import { ViewShell } from "@/tui/components/view-shell"
|
|
9
|
-
import type { Snapshot } from "@/tui/types"
|
|
10
|
-
import { uniqueName } from "@/tui/unique-name"
|
|
11
|
-
import type { Funnel } from "@/funnel"
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
snapshot: Snapshot
|
|
15
|
-
selectedIndex: number
|
|
16
|
-
funnel: Funnel
|
|
17
|
-
refresh: () => void
|
|
18
|
-
focusedKey: string | null
|
|
19
|
-
setFocusedKey: (key: string | null) => void
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type Profile = Snapshot["profiles"][number]
|
|
23
|
-
|
|
24
|
-
const fieldKey = (name: string, field: string): string => `profiles::${name}::${field}`
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Profile list — one Card per profile. Selection (j/k cursor) shows the
|
|
28
|
-
* `▏` primary rule via the Card's `selected` prop; pressing `c`
|
|
29
|
-
* launches Claude Code with the selected profile.
|
|
30
|
-
*
|
|
31
|
-
* `+ add profile` at the foot creates a new profile pointed at the
|
|
32
|
-
* first existing channel (or an empty string if there are none, which
|
|
33
|
-
* the user must then edit before launching).
|
|
34
|
-
*/
|
|
35
|
-
export function ProfilesView(props: Props) {
|
|
36
|
-
const profiles = props.snapshot.profiles
|
|
37
|
-
const channels = props.snapshot.channels
|
|
38
|
-
|
|
39
|
-
const commit = (profile: Profile, field: string, raw: string): void => {
|
|
40
|
-
try {
|
|
41
|
-
if (field === "name") {
|
|
42
|
-
const next = raw.trim()
|
|
43
|
-
|
|
44
|
-
if (next && next !== profile.name) props.funnel.profiles.rename(profile.name, next)
|
|
45
|
-
} else if (field === "channel") {
|
|
46
|
-
const next = raw.trim()
|
|
47
|
-
|
|
48
|
-
if (next) props.funnel.profiles.update(profile.name, { channelId: next })
|
|
49
|
-
} else if (field === "path") {
|
|
50
|
-
const next = raw.trim()
|
|
51
|
-
|
|
52
|
-
if (next) props.funnel.profiles.update(profile.name, { path: next })
|
|
53
|
-
} else if (field === "sub-agent") {
|
|
54
|
-
const next = raw.trim()
|
|
55
|
-
|
|
56
|
-
if (next) props.funnel.profiles.update(profile.name, { subAgent: next })
|
|
57
|
-
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
props.setFocusedKey(null)
|
|
63
|
-
props.refresh()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const removeProfile = (name: string): void => {
|
|
67
|
-
try {
|
|
68
|
-
props.funnel.profiles.remove(name)
|
|
69
|
-
} catch (error) {
|
|
70
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
props.setFocusedKey(null)
|
|
74
|
-
props.refresh()
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const addProfile = (): void => {
|
|
78
|
-
const name = uniqueName(
|
|
79
|
-
profiles.map((p) => p.name),
|
|
80
|
-
"profile",
|
|
81
|
-
)
|
|
82
|
-
const channelId = channels[0]?.name ?? ""
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
props.funnel.profiles.add({ name, path: "", subAgent: "", channelId })
|
|
86
|
-
props.setFocusedKey(fieldKey(name, "name"))
|
|
87
|
-
} catch (error) {
|
|
88
|
-
props.funnel.logger.error(error instanceof Error ? error.message : String(error))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
props.refresh()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<ViewShell>
|
|
96
|
-
<PanelHeader label="profiles" count={profiles.length} />
|
|
97
|
-
|
|
98
|
-
{profiles.length === 0 ? (
|
|
99
|
-
<EmptyState message="(none — use the button below to add one)" />
|
|
100
|
-
) : (
|
|
101
|
-
profiles.map((profile, index) => (
|
|
102
|
-
<Card
|
|
103
|
-
key={profile.name}
|
|
104
|
-
title={profile.name}
|
|
105
|
-
selected={index === props.selectedIndex}
|
|
106
|
-
onDelete={() => removeProfile(profile.name)}
|
|
107
|
-
>
|
|
108
|
-
<EditableField
|
|
109
|
-
label="name"
|
|
110
|
-
initialValue={profile.name}
|
|
111
|
-
focused={props.focusedKey === fieldKey(profile.name, "name")}
|
|
112
|
-
onFocus={() => props.setFocusedKey(fieldKey(profile.name, "name"))}
|
|
113
|
-
onCommit={(raw) => commit(profile, "name", raw)}
|
|
114
|
-
/>
|
|
115
|
-
<EditableField
|
|
116
|
-
label="path"
|
|
117
|
-
initialValue={profile.path}
|
|
118
|
-
focused={props.focusedKey === fieldKey(profile.name, "path")}
|
|
119
|
-
onFocus={() => props.setFocusedKey(fieldKey(profile.name, "path"))}
|
|
120
|
-
onCommit={(raw) => commit(profile, "path", raw)}
|
|
121
|
-
placeholder="repository path"
|
|
122
|
-
/>
|
|
123
|
-
<EditableField
|
|
124
|
-
label="sub-agent"
|
|
125
|
-
initialValue={profile.subAgent}
|
|
126
|
-
focused={props.focusedKey === fieldKey(profile.name, "sub-agent")}
|
|
127
|
-
onFocus={() => props.setFocusedKey(fieldKey(profile.name, "sub-agent"))}
|
|
128
|
-
onCommit={(raw) => commit(profile, "sub-agent", raw)}
|
|
129
|
-
placeholder="claude --agent value"
|
|
130
|
-
/>
|
|
131
|
-
<EditableField
|
|
132
|
-
label="channel"
|
|
133
|
-
initialValue={profile.channelId}
|
|
134
|
-
focused={props.focusedKey === fieldKey(profile.name, "channel")}
|
|
135
|
-
onFocus={() => props.setFocusedKey(fieldKey(profile.name, "channel"))}
|
|
136
|
-
onCommit={(raw) => commit(profile, "channel", raw)}
|
|
137
|
-
/>
|
|
138
|
-
</Card>
|
|
139
|
-
))
|
|
140
|
-
)}
|
|
141
|
-
|
|
142
|
-
<AddRow label="add profile" onClick={addProfile} />
|
|
143
|
-
|
|
144
|
-
<Keymap
|
|
145
|
-
hints={[
|
|
146
|
-
{ key: "j/k", label: "select" },
|
|
147
|
-
{ key: "c", label: "launch" },
|
|
148
|
-
]}
|
|
149
|
-
/>
|
|
150
|
-
</ViewShell>
|
|
151
|
-
)
|
|
152
|
-
}
|