agent-facets 0.1.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/bunfig.toml +2 -0
  3. package/dist/facet +0 -0
  4. package/dist/facet-ink-test +0 -0
  5. package/package.json +41 -0
  6. package/src/__tests__/cli.test.ts +152 -0
  7. package/src/__tests__/create-build.test.ts +207 -0
  8. package/src/commands/build.ts +45 -0
  9. package/src/commands/create/index.ts +30 -0
  10. package/src/commands/create/types.ts +9 -0
  11. package/src/commands/create/wizard.tsx +24 -0
  12. package/src/commands/create-scaffold.ts +180 -0
  13. package/src/commands.ts +31 -0
  14. package/src/help.ts +36 -0
  15. package/src/index.ts +9 -0
  16. package/src/run.ts +55 -0
  17. package/src/suggest.ts +35 -0
  18. package/src/tui/components/asset-inline-input.tsx +79 -0
  19. package/src/tui/components/asset-item.tsx +48 -0
  20. package/src/tui/components/asset-section.tsx +145 -0
  21. package/src/tui/components/button.tsx +92 -0
  22. package/src/tui/components/editable-field.tsx +172 -0
  23. package/src/tui/components/exit-toast.tsx +20 -0
  24. package/src/tui/components/stage-row.tsx +33 -0
  25. package/src/tui/components/version-selector.tsx +79 -0
  26. package/src/tui/context/focus-mode-context.ts +36 -0
  27. package/src/tui/context/focus-order-context.ts +62 -0
  28. package/src/tui/context/form-state-context.ts +229 -0
  29. package/src/tui/gradient.ts +1 -0
  30. package/src/tui/hooks/use-exit-keys.ts +75 -0
  31. package/src/tui/hooks/use-navigation-keys.ts +34 -0
  32. package/src/tui/layouts/wizard-layout.tsx +41 -0
  33. package/src/tui/theme.ts +1 -0
  34. package/src/tui/views/build/build-view.tsx +153 -0
  35. package/src/tui/views/create/confirm-view.tsx +74 -0
  36. package/src/tui/views/create/create-view.tsx +154 -0
  37. package/src/tui/views/create/wizard.tsx +68 -0
  38. package/src/version.ts +3 -0
  39. package/tsconfig.json +4 -0
@@ -0,0 +1,154 @@
1
+ import { Box, Text } from 'ink'
2
+ import { useCallback, useEffect } from 'react'
3
+ import { ASSET_LABELS, ASSET_TYPES } from '../../../commands/create/types.ts'
4
+ import { isValidKebabCase } from '../../../commands/create-scaffold.ts'
5
+ import { AssetSection } from '../../components/asset-section.tsx'
6
+ import { Button } from '../../components/button.tsx'
7
+ import { EditableField } from '../../components/editable-field.tsx'
8
+ import { useFocusOrder } from '../../context/focus-order-context.ts'
9
+ import { useFormState } from '../../context/form-state-context.ts'
10
+ import { WizardLayout } from '../../layouts/wizard-layout.tsx'
11
+
12
+ function computeFocusIds(form: ReturnType<typeof useFormState>['form'], hasAnyAsset: boolean): string[] {
13
+ const ids: string[] = ['field-name', 'field-description', 'field-version']
14
+
15
+ for (const type of ASSET_TYPES) {
16
+ const section = form.assets[type]
17
+
18
+ for (let i = 0; i < section.items.length; i++) {
19
+ ids.push(`item-${type}-${i}`)
20
+ }
21
+
22
+ ids.push(`add-${type}`)
23
+ }
24
+
25
+ if (hasAnyAsset) {
26
+ ids.push('create-btn')
27
+ }
28
+
29
+ return ids
30
+ }
31
+
32
+ export function CreateView({ onSubmit }: { onSubmit: () => void }) {
33
+ const { form } = useFormState()
34
+ const { setFocusIds, focus, focusedId } = useFocusOrder()
35
+
36
+ const validateKebab = useCallback((v: string) => {
37
+ if (!v) return undefined
38
+ if (!isValidKebabCase(v)) return 'Must be kebab-case (e.g., my-facet)'
39
+ return undefined
40
+ }, [])
41
+
42
+ // Derived state from context
43
+ const nameConfirmed = form.fields.name.status === 'confirmed'
44
+ const descriptionConfirmed = form.fields.description.status === 'confirmed'
45
+ const versionConfirmed = form.fields.version.status === 'confirmed'
46
+
47
+ // Settled = confirmed or has a value (being revised). Used for dimming.
48
+ const nameSettled = nameConfirmed || !!form.fields.name.value
49
+ const descriptionSettled = descriptionConfirmed || !!form.fields.description.value
50
+ const versionSettled = versionConfirmed || !!form.fields.version.value
51
+
52
+ const descriptionReady = nameSettled
53
+ const versionReady = nameSettled && descriptionSettled
54
+ const assetsReady = nameSettled && descriptionSettled && versionSettled
55
+
56
+ const totalAssets = form.assets.skill.items.length + form.assets.command.items.length + form.assets.agent.items.length
57
+ const hasAnyAsset = totalAssets > 0
58
+ const canCreate = assetsReady && hasAnyAsset
59
+
60
+ // Recompute focus order
61
+ useEffect(() => {
62
+ const ids = computeFocusIds(form, hasAnyAsset)
63
+ setFocusIds(ids)
64
+
65
+ if (focusedId && !ids.includes(focusedId)) {
66
+ focus(ids[0] ?? '')
67
+ }
68
+ }, [form, hasAnyAsset, setFocusIds, focus, focusedId])
69
+
70
+ // Auto-focus name field on mount
71
+ useEffect(() => {
72
+ if (!focusedId) {
73
+ focus('field-name')
74
+ }
75
+ }, [focusedId, focus])
76
+
77
+ return (
78
+ <WizardLayout>
79
+ <EditableField
80
+ field="name"
81
+ label="Name"
82
+ placeholder="my-facet"
83
+ hint="kebab-case"
84
+ validate={validateKebab}
85
+ onConfirm={() => focus('field-description')}
86
+ />
87
+
88
+ <EditableField
89
+ field="description"
90
+ label="Description"
91
+ placeholder="A brief description of what this facet does"
92
+ dimmed={!descriptionReady}
93
+ onConfirm={() => focus('field-version')}
94
+ />
95
+
96
+ <EditableField
97
+ field="version"
98
+ label="Version"
99
+ hint="SemVer N.N.N"
100
+ defaultValue="0.1.0"
101
+ dimmed={!versionReady}
102
+ validate={(v) => (/^\d+\.\d+\.\d+$/.test(v) ? undefined : 'Must be SemVer (e.g., 0.1.0)')}
103
+ onConfirm={() => focus(`add-${ASSET_TYPES[0]}`)}
104
+ />
105
+
106
+ {ASSET_TYPES.map((type) => (
107
+ <Box key={type} marginTop={0}>
108
+ <AssetSection
109
+ section={type}
110
+ label={ASSET_LABELS[type]}
111
+ defaultName={form.assets[type].items.length === 0 ? form.fields.name.value : undefined}
112
+ dimmed={!assetsReady}
113
+ validate={(v) => {
114
+ if (!isValidKebabCase(v)) return 'Must be kebab-case'
115
+ const editing = form.assets[type].editing
116
+ if (form.assets[type].items.some((item) => item === v && item !== editing)) return `"${v}" already exists`
117
+ return undefined
118
+ }}
119
+ />
120
+ </Box>
121
+ ))}
122
+
123
+ <Box marginTop={1}>
124
+ <Button
125
+ id="create-btn"
126
+ label="[ Create facet ]"
127
+ color="green"
128
+ disabled={!canCreate}
129
+ gradient={canCreate}
130
+ animateGradient={canCreate && focusedId === 'create-btn'}
131
+ onPress={onSubmit}
132
+ />
133
+ </Box>
134
+
135
+ {!canCreate && (
136
+ <Box marginLeft={2}>
137
+ <Text dimColor>
138
+ {!nameConfirmed
139
+ ? 'Enter a name to continue'
140
+ : !descriptionConfirmed
141
+ ? 'Enter a description to continue'
142
+ : !versionConfirmed
143
+ ? 'Enter a version to continue'
144
+ : 'Add at least one skill, command, or agent'}
145
+ </Text>
146
+ </Box>
147
+ )}
148
+
149
+ <Box marginTop={1}>
150
+ <Text dimColor>↑ ↓ to navigate, Enter to select/edit, Esc Esc to exit</Text>
151
+ </Box>
152
+ </WizardLayout>
153
+ )
154
+ }
@@ -0,0 +1,68 @@
1
+ import { useApp } from 'ink'
2
+ import { useCallback, useState } from 'react'
3
+ import type { CreateOptions } from '../../../commands/create-scaffold.ts'
4
+ import { FocusModeProvider, useFocusMode } from '../../context/focus-mode-context.ts'
5
+ import { FocusOrderProvider } from '../../context/focus-order-context.ts'
6
+ import { FormStateProvider, useFormState } from '../../context/form-state-context.ts'
7
+ import { useExitKeys } from '../../hooks/use-exit-keys.ts'
8
+ import { useNavigationKeys } from '../../hooks/use-navigation-keys.ts'
9
+ import { ConfirmView } from './confirm-view.tsx'
10
+ import { CreateView } from './create-view.tsx'
11
+
12
+ export interface CreateWizardProps {
13
+ onComplete: (opts: CreateOptions) => void
14
+ onCancel: () => void
15
+ }
16
+
17
+ function CreateWizardInner({ onComplete, onCancel }: CreateWizardProps) {
18
+ const { exit } = useApp()
19
+ const { setMode } = useFocusMode()
20
+ const { toCreateOptions } = useFormState()
21
+
22
+ const [confirming, setConfirming] = useState(false)
23
+
24
+ const cancel = useCallback(() => {
25
+ onCancel()
26
+ exit()
27
+ }, [onCancel, exit])
28
+
29
+ useExitKeys(cancel)
30
+ useNavigationKeys()
31
+
32
+ if (confirming) {
33
+ return (
34
+ <ConfirmView
35
+ opts={toCreateOptions()}
36
+ onConfirm={() => {
37
+ onComplete(toCreateOptions())
38
+ exit()
39
+ }}
40
+ onBack={() => {
41
+ setConfirming(false)
42
+ setMode('form-navigation')
43
+ }}
44
+ />
45
+ )
46
+ }
47
+
48
+ return (
49
+ <CreateView
50
+ onSubmit={() => {
51
+ setConfirming(true)
52
+ setMode('form-confirmation')
53
+ }}
54
+ />
55
+ )
56
+ }
57
+
58
+ export function CreateWizard(props: CreateWizardProps) {
59
+ return (
60
+ <FocusModeProvider>
61
+ <FocusOrderProvider>
62
+ <FormStateProvider>
63
+ <CreateWizardInner {...props} />
64
+ </FormStateProvider>
65
+ </FocusOrderProvider>
66
+ </FocusModeProvider>
67
+ )
68
+ }
package/src/version.ts ADDED
@@ -0,0 +1,3 @@
1
+ import pkg from '../package.json'
2
+
3
+ export const version: string = pkg.version
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "include": ["src"]
4
+ }