@shaykec/app-agent 1.0.9 → 1.0.11
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/.claude/agents/catalog-analyzer.md +57 -0
- package/.claude/skills/android-customizer/SKILL.md +23 -10
- package/.claude/skills/bug-fixer/SKILL.md +59 -0
- package/.claude/skills/catalog-analyzer/SKILL.md +96 -0
- package/.claude/skills/customization-planner/SKILL.md +44 -5
- package/.claude/skills/design-selector/SKILL.md +3 -1
- package/.claude/skills/design-system/SKILL.md +1 -1
- package/.claude/skills/exploratory-tester/SKILL.md +82 -0
- package/.claude/skills/ios-customizer/SKILL.md +29 -8
- package/.claude/skills/module-integrator/SKILL.md +1 -1
- package/.claude/skills/react-native-customizer/SKILL.md +22 -10
- package/.claude/skills/test-planner/SKILL.md +72 -0
- package/.cursor/agents/README.md +3 -1
- package/.cursor/agents/catalog-analyzer.md +83 -0
- package/.cursor/rules/safety-guardrails.mdc +1 -1
- package/.cursor/rules/workflow.mdc +52 -18
- package/.cursor/skills/android-customizer/SKILL.md +43 -19
- package/.cursor/skills/bug-fixer/SKILL.md +189 -0
- package/.cursor/skills/catalog-analyzer/SKILL.md +222 -0
- package/.cursor/skills/customization-planner/SKILL.md +55 -8
- package/.cursor/skills/design-selector/SKILL.md +6 -5
- package/.cursor/skills/design-system/SKILL.md +8 -7
- package/.cursor/skills/exploratory-tester/SKILL.md +223 -0
- package/.cursor/skills/ios-customizer/SKILL.md +47 -12
- package/.cursor/skills/module-integrator/SKILL.md +2 -2
- package/.cursor/skills/output-validator/SKILL.md +1 -1
- package/.cursor/skills/react-native-customizer/SKILL.md +46 -16
- package/.cursor/skills/test-planner/SKILL.md +199 -0
- package/.cursor/skills/web-analyzer/SKILL.md +310 -0
- package/.cursor/skills/web-crawler/SKILL.md +252 -0
- package/AGENTS.md +32 -11
- package/CLAUDE.md +78 -33
- package/README.md +77 -11
- package/designs/DESIGN_CATALOG.md +17 -15
- package/designs/DESIGN_PRINCIPLES.md +53 -0
- package/designs/brands/accessible-high-contrast.md +14 -0
- package/designs/brands/corporate-professional.md +14 -0
- package/designs/brands/dark-luxe.md +14 -0
- package/designs/brands/kids-playful.md +14 -0
- package/designs/brands/medical-clinical.md +14 -0
- package/designs/brands/modern-minimal.md +14 -0
- package/designs/brands/nature-organic.md +14 -0
- package/designs/brands/neo-brutalist.md +14 -0
- package/designs/brands/retro-vintage.md +14 -0
- package/designs/brands/soft-gradient.md +14 -0
- package/designs/brands/sport-athletic.md +14 -0
- package/designs/brands/tech-dynamic.md +14 -0
- package/designs/brands/vibrant-playful.md +14 -0
- package/dist/cli.d.ts +4 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +123 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -1
- package/dist/config.js.map +1 -1
- package/dist/engines/claude-engine.d.ts.map +1 -1
- package/dist/engines/claude-engine.js +16 -4
- package/dist/engines/claude-engine.js.map +1 -1
- package/dist/engines/types.d.ts +1 -1
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/engines/types.js +31 -2
- package/dist/engines/types.js.map +1 -1
- package/dist/github.d.ts +3 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +47 -4
- package/dist/github.js.map +1 -1
- package/dist/index.js +294 -16
- package/dist/index.js.map +1 -1
- package/dist/prompt-builder.d.ts +17 -1
- package/dist/prompt-builder.d.ts.map +1 -1
- package/dist/prompt-builder.js +272 -1
- package/dist/prompt-builder.js.map +1 -1
- package/dist/validator.d.ts +7 -2
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +61 -41
- package/dist/validator.js.map +1 -1
- package/dist/workspace.js +2 -2
- package/dist/workspace.js.map +1 -1
- package/package.json +2 -4
- package/prompts/agent-prompt.md +35 -18
- package/prompts/deep-test-agent-prompt.md +122 -0
- package/prompts/fix-agent-prompt.md +90 -0
- package/prompts/quick-agent-prompt.md +32 -2
- package/prompts/scratch-agent-prompt.md +5 -8
- package/prompts/web-clone-agent-prompt.md +179 -0
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ConversationsScreen.kt +1 -1
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/navigation/MainScreen.kt +1 -0
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/navigation/MainNavigation.kt +5 -1
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/MotionPreferencesScreen.kt +3 -3
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/navigation/Navigation.kt +1 -1
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/settings/SettingsScreen.kt +1 -1
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/settings/SettingsScreen.kt +3 -2
- package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/features/settings/SettingsScreen.kt +1 -1
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/cart/CartScreen.kt +3 -2
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/Skeleton/tests/03_detail_screen.yaml +1 -1
- package/templates/android/Skeleton/tests/04_favorites.yaml +1 -1
- package/templates/android/Skeleton/tests/08_full_e2e.yaml +7 -1
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/settings/SettingsScreen.kt +3 -2
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/core/animation/AnimatedTransitionsModifiers.kt +188 -0
- package/templates/ios/BookTemplate/BookTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/ChatTemplate/ChatTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/DashTemplate/DashTemplate/App/AppConfig.swift +1 -0
- package/templates/ios/DashTemplate/DashTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/DashTemplate/DashTemplate/Core/Strings.swift +13 -0
- package/templates/ios/DashTemplate/DashTemplate.xcodeproj/project.pbxproj +32 -20
- package/templates/ios/FamilyTemplate/FamilyTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Strings.swift +42 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate.xcodeproj/project.pbxproj +36 -30
- package/templates/ios/GameTemplate/GameTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/HealthTemplate/HealthTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/LearnTemplate/LearnTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/MapTemplate/MapTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/MediaTemplate/MediaTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Strings.swift +12 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Features/SkeletonLoading/SkeletonLoadingView.swift +2 -37
- package/templates/ios/ShopTemplate/ShopTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/Skeleton/Skeleton/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/Skeleton/tests/08_full_e2e.yaml +4 -0
- package/templates/ios/SocialTemplate/SocialTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/TaskTemplate/TaskTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/ios/TrackTemplate/TrackTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
- package/templates/react-native/BookTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/BookTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/BookTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/ChatTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/ChatTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/ChatTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/DashTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/DashTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/DashTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/FamilyTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/FamilyTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/FamilyTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/FinanceTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/FinanceTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/FinanceTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/GameTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/GameTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/GameTemplate/src/screens/GameDetail/GameDetailScreen.tsx +2 -1
- package/templates/react-native/GameTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/HealthTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/HealthTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/HealthTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/HealthTemplate/src/screens/WorkoutDetail/WorkoutDetailScreen.tsx +1 -1
- package/templates/react-native/LearnTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/LearnTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/LearnTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/MapTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/MapTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/MapTemplate/src/screens/Map/MapScreen.tsx +14 -0
- package/templates/react-native/MapTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/MediaTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/MediaTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/MediaTemplate/src/screens/PlaylistDetail/PlaylistDetailScreen.tsx +1 -1
- package/templates/react-native/MediaTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/ReferenceTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/ReferenceTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/ReferenceTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
- package/templates/react-native/ShopTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/ShopTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/ShopTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/Skeleton/TESTING_MANIFEST.md +1 -1
- package/templates/react-native/Skeleton/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/Skeleton/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/Skeleton/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/Skeleton/tests/07_profile.yaml +3 -2
- package/templates/react-native/Skeleton/tests/08_full_e2e.yaml +12 -1
- package/templates/react-native/SocialTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/SocialTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/SocialTemplate/src/screens/Feed/FeedScreen.tsx +1 -0
- package/templates/react-native/SocialTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/TaskTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/TaskTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/TaskTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
- package/templates/react-native/TrackTemplate/src/animation/useAnimatedList.ts +219 -2
- package/templates/react-native/TrackTemplate/src/animation/useMotionPreferences.ts +23 -9
- package/templates/react-native/TrackTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
- package/templates/shared/ios/AnimatedTransitions/AnimatedTransitionsView.swift +233 -93
- package/.claude/agents/template-selector.md +0 -39
- package/.claude/skills/module-selector/SKILL.md +0 -81
- package/.claude/skills/template-selector/SKILL.md +0 -44
- package/.cursor/agents/template-selector.md +0 -52
- package/.cursor/skills/module-selector/SKILL.md +0 -135
- package/.cursor/skills/template-selector/SKILL.md +0 -123
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "fs-extra";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { execSync } from "node:child_process";
|
|
6
6
|
import { collectUserInput, confirmInput, parseCliArgs, parseManagementCommand } from "./cli.js";
|
|
7
|
-
import { buildAgentPrompt, buildTestPrompt, buildScratchPrompt, buildQuickPrompt, buildUpdatePrompt } from "./prompt-builder.js";
|
|
7
|
+
import { buildAgentPrompt, buildTestPrompt, buildDeepTestPrompt, buildFixPrompt, buildScratchPrompt, buildQuickPrompt, buildUpdatePrompt, buildWebClonePrompt } from "./prompt-builder.js";
|
|
8
8
|
import { createEngine } from "./engines/index.js";
|
|
9
9
|
import { validateOutput, reportValidation } from "./validator.js";
|
|
10
10
|
import { validatePrompt, formatSanitizationResult } from "./prompt-sanitizer.js";
|
|
@@ -16,6 +16,7 @@ import { scaffoldWorkspace, cleanupWorkspace } from "./workspace.js";
|
|
|
16
16
|
import { resolveUpdateContext } from "./updater.js";
|
|
17
17
|
import { generatePreview } from "./preview-generator.js";
|
|
18
18
|
import { createServer } from "node:http";
|
|
19
|
+
import { URL } from "node:url";
|
|
19
20
|
import open from "open";
|
|
20
21
|
async function main() {
|
|
21
22
|
// ── Management commands (no build agent needed) ─────────────────────
|
|
@@ -30,6 +31,12 @@ async function main() {
|
|
|
30
31
|
else if (mgmtCmd.command === "test") {
|
|
31
32
|
await testApp(mgmtCmd.appName, mgmtCmd.engine, mgmtCmd.model);
|
|
32
33
|
}
|
|
34
|
+
else if (mgmtCmd.command === "deep-test") {
|
|
35
|
+
await deepTestApp(mgmtCmd.appName, mgmtCmd.autoFix ?? false, mgmtCmd.engine, mgmtCmd.model);
|
|
36
|
+
}
|
|
37
|
+
else if (mgmtCmd.command === "fix") {
|
|
38
|
+
await fixApp(mgmtCmd.appName, mgmtCmd.engine, mgmtCmd.model);
|
|
39
|
+
}
|
|
33
40
|
else if (mgmtCmd.command === "preview") {
|
|
34
41
|
await handlePreview(mgmtCmd.appName);
|
|
35
42
|
}
|
|
@@ -55,6 +62,10 @@ async function main() {
|
|
|
55
62
|
await runApp(cmd.appName);
|
|
56
63
|
else if (cmd.command === "test")
|
|
57
64
|
await testApp(cmd.appName, cmd.engine, cmd.model);
|
|
65
|
+
else if (cmd.command === "deep-test")
|
|
66
|
+
await deepTestApp(cmd.appName, cmd.autoFix ?? false, cmd.engine, cmd.model);
|
|
67
|
+
else if (cmd.command === "fix")
|
|
68
|
+
await fixApp(cmd.appName, cmd.engine, cmd.model);
|
|
58
69
|
else if (cmd.command === "preview")
|
|
59
70
|
await handlePreview(cmd.appName);
|
|
60
71
|
else if (cmd.command === "screenshot")
|
|
@@ -96,11 +107,22 @@ async function main() {
|
|
|
96
107
|
p.log.info("Mode: Update");
|
|
97
108
|
}
|
|
98
109
|
else {
|
|
99
|
-
|
|
110
|
+
if (input.url) {
|
|
111
|
+
p.log.info(`URL: ${input.url}`);
|
|
112
|
+
if (input.description && !input.description.startsWith("Clone website:")) {
|
|
113
|
+
p.log.info(`Focus: ${input.description}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
p.log.info(`App: ${input.description}`);
|
|
118
|
+
}
|
|
100
119
|
p.log.info(`Platform: ${input.platform}`);
|
|
101
120
|
p.log.info(`Engine: ${resolvedEngine.name}`);
|
|
102
121
|
p.log.info(`Model: ${input.model}`);
|
|
103
|
-
if (input.
|
|
122
|
+
if (input.url) {
|
|
123
|
+
p.log.info("Mode: Web Clone");
|
|
124
|
+
}
|
|
125
|
+
else if (input.quick) {
|
|
104
126
|
p.log.info("Mode: Quick");
|
|
105
127
|
}
|
|
106
128
|
else if (input.scratch) {
|
|
@@ -153,7 +175,23 @@ async function main() {
|
|
|
153
175
|
const monorepoDir = path.join(PATHS.output, appName); // repo root for GitHub
|
|
154
176
|
const appDir = path.join(monorepoDir, singlePlatform); // where code lives
|
|
155
177
|
let knownAppDir = appDir;
|
|
156
|
-
if (input.
|
|
178
|
+
if (input.url) {
|
|
179
|
+
// Web clone mode: agent crawls the website, analyzes it, then builds an app
|
|
180
|
+
await fs.ensureDir(appDir);
|
|
181
|
+
// Create the web-crawl screenshots directory for the crawler
|
|
182
|
+
await fs.ensureDir(path.join(appDir, "web-crawl", "screenshots"));
|
|
183
|
+
prompt = await buildWebClonePrompt({ ...input, platform: singlePlatform });
|
|
184
|
+
// Redirect agent output to the platform subdir (monorepo layout)
|
|
185
|
+
prompt += `\n\n## OUTPUT DIRECTORY OVERRIDE\n\n` +
|
|
186
|
+
`IMPORTANT: This build uses a monorepo layout. You MUST write ALL output to \`${appDir}/\` instead of \`output/{app-name}/\`.\n` +
|
|
187
|
+
`Use "${appName}" as the app name (do NOT append the platform suffix).\n` +
|
|
188
|
+
`The app directory is: ${appDir}\n` +
|
|
189
|
+
`Reports go to: ${appDir}/reports/\n` +
|
|
190
|
+
`Tests go to: ${appDir}/tests/\n` +
|
|
191
|
+
`Web crawl screenshots go to: ${appDir}/web-crawl/screenshots/\n`;
|
|
192
|
+
p.log.info("Web clone prompt built successfully.");
|
|
193
|
+
}
|
|
194
|
+
else if (input.quick) {
|
|
157
195
|
// Quick mode: orchestrator pre-clones the skeleton template, renames it, and generates project
|
|
158
196
|
const displayName = deriveDisplayName(appName);
|
|
159
197
|
const bundleId = deriveBundleId(appName);
|
|
@@ -246,7 +284,8 @@ async function main() {
|
|
|
246
284
|
displayMaestroResults(agentResult);
|
|
247
285
|
// ── Validate output ─────────────────────────────────────────────────
|
|
248
286
|
p.log.step("Validating generated app...");
|
|
249
|
-
const
|
|
287
|
+
const buildMode = input.url ? "web-clone" : input.quick ? "quick" : input.scratch ? "scratch" : "full";
|
|
288
|
+
const validation = await validateOutput(singlePlatform, knownAppDir, { mode: buildMode });
|
|
250
289
|
reportValidation(validation);
|
|
251
290
|
if (!validation.valid) {
|
|
252
291
|
p.outro("Validation failed. The stop hook may have already retried. Check output/ for details.");
|
|
@@ -410,9 +449,10 @@ async function runParallelBuild(input, engine, cliResult, cliFlags, ghAvailable)
|
|
|
410
449
|
// Validate each platform
|
|
411
450
|
p.log.step("Validating generated apps...");
|
|
412
451
|
let allValid = true;
|
|
452
|
+
const dualBuildMode = input.quick ? "quick" : input.scratch ? "scratch" : "full";
|
|
413
453
|
for (const plat of platforms) {
|
|
414
454
|
const platDir = path.join(monorepoDir, plat);
|
|
415
|
-
const validation = await validateOutput(plat, platDir);
|
|
455
|
+
const validation = await validateOutput(plat, platDir, { mode: dualBuildMode });
|
|
416
456
|
if (validation.valid) {
|
|
417
457
|
p.log.success(`[${plat}] Validation passed`);
|
|
418
458
|
}
|
|
@@ -515,9 +555,10 @@ async function handleGitHubPush(cliResult, cliFlags, ghAvailable, appDir, artifa
|
|
|
515
555
|
}
|
|
516
556
|
if (cliResult && cliFlags.push && appDir) {
|
|
517
557
|
if (!ghAvailable) {
|
|
518
|
-
p.log.
|
|
558
|
+
p.log.warn("Cannot push to GitHub: prerequisites not met. Run 'gh auth login' first.");
|
|
519
559
|
p.outro(`App was generated at: ${appDir}${artifactLine}`);
|
|
520
|
-
|
|
560
|
+
// Don't exit(1) — the app was generated successfully; push failure is non-fatal
|
|
561
|
+
return;
|
|
521
562
|
}
|
|
522
563
|
const repoName = cliFlags.repoName || deriveRepoName(appDir);
|
|
523
564
|
const ghResult = await pushToGitHub(appDir, repoName);
|
|
@@ -525,9 +566,9 @@ async function handleGitHubPush(cliResult, cliFlags, ghAvailable, appDir, artifa
|
|
|
525
566
|
p.outro(`Done! Your app is at: ${ghResult.repoUrl}${artifactLine}`);
|
|
526
567
|
}
|
|
527
568
|
else {
|
|
528
|
-
p.log.
|
|
569
|
+
p.log.warn(`GitHub push failed: ${ghResult.error}`);
|
|
529
570
|
p.outro(`App was generated at: ${appDir}${artifactLine}\nPush manually with: cd ${appDir} && gh repo create`);
|
|
530
|
-
|
|
571
|
+
// Don't exit(1) — the app was generated successfully; push failure is non-fatal
|
|
531
572
|
}
|
|
532
573
|
}
|
|
533
574
|
else if (cliResult) {
|
|
@@ -567,6 +608,50 @@ async function handleGitHubPush(cliResult, cliFlags, ghAvailable, appDir, artifa
|
|
|
567
608
|
// ---------------------------------------------------------------------------
|
|
568
609
|
// Preview & Screenshot
|
|
569
610
|
// ---------------------------------------------------------------------------
|
|
611
|
+
const MIME_TYPES = {
|
|
612
|
+
".html": "text/html",
|
|
613
|
+
".css": "text/css",
|
|
614
|
+
".js": "application/javascript",
|
|
615
|
+
".json": "application/json",
|
|
616
|
+
".png": "image/png",
|
|
617
|
+
".jpg": "image/jpeg",
|
|
618
|
+
".jpeg": "image/jpeg",
|
|
619
|
+
".gif": "image/gif",
|
|
620
|
+
".svg": "image/svg+xml",
|
|
621
|
+
".ico": "image/x-icon",
|
|
622
|
+
".webp": "image/webp",
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Simple static file server using Node built-ins.
|
|
626
|
+
* Replaces serve-handler to avoid transitive dependency issues with npx.
|
|
627
|
+
*/
|
|
628
|
+
function serveStatic(root, req, res) {
|
|
629
|
+
const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
630
|
+
let pathname = decodeURIComponent(parsedUrl.pathname);
|
|
631
|
+
// Default to index.html
|
|
632
|
+
if (pathname === "/")
|
|
633
|
+
pathname = "/index.html";
|
|
634
|
+
// Prevent directory traversal
|
|
635
|
+
const safePath = path.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
|
|
636
|
+
const filePath = path.join(root, safePath);
|
|
637
|
+
// Ensure the resolved path is within root
|
|
638
|
+
if (!filePath.startsWith(root)) {
|
|
639
|
+
res.writeHead(403);
|
|
640
|
+
res.end("Forbidden");
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
644
|
+
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
645
|
+
fs.readFile(filePath)
|
|
646
|
+
.then((data) => {
|
|
647
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
648
|
+
res.end(data);
|
|
649
|
+
})
|
|
650
|
+
.catch(() => {
|
|
651
|
+
res.writeHead(404);
|
|
652
|
+
res.end("Not Found");
|
|
653
|
+
});
|
|
654
|
+
}
|
|
570
655
|
/**
|
|
571
656
|
* Handle --preview <app-name> command.
|
|
572
657
|
* Generates preview HTML from screenshots and serves it on a local HTTP server.
|
|
@@ -596,8 +681,7 @@ async function handlePreview(appName) {
|
|
|
596
681
|
await generatePreview(appDir);
|
|
597
682
|
// Start local server
|
|
598
683
|
const PORT = 4321;
|
|
599
|
-
const
|
|
600
|
-
const server = createServer((req, res) => handler.default(req, res, { public: previewDir }));
|
|
684
|
+
const server = createServer((req, res) => serveStatic(previewDir, req, res));
|
|
601
685
|
server.listen(PORT, async () => {
|
|
602
686
|
const url = `http://localhost:${PORT}`;
|
|
603
687
|
p.log.success(`Preview server running at: ${url}`);
|
|
@@ -635,6 +719,40 @@ async function handleScreenshot(appName) {
|
|
|
635
719
|
if (!platform) {
|
|
636
720
|
platform = hasXcodeproj ? "ios" : hasBuildGradle ? "android" : null;
|
|
637
721
|
}
|
|
722
|
+
// Check subdirectories for nested project structures (e.g. output/app-name/ios/*.xcodeproj)
|
|
723
|
+
if (!platform) {
|
|
724
|
+
for (const entry of appDirEntries) {
|
|
725
|
+
const subPath = path.join(appDir, entry);
|
|
726
|
+
try {
|
|
727
|
+
const stat = await fs.stat(subPath);
|
|
728
|
+
if (!stat.isDirectory() || entry.startsWith(".") || entry === "node_modules")
|
|
729
|
+
continue;
|
|
730
|
+
const subEntries = await fs.readdir(subPath);
|
|
731
|
+
if (subEntries.some((e) => e.endsWith(".xcodeproj"))) {
|
|
732
|
+
platform = "ios";
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
if (subEntries.includes("build.gradle.kts") || subEntries.includes("build.gradle")) {
|
|
736
|
+
platform = "android";
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
// Check for React Native in subdirectory
|
|
740
|
+
if ((subEntries.includes("metro.config.js") || subEntries.includes("metro.config.ts")) && subEntries.includes("package.json")) {
|
|
741
|
+
try {
|
|
742
|
+
const pkgJson = await fs.readJson(path.join(subPath, "package.json"));
|
|
743
|
+
if (pkgJson.dependencies?.["react-native"] || pkgJson.devDependencies?.["react-native"]) {
|
|
744
|
+
platform = "react-native";
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
catch { /* continue */ }
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
// Skip unreadable directories
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
638
756
|
if (!platform) {
|
|
639
757
|
p.log.error("Could not detect platform (no .xcodeproj, build.gradle.kts, or react-native package.json found).");
|
|
640
758
|
process.exit(1);
|
|
@@ -731,8 +849,7 @@ async function autoOpenPreview(appDir, noPreview) {
|
|
|
731
849
|
await generatePreview(appDir);
|
|
732
850
|
// Start local server
|
|
733
851
|
const PORT = 4321;
|
|
734
|
-
const
|
|
735
|
-
const server = createServer((req, res) => handler.default(req, res, { public: previewDir }));
|
|
852
|
+
const server = createServer((req, res) => serveStatic(previewDir, req, res));
|
|
736
853
|
server.listen(PORT, async () => {
|
|
737
854
|
const url = `http://localhost:${PORT}`;
|
|
738
855
|
p.log.success(`Preview: ${url}`);
|
|
@@ -834,6 +951,152 @@ async function testApp(appName, engineOverride, modelOverride) {
|
|
|
834
951
|
}
|
|
835
952
|
p.outro(`UI testing complete for: ${ctx.dirPath}`);
|
|
836
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Deep-test an existing app: generate test plan, run exploratory + structured testing,
|
|
956
|
+
* produce bug report, and optionally auto-fix bugs.
|
|
957
|
+
*/
|
|
958
|
+
async function deepTestApp(appName, autoFix, engineOverride, modelOverride) {
|
|
959
|
+
const fixLabel = autoFix ? " + Fix" : "";
|
|
960
|
+
p.intro(`AppAgent — Deep Test${fixLabel}: ${appName}`);
|
|
961
|
+
// ── Resolve app context ─────────────────────────────────────────────
|
|
962
|
+
const ctx = await resolveAppContext(appName);
|
|
963
|
+
p.log.info(`App: ${ctx.appName}`);
|
|
964
|
+
p.log.info(`Platform: ${ctx.platform}`);
|
|
965
|
+
p.log.info(`Bundle: ${ctx.bundleId}`);
|
|
966
|
+
p.log.info(`Mode: Deep Test${fixLabel}`);
|
|
967
|
+
if (ctx.artifactPath) {
|
|
968
|
+
p.log.info(`Artifact: ${ctx.artifactPath}`);
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
p.log.warn("Build artifact not found — agent will rebuild before testing.");
|
|
972
|
+
}
|
|
973
|
+
if (ctx.manifestContent) {
|
|
974
|
+
p.log.info("Manifest: found (parallel batch testing enabled)");
|
|
975
|
+
}
|
|
976
|
+
// ── Determine engine and model ──────────────────────────────────────
|
|
977
|
+
const selectedEngine = engineOverride ?? DEFAULT_ENGINE;
|
|
978
|
+
const selectedModel = modelOverride ?? DEFAULT_MODEL;
|
|
979
|
+
const engine = createEngine(selectedEngine);
|
|
980
|
+
// ── Pre-flight check ────────────────────────────────────────────────
|
|
981
|
+
const agentCheck = await engine.checkInstallation();
|
|
982
|
+
if (!agentCheck.ok) {
|
|
983
|
+
p.log.error(agentCheck.error);
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
p.log.info(`Engine: ${engine.name}`);
|
|
987
|
+
p.log.info(`Model: ${selectedModel}`);
|
|
988
|
+
// ── Scaffold workspace (npx mode) ───────────────────────────────────
|
|
989
|
+
await scaffoldWorkspace();
|
|
990
|
+
// ── Build deep-test prompt ──────────────────────────────────────────
|
|
991
|
+
const prompt = await buildDeepTestPrompt(ctx, autoFix);
|
|
992
|
+
p.log.info("Deep test prompt built successfully.");
|
|
993
|
+
// ── Run the agent engine ────────────────────────────────────────────
|
|
994
|
+
p.log.step(`Launching ${engine.name} agent for deep testing...`);
|
|
995
|
+
const agentResult = await engine.run(prompt, selectedModel);
|
|
996
|
+
if (!agentResult.success) {
|
|
997
|
+
p.log.error(`Agent failed: ${agentResult.error}`);
|
|
998
|
+
p.outro("Deep testing did not complete successfully. Check the output directory for partial results.");
|
|
999
|
+
process.exit(1);
|
|
1000
|
+
}
|
|
1001
|
+
// ── Proof of work summary ──────────────────────────────────────────
|
|
1002
|
+
const { proof } = agentResult;
|
|
1003
|
+
p.log.step("Proof of work:");
|
|
1004
|
+
p.log.info(` Files written: ${proof.filesWritten}`);
|
|
1005
|
+
p.log.info(` Files read: ${proof.filesRead}`);
|
|
1006
|
+
p.log.info(` Shell commands: ${proof.shellCommands.length}`);
|
|
1007
|
+
p.log.info(` MCP tool calls: ${proof.mcpCalls.length}`);
|
|
1008
|
+
if (proof.costUsd > 0) {
|
|
1009
|
+
p.log.info(` API cost: $${proof.costUsd.toFixed(4)}`);
|
|
1010
|
+
}
|
|
1011
|
+
if (proof.subagentCalls.length > 0) {
|
|
1012
|
+
p.log.info(` Subagent delegations: ${proof.subagentCalls.join(", ")}`);
|
|
1013
|
+
}
|
|
1014
|
+
// ── Check reports ─────────────────────────────────────────────────
|
|
1015
|
+
const reportsDir = path.join(ctx.dirPath, "reports");
|
|
1016
|
+
const testPlanPath = path.join(reportsDir, "10-test-plan.md");
|
|
1017
|
+
const bugReportPath = path.join(reportsDir, "11-bug-report.md");
|
|
1018
|
+
const fixReportPath = path.join(reportsDir, "12-fix-report.md");
|
|
1019
|
+
if (await fs.pathExists(testPlanPath)) {
|
|
1020
|
+
p.log.info(" Test plan: reports/10-test-plan.md ✓");
|
|
1021
|
+
}
|
|
1022
|
+
if (await fs.pathExists(bugReportPath)) {
|
|
1023
|
+
p.log.info(" Bug report: reports/11-bug-report.md ✓");
|
|
1024
|
+
}
|
|
1025
|
+
if (autoFix && (await fs.pathExists(fixReportPath))) {
|
|
1026
|
+
p.log.info(" Fix report: reports/12-fix-report.md ✓");
|
|
1027
|
+
}
|
|
1028
|
+
// ── Report-based summary ───────────────────────────────────────────
|
|
1029
|
+
await displayReportSummary(appName);
|
|
1030
|
+
p.outro(`Deep testing complete for: ${ctx.dirPath}`);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Fix bugs from a previous deep-test run: read bug report, fix each bug,
|
|
1034
|
+
* rebuild, and verify.
|
|
1035
|
+
*/
|
|
1036
|
+
async function fixApp(appName, engineOverride, modelOverride) {
|
|
1037
|
+
p.intro(`AppAgent — Fix Bugs: ${appName}`);
|
|
1038
|
+
// ── Resolve app context ─────────────────────────────────────────────
|
|
1039
|
+
const ctx = await resolveAppContext(appName);
|
|
1040
|
+
p.log.info(`App: ${ctx.appName}`);
|
|
1041
|
+
p.log.info(`Platform: ${ctx.platform}`);
|
|
1042
|
+
p.log.info(`Bundle: ${ctx.bundleId}`);
|
|
1043
|
+
// ── Check for bug report ────────────────────────────────────────────
|
|
1044
|
+
const bugReportPath = path.join(ctx.dirPath, "reports", "11-bug-report.md");
|
|
1045
|
+
if (!(await fs.pathExists(bugReportPath))) {
|
|
1046
|
+
p.log.error(`No bug report found at: ${bugReportPath}`);
|
|
1047
|
+
p.log.error("Run --deep-test first to generate a bug report, then use --fix to fix the bugs.");
|
|
1048
|
+
process.exit(1);
|
|
1049
|
+
}
|
|
1050
|
+
p.log.info("Bug report: found ✓");
|
|
1051
|
+
if (ctx.artifactPath) {
|
|
1052
|
+
p.log.info(`Artifact: ${ctx.artifactPath}`);
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
p.log.warn("Build artifact not found — agent will rebuild.");
|
|
1056
|
+
}
|
|
1057
|
+
// ── Determine engine and model ──────────────────────────────────────
|
|
1058
|
+
const selectedEngine = engineOverride ?? DEFAULT_ENGINE;
|
|
1059
|
+
const selectedModel = modelOverride ?? DEFAULT_MODEL;
|
|
1060
|
+
const engine = createEngine(selectedEngine);
|
|
1061
|
+
// ── Pre-flight check ────────────────────────────────────────────────
|
|
1062
|
+
const agentCheck = await engine.checkInstallation();
|
|
1063
|
+
if (!agentCheck.ok) {
|
|
1064
|
+
p.log.error(agentCheck.error);
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
}
|
|
1067
|
+
p.log.info(`Engine: ${engine.name}`);
|
|
1068
|
+
p.log.info(`Model: ${selectedModel}`);
|
|
1069
|
+
// ── Scaffold workspace (npx mode) ───────────────────────────────────
|
|
1070
|
+
await scaffoldWorkspace();
|
|
1071
|
+
// ── Build fix prompt ───────────────────────────────────────────────
|
|
1072
|
+
const prompt = await buildFixPrompt(ctx);
|
|
1073
|
+
p.log.info("Fix prompt built successfully.");
|
|
1074
|
+
// ── Run the agent engine ────────────────────────────────────────────
|
|
1075
|
+
p.log.step(`Launching ${engine.name} agent for bug fixing...`);
|
|
1076
|
+
const agentResult = await engine.run(prompt, selectedModel);
|
|
1077
|
+
if (!agentResult.success) {
|
|
1078
|
+
p.log.error(`Agent failed: ${agentResult.error}`);
|
|
1079
|
+
p.outro("Bug fixing did not complete successfully. Check the output directory for partial results.");
|
|
1080
|
+
process.exit(1);
|
|
1081
|
+
}
|
|
1082
|
+
// ── Proof of work summary ──────────────────────────────────────────
|
|
1083
|
+
const { proof } = agentResult;
|
|
1084
|
+
p.log.step("Proof of work:");
|
|
1085
|
+
p.log.info(` Files written: ${proof.filesWritten}`);
|
|
1086
|
+
p.log.info(` Files read: ${proof.filesRead}`);
|
|
1087
|
+
p.log.info(` Shell commands: ${proof.shellCommands.length}`);
|
|
1088
|
+
if (proof.costUsd > 0) {
|
|
1089
|
+
p.log.info(` API cost: $${proof.costUsd.toFixed(4)}`);
|
|
1090
|
+
}
|
|
1091
|
+
// ── Check fix report ──────────────────────────────────────────────
|
|
1092
|
+
const fixReportPath = path.join(ctx.dirPath, "reports", "12-fix-report.md");
|
|
1093
|
+
if (await fs.pathExists(fixReportPath)) {
|
|
1094
|
+
p.log.info(" Fix report: reports/12-fix-report.md ✓");
|
|
1095
|
+
}
|
|
1096
|
+
// ── Report-based summary ───────────────────────────────────────────
|
|
1097
|
+
await displayReportSummary(appName);
|
|
1098
|
+
p.outro(`Bug fixing complete for: ${ctx.dirPath}`);
|
|
1099
|
+
}
|
|
837
1100
|
/**
|
|
838
1101
|
* Update an existing app: resolve source, run agent, commit, and optionally create a PR.
|
|
839
1102
|
*/
|
|
@@ -1107,13 +1370,28 @@ function deriveAppName(description) {
|
|
|
1107
1370
|
}
|
|
1108
1371
|
else {
|
|
1109
1372
|
// Take the first few meaningful words from the description
|
|
1110
|
-
const stopWords = new Set([
|
|
1373
|
+
const stopWords = new Set([
|
|
1374
|
+
// Articles & conjunctions
|
|
1375
|
+
"a", "an", "the", "and", "or", "but", "not", "nor",
|
|
1376
|
+
// Prepositions
|
|
1377
|
+
"for", "with", "to", "of", "in", "on", "at", "by", "from", "into", "about", "between",
|
|
1378
|
+
// Common verbs / auxiliaries
|
|
1379
|
+
"is", "are", "was", "be", "has", "have", "will", "would", "could", "should",
|
|
1380
|
+
"can", "do", "does", "did",
|
|
1381
|
+
// Pronouns & determiners
|
|
1382
|
+
"my", "your", "its", "their", "our", "this", "that", "these", "those",
|
|
1383
|
+
"it", "them", "which", "where", "when", "who", "what", "how",
|
|
1384
|
+
// Filler / noise words
|
|
1385
|
+
"app", "application", "based", "using", "like", "just", "very", "also",
|
|
1386
|
+
"so", "such", "each", "every", "some", "any", "no", "only", "own", "more",
|
|
1387
|
+
"than", "been", "all",
|
|
1388
|
+
]);
|
|
1111
1389
|
const words = description
|
|
1112
1390
|
.toLowerCase()
|
|
1113
1391
|
.replace(/[^a-z0-9\s]/g, "")
|
|
1114
1392
|
.split(/\s+/)
|
|
1115
1393
|
.filter((w) => w.length > 1 && !stopWords.has(w))
|
|
1116
|
-
.slice(0,
|
|
1394
|
+
.slice(0, 2);
|
|
1117
1395
|
baseName = words.join("-") || "my-app";
|
|
1118
1396
|
}
|
|
1119
1397
|
// Convert to kebab-case
|