openuispec 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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/cli/index.ts +49 -0
  4. package/cli/init.ts +390 -0
  5. package/drift/index.ts +398 -0
  6. package/examples/taskflow/README.md +103 -0
  7. package/examples/taskflow/contracts/README.md +18 -0
  8. package/examples/taskflow/contracts/action_trigger.yaml +7 -0
  9. package/examples/taskflow/contracts/collection.yaml +7 -0
  10. package/examples/taskflow/contracts/data_display.yaml +7 -0
  11. package/examples/taskflow/contracts/feedback.yaml +7 -0
  12. package/examples/taskflow/contracts/input_field.yaml +7 -0
  13. package/examples/taskflow/contracts/nav_container.yaml +7 -0
  14. package/examples/taskflow/contracts/surface.yaml +7 -0
  15. package/examples/taskflow/contracts/x_media_player.yaml +185 -0
  16. package/examples/taskflow/flows/create_task.yaml +171 -0
  17. package/examples/taskflow/flows/edit_task.yaml +131 -0
  18. package/examples/taskflow/locales/en.json +158 -0
  19. package/examples/taskflow/openuispec.yaml +144 -0
  20. package/examples/taskflow/platform/android.yaml +32 -0
  21. package/examples/taskflow/platform/ios.yaml +39 -0
  22. package/examples/taskflow/platform/web.yaml +35 -0
  23. package/examples/taskflow/screens/calendar.yaml +23 -0
  24. package/examples/taskflow/screens/home.yaml +220 -0
  25. package/examples/taskflow/screens/profile_edit.yaml +70 -0
  26. package/examples/taskflow/screens/project_detail.yaml +65 -0
  27. package/examples/taskflow/screens/projects.yaml +142 -0
  28. package/examples/taskflow/screens/settings.yaml +178 -0
  29. package/examples/taskflow/screens/task_detail.yaml +317 -0
  30. package/examples/taskflow/tokens/color.yaml +88 -0
  31. package/examples/taskflow/tokens/elevation.yaml +27 -0
  32. package/examples/taskflow/tokens/icons.yaml +189 -0
  33. package/examples/taskflow/tokens/layout.yaml +156 -0
  34. package/examples/taskflow/tokens/motion.yaml +41 -0
  35. package/examples/taskflow/tokens/spacing.yaml +23 -0
  36. package/examples/taskflow/tokens/themes.yaml +34 -0
  37. package/examples/taskflow/tokens/typography.yaml +61 -0
  38. package/package.json +43 -0
  39. package/schema/custom-contract.schema.json +257 -0
  40. package/schema/defs/action.schema.json +272 -0
  41. package/schema/defs/adaptive.schema.json +13 -0
  42. package/schema/defs/common.schema.json +330 -0
  43. package/schema/defs/data-binding.schema.json +119 -0
  44. package/schema/defs/validation.schema.json +121 -0
  45. package/schema/flow.schema.json +164 -0
  46. package/schema/locale.schema.json +26 -0
  47. package/schema/openuispec.schema.json +287 -0
  48. package/schema/platform.schema.json +95 -0
  49. package/schema/screen.schema.json +346 -0
  50. package/schema/tokens/color.schema.json +104 -0
  51. package/schema/tokens/elevation.schema.json +84 -0
  52. package/schema/tokens/icons.schema.json +149 -0
  53. package/schema/tokens/layout.schema.json +170 -0
  54. package/schema/tokens/motion.schema.json +83 -0
  55. package/schema/tokens/spacing.schema.json +93 -0
  56. package/schema/tokens/themes.schema.json +92 -0
  57. package/schema/tokens/typography.schema.json +106 -0
  58. package/schema/validate.ts +258 -0
  59. package/spec/openuispec-v0.1.md +3677 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rustam Samandarov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # OpenUISpec
2
+
3
+ > A single source of truth design language for AI-native, platform-native app development.
4
+
5
+ OpenUISpec is a **semantic UI specification format** that replaces cross-platform frameworks with a declarative design language. Instead of sharing runtime code across platforms, you share the *spec* — and AI generates native SwiftUI, Jetpack Compose, and React code from it.
6
+
7
+ ## Why
8
+
9
+ Cross-platform frameworks (Flutter, React Native, KMP/CMP) solve code duplication by sharing a runtime. OpenUISpec solves it by sharing **intent**:
10
+
11
+ | Approach | Shares | Runs |
12
+ |----------|--------|------|
13
+ | Cross-platform framework | Runtime code | Abstraction layer |
14
+ | **OpenUISpec** | **Semantic spec** | **Native per platform** |
15
+
16
+ The result: each platform feels native, but every app stays consistent because it's generated from the same source of truth.
17
+
18
+ ## How it works
19
+
20
+ ```
21
+ ┌─────────────────────────┐
22
+ │ OpenUISpec (YAML) │ ← Single source of truth
23
+ │ Tokens + Contracts │
24
+ │ Screens + Flows │
25
+ └────────┬────────────────┘
26
+
27
+ AI Generation Layer
28
+
29
+ ┌────┴─────┬──────────┐
30
+ ▼ ▼ ▼
31
+ SwiftUI Compose React
32
+ (iOS) (Android) (Web)
33
+ ```
34
+
35
+ ## Workflows
36
+
37
+ OpenUISpec supports two complementary workflows — one for designing new features, another for day-to-day development.
38
+
39
+ ```
40
+ DESIGN MODE (spec-first) DEVELOPMENT MODE (platform-first)
41
+ For new features & design changes For iteration, tweaks & bug fixes
42
+
43
+ Spec (YAML) Xcode / Android Studio
44
+ │ │
45
+ ▼ ▼
46
+ AI generates native code Developer edits native code
47
+ │ │
48
+ ▼ ▼
49
+ Refine per platform AI syncs changes back to spec
50
+
51
+
52
+ Other platforms see spec diff
53
+ and update their code
54
+ ```
55
+
56
+ **Design mode** is the starting point: you write (or generate) spec YAML, then AI produces native SwiftUI, Compose, or React code. This is how new screens, flows, and design system changes enter the project.
57
+
58
+ **Development mode** is where most time is spent: a developer works in their IDE with live preview, iterating on layout, interactions, and fixes. When they're done, AI reads the code changes and updates the spec YAML. Other platform teams see the spec diff in version control and propagate the changes to their codebase. The spec acts as a shared sync layer — a UI changelog that keeps all platforms consistent.
59
+
60
+ ## Key concepts
61
+
62
+ - **Tokens**: Design values (color, typography, spacing, elevation, motion) with semantic names and constrained ranges
63
+ - **Contracts**: 7 behavioral component families defined by role, props, state machines, and accessibility
64
+ - **Screens**: Compositions of contracts with data bindings, adaptive layout, and conditional rendering
65
+ - **Flows**: Multi-screen navigation journeys, intent-based and platform-agnostic
66
+ - **Actions**: 13 typed action types with composition, error handling, and optimistic updates
67
+ - **Data binding**: Reactive state, format expressions, caching, and loading/error/empty states
68
+ - **Adaptive layout**: Size classes (compact/regular/expanded) with per-section overrides
69
+ - **Platform adaptation**: Per-target overrides for iOS, Android, and Web behaviors
70
+
71
+ ## Quick start
72
+
73
+ ```bash
74
+ npm install -g openuispec
75
+ cd your-project
76
+ openuispec init
77
+ ```
78
+
79
+ This scaffolds a spec directory, starter tokens, and adds rules to `CLAUDE.md` / `AGENTS.md` so AI assistants track spec changes automatically.
80
+
81
+ Then hand your spec to any AI code generator:
82
+
83
+ > Generate a native iOS app from this OpenUISpec. Follow all contract state machines, apply token ranges for iOS, and implement navigation flows as defined. Use `platform/ios.yaml` for SwiftUI-specific overrides.
84
+
85
+ See the [TaskFlow example](./examples/taskflow/) for a complete spec covering all 7 contract families.
86
+
87
+ ## Repository structure
88
+
89
+ ```
90
+ openuispec/
91
+ ├── spec/
92
+ │ └── openuispec-v0.1.md # Full specification (14 sections)
93
+ ├── schema/ # JSON Schema for validation (draft 2020-12)
94
+ │ ├── openuispec.schema.json # Root manifest schema
95
+ │ ├── screen.schema.json # Screen composition schema
96
+ │ ├── flow.schema.json # Navigation flow schema
97
+ │ ├── platform.schema.json # Platform adaptation schema
98
+ │ ├── locale.schema.json # Locale file schema
99
+ │ ├── custom-contract.schema.json # Custom contract extension schema
100
+ │ ├── tokens/
101
+ │ │ ├── color.schema.json # Color token schema
102
+ │ │ ├── typography.schema.json # Typography token schema
103
+ │ │ ├── spacing.schema.json # Spacing token schema
104
+ │ │ ├── elevation.schema.json # Elevation token schema
105
+ │ │ ├── motion.schema.json # Motion token schema
106
+ │ │ ├── layout.schema.json # Layout token schema
107
+ │ │ ├── themes.schema.json # Theme token schema
108
+ │ │ └── icons.schema.json # Icon token schema
109
+ │ ├── defs/
110
+ │ │ ├── common.schema.json # Shared types (icons, badges, etc.)
111
+ │ │ ├── action.schema.json # 13 action types (discriminated union)
112
+ │ │ ├── data-binding.schema.json # Data sources, state, params
113
+ │ │ ├── adaptive.schema.json # Adaptive override pattern
114
+ │ │ └── validation.schema.json # Validation rule definitions
115
+ │ └── validate.ts # Validation script (npm run validate)
116
+ ├── examples/
117
+ │ └── taskflow/ # Complete example app
118
+ │ ├── openuispec.yaml # Root manifest + data model + API endpoints
119
+ │ ├── tokens/
120
+ │ │ ├── color.yaml # Brand + semantic + status colors
121
+ │ │ ├── typography.yaml # Font family + 8-step type scale
122
+ │ │ ├── spacing.yaml # 4px base unit, 9-step scale
123
+ │ │ ├── elevation.yaml # 4-level elevation (none/sm/md/lg)
124
+ │ │ ├── motion.yaml # Durations, easings, patterns
125
+ │ │ ├── layout.yaml # Size classes, primitives, reflow rules
126
+ │ │ ├── themes.yaml # Light, dark, warm variants
127
+ │ │ └── icons.yaml # Icon registry with platform mappings
128
+ │ ├── contracts/ # 7 contract family stubs + custom contracts
129
+ │ │ └── x_media_player.yaml # Custom media player contract (Section 12)
130
+ │ ├── screens/
131
+ │ │ ├── home.yaml # Task list with search, filters, FAB, adaptive nav
132
+ │ │ ├── task_detail.yaml # Full task view with actions + assignee sheet
133
+ │ │ ├── projects.yaml # Project grid + new project dialog
134
+ │ │ ├── project_detail.yaml # Single project with task list (stub)
135
+ │ │ ├── settings.yaml # Preferences, toggles, account management
136
+ │ │ ├── profile_edit.yaml # Edit profile form (stub)
137
+ │ │ └── calendar.yaml # Calendar view (stub)
138
+ │ ├── flows/
139
+ │ │ ├── create_task.yaml # Task creation form (sheet presentation)
140
+ │ │ └── edit_task.yaml # Task editing flow
141
+ │ ├── locales/
142
+ │ │ └── en.json # English locale (ICU MessageFormat)
143
+ │ └── platform/
144
+ │ ├── ios.yaml # SwiftUI overrides + behaviors
145
+ │ ├── android.yaml # Compose overrides + behaviors
146
+ │ └── web.yaml # React overrides + responsive rules
147
+ ├── cli/ # CLI tool (openuispec init, drift, validate)
148
+ │ ├── index.ts # Entry point
149
+ │ └── init.ts # Project scaffolding + AI rules
150
+ ├── drift/ # Drift detection (spec change tracking)
151
+ │ └── index.ts # Hash-based drift checker
152
+ ├── LICENSE
153
+ └── README.md
154
+ ```
155
+
156
+ ## Spec at a glance
157
+
158
+ | Section | What it defines |
159
+ |---------|----------------|
160
+ | 1. Philosophy | Core principles: semantic, constrained, contract-driven, AI-first |
161
+ | 2. Document structure | Project layout and root manifest |
162
+ | 3. Token layer | Color, typography, spacing, elevation, motion, layout, themes, icons |
163
+ | 4. Component contracts | 7 behavioral families (action_trigger, data_display, input_field, nav_container, feedback, surface, collection) |
164
+ | 5. Screen composition | Contract-based layouts, screen-level keys, adaptive layout system |
165
+ | 6. Navigation flows | Multi-screen journeys with transitions and progress |
166
+ | 7. Platform adaptation | Per-target overrides for iOS, Android, Web |
167
+ | 8. AI generation contract | Compliance levels (MUST/SHOULD/MAY), validation, drift detection |
168
+ | 9. Action system | 13 action types, composition, optimistic updates |
169
+ | 10. Data binding & state | Sources, paths, format expressions, reactivity, caching |
170
+ | 11. Internationalization | Locale files, `$t:` references, ICU MessageFormat, RTL, platform mapping |
171
+ | 12. Custom contract extensions | `x_` prefixed domain-specific contracts, registration, dependencies |
172
+ | 13. Form validation & field dependencies | Validation rules, field dependencies, cross-field checks, async validation |
173
+ | 14. Development workflow | Dual-workflow model, drift detection, spec as sync layer |
174
+
175
+ ## The 7 contract families
176
+
177
+ | Contract | Role | Maps to |
178
+ |----------|------|---------|
179
+ | `action_trigger` | Initiates actions | Button, FAB, link |
180
+ | `data_display` | Shows read-only info | Card, list item, stat |
181
+ | `input_field` | Captures user input | TextField, toggle, picker |
182
+ | `nav_container` | Persistent navigation | Tab bar, sidebar, drawer |
183
+ | `feedback` | System status messages | Toast, dialog, banner |
184
+ | `surface` | Contains other components | Sheet, modal, popover |
185
+ | `collection` | Repeating data sets | List, grid, table |
186
+
187
+ ## Status
188
+
189
+ **v0.1 — Draft**. The spec covers all foundational layers. One complete example app (TaskFlow) demonstrates full coverage.
190
+
191
+ ### Roadmap
192
+
193
+ - [x] Token system with constrained ranges
194
+ - [x] 7 component contract families
195
+ - [x] Adaptive layout system (size classes + reflow rules)
196
+ - [x] Action system (13 types, composition, optimistic updates)
197
+ - [x] Data binding & state management (sources, expressions, caching)
198
+ - [x] Format expression grammar with computed expressions
199
+ - [x] Internationalization (i18n) with ICU MessageFormat and `$t:` references
200
+ - [x] JSON Schema for spec validation
201
+ - [x] Custom contract extension mechanism
202
+ - [x] Icon system definition
203
+ - [x] Form system (validation rules, field dependencies)
204
+ - [x] Drift detection (spec change tracking per platform)
205
+ - [x] CLI tool (`openuispec init` for project scaffolding + AI rules)
206
+ - [ ] More example apps (e-commerce, social, dashboard)
207
+
208
+ ## Contributing
209
+
210
+ OpenUISpec is in early development. If you're interested in contributing — especially around AI code generation, platform-specific expertise, or additional example apps — open an issue to start the conversation.
211
+
212
+ ## License
213
+
214
+ MIT
package/cli/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ /**
3
+ * OpenUISpec CLI
4
+ *
5
+ * Usage:
6
+ * openuispec init # scaffold a new spec project
7
+ * openuispec drift # check for spec drift (all targets)
8
+ * openuispec drift --target ios
9
+ * openuispec drift --snapshot --target ios
10
+ */
11
+
12
+ import { init } from "./init.js";
13
+
14
+ async function main(): Promise<void> {
15
+ const [command] = process.argv.slice(2);
16
+
17
+ switch (command) {
18
+ case "init":
19
+ await init();
20
+ break;
21
+
22
+ case "drift":
23
+ console.error("drift command coming soon — use npm run drift for now");
24
+ process.exit(1);
25
+ break;
26
+
27
+ case undefined:
28
+ case "--help":
29
+ case "-h":
30
+ console.log(`
31
+ OpenUISpec CLI v0.1
32
+
33
+ Usage:
34
+ openuispec init Create a new spec project
35
+ openuispec drift [--target <t>] Check for spec drift
36
+ openuispec drift --snapshot --target <t> Snapshot current state
37
+
38
+ Learn more: https://github.com/anthropics/openuispec
39
+ `);
40
+ break;
41
+
42
+ default:
43
+ console.error(`Unknown command: ${command}`);
44
+ console.error(`Run "openuispec --help" for usage.`);
45
+ process.exit(1);
46
+ }
47
+ }
48
+
49
+ main();
package/cli/init.ts ADDED
@@ -0,0 +1,390 @@
1
+ /**
2
+ * openuispec init — interactive project scaffolding
3
+ *
4
+ * Creates folder structure, manifest, and AI assistant rules
5
+ * (CLAUDE.md / AGENTS.md) so AI tools track spec changes properly.
6
+ */
7
+
8
+ import { createInterface } from "node:readline/promises";
9
+ import { stdin, stdout } from "node:process";
10
+ import {
11
+ mkdirSync,
12
+ writeFileSync,
13
+ readFileSync,
14
+ readdirSync,
15
+ existsSync,
16
+ appendFileSync,
17
+ } from "node:fs";
18
+ import { join, relative } from "node:path";
19
+
20
+ // ── prompts ──────────────────────────────────────────────────────────
21
+
22
+ async function ask(
23
+ rl: ReturnType<typeof createInterface>,
24
+ question: string,
25
+ fallback?: string
26
+ ): Promise<string> {
27
+ const suffix = fallback ? ` (${fallback})` : "";
28
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
29
+ return answer || fallback || "";
30
+ }
31
+
32
+ async function askList(
33
+ rl: ReturnType<typeof createInterface>,
34
+ question: string,
35
+ options: string[],
36
+ defaults: string[]
37
+ ): Promise<string[]> {
38
+ console.log(`${question}`);
39
+ for (const opt of options) {
40
+ const mark = defaults.includes(opt) ? "[x]" : "[ ]";
41
+ console.log(` ${mark} ${opt}`);
42
+ }
43
+ const raw = (
44
+ await rl.question(`Enter choices (comma-separated, enter for defaults): `)
45
+ ).trim();
46
+ if (!raw) return defaults;
47
+ return raw
48
+ .split(",")
49
+ .map((s) => s.trim().toLowerCase())
50
+ .filter((s) => options.includes(s));
51
+ }
52
+
53
+ // ── scaffold ─────────────────────────────────────────────────────────
54
+
55
+ function ensureDir(path: string): void {
56
+ if (!existsSync(path)) {
57
+ mkdirSync(path, { recursive: true });
58
+ }
59
+ }
60
+
61
+ function writeIfMissing(path: string, content: string): boolean {
62
+ if (existsSync(path)) {
63
+ console.log(` skip ${relative(process.cwd(), path)} (exists)`);
64
+ return false;
65
+ }
66
+ writeFileSync(path, content);
67
+ console.log(` create ${relative(process.cwd(), path)}`);
68
+ return true;
69
+ }
70
+
71
+ // ── templates ────────────────────────────────────────────────────────
72
+
73
+ function manifestTemplate(
74
+ name: string,
75
+ targets: string[],
76
+ specDir: string
77
+ ): string {
78
+ const targetList = targets.join(", ");
79
+ const outputLines = targets
80
+ .map((t) => {
81
+ if (t === "ios")
82
+ return ` ios: { language: swift, framework: swiftui, min_version: "17.0" }`;
83
+ if (t === "android")
84
+ return ` android: { language: kotlin, framework: compose, min_sdk: 26 }`;
85
+ if (t === "web")
86
+ return ` web: { language: typescript, framework: react, bundler: vite }`;
87
+ return ` ${t}: {}`;
88
+ })
89
+ .join("\n");
90
+
91
+ return `# ${name} — OpenUISpec v0.1
92
+ spec_version: "0.1"
93
+
94
+ project:
95
+ name: "${name}"
96
+ description: ""
97
+
98
+ includes:
99
+ tokens: "./tokens/"
100
+ contracts: "./contracts/"
101
+ screens: "./screens/"
102
+ flows: "./flows/"
103
+ platform: "./platform/"
104
+ locales: "./locales/"
105
+
106
+ i18n:
107
+ default_locale: "en"
108
+ supported_locales: [en]
109
+ fallback_strategy: "default"
110
+
111
+ generation:
112
+ targets: [${targetList}]
113
+ output_format:
114
+ ${outputLines}
115
+
116
+ data_model: {}
117
+
118
+ api:
119
+ base_url: "/api/v1"
120
+ auth: "bearer_token"
121
+ endpoints: {}
122
+ `;
123
+ }
124
+
125
+ function starterColorTokens(): string {
126
+ return `# Design tokens: Color palette
127
+ brand:
128
+ primary:
129
+ "50": "#EEF2FF"
130
+ "100": "#E0E7FF"
131
+ "500": "#6366F1"
132
+ "600": "#4F46E5"
133
+ "900": "#312E81"
134
+
135
+ surface:
136
+ background: "#FFFFFF"
137
+ card: "#F9FAFB"
138
+ elevated: "#FFFFFF"
139
+
140
+ text:
141
+ primary: "#111827"
142
+ secondary: "#6B7280"
143
+ disabled: "#D1D5DB"
144
+
145
+ semantic:
146
+ success: "#10B981"
147
+ warning: "#F59E0B"
148
+ error: "#EF4444"
149
+ info: "#3B82F6"
150
+ `;
151
+ }
152
+
153
+ function starterTypographyTokens(): string {
154
+ return `# Design tokens: Typography
155
+ font_families:
156
+ sans: { default: "System" }
157
+
158
+ type_scale:
159
+ title_lg: { size: 28, weight: bold, tracking: 0, line_height: 1.2 }
160
+ title_md: { size: 22, weight: semibold, tracking: 0, line_height: 1.3 }
161
+ title_sm: { size: 17, weight: semibold, tracking: 0, line_height: 1.3 }
162
+ body_lg: { size: 17, weight: regular, tracking: 0, line_height: 1.5 }
163
+ body_md: { size: 15, weight: regular, tracking: 0, line_height: 1.5 }
164
+ body_sm: { size: 13, weight: regular, tracking: 0, line_height: 1.4 }
165
+ label_lg: { size: 15, weight: medium, tracking: 0.02, line_height: 1.3 }
166
+ label_md: { size: 13, weight: medium, tracking: 0.02, line_height: 1.3 }
167
+ caption: { size: 11, weight: regular, tracking: 0.02, line_height: 1.3 }
168
+ `;
169
+ }
170
+
171
+ function starterSpacingTokens(): string {
172
+ return `# Design tokens: Spacing
173
+ base_unit: 4
174
+ platform_flex: "10%"
175
+
176
+ scale:
177
+ "0": 0
178
+ "1": 4
179
+ "2": 8
180
+ "3": 12
181
+ "4": 16
182
+ "5": 20
183
+ "6": 24
184
+ "8": 32
185
+ "10": 40
186
+ "12": 48
187
+ "16": 64
188
+ `;
189
+ }
190
+
191
+ function starterLocale(): string {
192
+ return JSON.stringify(
193
+ {
194
+ app: {
195
+ name: "My App",
196
+ },
197
+ },
198
+ null,
199
+ 2
200
+ );
201
+ }
202
+
203
+ function aiRulesBlock(specDir: string, targets: string[]): string {
204
+ const targetList = targets.map((t) => `"${t}"`).join(", ");
205
+ return `
206
+ # OpenUISpec — AI Assistant Rules
207
+ # ================================
208
+ # This project uses OpenUISpec to define UI as a semantic spec.
209
+ # Spec files are the single source of truth for all UI across platforms.
210
+
211
+ ## Spec location
212
+ - Spec root: \`${specDir}/\`
213
+ - Manifest: \`${specDir}/openuispec.yaml\`
214
+ - Always read \`openuispec.yaml\` first to understand the project structure.
215
+
216
+ ## Before making UI changes
217
+ 1. Read the relevant spec files (screens, tokens, flows) before modifying any UI code.
218
+ 2. If the change requires a spec update, modify the spec files FIRST, then update generated code.
219
+ 3. Never modify generated code directly — change the spec and regenerate.
220
+
221
+ ## After modifying spec files
222
+ 1. Run \`openuispec drift --snapshot --target <target>\` for each affected platform.
223
+ 2. Targets in this project: ${targetList}.
224
+ 3. Run \`openuispec drift\` to verify no untracked drift remains.
225
+
226
+ ## Spec file conventions
227
+ - Tokens (colors, typography, spacing, motion, icons) live in \`${specDir}/tokens/\`.
228
+ - Contracts (UI component definitions) live in \`${specDir}/contracts/\`.
229
+ - Screens live in \`${specDir}/screens/\`. One screen per file.
230
+ - Navigation flows live in \`${specDir}/flows/\`.
231
+ - Platform overrides live in \`${specDir}/platform/\`.
232
+ - Localization strings live in \`${specDir}/locales/\`.
233
+
234
+ ## Key rules
235
+ - The spec uses 7 contract families: nav_container, surface, action_trigger, input_field, data_display, collection, feedback.
236
+ - Custom contracts are prefixed with \`x_\` (e.g., \`x_media_player\`).
237
+ - Data binding uses \`$data:\`, \`$state:\`, \`$param:\`, \`$t:\` prefixes.
238
+ - Actions use typed action objects (navigate, api_call, set_state, confirm, etc.).
239
+ - When adding a new screen, also create the corresponding spec file.
240
+ - When renaming or removing a screen, update the spec and all flow references.
241
+ `;
242
+ }
243
+
244
+ // ── main ─────────────────────────────────────────────────────────────
245
+
246
+ export async function init(): Promise<void> {
247
+ const rl = createInterface({ input: stdin, output: stdout });
248
+
249
+ console.log("\nOpenUISpec — Project Setup\n");
250
+
251
+ try {
252
+ // 1. Project name
253
+ const cwd = process.cwd();
254
+ const defaultName =
255
+ cwd.split("/").pop()?.replace(/[^a-zA-Z0-9]/g, "") || "MyApp";
256
+ const name = await ask(rl, "Project name", defaultName);
257
+
258
+ // 2. Spec directory
259
+ const specDir = await ask(rl, "Spec directory", "openuispec");
260
+
261
+ // 3. Platforms
262
+ const allTargets = ["ios", "android", "web"];
263
+ const targets = await askList(
264
+ rl,
265
+ "\nWhich platforms?",
266
+ allTargets,
267
+ ["ios", "android"]
268
+ );
269
+
270
+ if (targets.length === 0) {
271
+ console.error("At least one target is required.");
272
+ process.exit(1);
273
+ }
274
+
275
+ // 4. Starter tokens?
276
+ const wantTokens = await ask(rl, "Include starter tokens? (y/n)", "y");
277
+
278
+ // 5. AI rules?
279
+ const wantRules = await ask(
280
+ rl,
281
+ "Add rules to CLAUDE.md and AGENTS.md? (y/n)",
282
+ "y"
283
+ );
284
+
285
+ rl.close();
286
+
287
+ // ── create folders ─────────────────────────────────────────────
288
+
289
+ console.log("\nScaffolding...\n");
290
+
291
+ const root = join(cwd, specDir);
292
+ const dirs = [
293
+ "tokens",
294
+ "contracts",
295
+ "screens",
296
+ "flows",
297
+ "platform",
298
+ "locales",
299
+ ];
300
+
301
+ ensureDir(root);
302
+ for (const d of dirs) {
303
+ ensureDir(join(root, d));
304
+ }
305
+
306
+ // ── manifest ───────────────────────────────────────────────────
307
+
308
+ writeIfMissing(
309
+ join(root, "openuispec.yaml"),
310
+ manifestTemplate(name, targets, specDir)
311
+ );
312
+
313
+ // ── starter tokens ─────────────────────────────────────────────
314
+
315
+ if (wantTokens.toLowerCase().startsWith("y")) {
316
+ writeIfMissing(join(root, "tokens", "color.yaml"), starterColorTokens());
317
+ writeIfMissing(
318
+ join(root, "tokens", "typography.yaml"),
319
+ starterTypographyTokens()
320
+ );
321
+ writeIfMissing(
322
+ join(root, "tokens", "spacing.yaml"),
323
+ starterSpacingTokens()
324
+ );
325
+ }
326
+
327
+ // ── starter locale ─────────────────────────────────────────────
328
+
329
+ writeIfMissing(
330
+ join(root, "locales", "en.json"),
331
+ starterLocale() + "\n"
332
+ );
333
+
334
+ // ── .gitkeep for empty dirs ────────────────────────────────────
335
+
336
+ for (const d of dirs) {
337
+ const dir = join(root, d);
338
+ const entries = existsSync(dir)
339
+ ? readdirSync(dir).filter((f) => f !== ".gitkeep")
340
+ : [];
341
+ if (entries.length === 0) {
342
+ const gk = join(dir, ".gitkeep");
343
+ if (!existsSync(gk)) {
344
+ writeFileSync(gk, "");
345
+ console.log(` create ${relative(cwd, gk)}`);
346
+ }
347
+ }
348
+ }
349
+
350
+ // ── AI assistant rules ─────────────────────────────────────────
351
+
352
+ if (wantRules.toLowerCase().startsWith("y")) {
353
+ const rules = aiRulesBlock(specDir, targets);
354
+
355
+ for (const file of ["CLAUDE.md", "AGENTS.md"]) {
356
+ const filePath = join(cwd, file);
357
+ if (existsSync(filePath)) {
358
+ const existing = readFileSync(filePath, "utf-8");
359
+ if (existing.includes("OpenUISpec")) {
360
+ console.log(` skip ${file} (already has OpenUISpec rules)`);
361
+ continue;
362
+ }
363
+ appendFileSync(filePath, "\n" + rules);
364
+ console.log(` update ${file} (appended rules)`);
365
+ } else {
366
+ writeFileSync(filePath, rules.trimStart());
367
+ console.log(` create ${file}`);
368
+ }
369
+ }
370
+ }
371
+
372
+ // ── done ───────────────────────────────────────────────────────
373
+
374
+ console.log(`
375
+ Done! Your spec project is ready at ./${specDir}/
376
+
377
+ Next steps:
378
+ 1. Edit ${specDir}/openuispec.yaml to define your data model and API
379
+ 2. Add screens in ${specDir}/screens/
380
+ 3. Add flows in ${specDir}/flows/
381
+ 4. Generate code for your target platform
382
+ 5. Run \`openuispec drift --snapshot --target ${targets[0]}\` to baseline
383
+
384
+ Learn more: https://github.com/anthropics/openuispec
385
+ `);
386
+ } catch (err) {
387
+ rl.close();
388
+ throw err;
389
+ }
390
+ }