create-twinbloc-app 0.1.1 → 0.1.3
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 +123 -5
- package/bin/cli.js +155 -2
- package/package.json +1 -1
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/AGENTS.md +946 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/SKILL.md +89 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/template/react-native-starter/.agents/skills/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/AGENTS.md +2897 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/SKILL.md +121 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/animation-derived-value.md +53 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/design-system-compound-components.md +66 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/js-hoist-intl.md +61 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-function-references.md +132 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-images.md +53 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-item-types.md +104 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/react-state-fallback.md +56 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/react-state-minimize.md +65 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/state-ground-truth.md +80 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-expo-image.md +66 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-image-gallery.md +104 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-measure-views.md +78 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-menus.md +174 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-native-modals.md +77 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-pressable.md +61 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/template/react-native-starter/.agents/skills/vercel-react-native-skills/rules/ui-styling.md +87 -0
- package/template/react-native-starter/.env.development +2 -0
- package/template/react-native-starter/.env.production +2 -0
- package/template/react-native-starter/.env.staging +2 -0
- package/template/react-native-starter/README.md +1 -2
- package/template/react-native-starter/app.config.ts +57 -0
- package/template/react-native-starter/babel.config.js +21 -0
- package/template/react-native-starter/eslint.config.js +35 -3
- package/template/react-native-starter/global.css +3 -0
- package/template/react-native-starter/jest.config.js +13 -0
- package/template/react-native-starter/jest.setup.js +184 -0
- package/template/react-native-starter/metro.config.js +8 -0
- package/template/react-native-starter/nativewind-env.d.ts +3 -0
- package/template/react-native-starter/package-lock.json +5913 -1647
- package/template/react-native-starter/package.json +53 -2
- package/template/react-native-starter/root-env.js +122 -0
- package/template/react-native-starter/src/api/common/__tests__/api-provider.test.tsx +83 -0
- package/template/react-native-starter/src/api/common/__tests__/api-utils.test.ts +117 -0
- package/template/react-native-starter/src/api/common/__tests__/execute-client.test.ts +118 -0
- package/template/react-native-starter/src/api/common/api-provider.tsx +57 -0
- package/template/react-native-starter/src/api/common/api-utils.ts +159 -0
- package/template/react-native-starter/src/api/common/execute-client.ts +89 -0
- package/template/react-native-starter/src/api/common/index.ts +4 -0
- package/template/react-native-starter/src/api/common/types.ts +10 -0
- package/template/react-native-starter/src/api/index.ts +1 -0
- package/template/react-native-starter/src/app/(main)/_layout.tsx +15 -0
- package/template/react-native-starter/src/app/(main)/index.tsx +223 -0
- package/template/react-native-starter/src/app/+html.tsx +46 -0
- package/template/react-native-starter/src/app/[...messing].tsx +25 -0
- package/template/react-native-starter/src/app/_layout.tsx +56 -0
- package/template/react-native-starter/src/app/onboarding.tsx +23 -0
- package/template/react-native-starter/src/components/index.ts +1 -0
- package/template/react-native-starter/src/components/providers/index.tsx +62 -0
- package/template/react-native-starter/src/components/ui/__tests__/ui-basic.test.tsx +212 -0
- package/template/react-native-starter/src/components/ui/__tests__/ui-forms.test.tsx +116 -0
- package/template/react-native-starter/src/components/ui/accordion.tsx +142 -0
- package/template/react-native-starter/src/components/ui/avatar.tsx +141 -0
- package/template/react-native-starter/src/components/ui/bottom-sheet.tsx +120 -0
- package/template/react-native-starter/src/components/ui/button.tsx +139 -0
- package/template/react-native-starter/src/components/ui/check-box.tsx +74 -0
- package/template/react-native-starter/src/components/ui/container.tsx +52 -0
- package/template/react-native-starter/src/components/ui/icon.tsx +30 -0
- package/template/react-native-starter/src/components/ui/image.tsx +18 -0
- package/template/react-native-starter/src/components/ui/index.ts +15 -0
- package/template/react-native-starter/src/components/ui/input-view.tsx +22 -0
- package/template/react-native-starter/src/components/ui/input.tsx +132 -0
- package/template/react-native-starter/src/components/ui/progress-bar.tsx +136 -0
- package/template/react-native-starter/src/components/ui/radio.tsx +67 -0
- package/template/react-native-starter/src/components/ui/safe-fast-image.tsx +50 -0
- package/template/react-native-starter/src/components/ui/select.tsx +247 -0
- package/template/react-native-starter/src/components/ui/stacks.tsx +49 -0
- package/template/react-native-starter/src/components/ui/switch.tsx +134 -0
- package/template/react-native-starter/src/components/ui/text.tsx +115 -0
- package/template/react-native-starter/src/components/ui/toggle-shared.tsx +49 -0
- package/template/react-native-starter/src/components/utilities/colors.js +147 -0
- package/template/react-native-starter/src/components/utilities/confirm-dialog.tsx +112 -0
- package/template/react-native-starter/src/components/utilities/index.ts +4 -0
- package/template/react-native-starter/src/components/utilities/show-toast.ts +75 -0
- package/template/react-native-starter/src/components/utilities/ui-utils.tsx +7 -0
- package/template/react-native-starter/src/hooks/general/index.ts +0 -0
- package/template/react-native-starter/src/hooks/general/use-countdown.ts +61 -0
- package/template/react-native-starter/src/hooks/general/use-debounce.ts +22 -0
- package/template/react-native-starter/src/hooks/general/use-is-first-time.tsx +17 -0
- package/template/react-native-starter/src/hooks/general/use-select-theme.ts +34 -0
- package/template/react-native-starter/src/hooks/general/use-theme.tsx +47 -0
- package/template/react-native-starter/src/hooks/index.ts +1 -0
- package/template/react-native-starter/src/lib/app-initializer.ts +31 -0
- package/template/react-native-starter/src/lib/env.ts +12 -0
- package/template/react-native-starter/src/lib/i18n/__tests__/index.test.ts +108 -0
- package/template/react-native-starter/src/lib/i18n/__tests__/utils.test.ts +123 -0
- package/template/react-native-starter/src/lib/i18n/index.tsx +51 -0
- package/template/react-native-starter/src/lib/i18n/react-i18next.d.ts +12 -0
- package/template/react-native-starter/src/lib/i18n/resources.ts +28 -0
- package/template/react-native-starter/src/lib/i18n/types.ts +23 -0
- package/template/react-native-starter/src/lib/i18n/utils.tsx +65 -0
- package/template/react-native-starter/src/lib/index.ts +1 -0
- package/template/react-native-starter/src/lib/utils/__tests__/secure-store.test.ts +75 -0
- package/template/react-native-starter/src/lib/utils/__tests__/storage.test.ts +168 -0
- package/template/react-native-starter/src/lib/utils/blurhash.ts +29 -0
- package/template/react-native-starter/src/lib/utils/extract-error.ts +23 -0
- package/template/react-native-starter/src/lib/utils/format-currency.ts +13 -0
- package/template/react-native-starter/src/lib/utils/index.ts +2 -0
- package/template/react-native-starter/src/lib/utils/rate-app.ts +37 -0
- package/template/react-native-starter/src/lib/utils/secure-store.ts +83 -0
- package/template/react-native-starter/src/lib/utils/storage.ts +126 -0
- package/template/react-native-starter/src/lib/utils/toast-config.ts +33 -0
- package/template/react-native-starter/src/store/auth/index.ts +69 -0
- package/template/react-native-starter/src/store/auth/utils.ts +26 -0
- package/template/react-native-starter/src/store/store-utils.ts +13 -0
- package/template/react-native-starter/src/store/utility/index.tsx +39 -0
- package/template/react-native-starter/src/translations/ar.json +13 -0
- package/template/react-native-starter/src/translations/en.json +13 -0
- package/template/react-native-starter/src/translations/es.json +13 -0
- package/template/react-native-starter/src/translations/fr.json +13 -0
- package/template/react-native-starter/src/types/expo-asset.d.ts +9 -0
- package/template/react-native-starter/tailwind.config.js +18 -0
- package/template/react-native-starter/tsconfig.json +7 -9
- package/template/react-native-starter/app/_layout.tsx +0 -5
- package/template/react-native-starter/app/index.tsx +0 -86
- package/template/react-native-starter/app.json +0 -50
- /package/template/react-native-starter/src/{state → store}/useCounterStore.ts +0 -0
package/README.md
CHANGED
|
@@ -1,14 +1,51 @@
|
|
|
1
1
|
# Create Twinbloc App
|
|
2
2
|
|
|
3
|
-
Create a React Native app
|
|
3
|
+
Create a React Native app with the Twinbloc starter template. This CLI scaffolds a production-ready Expo SDK 54 project with Expo Router, TypeScript, native modules, and a curated set of defaults for state, API, UI, and localization.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What You Get
|
|
6
|
+
|
|
7
|
+
- Expo SDK 54 project with Expo Router and typed routes
|
|
8
|
+
- React Native 0.81 + React 19 setup
|
|
9
|
+
- TypeScript-first structure with strict TS config
|
|
10
|
+
- NativeWind + Tailwind utilities, themed UI primitives, and tokens
|
|
11
|
+
- Zustand store examples with persisted auth state
|
|
12
|
+
- TanStack Query with MMKV persistence
|
|
13
|
+
- Axios API client with auth-aware helpers
|
|
14
|
+
- i18next localization with English, Spanish, French, and Arabic samples
|
|
15
|
+
- Reanimated-ready bottom sheet provider and gesture handler setup
|
|
16
|
+
- Toasts, haptics, blur, image, and icon tooling prewired
|
|
17
|
+
- Environment-driven app config for name, slug, scheme, bundle IDs
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Node.js 18+
|
|
22
|
+
- npm, yarn, pnpm, or bun
|
|
23
|
+
- Xcode for iOS (macOS) and/or Android Studio for Android
|
|
24
|
+
|
|
25
|
+
## Quick Start (npm)
|
|
6
26
|
|
|
7
27
|
```bash
|
|
8
28
|
npx create-twinbloc-app@latest MyApp
|
|
29
|
+
cd MyApp
|
|
30
|
+
npm run start
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then build and open a dev client:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx expo run:ios
|
|
37
|
+
npx expo run:android
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Using Other Package Managers
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
yarn create twinbloc-app MyApp
|
|
44
|
+
pnpm create twinbloc-app MyApp
|
|
45
|
+
bun create twinbloc-app MyApp
|
|
9
46
|
```
|
|
10
47
|
|
|
11
|
-
## Options
|
|
48
|
+
## CLI Options
|
|
12
49
|
|
|
13
50
|
```bash
|
|
14
51
|
npx create-twinbloc-app@latest MyApp --example base
|
|
@@ -19,6 +56,87 @@ npx create-twinbloc-app@latest MyApp --pm pnpm
|
|
|
19
56
|
npx create-twinbloc-app@latest MyApp --pm bun
|
|
20
57
|
```
|
|
21
58
|
|
|
22
|
-
|
|
59
|
+
### Example Variants
|
|
60
|
+
|
|
61
|
+
Only the base template ships in this repo. Use:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx create-twinbloc-app@latest MyApp --example base
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Running the App
|
|
68
|
+
|
|
69
|
+
This starter includes native modules, so you should use a development build instead of Expo Go.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm run start
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then run a dev client when you need native modules:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx expo run:ios
|
|
79
|
+
npx expo run:android
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Project Structure
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
MyApp/
|
|
86
|
+
app/ # Expo Router routes
|
|
87
|
+
assets/ # Images and icons
|
|
88
|
+
src/
|
|
89
|
+
api/ # Axios client and React Query helpers
|
|
90
|
+
components/ # UI components and providers
|
|
91
|
+
hooks/ # App hooks
|
|
92
|
+
lib/ # i18n, env, storage, utilities
|
|
93
|
+
store/ # Zustand stores
|
|
94
|
+
translations/ # i18n resource files
|
|
95
|
+
app.config.ts # Env-driven Expo config
|
|
96
|
+
global.css # NativeWind globals
|
|
97
|
+
package.json # Scripts and dependencies
|
|
98
|
+
tsconfig.json # TypeScript config
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Environment Configuration
|
|
102
|
+
|
|
103
|
+
The template loads environment values from `.env.{APP_ENV}` and `.env` with `APP_ENV=development|staging|production`.
|
|
104
|
+
|
|
105
|
+
Required by default:
|
|
106
|
+
|
|
107
|
+
- `EXPO_PUBLIC_API_URL` for your API base URL
|
|
108
|
+
|
|
109
|
+
Optional overrides:
|
|
110
|
+
|
|
111
|
+
- `APP_NAME`, `APP_SLUG`, `APP_SCHEME`
|
|
112
|
+
- `APP_BUNDLE_ID_DEVELOPMENT`, `APP_BUNDLE_ID_STAGING`, `APP_BUNDLE_ID_PRODUCTION`
|
|
113
|
+
- `APP_PACKAGE_DEVELOPMENT`, `APP_PACKAGE_STAGING`, `APP_PACKAGE_PRODUCTION`
|
|
114
|
+
|
|
115
|
+
Values are validated on startup. If you update `.env` files and still see errors, restart the dev server with `-c` to clear cache.
|
|
116
|
+
|
|
117
|
+
## Scripts
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run start # Expo dev server
|
|
121
|
+
npm run ios # Build iOS dev client
|
|
122
|
+
npm run android # Build Android dev client
|
|
123
|
+
npm run web # Run web build
|
|
124
|
+
npm run lint # Lint project
|
|
125
|
+
npm run test # Jest tests
|
|
126
|
+
npm run reset-project # Move starter to app-example and create a fresh app
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Template Details
|
|
130
|
+
|
|
131
|
+
- Location: `template/react-native-starter`
|
|
132
|
+
- Framework: Expo SDK 54 + Expo Router
|
|
133
|
+
- Styling: NativeWind + Tailwind utilities
|
|
134
|
+
- State: Zustand stores in `src/store`
|
|
135
|
+
- Data: TanStack Query helpers in `src/api`
|
|
136
|
+
- i18n: `src/lib/i18n` and `src/translations`
|
|
137
|
+
|
|
138
|
+
## Troubleshooting
|
|
23
139
|
|
|
24
|
-
|
|
140
|
+
- If install fails, retry with `--pm` to force a package manager.
|
|
141
|
+
- If the folder is not empty, choose a new directory name.
|
|
142
|
+
- If your app launches but native modules crash, rebuild the dev client.
|
package/bin/cli.js
CHANGED
|
@@ -5,6 +5,8 @@ import fs from "fs-extra";
|
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import readline from "readline";
|
|
9
|
+
import { stdin as input, stdout as output } from "process";
|
|
8
10
|
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -64,6 +66,129 @@ const runInstall = (dir, pm) => {
|
|
|
64
66
|
if (result.status !== 0) process.exit(result.status);
|
|
65
67
|
};
|
|
66
68
|
|
|
69
|
+
const brandPrimary = chalk.hex("#6CABDD");
|
|
70
|
+
const brandDeep = chalk.hex("#1C2C5B");
|
|
71
|
+
const rnBlue = chalk.hex("#61DAFB");
|
|
72
|
+
const uiMuted = chalk.gray;
|
|
73
|
+
const uiAccent = chalk.hex("#9FD7F2");
|
|
74
|
+
const uiActive = chalk.hex("#88C6FF");
|
|
75
|
+
const uiSelected = chalk.hex("#6EDC91");
|
|
76
|
+
|
|
77
|
+
const printBanner = () => {
|
|
78
|
+
const lines = [
|
|
79
|
+
"████████╗██╗ ██╗██╗███╗ ██╗██████╗ ██╗ ██████╗ ██████╗",
|
|
80
|
+
"╚══██╔══╝██║ ██║██║████╗ ██║██╔══██╗██║ ██╔═══██╗██╔════╝",
|
|
81
|
+
" ██║ ██║ █╗ ██║██║██╔██╗ ██║██████╔╝██║ ██║ ██║██║",
|
|
82
|
+
" ██║ ██║███╗██║██║██║╚██╗██║██╔══██╗██║ ██║ ██║██║",
|
|
83
|
+
" ██║ ╚███╔███╔╝██║██║ ╚████║██████╔╝███████╗╚██████╔╝╚██████╗",
|
|
84
|
+
" ╚═╝ ╚══╝╚══╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝",
|
|
85
|
+
];
|
|
86
|
+
lines.forEach((line, index) => {
|
|
87
|
+
const paint = index < 3 ? brandPrimary : brandDeep;
|
|
88
|
+
console.log(paint(line));
|
|
89
|
+
});
|
|
90
|
+
console.log(brandPrimary("React Native Starter"));
|
|
91
|
+
console.log(rnBlue(" ⚛ React Native • Expo + Router • TypeScript • Zustand"));
|
|
92
|
+
console.log("");
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const agentDirectories = [
|
|
96
|
+
{ key: "Antigravity", path: ".agents", label: ".agents/skills" },
|
|
97
|
+
{ key: "agent", path: ".agent", label: ".agent/skills" },
|
|
98
|
+
{ key: "claude", path: ".claude", label: ".claude/skills" },
|
|
99
|
+
{ key: "codex", path: ".codex", label: ".codex/skills" },
|
|
100
|
+
{ key: "cursor", path: ".cursor", label: ".cursor/skills" },
|
|
101
|
+
{ key: "github", path: ".github", label: ".github/skills" },
|
|
102
|
+
{ key: "kilocode", path: ".kilocode", label: ".kilocode/skills" },
|
|
103
|
+
{ key: "opencode", path: ".opencode", label: ".opencode/skills" },
|
|
104
|
+
{ key: "trae", path: ".trae", label: ".trae" },
|
|
105
|
+
{ key: "vscode", path: ".vscode", label: ".vscode" },
|
|
106
|
+
{ key: "windsurf", path: ".windsurf", label: ".windsurf" },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const getExistingAgentKeys = async (rootDir) => {
|
|
110
|
+
const existing = new Set();
|
|
111
|
+
for (const dir of agentDirectories) {
|
|
112
|
+
if (await fs.pathExists(path.join(rootDir, dir.path))) {
|
|
113
|
+
existing.add(dir.key);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return existing;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const renderAgentSelection = (choices, selectedKeys, activeIndex) => {
|
|
120
|
+
output.write("\x1b[2J\x1b[0;0H");
|
|
121
|
+
printBanner();
|
|
122
|
+
console.log(brandPrimary.bold("Select agent configs to include"));
|
|
123
|
+
console.log(uiMuted("Use ↑/↓ to move, space to toggle, enter to confirm."));
|
|
124
|
+
console.log(uiMuted("Press enter with nothing selected for none."));
|
|
125
|
+
console.log("");
|
|
126
|
+
|
|
127
|
+
choices.forEach((choice, index) => {
|
|
128
|
+
const isActive = index === activeIndex;
|
|
129
|
+
const isSelected = selectedKeys.has(choice.key);
|
|
130
|
+
const cursor = isActive ? ">" : " ";
|
|
131
|
+
const mark = isSelected ? "x" : " ";
|
|
132
|
+
const line = `${cursor} [${mark}] ${choice.label}`;
|
|
133
|
+
if (isActive) {
|
|
134
|
+
console.log(uiActive.bold(line));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (isSelected) {
|
|
138
|
+
console.log(uiSelected(line));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.log(uiAccent(line));
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const promptAgentSelection = async (choices, defaultKeys) => {
|
|
146
|
+
readline.emitKeypressEvents(input);
|
|
147
|
+
if (input.isTTY) {
|
|
148
|
+
input.setRawMode(true);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const selectedKeys = new Set(defaultKeys);
|
|
152
|
+
let activeIndex = 0;
|
|
153
|
+
|
|
154
|
+
renderAgentSelection(choices, selectedKeys, activeIndex);
|
|
155
|
+
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
const onKeypress = (_str, key) => {
|
|
158
|
+
if (key.name === "up") {
|
|
159
|
+
activeIndex = (activeIndex - 1 + choices.length) % choices.length;
|
|
160
|
+
renderAgentSelection(choices, selectedKeys, activeIndex);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (key.name === "down") {
|
|
164
|
+
activeIndex = (activeIndex + 1) % choices.length;
|
|
165
|
+
renderAgentSelection(choices, selectedKeys, activeIndex);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (key.name === "space") {
|
|
169
|
+
const choice = choices[activeIndex];
|
|
170
|
+
if (selectedKeys.has(choice.key)) {
|
|
171
|
+
selectedKeys.delete(choice.key);
|
|
172
|
+
} else {
|
|
173
|
+
selectedKeys.add(choice.key);
|
|
174
|
+
}
|
|
175
|
+
renderAgentSelection(choices, selectedKeys, activeIndex);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (key.name === "return") {
|
|
179
|
+
input.off("keypress", onKeypress);
|
|
180
|
+
if (input.isTTY) {
|
|
181
|
+
input.setRawMode(false);
|
|
182
|
+
}
|
|
183
|
+
output.write("\n");
|
|
184
|
+
resolve(selectedKeys);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
input.on("keypress", onKeypress);
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
67
192
|
const main = async () => {
|
|
68
193
|
const empty = await isEmptyDir(targetDir);
|
|
69
194
|
if (!empty) {
|
|
@@ -89,10 +214,20 @@ const main = async () => {
|
|
|
89
214
|
await fs.copy(examplePath, targetDir, { overwrite: true });
|
|
90
215
|
}
|
|
91
216
|
|
|
217
|
+
const existingAgentKeys = await getExistingAgentKeys(targetDir);
|
|
218
|
+
const defaultKeys = agentDirectories.filter((dir) => existingAgentKeys.has(dir.key)).map((dir) => dir.key);
|
|
219
|
+
|
|
220
|
+
const selectedKeys = await promptAgentSelection(agentDirectories, defaultKeys);
|
|
221
|
+
const selectedLabels = agentDirectories.filter((dir) => selectedKeys.has(dir.key)).map((dir) => dir.label);
|
|
222
|
+
|
|
223
|
+
const dirsToRemove = agentDirectories.filter((dir) => !selectedKeys.has(dir.key));
|
|
224
|
+
await Promise.all(dirsToRemove.map((dir) => fs.remove(path.join(targetDir, dir.path))));
|
|
225
|
+
|
|
226
|
+
const appName = path.basename(targetDir);
|
|
92
227
|
const targetPackageJson = path.join(targetDir, "package.json");
|
|
93
228
|
if (fs.existsSync(targetPackageJson)) {
|
|
94
229
|
const pkg = await fs.readJson(targetPackageJson);
|
|
95
|
-
pkg.name =
|
|
230
|
+
pkg.name = appName;
|
|
96
231
|
await fs.writeJson(targetPackageJson, pkg, { spaces: 2 });
|
|
97
232
|
}
|
|
98
233
|
|
|
@@ -104,7 +239,25 @@ const main = async () => {
|
|
|
104
239
|
runInstall(targetDir, pm);
|
|
105
240
|
}
|
|
106
241
|
|
|
107
|
-
|
|
242
|
+
printBanner();
|
|
243
|
+
console.log(
|
|
244
|
+
[
|
|
245
|
+
brandPrimary.bold("Project created successfully."),
|
|
246
|
+
"",
|
|
247
|
+
uiMuted(`Agent configs: ${selectedLabels.length > 0 ? selectedLabels.join(", ") : "none"}`),
|
|
248
|
+
"",
|
|
249
|
+
brandPrimary("Next steps"),
|
|
250
|
+
uiAccent(` cd ${appName}`),
|
|
251
|
+
options.skipInstall ? uiAccent(" npm install") : null,
|
|
252
|
+
uiAccent(" npm run start"),
|
|
253
|
+
"",
|
|
254
|
+
brandPrimary("Then"),
|
|
255
|
+
uiMuted(" - Build a dev client (if you haven’t): npx expo run:ios / npx expo run:android"),
|
|
256
|
+
uiMuted(" - Open the app from the dev client on your device or simulator"),
|
|
257
|
+
]
|
|
258
|
+
.filter(Boolean)
|
|
259
|
+
.join("\n"),
|
|
260
|
+
);
|
|
108
261
|
};
|
|
109
262
|
|
|
110
263
|
main().catch((error) => {
|