multimodel-dev-os 1.0.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.
Files changed (73) hide show
  1. package/.ai/adapters/custom-adapter.example.yaml +9 -0
  2. package/.ai/adapters/registry.yaml +56 -0
  3. package/.ai/models/README.md +14 -0
  4. package/.ai/models/local-models.yaml +20 -0
  5. package/.ai/models/providers.yaml +29 -0
  6. package/.ai/models/registry.yaml +73 -0
  7. package/.ai/models/routing-presets.yaml +23 -0
  8. package/.ai/skills/custom-skill.example.md +15 -0
  9. package/.ai/templates/custom-template.example.yaml +19 -0
  10. package/.ai/templates/registry.yaml +522 -0
  11. package/README.md +48 -22
  12. package/assets/favicon.png +0 -0
  13. package/assets/logo.png +0 -0
  14. package/bin/multimodel-dev-os.js +810 -91
  15. package/docs/.vitepress/config.js +69 -0
  16. package/docs/adapter-authoring.md +46 -0
  17. package/docs/agent-compatibility.md +51 -0
  18. package/docs/case-studies/index.md +11 -7
  19. package/docs/cli-roadmap.md +15 -18
  20. package/docs/compatibility.md +7 -3
  21. package/docs/cost-optimization.md +6 -2
  22. package/docs/faq.md +32 -56
  23. package/docs/final-launch.md +10 -4
  24. package/docs/index.md +44 -1
  25. package/docs/local-models.md +48 -0
  26. package/docs/mobile-android.md +75 -0
  27. package/docs/model-compatibility.md +65 -0
  28. package/docs/model-routing.md +45 -0
  29. package/docs/npm-publishing.md +27 -0
  30. package/docs/package-safety.md +29 -0
  31. package/docs/protocol.md +8 -4
  32. package/docs/provider-strategy.md +44 -0
  33. package/docs/public/favicon.png +0 -0
  34. package/docs/public/humans.txt +13 -0
  35. package/docs/public/llms-full.txt +82 -0
  36. package/docs/public/llms.txt +36 -0
  37. package/docs/public/logo.png +0 -0
  38. package/docs/public/robots.txt +4 -0
  39. package/docs/public/sitemap.xml +68 -0
  40. package/docs/quickstart.md +17 -12
  41. package/docs/registry-contribution.md +20 -0
  42. package/docs/release-policy.md +26 -0
  43. package/docs/skill-authoring.md +56 -0
  44. package/docs/stable-protocol.md +8 -4
  45. package/docs/template-authoring.md +65 -0
  46. package/docs/token-optimization.md +27 -0
  47. package/docs/v2-migration.md +31 -0
  48. package/docs/v2-release-checklist.md +30 -0
  49. package/docs/v2-roadmap.md +95 -0
  50. package/examples/expo-react-native-android/.ai/config.yaml +22 -0
  51. package/examples/expo-react-native-android/.ai/context/architecture.md +18 -0
  52. package/examples/expo-react-native-android/.ai/context/context-budget.md +4 -0
  53. package/examples/expo-react-native-android/.ai/context/model-map.md +6 -0
  54. package/examples/expo-react-native-android/.ai/context/project-brief.md +9 -0
  55. package/examples/expo-react-native-android/.ai/session-logs/.gitkeep +1 -0
  56. package/examples/expo-react-native-android/.ai/skills/expo-android-build.md +11 -0
  57. package/examples/expo-react-native-android/AGENTS.md +20 -0
  58. package/examples/expo-react-native-android/MEMORY.md +13 -0
  59. package/examples/expo-react-native-android/README.md +101 -0
  60. package/examples/expo-react-native-android/RUNBOOK.md +36 -0
  61. package/examples/expo-react-native-android/TASKS.md +14 -0
  62. package/examples/expo-react-native-android/app.config.ts +40 -0
  63. package/examples/expo-react-native-android/app.json +34 -0
  64. package/examples/expo-react-native-android/eas.json +26 -0
  65. package/examples/expo-react-native-android/jest.config.js +11 -0
  66. package/examples/expo-react-native-android/src/app/_layout.tsx +89 -0
  67. package/examples/expo-react-native-android/src/lib/secure-storage.ts +63 -0
  68. package/examples/expo-react-native-android/src/services/api-client.ts +106 -0
  69. package/package.json +3 -2
  70. package/scripts/install.ps1 +230 -230
  71. package/scripts/install.sh +1 -1
  72. package/scripts/prepublish-guard.js +43 -0
  73. package/scripts/verify.js +192 -1
@@ -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": "1.0.0",
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"