multimodel-dev-os 1.1.0 → 2.0.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.
- package/.ai/adapters/custom-adapter.example.yaml +9 -0
- package/.ai/adapters/registry.yaml +56 -0
- package/.ai/models/README.md +14 -0
- package/.ai/models/local-models.yaml +20 -0
- package/.ai/models/providers.yaml +29 -0
- package/.ai/models/registry.yaml +73 -0
- package/.ai/models/routing-presets.yaml +23 -0
- package/.ai/skills/custom-skill.example.md +15 -0
- package/.ai/templates/custom-template.example.yaml +19 -0
- package/.ai/templates/registry.yaml +522 -0
- package/README.md +30 -18
- package/bin/multimodel-dev-os.js +810 -91
- package/docs/.vitepress/config.js +36 -1
- package/docs/adapter-authoring.md +46 -0
- package/docs/agent-compatibility.md +51 -0
- package/docs/cli-roadmap.md +15 -18
- package/docs/final-launch.md +5 -4
- package/docs/local-models.md +48 -0
- package/docs/mobile-android.md +75 -0
- package/docs/model-compatibility.md +65 -0
- package/docs/model-routing.md +45 -0
- package/docs/npm-publishing.md +27 -0
- package/docs/package-safety.md +29 -0
- package/docs/provider-strategy.md +44 -0
- package/docs/public/llms-full.txt +82 -73
- package/docs/public/llms.txt +36 -34
- package/docs/quickstart.md +7 -6
- package/docs/registry-contribution.md +20 -0
- package/docs/release-policy.md +26 -0
- package/docs/skill-authoring.md +56 -0
- package/docs/template-authoring.md +65 -0
- package/docs/token-optimization.md +27 -0
- package/docs/v2-migration.md +31 -0
- package/docs/v2-release-checklist.md +30 -0
- package/docs/v2-roadmap.md +95 -0
- package/examples/expo-react-native-android/.ai/config.yaml +22 -0
- package/examples/expo-react-native-android/.ai/context/architecture.md +18 -0
- package/examples/expo-react-native-android/.ai/context/context-budget.md +4 -0
- package/examples/expo-react-native-android/.ai/context/model-map.md +6 -0
- package/examples/expo-react-native-android/.ai/context/project-brief.md +9 -0
- package/examples/expo-react-native-android/.ai/session-logs/.gitkeep +1 -0
- package/examples/expo-react-native-android/.ai/skills/expo-android-build.md +11 -0
- package/examples/expo-react-native-android/AGENTS.md +20 -0
- package/examples/expo-react-native-android/MEMORY.md +13 -0
- package/examples/expo-react-native-android/README.md +101 -0
- package/examples/expo-react-native-android/RUNBOOK.md +36 -0
- package/examples/expo-react-native-android/TASKS.md +14 -0
- package/examples/expo-react-native-android/app.config.ts +40 -0
- package/examples/expo-react-native-android/app.json +34 -0
- package/examples/expo-react-native-android/eas.json +26 -0
- package/examples/expo-react-native-android/jest.config.js +11 -0
- package/examples/expo-react-native-android/src/app/_layout.tsx +89 -0
- package/examples/expo-react-native-android/src/lib/secure-storage.ts +63 -0
- package/examples/expo-react-native-android/src/services/api-client.ts +106 -0
- package/package.json +3 -2
- package/scripts/install.ps1 +230 -230
- package/scripts/install.sh +1 -1
- package/scripts/prepublish-guard.js +43 -0
- package/scripts/verify.js +178 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Mobile App Architecture Map
|
|
2
|
+
|
|
3
|
+
This document establishes key folder boundaries and separation of concerns.
|
|
4
|
+
|
|
5
|
+
## Layout Folder Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
mobile/
|
|
9
|
+
app/ # Expo Router screens & layouts
|
|
10
|
+
components/ # Reusable UI theme primitives
|
|
11
|
+
services/ # API Clients, storage abstractions
|
|
12
|
+
hooks/ # Custom custom hook libraries
|
|
13
|
+
tests/ # Jest testing configurations
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Core Foundations
|
|
17
|
+
1. **API Communications**: Handled exclusively under `/services/api-client.ts`.
|
|
18
|
+
2. **Device State**: Sensitive keys kept out of Async Storage, saved in `expo-secure-store`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Model Mapping for Mobile Codebase
|
|
2
|
+
|
|
3
|
+
| Tasks | Model Choice | Rationales |
|
|
4
|
+
| :--- | :--- | :--- |
|
|
5
|
+
| Mocking Native Components / React Hooks | Claude 3.5 Sonnet | Strong TypeScript and code reasoning |
|
|
6
|
+
| Testing configurations / layout updates | Gemini 1.5 Flash | Fast verification loops |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Project Brief: Android Mobile Client
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Scaffolds a production-grade React Native app utilizing Expo Go, EAS CLI, and TypeScript.
|
|
5
|
+
|
|
6
|
+
## Goals
|
|
7
|
+
- Establish secure storage profiles (`expo-secure-store`).
|
|
8
|
+
- Construct environment splits via `eas.json` (development, staging, production).
|
|
9
|
+
- Implement responsive layout layouts and offline warnings.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Placeholder
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Skill: Expo Android EAS Build Checks
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Ensures that app.json, eas.json, and environment variables are verified before launching an EAS Build.
|
|
5
|
+
|
|
6
|
+
## Activation Trigger
|
|
7
|
+
Running `EAS build` or preparing staging/production releases.
|
|
8
|
+
|
|
9
|
+
## Safe Verification Check
|
|
10
|
+
- Assert `android.package` is set to `com.multimodel.devos` in `app.json`.
|
|
11
|
+
- Confirm `eas.json` contains `preview` and `production` profiles.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# MultiModel Dev OS - Android App Agents Specification
|
|
2
|
+
|
|
3
|
+
This document defines agent roles and boundaries for the Expo React Native Android application.
|
|
4
|
+
|
|
5
|
+
## Development Stack Guidelines
|
|
6
|
+
|
|
7
|
+
- **Framework**: Expo + React Native + TypeScript
|
|
8
|
+
- **State Management**: Zustand / React Context
|
|
9
|
+
- **Navigation**: Expo Router (File-based)
|
|
10
|
+
- **UI System**: Vanilla React Native + Tailwind CSS (NativeWind v4)
|
|
11
|
+
|
|
12
|
+
## CLI Executables Matrix
|
|
13
|
+
|
|
14
|
+
| Command | Action | Agent Role |
|
|
15
|
+
| :--- | :--- | :--- |
|
|
16
|
+
| `npm run lint` | Code style audit | Coder / Reviewer |
|
|
17
|
+
| `npm run test` | Jest test suite execution | QA Tester |
|
|
18
|
+
| `npx expo start` | Local development server | Coder |
|
|
19
|
+
| `npx eas build --platform android --profile preview` | Build staging APK bundle | DevOps |
|
|
20
|
+
| `npx eas build --platform android --profile production` | Build Play Store AAB release | DevOps |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Android App Architecture Memory & Constraints
|
|
2
|
+
|
|
3
|
+
## Technical Baseline
|
|
4
|
+
- **Build Tooling**: EAS CLI (Expo Application Services)
|
|
5
|
+
- **Android Target**: API level 34 (Android 14)
|
|
6
|
+
- **Minimum SDK**: API level 23 (Android 6.0)
|
|
7
|
+
|
|
8
|
+
## Security Guidelines
|
|
9
|
+
- **No Hardcoded Secrets**: All keys (API endpoints, tokens) must be injected dynamically via Expo Config `extra` parameters using `.env` files.
|
|
10
|
+
- **Secure Storage**: Sensitive auth tokens must be saved using `expo-secure-store`. Do NOT use standard `AsyncStorage` for passwords or tokens.
|
|
11
|
+
|
|
12
|
+
## Package Identity
|
|
13
|
+
- **Android Package Name**: `com.multimodel.devos` (defined in `app.json`)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Expo React Native Android Boilerplate
|
|
2
|
+
|
|
3
|
+
This template provides a production-ready React Native boilerplate configured for Android delivery using Expo, EAS Build, secure environment profile separation, automated API retries, and unit tests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Directory Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
├── app.json # Expo application static configurations
|
|
11
|
+
├── eas.json # EAS Build pipeline profiles
|
|
12
|
+
├── app.config.ts # Dynamic env profiles selector
|
|
13
|
+
├── jest.config.js # Jest testing suite configuration
|
|
14
|
+
├── src/
|
|
15
|
+
│ ├── app/
|
|
16
|
+
│ │ └── _layout.tsx # Root layout, NetInfo listeners, and screen boundaries
|
|
17
|
+
│ ├── lib/
|
|
18
|
+
│ │ └── secure-storage.ts # Safe wrapper for expo-secure-store (key/value device encryption)
|
|
19
|
+
│ └── services/
|
|
20
|
+
│ └── api-client.ts # Fetch API wrapper with timeout thresholds and auto-retries
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Environment Variables & App Configuration
|
|
26
|
+
|
|
27
|
+
We decouple environment configurations in [app.config.ts](file:///f:/multimodel-dev-os/examples/expo-react-native-android/app.config.ts) using `APP_ENV`.
|
|
28
|
+
|
|
29
|
+
### Supported Profiles
|
|
30
|
+
1. **Development (`development`)**:
|
|
31
|
+
* API Url: `http://10.0.2.2:3000/api` (Localhost mapping for Android Emulator)
|
|
32
|
+
2. **Staging (`staging`)**:
|
|
33
|
+
* API Url: `https://staging-api.multimodel.dev`
|
|
34
|
+
3. **Production (`production`)**:
|
|
35
|
+
* API Url: `https://api.multimodel.dev`
|
|
36
|
+
|
|
37
|
+
### To launch a specific environment locally:
|
|
38
|
+
```bash
|
|
39
|
+
# Start with staging parameters
|
|
40
|
+
APP_ENV=staging npx expo start
|
|
41
|
+
|
|
42
|
+
# Start with production parameters
|
|
43
|
+
APP_ENV=production npx expo start
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. EAS Build Setup
|
|
49
|
+
|
|
50
|
+
We configure three build targets in [eas.json](file:///f:/multimodel-dev-os/examples/expo-react-native-android/eas.json) to separate local debugging from store delivery.
|
|
51
|
+
|
|
52
|
+
### Build Commands
|
|
53
|
+
```bash
|
|
54
|
+
# 1. Build local development client (Internal testing APK with debugging tools)
|
|
55
|
+
eas build --profile development --platform android
|
|
56
|
+
|
|
57
|
+
# 2. Build staging/preview release (Internal testing APK)
|
|
58
|
+
eas build --profile preview --platform android
|
|
59
|
+
|
|
60
|
+
# 3. Build production release (Signed Android App Bundle (.aab) ready for Google Play Store)
|
|
61
|
+
eas build --profile production --platform android
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> [!CAUTION]
|
|
65
|
+
> **No-Secrets Policy**: Do not commit actual `owner` or `projectId` credentials into `app.json` or environment files. Fill in placeholders locally during project provisioning.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 4. API Client & Secure Storage
|
|
70
|
+
|
|
71
|
+
### API Retry Loop
|
|
72
|
+
The [api-client.ts](file:///f:/multimodel-dev-os/examples/expo-react-native-android/src/services/api-client.ts) automatically retries requests up to 3 times on transient network failures before aborting. You can enable offline mock responses by setting:
|
|
73
|
+
```typescript
|
|
74
|
+
const USE_MOCK_DATA = true;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Encrypted Key-Value Storage
|
|
78
|
+
Use the [secure-storage.ts](file:///f:/multimodel-dev-os/examples/expo-react-native-android/src/lib/secure-storage.ts) class to securely store credentials (like JWT tokens) on the device using keychain encryption:
|
|
79
|
+
```typescript
|
|
80
|
+
import { SecureStorage } from '../lib/secure-storage';
|
|
81
|
+
|
|
82
|
+
// Write credential
|
|
83
|
+
await SecureStorage.setItem('auth_token', 'JWT-TOKEN-DATA');
|
|
84
|
+
|
|
85
|
+
// Retrieve credential
|
|
86
|
+
const token = await SecureStorage.getItem('auth_token');
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. Pre-flight Play Store Release Checklist
|
|
92
|
+
|
|
93
|
+
Ensure you complete these steps before submitting `production` bundles:
|
|
94
|
+
1. **Change package name**: Update `"package": "com.multimodel.devos"` in `app.json` to your unique identifier.
|
|
95
|
+
2. **Update version parameters**: Set unique `"version"` and incremental `"versionCode"` in `app.json` for every build.
|
|
96
|
+
3. **Configure App Signing Keys**: Ensure your keystore is generated securely on EAS or imported safely from local credentials. Do not commit keystores to Git.
|
|
97
|
+
4. **Permissions Audit**: Verify that only required permissions (like `INTERNET`) are enabled in `app.json`.
|
|
98
|
+
5. **Run tests**:
|
|
99
|
+
```bash
|
|
100
|
+
npm test
|
|
101
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Mobile Staging Operations & Build Runbook
|
|
2
|
+
|
|
3
|
+
This document details common development, testing, and release build workflows.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Local Testing & Execution
|
|
8
|
+
|
|
9
|
+
Start local bundler:
|
|
10
|
+
```bash
|
|
11
|
+
npm run start
|
|
12
|
+
```
|
|
13
|
+
Run Jest unit tests:
|
|
14
|
+
```bash
|
|
15
|
+
npm run test
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. Staging / Preview Builds
|
|
21
|
+
|
|
22
|
+
Build staging APK locally or remotely using EAS CLI:
|
|
23
|
+
```bash
|
|
24
|
+
# Preview build for internal testing (requires EAS account configured)
|
|
25
|
+
npx eas build --platform android --profile preview
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 3. Production Play Store release
|
|
31
|
+
|
|
32
|
+
Build production Play Store package (AAB):
|
|
33
|
+
```bash
|
|
34
|
+
npx eas build --platform android --profile production
|
|
35
|
+
```
|
|
36
|
+
Ensure that your Google Play Console credentials and key keystores are linked securely inside the EAS dashboard.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Active Mobile Application Backlog
|
|
2
|
+
|
|
3
|
+
- [ ] Mobile App Initialization
|
|
4
|
+
- [ ] Configure `app.json` package namespace and icon directories
|
|
5
|
+
- [ ] Setup `eas.json` profiles
|
|
6
|
+
- [ ] Core Integrations
|
|
7
|
+
- [ ] Implement API client with baseUrl environment variables
|
|
8
|
+
- [ ] Implement secure storage utility using `expo-secure-store`
|
|
9
|
+
- [ ] App Features
|
|
10
|
+
- [ ] Setup Expo Router layout screens
|
|
11
|
+
- [ ] Design loading states and offline warning screens
|
|
12
|
+
- [ ] Quality Assurance
|
|
13
|
+
- [ ] Add Jest test setups
|
|
14
|
+
- [ ] Build staging APK via `npx eas build`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ExpoConfig, ConfigContext } from 'expo/config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dynamic Expo App Configuration File
|
|
5
|
+
* Decouples staging/production URLs and binds environment variables at build-time.
|
|
6
|
+
*/
|
|
7
|
+
export default ({ config }: ConfigContext): ExpoConfig => {
|
|
8
|
+
const env = process.env.APP_ENV || 'development';
|
|
9
|
+
|
|
10
|
+
const extraConfig = {
|
|
11
|
+
development: {
|
|
12
|
+
apiUrl: 'http://10.0.2.2:3000/api', // Localhost mapping for Android Emulator loopbacks
|
|
13
|
+
envName: 'Development'
|
|
14
|
+
},
|
|
15
|
+
staging: {
|
|
16
|
+
apiUrl: 'https://staging-api.multimodel.dev',
|
|
17
|
+
envName: 'Staging'
|
|
18
|
+
},
|
|
19
|
+
production: {
|
|
20
|
+
apiUrl: 'https://api.multimodel.dev',
|
|
21
|
+
envName: 'Production'
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const selectedEnv = extraConfig[env as keyof typeof extraConfig] || extraConfig.development;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...config,
|
|
29
|
+
name: config.name || "MultiModel Dev OS Mobile",
|
|
30
|
+
slug: config.slug || "multimodel-dev-os-mobile",
|
|
31
|
+
// Configure EAS parameters. Fill these in during local setup. Do not commit actual tokens to Git.
|
|
32
|
+
owner: "your-expo-username-placeholder",
|
|
33
|
+
extra: {
|
|
34
|
+
...selectedEnv,
|
|
35
|
+
eas: {
|
|
36
|
+
projectId: "your-eas-project-id-placeholder"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "MultiModel Dev OS Mobile",
|
|
4
|
+
"slug": "multimodel-dev-os-mobile",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"icon": "./assets/icon.png",
|
|
8
|
+
"userInterfaceStyle": "light",
|
|
9
|
+
"splash": {
|
|
10
|
+
"image": "./assets/splash.png",
|
|
11
|
+
"resizeMode": "contain",
|
|
12
|
+
"backgroundColor": "#ffffff"
|
|
13
|
+
},
|
|
14
|
+
"assetBundlePatterns": [
|
|
15
|
+
"**/*"
|
|
16
|
+
],
|
|
17
|
+
"ios": {
|
|
18
|
+
"supportsTablet": true
|
|
19
|
+
},
|
|
20
|
+
"android": {
|
|
21
|
+
"adaptiveIcon": {
|
|
22
|
+
"foregroundImage": "./assets/adaptive-icon.png",
|
|
23
|
+
"backgroundColor": "#ffffff"
|
|
24
|
+
},
|
|
25
|
+
"package": "com.multimodel.devos",
|
|
26
|
+
"permissions": [
|
|
27
|
+
"INTERNET"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"web": {
|
|
31
|
+
"favicon": "./assets/favicon.png"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cli": {
|
|
3
|
+
"version": ">= 9.0.0"
|
|
4
|
+
},
|
|
5
|
+
"build": {
|
|
6
|
+
"development": {
|
|
7
|
+
"developmentClient": true,
|
|
8
|
+
"distribution": "internal",
|
|
9
|
+
"channel": "development"
|
|
10
|
+
},
|
|
11
|
+
"preview": {
|
|
12
|
+
"distribution": "internal",
|
|
13
|
+
"channel": "preview"
|
|
14
|
+
},
|
|
15
|
+
"production": {
|
|
16
|
+
"distribution": "store",
|
|
17
|
+
"channel": "production",
|
|
18
|
+
"android": {
|
|
19
|
+
"buildType": "app-bundle"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"submit": {
|
|
24
|
+
"production": {}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'jest-expo',
|
|
3
|
+
transformIgnorePatterns: [
|
|
4
|
+
'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)'
|
|
5
|
+
],
|
|
6
|
+
setupFilesAfterEnv: ["@testing-library/jest-native/extend-expect"],
|
|
7
|
+
testMatch: ['**/__tests__/**/*.test.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
|
|
8
|
+
moduleNameMapper: {
|
|
9
|
+
'^@/(.*)$': '<rootDir>/src/$1'
|
|
10
|
+
}
|
|
11
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { StyleSheet, Text, View, ActivityIndicator } from 'react-native';
|
|
3
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Root Application Layout Component
|
|
7
|
+
* Serves as the primary entry point for Expo Router.
|
|
8
|
+
* Resolves network connection states and establishes offline screen boundaries.
|
|
9
|
+
*/
|
|
10
|
+
export default function RootLayout() {
|
|
11
|
+
const [isConnected, setIsConnected] = useState<boolean | null>(true);
|
|
12
|
+
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
// Monitor connection states dynamically across Android devices
|
|
16
|
+
const unsubscribe = NetInfo.addEventListener(state => {
|
|
17
|
+
setIsConnected(state.isConnected);
|
|
18
|
+
setIsLoading(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return () => unsubscribe();
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
if (isLoading) {
|
|
25
|
+
return (
|
|
26
|
+
<View style={styles.center}>
|
|
27
|
+
<ActivityIndicator size="large" color="#6366f1" />
|
|
28
|
+
</View>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Offline boundary gate: Render fallback UI if connection is lost
|
|
33
|
+
if (!isConnected) {
|
|
34
|
+
return (
|
|
35
|
+
<View style={styles.center}>
|
|
36
|
+
<Text style={styles.errorText}>No Internet Connection</Text>
|
|
37
|
+
<Text style={styles.subtext}>Please check your network settings and try again.</Text>
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Guidelines: For Expo Router navigation, replace the below container with:
|
|
43
|
+
// import { Stack } from 'expo-router';
|
|
44
|
+
// return <Stack screenOptions={{ headerShown: false }} />;
|
|
45
|
+
return (
|
|
46
|
+
<View style={styles.container}>
|
|
47
|
+
<Text style={styles.welcomeTitle}>MultiModel Dev OS Mobile</Text>
|
|
48
|
+
<Text style={styles.body}>Scaffolded React Native App Layout successfully mounted!</Text>
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const styles = StyleSheet.create({
|
|
54
|
+
container: {
|
|
55
|
+
flex: 1,
|
|
56
|
+
backgroundColor: '#f8fafc',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
justifyContent: 'center',
|
|
59
|
+
padding: 24
|
|
60
|
+
},
|
|
61
|
+
center: {
|
|
62
|
+
flex: 1,
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
padding: 24
|
|
66
|
+
},
|
|
67
|
+
welcomeTitle: {
|
|
68
|
+
fontSize: 24,
|
|
69
|
+
fontWeight: 'bold',
|
|
70
|
+
color: '#0f172a',
|
|
71
|
+
marginBottom: 8
|
|
72
|
+
},
|
|
73
|
+
body: {
|
|
74
|
+
fontSize: 16,
|
|
75
|
+
color: '#475569',
|
|
76
|
+
textAlign: 'center'
|
|
77
|
+
},
|
|
78
|
+
errorText: {
|
|
79
|
+
fontSize: 20,
|
|
80
|
+
fontWeight: 'bold',
|
|
81
|
+
color: '#ef4444',
|
|
82
|
+
marginBottom: 8
|
|
83
|
+
},
|
|
84
|
+
subtext: {
|
|
85
|
+
fontSize: 14,
|
|
86
|
+
color: '#64748b',
|
|
87
|
+
textAlign: 'center'
|
|
88
|
+
}
|
|
89
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as SecureStore from 'expo-secure-store';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encrypted Key-Value Storage Wrapper
|
|
5
|
+
* Safely accesses expo-secure-store and runs assertions to verify key structures.
|
|
6
|
+
*/
|
|
7
|
+
export class SecureStorage {
|
|
8
|
+
static async setItem(key: string, value: string): Promise<boolean> {
|
|
9
|
+
if (!key || key.trim() === '') {
|
|
10
|
+
console.error('SecureStorage: Key cannot be empty');
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
if (SecureStore && typeof SecureStore.setItemAsync === 'function') {
|
|
15
|
+
await SecureStore.setItemAsync(key, value);
|
|
16
|
+
return true;
|
|
17
|
+
} else {
|
|
18
|
+
console.warn(`[SecureStorage Mock] setItem: ${key} = ${value}`);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error(`Failed to set item in SecureStore for key: ${key}`, e);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static async getItem(key: string): Promise<string | null> {
|
|
28
|
+
if (!key || key.trim() === '') {
|
|
29
|
+
console.error('SecureStorage: Key cannot be empty');
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
if (SecureStore && typeof SecureStore.getItemAsync === 'function') {
|
|
34
|
+
return await SecureStore.getItemAsync(key);
|
|
35
|
+
} else {
|
|
36
|
+
console.warn(`[SecureStorage Mock] getItem: ${key}`);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error(`Failed to retrieve item from SecureStore for key: ${key}`, e);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async deleteItem(key: string): Promise<boolean> {
|
|
46
|
+
if (!key || key.trim() === '') {
|
|
47
|
+
console.error('SecureStorage: Key cannot be empty');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
if (SecureStore && typeof SecureStore.deleteItemAsync === 'function') {
|
|
52
|
+
await SecureStore.deleteItemAsync(key);
|
|
53
|
+
return true;
|
|
54
|
+
} else {
|
|
55
|
+
console.warn(`[SecureStorage Mock] deleteItem: ${key}`);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(`Failed to delete item from SecureStore for key: ${key}`, e);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import Constants from 'expo-constants';
|
|
2
|
+
|
|
3
|
+
export interface ApiResponse<T> {
|
|
4
|
+
data: T | null;
|
|
5
|
+
error: string | null;
|
|
6
|
+
status: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ApiClient {
|
|
10
|
+
private baseUrl: string;
|
|
11
|
+
private timeoutMs: number = 10000;
|
|
12
|
+
private maxRetries: number = 3;
|
|
13
|
+
private useMockData: boolean = false; // Toggle true for offline mock validations
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
// Dynamically retrieve base API URL configured in app.config.ts extras
|
|
17
|
+
const extra = Constants.expoConfig?.extra || {};
|
|
18
|
+
this.baseUrl = extra.apiUrl || 'http://10.0.2.2:3000/api';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async request<T>(path: string, options: RequestInit = {}): Promise<ApiResponse<T>> {
|
|
22
|
+
if (this.useMockData) {
|
|
23
|
+
// Mock response resolver
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
resolve({
|
|
27
|
+
data: { message: "Mock Success data from ApiClient" } as unknown as T,
|
|
28
|
+
error: null,
|
|
29
|
+
status: 200
|
|
30
|
+
});
|
|
31
|
+
}, 500);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const url = `${this.baseUrl}${path}`;
|
|
36
|
+
const defaultHeaders = {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Accept': 'application/json'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let attempt = 0;
|
|
42
|
+
while (attempt < this.maxRetries) {
|
|
43
|
+
attempt++;
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const id = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(url, {
|
|
49
|
+
...options,
|
|
50
|
+
headers: {
|
|
51
|
+
...defaultHeaders,
|
|
52
|
+
...options.headers
|
|
53
|
+
},
|
|
54
|
+
signal: controller.signal
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
clearTimeout(id);
|
|
58
|
+
|
|
59
|
+
// Retry on Server Error (5xx)
|
|
60
|
+
if (response.status >= 500 && attempt < this.maxRetries) {
|
|
61
|
+
console.warn(`[ApiClient] Attempt ${attempt} failed with status ${response.status}. Retrying...`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
return {
|
|
67
|
+
data: null,
|
|
68
|
+
error: `HTTP Error: ${response.status} ${response.statusText}`,
|
|
69
|
+
status: response.status
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
return {
|
|
75
|
+
data: data as T,
|
|
76
|
+
error: null,
|
|
77
|
+
status: response.status
|
|
78
|
+
};
|
|
79
|
+
} catch (e: any) {
|
|
80
|
+
clearTimeout(id);
|
|
81
|
+
const isTimeout = e.name === 'AbortError';
|
|
82
|
+
|
|
83
|
+
// Retry on timeout or transient network failures
|
|
84
|
+
if (attempt < this.maxRetries) {
|
|
85
|
+
console.warn(`[ApiClient] Attempt ${attempt} failed: ${e.message}. Retrying...`);
|
|
86
|
+
await new Promise((res) => setTimeout(res, 1000 * attempt)); // Exponential backoff
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
data: null,
|
|
92
|
+
error: isTimeout ? 'Request Timeout' : e.message || 'Unknown network error',
|
|
93
|
+
status: 0
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
data: null,
|
|
100
|
+
error: 'Max retries exceeded',
|
|
101
|
+
status: 0
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const apiClient = new ApiClient();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multimodel-dev-os",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"multimodel-dev-os": "bin/multimodel-dev-os.js"
|
|
6
6
|
},
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"pack": "bash scripts/pack-template.sh",
|
|
46
46
|
"docs:dev": "vitepress dev docs",
|
|
47
47
|
"docs:build": "vitepress build docs",
|
|
48
|
-
"docs:preview": "vitepress preview docs"
|
|
48
|
+
"docs:preview": "vitepress preview docs",
|
|
49
|
+
"prepublishOnly": "node scripts/prepublish-guard.js"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"vitepress": "^1.6.4"
|