codebakers 3.3.0 → 4.3.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 (2) hide show
  1. package/dist/index.js +1417 -1442
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,9 +6,10 @@ import {
6
6
  } from "./chunk-HOWR3YTF.js";
7
7
 
8
8
  // src/index.ts
9
- import * as p8 from "@clack/prompts";
9
+ import * as p7 from "@clack/prompts";
10
10
  import ora3 from "ora";
11
- import fs18 from "fs-extra";
11
+ import fs17 from "fs-extra";
12
+ import path18 from "path";
12
13
 
13
14
  // src/core/config.ts
14
15
  import Conf from "conf";
@@ -749,7 +750,7 @@ import Anthropic from "@anthropic-ai/sdk";
749
750
  var CORE_PATTERNS = `
750
751
  ## RULES (You MUST follow these)
751
752
 
752
- ### BANNED (never do these)
753
+ ### BANNED (never do these) - 15 Rules
753
754
  - \u274C \`any\` type \u2192 use proper TypeScript types
754
755
  - \u274C \`@ts-ignore\` or \`@ts-nocheck\` \u2192 fix the actual type error
755
756
  - \u274C \`console.log\` \u2192 remove or use proper logger
@@ -760,226 +761,468 @@ var CORE_PATTERNS = `
760
761
  - \u274C \`throw new Error('Not implemented')\` \u2192 implement it
761
762
  - \u274C \`debugger\` statements \u2192 remove before commit
762
763
  - \u274C hardcoded secrets/keys \u2192 use environment variables
763
-
764
- ### REQUIRED (always do these)
765
- - \u2705 Buttons: must have \`disabled={isLoading}\` and loading indicator
766
- - \u2705 Forms: must have Zod validation schema
767
- - \u2705 Forms: must show field-level error messages
768
- - \u2705 Forms: must disable submit while submitting
769
- - \u2705 Async operations: must have try/catch with error handling
770
- - \u2705 Async operations: must have loading state
771
- - \u2705 Lists: must have loading state (skeleton/spinner)
772
- - \u2705 Lists: must have empty state
773
- - \u2705 Lists: must have error state with retry
774
- - \u2705 API routes: must check authentication
775
- - \u2705 API routes: must validate input with Zod
776
- - \u2705 API routes: must return proper HTTP status codes
777
- - \u2705 All exports: must have TypeScript types
778
-
779
- ### PATTERNS (use these structures)
780
-
781
- #### Button with Loading
782
- \`\`\`tsx
783
- <Button
784
- onClick={handleAction}
785
- disabled={isLoading}
786
- >
787
- {isLoading ? (
788
- <>
789
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
790
- Loading...
791
- </>
792
- ) : (
793
- 'Action'
794
- )}
795
- </Button>
796
- \`\`\`
797
-
798
- #### Form with Validation
799
- \`\`\`tsx
800
- const schema = z.object({
801
- email: z.string().email('Invalid email'),
802
- password: z.string().min(8, 'Password must be at least 8 characters'),
803
- });
804
-
805
- type FormData = z.infer<typeof schema>;
806
-
807
- function MyForm() {
808
- const [isLoading, setIsLoading] = useState(false);
809
- const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
810
- resolver: zodResolver(schema),
811
- });
812
-
813
- const onSubmit = async (data: FormData) => {
814
- setIsLoading(true);
815
- try {
816
- await submitData(data);
817
- toast.success('Success!');
818
- } catch (error) {
819
- toast.error(error instanceof Error ? error.message : 'Failed');
820
- } finally {
821
- setIsLoading(false);
822
- }
823
- };
824
-
825
- return (
826
- <form onSubmit={handleSubmit(onSubmit)}>
827
- <input {...register('email')} />
828
- {errors.email && <p className="text-red-500">{errors.email.message}</p>}
829
-
830
- <Button type="submit" disabled={isLoading}>
831
- {isLoading ? 'Submitting...' : 'Submit'}
832
- </Button>
833
- </form>
834
- );
835
- }
836
- \`\`\`
837
-
838
- #### Async Data Fetching
839
- \`\`\`tsx
840
- function MyComponent() {
841
- const [data, setData] = useState<DataType | null>(null);
842
- const [isLoading, setIsLoading] = useState(true);
843
- const [error, setError] = useState<string | null>(null);
844
-
845
- useEffect(() => {
846
- async function fetchData() {
847
- try {
848
- const response = await fetch('/api/data');
849
- if (!response.ok) throw new Error('Failed to fetch');
850
- const result = await response.json();
851
- setData(result);
852
- } catch (err) {
853
- setError(err instanceof Error ? err.message : 'Failed to load');
854
- } finally {
855
- setIsLoading(false);
856
- }
857
- }
858
- fetchData();
859
- }, []);
860
-
861
- if (isLoading) return <Skeleton />;
862
- if (error) return <ErrorState message={error} onRetry={() => window.location.reload()} />;
863
- if (!data) return <EmptyState message="No data found" />;
864
-
865
- return <DataDisplay data={data} />;
866
- }
867
- \`\`\`
868
-
869
- #### API Route
870
- \`\`\`tsx
871
- import { NextRequest, NextResponse } from 'next/server';
872
- import { z } from 'zod';
873
- import { createClient } from '@/lib/supabase/server';
874
-
875
- const requestSchema = z.object({
876
- name: z.string().min(1),
877
- email: z.string().email(),
878
- });
879
-
880
- export async function POST(request: NextRequest) {
881
- try {
882
- // 1. Auth check
883
- const supabase = createClient();
884
- const { data: { user }, error: authError } = await supabase.auth.getUser();
885
-
886
- if (authError || !user) {
887
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
888
- }
889
-
890
- // 2. Validate input
891
- const body = await request.json();
892
- const result = requestSchema.safeParse(body);
893
-
894
- if (!result.success) {
895
- return NextResponse.json(
896
- { error: 'Validation failed', details: result.error.flatten() },
897
- { status: 400 }
898
- );
899
- }
900
-
901
- // 3. Business logic
902
- const data = result.data;
903
- // ... do something
904
-
905
- // 4. Return success
906
- return NextResponse.json({ data }, { status: 201 });
907
-
908
- } catch (error) {
909
- console.error('[API] Error:', error);
910
- return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
911
- }
912
- }
913
- \`\`\`
764
+ - \u274C \`innerHTML\` or \`dangerouslySetInnerHTML\` without sanitization
765
+ - \u274C \`document.write\` \u2192 use React/DOM methods
766
+ - \u274C synchronous \`localStorage\` in render \u2192 use useEffect
767
+ - \u274C \`var\` keyword \u2192 use const/let
768
+ - \u274C nested ternaries \u2192 use if/else or early returns
769
+
770
+ ### REACT PATTERNS - 12 Rules
771
+ - \u2705 Components must have TypeScript props interface
772
+ - \u2705 useState must have explicit type: \`useState<Type>()\`
773
+ - \u2705 useEffect must have dependency array
774
+ - \u2705 useEffect cleanup for subscriptions/timers
775
+ - \u2705 useMemo/useCallback for expensive computations
776
+ - \u2705 Keys in lists must be stable IDs (not index)
777
+ - \u2705 Error boundaries for component trees
778
+ - \u2705 Suspense boundaries for lazy components
779
+ - \u2705 forwardRef for components accepting refs
780
+ - \u2705 displayName for HOCs and forwardRef
781
+ - \u2705 Prop drilling > 2 levels \u2192 use Context
782
+ - \u2705 Custom hooks for reusable stateful logic
783
+
784
+ ### FORM PATTERNS - 8 Rules
785
+ - \u2705 Forms must have Zod validation schema
786
+ - \u2705 Forms must show field-level error messages
787
+ - \u2705 Forms must disable submit while submitting
788
+ - \u2705 Forms must show loading state on submit button
789
+ - \u2705 Forms must handle server errors gracefully
790
+ - \u2705 Forms must prevent double-submission
791
+ - \u2705 File inputs must validate file type/size
792
+ - \u2705 Forms must have accessible labels
793
+
794
+ ### ASYNC/DATA PATTERNS - 10 Rules
795
+ - \u2705 Async operations must have try/catch
796
+ - \u2705 Async operations must have loading state
797
+ - \u2705 Lists must have loading state (skeleton/spinner)
798
+ - \u2705 Lists must have empty state
799
+ - \u2705 Lists must have error state with retry
800
+ - \u2705 Fetch must check response.ok
801
+ - \u2705 Fetch must have timeout handling
802
+ - \u2705 Pagination for lists > 20 items
803
+ - \u2705 Optimistic updates for better UX
804
+ - \u2705 Debounce for search/filter inputs
805
+
806
+ ### API ROUTE PATTERNS - 8 Rules
807
+ - \u2705 API routes must check authentication
808
+ - \u2705 API routes must validate input with Zod
809
+ - \u2705 API routes must return proper HTTP status codes
810
+ - \u2705 API routes must log errors
811
+ - \u2705 API routes must sanitize user input
812
+ - \u2705 API routes must rate limit sensitive endpoints
813
+ - \u2705 API routes must validate content-type
814
+ - \u2705 API routes must handle CORS properly
815
+
816
+ ### SECURITY PATTERNS - 8 Rules
817
+ - \u2705 Sanitize all user input before display
818
+ - \u2705 Use parameterized queries (no SQL injection)
819
+ - \u2705 CSRF tokens for state-changing operations
820
+ - \u2705 Secure cookies (httpOnly, secure, sameSite)
821
+ - \u2705 Validate redirect URLs
822
+ - \u2705 Use Content-Security-Policy headers
823
+ - \u2705 Hash passwords with bcrypt/argon2
824
+ - \u2705 Rate limit authentication endpoints
825
+
826
+ ### ACCESSIBILITY PATTERNS - 6 Rules
827
+ - \u2705 Images must have alt text
828
+ - \u2705 Interactive elements must be keyboard accessible
829
+ - \u2705 Form inputs must have labels
830
+ - \u2705 Color contrast must meet WCAG AA
831
+ - \u2705 Focus indicators must be visible
832
+ - \u2705 ARIA labels for icon-only buttons
833
+
834
+ ### TYPESCRIPT PATTERNS - 5 Rules
835
+ - \u2705 All exports must have explicit types
836
+ - \u2705 Prefer interfaces over type aliases for objects
837
+ - \u2705 Use discriminated unions for state
838
+ - \u2705 Avoid type assertions (as) where possible
839
+ - \u2705 Use strict mode
840
+
841
+ ### TESTING PATTERNS - 4 Rules
842
+ - \u2705 Test files must be co-located with source
843
+ - \u2705 Test user behavior, not implementation
844
+ - \u2705 Mock external dependencies
845
+ - \u2705 Use data-testid for test selectors
914
846
  `;
915
847
  var BANNED_PATTERNS = [
848
+ // === BANNED PATTERNS (15) ===
916
849
  {
917
850
  id: "no-any",
918
851
  regex: /:\s*any\b|<any>|as\s+any/g,
919
852
  rule: "No `any` type",
920
853
  fix: "Use proper TypeScript types",
921
- severity: "error"
854
+ severity: "error",
855
+ category: "banned"
922
856
  },
923
857
  {
924
858
  id: "no-ts-ignore",
925
859
  regex: /@ts-ignore|@ts-nocheck/g,
926
860
  rule: "No @ts-ignore",
927
861
  fix: "Fix the actual type error",
928
- severity: "error"
862
+ severity: "error",
863
+ category: "banned"
929
864
  },
930
865
  {
931
866
  id: "no-console",
932
867
  regex: /console\.(log|debug|info)\(/g,
933
868
  rule: "No console.log",
934
869
  fix: "Remove or use proper logger",
935
- severity: "error"
870
+ severity: "warning",
871
+ category: "banned"
936
872
  },
937
873
  {
938
874
  id: "no-todo",
939
875
  regex: /\/\/\s*(TODO|FIXME|HACK|XXX):/gi,
940
876
  rule: "No TODO/FIXME",
941
877
  fix: "Complete the implementation now",
942
- severity: "error"
878
+ severity: "error",
879
+ category: "banned"
943
880
  },
944
881
  {
945
882
  id: "no-eval",
946
883
  regex: /\beval\s*\(/g,
947
884
  rule: "No eval()",
948
885
  fix: "Use safer alternatives",
949
- severity: "error"
886
+ severity: "error",
887
+ category: "security"
950
888
  },
951
889
  {
952
890
  id: "no-empty-handler",
953
891
  regex: /on\w+\s*=\s*\{\s*\(\)\s*=>\s*\{\s*\}\s*\}/g,
954
892
  rule: "No empty event handlers",
955
893
  fix: "Implement the handler",
956
- severity: "error"
894
+ severity: "error",
895
+ category: "banned"
957
896
  },
958
897
  {
959
898
  id: "no-debugger",
960
899
  regex: /\bdebugger\b/g,
961
900
  rule: "No debugger statements",
962
901
  fix: "Remove debugger",
963
- severity: "error"
902
+ severity: "error",
903
+ category: "banned"
964
904
  },
965
905
  {
966
906
  id: "no-placeholder",
967
907
  regex: /\/\/\s*\.\.\.|\/\*\s*\.\.\.\s*\*\//g,
968
908
  rule: "No placeholder comments",
969
909
  fix: "Write actual code",
970
- severity: "error"
910
+ severity: "error",
911
+ category: "banned"
971
912
  },
972
913
  {
973
914
  id: "no-not-implemented",
974
915
  regex: /throw\s+new\s+Error\s*\(\s*['"`]not\s+implemented/gi,
975
916
  rule: 'No "not implemented" errors',
976
917
  fix: "Implement the function",
977
- severity: "error"
918
+ severity: "error",
919
+ category: "banned"
920
+ },
921
+ {
922
+ id: "no-var",
923
+ regex: /\bvar\s+\w+/g,
924
+ rule: "No var keyword",
925
+ fix: "Use const or let",
926
+ severity: "error",
927
+ category: "banned"
928
+ },
929
+ {
930
+ id: "no-innerhtml",
931
+ regex: /\.innerHTML\s*=|dangerouslySetInnerHTML/g,
932
+ rule: "No innerHTML without sanitization",
933
+ fix: "Sanitize content or use React methods",
934
+ severity: "error",
935
+ category: "security"
936
+ },
937
+ {
938
+ id: "no-document-write",
939
+ regex: /document\.write\s*\(/g,
940
+ rule: "No document.write",
941
+ fix: "Use DOM methods or React",
942
+ severity: "error",
943
+ category: "banned"
944
+ },
945
+ {
946
+ id: "no-hardcoded-secret",
947
+ regex: /(api[_-]?key|secret|password|token)\s*[:=]\s*['"`][^'"`]{8,}/gi,
948
+ rule: "No hardcoded secrets",
949
+ fix: "Use environment variables",
950
+ severity: "error",
951
+ category: "security"
952
+ },
953
+ {
954
+ id: "no-nested-ternary",
955
+ regex: /\?\s*[^:]+\?\s*[^:]+:/g,
956
+ rule: "No nested ternaries",
957
+ fix: "Use if/else or early returns",
958
+ severity: "warning",
959
+ category: "banned"
960
+ },
961
+ {
962
+ id: "no-alert",
963
+ regex: /\balert\s*\(|\bconfirm\s*\(|\bprompt\s*\(/g,
964
+ rule: "No browser alerts",
965
+ fix: "Use proper UI components",
966
+ severity: "warning",
967
+ category: "banned"
968
+ },
969
+ // === REACT PATTERNS (12) ===
970
+ {
971
+ id: "react-no-index-key",
972
+ regex: /key\s*=\s*\{?\s*(index|i|idx)\s*\}?/g,
973
+ rule: "No array index as key",
974
+ fix: "Use stable unique ID",
975
+ severity: "warning",
976
+ category: "react"
977
+ },
978
+ {
979
+ id: "react-missing-deps",
980
+ regex: /useEffect\s*\(\s*\(\)\s*=>\s*\{[^}]*\}\s*\)/g,
981
+ rule: "useEffect missing dependency array",
982
+ fix: "Add dependency array []",
983
+ severity: "error",
984
+ category: "react"
985
+ },
986
+ {
987
+ id: "react-setstate-in-render",
988
+ regex: /function\s+\w+\s*\([^)]*\)\s*\{[^}]*\bset\w+\s*\([^)]+\)[^}]*return\s*\(/gs,
989
+ rule: "setState called during render",
990
+ fix: "Move to useEffect or event handler",
991
+ severity: "error",
992
+ category: "react"
993
+ },
994
+ // === SECURITY PATTERNS (8) ===
995
+ {
996
+ id: "security-sql-injection",
997
+ regex: /\$\{[^}]+\}.*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)/gi,
998
+ rule: "Potential SQL injection",
999
+ fix: "Use parameterized queries",
1000
+ severity: "error",
1001
+ category: "security"
1002
+ },
1003
+ {
1004
+ id: "security-open-redirect",
1005
+ regex: /redirect\s*\(\s*(?:req\.|request\.)/gi,
1006
+ rule: "Potential open redirect",
1007
+ fix: "Validate redirect URL",
1008
+ severity: "error",
1009
+ category: "security"
1010
+ },
1011
+ {
1012
+ id: "security-exec",
1013
+ regex: /\bexec\s*\(|\bexecSync\s*\(|\bspawn\s*\(/g,
1014
+ rule: "Command execution detected",
1015
+ fix: "Sanitize input or avoid shell commands",
1016
+ severity: "warning",
1017
+ category: "security"
1018
+ },
1019
+ // === ACCESSIBILITY PATTERNS (6) ===
1020
+ {
1021
+ id: "a11y-img-alt",
1022
+ regex: /<img[^>]+(?!alt\s*=)[^>]*>/gi,
1023
+ rule: "Image missing alt attribute",
1024
+ fix: 'Add alt="description"',
1025
+ severity: "warning",
1026
+ category: "a11y"
1027
+ },
1028
+ {
1029
+ id: "a11y-button-type",
1030
+ regex: /<button(?![^>]*type\s*=)[^>]*>/gi,
1031
+ rule: "Button missing type attribute",
1032
+ fix: 'Add type="button" or type="submit"',
1033
+ severity: "warning",
1034
+ category: "a11y"
1035
+ },
1036
+ {
1037
+ id: "a11y-anchor-blank",
1038
+ regex: /target\s*=\s*['"]_blank['"](?![^>]*rel\s*=)/gi,
1039
+ rule: "External link missing rel attribute",
1040
+ fix: 'Add rel="noopener noreferrer"',
1041
+ severity: "warning",
1042
+ category: "a11y"
1043
+ },
1044
+ // === ASYNC PATTERNS (10) ===
1045
+ {
1046
+ id: "async-no-await",
1047
+ regex: /async\s+(?:function|\([^)]*\)\s*=>)[^{]*\{(?:(?!await)[^}])*\}/g,
1048
+ rule: "Async function without await",
1049
+ fix: "Add await or remove async",
1050
+ severity: "warning",
1051
+ category: "async"
1052
+ },
1053
+ {
1054
+ id: "async-promise-no-catch",
1055
+ regex: /\.then\s*\([^)]+\)(?!\s*\.catch)/g,
1056
+ rule: "Promise without catch",
1057
+ fix: "Add .catch() handler",
1058
+ severity: "warning",
1059
+ category: "async"
1060
+ },
1061
+ // === TYPESCRIPT PATTERNS (5) ===
1062
+ {
1063
+ id: "ts-non-null-assertion",
1064
+ regex: /\w+!/g,
1065
+ rule: "Non-null assertion used",
1066
+ fix: "Use optional chaining or null check",
1067
+ severity: "warning",
1068
+ category: "typescript"
1069
+ },
1070
+ {
1071
+ id: "ts-explicit-any-return",
1072
+ regex: /\):\s*any\s*\{/g,
1073
+ rule: "Function returns any",
1074
+ fix: "Add explicit return type",
1075
+ severity: "error",
1076
+ category: "typescript"
1077
+ },
1078
+ {
1079
+ id: "ts-object-type",
1080
+ regex: /:\s*object\b/g,
1081
+ rule: "Generic object type used",
1082
+ fix: "Use specific interface or Record<K,V>",
1083
+ severity: "warning",
1084
+ category: "typescript"
1085
+ },
1086
+ // === MORE REACT PATTERNS ===
1087
+ {
1088
+ id: "react-no-bind-in-render",
1089
+ regex: /onClick\s*=\s*\{[^}]*\.bind\s*\(/g,
1090
+ rule: "No .bind() in render",
1091
+ fix: "Use arrow function or useCallback",
1092
+ severity: "warning",
1093
+ category: "react"
1094
+ },
1095
+ {
1096
+ id: "react-no-arrow-in-render",
1097
+ regex: /onClick\s*=\s*\{\s*\(\)\s*=>\s*\w+\([^)]*\)\s*\}/g,
1098
+ rule: "Arrow function in render creates new function",
1099
+ fix: "Extract handler or use useCallback",
1100
+ severity: "warning",
1101
+ category: "react"
1102
+ },
1103
+ {
1104
+ id: "react-missing-error-boundary",
1105
+ regex: /throw\s+new\s+Error\s*\(/g,
1106
+ rule: "Throwing error without boundary",
1107
+ fix: "Wrap in Error Boundary",
1108
+ severity: "warning",
1109
+ category: "react"
1110
+ },
1111
+ {
1112
+ id: "react-direct-dom",
1113
+ regex: /document\.getElementById|document\.querySelector|document\.getElement/g,
1114
+ rule: "Direct DOM manipulation in React",
1115
+ fix: "Use refs or React state",
1116
+ severity: "warning",
1117
+ category: "react"
1118
+ },
1119
+ // === MORE SECURITY PATTERNS ===
1120
+ {
1121
+ id: "security-jwt-secret",
1122
+ regex: /jwt\.sign\s*\([^)]*['"`][^'"`]{8,}['"`]/gi,
1123
+ rule: "Hardcoded JWT secret",
1124
+ fix: "Use environment variable",
1125
+ severity: "error",
1126
+ category: "security"
1127
+ },
1128
+ {
1129
+ id: "security-md5",
1130
+ regex: /\bmd5\s*\(|createHash\s*\(\s*['"]md5['"]/gi,
1131
+ rule: "MD5 is insecure for passwords",
1132
+ fix: "Use bcrypt or argon2",
1133
+ severity: "error",
1134
+ category: "security"
1135
+ },
1136
+ {
1137
+ id: "security-sha1",
1138
+ regex: /createHash\s*\(\s*['"]sha1['"]/gi,
1139
+ rule: "SHA1 is deprecated",
1140
+ fix: "Use SHA256 or bcrypt",
1141
+ severity: "warning",
1142
+ category: "security"
1143
+ },
1144
+ {
1145
+ id: "security-cors-star",
1146
+ regex: /Access-Control-Allow-Origin['":\s]+\*/gi,
1147
+ rule: "CORS allows all origins",
1148
+ fix: "Specify allowed origins",
1149
+ severity: "warning",
1150
+ category: "security"
1151
+ },
1152
+ // === MORE ASYNC PATTERNS ===
1153
+ {
1154
+ id: "async-floating-promise",
1155
+ regex: /(?<!await\s)(?<!return\s)\w+\s*\.\s*then\s*\(/g,
1156
+ rule: "Floating promise (not awaited)",
1157
+ fix: "Add await or handle promise",
1158
+ severity: "warning",
1159
+ category: "async"
1160
+ },
1161
+ {
1162
+ id: "async-foreach-async",
1163
+ regex: /\.forEach\s*\(\s*async/g,
1164
+ rule: "async in forEach does not wait",
1165
+ fix: "Use for...of or Promise.all with map",
1166
+ severity: "error",
1167
+ category: "async"
1168
+ },
1169
+ // === MORE ACCESSIBILITY PATTERNS ===
1170
+ {
1171
+ id: "a11y-no-autofocus",
1172
+ regex: /autoFocus|autofocus/g,
1173
+ rule: "Autofocus can confuse screen readers",
1174
+ fix: "Remove or use carefully",
1175
+ severity: "warning",
1176
+ category: "a11y"
1177
+ },
1178
+ {
1179
+ id: "a11y-positive-tabindex",
1180
+ regex: /tabIndex\s*=\s*\{?\s*[1-9]/g,
1181
+ rule: "Positive tabindex disrupts focus order",
1182
+ fix: "Use tabIndex={0} or tabIndex={-1}",
1183
+ severity: "warning",
1184
+ category: "a11y"
1185
+ },
1186
+ {
1187
+ id: "a11y-onclick-no-keyboard",
1188
+ regex: /onClick\s*=\s*\{[^}]+\}(?![^>]*onKeyDown|onKeyPress|onKeyUp)/g,
1189
+ rule: "Click handler without keyboard support",
1190
+ fix: "Add onKeyDown for Enter/Space",
1191
+ severity: "warning",
1192
+ category: "a11y"
1193
+ },
1194
+ // === FORM PATTERNS ===
1195
+ {
1196
+ id: "form-no-autocomplete",
1197
+ regex: /<input[^>]+type\s*=\s*['"]password['"][^>]*(?!autocomplete)/gi,
1198
+ rule: "Password field missing autocomplete",
1199
+ fix: 'Add autocomplete="current-password" or "new-password"',
1200
+ severity: "warning",
1201
+ category: "form"
1202
+ },
1203
+ // === PERFORMANCE PATTERNS ===
1204
+ {
1205
+ id: "perf-console-in-loop",
1206
+ regex: /(?:for|while|\.forEach|\.map)\s*\([^)]*\)\s*\{[^}]*console\./g,
1207
+ rule: "Console in loop impacts performance",
1208
+ fix: "Remove or move outside loop",
1209
+ severity: "warning",
1210
+ category: "banned"
1211
+ },
1212
+ {
1213
+ id: "perf-await-in-loop",
1214
+ regex: /(?:for|while)\s*\([^)]*\)\s*\{[^}]*await\s/g,
1215
+ rule: "await in loop is sequential",
1216
+ fix: "Use Promise.all for parallel execution",
1217
+ severity: "warning",
1218
+ category: "async"
978
1219
  }
979
1220
  ];
980
1221
  var REQUIRED_CHECKS = [
1222
+ // === BUTTON CHECKS ===
981
1223
  {
982
1224
  id: "button-loading",
1225
+ category: "form",
983
1226
  check: (code) => {
984
1227
  const hasButton = /<Button|<button/i.test(code);
985
1228
  if (!hasButton) return { pass: true };
@@ -987,17 +1230,31 @@ var REQUIRED_CHECKS = [
987
1230
  return { pass: hasDisabled, rule: "Buttons must have disabled={isLoading}", fix: "Add loading state" };
988
1231
  }
989
1232
  },
1233
+ // === FORM CHECKS ===
990
1234
  {
991
1235
  id: "form-validation",
1236
+ category: "form",
992
1237
  check: (code) => {
993
1238
  const hasForm = /<form|useForm|handleSubmit/i.test(code);
994
1239
  if (!hasForm) return { pass: true };
995
- const hasValidation = /zod|z\.|zodResolver|schema/i.test(code);
996
- return { pass: hasValidation, rule: "Forms must have Zod validation", fix: "Add Zod schema" };
1240
+ const hasValidation = /zod|z\.|zodResolver|schema|yup|validate/i.test(code);
1241
+ return { pass: hasValidation, rule: "Forms must have validation", fix: "Add Zod/Yup schema" };
1242
+ }
1243
+ },
1244
+ {
1245
+ id: "form-error-display",
1246
+ category: "form",
1247
+ check: (code) => {
1248
+ const hasForm = /<form|useForm/i.test(code);
1249
+ if (!hasForm) return { pass: true };
1250
+ const hasErrors = /errors\.|error\s*&&|formState.*errors|\.message/i.test(code);
1251
+ return { pass: hasErrors, rule: "Forms must display errors", fix: "Add error messages" };
997
1252
  }
998
1253
  },
1254
+ // === ASYNC CHECKS ===
999
1255
  {
1000
1256
  id: "async-error-handling",
1257
+ category: "async",
1001
1258
  check: (code) => {
1002
1259
  const hasAsync = /async\s+|await\s+/i.test(code);
1003
1260
  if (!hasAsync) return { pass: true };
@@ -1005,14 +1262,102 @@ var REQUIRED_CHECKS = [
1005
1262
  return { pass: hasTryCatch, rule: "Async operations must have try/catch", fix: "Add error handling" };
1006
1263
  }
1007
1264
  },
1265
+ {
1266
+ id: "async-loading-state",
1267
+ category: "async",
1268
+ check: (code) => {
1269
+ const hasAsync = /async\s+|await\s+|\.then\s*\(/i.test(code);
1270
+ if (!hasAsync) return { pass: true };
1271
+ const hasLoading = /loading|isLoading|pending|fetching|submitting/i.test(code);
1272
+ return { pass: hasLoading, rule: "Async operations must have loading state", fix: "Add isLoading state" };
1273
+ }
1274
+ },
1275
+ // === API ROUTE CHECKS ===
1008
1276
  {
1009
1277
  id: "api-auth-check",
1278
+ category: "api",
1010
1279
  check: (code) => {
1011
1280
  const isApiRoute = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/i.test(code);
1012
1281
  if (!isApiRoute) return { pass: true };
1013
- const hasAuth = /getUser|getSession|auth\(\)|authenticate|unauthorized|401/i.test(code);
1282
+ const hasAuth = /getUser|getSession|auth\(\)|authenticate|unauthorized|401|getServerSession/i.test(code);
1014
1283
  return { pass: hasAuth, rule: "API routes must check authentication", fix: "Add auth check" };
1015
1284
  }
1285
+ },
1286
+ {
1287
+ id: "api-input-validation",
1288
+ category: "api",
1289
+ check: (code) => {
1290
+ const isApiRoute = /export\s+(async\s+)?function\s+(POST|PUT|PATCH)/i.test(code);
1291
+ if (!isApiRoute) return { pass: true };
1292
+ const hasValidation = /safeParse|parse\(|validate|schema|zod|z\./i.test(code);
1293
+ return { pass: hasValidation, rule: "API routes must validate input", fix: "Add Zod validation" };
1294
+ }
1295
+ },
1296
+ {
1297
+ id: "api-status-codes",
1298
+ category: "api",
1299
+ check: (code) => {
1300
+ const isApiRoute = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/i.test(code);
1301
+ if (!isApiRoute) return { pass: true };
1302
+ const hasStatusCodes = /status:\s*\d{3}|\{\s*status:\s*\d/i.test(code);
1303
+ return { pass: hasStatusCodes, rule: "API routes must return proper status codes", fix: "Add status codes" };
1304
+ }
1305
+ },
1306
+ // === LIST/DATA CHECKS ===
1307
+ {
1308
+ id: "list-loading-state",
1309
+ category: "async",
1310
+ check: (code) => {
1311
+ const hasList = /\.map\s*\(|Array\.from|forEach/i.test(code);
1312
+ const hasFetch = /fetch|useQuery|useSWR|axios/i.test(code);
1313
+ if (!hasList || !hasFetch) return { pass: true };
1314
+ const hasLoading = /Skeleton|Spinner|Loading|isLoading|loading/i.test(code);
1315
+ return { pass: hasLoading, rule: "Lists must have loading state", fix: "Add skeleton/spinner" };
1316
+ }
1317
+ },
1318
+ {
1319
+ id: "list-empty-state",
1320
+ category: "async",
1321
+ check: (code) => {
1322
+ const hasList = /\.map\s*\(/i.test(code);
1323
+ const hasFetch = /fetch|useQuery|useSWR|axios/i.test(code);
1324
+ if (!hasList || !hasFetch) return { pass: true };
1325
+ const hasEmpty = /length\s*===?\s*0|!.*\.length|empty|no\s+\w+\s+found/i.test(code);
1326
+ return { pass: hasEmpty, rule: "Lists must have empty state", fix: "Add empty state UI" };
1327
+ }
1328
+ },
1329
+ // === SECURITY CHECKS ===
1330
+ {
1331
+ id: "security-env-secrets",
1332
+ category: "security",
1333
+ check: (code) => {
1334
+ const hasSecret = /api[_-]?key|secret|password|token/i.test(code);
1335
+ if (!hasSecret) return { pass: true };
1336
+ const usesEnv = /process\.env|import\.meta\.env/i.test(code);
1337
+ return { pass: usesEnv, rule: "Secrets must use environment variables", fix: "Move to .env" };
1338
+ }
1339
+ },
1340
+ // === TYPESCRIPT CHECKS ===
1341
+ {
1342
+ id: "ts-export-types",
1343
+ category: "typescript",
1344
+ check: (code) => {
1345
+ const hasExport = /export\s+(const|function|class)\s+\w+/i.test(code);
1346
+ if (!hasExport) return { pass: true };
1347
+ const hasTypes = /:\s*\w+|<\w+>|interface\s+|type\s+/i.test(code);
1348
+ return { pass: hasTypes, rule: "Exports must have TypeScript types", fix: "Add type annotations" };
1349
+ }
1350
+ },
1351
+ // === ACCESSIBILITY CHECKS ===
1352
+ {
1353
+ id: "a11y-form-labels",
1354
+ category: "a11y",
1355
+ check: (code) => {
1356
+ const hasInput = /<input|<select|<textarea/i.test(code);
1357
+ if (!hasInput) return { pass: true };
1358
+ const hasLabel = /<label|aria-label|aria-labelledby|id=.*htmlFor/i.test(code);
1359
+ return { pass: hasLabel, rule: "Form inputs must have labels", fix: "Add <label> or aria-label" };
1360
+ }
1016
1361
  }
1017
1362
  ];
1018
1363
  function validateCode(code) {
@@ -1029,7 +1374,8 @@ function validateCode(code) {
1029
1374
  line: lineNumber,
1030
1375
  code: match[0],
1031
1376
  fix: pattern.fix,
1032
- severity: pattern.severity
1377
+ severity: pattern.severity,
1378
+ category: pattern.category
1033
1379
  });
1034
1380
  }
1035
1381
  }
@@ -1039,7 +1385,8 @@ function validateCode(code) {
1039
1385
  violations.push({
1040
1386
  rule: result.rule,
1041
1387
  fix: result.fix || "See pattern documentation",
1042
- severity: "error"
1388
+ severity: "error",
1389
+ category: check.category
1043
1390
  });
1044
1391
  }
1045
1392
  }
@@ -1285,7 +1632,7 @@ ${originalRequest}`;
1285
1632
  }
1286
1633
  const pathMatch = request.match(/['"`]([^'"`]+)['"`]/g);
1287
1634
  if (pathMatch) {
1288
- keywords.push(...pathMatch.map((p9) => p9.replace(/['"`]/g, "")));
1635
+ keywords.push(...pathMatch.map((p8) => p8.replace(/['"`]/g, "")));
1289
1636
  }
1290
1637
  return keywords;
1291
1638
  }
@@ -1499,11 +1846,11 @@ Format each as a single line. Be brief.`;
1499
1846
  } else if (line.toLowerCase().includes("suggestion") || line.toLowerCase().includes("improvement")) {
1500
1847
  currentSection = "suggestions";
1501
1848
  } else if (line.startsWith("-") || line.startsWith("\u2022") || /^\d+\./.test(line)) {
1502
- const text6 = line.replace(/^[-•\d.]\s*/, "").trim();
1503
- if (text6 && currentSection === "issues") {
1504
- issues.push(text6);
1505
- } else if (text6 && currentSection === "suggestions") {
1506
- suggestions.push(text6);
1849
+ const text5 = line.replace(/^[-•\d.]\s*/, "").trim();
1850
+ if (text5 && currentSection === "issues") {
1851
+ issues.push(text5);
1852
+ } else if (text5 && currentSection === "suggestions") {
1853
+ suggestions.push(text5);
1507
1854
  }
1508
1855
  }
1509
1856
  }
@@ -1572,8 +1919,8 @@ Format each as a single line. Be brief.`;
1572
1919
  if (this.ai) {
1573
1920
  onProgress?.("Running AI deep analysis...", 60);
1574
1921
  const filesForAI = await Promise.all(
1575
- filePaths.slice(0, 50).map(async (p9) => {
1576
- const f = await this.loadFile(p9);
1922
+ filePaths.slice(0, 50).map(async (p8) => {
1923
+ const f = await this.loadFile(p8);
1577
1924
  return f ? { path: f.path, content: f.content } : null;
1578
1925
  })
1579
1926
  );
@@ -1730,57 +2077,67 @@ import * as p from "@clack/prompts";
1730
2077
  import chalk from "chalk";
1731
2078
  var colors = {
1732
2079
  primary: chalk.hex("#7fff00"),
1733
- // Lime green (accent)
2080
+ // Lime green (brand)
1734
2081
  secondary: chalk.hex("#888888"),
1735
2082
  // Gray text
1736
2083
  muted: chalk.hex("#555555"),
1737
2084
  // Dimmed text
1738
- success: chalk.hex("#27ae60"),
1739
- // Green
1740
- warning: chalk.hex("#f39c12"),
1741
- // Orange
2085
+ success: chalk.hex("#2ecc71"),
2086
+ // Bright green
2087
+ warning: chalk.hex("#f1c40f"),
2088
+ // Yellow
1742
2089
  error: chalk.hex("#e74c3c"),
1743
2090
  // Red
1744
2091
  info: chalk.hex("#3498db"),
1745
2092
  // Blue
1746
- white: chalk.hex("#eeeeee"),
1747
- dim: chalk.hex("#666666")
2093
+ white: chalk.hex("#ffffff"),
2094
+ dim: chalk.hex("#666666"),
2095
+ accent: chalk.hex("#9b59b6")
2096
+ // Purple for highlights
2097
+ };
2098
+ var box = {
2099
+ topLeft: "\u256D",
2100
+ topRight: "\u256E",
2101
+ bottomLeft: "\u2570",
2102
+ bottomRight: "\u256F",
2103
+ horizontal: "\u2500",
2104
+ vertical: "\u2502",
2105
+ teeRight: "\u251C",
2106
+ teeLeft: "\u2524"
1748
2107
  };
1749
2108
  var sym = {
1750
- check: chalk.green("\u2713"),
1751
- cross: chalk.red("\u2717"),
1752
- box: chalk.dim("\u2610"),
1753
- bullet: chalk.dim("\u2022"),
1754
- arrow: chalk.dim("\u2192"),
1755
- ellipsis: "\u2026",
1756
- spinner: "\u27F3"
2109
+ check: colors.success("\u2713"),
2110
+ cross: colors.error("\u2717"),
2111
+ bullet: colors.dim("\u2022"),
2112
+ arrow: colors.primary("\u2192"),
2113
+ arrowRight: "\u203A",
2114
+ spinner: "\u27F3",
2115
+ star: "\u2605",
2116
+ lightning: "\u26A1",
2117
+ folder: "\u{1F4C1}",
2118
+ file: "\u{1F4C4}",
2119
+ gear: "\u2699",
2120
+ rocket: "\u{1F680}",
2121
+ sparkles: "\u2728",
2122
+ warning: "\u26A0",
2123
+ info: "\u2139"
1757
2124
  };
1758
- function showHeader() {
1759
- console.clear();
1760
- console.log("");
1761
- console.log(colors.muted(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1762
- console.log(colors.muted(" \u2502") + colors.white(" \u{1F4E6} C O D E B A K E R S ") + colors.muted("\u2502"));
1763
- console.log(colors.muted(" \u2502") + colors.dim(" ") + colors.muted("\u2502"));
1764
- console.log(colors.muted(" \u2502") + colors.secondary(" AI Dev Team That Follows The Rules ") + colors.muted("\u2502"));
1765
- console.log(colors.muted(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1766
- console.log("");
1767
- }
1768
2125
  function showSuccess(message) {
1769
- console.log(` ${sym.check} ${colors.white(message)}`);
2126
+ console.log(` ${sym.check} ${colors.success(message)}`);
1770
2127
  }
1771
2128
  function showError(message) {
1772
2129
  console.log(` ${sym.cross} ${colors.error(message)}`);
1773
2130
  }
1774
2131
  function showWarning(message) {
1775
- console.log(` ${colors.warning("\u26A0")} ${colors.warning(message)}`);
2132
+ console.log(` ${colors.warning(sym.warning)} ${colors.warning(message)}`);
1776
2133
  }
1777
2134
  function showInfo(message) {
1778
- console.log(colors.muted(` \u2139\uFE0F ${message}`));
2135
+ console.log(` ${colors.info(sym.info)} ${colors.info(message)}`);
1779
2136
  }
1780
2137
  function divider() {
1781
- console.log(colors.muted(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2138
+ console.log(colors.muted(` ${box.horizontal.repeat(50)}`));
1782
2139
  }
1783
- var promptSymbol = colors.primary("\u276F");
2140
+ var promptSymbol = colors.primary("\u203A");
1784
2141
 
1785
2142
  // src/core/nlp.ts
1786
2143
  var INTENT_PATTERNS = [
@@ -1820,6 +2177,24 @@ var INTENT_PATTERNS = [
1820
2177
  weight: 8
1821
2178
  },
1822
2179
  // ═══════════════════════════════════════════════════════════════════════════
2180
+ // WEBSITE REVIEW - Audit a website URL
2181
+ // ═══════════════════════════════════════════════════════════════════════════
2182
+ {
2183
+ intent: "website-review",
2184
+ patterns: [
2185
+ /\b(review|audit|check|analyze|scan)\b.*\b(website|site|url|page|webpage)\b/i,
2186
+ /\b(website|site|url|page)\b.*\b(review|audit|check|issues|seo|accessibility)\b/i,
2187
+ /\bseo\s*(audit|check|review|analysis)\b/i,
2188
+ /\baccessibility\s*(audit|check|review)\b/i,
2189
+ /\bcheck\s*(this\s*)?(url|site|website)\b/i,
2190
+ /\bwhat('s| is)\s*wrong\s*with\s*(this\s*)?(website|site|url)\b/i,
2191
+ /\breview\s+(https?:\/\/|www\.)/i,
2192
+ /\baudit\s+(https?:\/\/|www\.)/i
2193
+ ],
2194
+ keywords: ["review website", "audit website", "check site", "seo audit", "accessibility check", "website issues", "scan url"],
2195
+ weight: 9
2196
+ },
2197
+ // ═══════════════════════════════════════════════════════════════════════════
1823
2198
  // PATTERNS - Pattern rules
1824
2199
  // ═══════════════════════════════════════════════════════════════════════════
1825
2200
  {
@@ -2503,12 +2878,12 @@ var NLPInterpreter = class {
2503
2878
  deploy: "This will deploy your code. Continue?"
2504
2879
  };
2505
2880
  console.log("");
2506
- const confirm8 = await p.confirm({
2881
+ const confirm7 = await p.confirm({
2507
2882
  message: colors.warning(messages[intent.type] || "Are you sure?"),
2508
2883
  initialValue: intent.type !== "deploy"
2509
2884
  // Default no for deploy
2510
2885
  });
2511
- return !p.isCancel(confirm8) && confirm8 === true;
2886
+ return !p.isCancel(confirm7) && confirm7 === true;
2512
2887
  }
2513
2888
  };
2514
2889
  var nlp = new NLPInterpreter();
@@ -2736,22 +3111,22 @@ Please provide ONLY the corrected file content with the path. No explanation nee
2736
3111
  // HELPERS
2737
3112
  // ============================================================================
2738
3113
  async findMissingPackages(packages) {
2739
- const fs19 = await import("fs-extra");
3114
+ const fs18 = await import("fs-extra");
2740
3115
  const path19 = await import("path");
2741
3116
  const pkgPath = path19.join(this.scanner["projectPath"], "package.json");
2742
- if (!await fs19.pathExists(pkgPath)) return packages;
2743
- const pkg = await fs19.readJson(pkgPath);
3117
+ if (!await fs18.pathExists(pkgPath)) return packages;
3118
+ const pkg = await fs18.readJson(pkgPath);
2744
3119
  const installed = {
2745
3120
  ...pkg.dependencies,
2746
3121
  ...pkg.devDependencies
2747
3122
  };
2748
- return packages.filter((p9) => !installed[p9]);
3123
+ return packages.filter((p8) => !installed[p8]);
2749
3124
  }
2750
- spin(text6) {
3125
+ spin(text5) {
2751
3126
  if (this.spinner) {
2752
- this.spinner.text = text6 + " (ESC to cancel)";
3127
+ this.spinner.text = text5 + " (ESC to cancel)";
2753
3128
  } else {
2754
- this.spinner = ora(text6 + " (ESC to cancel)").start();
3129
+ this.spinner = ora(text5 + " (ESC to cancel)").start();
2755
3130
  }
2756
3131
  }
2757
3132
  stop() {
@@ -4410,570 +4785,42 @@ function getModeManager() {
4410
4785
 
4411
4786
  // src/core/plan-executor.ts
4412
4787
  import Anthropic3 from "@anthropic-ai/sdk";
4413
- var PlanExecutor = class {
4788
+
4789
+ // src/core/question-executor.ts
4790
+ import * as p2 from "@clack/prompts";
4791
+ import Anthropic4 from "@anthropic-ai/sdk";
4792
+
4793
+ // src/core/agent-executor.ts
4794
+ import ora2 from "ora";
4795
+
4796
+ // src/core/input-handler.ts
4797
+ import fs9 from "fs-extra";
4798
+ import path9 from "path";
4799
+
4800
+ // src/core/prd-parser.ts
4801
+ import Anthropic5 from "@anthropic-ai/sdk";
4802
+ var PRDParser = class {
4414
4803
  anthropic;
4415
4804
  memory;
4416
- scanner;
4417
- constructor(config, memory, scanner) {
4805
+ constructor(config, memory) {
4418
4806
  const apiKey = config.getAnthropicKey();
4419
4807
  if (!apiKey) throw new Error("API key not configured");
4420
- this.anthropic = new Anthropic3({ apiKey });
4808
+ this.anthropic = new Anthropic5({ apiKey });
4421
4809
  this.memory = memory;
4422
- this.scanner = scanner;
4423
4810
  }
4424
- async createPlan(request) {
4425
- const projectContext = await this.scanner.getContextString([]);
4426
- const prompt = `You are planning a coding task. DO NOT write any code yet - just create a plan.
4427
-
4428
- PROJECT CONTEXT:
4429
- ${projectContext}
4430
-
4431
- USER REQUEST:
4432
- ${request}
4433
-
4434
- Create a detailed plan. Respond with ONLY valid JSON:
4435
- {
4436
- "description": "Brief summary of what will be built",
4437
- "filesToCreate": ["src/path/file.ts", ...],
4438
- "filesToModify": ["src/existing/file.ts", ...],
4439
- "packagesToInstall": ["package-name", ...],
4440
- "commandsToRun": ["npm install", "npm run build", ...],
4441
- "estimatedTime": "2-3 minutes"
4442
- }
4443
-
4444
- Be specific about file paths. List ALL files that will be created or modified.`;
4445
- const response = await this.anthropic.messages.create({
4446
- model: "claude-sonnet-4-20250514",
4447
- max_tokens: 2048,
4448
- messages: [{ role: "user", content: prompt }]
4449
- });
4450
- const content = response.content[0]?.type === "text" ? response.content[0].text : "";
4451
- try {
4452
- const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
4453
- return JSON.parse(jsonStr);
4454
- } catch {
4455
- return {
4456
- description: "Unable to parse plan",
4457
- filesToCreate: [],
4458
- filesToModify: [],
4459
- packagesToInstall: [],
4460
- commandsToRun: [],
4461
- estimatedTime: "Unknown"
4462
- };
4811
+ // ============================================================================
4812
+ // PARSE PRD
4813
+ // ============================================================================
4814
+ async parse(prdContent) {
4815
+ const chunks = this.chunkContent(prdContent, 5e4);
4816
+ let analysis;
4817
+ if (chunks.length === 1) {
4818
+ analysis = await this.analyzeChunk(chunks[0], true);
4819
+ } else {
4820
+ analysis = await this.analyzeMultipleChunks(chunks);
4463
4821
  }
4464
- }
4465
- formatPlan(plan) {
4466
- let output = "\n";
4467
- output += colors.white(" \u{1F4CB} PLAN") + colors.muted(" (not executed yet)") + "\n\n";
4468
- output += colors.muted(" " + plan.description) + "\n\n";
4469
- if (plan.filesToCreate.length > 0) {
4470
- output += colors.white(" Files to create:\n");
4471
- for (const file of plan.filesToCreate) {
4472
- output += colors.success(` + ${file}`) + "\n";
4473
- }
4474
- output += "\n";
4475
- }
4476
- if (plan.filesToModify.length > 0) {
4477
- output += colors.white(" Files to modify:\n");
4478
- for (const file of plan.filesToModify) {
4479
- output += colors.warning(` ~ ${file}`) + "\n";
4480
- }
4481
- output += "\n";
4482
- }
4483
- if (plan.packagesToInstall.length > 0) {
4484
- output += colors.white(" Packages to install:\n");
4485
- for (const pkg of plan.packagesToInstall) {
4486
- output += colors.muted(` + ${pkg}`) + "\n";
4487
- }
4488
- output += "\n";
4489
- }
4490
- if (plan.commandsToRun.length > 0) {
4491
- output += colors.white(" Commands to run:\n");
4492
- for (const cmd of plan.commandsToRun) {
4493
- output += colors.muted(` $ ${cmd}`) + "\n";
4494
- }
4495
- output += "\n";
4496
- }
4497
- output += colors.muted(` Estimated time: ${plan.estimatedTime}`) + "\n";
4498
- return output;
4499
- }
4500
- };
4501
-
4502
- // src/core/question-executor.ts
4503
- import * as p2 from "@clack/prompts";
4504
- import Anthropic4 from "@anthropic-ai/sdk";
4505
- var QuestionExecutor = class {
4506
- anthropic;
4507
- memory;
4508
- scanner;
4509
- constructor(config, memory, scanner) {
4510
- const apiKey = config.getAnthropicKey();
4511
- if (!apiKey) throw new Error("API key not configured");
4512
- this.anthropic = new Anthropic4({ apiKey });
4513
- this.memory = memory;
4514
- this.scanner = scanner;
4515
- }
4516
- async generateQuestions(request) {
4517
- const projectContext = await this.scanner.getContextString([]);
4518
- const memoryContext = this.memory.getFullContext();
4519
- const prompt = `You are about to build something. Before starting, identify what clarifying questions would help you build exactly what the user wants.
4520
-
4521
- PROJECT CONTEXT:
4522
- ${projectContext}
4523
-
4524
- MEMORY (what you already know):
4525
- ${memoryContext}
4526
-
4527
- USER REQUEST:
4528
- ${request}
4529
-
4530
- Generate 2-4 clarifying questions. Only ask questions where the answer would significantly change what you build. Skip questions if the answer is obvious from context or memory.
4531
-
4532
- Respond with ONLY valid JSON:
4533
- {
4534
- "questions": [
4535
- {
4536
- "id": "auth_type",
4537
- "text": "What type of authentication do you need?",
4538
- "type": "select",
4539
- "options": [
4540
- { "value": "email", "label": "Email/password only" },
4541
- { "value": "oauth", "label": "OAuth (Google, GitHub)" },
4542
- { "value": "both", "label": "Both email and OAuth" }
4543
- ]
4544
- },
4545
- {
4546
- "id": "need_reset",
4547
- "text": "Include password reset flow?",
4548
- "type": "confirm",
4549
- "default": "yes"
4550
- }
4551
- ]
4552
- }
4553
-
4554
- Question types:
4555
- - "select": Multiple choice (provide options)
4556
- - "confirm": Yes/no question
4557
- - "text": Free text input
4558
-
4559
- Only ask ESSENTIAL questions. If you can make a reasonable assumption, do so.`;
4560
- const response = await this.anthropic.messages.create({
4561
- model: "claude-sonnet-4-20250514",
4562
- max_tokens: 2048,
4563
- messages: [{ role: "user", content: prompt }]
4564
- });
4565
- const content = response.content[0]?.type === "text" ? response.content[0].text : "";
4566
- try {
4567
- const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
4568
- const parsed = JSON.parse(jsonStr);
4569
- return parsed.questions || [];
4570
- } catch {
4571
- return [];
4572
- }
4573
- }
4574
- async askQuestions(questions) {
4575
- const answers = {};
4576
- if (questions.length === 0) {
4577
- console.log(colors.muted("\n No clarification needed - proceeding with build.\n"));
4578
- return { questions: [], answers: {} };
4579
- }
4580
- console.log("\n" + colors.secondary(" \u{1F914} A few questions first:") + "\n");
4581
- for (const question of questions) {
4582
- let answer;
4583
- switch (question.type) {
4584
- case "select":
4585
- answer = await p2.select({
4586
- message: question.text,
4587
- options: question.options?.map((opt) => ({
4588
- value: opt.value,
4589
- label: opt.label
4590
- })) || []
4591
- });
4592
- break;
4593
- case "confirm":
4594
- answer = await p2.confirm({
4595
- message: question.text,
4596
- initialValue: question.default === "yes"
4597
- });
4598
- break;
4599
- case "text":
4600
- default:
4601
- answer = await p2.text({
4602
- message: question.text,
4603
- placeholder: question.default
4604
- });
4605
- break;
4606
- }
4607
- if (p2.isCancel(answer)) {
4608
- return { questions, answers };
4609
- }
4610
- answers[question.id] = String(answer);
4611
- }
4612
- return { questions, answers };
4613
- }
4614
- formatAnswersForPrompt(result) {
4615
- if (Object.keys(result.answers).length === 0) {
4616
- return "";
4617
- }
4618
- let context = "\n\nUSER PREFERENCES (from clarifying questions):\n";
4619
- for (const question of result.questions) {
4620
- const answer = result.answers[question.id];
4621
- if (answer) {
4622
- context += `- ${question.text}: ${answer}
4623
- `;
4624
- }
4625
- }
4626
- return context;
4627
- }
4628
- showSummary(result) {
4629
- if (Object.keys(result.answers).length === 0) return;
4630
- console.log("\n" + colors.muted(" Got it! Building with:"));
4631
- for (const question of result.questions) {
4632
- const answer = result.answers[question.id];
4633
- if (answer) {
4634
- let displayAnswer = answer;
4635
- if (question.type === "select" && question.options) {
4636
- const option = question.options.find((o) => o.value === answer);
4637
- if (option) displayAnswer = option.label;
4638
- } else if (question.type === "confirm") {
4639
- displayAnswer = answer === "true" ? "Yes" : "No";
4640
- }
4641
- console.log(colors.muted(` \u2022 ${displayAnswer}`));
4642
- }
4643
- }
4644
- console.log("");
4645
- }
4646
- };
4647
-
4648
- // src/core/agent-executor.ts
4649
- import ora2 from "ora";
4650
- var AgentExecutor = class {
4651
- ai;
4652
- config;
4653
- memory;
4654
- scanner;
4655
- editor;
4656
- spinner = null;
4657
- paused = false;
4658
- cancelled = false;
4659
- constructor(config, ai, memory, scanner, editor) {
4660
- this.config = config;
4661
- this.ai = ai;
4662
- this.memory = memory;
4663
- this.scanner = scanner;
4664
- this.editor = editor;
4665
- }
4666
- // ============================================================================
4667
- // MAIN EXECUTION
4668
- // ============================================================================
4669
- async execute(request, options = {}) {
4670
- const startTime = Date.now();
4671
- this.paused = false;
4672
- this.cancelled = false;
4673
- const result = {
4674
- success: false,
4675
- filesCreated: [],
4676
- filesModified: [],
4677
- packagesInstalled: [],
4678
- commandsRun: [],
4679
- errors: [],
4680
- duration: 0
4681
- };
4682
- const checkCancelled = () => {
4683
- if (options.checkCancelled?.() || this.cancelled) {
4684
- return true;
4685
- }
4686
- return false;
4687
- };
4688
- console.log("");
4689
- console.log(colors.primary(" \u{1F916} AGENT MODE") + colors.muted(" - Running autonomously"));
4690
- console.log(colors.muted(" Press ESC to pause at any checkpoint"));
4691
- console.log("");
4692
- try {
4693
- const isComplex = this.isComplexRequest(request);
4694
- if (isComplex) {
4695
- return await this.executeParallel(request, options, checkCancelled);
4696
- }
4697
- const orchestrator = new Orchestrator(this.ai, this.editor, this.scanner, this.memory);
4698
- this.spin("Generating code...");
4699
- const genResult = await this.ai.generateStream(
4700
- request,
4701
- {
4702
- onToken: (token) => {
4703
- }
4704
- },
4705
- checkCancelled
4706
- );
4707
- if (checkCancelled()) {
4708
- this.stop();
4709
- return { ...result, cancelled: true, duration: Date.now() - startTime };
4710
- }
4711
- if (this.paused) {
4712
- this.stop();
4713
- return { ...result, paused: true, duration: Date.now() - startTime };
4714
- }
4715
- this.spin("Applying changes...");
4716
- this.editor.parseResponse(genResult.content);
4717
- const changes = this.editor.getPendingChanges();
4718
- for (const change of changes) {
4719
- if (checkCancelled()) {
4720
- this.stop();
4721
- return { ...result, cancelled: true, duration: Date.now() - startTime };
4722
- }
4723
- options.onFile?.(change.path, change.type === "create" ? "create" : "modify");
4724
- if (change.type === "create") {
4725
- result.filesCreated.push(change.path);
4726
- } else {
4727
- result.filesModified.push(change.path);
4728
- }
4729
- this.logStep(`${change.type === "create" ? "Created" : "Modified"} ${change.path}`);
4730
- }
4731
- await this.editor.apply();
4732
- const allContent = changes.map((c) => c.content || "").join("\n");
4733
- const packages = this.detectPackages(allContent);
4734
- if (packages.length > 0 && !checkCancelled()) {
4735
- this.spin("Installing packages...");
4736
- const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
4737
- const terminal = new Terminal2();
4738
- for (const pkg of packages) {
4739
- this.logStep(`Installing ${pkg}...`);
4740
- }
4741
- const installResult = await terminal.npmInstall(packages);
4742
- if (installResult.success) {
4743
- result.packagesInstalled = packages;
4744
- result.commandsRun.push(`npm install ${packages.join(" ")}`);
4745
- }
4746
- }
4747
- if (!checkCancelled()) {
4748
- this.spin("Type checking...");
4749
- const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
4750
- const terminal = new Terminal2();
4751
- const typeResult = await terminal.typecheck();
4752
- result.commandsRun.push("tsc --noEmit");
4753
- if (!typeResult.success) {
4754
- this.logStep("Fixing type errors...");
4755
- }
4756
- }
4757
- this.stop();
4758
- result.success = result.errors.length === 0;
4759
- result.duration = Date.now() - startTime;
4760
- this.showSummary(result);
4761
- for (const file of [...result.filesCreated, ...result.filesModified]) {
4762
- this.memory.addModifiedFile(file);
4763
- }
4764
- await this.memory.save();
4765
- return result;
4766
- } catch (error) {
4767
- this.stop();
4768
- result.errors.push(error instanceof Error ? error.message : "Unknown error");
4769
- result.duration = Date.now() - startTime;
4770
- return result;
4771
- }
4772
- }
4773
- // ============================================================================
4774
- // PARALLEL EXECUTION
4775
- // ============================================================================
4776
- async executeParallel(request, options, checkCancelled) {
4777
- const startTime = Date.now();
4778
- const result = {
4779
- success: false,
4780
- filesCreated: [],
4781
- filesModified: [],
4782
- packagesInstalled: [],
4783
- commandsRun: [],
4784
- errors: [],
4785
- duration: 0
4786
- };
4787
- console.log(colors.muted(" Using parallel agents for faster build..."));
4788
- console.log("");
4789
- const parallelSystem = new ParallelAgentSystem(
4790
- this.config,
4791
- this.memory,
4792
- this.scanner,
4793
- this.editor
4794
- );
4795
- const parallelResult = await parallelSystem.build(
4796
- request,
4797
- {
4798
- onTaskPlan: (tasks) => {
4799
- console.log(colors.muted(` \u{1F4CB} ${tasks.length} tasks planned`));
4800
- },
4801
- onAgentStart: (taskId, name) => {
4802
- console.log(colors.muted(` \u25CF [${name}] Starting...`));
4803
- },
4804
- onAgentComplete: (taskId, agentResult) => {
4805
- if (agentResult.success) {
4806
- console.log(colors.success(` \u2713 [${taskId}] Complete (${agentResult.files.length} files)`));
4807
- } else {
4808
- console.log(colors.error(` \u2717 [${taskId}] Failed`));
4809
- }
4810
- },
4811
- onMergeStart: () => {
4812
- console.log(colors.muted(" \u{1F504} Merging results..."));
4813
- },
4814
- onMergeComplete: (conflicts) => {
4815
- if (conflicts.length === 0) {
4816
- console.log(colors.success(" \u2713 No conflicts"));
4817
- }
4818
- }
4819
- },
4820
- checkCancelled
4821
- );
4822
- if (parallelResult.cancelled) {
4823
- return { ...result, cancelled: true, duration: Date.now() - startTime };
4824
- }
4825
- const changes = this.editor.getPendingChanges();
4826
- await this.editor.apply();
4827
- for (const change of changes) {
4828
- if (change.type === "create") {
4829
- result.filesCreated.push(change.path);
4830
- } else {
4831
- result.filesModified.push(change.path);
4832
- }
4833
- }
4834
- result.success = parallelResult.success;
4835
- result.duration = Date.now() - startTime;
4836
- this.showSummary(result);
4837
- return result;
4838
- }
4839
- // ============================================================================
4840
- // HELPERS
4841
- // ============================================================================
4842
- isComplexRequest(request) {
4843
- const lower = request.toLowerCase();
4844
- const complexIndicators = [
4845
- " and ",
4846
- " with ",
4847
- ", ",
4848
- " + ",
4849
- "full",
4850
- "complete",
4851
- "entire",
4852
- "saas",
4853
- "dashboard",
4854
- "auth",
4855
- "billing",
4856
- "payment"
4857
- ];
4858
- let score = 0;
4859
- for (const indicator of complexIndicators) {
4860
- if (lower.includes(indicator)) score++;
4861
- }
4862
- return score >= 3;
4863
- }
4864
- detectPackages(content) {
4865
- const packages = /* @__PURE__ */ new Set();
4866
- const importRegex = /import\s+.*?from\s+['"]([^'"./][^'"]*)['"]/g;
4867
- let match;
4868
- while ((match = importRegex.exec(content)) !== null) {
4869
- let pkg = match[1];
4870
- if (pkg.startsWith("@")) {
4871
- pkg = pkg.split("/").slice(0, 2).join("/");
4872
- } else {
4873
- pkg = pkg.split("/")[0];
4874
- }
4875
- const builtins = ["fs", "path", "http", "https", "crypto", "stream", "events", "os", "util", "url"];
4876
- if (!builtins.includes(pkg)) {
4877
- packages.add(pkg);
4878
- }
4879
- }
4880
- return Array.from(packages);
4881
- }
4882
- spin(text6) {
4883
- if (this.spinner) {
4884
- this.spinner.text = text6;
4885
- } else {
4886
- this.spinner = ora2(text6).start();
4887
- }
4888
- }
4889
- stop() {
4890
- if (this.spinner) {
4891
- this.spinner.stop();
4892
- this.spinner = null;
4893
- }
4894
- }
4895
- logStep(message) {
4896
- this.stop();
4897
- console.log(colors.muted(` \u25CF ${message}`));
4898
- }
4899
- showSummary(result) {
4900
- console.log("");
4901
- console.log(" " + "\u2550".repeat(50));
4902
- if (result.success) {
4903
- console.log(colors.success(" \u{1F389} AUTONOMOUS BUILD COMPLETE"));
4904
- } else if (result.cancelled) {
4905
- console.log(colors.warning(" \u26A0\uFE0F BUILD CANCELLED"));
4906
- } else if (result.paused) {
4907
- console.log(colors.warning(" \u23F8\uFE0F BUILD PAUSED"));
4908
- } else {
4909
- console.log(colors.error(" \u274C BUILD FAILED"));
4910
- }
4911
- console.log("");
4912
- if (result.filesCreated.length > 0) {
4913
- console.log(colors.muted(` Created: ${result.filesCreated.length} files`));
4914
- }
4915
- if (result.filesModified.length > 0) {
4916
- console.log(colors.muted(` Modified: ${result.filesModified.length} files`));
4917
- }
4918
- if (result.packagesInstalled.length > 0) {
4919
- console.log(colors.muted(` Installed: ${result.packagesInstalled.join(", ")}`));
4920
- }
4921
- const seconds = (result.duration / 1e3).toFixed(1);
4922
- console.log(colors.muted(` Time: ${seconds}s`));
4923
- if (result.errors.length > 0) {
4924
- console.log("");
4925
- console.log(colors.error(" Errors:"));
4926
- for (const error of result.errors) {
4927
- console.log(colors.error(` \u2022 ${error}`));
4928
- }
4929
- }
4930
- console.log("");
4931
- console.log(colors.muted(" All changes saved. Use /undo to rollback."));
4932
- console.log(" " + "\u2550".repeat(50));
4933
- console.log("");
4934
- }
4935
- // ============================================================================
4936
- // CONTROL
4937
- // ============================================================================
4938
- pause() {
4939
- this.paused = true;
4940
- }
4941
- cancel() {
4942
- this.cancelled = true;
4943
- }
4944
- resume() {
4945
- this.paused = false;
4946
- }
4947
- };
4948
-
4949
- // src/core/input-handler.ts
4950
- import fs9 from "fs-extra";
4951
- import path9 from "path";
4952
-
4953
- // src/core/prd-parser.ts
4954
- import Anthropic5 from "@anthropic-ai/sdk";
4955
- var PRDParser = class {
4956
- anthropic;
4957
- memory;
4958
- constructor(config, memory) {
4959
- const apiKey = config.getAnthropicKey();
4960
- if (!apiKey) throw new Error("API key not configured");
4961
- this.anthropic = new Anthropic5({ apiKey });
4962
- this.memory = memory;
4963
- }
4964
- // ============================================================================
4965
- // PARSE PRD
4966
- // ============================================================================
4967
- async parse(prdContent) {
4968
- const chunks = this.chunkContent(prdContent, 5e4);
4969
- let analysis;
4970
- if (chunks.length === 1) {
4971
- analysis = await this.analyzeChunk(chunks[0], true);
4972
- } else {
4973
- analysis = await this.analyzeMultipleChunks(chunks);
4974
- }
4975
- analysis.phases = this.createPhases(analysis.features);
4976
- return analysis;
4822
+ analysis.phases = this.createPhases(analysis.features);
4823
+ return analysis;
4977
4824
  }
4978
4825
  chunkContent(content, maxSize) {
4979
4826
  if (content.length <= maxSize) {
@@ -5188,7 +5035,7 @@ ${chunks[i]}`,
5188
5035
  projectName: analysis.projectName,
5189
5036
  totalFeatures: analysis.features.length,
5190
5037
  completedFeatures: analysis.features.filter((f) => f.status === "complete").length,
5191
- currentPhase: analysis.phases.find((p9) => p9.status === "in_progress")?.id || analysis.phases[0]?.id || "",
5038
+ currentPhase: analysis.phases.find((p8) => p8.status === "in_progress")?.id || analysis.phases[0]?.id || "",
5192
5039
  currentFeature: analysis.features.find((f) => f.status === "in_progress")?.id || "",
5193
5040
  features: analysis.features.map((f) => ({ id: f.id, status: f.status })),
5194
5041
  startedAt: Date.now(),
@@ -5394,42 +5241,197 @@ Provide the complete component code.`;
5394
5241
  suggestedApproach: "URL could not be accessed"
5395
5242
  };
5396
5243
  }
5397
- const prompt = `Analyze this website HTML to understand its structure for recreation.
5244
+ const prompt = `Analyze this website HTML to understand its structure for recreation.
5245
+
5246
+ URL: ${url}
5247
+
5248
+ HTML (truncated):
5249
+ ${html.slice(0, 3e4)}
5250
+
5251
+ Respond with ONLY valid JSON:
5252
+ {
5253
+ "title": "Website/page title",
5254
+ "description": "What this page does",
5255
+ "components": ["Key UI components", "Header", "Hero section", "Pricing cards", etc],
5256
+ "features": ["Interactive features", "Animations", "Forms", etc],
5257
+ "techStack": ["Detected technologies"],
5258
+ "suggestedApproach": "How to recreate this step by step"
5259
+ }`;
5260
+ const response = await this.anthropic.messages.create({
5261
+ model: "claude-sonnet-4-20250514",
5262
+ max_tokens: 2048,
5263
+ messages: [{ role: "user", content: prompt }]
5264
+ });
5265
+ const content = response.content[0]?.type === "text" ? response.content[0].text : "";
5266
+ try {
5267
+ const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
5268
+ return JSON.parse(jsonStr);
5269
+ } catch {
5270
+ return {
5271
+ title: "Unable to analyze",
5272
+ description: "",
5273
+ components: [],
5274
+ features: [],
5275
+ techStack: [],
5276
+ suggestedApproach: "Manual analysis needed"
5277
+ };
5278
+ }
5279
+ }
5280
+ // ============================================================================
5281
+ // WEBSITE REVIEW/AUDIT
5282
+ // ============================================================================
5283
+ async reviewWebsite(url) {
5284
+ const html = await this.fetchURL(url);
5285
+ if (!html) {
5286
+ return {
5287
+ url,
5288
+ score: 0,
5289
+ issues: [{ category: "error", severity: "error", message: "Unable to fetch URL" }],
5290
+ suggestions: [],
5291
+ seoAnalysis: null,
5292
+ accessibilityAnalysis: null,
5293
+ performanceHints: []
5294
+ };
5295
+ }
5296
+ const prompt = `Review this website HTML for issues, best practices, SEO, and accessibility.
5398
5297
 
5399
5298
  URL: ${url}
5400
5299
 
5401
- HTML (truncated):
5402
- ${html.slice(0, 3e4)}
5300
+ HTML:
5301
+ ${html.slice(0, 4e4)}
5403
5302
 
5404
- Respond with ONLY valid JSON:
5303
+ Analyze and respond with ONLY valid JSON:
5405
5304
  {
5406
- "title": "Website/page title",
5407
- "description": "What this page does",
5408
- "components": ["Key UI components", "Header", "Hero section", "Pricing cards", etc],
5409
- "features": ["Interactive features", "Animations", "Forms", etc],
5410
- "techStack": ["Detected technologies"],
5411
- "suggestedApproach": "How to recreate this step by step"
5305
+ "score": 85,
5306
+ "issues": [
5307
+ {"category": "seo", "severity": "warning", "message": "Missing meta description", "fix": "Add <meta name='description'>"},
5308
+ {"category": "accessibility", "severity": "error", "message": "Images missing alt text", "fix": "Add alt attributes to all images"},
5309
+ {"category": "performance", "severity": "warning", "message": "Large inline styles", "fix": "Move to external CSS"},
5310
+ {"category": "security", "severity": "error", "message": "Mixed content (http in https)", "fix": "Use https for all resources"},
5311
+ {"category": "html", "severity": "warning", "message": "Invalid HTML structure", "fix": "Fix nesting issues"}
5312
+ ],
5313
+ "suggestions": [
5314
+ "Add Open Graph tags for social sharing",
5315
+ "Implement lazy loading for images",
5316
+ "Add structured data markup"
5317
+ ],
5318
+ "seoAnalysis": {
5319
+ "title": "Page title here",
5320
+ "titleLength": 55,
5321
+ "hasMetaDescription": true,
5322
+ "metaDescriptionLength": 150,
5323
+ "hasCanonical": false,
5324
+ "hasOpenGraph": false,
5325
+ "headingStructure": "H1 > H2 > H3 (good)",
5326
+ "keywordDensity": "moderate"
5327
+ },
5328
+ "accessibilityAnalysis": {
5329
+ "hasSkipLink": false,
5330
+ "imagesWithAlt": 8,
5331
+ "imagesWithoutAlt": 3,
5332
+ "formLabels": "all labeled",
5333
+ "colorContrast": "needs review",
5334
+ "keyboardNavigation": "partial"
5335
+ },
5336
+ "performanceHints": [
5337
+ "Compress images",
5338
+ "Minify CSS/JS",
5339
+ "Enable caching headers"
5340
+ ]
5412
5341
  }`;
5413
5342
  const response = await this.anthropic.messages.create({
5414
5343
  model: "claude-sonnet-4-20250514",
5415
- max_tokens: 2048,
5344
+ max_tokens: 4096,
5416
5345
  messages: [{ role: "user", content: prompt }]
5417
5346
  });
5418
5347
  const content = response.content[0]?.type === "text" ? response.content[0].text : "";
5419
5348
  try {
5420
5349
  const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
5421
- return JSON.parse(jsonStr);
5350
+ return { url, ...JSON.parse(jsonStr) };
5422
5351
  } catch {
5423
5352
  return {
5424
- title: "Unable to analyze",
5425
- description: "",
5426
- components: [],
5427
- features: [],
5428
- techStack: [],
5429
- suggestedApproach: "Manual analysis needed"
5353
+ url,
5354
+ score: 0,
5355
+ issues: [{ category: "error", severity: "error", message: "Unable to analyze website" }],
5356
+ suggestions: [],
5357
+ seoAnalysis: null,
5358
+ accessibilityAnalysis: null,
5359
+ performanceHints: []
5430
5360
  };
5431
5361
  }
5432
5362
  }
5363
+ formatWebsiteReview(review) {
5364
+ let output = "\n";
5365
+ output += colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\n");
5366
+ output += colors.muted(" \u2502") + colors.white(" \u{1F310} Website Review ") + colors.muted("\u2502\n");
5367
+ output += colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F\n\n");
5368
+ output += colors.muted(` URL: ${review.url}
5369
+
5370
+ `);
5371
+ const scoreColor = review.score >= 80 ? colors.success : review.score >= 60 ? colors.warning : colors.error;
5372
+ output += colors.white(" Score: ") + scoreColor(`${review.score}/100
5373
+
5374
+ `);
5375
+ if (review.issues && review.issues.length > 0) {
5376
+ output += colors.white(" Issues Found:\n");
5377
+ const categories = ["error", "security", "accessibility", "seo", "performance", "html"];
5378
+ for (const cat of categories) {
5379
+ const catIssues = review.issues.filter((i) => i.category === cat || i.severity === cat);
5380
+ if (catIssues.length > 0) {
5381
+ for (const issue of catIssues.slice(0, 3)) {
5382
+ const icon = issue.severity === "error" ? colors.error("\u2717") : colors.warning("\u26A0");
5383
+ output += ` ${icon} ${colors.muted(`[${issue.category}]`)} ${issue.message}
5384
+ `;
5385
+ if (issue.fix) {
5386
+ output += colors.dim(` \u2192 ${issue.fix}
5387
+ `);
5388
+ }
5389
+ }
5390
+ }
5391
+ }
5392
+ output += "\n";
5393
+ }
5394
+ if (review.seoAnalysis) {
5395
+ output += colors.white(" SEO:\n");
5396
+ const seo = review.seoAnalysis;
5397
+ output += colors.muted(` Title: ${seo.title || "Missing"} (${seo.titleLength || 0} chars)
5398
+ `);
5399
+ output += colors.muted(` Meta Description: ${seo.hasMetaDescription ? "\u2713" : "\u2717"}
5400
+ `);
5401
+ output += colors.muted(` Open Graph: ${seo.hasOpenGraph ? "\u2713" : "\u2717"}
5402
+ `);
5403
+ output += colors.muted(` Headings: ${seo.headingStructure || "Unknown"}
5404
+
5405
+ `);
5406
+ }
5407
+ if (review.accessibilityAnalysis) {
5408
+ output += colors.white(" Accessibility:\n");
5409
+ const a11y = review.accessibilityAnalysis;
5410
+ output += colors.muted(` Images with alt: ${a11y.imagesWithAlt || 0}/${(a11y.imagesWithAlt || 0) + (a11y.imagesWithoutAlt || 0)}
5411
+ `);
5412
+ output += colors.muted(` Skip link: ${a11y.hasSkipLink ? "\u2713" : "\u2717"}
5413
+ `);
5414
+ output += colors.muted(` Form labels: ${a11y.formLabels || "Unknown"}
5415
+
5416
+ `);
5417
+ }
5418
+ if (review.suggestions && review.suggestions.length > 0) {
5419
+ output += colors.white(" Suggestions:\n");
5420
+ for (const suggestion of review.suggestions.slice(0, 5)) {
5421
+ output += colors.dim(` \u2022 ${suggestion}
5422
+ `);
5423
+ }
5424
+ output += "\n";
5425
+ }
5426
+ if (review.performanceHints && review.performanceHints.length > 0) {
5427
+ output += colors.white(" Performance Tips:\n");
5428
+ for (const hint of review.performanceHints.slice(0, 3)) {
5429
+ output += colors.dim(` \u2022 ${hint}
5430
+ `);
5431
+ }
5432
+ }
5433
+ return output;
5434
+ }
5433
5435
  async buildFromURL(url, specificSection) {
5434
5436
  const html = await this.fetchURL(url);
5435
5437
  if (!html) {
@@ -6684,8 +6686,8 @@ var SmartGitManager = class {
6684
6686
  const pr = await this.createPR(phaseBranch, baseBranch);
6685
6687
  return pr;
6686
6688
  }
6687
- slugify(text6) {
6688
- return text6.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
6689
+ slugify(text5) {
6690
+ return text5.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
6689
6691
  }
6690
6692
  // ============================================================================
6691
6693
  // SMART COMMITS
@@ -6964,7 +6966,7 @@ var ProjectBuilder = class {
6964
6966
  totalDuration: 0
6965
6967
  };
6966
6968
  try {
6967
- const phaseNames = spec.buildPhases.map((p9) => p9.name.replace(/Phase \d+:\s*/i, ""));
6969
+ const phaseNames = spec.buildPhases.map((p8) => p8.name.replace(/Phase \d+:\s*/i, ""));
6968
6970
  const strategy = await this.git.createProjectStrategy(spec.name, phaseNames);
6969
6971
  console.log(this.git.formatStrategy(strategy));
6970
6972
  const proceed = await p4.confirm({
@@ -7671,7 +7673,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7671
7673
  return this.patterns;
7672
7674
  }
7673
7675
  enablePattern(id) {
7674
- const pattern = this.patterns.find((p9) => p9.id === id);
7676
+ const pattern = this.patterns.find((p8) => p8.id === id);
7675
7677
  if (pattern) {
7676
7678
  pattern.enabled = true;
7677
7679
  return true;
@@ -7679,7 +7681,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7679
7681
  return false;
7680
7682
  }
7681
7683
  disablePattern(id) {
7682
- const pattern = this.patterns.find((p9) => p9.id === id);
7684
+ const pattern = this.patterns.find((p8) => p8.id === id);
7683
7685
  if (pattern) {
7684
7686
  pattern.enabled = false;
7685
7687
  return true;
@@ -7738,8 +7740,8 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7738
7740
  formatPatterns() {
7739
7741
  let output = "\n";
7740
7742
  output += colors.white(" \u{1F4CB} Pattern Rules") + "\n\n";
7741
- const enabled = this.patterns.filter((p9) => p9.enabled);
7742
- const disabled = this.patterns.filter((p9) => !p9.enabled);
7743
+ const enabled = this.patterns.filter((p8) => p8.enabled);
7744
+ const disabled = this.patterns.filter((p8) => !p8.enabled);
7743
7745
  output += colors.success(" ENABLED") + "\n";
7744
7746
  for (const pattern of enabled) {
7745
7747
  const severityIcon = pattern.severity === "critical" ? "\u{1F534}" : pattern.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
@@ -7764,7 +7766,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7764
7766
  return grouped;
7765
7767
  }
7766
7768
  getPatternName(id) {
7767
- const pattern = this.patterns.find((p9) => p9.id === id);
7769
+ const pattern = this.patterns.find((p8) => p8.id === id);
7768
7770
  return pattern?.name || id;
7769
7771
  }
7770
7772
  // ============================================================================
@@ -7970,7 +7972,7 @@ var SmartSuggestions = class {
7970
7972
  /platform\s*(that|which|for)/,
7971
7973
  /saas\s*(for|that|which)/
7972
7974
  ];
7973
- return ideaPatterns.some((p9) => p9.test(lower));
7975
+ return ideaPatterns.some((p8) => p8.test(lower));
7974
7976
  }
7975
7977
  looksLikeComplexRequest(input) {
7976
7978
  const lower = input.toLowerCase();
@@ -8000,7 +8002,7 @@ var SmartSuggestions = class {
8000
8002
  /clean\s*up/,
8001
8003
  /improve\s*(the\s*)?(code|quality)/
8002
8004
  ];
8003
- return patterns.some((p9) => p9.test(lower));
8005
+ return patterns.some((p8) => p8.test(lower));
8004
8006
  }
8005
8007
  // ============================================================================
8006
8008
  // DISPLAY
@@ -8863,7 +8865,7 @@ ${issues.map((i) => ` \u2022 ${i}`).join("\n")}` : "\n\nEverything looks good!"
8863
8865
  /^(ugh|argh|damn|shit|fuck)/i
8864
8866
  // Frustration words
8865
8867
  ];
8866
- return frustrationPatterns.some((p9) => p9.test(input));
8868
+ return frustrationPatterns.some((p8) => p8.test(input));
8867
8869
  }
8868
8870
  // ============================================================================
8869
8871
  // RATE LIMITING (Don't be annoying)
@@ -9125,8 +9127,8 @@ ${chunk.content.slice(0, 2e3)}`
9125
9127
  chunk.embedding = this.simpleEmbed(chunk.content + " " + chunk.summary);
9126
9128
  }
9127
9129
  }
9128
- simpleEmbed(text6) {
9129
- const words = text6.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
9130
+ simpleEmbed(text5) {
9131
+ const words = text5.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
9130
9132
  const embedding = new Array(256).fill(0);
9131
9133
  for (const word of words) {
9132
9134
  const hash = this.hashString(word);
@@ -9587,7 +9589,7 @@ ${featureList}`
9587
9589
  "Redux": ["@reduxjs/toolkit", "redux"]
9588
9590
  };
9589
9591
  for (const [name, packages] of Object.entries(categories)) {
9590
- if (packages.some((p9) => deps.has(p9))) {
9592
+ if (packages.some((p8) => deps.has(p8))) {
9591
9593
  stack.push(name);
9592
9594
  }
9593
9595
  }
@@ -9719,279 +9721,11 @@ ${featureList}`
9719
9721
  }
9720
9722
  };
9721
9723
 
9722
- // src/core/onboarding.ts
9723
- import * as p6 from "@clack/prompts";
9724
- import fs16 from "fs-extra";
9725
- import path16 from "path";
9726
- function showWelcomeScreen() {
9727
- console.clear();
9728
- console.log("");
9729
- console.log(colors.primary(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
9730
- console.log(colors.primary(" \u2502 \u2502"));
9731
- console.log(colors.primary(" \u2502") + colors.white(" Welcome to CodeBakers! ") + colors.primary("\u2502"));
9732
- console.log(colors.primary(" \u2502") + colors.muted(" AI Dev Team That Follows The Rules ") + colors.primary("\u2502"));
9733
- console.log(colors.primary(" \u2502 \u2502"));
9734
- console.log(colors.primary(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
9735
- console.log("");
9736
- console.log(colors.white(" Let's get you set up in 2 quick steps:"));
9737
- console.log("");
9738
- console.log(colors.muted(" 1. Connect your account (or use API key)"));
9739
- console.log(colors.muted(" 2. Initialize your project"));
9740
- console.log("");
9741
- console.log(colors.dim(" Press ESC at any time to go back"));
9742
- console.log("");
9743
- }
9744
- async function showAuthScreen() {
9745
- console.clear();
9746
- console.log("");
9747
- console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
9748
- console.log(colors.white(" \u2502") + colors.primary(" Step 1 of 2: ") + colors.white("Connect Your Account ") + colors.white("\u2502"));
9749
- console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
9750
- console.log("");
9751
- const choice = await p6.select({
9752
- message: "How would you like to connect?",
9753
- options: [
9754
- {
9755
- value: "browser",
9756
- label: "\u{1F310} Login with browser",
9757
- hint: "Google or GitHub - recommended"
9758
- },
9759
- {
9760
- value: "code",
9761
- label: "\u{1F511} Enter admin/beta code",
9762
- hint: "If you have a special code"
9763
- },
9764
- {
9765
- value: "apikey",
9766
- label: "\u{1F527} Use my own API key",
9767
- hint: "No account needed, limited features"
9768
- },
9769
- {
9770
- value: "skip",
9771
- label: "\u23ED\uFE0F Skip for now",
9772
- hint: "You can login later with /login"
9773
- }
9774
- ]
9775
- });
9776
- if (p6.isCancel(choice)) {
9777
- return "back";
9778
- }
9779
- return choice;
9780
- }
9781
- async function showApiKeyScreen(config) {
9782
- console.clear();
9783
- console.log("");
9784
- console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
9785
- console.log(colors.white(" \u2502") + colors.primary(" Step 1 of 2: ") + colors.white("Enter Your API Key ") + colors.white("\u2502"));
9786
- console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
9787
- console.log("");
9788
- console.log(colors.muted(" Get your API key from: https://console.anthropic.com/"));
9789
- console.log("");
9790
- const apiKey = await p6.text({
9791
- message: "Anthropic API Key:",
9792
- placeholder: "sk-ant-api...",
9793
- validate: (value) => {
9794
- if (!value) return "API key is required";
9795
- if (!value.startsWith("sk-ant-")) return "API key should start with sk-ant-";
9796
- return void 0;
9797
- }
9798
- });
9799
- if (p6.isCancel(apiKey)) {
9800
- return "back";
9801
- }
9802
- config.setAnthropicKey(apiKey);
9803
- console.log("");
9804
- console.log(colors.success(" \u2713 API key saved"));
9805
- await sleep(500);
9806
- return "done";
9807
- }
9808
- async function showProjectScreen(memory) {
9809
- console.clear();
9810
- console.log("");
9811
- console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
9812
- console.log(colors.white(" \u2502") + colors.primary(" Step 2 of 2: ") + colors.white("Initialize Your Project ") + colors.white("\u2502"));
9813
- console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
9814
- console.log("");
9815
- const cwd = process.cwd();
9816
- const dirName = path16.basename(cwd);
9817
- const hasPackageJson = await fs16.pathExists(path16.join(cwd, "package.json"));
9818
- if (hasPackageJson) {
9819
- console.log(colors.muted(` \u{1F4C1} Detected project: ${dirName}`));
9820
- console.log("");
9821
- } else {
9822
- console.log(colors.muted(` \u{1F4C1} Current folder: ${dirName}`));
9823
- console.log("");
9824
- }
9825
- const action = await p6.select({
9826
- message: "What would you like to do?",
9827
- options: [
9828
- {
9829
- value: "init",
9830
- label: "\u2728 Initialize this folder as a CB project",
9831
- hint: "CB will remember context about this project"
9832
- },
9833
- {
9834
- value: "skip",
9835
- label: "\u23ED\uFE0F Skip for now",
9836
- hint: "You can initialize later with /init"
9837
- }
9838
- ]
9839
- });
9840
- if (p6.isCancel(action)) {
9841
- return "back";
9842
- }
9843
- if (action === "skip") {
9844
- return "skip";
9845
- }
9846
- const projectName = await p6.text({
9847
- message: "Project name:",
9848
- initialValue: dirName,
9849
- placeholder: dirName
9850
- });
9851
- if (p6.isCancel(projectName)) {
9852
- return "back";
9853
- }
9854
- const description = await p6.text({
9855
- message: "Brief description (optional):",
9856
- placeholder: "A web app that..."
9857
- });
9858
- if (p6.isCancel(description)) {
9859
- return "back";
9860
- }
9861
- await memory.init();
9862
- memory.setIdentity(projectName, description || "");
9863
- console.log("");
9864
- console.log(colors.success(` \u2713 Project "${projectName}" initialized`));
9865
- console.log(colors.muted(" CB will now remember context about this project"));
9866
- await sleep(500);
9867
- return "done";
9868
- }
9869
- function showCompleteScreen() {
9870
- console.clear();
9871
- console.log("");
9872
- console.log(colors.primary(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
9873
- console.log(colors.primary(" \u2502") + colors.white(" ") + colors.primary("\u2502"));
9874
- console.log(colors.primary(" \u2502") + colors.success(" \u2713 You're all set! ") + colors.primary("\u2502"));
9875
- console.log(colors.primary(" \u2502") + colors.white(" ") + colors.primary("\u2502"));
9876
- console.log(colors.primary(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
9877
- console.log("");
9878
- console.log(colors.white(" Quick start:"));
9879
- console.log("");
9880
- console.log(colors.muted(" Just type what you want:"));
9881
- console.log(colors.white(' "Add a login page"'));
9882
- console.log(colors.white(' "Fix the bug in navbar"'));
9883
- console.log(colors.white(' "Create an API for users"'));
9884
- console.log("");
9885
- console.log(colors.muted(" Or use commands:"));
9886
- console.log(colors.dim(" /help ") + colors.muted("Show all commands"));
9887
- console.log(colors.dim(" /audit ") + colors.muted("Check code quality"));
9888
- console.log(colors.dim(" /spec ") + colors.muted("Generate a product spec"));
9889
- console.log("");
9890
- }
9891
- async function runOnboarding(config, memory) {
9892
- let currentStep = "welcome";
9893
- const hasApiKey = config.isConfigured();
9894
- const hasAuth = auth.isLoggedIn();
9895
- const hasProject = memory.getIdentity().name !== "";
9896
- if (hasAuth || hasApiKey) {
9897
- if (hasProject) {
9898
- return true;
9899
- }
9900
- currentStep = "project";
9901
- }
9902
- while (true) {
9903
- switch (currentStep) {
9904
- case "welcome": {
9905
- showWelcomeScreen();
9906
- const proceed = await p6.confirm({
9907
- message: "Ready to begin?",
9908
- initialValue: true
9909
- });
9910
- if (p6.isCancel(proceed) || !proceed) {
9911
- return false;
9912
- }
9913
- currentStep = "auth";
9914
- break;
9915
- }
9916
- case "auth": {
9917
- const authChoice = await showAuthScreen();
9918
- if (authChoice === "back") {
9919
- currentStep = "welcome";
9920
- break;
9921
- }
9922
- if (authChoice === "skip") {
9923
- if (!config.isConfigured()) {
9924
- console.log("");
9925
- console.log(colors.warning(" \u26A0 You need either an account or API key to use CB"));
9926
- console.log("");
9927
- await sleep(1500);
9928
- break;
9929
- }
9930
- currentStep = "project";
9931
- break;
9932
- }
9933
- if (authChoice === "apikey") {
9934
- currentStep = "api_key";
9935
- break;
9936
- }
9937
- if (authChoice === "browser" || authChoice === "code") {
9938
- const success = authChoice === "browser" ? await auth.loginWithBrowser() : await auth.loginWithCode();
9939
- if (success) {
9940
- currentStep = "project";
9941
- }
9942
- break;
9943
- }
9944
- break;
9945
- }
9946
- case "api_key": {
9947
- const result = await showApiKeyScreen(config);
9948
- if (result === "back") {
9949
- currentStep = "auth";
9950
- break;
9951
- }
9952
- currentStep = "project";
9953
- break;
9954
- }
9955
- case "project": {
9956
- const result = await showProjectScreen(memory);
9957
- if (result === "back") {
9958
- if (!config.isConfigured() && !auth.isLoggedIn()) {
9959
- currentStep = "auth";
9960
- }
9961
- break;
9962
- }
9963
- currentStep = "complete";
9964
- break;
9965
- }
9966
- case "complete": {
9967
- showCompleteScreen();
9968
- const ready = await p6.confirm({
9969
- message: "Start using CodeBakers?",
9970
- initialValue: true
9971
- });
9972
- if (p6.isCancel(ready)) {
9973
- currentStep = "project";
9974
- break;
9975
- }
9976
- return true;
9977
- }
9978
- }
9979
- }
9980
- }
9981
- function needsOnboarding(config, memory) {
9982
- const hasApiKey = config.isConfigured();
9983
- const hasAuth = auth.isLoggedIn();
9984
- return !hasApiKey && !hasAuth;
9985
- }
9986
- function sleep(ms) {
9987
- return new Promise((resolve) => setTimeout(resolve, ms));
9988
- }
9989
-
9990
9724
  // src/utils/folder-picker.ts
9991
9725
  import { execSync } from "child_process";
9992
- import path17 from "path";
9726
+ import path16 from "path";
9993
9727
  import os2 from "os";
9994
- import * as p7 from "@clack/prompts";
9728
+ import * as p6 from "@clack/prompts";
9995
9729
  async function pickFolder(message = "Select a folder") {
9996
9730
  const platform = os2.platform();
9997
9731
  try {
@@ -10084,24 +9818,24 @@ async function manualFolderEntry(message) {
10084
9818
  const commonPaths = [
10085
9819
  { label: "\u{1F4C1} Current folder", value: process.cwd() },
10086
9820
  { label: "\u{1F3E0} Home", value: homeDir },
10087
- { label: "\u{1F4C2} Desktop", value: path17.join(homeDir, "Desktop") },
10088
- { label: "\u{1F4C2} Documents", value: path17.join(homeDir, "Documents") },
10089
- { label: "\u{1F4C2} Downloads", value: path17.join(homeDir, "Downloads") },
9821
+ { label: "\u{1F4C2} Desktop", value: path16.join(homeDir, "Desktop") },
9822
+ { label: "\u{1F4C2} Documents", value: path16.join(homeDir, "Documents") },
9823
+ { label: "\u{1F4C2} Downloads", value: path16.join(homeDir, "Downloads") },
10090
9824
  { label: "\u270F\uFE0F Type custom path", value: "__custom__" }
10091
9825
  ];
10092
- const choice = await p7.select({
9826
+ const choice = await p6.select({
10093
9827
  message,
10094
- options: commonPaths.map((p9) => ({
10095
- value: p9.value,
10096
- label: p9.label,
10097
- hint: p9.value !== "__custom__" ? p9.value : void 0
9828
+ options: commonPaths.map((p8) => ({
9829
+ value: p8.value,
9830
+ label: p8.label,
9831
+ hint: p8.value !== "__custom__" ? p8.value : void 0
10098
9832
  }))
10099
9833
  });
10100
- if (p7.isCancel(choice)) {
9834
+ if (p6.isCancel(choice)) {
10101
9835
  return null;
10102
9836
  }
10103
9837
  if (choice === "__custom__") {
10104
- const customPath = await p7.text({
9838
+ const customPath = await p6.text({
10105
9839
  message: "Enter folder path:",
10106
9840
  placeholder: "/path/to/folder",
10107
9841
  validate: (value) => {
@@ -10109,7 +9843,7 @@ async function manualFolderEntry(message) {
10109
9843
  return void 0;
10110
9844
  }
10111
9845
  });
10112
- if (p7.isCancel(customPath)) {
9846
+ if (p6.isCancel(customPath)) {
10113
9847
  return null;
10114
9848
  }
10115
9849
  let cleanPath = customPath.trim();
@@ -10122,8 +9856,8 @@ async function manualFolderEntry(message) {
10122
9856
 
10123
9857
  // src/services/git.ts
10124
9858
  import simpleGit from "simple-git";
10125
- import fs17 from "fs-extra";
10126
- import path18 from "path";
9859
+ import fs16 from "fs-extra";
9860
+ import path17 from "path";
10127
9861
  var GitService = class {
10128
9862
  git;
10129
9863
  projectPath;
@@ -10132,7 +9866,7 @@ var GitService = class {
10132
9866
  this.git = simpleGit(projectPath);
10133
9867
  }
10134
9868
  async isRepo() {
10135
- return fs17.pathExists(path18.join(this.projectPath, ".git"));
9869
+ return fs16.pathExists(path17.join(this.projectPath, ".git"));
10136
9870
  }
10137
9871
  async init() {
10138
9872
  await this.git.init();
@@ -10243,7 +9977,32 @@ var VercelService = class {
10243
9977
  };
10244
9978
 
10245
9979
  // src/index.ts
10246
- var VERSION = "3.3.0";
9980
+ import * as readline from "readline";
9981
+ var VERSION = "4.3.0";
9982
+ async function showChatInput() {
9983
+ return new Promise((resolve) => {
9984
+ const width = Math.min(process.stdout.columns || 80, 70);
9985
+ const innerWidth = width - 4;
9986
+ console.log("");
9987
+ console.log(colors.primary(` \u256D${"\u2500".repeat(innerWidth)}\u256E`));
9988
+ const rl = readline.createInterface({
9989
+ input: process.stdin,
9990
+ output: process.stdout,
9991
+ terminal: true
9992
+ });
9993
+ rl.on("SIGINT", () => {
9994
+ rl.close();
9995
+ console.log(colors.primary(` \u2570${"\u2500".repeat(innerWidth)}\u256F`));
9996
+ resolve(null);
9997
+ });
9998
+ process.stdout.write(colors.primary(" \u2502 ") + colors.dim("\u203A "));
9999
+ rl.question("", (answer) => {
10000
+ rl.close();
10001
+ console.log(colors.primary(` \u2570${"\u2500".repeat(innerWidth)}\u256F`));
10002
+ resolve(answer);
10003
+ });
10004
+ });
10005
+ }
10247
10006
  async function main() {
10248
10007
  const config = new Config();
10249
10008
  const args = process.argv.slice(2);
@@ -10255,76 +10014,100 @@ async function main() {
10255
10014
  showHelp();
10256
10015
  return;
10257
10016
  }
10258
- if (args[0] === "setup") {
10259
- await runSetup(config);
10260
- return;
10261
- }
10262
10017
  if (args[0] === "login") {
10263
10018
  await auth.login();
10264
10019
  return;
10265
10020
  }
10021
+ if (args[0] === "logout") {
10022
+ auth.logout();
10023
+ return;
10024
+ }
10266
10025
  const memory = new Memory();
10267
10026
  await memory.init();
10268
- if (needsOnboarding(config, memory)) {
10269
- const completed = await runOnboarding(config, memory);
10270
- if (!completed) {
10271
- console.log("");
10272
- console.log(colors.muted(" Run `cb` again when you're ready."));
10273
- console.log("");
10274
- return;
10275
- }
10276
- }
10277
- showHeader();
10278
- console.log(colors.muted(` v${VERSION}`));
10279
- console.log("");
10280
10027
  const scanner = new ProjectScanner();
10281
10028
  const structure = await scanner.detectProject();
10282
10029
  const editor = new FileEditor();
10283
10030
  const git = new GitService();
10284
- const identity = memory.getIdentity();
10285
- if (identity.name) {
10286
- console.log(colors.white(` \u{1F4C1} ${identity.name}`) + colors.muted(` (${structure.framework || "Project"})`));
10287
- } else {
10288
- console.log(colors.white(` \u{1F4C1} ${structure.name}`) + colors.muted(` (${structure.framework || "Unknown"})`));
10289
- console.log(colors.dim(" Run /init to enable project memory"));
10031
+ if (!config.isConfigured() && !auth.isLoggedIn()) {
10032
+ console.log("");
10033
+ console.log(colors.white(" Welcome to CodeBakers"));
10034
+ console.log("");
10035
+ console.log(colors.muted(" You need an API key to continue."));
10036
+ console.log("");
10037
+ const apiKey = await p7.text({
10038
+ message: "Anthropic API Key:",
10039
+ placeholder: "sk-ant-api..."
10040
+ });
10041
+ if (p7.isCancel(apiKey) || !apiKey) {
10042
+ console.log("");
10043
+ console.log(colors.muted(" Get your key at: https://console.anthropic.com"));
10044
+ console.log(colors.muted(" Then run: cb"));
10045
+ console.log("");
10046
+ return;
10047
+ }
10048
+ config.setAnthropicKey(apiKey);
10049
+ console.log(colors.success(" \u2713 API key saved"));
10050
+ console.log("");
10290
10051
  }
10291
- console.log("");
10292
10052
  let ai;
10293
10053
  try {
10294
10054
  ai = new AIEngine(config, memory, scanner);
10295
10055
  } catch (error) {
10296
- showError(error instanceof Error ? error.message : "Failed to initialize AI");
10056
+ console.log("");
10057
+ console.log(colors.error(` \u2717 ${error instanceof Error ? error.message : "Failed to start"}`));
10058
+ console.log("");
10059
+ console.log(colors.muted(" Check your API key with: cb login"));
10060
+ console.log("");
10297
10061
  return;
10298
10062
  }
10299
10063
  const nlp2 = new NLPInterpreter();
10300
10064
  const modeManager = getModeManager();
10301
10065
  const suggestions = new SmartSuggestions(memory, scanner);
10302
10066
  const proactive = new ProactiveAssistant(memory, scanner, config);
10303
- console.log(colors.muted(" Just tell me what you need:"));
10304
- console.log(colors.dim(' "Add a login page" \u2022 "Fix the navbar" \u2022 /help'));
10067
+ console.clear();
10068
+ console.log("");
10069
+ console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
10070
+ console.log(colors.muted(" \u2502") + colors.primary(" \u2B21 C O D E B A K E R S ") + colors.muted("\u2502"));
10071
+ console.log(colors.muted(" \u2502") + colors.dim(` v${VERSION} \u2022 58 patterns \u2022 auto-parallel `) + colors.muted("\u2502"));
10072
+ console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
10073
+ console.log("");
10074
+ console.log(colors.muted(" \u{1F4C1} ") + colors.white(structure.name) + (structure.framework ? colors.dim(` (${structure.framework})`) : ""));
10075
+ console.log("");
10076
+ console.log(colors.dim(" Just tell me what you want to build or do."));
10077
+ console.log(colors.dim(" Type /help to see examples."));
10305
10078
  console.log("");
10306
10079
  let lastAction;
10307
10080
  let running = true;
10308
10081
  while (running) {
10309
- const modePrompt = modeManager.getPrompt();
10310
- const input = await p8.text({
10311
- message: modePrompt,
10312
- placeholder: "What would you like to build? (Shift+Tab to change mode)"
10313
- });
10314
- if (p8.isCancel(input)) {
10082
+ const input = await showChatInput();
10083
+ if (input === null) {
10315
10084
  running = false;
10316
- showInfo("Goodbye! \u{1F44B}");
10085
+ console.log("");
10086
+ console.log(colors.muted(" Goodbye! \u{1F44B}"));
10087
+ console.log("");
10317
10088
  break;
10318
10089
  }
10319
10090
  const cmd = input.trim();
10320
10091
  if (!cmd) continue;
10321
- const suggestion = await suggestions.checkForSuggestions(cmd, lastAction);
10322
- if (suggestion) {
10323
- console.log(suggestions.formatSuggestion(suggestion));
10092
+ const smartSuggestion = await suggestions.checkForSuggestions(cmd, lastAction);
10093
+ if (smartSuggestion) {
10094
+ console.log(suggestions.formatSuggestion(smartSuggestion));
10324
10095
  }
10325
10096
  const result = await nlp2.interpret(cmd);
10326
10097
  if (result.type === "unclear") {
10327
- showWarning("I didn't understand that. Try rephrasing or type /help for commands.");
10098
+ console.log("");
10099
+ console.log(colors.muted(" I'm not sure what you want to do."));
10100
+ console.log("");
10101
+ const cmdSuggestions = getSuggestions(cmd);
10102
+ if (cmdSuggestions.length > 0) {
10103
+ console.log(colors.muted(" Did you mean:"));
10104
+ cmdSuggestions.forEach((s) => {
10105
+ console.log(colors.dim(` \u2022 ${s}`));
10106
+ });
10107
+ console.log("");
10108
+ }
10109
+ console.log(colors.dim(" Type /help for a list of commands"));
10110
+ console.log("");
10328
10111
  continue;
10329
10112
  }
10330
10113
  if (["undo", "clear", "deploy"].includes(result.type)) {
@@ -10335,7 +10118,9 @@ async function main() {
10335
10118
  switch (result.type) {
10336
10119
  case "quit":
10337
10120
  running = false;
10338
- showInfo("Goodbye! \u{1F44B}");
10121
+ console.log("");
10122
+ console.log(colors.muted(" Goodbye! \u{1F44B}"));
10123
+ console.log("");
10339
10124
  break;
10340
10125
  case "help":
10341
10126
  showCommandHelp();
@@ -10368,9 +10153,13 @@ async function main() {
10368
10153
  await memory.save();
10369
10154
  showSuccess(`Decision recorded: ${result.params.content}`);
10370
10155
  } else {
10371
- showInfo("What decision would you like to record?");
10372
- const decision = await p8.text({ message: "Decision:" });
10373
- if (!p8.isCancel(decision) && decision) {
10156
+ const decision = await p7.text({
10157
+ message: "What decision would you like to record?",
10158
+ placeholder: "e.g., Use Supabase for auth"
10159
+ });
10160
+ if (p7.isCancel(decision)) {
10161
+ console.log(colors.muted(" Cancelled"));
10162
+ } else if (decision) {
10374
10163
  memory.addDecision(decision);
10375
10164
  await memory.save();
10376
10165
  showSuccess(`Decision recorded: ${decision}`);
@@ -10383,9 +10172,13 @@ async function main() {
10383
10172
  await memory.save();
10384
10173
  showSuccess(`Rule added: ${result.params.content}`);
10385
10174
  } else {
10386
- showInfo("What rule would you like to add?");
10387
- const rule = await p8.text({ message: "Rule:" });
10388
- if (!p8.isCancel(rule) && rule) {
10175
+ const rule = await p7.text({
10176
+ message: "What rule would you like to add?",
10177
+ placeholder: "e.g., Always use TypeScript strict mode"
10178
+ });
10179
+ if (p7.isCancel(rule)) {
10180
+ console.log(colors.muted(" Cancelled"));
10181
+ } else if (rule) {
10389
10182
  memory.addCustomRule(rule);
10390
10183
  await memory.save();
10391
10184
  showSuccess(`Rule added: ${rule}`);
@@ -10401,8 +10194,10 @@ async function main() {
10401
10194
  if (result.params?.command) {
10402
10195
  await handleRunCommand(result.params.command, ctx);
10403
10196
  } else {
10404
- showInfo("Usage: /run <command>");
10405
- showInfo("Example: /run npm install lodash");
10197
+ console.log("");
10198
+ console.log(colors.muted(" Usage: /run <command>"));
10199
+ console.log(colors.dim(" Example: /run npm install lodash"));
10200
+ console.log("");
10406
10201
  }
10407
10202
  break;
10408
10203
  case "create":
@@ -10435,6 +10230,9 @@ async function main() {
10435
10230
  case "audit":
10436
10231
  await handleAudit(result.params?.args, ctx);
10437
10232
  break;
10233
+ case "website-review":
10234
+ await handleWebsiteReview(cmd, ctx);
10235
+ break;
10438
10236
  case "patterns":
10439
10237
  await handlePatterns(result.params?.args, ctx);
10440
10238
  break;
@@ -10458,68 +10256,167 @@ async function main() {
10458
10256
  const inputHandler = new InputHandler(config, memory, scanner);
10459
10257
  const detected = await inputHandler.detectAndProcess(cmd);
10460
10258
  if (detected.type !== "text") {
10461
- console.log(inputHandler.formatDetection(detected));
10462
- const proceed = await p8.confirm({
10463
- message: `Process this ${detected.type}?`,
10464
- initialValue: true
10465
- });
10466
- if (!proceed || p8.isCancel(proceed)) break;
10467
- const enhancedCmd = inputHandler.enhancePrompt(detected, cmd);
10468
- await handleGeneration(enhancedCmd, ctx);
10469
- break;
10470
- }
10471
- const currentMode = getModeManager().getMode();
10472
- if (currentMode === "plan") {
10473
- await handlePlanMode(cmd, ctx);
10474
- } else if (currentMode === "question") {
10475
- await handleQuestionMode(cmd, ctx);
10476
- } else if (currentMode === "agent") {
10477
- await handleAgentMode(cmd, ctx);
10478
- } else {
10479
- const isComplex = detectComplexRequest(cmd);
10480
- if (isComplex) {
10481
- console.log("");
10482
- console.log(colors.secondary(" \u{1F4A1} This looks like a complex request."));
10483
- console.log(colors.muted(" Parallel build could be ~3x faster."));
10484
- console.log("");
10485
- const useParallel = await p8.confirm({
10486
- message: "Use parallel build with 3 agents?",
10487
- initialValue: true
10488
- });
10489
- if (useParallel && !p8.isCancel(useParallel)) {
10490
- await handleParallelBuild(cmd, ctx);
10491
- break;
10492
- }
10493
- }
10494
- const templateManager = new TemplateManager();
10495
- const suggestedTemplate = templateManager.suggestTemplate(cmd);
10496
- if (suggestedTemplate) {
10497
- console.log("");
10498
- console.log(colors.secondary(` \u{1F4A1} I recommend the "${suggestedTemplate.name}" template.`));
10499
- console.log(colors.muted(` ${suggestedTemplate.description}`));
10500
- console.log("");
10501
- const useTemplate = await p8.confirm({
10502
- message: "Install this template first?",
10503
- initialValue: true
10504
- });
10505
- if (useTemplate && !p8.isCancel(useTemplate)) {
10506
- await handleInstallTemplate(suggestedTemplate.id, ctx);
10507
- showSuccess("Template installed! Now customizing...");
10508
- }
10509
- }
10510
- await handleGeneration(cmd, ctx);
10511
- const filesCreated = editor.getPendingChanges().length;
10512
- const followUp = await proactive.onAfterAction(cmd, filesCreated);
10513
- if (followUp) {
10514
- console.log(proactive.formatAction(followUp));
10515
- proactive.markSuggestionShown();
10259
+ console.log(inputHandler.formatDetection(detected));
10260
+ const proceed = await p7.confirm({
10261
+ message: `Process this ${detected.type}?`,
10262
+ initialValue: true
10263
+ });
10264
+ if (!proceed || p7.isCancel(proceed)) {
10265
+ console.log(colors.muted(" Cancelled"));
10266
+ break;
10516
10267
  }
10268
+ const enhancedCmd = inputHandler.enhancePrompt(detected, cmd);
10269
+ await handleAutomaticBuild(enhancedCmd, ctx);
10270
+ break;
10517
10271
  }
10272
+ await handleAutomaticBuild(cmd, ctx);
10273
+ lastAction = cmd;
10274
+ break;
10518
10275
  break;
10519
10276
  }
10520
10277
  proactive.updateLastActivity();
10521
10278
  }
10522
10279
  }
10280
+ async function handleAutomaticBuild(request, ctx) {
10281
+ const { config, memory, scanner, editor, ai } = ctx;
10282
+ memory.addMessage("user", request);
10283
+ memory.setActiveTask(request);
10284
+ await memory.createSnapshot(`Before: ${request.slice(0, 50)}`);
10285
+ const tasks = analyzeAndSplitTasks(request);
10286
+ if (tasks.length > 1) {
10287
+ console.log("");
10288
+ console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
10289
+ console.log(colors.muted(" \u2502") + colors.primary(" \u26A1 Parallel Build ") + colors.muted("\u2502"));
10290
+ console.log(colors.muted(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
10291
+ tasks.forEach((task, i) => {
10292
+ const taskText = task.slice(0, 43);
10293
+ console.log(colors.muted(" \u2502") + colors.dim(` ${i + 1}. ${taskText}`.padEnd(47)) + colors.muted("\u2502"));
10294
+ });
10295
+ console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
10296
+ console.log("");
10297
+ const hasParallel = await auth.checkFeature(FEATURES.parallel);
10298
+ if (hasParallel) {
10299
+ await runParallelBuild(tasks, ctx);
10300
+ } else {
10301
+ console.log(colors.dim(" Running sequentially (parallel requires Pro)..."));
10302
+ console.log("");
10303
+ for (const task of tasks) {
10304
+ await handleGeneration(task, ctx);
10305
+ }
10306
+ }
10307
+ } else {
10308
+ await handleGeneration(request, ctx);
10309
+ }
10310
+ }
10311
+ function analyzeAndSplitTasks(request) {
10312
+ const lowered = request.toLowerCase();
10313
+ const multiIndicators = [
10314
+ /\band\b/i,
10315
+ // "login and dashboard"
10316
+ /\+/,
10317
+ // "login + dashboard"
10318
+ /,\s*(?:and\s+)?/,
10319
+ // "login, dashboard, settings"
10320
+ /\bthen\b/i,
10321
+ // "login then dashboard"
10322
+ /\balso\b/i,
10323
+ // "also add settings"
10324
+ /\bplus\b/i
10325
+ // "plus a settings page"
10326
+ ];
10327
+ const hasMultiIndicator = multiIndicators.some((r) => r.test(request));
10328
+ if (!hasMultiIndicator) {
10329
+ return [request];
10330
+ }
10331
+ let tasks = request.split(/(?:\band\b|\+|,\s*(?:and\s+)?|\bthen\b|\balso\b|\bplus\b)/i).map((t) => t.trim()).filter((t) => t.length > 3);
10332
+ if (tasks.length <= 1) {
10333
+ return [request];
10334
+ }
10335
+ if (tasks.length > 4) {
10336
+ const first3 = tasks.slice(0, 3);
10337
+ const rest = tasks.slice(3).join(" and ");
10338
+ tasks = [...first3, rest];
10339
+ }
10340
+ return tasks;
10341
+ }
10342
+ async function runParallelBuild(tasks, ctx) {
10343
+ const { config, memory, scanner, editor } = ctx;
10344
+ if (!await auth.checkFeature(FEATURES.parallel)) {
10345
+ return;
10346
+ }
10347
+ const parallelSystem = new ParallelAgentSystem(config, memory, scanner, editor);
10348
+ let cancelled = false;
10349
+ const checkCancelled = () => cancelled;
10350
+ const onKeypress = (key) => {
10351
+ const str = key.toString();
10352
+ if (str === "\x1B" || str === "\x1B") {
10353
+ cancelled = true;
10354
+ parallelSystem.cancel();
10355
+ }
10356
+ };
10357
+ if (process.stdin.isTTY) {
10358
+ process.stdin.setRawMode(true);
10359
+ }
10360
+ process.stdin.resume();
10361
+ process.stdin.on("data", onKeypress);
10362
+ console.log(colors.muted(" Press ESC to cancel"));
10363
+ console.log("");
10364
+ try {
10365
+ const startTime = Date.now();
10366
+ const combinedRequest = tasks.join(" AND ");
10367
+ const result = await parallelSystem.build(
10368
+ combinedRequest,
10369
+ {
10370
+ onTaskPlan: (plannedTasks) => {
10371
+ console.log(colors.muted(` Planned ${plannedTasks.length} parallel tasks`));
10372
+ },
10373
+ onAgentStart: (taskId, name) => {
10374
+ console.log(colors.dim(` \u2192 Starting: ${name}`));
10375
+ },
10376
+ onAgentComplete: (taskId, agentResult) => {
10377
+ const status = agentResult.success ? colors.success("\u2713") : colors.error("\u2717");
10378
+ console.log(` ${status} ${taskId}: ${agentResult.files.length} files`);
10379
+ }
10380
+ },
10381
+ checkCancelled
10382
+ );
10383
+ process.stdin.removeListener("data", onKeypress);
10384
+ if (process.stdin.isTTY) {
10385
+ process.stdin.setRawMode(false);
10386
+ }
10387
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
10388
+ console.log("");
10389
+ if (result.cancelled) {
10390
+ console.log(colors.muted(" Cancelled"));
10391
+ } else if (result.success) {
10392
+ console.log(colors.success(` \u2713 Completed in ${duration}s`));
10393
+ console.log(colors.muted(` Files: ${result.totalFiles}`));
10394
+ if (result.mergeConflicts.length > 0) {
10395
+ console.log(colors.warning(` Conflicts: ${result.mergeConflicts.length}`));
10396
+ }
10397
+ } else {
10398
+ console.log(colors.error(" Build failed"));
10399
+ result.results.forEach((r) => {
10400
+ if (!r.success && r.errors.length > 0) {
10401
+ r.errors.forEach((e) => console.log(colors.dim(` \u2022 ${e}`)));
10402
+ }
10403
+ });
10404
+ }
10405
+ console.log("");
10406
+ } catch (error) {
10407
+ process.stdin.removeListener("data", onKeypress);
10408
+ if (process.stdin.isTTY) {
10409
+ process.stdin.setRawMode(false);
10410
+ }
10411
+ if (cancelled) {
10412
+ console.log("");
10413
+ console.log(colors.muted(" Cancelled"));
10414
+ console.log("");
10415
+ return;
10416
+ }
10417
+ showError(error instanceof Error ? error.message : "Parallel build failed");
10418
+ }
10419
+ }
10523
10420
  async function handleGeneration(request, ctx) {
10524
10421
  const { memory, editor, ai, scanner } = ctx;
10525
10422
  memory.addMessage("user", request);
@@ -10559,31 +10456,46 @@ async function handleGeneration(request, ctx) {
10559
10456
  return;
10560
10457
  }
10561
10458
  console.log("");
10459
+ console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
10562
10460
  if (result.filesCreated.length > 0) {
10563
- console.log(colors.success(` \u2713 Created ${result.filesCreated.length} file(s)`));
10564
- for (const f of result.filesCreated) {
10565
- console.log(colors.muted(` + ${f}`));
10461
+ console.log(colors.muted(" \u2502") + colors.success(` \u2713 Created ${result.filesCreated.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
10462
+ for (const f of result.filesCreated.slice(0, 5)) {
10463
+ console.log(colors.muted(" \u2502") + colors.dim(` + ${f}`.padEnd(47)) + colors.muted("\u2502"));
10464
+ }
10465
+ if (result.filesCreated.length > 5) {
10466
+ console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesCreated.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
10566
10467
  }
10567
10468
  }
10568
10469
  if (result.filesModified.length > 0) {
10569
- console.log(colors.success(` \u2713 Modified ${result.filesModified.length} file(s)`));
10570
- for (const f of result.filesModified) {
10571
- console.log(colors.muted(` ~ ${f}`));
10470
+ console.log(colors.muted(" \u2502") + colors.warning(` ~ Modified ${result.filesModified.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
10471
+ for (const f of result.filesModified.slice(0, 5)) {
10472
+ console.log(colors.muted(" \u2502") + colors.dim(` ~ ${f}`.padEnd(47)) + colors.muted("\u2502"));
10473
+ }
10474
+ if (result.filesModified.length > 5) {
10475
+ console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesModified.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
10572
10476
  }
10573
10477
  }
10574
10478
  if (result.commandsRun.length > 0) {
10575
- console.log(colors.muted(` \u26A1 Ran: ${result.commandsRun.join(", ")}`));
10479
+ console.log(colors.muted(" \u2502") + colors.dim(` \u26A1 Ran: ${result.commandsRun.join(", ")}`.padEnd(47)) + colors.muted("\u2502"));
10576
10480
  }
10577
10481
  if (result.errors.length > 0) {
10578
- console.log("");
10579
- console.log(colors.error(` \u274C ${result.errors.length} error(s):`));
10580
- for (const e of result.errors.slice(0, 5)) {
10581
- console.log(colors.error(` \u2022 ${e}`));
10482
+ console.log(colors.muted(" \u2502") + colors.error(` \u2717 ${result.errors.length} error(s)`.padEnd(47)) + colors.muted("\u2502"));
10483
+ for (const e of result.errors.slice(0, 3)) {
10484
+ console.log(colors.muted(" \u2502") + colors.dim(` ${e.slice(0, 43)}`.padEnd(47)) + colors.muted("\u2502"));
10582
10485
  }
10583
10486
  }
10487
+ console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
10584
10488
  if (result.success) {
10585
10489
  console.log("");
10586
- showSuccess("Done!");
10490
+ console.log(colors.success(" \u2713 Done!"));
10491
+ const totalFiles = result.filesCreated.length + result.filesModified.length;
10492
+ if (totalFiles > 0) {
10493
+ console.log("");
10494
+ console.log(colors.dim(" What's next?"));
10495
+ console.log(colors.dim(' \u2022 "Check my code for issues"'));
10496
+ console.log(colors.dim(' \u2022 "Generate tests"'));
10497
+ console.log(colors.dim(' \u2022 "Commit my changes"'));
10498
+ }
10587
10499
  }
10588
10500
  console.log("");
10589
10501
  memory.addMessage("assistant", `Created: ${result.filesCreated.join(", ")}. Modified: ${result.filesModified.join(", ")}`);
@@ -10641,7 +10553,7 @@ async function handleCreate(name, ctx) {
10641
10553
  const { config } = ctx;
10642
10554
  let projectName = name;
10643
10555
  if (!projectName) {
10644
- const nameInput = await p8.text({
10556
+ const nameInput = await p7.text({
10645
10557
  message: "Project name:",
10646
10558
  placeholder: "my-awesome-app",
10647
10559
  validate: (value) => {
@@ -10649,23 +10561,23 @@ async function handleCreate(name, ctx) {
10649
10561
  if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
10650
10562
  }
10651
10563
  });
10652
- if (p8.isCancel(nameInput)) return;
10564
+ if (p7.isCancel(nameInput)) return;
10653
10565
  projectName = nameInput;
10654
10566
  }
10655
10567
  console.log("");
10656
- const github = await p8.confirm({
10568
+ const github = await p7.confirm({
10657
10569
  message: "Create GitHub repository?",
10658
10570
  initialValue: !!config.getGithubToken()
10659
10571
  });
10660
- const supabase = await p8.confirm({
10572
+ const supabase = await p7.confirm({
10661
10573
  message: "Create Supabase project?",
10662
10574
  initialValue: !!config.getSupabaseToken()
10663
10575
  });
10664
- const vercel = await p8.confirm({
10576
+ const vercel = await p7.confirm({
10665
10577
  message: "Deploy to Vercel?",
10666
10578
  initialValue: !!config.getVercelToken()
10667
10579
  });
10668
- if (p8.isCancel(github) || p8.isCancel(supabase) || p8.isCancel(vercel)) return;
10580
+ if (p7.isCancel(github) || p7.isCancel(supabase) || p7.isCancel(vercel)) return;
10669
10581
  const creator = new ProjectCreator(config);
10670
10582
  const spinner = ora3("Creating project...").start();
10671
10583
  const result = await creator.create(
@@ -10726,14 +10638,17 @@ async function handleInstallTemplate(templateId, ctx) {
10726
10638
  let id = templateId;
10727
10639
  if (!id) {
10728
10640
  const templates = templateManager.listTemplates();
10729
- const choice = await p8.select({
10641
+ const choice = await p7.select({
10730
10642
  message: "Choose a template:",
10731
10643
  options: templates.map((t) => ({
10732
10644
  value: t.id,
10733
10645
  label: `${t.name} - ${t.description}`
10734
10646
  }))
10735
10647
  });
10736
- if (p8.isCancel(choice)) return;
10648
+ if (p7.isCancel(choice)) {
10649
+ console.log(colors.muted(" Cancelled"));
10650
+ return;
10651
+ }
10737
10652
  id = choice;
10738
10653
  }
10739
10654
  const spinner = ora3(`Installing ${id}...`).start();
@@ -10809,11 +10724,11 @@ async function handleParallelBuild(request, ctx) {
10809
10724
  console.log(colors.muted(` + ${change.path}`));
10810
10725
  }
10811
10726
  console.log("");
10812
- const apply = await p8.confirm({
10727
+ const apply = await p7.confirm({
10813
10728
  message: "Apply all files?",
10814
10729
  initialValue: true
10815
10730
  });
10816
- if (apply && !p8.isCancel(apply)) {
10731
+ if (apply && !p7.isCancel(apply)) {
10817
10732
  const applied = await editor.apply();
10818
10733
  showSuccess(`Applied ${applied.length} files`);
10819
10734
  for (const file of applied) {
@@ -10839,124 +10754,18 @@ async function handleParallelBuild(request, ctx) {
10839
10754
  }
10840
10755
  console.log("");
10841
10756
  }
10842
- function detectComplexRequest(request) {
10843
- const lower = request.toLowerCase();
10844
- const features = [
10845
- "auth",
10846
- "login",
10847
- "signup",
10848
- "authentication",
10849
- "dashboard",
10850
- "admin",
10851
- "settings",
10852
- "profile",
10853
- "billing",
10854
- "payment",
10855
- "stripe",
10856
- "subscription",
10857
- "analytics",
10858
- "charts",
10859
- "users",
10860
- "user management",
10861
- "api",
10862
- "backend",
10863
- "database",
10864
- "crud"
10865
- ];
10866
- let featureCount = 0;
10867
- for (const feature of features) {
10868
- if (lower.includes(feature)) {
10869
- featureCount++;
10870
- }
10871
- }
10872
- const hasMultiple = lower.includes(" and ") || lower.includes(" with ") || lower.includes(", ") || lower.includes(" + ");
10873
- const scopeKeywords = ["full", "complete", "entire", "whole", "saas", "app", "application", "system"];
10874
- const hasScope = scopeKeywords.some((k) => lower.includes(k));
10875
- return featureCount >= 3 || featureCount >= 2 && hasMultiple || hasScope && featureCount >= 2;
10876
- }
10877
- async function handlePlanMode(request, ctx) {
10878
- const { config, memory, scanner } = ctx;
10879
- console.log("");
10880
- console.log(colors.primary(" \u{1F4CB} PLAN MODE") + colors.muted(" - Showing what will happen"));
10881
- console.log("");
10882
- const planExecutor = new PlanExecutor(config, memory, scanner);
10883
- const spinner = ora3("Creating plan...").start();
10884
- try {
10885
- const plan = await planExecutor.createPlan(request);
10886
- spinner.stop();
10887
- console.log(planExecutor.formatPlan(plan));
10888
- const execute = await p8.confirm({
10889
- message: "Execute this plan?",
10890
- initialValue: true
10891
- });
10892
- if (execute && !p8.isCancel(execute)) {
10893
- await handleGeneration(request, ctx);
10894
- }
10895
- } catch (error) {
10896
- spinner.stop();
10897
- showError(error instanceof Error ? error.message : "Plan failed");
10898
- }
10899
- }
10900
- async function handleQuestionMode(request, ctx) {
10901
- const { config, memory, scanner } = ctx;
10902
- const questionExecutor = new QuestionExecutor(config, memory, scanner);
10903
- try {
10904
- const questions = await questionExecutor.generateQuestions(request);
10905
- const result = await questionExecutor.askQuestions(questions);
10906
- if (Object.keys(result.answers).length > 0) {
10907
- questionExecutor.showSummary(result);
10908
- const enhancedRequest = request + questionExecutor.formatAnswersForPrompt(result);
10909
- await handleGeneration(enhancedRequest, ctx);
10910
- } else {
10911
- await handleGeneration(request, ctx);
10912
- }
10913
- } catch (error) {
10914
- showError(error instanceof Error ? error.message : "Question mode failed");
10915
- }
10916
- }
10917
- async function handleAgentMode(request, ctx) {
10918
- const { config, ai, memory, scanner, editor } = ctx;
10919
- await memory.createSnapshot(`Before agent: ${request.slice(0, 50)}`);
10920
- let cancelled = false;
10921
- const checkCancelled = () => cancelled;
10922
- const onKeypress = (key) => {
10923
- const str = key.toString();
10924
- if (str === "\x1B" || str === "\x1B") {
10925
- cancelled = true;
10926
- }
10927
- };
10928
- if (process.stdin.isTTY) {
10929
- process.stdin.setRawMode(true);
10930
- }
10931
- process.stdin.resume();
10932
- process.stdin.on("data", onKeypress);
10933
- const agentExecutor = new AgentExecutor(config, ai, memory, scanner, editor);
10934
- try {
10935
- const result = await agentExecutor.execute(request, { checkCancelled });
10936
- process.stdin.removeListener("data", onKeypress);
10937
- if (process.stdin.isTTY) {
10938
- process.stdin.setRawMode(false);
10939
- }
10940
- if (result.cancelled) {
10941
- showWarning("Cancelled.");
10942
- }
10943
- } catch (error) {
10944
- process.stdin.removeListener("data", onKeypress);
10945
- if (process.stdin.isTTY) {
10946
- process.stdin.setRawMode(false);
10947
- }
10948
- showError(error instanceof Error ? error.message : "Agent mode failed");
10949
- }
10950
- }
10951
10757
  async function handleTestGeneration(filePath, ctx) {
10952
10758
  const { config, scanner, editor } = ctx;
10953
10759
  const testGen = new TestGenerator(config, scanner);
10954
10760
  if (!filePath) {
10955
- const file = await p8.text({
10761
+ const file = await p7.text({
10956
10762
  message: "File to generate tests for:",
10957
10763
  placeholder: "src/components/Button.tsx"
10958
10764
  });
10959
- if (p8.isCancel(file) || !file) return;
10765
+ if (p7.isCancel(file) || !file) {
10766
+ console.log(colors.muted(" Cancelled"));
10767
+ return;
10768
+ }
10960
10769
  filePath = file;
10961
10770
  }
10962
10771
  const spinner = ora3(`Generating tests for ${filePath}...`).start();
@@ -10973,13 +10782,17 @@ async function handleTestGeneration(filePath, ctx) {
10973
10782
  console.log(colors.muted(" " + test.content.split("\n").slice(0, 10).join("\n ")));
10974
10783
  console.log(colors.muted(" ..."));
10975
10784
  console.log("");
10976
- const save = await p8.confirm({
10785
+ const save = await p7.confirm({
10977
10786
  message: `Save to ${test.testPath}?`,
10978
10787
  initialValue: true
10979
10788
  });
10980
- if (save && !p8.isCancel(save)) {
10789
+ if (p7.isCancel(save)) {
10790
+ console.log(colors.muted(" Test not saved"));
10791
+ } else if (save) {
10981
10792
  await testGen.writeTest(test);
10982
10793
  showSuccess(`Test saved to ${test.testPath}`);
10794
+ } else {
10795
+ console.log(colors.muted(" Test not saved"));
10983
10796
  }
10984
10797
  } catch (error) {
10985
10798
  spinner.stop();
@@ -11015,11 +10828,11 @@ async function handleLivePreview(ctx) {
11015
10828
  const { scanner } = ctx;
11016
10829
  const preview = new LivePreview(scanner);
11017
10830
  if (preview.isRunning()) {
11018
- const stop = await p8.confirm({
10831
+ const stop = await p7.confirm({
11019
10832
  message: "Preview is running. Stop it?",
11020
10833
  initialValue: false
11021
10834
  });
11022
- if (stop && !p8.isCancel(stop)) {
10835
+ if (stop && !p7.isCancel(stop)) {
11023
10836
  await preview.stop();
11024
10837
  showSuccess("Preview stopped");
11025
10838
  } else {
@@ -11047,28 +10860,38 @@ async function handleSpecGeneration(input, ctx) {
11047
10860
  return;
11048
10861
  }
11049
10862
  const { config, memory, scanner, editor, ai } = ctx;
10863
+ console.log("");
10864
+ console.log(colors.white(" \u{1F4DD} Spec Generator"));
10865
+ console.log(colors.muted(" \u2500".repeat(35)));
10866
+ console.log("");
11050
10867
  let description = input.replace(/^\/spec\s*/i, "").replace(/^\/prd\s*/i, "").replace(/^\/plan\s*/i, "").trim();
11051
10868
  if (!description) {
11052
- const desc = await p8.text({
10869
+ const desc = await p7.text({
11053
10870
  message: "What do you want to build?",
11054
10871
  placeholder: "An Uber for dog walkers"
11055
10872
  });
11056
- if (p8.isCancel(desc) || !desc) return;
10873
+ if (p7.isCancel(desc) || !desc) {
10874
+ console.log(colors.muted(" Spec generation cancelled"));
10875
+ console.log("");
10876
+ return;
10877
+ }
11057
10878
  description = desc;
11058
10879
  }
11059
10880
  const specGen = new SpecGenerator(config);
11060
10881
  try {
11061
10882
  const spec = await specGen.generate(description);
11062
10883
  if (!spec) {
11063
- showInfo("Spec generation cancelled");
10884
+ console.log("");
10885
+ console.log(colors.muted(" Spec generation cancelled"));
10886
+ console.log("");
11064
10887
  return;
11065
10888
  }
11066
10889
  console.log(specGen.formatSpec(spec));
11067
- const buildNow = await p8.confirm({
10890
+ const buildNow = await p7.confirm({
11068
10891
  message: "Build from this spec?",
11069
10892
  initialValue: true
11070
10893
  });
11071
- if (buildNow && !p8.isCancel(buildNow)) {
10894
+ if (buildNow && !p7.isCancel(buildNow)) {
11072
10895
  const builder = new ProjectBuilder(config, memory, scanner, editor, ai);
11073
10896
  let cancelled = false;
11074
10897
  const checkCancelled = () => cancelled;
@@ -11195,30 +11018,47 @@ async function handleAudit(args, ctx) {
11195
11018
  const auditor = new CodebaseAuditor(config);
11196
11019
  const git = new SmartGitManager(config);
11197
11020
  const subcommand = args?.split(" ")[0] || "";
11021
+ console.log("");
11022
+ console.log(colors.white(" \u{1F50D} Codebase Audit"));
11023
+ console.log(colors.muted(" \u2500".repeat(35)));
11198
11024
  switch (subcommand) {
11199
- case "fix": {
11025
+ case "fix":
11026
+ case "--fix": {
11027
+ console.log("");
11200
11028
  const spinner = ora3("Scanning codebase...").start();
11201
11029
  const report = await auditor.audit((file, current, total) => {
11202
11030
  spinner.text = `Scanning ${current}/${total}: ${file}`;
11203
11031
  });
11204
- spinner.stop();
11032
+ spinner.succeed("Scan complete");
11205
11033
  console.log(auditor.formatReport(report));
11206
11034
  if (report.totalIssues === 0) {
11207
- showSuccess("No issues found!");
11035
+ console.log("");
11036
+ showSuccess("No issues found! Your code is clean.");
11037
+ console.log("");
11038
+ console.log(colors.muted(" What you can do next:"));
11039
+ console.log(colors.dim(" \u2022 /review Get a full codebase review"));
11040
+ console.log(colors.dim(" \u2022 /spec Generate a product spec"));
11041
+ console.log("");
11208
11042
  return;
11209
11043
  }
11210
11044
  const fixType = args?.includes("critical") ? "critical" : args?.includes("all") ? "all" : null;
11211
11045
  let issuesToFix = report.critical;
11212
11046
  if (!fixType) {
11213
- const choice = await p8.select({
11047
+ const choice = await p7.select({
11214
11048
  message: "What would you like to fix?",
11215
11049
  options: [
11216
11050
  { value: "critical", label: `Critical issues only (${report.critical.length})` },
11217
11051
  { value: "warnings", label: `Critical + Warnings (${report.critical.length + report.warnings.length})` },
11218
- { value: "all", label: `Everything (${report.totalIssues})` }
11052
+ { value: "all", label: `Everything (${report.totalIssues})` },
11053
+ { value: "cancel", label: "\u2190 Back", hint: "Fix later" }
11219
11054
  ]
11220
11055
  });
11221
- if (p8.isCancel(choice)) return;
11056
+ if (p7.isCancel(choice) || choice === "cancel") {
11057
+ console.log("");
11058
+ console.log(colors.muted(" Auto-fix cancelled. You can run /audit fix anytime."));
11059
+ console.log("");
11060
+ return;
11061
+ }
11222
11062
  if (choice === "warnings") {
11223
11063
  issuesToFix = [...report.critical, ...report.warnings];
11224
11064
  } else if (choice === "all") {
@@ -11249,11 +11089,11 @@ async function handleAudit(args, ctx) {
11249
11089
  const files = result.changes.map((c) => c.file);
11250
11090
  await git.commitFiles(files, "fix: resolve codebase audit issues");
11251
11091
  console.log("");
11252
- const push = await p8.confirm({
11092
+ const push = await p7.confirm({
11253
11093
  message: "Push and create PR?",
11254
11094
  initialValue: true
11255
11095
  });
11256
- if (push && !p8.isCancel(push)) {
11096
+ if (push && !p7.isCancel(push)) {
11257
11097
  await git.push();
11258
11098
  const pr = await git.createPR(branchName, await git.getMainBranch(), "Fix: Codebase Audit Issues");
11259
11099
  if (pr?.url) {
@@ -11271,25 +11111,69 @@ async function handleAudit(args, ctx) {
11271
11111
  spinner.stop();
11272
11112
  const markdown = auditor.exportMarkdown(report);
11273
11113
  const reportPath = "AUDIT_REPORT.md";
11274
- await fs18.writeFile(reportPath, markdown);
11114
+ await fs17.writeFile(reportPath, markdown);
11275
11115
  console.log(auditor.formatReport(report));
11276
11116
  showSuccess(`Report saved to ${reportPath}`);
11277
11117
  break;
11278
11118
  }
11279
11119
  default: {
11120
+ console.log("");
11280
11121
  const spinner = ora3("Scanning codebase...").start();
11281
11122
  const report = await auditor.audit((file, current, total) => {
11282
11123
  spinner.text = `Scanning ${current}/${total}: ${file}`;
11283
11124
  });
11284
- spinner.stop();
11125
+ spinner.succeed("Scan complete");
11285
11126
  console.log(auditor.formatReport(report));
11286
- if (report.totalIssues > 0) {
11287
- console.log(colors.muted(" Use /audit fix to auto-fix issues"));
11288
- console.log(colors.muted(" Use /audit report to export markdown"));
11127
+ if (report.totalIssues === 0) {
11128
+ console.log("");
11129
+ showSuccess("No issues found! Your code is clean.");
11130
+ console.log("");
11131
+ } else {
11132
+ console.log("");
11133
+ console.log(colors.muted(" What you can do:"));
11134
+ console.log(colors.dim(" /audit fix Auto-fix issues"));
11135
+ console.log(colors.dim(" /audit report Export markdown report"));
11136
+ console.log("");
11289
11137
  }
11290
11138
  }
11291
11139
  }
11292
11140
  }
11141
+ async function handleWebsiteReview(input, ctx) {
11142
+ const { config } = ctx;
11143
+ const urlMatch = input.match(/(https?:\/\/[^\s]+)|(www\.[^\s]+)/i);
11144
+ let url = urlMatch ? urlMatch[0] : "";
11145
+ if (!url) {
11146
+ const urlInput = await p7.text({
11147
+ message: "Website URL to review:",
11148
+ placeholder: "https://example.com"
11149
+ });
11150
+ if (p7.isCancel(urlInput) || !urlInput) {
11151
+ console.log(colors.muted(" Cancelled"));
11152
+ return;
11153
+ }
11154
+ url = urlInput;
11155
+ }
11156
+ if (!url.startsWith("http")) {
11157
+ url = "https://" + url;
11158
+ }
11159
+ console.log("");
11160
+ const spinner = ora3(`Reviewing ${url}...`).start();
11161
+ try {
11162
+ const visualHandler = new VisualInputHandler(config);
11163
+ const review = await visualHandler.reviewWebsite(url);
11164
+ spinner.stop();
11165
+ console.log(visualHandler.formatWebsiteReview(review));
11166
+ console.log("");
11167
+ console.log(colors.muted(" What you can do:"));
11168
+ console.log(colors.dim(' "Build a better version of this"'));
11169
+ console.log(colors.dim(' "Fix the accessibility issues"'));
11170
+ console.log(colors.dim(' "Create an SEO-optimized version"'));
11171
+ console.log("");
11172
+ } catch (error) {
11173
+ spinner.stop();
11174
+ showError(error instanceof Error ? error.message : "Failed to review website");
11175
+ }
11176
+ }
11293
11177
  async function handlePatterns(args, ctx) {
11294
11178
  const { config } = ctx;
11295
11179
  const auditor = new CodebaseAuditor(config);
@@ -11375,7 +11259,7 @@ async function handleFolderChange() {
11375
11259
  console.log(colors.muted(" Cancelled"));
11376
11260
  return;
11377
11261
  }
11378
- if (!await fs18.pathExists(folder)) {
11262
+ if (!await fs17.pathExists(folder)) {
11379
11263
  showError(`Folder not found: ${folder}`);
11380
11264
  return;
11381
11265
  }
@@ -11393,17 +11277,29 @@ async function initProject(ctx) {
11393
11277
  const { memory, scanner } = ctx;
11394
11278
  const structure = await scanner.detectProject();
11395
11279
  console.log("");
11396
- const name = await p8.text({
11280
+ console.log(colors.white(" \u{1F4C1} Initialize Project"));
11281
+ console.log(colors.muted(" \u2500".repeat(35)));
11282
+ console.log("");
11283
+ const name = await p7.text({
11397
11284
  message: "Project name:",
11398
- initialValue: structure.name
11285
+ initialValue: structure.name,
11286
+ placeholder: structure.name
11399
11287
  });
11400
- if (p8.isCancel(name)) return;
11401
- const description = await p8.text({
11402
- message: "Description:",
11288
+ if (p7.isCancel(name)) {
11289
+ console.log(colors.muted(" Cancelled"));
11290
+ console.log("");
11291
+ return;
11292
+ }
11293
+ const description = await p7.text({
11294
+ message: "Description (optional):",
11403
11295
  placeholder: "A brief description of your project"
11404
11296
  });
11405
- if (p8.isCancel(description)) return;
11406
- const stack = await p8.text({
11297
+ if (p7.isCancel(description)) {
11298
+ console.log(colors.muted(" Cancelled"));
11299
+ console.log("");
11300
+ return;
11301
+ }
11302
+ const stack = await p7.text({
11407
11303
  message: "Tech stack (comma-separated):",
11408
11304
  initialValue: [
11409
11305
  structure.framework,
@@ -11412,7 +11308,11 @@ async function initProject(ctx) {
11412
11308
  "Tailwind"
11413
11309
  ].filter(Boolean).join(", ")
11414
11310
  });
11415
- if (p8.isCancel(stack)) return;
11311
+ if (p7.isCancel(stack)) {
11312
+ console.log(colors.muted(" Cancelled"));
11313
+ console.log("");
11314
+ return;
11315
+ }
11416
11316
  memory.setIdentity(
11417
11317
  name,
11418
11318
  description,
@@ -11455,11 +11355,11 @@ async function undoChanges(ctx, steps) {
11455
11355
  const snapshot = snapshots[targetIndex];
11456
11356
  console.log("");
11457
11357
  console.log(colors.white(` Rolling back to: ${snapshot.description}`));
11458
- const confirm8 = await p8.confirm({
11358
+ const confirm7 = await p7.confirm({
11459
11359
  message: "Proceed with rollback?",
11460
11360
  initialValue: false
11461
11361
  });
11462
- if (confirm8 && !p8.isCancel(confirm8)) {
11362
+ if (confirm7 && !p7.isCancel(confirm7)) {
11463
11363
  const restored = await memory.rollback(snapshot.id);
11464
11364
  showSuccess(`Restored ${restored.length} file${restored.length > 1 ? "s" : ""}`);
11465
11365
  }
@@ -11467,11 +11367,11 @@ async function undoChanges(ctx, steps) {
11467
11367
  async function commitChanges(ctx, message) {
11468
11368
  const { git, memory } = ctx;
11469
11369
  if (!await git.isRepo()) {
11470
- const init = await p8.confirm({
11370
+ const init = await p7.confirm({
11471
11371
  message: "No git repo found. Initialize one?",
11472
11372
  initialValue: true
11473
11373
  });
11474
- if (init && !p8.isCancel(init)) {
11374
+ if (init && !p7.isCancel(init)) {
11475
11375
  await git.init();
11476
11376
  showSuccess("Git repository initialized");
11477
11377
  } else {
@@ -11496,13 +11396,13 @@ async function deployProject(ctx) {
11496
11396
  showError("Vercel CLI not installed. Run: npm i -g vercel");
11497
11397
  return;
11498
11398
  }
11499
- const prod = await p8.confirm({
11399
+ const prod = await p7.confirm({
11500
11400
  message: "Deploy to production?",
11501
11401
  initialValue: false
11502
11402
  });
11503
11403
  const spinner = ora3("Deploying...").start();
11504
11404
  try {
11505
- const result = await vercel.deploy(process.cwd(), prod && !p8.isCancel(prod));
11405
+ const result = await vercel.deploy(process.cwd(), prod && !p7.isCancel(prod));
11506
11406
  spinner.stop();
11507
11407
  showSuccess(`Deployed: ${result.url}`);
11508
11408
  } catch (error) {
@@ -11511,78 +11411,108 @@ async function deployProject(ctx) {
11511
11411
  }
11512
11412
  }
11513
11413
  async function reviewCodebase(ctx) {
11514
- const { ai } = ctx;
11515
- const includeAI = await p8.confirm({
11414
+ const { ai, memory } = ctx;
11415
+ console.log("");
11416
+ console.log(colors.white(" \u{1F4CA} Codebase Review"));
11417
+ console.log(colors.muted(" \u2500".repeat(35)));
11418
+ console.log("");
11419
+ const includeAI = await p7.confirm({
11516
11420
  message: "Include AI deep analysis? (slower but more thorough)",
11517
11421
  initialValue: true
11518
11422
  });
11519
- if (p8.isCancel(includeAI)) return;
11423
+ if (p7.isCancel(includeAI)) {
11424
+ console.log(colors.muted(" Review cancelled"));
11425
+ console.log("");
11426
+ return;
11427
+ }
11520
11428
  const analyzer = new CodebaseAnalyzer(
11521
11429
  process.cwd(),
11522
11430
  includeAI ? ai : void 0
11523
11431
  );
11524
- const spinner = ora3("Starting codebase review...").start();
11432
+ console.log("");
11433
+ const spinner = ora3({
11434
+ text: "Starting codebase review...",
11435
+ spinner: "dots"
11436
+ }).start();
11525
11437
  try {
11526
11438
  const report = await analyzer.fullReview((step, progress) => {
11527
11439
  spinner.text = `${step} (${Math.round(progress)}%)`;
11528
11440
  });
11529
- spinner.stop();
11441
+ spinner.succeed("Review complete!");
11442
+ console.log("");
11530
11443
  const formatted = analyzer.formatReport(report);
11531
11444
  console.log(formatted);
11532
- if (report.byCategory.patterns.length > 0) {
11533
- const fix = await p8.confirm({
11534
- message: `Fix ${report.byCategory.patterns.length} pattern violations automatically?`,
11535
- initialValue: false
11445
+ console.log(colors.muted(" \u2500".repeat(35)));
11446
+ console.log("");
11447
+ const hasIssues = report.summary.totalViolations > 0 || report.summary.totalIssues > 0;
11448
+ if (!hasIssues) {
11449
+ console.log(colors.success(" \u2713 Your codebase looks healthy!"));
11450
+ console.log("");
11451
+ console.log(colors.muted(" What you can do next:"));
11452
+ console.log(colors.dim(" \u2022 /audit Run a deeper audit with auto-fix"));
11453
+ console.log(colors.dim(" \u2022 /spec Generate a product spec"));
11454
+ console.log(colors.dim(" \u2022 /git Check your git status"));
11455
+ console.log("");
11456
+ } else {
11457
+ console.log(colors.warning(` Found ${report.summary.totalViolations + report.summary.totalIssues} issues to address`));
11458
+ console.log("");
11459
+ const action = await p7.select({
11460
+ message: "What would you like to do?",
11461
+ options: [
11462
+ {
11463
+ value: "fix",
11464
+ label: "\u{1F527} Auto-fix pattern violations",
11465
+ hint: `Fix ${report.byCategory.patterns.length} violations`
11466
+ },
11467
+ {
11468
+ value: "audit",
11469
+ label: "\u{1F50D} Run detailed audit",
11470
+ hint: "Get line-by-line fixes"
11471
+ },
11472
+ {
11473
+ value: "export",
11474
+ label: "\u{1F4C4} Export report",
11475
+ hint: "Save to file"
11476
+ },
11477
+ {
11478
+ value: "skip",
11479
+ label: "\u2190 Back to main",
11480
+ hint: "Address later"
11481
+ }
11482
+ ]
11536
11483
  });
11537
- if (fix && !p8.isCancel(fix)) {
11538
- showInfo("Use `/fix` command to auto-fix pattern violations (coming soon)");
11484
+ if (p7.isCancel(action) || action === "skip") {
11485
+ console.log("");
11486
+ console.log(colors.muted(" You can run /audit anytime to fix issues"));
11487
+ console.log("");
11488
+ return;
11489
+ }
11490
+ if (action === "fix") {
11491
+ console.log("");
11492
+ console.log(colors.muted(" Starting auto-fix..."));
11493
+ await handleAudit("--fix", ctx);
11494
+ } else if (action === "audit") {
11495
+ await handleAudit("", ctx);
11496
+ } else if (action === "export") {
11497
+ const reportPath = path18.join(process.cwd(), "codebase-review.md");
11498
+ await fs17.writeFile(reportPath, formatted.replace(/\x1b\[[0-9;]*m/g, ""));
11499
+ console.log("");
11500
+ showSuccess(`Report saved to ${reportPath}`);
11539
11501
  }
11540
11502
  }
11503
+ memory.addDecision(`Codebase reviewed: ${report.summary.healthScore}/100 health score, ${report.summary.totalViolations} violations`);
11504
+ await memory.save();
11541
11505
  } catch (error) {
11542
- spinner.stop();
11543
- showError(error instanceof Error ? error.message : "Review failed");
11544
- }
11545
- }
11546
- async function runSetup(config) {
11547
- console.log("");
11548
- console.log(colors.white(" Setup CodeBakers CLI"));
11549
- divider();
11550
- const apiKey = await p8.text({
11551
- message: "Anthropic API key:",
11552
- placeholder: "sk-ant-...",
11553
- validate: (value) => {
11554
- if (!value) return "Required";
11555
- if (!value.startsWith("sk-ant-")) return "Invalid format";
11556
- }
11557
- });
11558
- if (p8.isCancel(apiKey)) return;
11559
- config.setAnthropicKey(apiKey);
11560
- const setupGithub = await p8.confirm({
11561
- message: "Set up GitHub token? (optional)",
11562
- initialValue: false
11563
- });
11564
- if (setupGithub && !p8.isCancel(setupGithub)) {
11565
- const token = await p8.text({
11566
- message: "GitHub token:",
11567
- placeholder: "ghp_..."
11568
- });
11569
- if (!p8.isCancel(token) && token) {
11570
- config.setGithubToken(token);
11571
- }
11572
- }
11573
- const setupVercel = await p8.confirm({
11574
- message: "Set up Vercel token? (optional)",
11575
- initialValue: false
11576
- });
11577
- if (setupVercel && !p8.isCancel(setupVercel)) {
11578
- const token = await p8.text({
11579
- message: "Vercel token:"
11580
- });
11581
- if (!p8.isCancel(token) && token) {
11582
- config.setVercelToken(token);
11583
- }
11506
+ spinner.fail("Review failed");
11507
+ console.log("");
11508
+ showError(error instanceof Error ? error.message : "Unknown error");
11509
+ console.log("");
11510
+ console.log(colors.muted(" Try:"));
11511
+ console.log(colors.dim(" \u2022 Check if you're in a project directory"));
11512
+ console.log(colors.dim(" \u2022 Run /status to verify your setup"));
11513
+ console.log(colors.dim(' \u2022 Run without AI: answer "no" to deep analysis'));
11514
+ console.log("");
11584
11515
  }
11585
- showSuccess("Setup complete!");
11586
11516
  }
11587
11517
  function showHelp() {
11588
11518
  console.log(`
@@ -11602,33 +11532,78 @@ function showHelp() {
11602
11532
  `);
11603
11533
  }
11604
11534
  function showCommandHelp() {
11605
- console.log(`
11606
- ${colors.white("Modes (Shift+Tab to cycle):")}
11607
- [PLAN] Shows what will happen, then asks to execute
11608
- [QUESTION] Asks clarifying questions first
11609
- [AGENT] Runs autonomously without asking
11610
-
11611
- ${colors.white("Commands:")}
11612
- /spec <idea> \u{1F4C4} Generate spec + build from it
11613
- /parallel <req> \u{1F680} Parallel build (3x faster)
11614
- /create <name> One-click: GitHub + Supabase + Vercel
11615
- /templates List available templates
11616
- /install <id> Install a template
11617
- /test [file] Generate tests
11618
- /pr [title] Create pull request
11619
- /preview Start dev server
11620
- /review Codebase review
11621
- /run <cmd> Terminal command
11622
- /status Project status
11623
- /undo Rollback changes
11624
- /commit Git commit
11625
- /deploy Vercel deploy
11626
- /quit Exit
11627
-
11628
- ${colors.white("Examples:")}
11629
- /spec An Uber for dog walkers
11630
- /parallel Build auth + dashboard
11631
- ./design.png \u2192 Build from image
11632
- `);
11535
+ console.log("");
11536
+ console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
11537
+ console.log(colors.muted(" \u2502") + colors.white(" Just tell me what you want ") + colors.muted("\u2502"));
11538
+ console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
11539
+ console.log("");
11540
+ console.log(colors.primary(" Build & Create"));
11541
+ console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11542
+ console.log(colors.white(' "Add a login page with Google auth"'));
11543
+ console.log(colors.white(' "Build login, dashboard, and settings"'));
11544
+ console.log(colors.white(' "I want to build an Uber for dog walking"'));
11545
+ console.log("");
11546
+ console.log(colors.primary(" Review & Fix"));
11547
+ console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11548
+ console.log(colors.white(' "Check my code for issues"'));
11549
+ console.log(colors.white(` "What's wrong with this project?"`));
11550
+ console.log(colors.white(' "Fix the problems you find"'));
11551
+ console.log("");
11552
+ console.log(colors.primary(" Git & Deploy"));
11553
+ console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11554
+ console.log(colors.white(' "Commit my changes"'));
11555
+ console.log(colors.white(' "Push to GitHub"'));
11556
+ console.log(colors.white(' "Deploy to Vercel"'));
11557
+ console.log("");
11558
+ console.log(colors.primary(" Other"));
11559
+ console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11560
+ console.log(colors.white(' "Generate tests"') + colors.dim(' "Start dev server"'));
11561
+ console.log(colors.white(' "Create a PR"') + colors.dim(' "Undo that"'));
11562
+ console.log("");
11563
+ console.log(colors.dim(" Shortcuts: /help /quit /status /login"));
11564
+ console.log("");
11565
+ }
11566
+ function getSuggestions(input) {
11567
+ const lowered = input.toLowerCase();
11568
+ const suggestions = [];
11569
+ const mappings = {
11570
+ "reveiw": ["/review - Review your codebase"],
11571
+ "reviw": ["/review - Review your codebase"],
11572
+ "aduit": ["/audit - Audit code quality"],
11573
+ "auidt": ["/audit - Audit code quality"],
11574
+ "hlep": ["/help - Show all commands"],
11575
+ "helo": ["/help - Show all commands"],
11576
+ "init": ["/init - Initialize project"],
11577
+ "start": ["/preview - Start dev server", "/init - Initialize project"],
11578
+ "run": ["/run <command> - Run a terminal command"],
11579
+ "fix": ["/audit fix - Auto-fix issues"],
11580
+ "check": ["/audit - Check code quality", "/review - Review codebase"],
11581
+ "test": ["/test - Generate tests"],
11582
+ "build": ['"Build a login page" - Describe what you want'],
11583
+ "make": ['"Make a navbar" - Describe what you want'],
11584
+ "create": ["/create - Create new project", '"Create a dashboard" - Describe it'],
11585
+ "git": ["/git status - Check git", "/git commit - Commit changes"],
11586
+ "commit": ["/commit - Commit changes", '/git commit -m "message"'],
11587
+ "push": ["/git push - Push to remote"],
11588
+ "status": ["/status - Project status", "/git status - Git status"],
11589
+ "login": ["/login - Login to CodeBakers"],
11590
+ "logout": ["/logout - Logout"]
11591
+ };
11592
+ for (const [key, vals] of Object.entries(mappings)) {
11593
+ if (lowered.includes(key) || key.includes(lowered)) {
11594
+ suggestions.push(...vals);
11595
+ }
11596
+ }
11597
+ if (suggestions.length === 0) {
11598
+ if (lowered.length < 3) {
11599
+ suggestions.push("/help - Show all commands");
11600
+ } else {
11601
+ suggestions.push(
11602
+ '"' + input + '" - Try describing what you want to build',
11603
+ "/help - Show all commands"
11604
+ );
11605
+ }
11606
+ }
11607
+ return [...new Set(suggestions)].slice(0, 4);
11633
11608
  }
11634
11609
  main().catch(console.error);