codebakers 3.1.0 → 4.1.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 +1524 -1158
  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 p6 from "@clack/prompts";
9
+ import * as p7 from "@clack/prompts";
10
10
  import ora3 from "ora";
11
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((p7) => p7.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 text3 = line.replace(/^[-•\d.]\s*/, "").trim();
1503
- if (text3 && currentSection === "issues") {
1504
- issues.push(text3);
1505
- } else if (text3 && currentSection === "suggestions") {
1506
- suggestions.push(text3);
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 (p7) => {
1576
- const f = await this.loadFile(p7);
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,72 +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
- function showProjectStatus(name, framework) {
1769
- 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"));
1770
- console.log(colors.white(` \u{1F4C1} ${name}`) + colors.muted(` (${framework || "Unknown"})`));
1771
- 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"));
1772
- console.log("");
1773
- }
1774
2125
  function showSuccess(message) {
1775
- console.log(` ${sym.check} ${colors.white(message)}`);
2126
+ console.log(` ${sym.check} ${colors.success(message)}`);
1776
2127
  }
1777
2128
  function showError(message) {
1778
2129
  console.log(` ${sym.cross} ${colors.error(message)}`);
1779
2130
  }
1780
2131
  function showWarning(message) {
1781
- console.log(` ${colors.warning("\u26A0")} ${colors.warning(message)}`);
2132
+ console.log(` ${colors.warning(sym.warning)} ${colors.warning(message)}`);
1782
2133
  }
1783
2134
  function showInfo(message) {
1784
- console.log(colors.muted(` \u2139\uFE0F ${message}`));
2135
+ console.log(` ${colors.info(sym.info)} ${colors.info(message)}`);
1785
2136
  }
1786
2137
  function divider() {
1787
- 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"));
1788
- }
1789
- var promptSymbol = colors.primary("\u276F");
1790
- function showWelcome() {
1791
- console.log(colors.muted(" Just tell me what you need:"));
1792
- console.log(colors.muted(' "Add a login page" - Generate code'));
1793
- console.log(colors.muted(' "Review my code" - Analyze codebase'));
1794
- console.log(colors.muted(' "Deploy to production" - Ship it'));
1795
- console.log(colors.muted(' "Undo that" - Rollback'));
1796
- console.log(colors.muted(" /help - All commands"));
1797
- console.log("");
2138
+ console.log(colors.muted(` ${box.horizontal.repeat(50)}`));
1798
2139
  }
2140
+ var promptSymbol = colors.primary("\u203A");
1799
2141
 
1800
2142
  // src/core/nlp.ts
1801
2143
  var INTENT_PATTERNS = [
@@ -2411,7 +2753,11 @@ var NLPInterpreter = class {
2411
2753
  "account": "whoami",
2412
2754
  "me": "whoami",
2413
2755
  "index": "index",
2414
- "rag": "index"
2756
+ "rag": "index",
2757
+ "folder": "folder",
2758
+ "cd": "folder",
2759
+ "chdir": "folder",
2760
+ "browse": "folder"
2415
2761
  };
2416
2762
  const intent = commandMap[lower] || "unclear";
2417
2763
  const params = {};
@@ -2748,21 +3094,21 @@ Please provide ONLY the corrected file content with the path. No explanation nee
2748
3094
  // ============================================================================
2749
3095
  async findMissingPackages(packages) {
2750
3096
  const fs18 = await import("fs-extra");
2751
- const path17 = await import("path");
2752
- const pkgPath = path17.join(this.scanner["projectPath"], "package.json");
3097
+ const path19 = await import("path");
3098
+ const pkgPath = path19.join(this.scanner["projectPath"], "package.json");
2753
3099
  if (!await fs18.pathExists(pkgPath)) return packages;
2754
3100
  const pkg = await fs18.readJson(pkgPath);
2755
3101
  const installed = {
2756
3102
  ...pkg.dependencies,
2757
3103
  ...pkg.devDependencies
2758
3104
  };
2759
- return packages.filter((p7) => !installed[p7]);
3105
+ return packages.filter((p8) => !installed[p8]);
2760
3106
  }
2761
- spin(text3) {
3107
+ spin(text5) {
2762
3108
  if (this.spinner) {
2763
- this.spinner.text = text3 + " (ESC to cancel)";
3109
+ this.spinner.text = text5 + " (ESC to cancel)";
2764
3110
  } else {
2765
- this.spinner = ora(text3 + " (ESC to cancel)").start();
3111
+ this.spinner = ora(text5 + " (ESC to cancel)").start();
2766
3112
  }
2767
3113
  }
2768
3114
  stop() {
@@ -4080,9 +4426,9 @@ Create ALL files listed above.`;
4080
4426
  const count = pathMap.get(file.path) || 0;
4081
4427
  pathMap.set(file.path, count + 1);
4082
4428
  }
4083
- for (const [path17, count] of pathMap) {
4429
+ for (const [path19, count] of pathMap) {
4084
4430
  if (count > 1) {
4085
- conflicts.push(`Multiple agents created: ${path17}`);
4431
+ conflicts.push(`Multiple agents created: ${path19}`);
4086
4432
  }
4087
4433
  }
4088
4434
  for (const file of files) {
@@ -4421,574 +4767,46 @@ function getModeManager() {
4421
4767
 
4422
4768
  // src/core/plan-executor.ts
4423
4769
  import Anthropic3 from "@anthropic-ai/sdk";
4424
- var PlanExecutor = class {
4770
+
4771
+ // src/core/question-executor.ts
4772
+ import * as p2 from "@clack/prompts";
4773
+ import Anthropic4 from "@anthropic-ai/sdk";
4774
+
4775
+ // src/core/agent-executor.ts
4776
+ import ora2 from "ora";
4777
+
4778
+ // src/core/input-handler.ts
4779
+ import fs9 from "fs-extra";
4780
+ import path9 from "path";
4781
+
4782
+ // src/core/prd-parser.ts
4783
+ import Anthropic5 from "@anthropic-ai/sdk";
4784
+ var PRDParser = class {
4425
4785
  anthropic;
4426
4786
  memory;
4427
- scanner;
4428
- constructor(config, memory, scanner) {
4787
+ constructor(config, memory) {
4429
4788
  const apiKey = config.getAnthropicKey();
4430
4789
  if (!apiKey) throw new Error("API key not configured");
4431
- this.anthropic = new Anthropic3({ apiKey });
4790
+ this.anthropic = new Anthropic5({ apiKey });
4432
4791
  this.memory = memory;
4433
- this.scanner = scanner;
4434
4792
  }
4435
- async createPlan(request) {
4436
- const projectContext = await this.scanner.getContextString([]);
4437
- const prompt = `You are planning a coding task. DO NOT write any code yet - just create a plan.
4438
-
4439
- PROJECT CONTEXT:
4440
- ${projectContext}
4441
-
4442
- USER REQUEST:
4443
- ${request}
4444
-
4445
- Create a detailed plan. Respond with ONLY valid JSON:
4446
- {
4447
- "description": "Brief summary of what will be built",
4448
- "filesToCreate": ["src/path/file.ts", ...],
4449
- "filesToModify": ["src/existing/file.ts", ...],
4450
- "packagesToInstall": ["package-name", ...],
4451
- "commandsToRun": ["npm install", "npm run build", ...],
4452
- "estimatedTime": "2-3 minutes"
4453
- }
4454
-
4455
- Be specific about file paths. List ALL files that will be created or modified.`;
4456
- const response = await this.anthropic.messages.create({
4457
- model: "claude-sonnet-4-20250514",
4458
- max_tokens: 2048,
4459
- messages: [{ role: "user", content: prompt }]
4460
- });
4461
- const content = response.content[0]?.type === "text" ? response.content[0].text : "";
4462
- try {
4463
- const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
4464
- return JSON.parse(jsonStr);
4465
- } catch {
4466
- return {
4467
- description: "Unable to parse plan",
4468
- filesToCreate: [],
4469
- filesToModify: [],
4470
- packagesToInstall: [],
4471
- commandsToRun: [],
4472
- estimatedTime: "Unknown"
4473
- };
4793
+ // ============================================================================
4794
+ // PARSE PRD
4795
+ // ============================================================================
4796
+ async parse(prdContent) {
4797
+ const chunks = this.chunkContent(prdContent, 5e4);
4798
+ let analysis;
4799
+ if (chunks.length === 1) {
4800
+ analysis = await this.analyzeChunk(chunks[0], true);
4801
+ } else {
4802
+ analysis = await this.analyzeMultipleChunks(chunks);
4474
4803
  }
4804
+ analysis.phases = this.createPhases(analysis.features);
4805
+ return analysis;
4475
4806
  }
4476
- formatPlan(plan) {
4477
- let output = "\n";
4478
- output += colors.white(" \u{1F4CB} PLAN") + colors.muted(" (not executed yet)") + "\n\n";
4479
- output += colors.muted(" " + plan.description) + "\n\n";
4480
- if (plan.filesToCreate.length > 0) {
4481
- output += colors.white(" Files to create:\n");
4482
- for (const file of plan.filesToCreate) {
4483
- output += colors.success(` + ${file}`) + "\n";
4484
- }
4485
- output += "\n";
4486
- }
4487
- if (plan.filesToModify.length > 0) {
4488
- output += colors.white(" Files to modify:\n");
4489
- for (const file of plan.filesToModify) {
4490
- output += colors.warning(` ~ ${file}`) + "\n";
4491
- }
4492
- output += "\n";
4493
- }
4494
- if (plan.packagesToInstall.length > 0) {
4495
- output += colors.white(" Packages to install:\n");
4496
- for (const pkg of plan.packagesToInstall) {
4497
- output += colors.muted(` + ${pkg}`) + "\n";
4498
- }
4499
- output += "\n";
4500
- }
4501
- if (plan.commandsToRun.length > 0) {
4502
- output += colors.white(" Commands to run:\n");
4503
- for (const cmd of plan.commandsToRun) {
4504
- output += colors.muted(` $ ${cmd}`) + "\n";
4505
- }
4506
- output += "\n";
4507
- }
4508
- output += colors.muted(` Estimated time: ${plan.estimatedTime}`) + "\n";
4509
- return output;
4510
- }
4511
- };
4512
-
4513
- // src/core/question-executor.ts
4514
- import * as p2 from "@clack/prompts";
4515
- import Anthropic4 from "@anthropic-ai/sdk";
4516
- var QuestionExecutor = class {
4517
- anthropic;
4518
- memory;
4519
- scanner;
4520
- constructor(config, memory, scanner) {
4521
- const apiKey = config.getAnthropicKey();
4522
- if (!apiKey) throw new Error("API key not configured");
4523
- this.anthropic = new Anthropic4({ apiKey });
4524
- this.memory = memory;
4525
- this.scanner = scanner;
4526
- }
4527
- async generateQuestions(request) {
4528
- const projectContext = await this.scanner.getContextString([]);
4529
- const memoryContext = this.memory.getFullContext();
4530
- const prompt = `You are about to build something. Before starting, identify what clarifying questions would help you build exactly what the user wants.
4531
-
4532
- PROJECT CONTEXT:
4533
- ${projectContext}
4534
-
4535
- MEMORY (what you already know):
4536
- ${memoryContext}
4537
-
4538
- USER REQUEST:
4539
- ${request}
4540
-
4541
- 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.
4542
-
4543
- Respond with ONLY valid JSON:
4544
- {
4545
- "questions": [
4546
- {
4547
- "id": "auth_type",
4548
- "text": "What type of authentication do you need?",
4549
- "type": "select",
4550
- "options": [
4551
- { "value": "email", "label": "Email/password only" },
4552
- { "value": "oauth", "label": "OAuth (Google, GitHub)" },
4553
- { "value": "both", "label": "Both email and OAuth" }
4554
- ]
4555
- },
4556
- {
4557
- "id": "need_reset",
4558
- "text": "Include password reset flow?",
4559
- "type": "confirm",
4560
- "default": "yes"
4561
- }
4562
- ]
4563
- }
4564
-
4565
- Question types:
4566
- - "select": Multiple choice (provide options)
4567
- - "confirm": Yes/no question
4568
- - "text": Free text input
4569
-
4570
- Only ask ESSENTIAL questions. If you can make a reasonable assumption, do so.`;
4571
- const response = await this.anthropic.messages.create({
4572
- model: "claude-sonnet-4-20250514",
4573
- max_tokens: 2048,
4574
- messages: [{ role: "user", content: prompt }]
4575
- });
4576
- const content = response.content[0]?.type === "text" ? response.content[0].text : "";
4577
- try {
4578
- const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
4579
- const parsed = JSON.parse(jsonStr);
4580
- return parsed.questions || [];
4581
- } catch {
4582
- return [];
4583
- }
4584
- }
4585
- async askQuestions(questions) {
4586
- const answers = {};
4587
- if (questions.length === 0) {
4588
- console.log(colors.muted("\n No clarification needed - proceeding with build.\n"));
4589
- return { questions: [], answers: {} };
4590
- }
4591
- console.log("\n" + colors.secondary(" \u{1F914} A few questions first:") + "\n");
4592
- for (const question of questions) {
4593
- let answer;
4594
- switch (question.type) {
4595
- case "select":
4596
- answer = await p2.select({
4597
- message: question.text,
4598
- options: question.options?.map((opt) => ({
4599
- value: opt.value,
4600
- label: opt.label
4601
- })) || []
4602
- });
4603
- break;
4604
- case "confirm":
4605
- answer = await p2.confirm({
4606
- message: question.text,
4607
- initialValue: question.default === "yes"
4608
- });
4609
- break;
4610
- case "text":
4611
- default:
4612
- answer = await p2.text({
4613
- message: question.text,
4614
- placeholder: question.default
4615
- });
4616
- break;
4617
- }
4618
- if (p2.isCancel(answer)) {
4619
- return { questions, answers };
4620
- }
4621
- answers[question.id] = String(answer);
4622
- }
4623
- return { questions, answers };
4624
- }
4625
- formatAnswersForPrompt(result) {
4626
- if (Object.keys(result.answers).length === 0) {
4627
- return "";
4628
- }
4629
- let context = "\n\nUSER PREFERENCES (from clarifying questions):\n";
4630
- for (const question of result.questions) {
4631
- const answer = result.answers[question.id];
4632
- if (answer) {
4633
- context += `- ${question.text}: ${answer}
4634
- `;
4635
- }
4636
- }
4637
- return context;
4638
- }
4639
- showSummary(result) {
4640
- if (Object.keys(result.answers).length === 0) return;
4641
- console.log("\n" + colors.muted(" Got it! Building with:"));
4642
- for (const question of result.questions) {
4643
- const answer = result.answers[question.id];
4644
- if (answer) {
4645
- let displayAnswer = answer;
4646
- if (question.type === "select" && question.options) {
4647
- const option = question.options.find((o) => o.value === answer);
4648
- if (option) displayAnswer = option.label;
4649
- } else if (question.type === "confirm") {
4650
- displayAnswer = answer === "true" ? "Yes" : "No";
4651
- }
4652
- console.log(colors.muted(` \u2022 ${displayAnswer}`));
4653
- }
4654
- }
4655
- console.log("");
4656
- }
4657
- };
4658
-
4659
- // src/core/agent-executor.ts
4660
- import ora2 from "ora";
4661
- var AgentExecutor = class {
4662
- ai;
4663
- config;
4664
- memory;
4665
- scanner;
4666
- editor;
4667
- spinner = null;
4668
- paused = false;
4669
- cancelled = false;
4670
- constructor(config, ai, memory, scanner, editor) {
4671
- this.config = config;
4672
- this.ai = ai;
4673
- this.memory = memory;
4674
- this.scanner = scanner;
4675
- this.editor = editor;
4676
- }
4677
- // ============================================================================
4678
- // MAIN EXECUTION
4679
- // ============================================================================
4680
- async execute(request, options = {}) {
4681
- const startTime = Date.now();
4682
- this.paused = false;
4683
- this.cancelled = false;
4684
- const result = {
4685
- success: false,
4686
- filesCreated: [],
4687
- filesModified: [],
4688
- packagesInstalled: [],
4689
- commandsRun: [],
4690
- errors: [],
4691
- duration: 0
4692
- };
4693
- const checkCancelled = () => {
4694
- if (options.checkCancelled?.() || this.cancelled) {
4695
- return true;
4696
- }
4697
- return false;
4698
- };
4699
- console.log("");
4700
- console.log(colors.primary(" \u{1F916} AGENT MODE") + colors.muted(" - Running autonomously"));
4701
- console.log(colors.muted(" Press ESC to pause at any checkpoint"));
4702
- console.log("");
4703
- try {
4704
- const isComplex = this.isComplexRequest(request);
4705
- if (isComplex) {
4706
- return await this.executeParallel(request, options, checkCancelled);
4707
- }
4708
- const orchestrator = new Orchestrator(this.ai, this.editor, this.scanner, this.memory);
4709
- this.spin("Generating code...");
4710
- const genResult = await this.ai.generateStream(
4711
- request,
4712
- {
4713
- onToken: (token) => {
4714
- }
4715
- },
4716
- checkCancelled
4717
- );
4718
- if (checkCancelled()) {
4719
- this.stop();
4720
- return { ...result, cancelled: true, duration: Date.now() - startTime };
4721
- }
4722
- if (this.paused) {
4723
- this.stop();
4724
- return { ...result, paused: true, duration: Date.now() - startTime };
4725
- }
4726
- this.spin("Applying changes...");
4727
- this.editor.parseResponse(genResult.content);
4728
- const changes = this.editor.getPendingChanges();
4729
- for (const change of changes) {
4730
- if (checkCancelled()) {
4731
- this.stop();
4732
- return { ...result, cancelled: true, duration: Date.now() - startTime };
4733
- }
4734
- options.onFile?.(change.path, change.type === "create" ? "create" : "modify");
4735
- if (change.type === "create") {
4736
- result.filesCreated.push(change.path);
4737
- } else {
4738
- result.filesModified.push(change.path);
4739
- }
4740
- this.logStep(`${change.type === "create" ? "Created" : "Modified"} ${change.path}`);
4741
- }
4742
- await this.editor.apply();
4743
- const allContent = changes.map((c) => c.content || "").join("\n");
4744
- const packages = this.detectPackages(allContent);
4745
- if (packages.length > 0 && !checkCancelled()) {
4746
- this.spin("Installing packages...");
4747
- const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
4748
- const terminal = new Terminal2();
4749
- for (const pkg of packages) {
4750
- this.logStep(`Installing ${pkg}...`);
4751
- }
4752
- const installResult = await terminal.npmInstall(packages);
4753
- if (installResult.success) {
4754
- result.packagesInstalled = packages;
4755
- result.commandsRun.push(`npm install ${packages.join(" ")}`);
4756
- }
4757
- }
4758
- if (!checkCancelled()) {
4759
- this.spin("Type checking...");
4760
- const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
4761
- const terminal = new Terminal2();
4762
- const typeResult = await terminal.typecheck();
4763
- result.commandsRun.push("tsc --noEmit");
4764
- if (!typeResult.success) {
4765
- this.logStep("Fixing type errors...");
4766
- }
4767
- }
4768
- this.stop();
4769
- result.success = result.errors.length === 0;
4770
- result.duration = Date.now() - startTime;
4771
- this.showSummary(result);
4772
- for (const file of [...result.filesCreated, ...result.filesModified]) {
4773
- this.memory.addModifiedFile(file);
4774
- }
4775
- await this.memory.save();
4776
- return result;
4777
- } catch (error) {
4778
- this.stop();
4779
- result.errors.push(error instanceof Error ? error.message : "Unknown error");
4780
- result.duration = Date.now() - startTime;
4781
- return result;
4782
- }
4783
- }
4784
- // ============================================================================
4785
- // PARALLEL EXECUTION
4786
- // ============================================================================
4787
- async executeParallel(request, options, checkCancelled) {
4788
- const startTime = Date.now();
4789
- const result = {
4790
- success: false,
4791
- filesCreated: [],
4792
- filesModified: [],
4793
- packagesInstalled: [],
4794
- commandsRun: [],
4795
- errors: [],
4796
- duration: 0
4797
- };
4798
- console.log(colors.muted(" Using parallel agents for faster build..."));
4799
- console.log("");
4800
- const parallelSystem = new ParallelAgentSystem(
4801
- this.config,
4802
- this.memory,
4803
- this.scanner,
4804
- this.editor
4805
- );
4806
- const parallelResult = await parallelSystem.build(
4807
- request,
4808
- {
4809
- onTaskPlan: (tasks) => {
4810
- console.log(colors.muted(` \u{1F4CB} ${tasks.length} tasks planned`));
4811
- },
4812
- onAgentStart: (taskId, name) => {
4813
- console.log(colors.muted(` \u25CF [${name}] Starting...`));
4814
- },
4815
- onAgentComplete: (taskId, agentResult) => {
4816
- if (agentResult.success) {
4817
- console.log(colors.success(` \u2713 [${taskId}] Complete (${agentResult.files.length} files)`));
4818
- } else {
4819
- console.log(colors.error(` \u2717 [${taskId}] Failed`));
4820
- }
4821
- },
4822
- onMergeStart: () => {
4823
- console.log(colors.muted(" \u{1F504} Merging results..."));
4824
- },
4825
- onMergeComplete: (conflicts) => {
4826
- if (conflicts.length === 0) {
4827
- console.log(colors.success(" \u2713 No conflicts"));
4828
- }
4829
- }
4830
- },
4831
- checkCancelled
4832
- );
4833
- if (parallelResult.cancelled) {
4834
- return { ...result, cancelled: true, duration: Date.now() - startTime };
4835
- }
4836
- const changes = this.editor.getPendingChanges();
4837
- await this.editor.apply();
4838
- for (const change of changes) {
4839
- if (change.type === "create") {
4840
- result.filesCreated.push(change.path);
4841
- } else {
4842
- result.filesModified.push(change.path);
4843
- }
4844
- }
4845
- result.success = parallelResult.success;
4846
- result.duration = Date.now() - startTime;
4847
- this.showSummary(result);
4848
- return result;
4849
- }
4850
- // ============================================================================
4851
- // HELPERS
4852
- // ============================================================================
4853
- isComplexRequest(request) {
4854
- const lower = request.toLowerCase();
4855
- const complexIndicators = [
4856
- " and ",
4857
- " with ",
4858
- ", ",
4859
- " + ",
4860
- "full",
4861
- "complete",
4862
- "entire",
4863
- "saas",
4864
- "dashboard",
4865
- "auth",
4866
- "billing",
4867
- "payment"
4868
- ];
4869
- let score = 0;
4870
- for (const indicator of complexIndicators) {
4871
- if (lower.includes(indicator)) score++;
4872
- }
4873
- return score >= 3;
4874
- }
4875
- detectPackages(content) {
4876
- const packages = /* @__PURE__ */ new Set();
4877
- const importRegex = /import\s+.*?from\s+['"]([^'"./][^'"]*)['"]/g;
4878
- let match;
4879
- while ((match = importRegex.exec(content)) !== null) {
4880
- let pkg = match[1];
4881
- if (pkg.startsWith("@")) {
4882
- pkg = pkg.split("/").slice(0, 2).join("/");
4883
- } else {
4884
- pkg = pkg.split("/")[0];
4885
- }
4886
- const builtins = ["fs", "path", "http", "https", "crypto", "stream", "events", "os", "util", "url"];
4887
- if (!builtins.includes(pkg)) {
4888
- packages.add(pkg);
4889
- }
4890
- }
4891
- return Array.from(packages);
4892
- }
4893
- spin(text3) {
4894
- if (this.spinner) {
4895
- this.spinner.text = text3;
4896
- } else {
4897
- this.spinner = ora2(text3).start();
4898
- }
4899
- }
4900
- stop() {
4901
- if (this.spinner) {
4902
- this.spinner.stop();
4903
- this.spinner = null;
4904
- }
4905
- }
4906
- logStep(message) {
4907
- this.stop();
4908
- console.log(colors.muted(` \u25CF ${message}`));
4909
- }
4910
- showSummary(result) {
4911
- console.log("");
4912
- console.log(" " + "\u2550".repeat(50));
4913
- if (result.success) {
4914
- console.log(colors.success(" \u{1F389} AUTONOMOUS BUILD COMPLETE"));
4915
- } else if (result.cancelled) {
4916
- console.log(colors.warning(" \u26A0\uFE0F BUILD CANCELLED"));
4917
- } else if (result.paused) {
4918
- console.log(colors.warning(" \u23F8\uFE0F BUILD PAUSED"));
4919
- } else {
4920
- console.log(colors.error(" \u274C BUILD FAILED"));
4921
- }
4922
- console.log("");
4923
- if (result.filesCreated.length > 0) {
4924
- console.log(colors.muted(` Created: ${result.filesCreated.length} files`));
4925
- }
4926
- if (result.filesModified.length > 0) {
4927
- console.log(colors.muted(` Modified: ${result.filesModified.length} files`));
4928
- }
4929
- if (result.packagesInstalled.length > 0) {
4930
- console.log(colors.muted(` Installed: ${result.packagesInstalled.join(", ")}`));
4931
- }
4932
- const seconds = (result.duration / 1e3).toFixed(1);
4933
- console.log(colors.muted(` Time: ${seconds}s`));
4934
- if (result.errors.length > 0) {
4935
- console.log("");
4936
- console.log(colors.error(" Errors:"));
4937
- for (const error of result.errors) {
4938
- console.log(colors.error(` \u2022 ${error}`));
4939
- }
4940
- }
4941
- console.log("");
4942
- console.log(colors.muted(" All changes saved. Use /undo to rollback."));
4943
- console.log(" " + "\u2550".repeat(50));
4944
- console.log("");
4945
- }
4946
- // ============================================================================
4947
- // CONTROL
4948
- // ============================================================================
4949
- pause() {
4950
- this.paused = true;
4951
- }
4952
- cancel() {
4953
- this.cancelled = true;
4954
- }
4955
- resume() {
4956
- this.paused = false;
4957
- }
4958
- };
4959
-
4960
- // src/core/input-handler.ts
4961
- import fs9 from "fs-extra";
4962
- import path9 from "path";
4963
-
4964
- // src/core/prd-parser.ts
4965
- import Anthropic5 from "@anthropic-ai/sdk";
4966
- var PRDParser = class {
4967
- anthropic;
4968
- memory;
4969
- constructor(config, memory) {
4970
- const apiKey = config.getAnthropicKey();
4971
- if (!apiKey) throw new Error("API key not configured");
4972
- this.anthropic = new Anthropic5({ apiKey });
4973
- this.memory = memory;
4974
- }
4975
- // ============================================================================
4976
- // PARSE PRD
4977
- // ============================================================================
4978
- async parse(prdContent) {
4979
- const chunks = this.chunkContent(prdContent, 5e4);
4980
- let analysis;
4981
- if (chunks.length === 1) {
4982
- analysis = await this.analyzeChunk(chunks[0], true);
4983
- } else {
4984
- analysis = await this.analyzeMultipleChunks(chunks);
4985
- }
4986
- analysis.phases = this.createPhases(analysis.features);
4987
- return analysis;
4988
- }
4989
- chunkContent(content, maxSize) {
4990
- if (content.length <= maxSize) {
4991
- return [content];
4807
+ chunkContent(content, maxSize) {
4808
+ if (content.length <= maxSize) {
4809
+ return [content];
4992
4810
  }
4993
4811
  const chunks = [];
4994
4812
  let remaining = content;
@@ -5199,7 +5017,7 @@ ${chunks[i]}`,
5199
5017
  projectName: analysis.projectName,
5200
5018
  totalFeatures: analysis.features.length,
5201
5019
  completedFeatures: analysis.features.filter((f) => f.status === "complete").length,
5202
- currentPhase: analysis.phases.find((p7) => p7.status === "in_progress")?.id || analysis.phases[0]?.id || "",
5020
+ currentPhase: analysis.phases.find((p8) => p8.status === "in_progress")?.id || analysis.phases[0]?.id || "",
5203
5021
  currentFeature: analysis.features.find((f) => f.status === "in_progress")?.id || "",
5204
5022
  features: analysis.features.map((f) => ({ id: f.id, status: f.status })),
5205
5023
  startedAt: Date.now(),
@@ -6695,8 +6513,8 @@ var SmartGitManager = class {
6695
6513
  const pr = await this.createPR(phaseBranch, baseBranch);
6696
6514
  return pr;
6697
6515
  }
6698
- slugify(text3) {
6699
- return text3.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
6516
+ slugify(text5) {
6517
+ return text5.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
6700
6518
  }
6701
6519
  // ============================================================================
6702
6520
  // SMART COMMITS
@@ -6975,7 +6793,7 @@ var ProjectBuilder = class {
6975
6793
  totalDuration: 0
6976
6794
  };
6977
6795
  try {
6978
- const phaseNames = spec.buildPhases.map((p7) => p7.name.replace(/Phase \d+:\s*/i, ""));
6796
+ const phaseNames = spec.buildPhases.map((p8) => p8.name.replace(/Phase \d+:\s*/i, ""));
6979
6797
  const strategy = await this.git.createProjectStrategy(spec.name, phaseNames);
6980
6798
  console.log(this.git.formatStrategy(strategy));
6981
6799
  const proceed = await p4.confirm({
@@ -7682,7 +7500,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7682
7500
  return this.patterns;
7683
7501
  }
7684
7502
  enablePattern(id) {
7685
- const pattern = this.patterns.find((p7) => p7.id === id);
7503
+ const pattern = this.patterns.find((p8) => p8.id === id);
7686
7504
  if (pattern) {
7687
7505
  pattern.enabled = true;
7688
7506
  return true;
@@ -7690,7 +7508,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7690
7508
  return false;
7691
7509
  }
7692
7510
  disablePattern(id) {
7693
- const pattern = this.patterns.find((p7) => p7.id === id);
7511
+ const pattern = this.patterns.find((p8) => p8.id === id);
7694
7512
  if (pattern) {
7695
7513
  pattern.enabled = false;
7696
7514
  return true;
@@ -7749,8 +7567,8 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7749
7567
  formatPatterns() {
7750
7568
  let output = "\n";
7751
7569
  output += colors.white(" \u{1F4CB} Pattern Rules") + "\n\n";
7752
- const enabled = this.patterns.filter((p7) => p7.enabled);
7753
- const disabled = this.patterns.filter((p7) => !p7.enabled);
7570
+ const enabled = this.patterns.filter((p8) => p8.enabled);
7571
+ const disabled = this.patterns.filter((p8) => !p8.enabled);
7754
7572
  output += colors.success(" ENABLED") + "\n";
7755
7573
  for (const pattern of enabled) {
7756
7574
  const severityIcon = pattern.severity === "critical" ? "\u{1F534}" : pattern.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
@@ -7775,7 +7593,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
7775
7593
  return grouped;
7776
7594
  }
7777
7595
  getPatternName(id) {
7778
- const pattern = this.patterns.find((p7) => p7.id === id);
7596
+ const pattern = this.patterns.find((p8) => p8.id === id);
7779
7597
  return pattern?.name || id;
7780
7598
  }
7781
7599
  // ============================================================================
@@ -7981,7 +7799,7 @@ var SmartSuggestions = class {
7981
7799
  /platform\s*(that|which|for)/,
7982
7800
  /saas\s*(for|that|which)/
7983
7801
  ];
7984
- return ideaPatterns.some((p7) => p7.test(lower));
7802
+ return ideaPatterns.some((p8) => p8.test(lower));
7985
7803
  }
7986
7804
  looksLikeComplexRequest(input) {
7987
7805
  const lower = input.toLowerCase();
@@ -8011,7 +7829,7 @@ var SmartSuggestions = class {
8011
7829
  /clean\s*up/,
8012
7830
  /improve\s*(the\s*)?(code|quality)/
8013
7831
  ];
8014
- return patterns.some((p7) => p7.test(lower));
7832
+ return patterns.some((p8) => p8.test(lower));
8015
7833
  }
8016
7834
  // ============================================================================
8017
7835
  // DISPLAY
@@ -8041,6 +7859,23 @@ var AUTH_STORE = new Conf2({
8041
7859
  projectName: "codebakers",
8042
7860
  encryptionKey: "cb-secure-storage-key-2026"
8043
7861
  });
7862
+ var ADMIN_CODES = {
7863
+ "ADMIN-DANIEL-2026": { email: "daniel@botmakers.ai", name: "Daniel" },
7864
+ "ADMIN-BOTMAKERS": { email: "admin@botmakers.ai", name: "Admin" }
7865
+ // Add more admin codes here
7866
+ };
7867
+ var BETA_CODES = {
7868
+ "BETA-TESTER-001": { email: "tester1@beta.com", name: "Beta Tester 1", expiresAt: "2026-12-31" },
7869
+ "BETA-TESTER-002": { email: "tester2@beta.com", name: "Beta Tester 2", expiresAt: "2026-12-31" },
7870
+ "BETA-EARLY-ACCESS": { email: "early@beta.com", name: "Early Access", expiresAt: "2026-06-30" }
7871
+ // Add more beta codes here
7872
+ };
7873
+ var WHITELISTED_EMAILS = [
7874
+ "daniel@botmakers.ai",
7875
+ "admin@botmakers.ai",
7876
+ "team@botmakers.ai"
7877
+ // Add more whitelisted emails here
7878
+ ];
8044
7879
  var FEATURES = {
8045
7880
  // Free features
8046
7881
  basic_build: "basic_build",
@@ -8057,6 +7892,7 @@ var FEATURES = {
8057
7892
  whitelabel: "whitelabel",
8058
7893
  priority_support: "priority_support"
8059
7894
  };
7895
+ var ALL_FEATURES = Object.values(FEATURES);
8060
7896
  var Auth = class _Auth {
8061
7897
  static instance;
8062
7898
  constructor() {
@@ -8068,7 +7904,7 @@ var Auth = class _Auth {
8068
7904
  return _Auth.instance;
8069
7905
  }
8070
7906
  // ============================================================================
8071
- // LOGIN FLOW
7907
+ // LOGIN FLOW - Multiple methods
8072
7908
  // ============================================================================
8073
7909
  async login() {
8074
7910
  const existing = this.getAuth();
@@ -8084,6 +7920,163 @@ var Auth = class _Auth {
8084
7920
  console.log("");
8085
7921
  console.log(colors.white(" \u{1F510} CodeBakers Login"));
8086
7922
  console.log("");
7923
+ const method = await p5.select({
7924
+ message: "How would you like to login?",
7925
+ options: [
7926
+ { value: "browser", label: "Login with browser (Google/GitHub)" },
7927
+ { value: "code", label: "Enter admin/beta code" },
7928
+ { value: "apikey", label: "Use API key only (no account)" }
7929
+ ]
7930
+ });
7931
+ if (p5.isCancel(method)) {
7932
+ return false;
7933
+ }
7934
+ switch (method) {
7935
+ case "code":
7936
+ return this.loginWithCode();
7937
+ case "apikey":
7938
+ return this.loginWithApiKey();
7939
+ default:
7940
+ return this.loginWithBrowser();
7941
+ }
7942
+ }
7943
+ // ============================================================================
7944
+ // LOGIN WITH SPECIAL CODE (Admin/Beta)
7945
+ // ============================================================================
7946
+ async loginWithCode() {
7947
+ console.log("");
7948
+ console.log(colors.secondary(" Enter your admin or beta code:"));
7949
+ const code = await p5.text({
7950
+ message: "Code:",
7951
+ placeholder: "ADMIN-XXXX or BETA-XXXX"
7952
+ });
7953
+ if (p5.isCancel(code) || !code) {
7954
+ return false;
7955
+ }
7956
+ const codeUpper = code.toString().toUpperCase().trim();
7957
+ if (ADMIN_CODES[codeUpper]) {
7958
+ const admin = ADMIN_CODES[codeUpper];
7959
+ const auth2 = {
7960
+ accessToken: `admin_${codeUpper}_${Date.now()}`,
7961
+ refreshToken: "",
7962
+ user: {
7963
+ id: `admin_${codeUpper}`,
7964
+ email: admin.email,
7965
+ name: admin.name
7966
+ },
7967
+ license: {
7968
+ tier: "admin",
7969
+ features: ALL_FEATURES,
7970
+ expiresAt: null,
7971
+ // Never expires
7972
+ usage: { current: 0, limit: 999999 }
7973
+ },
7974
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1e3,
7975
+ // 1 year
7976
+ authType: "admin"
7977
+ };
7978
+ this.saveAuth(auth2);
7979
+ console.log("");
7980
+ console.log(colors.success(` \u2713 Logged in as Admin: ${admin.name}`));
7981
+ console.log(colors.muted(" Full access enabled. No expiration."));
7982
+ console.log("");
7983
+ return true;
7984
+ }
7985
+ if (BETA_CODES[codeUpper]) {
7986
+ const beta = BETA_CODES[codeUpper];
7987
+ const expiresAt = new Date(beta.expiresAt);
7988
+ if (expiresAt < /* @__PURE__ */ new Date()) {
7989
+ console.log(colors.error(" \u2717 This beta code has expired."));
7990
+ return false;
7991
+ }
7992
+ const auth2 = {
7993
+ accessToken: `beta_${codeUpper}_${Date.now()}`,
7994
+ refreshToken: "",
7995
+ user: {
7996
+ id: `beta_${codeUpper}`,
7997
+ email: beta.email,
7998
+ name: beta.name
7999
+ },
8000
+ license: {
8001
+ tier: "beta",
8002
+ features: ALL_FEATURES,
8003
+ expiresAt: beta.expiresAt,
8004
+ usage: { current: 0, limit: 999999 }
8005
+ },
8006
+ expiresAt: expiresAt.getTime(),
8007
+ authType: "beta"
8008
+ };
8009
+ this.saveAuth(auth2);
8010
+ console.log("");
8011
+ console.log(colors.success(` \u2713 Logged in as Beta Tester: ${beta.name}`));
8012
+ console.log(colors.muted(` Full access until ${beta.expiresAt}`));
8013
+ console.log("");
8014
+ return true;
8015
+ }
8016
+ console.log(colors.error(" \u2717 Invalid code. Please check and try again."));
8017
+ return false;
8018
+ }
8019
+ // ============================================================================
8020
+ // LOGIN WITH API KEY ONLY (No account needed)
8021
+ // ============================================================================
8022
+ async loginWithApiKey() {
8023
+ console.log("");
8024
+ console.log(colors.secondary(" Using API key only mode."));
8025
+ console.log(colors.muted(" You can use CB without an account."));
8026
+ console.log(colors.muted(" Some features may be limited."));
8027
+ console.log("");
8028
+ const apiKey = process.env.ANTHROPIC_API_KEY;
8029
+ if (!apiKey) {
8030
+ console.log(colors.warning(" \u26A0 No ANTHROPIC_API_KEY found in environment."));
8031
+ console.log(colors.muted(" Set it with: export ANTHROPIC_API_KEY=your_key"));
8032
+ console.log("");
8033
+ const enterKey = await p5.confirm({
8034
+ message: "Would you like to enter your API key now?",
8035
+ initialValue: true
8036
+ });
8037
+ if (enterKey && !p5.isCancel(enterKey)) {
8038
+ const key = await p5.text({
8039
+ message: "Anthropic API Key:",
8040
+ placeholder: "sk-ant-..."
8041
+ });
8042
+ if (p5.isCancel(key) || !key) {
8043
+ return false;
8044
+ }
8045
+ AUTH_STORE.set("anthropicApiKey", key);
8046
+ console.log(colors.success(" \u2713 API key saved."));
8047
+ }
8048
+ }
8049
+ const auth2 = {
8050
+ accessToken: `apikey_${Date.now()}`,
8051
+ refreshToken: "",
8052
+ user: {
8053
+ id: "apikey_user",
8054
+ email: "apikey@local",
8055
+ name: "API Key User"
8056
+ },
8057
+ license: {
8058
+ tier: "free",
8059
+ // Free tier features only
8060
+ features: ["basic_build", "memory", "patterns"],
8061
+ expiresAt: null,
8062
+ usage: { current: 0, limit: 50 }
8063
+ },
8064
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1e3,
8065
+ // 30 days
8066
+ authType: "apikey"
8067
+ };
8068
+ this.saveAuth(auth2);
8069
+ console.log("");
8070
+ console.log(colors.success(" \u2713 Using API key only mode."));
8071
+ console.log(colors.muted(" Free tier features enabled."));
8072
+ console.log(colors.muted(' Run "cb login" anytime to upgrade.'));
8073
+ console.log("");
8074
+ return true;
8075
+ }
8076
+ // ============================================================================
8077
+ // LOGIN WITH BROWSER (Original device flow)
8078
+ // ============================================================================
8079
+ async loginWithBrowser() {
8087
8080
  try {
8088
8081
  const deviceCode = await this.requestDeviceCode();
8089
8082
  if (!deviceCode) {
@@ -8112,6 +8105,15 @@ var Auth = class _Auth {
8112
8105
  console.log(colors.error(" Authentication failed or timed out."));
8113
8106
  return false;
8114
8107
  }
8108
+ if (WHITELISTED_EMAILS.includes(auth2.user.email)) {
8109
+ auth2.license = {
8110
+ tier: "admin",
8111
+ features: ALL_FEATURES,
8112
+ expiresAt: null,
8113
+ usage: { current: 0, limit: 999999 }
8114
+ };
8115
+ auth2.authType = "whitelist";
8116
+ }
8115
8117
  this.saveAuth(auth2);
8116
8118
  console.log("");
8117
8119
  console.log(colors.success(` \u2713 Logged in as ${auth2.user.email}`));
@@ -8134,6 +8136,13 @@ var Auth = class _Auth {
8134
8136
  console.log(colors.success(" \u2713 Logged out successfully"));
8135
8137
  }
8136
8138
  // ============================================================================
8139
+ // IS LOGGED IN - Quick check
8140
+ // ============================================================================
8141
+ isLoggedIn() {
8142
+ const auth2 = this.getAuth();
8143
+ return auth2 !== null && !this.isExpired(auth2);
8144
+ }
8145
+ // ============================================================================
8137
8146
  // WHO AM I
8138
8147
  // ============================================================================
8139
8148
  async whoami() {
@@ -8145,16 +8154,26 @@ var Auth = class _Auth {
8145
8154
  console.log("");
8146
8155
  return;
8147
8156
  }
8148
- const freshLicense = await this.verifyLicense();
8149
- if (freshLicense) {
8150
- auth2.license = freshLicense;
8151
- this.saveAuth(auth2);
8157
+ if (auth2.authType === "device") {
8158
+ const freshLicense = await this.verifyLicense();
8159
+ if (freshLicense) {
8160
+ auth2.license = freshLicense;
8161
+ this.saveAuth(auth2);
8162
+ }
8152
8163
  }
8153
8164
  console.log("");
8154
8165
  console.log(colors.white(" \u{1F464} CodeBakers Account"));
8155
8166
  console.log("");
8156
8167
  console.log(colors.secondary(` Email: ${auth2.user.email}`));
8157
8168
  console.log(colors.secondary(` Name: ${auth2.user.name}`));
8169
+ const authTypeLabels = {
8170
+ admin: "\u{1F511} Admin Access",
8171
+ beta: "\u{1F9EA} Beta Tester",
8172
+ apikey: "\u{1F527} API Key Only",
8173
+ whitelist: "\u2B50 Whitelisted",
8174
+ device: "\u{1F310} Standard"
8175
+ };
8176
+ console.log(colors.secondary(` Type: ${authTypeLabels[auth2.authType || "device"] || "Standard"}`));
8158
8177
  console.log("");
8159
8178
  console.log(colors.white(" \u{1F4C4} License"));
8160
8179
  console.log(colors.secondary(` Tier: ${auth2.license.tier.toUpperCase()}`));
@@ -8170,7 +8189,7 @@ var Auth = class _Auth {
8170
8189
  for (const feature of auth2.license.features) {
8171
8190
  console.log(colors.success(` \u2713 ${this.formatFeatureName(feature)}`));
8172
8191
  }
8173
- if (auth2.license.usage) {
8192
+ if (auth2.license.usage && auth2.authType !== "admin") {
8174
8193
  console.log("");
8175
8194
  console.log(colors.white(" \u{1F4CA} Usage This Month"));
8176
8195
  const pct = Math.round(auth2.license.usage.current / auth2.license.usage.limit * 100);
@@ -8673,7 +8692,7 @@ ${issues.map((i) => ` \u2022 ${i}`).join("\n")}` : "\n\nEverything looks good!"
8673
8692
  /^(ugh|argh|damn|shit|fuck)/i
8674
8693
  // Frustration words
8675
8694
  ];
8676
- return frustrationPatterns.some((p7) => p7.test(input));
8695
+ return frustrationPatterns.some((p8) => p8.test(input));
8677
8696
  }
8678
8697
  // ============================================================================
8679
8698
  // RATE LIMITING (Don't be annoying)
@@ -8935,8 +8954,8 @@ ${chunk.content.slice(0, 2e3)}`
8935
8954
  chunk.embedding = this.simpleEmbed(chunk.content + " " + chunk.summary);
8936
8955
  }
8937
8956
  }
8938
- simpleEmbed(text3) {
8939
- const words = text3.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
8957
+ simpleEmbed(text5) {
8958
+ const words = text5.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
8940
8959
  const embedding = new Array(256).fill(0);
8941
8960
  for (const word of words) {
8942
8961
  const hash = this.hashString(word);
@@ -9281,8 +9300,8 @@ ${content.slice(0, 3e3)}`
9281
9300
  for (const feature of commonFeatures) {
9282
9301
  if (!features.has(feature)) {
9283
9302
  const relatedFiles = [...files.entries()].filter(
9284
- ([path17, summary]) => path17.toLowerCase().includes(feature) || summary.summary.toLowerCase().includes(feature)
9285
- ).map(([path17]) => path17);
9303
+ ([path19, summary]) => path19.toLowerCase().includes(feature) || summary.summary.toLowerCase().includes(feature)
9304
+ ).map(([path19]) => path19);
9286
9305
  if (relatedFiles.length > 0) {
9287
9306
  features.set(feature, {
9288
9307
  name: feature,
@@ -9397,7 +9416,7 @@ ${featureList}`
9397
9416
  "Redux": ["@reduxjs/toolkit", "redux"]
9398
9417
  };
9399
9418
  for (const [name, packages] of Object.entries(categories)) {
9400
- if (packages.some((p7) => deps.has(p7))) {
9419
+ if (packages.some((p8) => deps.has(p8))) {
9401
9420
  stack.push(name);
9402
9421
  }
9403
9422
  }
@@ -9443,8 +9462,8 @@ ${featureList}`
9443
9462
  }
9444
9463
  }
9445
9464
  const relevantFiles = [...this.index.files.entries()].filter(
9446
- ([path17, summary]) => queryLower.split(" ").some(
9447
- (word) => word.length > 2 && (path17.toLowerCase().includes(word) || summary.summary.toLowerCase().includes(word))
9465
+ ([path19, summary]) => queryLower.split(" ").some(
9466
+ (word) => word.length > 2 && (path19.toLowerCase().includes(word) || summary.summary.toLowerCase().includes(word))
9448
9467
  )
9449
9468
  ).slice(0, 10);
9450
9469
  if (relevantFiles.length > 0) {
@@ -9529,10 +9548,143 @@ ${featureList}`
9529
9548
  }
9530
9549
  };
9531
9550
 
9551
+ // src/utils/folder-picker.ts
9552
+ import { execSync } from "child_process";
9553
+ import path16 from "path";
9554
+ import os2 from "os";
9555
+ import * as p6 from "@clack/prompts";
9556
+ async function pickFolder(message = "Select a folder") {
9557
+ const platform = os2.platform();
9558
+ try {
9559
+ const folder = await openNativePicker(platform, message);
9560
+ if (folder) {
9561
+ return folder;
9562
+ }
9563
+ } catch (e) {
9564
+ }
9565
+ return manualFolderEntry(message);
9566
+ }
9567
+ async function openNativePicker(platform, title) {
9568
+ return new Promise((resolve) => {
9569
+ let command;
9570
+ let args;
9571
+ if (platform === "win32") {
9572
+ const psScript = `
9573
+ Add-Type -AssemblyName System.Windows.Forms
9574
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
9575
+ $dialog.Description = "${title}"
9576
+ $dialog.ShowNewFolderButton = $true
9577
+ if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
9578
+ Write-Output $dialog.SelectedPath
9579
+ }
9580
+ `;
9581
+ try {
9582
+ const result = execSync(`powershell -Command "${psScript.replace(/\n/g, " ")}"`, {
9583
+ encoding: "utf-8",
9584
+ timeout: 6e4,
9585
+ // 60 second timeout
9586
+ windowsHide: true
9587
+ });
9588
+ const folder = result.trim();
9589
+ resolve(folder || null);
9590
+ } catch {
9591
+ resolve(null);
9592
+ }
9593
+ return;
9594
+ }
9595
+ if (platform === "darwin") {
9596
+ const script = `
9597
+ tell application "System Events"
9598
+ activate
9599
+ set theFolder to choose folder with prompt "${title}"
9600
+ return POSIX path of theFolder
9601
+ end tell
9602
+ `;
9603
+ try {
9604
+ const result = execSync(`osascript -e '${script.replace(/'/g, `'"'"'`)}'`, {
9605
+ encoding: "utf-8",
9606
+ timeout: 6e4
9607
+ });
9608
+ const folder = result.trim();
9609
+ resolve(folder || null);
9610
+ } catch {
9611
+ resolve(null);
9612
+ }
9613
+ return;
9614
+ }
9615
+ if (platform === "linux") {
9616
+ try {
9617
+ const result = execSync(`zenity --file-selection --directory --title="${title}" 2>/dev/null`, {
9618
+ encoding: "utf-8",
9619
+ timeout: 6e4
9620
+ });
9621
+ resolve(result.trim() || null);
9622
+ return;
9623
+ } catch {
9624
+ try {
9625
+ const result = execSync(`kdialog --getexistingdirectory ~ --title "${title}" 2>/dev/null`, {
9626
+ encoding: "utf-8",
9627
+ timeout: 6e4
9628
+ });
9629
+ resolve(result.trim() || null);
9630
+ return;
9631
+ } catch {
9632
+ resolve(null);
9633
+ }
9634
+ }
9635
+ return;
9636
+ }
9637
+ resolve(null);
9638
+ });
9639
+ }
9640
+ async function manualFolderEntry(message) {
9641
+ console.log("");
9642
+ console.log(colors.muted(" \u{1F4A1} Tip: You can drag & drop a folder into the terminal"));
9643
+ console.log("");
9644
+ const homeDir = os2.homedir();
9645
+ const commonPaths = [
9646
+ { label: "\u{1F4C1} Current folder", value: process.cwd() },
9647
+ { label: "\u{1F3E0} Home", value: homeDir },
9648
+ { label: "\u{1F4C2} Desktop", value: path16.join(homeDir, "Desktop") },
9649
+ { label: "\u{1F4C2} Documents", value: path16.join(homeDir, "Documents") },
9650
+ { label: "\u{1F4C2} Downloads", value: path16.join(homeDir, "Downloads") },
9651
+ { label: "\u270F\uFE0F Type custom path", value: "__custom__" }
9652
+ ];
9653
+ const choice = await p6.select({
9654
+ message,
9655
+ options: commonPaths.map((p8) => ({
9656
+ value: p8.value,
9657
+ label: p8.label,
9658
+ hint: p8.value !== "__custom__" ? p8.value : void 0
9659
+ }))
9660
+ });
9661
+ if (p6.isCancel(choice)) {
9662
+ return null;
9663
+ }
9664
+ if (choice === "__custom__") {
9665
+ const customPath = await p6.text({
9666
+ message: "Enter folder path:",
9667
+ placeholder: "/path/to/folder",
9668
+ validate: (value) => {
9669
+ if (!value) return "Path is required";
9670
+ return void 0;
9671
+ }
9672
+ });
9673
+ if (p6.isCancel(customPath)) {
9674
+ return null;
9675
+ }
9676
+ let cleanPath = customPath.trim();
9677
+ cleanPath = cleanPath.replace(/^["']|["']$/g, "");
9678
+ cleanPath = cleanPath.replace(/\\ /g, " ");
9679
+ return cleanPath;
9680
+ }
9681
+ return choice;
9682
+ }
9683
+
9532
9684
  // src/services/git.ts
9533
9685
  import simpleGit from "simple-git";
9534
9686
  import fs16 from "fs-extra";
9535
- import path16 from "path";
9687
+ import path17 from "path";
9536
9688
  var GitService = class {
9537
9689
  git;
9538
9690
  projectPath;
@@ -9541,7 +9693,7 @@ var GitService = class {
9541
9693
  this.git = simpleGit(projectPath);
9542
9694
  }
9543
9695
  async isRepo() {
9544
- return fs16.pathExists(path16.join(this.projectPath, ".git"));
9696
+ return fs16.pathExists(path17.join(this.projectPath, ".git"));
9545
9697
  }
9546
9698
  async init() {
9547
9699
  await this.git.init();
@@ -9652,7 +9804,7 @@ var VercelService = class {
9652
9804
  };
9653
9805
 
9654
9806
  // src/index.ts
9655
- var VERSION = "1.0.0";
9807
+ var VERSION = "4.1.0";
9656
9808
  async function main() {
9657
9809
  const config = new Config();
9658
9810
  const args = process.argv.slice(2);
@@ -9664,18 +9816,13 @@ async function main() {
9664
9816
  showHelp();
9665
9817
  return;
9666
9818
  }
9667
- if (args[0] === "setup") {
9668
- await runSetup(config);
9819
+ if (args[0] === "login") {
9820
+ await auth.login();
9669
9821
  return;
9670
9822
  }
9671
- showHeader();
9672
- if (!config.isConfigured()) {
9673
- showWarning("Not configured yet. Let's set up your API key.");
9674
- await runSetup(config);
9675
- if (!config.isConfigured()) {
9676
- showError("Setup incomplete. Run `cb setup` to configure.");
9677
- return;
9678
- }
9823
+ if (args[0] === "logout") {
9824
+ auth.logout();
9825
+ return;
9679
9826
  }
9680
9827
  const memory = new Memory();
9681
9828
  await memory.init();
@@ -9683,52 +9830,92 @@ async function main() {
9683
9830
  const structure = await scanner.detectProject();
9684
9831
  const editor = new FileEditor();
9685
9832
  const git = new GitService();
9686
- showProjectStatus(structure.name, structure.framework);
9833
+ if (!config.isConfigured() && !auth.isLoggedIn()) {
9834
+ console.log("");
9835
+ console.log(colors.white(" Welcome to CodeBakers"));
9836
+ console.log("");
9837
+ console.log(colors.muted(" You need an API key to continue."));
9838
+ console.log("");
9839
+ const apiKey = await p7.text({
9840
+ message: "Anthropic API Key:",
9841
+ placeholder: "sk-ant-api..."
9842
+ });
9843
+ if (p7.isCancel(apiKey) || !apiKey) {
9844
+ console.log("");
9845
+ console.log(colors.muted(" Get your key at: https://console.anthropic.com"));
9846
+ console.log(colors.muted(" Then run: cb"));
9847
+ console.log("");
9848
+ return;
9849
+ }
9850
+ config.setAnthropicKey(apiKey);
9851
+ console.log(colors.success(" \u2713 API key saved"));
9852
+ console.log("");
9853
+ }
9687
9854
  let ai;
9688
9855
  try {
9689
9856
  ai = new AIEngine(config, memory, scanner);
9690
9857
  } catch (error) {
9691
- showError(error instanceof Error ? error.message : "Failed to initialize AI");
9858
+ console.log("");
9859
+ console.log(colors.error(` \u2717 ${error instanceof Error ? error.message : "Failed to start"}`));
9860
+ console.log("");
9861
+ console.log(colors.muted(" Check your API key with: cb login"));
9862
+ console.log("");
9692
9863
  return;
9693
9864
  }
9694
9865
  const nlp2 = new NLPInterpreter();
9695
9866
  const modeManager = getModeManager();
9696
9867
  const suggestions = new SmartSuggestions(memory, scanner);
9697
9868
  const proactive = new ProactiveAssistant(memory, scanner, config);
9698
- const startupAction = await proactive.onStartup();
9699
- if (startupAction) {
9700
- console.log(proactive.formatAction(startupAction));
9701
- proactive.markSuggestionShown();
9702
- }
9703
- const identity = memory.getIdentity();
9704
- if (!identity.name) {
9705
- showInfo('Project not initialized. Say "initialize project" or use /init');
9706
- }
9707
- showWelcome();
9708
- console.log(colors.muted(" Modes: " + modeManager.getStatusBar()));
9869
+ console.clear();
9709
9870
  console.log("");
9871
+ 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"));
9872
+ console.log(colors.muted(" \u2502") + colors.primary(" \u2B21 C O D E B A K E R S ") + colors.muted("\u2502"));
9873
+ console.log(colors.muted(" \u2502") + colors.dim(` v${VERSION} \u2022 58 patterns \u2022 auto-parallel `) + colors.muted("\u2502"));
9874
+ 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"));
9875
+ console.log("");
9876
+ console.log(colors.muted(" \u{1F4C1} ") + colors.white(structure.name) + (structure.framework ? colors.dim(` (${structure.framework})`) : ""));
9877
+ console.log("");
9878
+ console.log(colors.dim(" Just tell me what you want to build or do."));
9879
+ console.log(colors.dim(" Type /help to see examples."));
9880
+ console.log("");
9881
+ const terminalHeight = process.stdout.rows || 24;
9882
+ const paddingLines = Math.max(0, terminalHeight - 14);
9883
+ console.log("\n".repeat(paddingLines));
9710
9884
  let lastAction;
9711
9885
  let running = true;
9712
9886
  while (running) {
9713
- const modePrompt = modeManager.getPrompt();
9714
- const input = await p6.text({
9715
- message: modePrompt,
9716
- placeholder: "What would you like to build? (Shift+Tab to change mode)"
9887
+ const input = await p7.text({
9888
+ message: colors.primary(">"),
9889
+ placeholder: 'e.g. "Add a login page" or "Check my code"'
9717
9890
  });
9718
- if (p6.isCancel(input)) {
9891
+ if (p7.isCancel(input)) {
9719
9892
  running = false;
9720
- showInfo("Goodbye! \u{1F44B}");
9893
+ console.log("");
9894
+ console.log(colors.muted(" Goodbye! \u{1F44B}"));
9895
+ console.log("");
9721
9896
  break;
9722
9897
  }
9723
9898
  const cmd = input.trim();
9724
9899
  if (!cmd) continue;
9725
- const suggestion = await suggestions.checkForSuggestions(cmd, lastAction);
9726
- if (suggestion) {
9727
- console.log(suggestions.formatSuggestion(suggestion));
9900
+ const smartSuggestion = await suggestions.checkForSuggestions(cmd, lastAction);
9901
+ if (smartSuggestion) {
9902
+ console.log(suggestions.formatSuggestion(smartSuggestion));
9728
9903
  }
9729
9904
  const result = await nlp2.interpret(cmd);
9730
9905
  if (result.type === "unclear") {
9731
- showWarning("I didn't understand that. Try rephrasing or type /help for commands.");
9906
+ console.log("");
9907
+ console.log(colors.muted(" I'm not sure what you want to do."));
9908
+ console.log("");
9909
+ const cmdSuggestions = getSuggestions(cmd);
9910
+ if (cmdSuggestions.length > 0) {
9911
+ console.log(colors.muted(" Did you mean:"));
9912
+ cmdSuggestions.forEach((s) => {
9913
+ console.log(colors.dim(` \u2022 ${s}`));
9914
+ });
9915
+ console.log("");
9916
+ }
9917
+ console.log(colors.dim(" Type /help for a list of commands"));
9918
+ console.log("");
9732
9919
  continue;
9733
9920
  }
9734
9921
  if (["undo", "clear", "deploy"].includes(result.type)) {
@@ -9739,7 +9926,9 @@ async function main() {
9739
9926
  switch (result.type) {
9740
9927
  case "quit":
9741
9928
  running = false;
9742
- showInfo("Goodbye! \u{1F44B}");
9929
+ console.log("");
9930
+ console.log(colors.muted(" Goodbye! \u{1F44B}"));
9931
+ console.log("");
9743
9932
  break;
9744
9933
  case "help":
9745
9934
  showCommandHelp();
@@ -9772,9 +9961,13 @@ async function main() {
9772
9961
  await memory.save();
9773
9962
  showSuccess(`Decision recorded: ${result.params.content}`);
9774
9963
  } else {
9775
- showInfo("What decision would you like to record?");
9776
- const decision = await p6.text({ message: "Decision:" });
9777
- if (!p6.isCancel(decision) && decision) {
9964
+ const decision = await p7.text({
9965
+ message: "What decision would you like to record?",
9966
+ placeholder: "e.g., Use Supabase for auth"
9967
+ });
9968
+ if (p7.isCancel(decision)) {
9969
+ console.log(colors.muted(" Cancelled"));
9970
+ } else if (decision) {
9778
9971
  memory.addDecision(decision);
9779
9972
  await memory.save();
9780
9973
  showSuccess(`Decision recorded: ${decision}`);
@@ -9787,9 +9980,13 @@ async function main() {
9787
9980
  await memory.save();
9788
9981
  showSuccess(`Rule added: ${result.params.content}`);
9789
9982
  } else {
9790
- showInfo("What rule would you like to add?");
9791
- const rule = await p6.text({ message: "Rule:" });
9792
- if (!p6.isCancel(rule) && rule) {
9983
+ const rule = await p7.text({
9984
+ message: "What rule would you like to add?",
9985
+ placeholder: "e.g., Always use TypeScript strict mode"
9986
+ });
9987
+ if (p7.isCancel(rule)) {
9988
+ console.log(colors.muted(" Cancelled"));
9989
+ } else if (rule) {
9793
9990
  memory.addCustomRule(rule);
9794
9991
  await memory.save();
9795
9992
  showSuccess(`Rule added: ${rule}`);
@@ -9805,8 +10002,10 @@ async function main() {
9805
10002
  if (result.params?.command) {
9806
10003
  await handleRunCommand(result.params.command, ctx);
9807
10004
  } else {
9808
- showInfo("Usage: /run <command>");
9809
- showInfo("Example: /run npm install lodash");
10005
+ console.log("");
10006
+ console.log(colors.muted(" Usage: /run <command>"));
10007
+ console.log(colors.dim(" Example: /run npm install lodash"));
10008
+ console.log("");
9810
10009
  }
9811
10010
  break;
9812
10011
  case "create":
@@ -9854,73 +10053,175 @@ async function main() {
9854
10053
  case "index":
9855
10054
  await handleIndex(ctx);
9856
10055
  break;
10056
+ case "folder":
10057
+ await handleFolderChange();
10058
+ break;
9857
10059
  case "generate":
9858
10060
  default:
9859
10061
  const inputHandler = new InputHandler(config, memory, scanner);
9860
10062
  const detected = await inputHandler.detectAndProcess(cmd);
9861
10063
  if (detected.type !== "text") {
9862
10064
  console.log(inputHandler.formatDetection(detected));
9863
- const proceed = await p6.confirm({
10065
+ const proceed = await p7.confirm({
9864
10066
  message: `Process this ${detected.type}?`,
9865
10067
  initialValue: true
9866
10068
  });
9867
- if (!proceed || p6.isCancel(proceed)) break;
10069
+ if (!proceed || p7.isCancel(proceed)) {
10070
+ console.log(colors.muted(" Cancelled"));
10071
+ break;
10072
+ }
9868
10073
  const enhancedCmd = inputHandler.enhancePrompt(detected, cmd);
9869
- await handleGeneration(enhancedCmd, ctx);
10074
+ await handleAutomaticBuild(enhancedCmd, ctx);
9870
10075
  break;
9871
10076
  }
9872
- const currentMode = getModeManager().getMode();
9873
- if (currentMode === "plan") {
9874
- await handlePlanMode(cmd, ctx);
9875
- } else if (currentMode === "question") {
9876
- await handleQuestionMode(cmd, ctx);
9877
- } else if (currentMode === "agent") {
9878
- await handleAgentMode(cmd, ctx);
9879
- } else {
9880
- const isComplex = detectComplexRequest(cmd);
9881
- if (isComplex) {
9882
- console.log("");
9883
- console.log(colors.secondary(" \u{1F4A1} This looks like a complex request."));
9884
- console.log(colors.muted(" Parallel build could be ~3x faster."));
9885
- console.log("");
9886
- const useParallel = await p6.confirm({
9887
- message: "Use parallel build with 3 agents?",
9888
- initialValue: true
9889
- });
9890
- if (useParallel && !p6.isCancel(useParallel)) {
9891
- await handleParallelBuild(cmd, ctx);
9892
- break;
9893
- }
9894
- }
9895
- const templateManager = new TemplateManager();
9896
- const suggestedTemplate = templateManager.suggestTemplate(cmd);
9897
- if (suggestedTemplate) {
9898
- console.log("");
9899
- console.log(colors.secondary(` \u{1F4A1} I recommend the "${suggestedTemplate.name}" template.`));
9900
- console.log(colors.muted(` ${suggestedTemplate.description}`));
9901
- console.log("");
9902
- const useTemplate = await p6.confirm({
9903
- message: "Install this template first?",
9904
- initialValue: true
9905
- });
9906
- if (useTemplate && !p6.isCancel(useTemplate)) {
9907
- await handleInstallTemplate(suggestedTemplate.id, ctx);
9908
- showSuccess("Template installed! Now customizing...");
9909
- }
9910
- }
9911
- await handleGeneration(cmd, ctx);
9912
- const filesCreated = editor.getPendingChanges().length;
9913
- const followUp = await proactive.onAfterAction(cmd, filesCreated);
9914
- if (followUp) {
9915
- console.log(proactive.formatAction(followUp));
9916
- proactive.markSuggestionShown();
9917
- }
9918
- }
10077
+ await handleAutomaticBuild(cmd, ctx);
10078
+ lastAction = cmd;
10079
+ break;
9919
10080
  break;
9920
10081
  }
9921
10082
  proactive.updateLastActivity();
9922
10083
  }
9923
10084
  }
10085
+ async function handleAutomaticBuild(request, ctx) {
10086
+ const { config, memory, scanner, editor, ai } = ctx;
10087
+ memory.addMessage("user", request);
10088
+ memory.setActiveTask(request);
10089
+ await memory.createSnapshot(`Before: ${request.slice(0, 50)}`);
10090
+ const tasks = analyzeAndSplitTasks(request);
10091
+ if (tasks.length > 1) {
10092
+ console.log("");
10093
+ 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"));
10094
+ console.log(colors.muted(" \u2502") + colors.primary(" \u26A1 Parallel Build ") + colors.muted("\u2502"));
10095
+ 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"));
10096
+ tasks.forEach((task, i) => {
10097
+ const taskText = task.slice(0, 43);
10098
+ console.log(colors.muted(" \u2502") + colors.dim(` ${i + 1}. ${taskText}`.padEnd(47)) + colors.muted("\u2502"));
10099
+ });
10100
+ 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"));
10101
+ console.log("");
10102
+ const hasParallel = await auth.checkFeature(FEATURES.parallel);
10103
+ if (hasParallel) {
10104
+ await runParallelBuild(tasks, ctx);
10105
+ } else {
10106
+ console.log(colors.dim(" Running sequentially (parallel requires Pro)..."));
10107
+ console.log("");
10108
+ for (const task of tasks) {
10109
+ await handleGeneration(task, ctx);
10110
+ }
10111
+ }
10112
+ } else {
10113
+ await handleGeneration(request, ctx);
10114
+ }
10115
+ }
10116
+ function analyzeAndSplitTasks(request) {
10117
+ const lowered = request.toLowerCase();
10118
+ const multiIndicators = [
10119
+ /\band\b/i,
10120
+ // "login and dashboard"
10121
+ /\+/,
10122
+ // "login + dashboard"
10123
+ /,\s*(?:and\s+)?/,
10124
+ // "login, dashboard, settings"
10125
+ /\bthen\b/i,
10126
+ // "login then dashboard"
10127
+ /\balso\b/i,
10128
+ // "also add settings"
10129
+ /\bplus\b/i
10130
+ // "plus a settings page"
10131
+ ];
10132
+ const hasMultiIndicator = multiIndicators.some((r) => r.test(request));
10133
+ if (!hasMultiIndicator) {
10134
+ return [request];
10135
+ }
10136
+ 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);
10137
+ if (tasks.length <= 1) {
10138
+ return [request];
10139
+ }
10140
+ if (tasks.length > 4) {
10141
+ const first3 = tasks.slice(0, 3);
10142
+ const rest = tasks.slice(3).join(" and ");
10143
+ tasks = [...first3, rest];
10144
+ }
10145
+ return tasks;
10146
+ }
10147
+ async function runParallelBuild(tasks, ctx) {
10148
+ const { config, memory, scanner, editor } = ctx;
10149
+ if (!await auth.checkFeature(FEATURES.parallel)) {
10150
+ return;
10151
+ }
10152
+ const parallelSystem = new ParallelAgentSystem(config, memory, scanner, editor);
10153
+ let cancelled = false;
10154
+ const checkCancelled = () => cancelled;
10155
+ const onKeypress = (key) => {
10156
+ const str = key.toString();
10157
+ if (str === "\x1B" || str === "\x1B") {
10158
+ cancelled = true;
10159
+ parallelSystem.cancel();
10160
+ }
10161
+ };
10162
+ if (process.stdin.isTTY) {
10163
+ process.stdin.setRawMode(true);
10164
+ }
10165
+ process.stdin.resume();
10166
+ process.stdin.on("data", onKeypress);
10167
+ console.log(colors.muted(" Press ESC to cancel"));
10168
+ console.log("");
10169
+ try {
10170
+ const startTime = Date.now();
10171
+ const combinedRequest = tasks.join(" AND ");
10172
+ const result = await parallelSystem.build(
10173
+ combinedRequest,
10174
+ {
10175
+ onTaskPlan: (plannedTasks) => {
10176
+ console.log(colors.muted(` Planned ${plannedTasks.length} parallel tasks`));
10177
+ },
10178
+ onAgentStart: (taskId, name) => {
10179
+ console.log(colors.dim(` \u2192 Starting: ${name}`));
10180
+ },
10181
+ onAgentComplete: (taskId, agentResult) => {
10182
+ const status = agentResult.success ? colors.success("\u2713") : colors.error("\u2717");
10183
+ console.log(` ${status} ${taskId}: ${agentResult.files.length} files`);
10184
+ }
10185
+ },
10186
+ checkCancelled
10187
+ );
10188
+ process.stdin.removeListener("data", onKeypress);
10189
+ if (process.stdin.isTTY) {
10190
+ process.stdin.setRawMode(false);
10191
+ }
10192
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
10193
+ console.log("");
10194
+ if (result.cancelled) {
10195
+ console.log(colors.muted(" Cancelled"));
10196
+ } else if (result.success) {
10197
+ console.log(colors.success(` \u2713 Completed in ${duration}s`));
10198
+ console.log(colors.muted(` Files: ${result.totalFiles}`));
10199
+ if (result.mergeConflicts.length > 0) {
10200
+ console.log(colors.warning(` Conflicts: ${result.mergeConflicts.length}`));
10201
+ }
10202
+ } else {
10203
+ console.log(colors.error(" Build failed"));
10204
+ result.results.forEach((r) => {
10205
+ if (!r.success && r.errors.length > 0) {
10206
+ r.errors.forEach((e) => console.log(colors.dim(` \u2022 ${e}`)));
10207
+ }
10208
+ });
10209
+ }
10210
+ console.log("");
10211
+ } catch (error) {
10212
+ process.stdin.removeListener("data", onKeypress);
10213
+ if (process.stdin.isTTY) {
10214
+ process.stdin.setRawMode(false);
10215
+ }
10216
+ if (cancelled) {
10217
+ console.log("");
10218
+ console.log(colors.muted(" Cancelled"));
10219
+ console.log("");
10220
+ return;
10221
+ }
10222
+ showError(error instanceof Error ? error.message : "Parallel build failed");
10223
+ }
10224
+ }
9924
10225
  async function handleGeneration(request, ctx) {
9925
10226
  const { memory, editor, ai, scanner } = ctx;
9926
10227
  memory.addMessage("user", request);
@@ -9960,31 +10261,46 @@ async function handleGeneration(request, ctx) {
9960
10261
  return;
9961
10262
  }
9962
10263
  console.log("");
10264
+ 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"));
9963
10265
  if (result.filesCreated.length > 0) {
9964
- console.log(colors.success(` \u2713 Created ${result.filesCreated.length} file(s)`));
9965
- for (const f of result.filesCreated) {
9966
- console.log(colors.muted(` + ${f}`));
10266
+ console.log(colors.muted(" \u2502") + colors.success(` \u2713 Created ${result.filesCreated.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
10267
+ for (const f of result.filesCreated.slice(0, 5)) {
10268
+ console.log(colors.muted(" \u2502") + colors.dim(` + ${f}`.padEnd(47)) + colors.muted("\u2502"));
10269
+ }
10270
+ if (result.filesCreated.length > 5) {
10271
+ console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesCreated.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
9967
10272
  }
9968
10273
  }
9969
10274
  if (result.filesModified.length > 0) {
9970
- console.log(colors.success(` \u2713 Modified ${result.filesModified.length} file(s)`));
9971
- for (const f of result.filesModified) {
9972
- console.log(colors.muted(` ~ ${f}`));
10275
+ console.log(colors.muted(" \u2502") + colors.warning(` ~ Modified ${result.filesModified.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
10276
+ for (const f of result.filesModified.slice(0, 5)) {
10277
+ console.log(colors.muted(" \u2502") + colors.dim(` ~ ${f}`.padEnd(47)) + colors.muted("\u2502"));
10278
+ }
10279
+ if (result.filesModified.length > 5) {
10280
+ console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesModified.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
9973
10281
  }
9974
10282
  }
9975
10283
  if (result.commandsRun.length > 0) {
9976
- console.log(colors.muted(` \u26A1 Ran: ${result.commandsRun.join(", ")}`));
10284
+ console.log(colors.muted(" \u2502") + colors.dim(` \u26A1 Ran: ${result.commandsRun.join(", ")}`.padEnd(47)) + colors.muted("\u2502"));
9977
10285
  }
9978
10286
  if (result.errors.length > 0) {
9979
- console.log("");
9980
- console.log(colors.error(` \u274C ${result.errors.length} error(s):`));
9981
- for (const e of result.errors.slice(0, 5)) {
9982
- console.log(colors.error(` \u2022 ${e}`));
10287
+ console.log(colors.muted(" \u2502") + colors.error(` \u2717 ${result.errors.length} error(s)`.padEnd(47)) + colors.muted("\u2502"));
10288
+ for (const e of result.errors.slice(0, 3)) {
10289
+ console.log(colors.muted(" \u2502") + colors.dim(` ${e.slice(0, 43)}`.padEnd(47)) + colors.muted("\u2502"));
9983
10290
  }
9984
10291
  }
10292
+ 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"));
9985
10293
  if (result.success) {
9986
10294
  console.log("");
9987
- showSuccess("Done!");
10295
+ console.log(colors.success(" \u2713 Done!"));
10296
+ const totalFiles = result.filesCreated.length + result.filesModified.length;
10297
+ if (totalFiles > 0) {
10298
+ console.log("");
10299
+ console.log(colors.dim(" What's next?"));
10300
+ console.log(colors.dim(' \u2022 "Check my code for issues"'));
10301
+ console.log(colors.dim(' \u2022 "Generate tests"'));
10302
+ console.log(colors.dim(' \u2022 "Commit my changes"'));
10303
+ }
9988
10304
  }
9989
10305
  console.log("");
9990
10306
  memory.addMessage("assistant", `Created: ${result.filesCreated.join(", ")}. Modified: ${result.filesModified.join(", ")}`);
@@ -10042,7 +10358,7 @@ async function handleCreate(name, ctx) {
10042
10358
  const { config } = ctx;
10043
10359
  let projectName = name;
10044
10360
  if (!projectName) {
10045
- const nameInput = await p6.text({
10361
+ const nameInput = await p7.text({
10046
10362
  message: "Project name:",
10047
10363
  placeholder: "my-awesome-app",
10048
10364
  validate: (value) => {
@@ -10050,23 +10366,23 @@ async function handleCreate(name, ctx) {
10050
10366
  if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
10051
10367
  }
10052
10368
  });
10053
- if (p6.isCancel(nameInput)) return;
10369
+ if (p7.isCancel(nameInput)) return;
10054
10370
  projectName = nameInput;
10055
10371
  }
10056
10372
  console.log("");
10057
- const github = await p6.confirm({
10373
+ const github = await p7.confirm({
10058
10374
  message: "Create GitHub repository?",
10059
10375
  initialValue: !!config.getGithubToken()
10060
10376
  });
10061
- const supabase = await p6.confirm({
10377
+ const supabase = await p7.confirm({
10062
10378
  message: "Create Supabase project?",
10063
10379
  initialValue: !!config.getSupabaseToken()
10064
10380
  });
10065
- const vercel = await p6.confirm({
10381
+ const vercel = await p7.confirm({
10066
10382
  message: "Deploy to Vercel?",
10067
10383
  initialValue: !!config.getVercelToken()
10068
10384
  });
10069
- if (p6.isCancel(github) || p6.isCancel(supabase) || p6.isCancel(vercel)) return;
10385
+ if (p7.isCancel(github) || p7.isCancel(supabase) || p7.isCancel(vercel)) return;
10070
10386
  const creator = new ProjectCreator(config);
10071
10387
  const spinner = ora3("Creating project...").start();
10072
10388
  const result = await creator.create(
@@ -10127,14 +10443,17 @@ async function handleInstallTemplate(templateId, ctx) {
10127
10443
  let id = templateId;
10128
10444
  if (!id) {
10129
10445
  const templates = templateManager.listTemplates();
10130
- const choice = await p6.select({
10446
+ const choice = await p7.select({
10131
10447
  message: "Choose a template:",
10132
10448
  options: templates.map((t) => ({
10133
10449
  value: t.id,
10134
10450
  label: `${t.name} - ${t.description}`
10135
10451
  }))
10136
10452
  });
10137
- if (p6.isCancel(choice)) return;
10453
+ if (p7.isCancel(choice)) {
10454
+ console.log(colors.muted(" Cancelled"));
10455
+ return;
10456
+ }
10138
10457
  id = choice;
10139
10458
  }
10140
10459
  const spinner = ora3(`Installing ${id}...`).start();
@@ -10210,11 +10529,11 @@ async function handleParallelBuild(request, ctx) {
10210
10529
  console.log(colors.muted(` + ${change.path}`));
10211
10530
  }
10212
10531
  console.log("");
10213
- const apply = await p6.confirm({
10532
+ const apply = await p7.confirm({
10214
10533
  message: "Apply all files?",
10215
10534
  initialValue: true
10216
10535
  });
10217
- if (apply && !p6.isCancel(apply)) {
10536
+ if (apply && !p7.isCancel(apply)) {
10218
10537
  const applied = await editor.apply();
10219
10538
  showSuccess(`Applied ${applied.length} files`);
10220
10539
  for (const file of applied) {
@@ -10240,124 +10559,18 @@ async function handleParallelBuild(request, ctx) {
10240
10559
  }
10241
10560
  console.log("");
10242
10561
  }
10243
- function detectComplexRequest(request) {
10244
- const lower = request.toLowerCase();
10245
- const features = [
10246
- "auth",
10247
- "login",
10248
- "signup",
10249
- "authentication",
10250
- "dashboard",
10251
- "admin",
10252
- "settings",
10253
- "profile",
10254
- "billing",
10255
- "payment",
10256
- "stripe",
10257
- "subscription",
10258
- "analytics",
10259
- "charts",
10260
- "users",
10261
- "user management",
10262
- "api",
10263
- "backend",
10264
- "database",
10265
- "crud"
10266
- ];
10267
- let featureCount = 0;
10268
- for (const feature of features) {
10269
- if (lower.includes(feature)) {
10270
- featureCount++;
10271
- }
10272
- }
10273
- const hasMultiple = lower.includes(" and ") || lower.includes(" with ") || lower.includes(", ") || lower.includes(" + ");
10274
- const scopeKeywords = ["full", "complete", "entire", "whole", "saas", "app", "application", "system"];
10275
- const hasScope = scopeKeywords.some((k) => lower.includes(k));
10276
- return featureCount >= 3 || featureCount >= 2 && hasMultiple || hasScope && featureCount >= 2;
10277
- }
10278
- async function handlePlanMode(request, ctx) {
10279
- const { config, memory, scanner } = ctx;
10280
- console.log("");
10281
- console.log(colors.primary(" \u{1F4CB} PLAN MODE") + colors.muted(" - Showing what will happen"));
10282
- console.log("");
10283
- const planExecutor = new PlanExecutor(config, memory, scanner);
10284
- const spinner = ora3("Creating plan...").start();
10285
- try {
10286
- const plan = await planExecutor.createPlan(request);
10287
- spinner.stop();
10288
- console.log(planExecutor.formatPlan(plan));
10289
- const execute = await p6.confirm({
10290
- message: "Execute this plan?",
10291
- initialValue: true
10292
- });
10293
- if (execute && !p6.isCancel(execute)) {
10294
- await handleGeneration(request, ctx);
10295
- }
10296
- } catch (error) {
10297
- spinner.stop();
10298
- showError(error instanceof Error ? error.message : "Plan failed");
10299
- }
10300
- }
10301
- async function handleQuestionMode(request, ctx) {
10302
- const { config, memory, scanner } = ctx;
10303
- const questionExecutor = new QuestionExecutor(config, memory, scanner);
10304
- try {
10305
- const questions = await questionExecutor.generateQuestions(request);
10306
- const result = await questionExecutor.askQuestions(questions);
10307
- if (Object.keys(result.answers).length > 0) {
10308
- questionExecutor.showSummary(result);
10309
- const enhancedRequest = request + questionExecutor.formatAnswersForPrompt(result);
10310
- await handleGeneration(enhancedRequest, ctx);
10311
- } else {
10312
- await handleGeneration(request, ctx);
10313
- }
10314
- } catch (error) {
10315
- showError(error instanceof Error ? error.message : "Question mode failed");
10316
- }
10317
- }
10318
- async function handleAgentMode(request, ctx) {
10319
- const { config, ai, memory, scanner, editor } = ctx;
10320
- await memory.createSnapshot(`Before agent: ${request.slice(0, 50)}`);
10321
- let cancelled = false;
10322
- const checkCancelled = () => cancelled;
10323
- const onKeypress = (key) => {
10324
- const str = key.toString();
10325
- if (str === "\x1B" || str === "\x1B") {
10326
- cancelled = true;
10327
- }
10328
- };
10329
- if (process.stdin.isTTY) {
10330
- process.stdin.setRawMode(true);
10331
- }
10332
- process.stdin.resume();
10333
- process.stdin.on("data", onKeypress);
10334
- const agentExecutor = new AgentExecutor(config, ai, memory, scanner, editor);
10335
- try {
10336
- const result = await agentExecutor.execute(request, { checkCancelled });
10337
- process.stdin.removeListener("data", onKeypress);
10338
- if (process.stdin.isTTY) {
10339
- process.stdin.setRawMode(false);
10340
- }
10341
- if (result.cancelled) {
10342
- showWarning("Cancelled.");
10343
- }
10344
- } catch (error) {
10345
- process.stdin.removeListener("data", onKeypress);
10346
- if (process.stdin.isTTY) {
10347
- process.stdin.setRawMode(false);
10348
- }
10349
- showError(error instanceof Error ? error.message : "Agent mode failed");
10350
- }
10351
- }
10352
10562
  async function handleTestGeneration(filePath, ctx) {
10353
10563
  const { config, scanner, editor } = ctx;
10354
10564
  const testGen = new TestGenerator(config, scanner);
10355
10565
  if (!filePath) {
10356
- const file = await p6.text({
10566
+ const file = await p7.text({
10357
10567
  message: "File to generate tests for:",
10358
10568
  placeholder: "src/components/Button.tsx"
10359
10569
  });
10360
- if (p6.isCancel(file) || !file) return;
10570
+ if (p7.isCancel(file) || !file) {
10571
+ console.log(colors.muted(" Cancelled"));
10572
+ return;
10573
+ }
10361
10574
  filePath = file;
10362
10575
  }
10363
10576
  const spinner = ora3(`Generating tests for ${filePath}...`).start();
@@ -10374,13 +10587,17 @@ async function handleTestGeneration(filePath, ctx) {
10374
10587
  console.log(colors.muted(" " + test.content.split("\n").slice(0, 10).join("\n ")));
10375
10588
  console.log(colors.muted(" ..."));
10376
10589
  console.log("");
10377
- const save = await p6.confirm({
10590
+ const save = await p7.confirm({
10378
10591
  message: `Save to ${test.testPath}?`,
10379
10592
  initialValue: true
10380
10593
  });
10381
- if (save && !p6.isCancel(save)) {
10594
+ if (p7.isCancel(save)) {
10595
+ console.log(colors.muted(" Test not saved"));
10596
+ } else if (save) {
10382
10597
  await testGen.writeTest(test);
10383
10598
  showSuccess(`Test saved to ${test.testPath}`);
10599
+ } else {
10600
+ console.log(colors.muted(" Test not saved"));
10384
10601
  }
10385
10602
  } catch (error) {
10386
10603
  spinner.stop();
@@ -10416,11 +10633,11 @@ async function handleLivePreview(ctx) {
10416
10633
  const { scanner } = ctx;
10417
10634
  const preview = new LivePreview(scanner);
10418
10635
  if (preview.isRunning()) {
10419
- const stop = await p6.confirm({
10636
+ const stop = await p7.confirm({
10420
10637
  message: "Preview is running. Stop it?",
10421
10638
  initialValue: false
10422
10639
  });
10423
- if (stop && !p6.isCancel(stop)) {
10640
+ if (stop && !p7.isCancel(stop)) {
10424
10641
  await preview.stop();
10425
10642
  showSuccess("Preview stopped");
10426
10643
  } else {
@@ -10448,28 +10665,38 @@ async function handleSpecGeneration(input, ctx) {
10448
10665
  return;
10449
10666
  }
10450
10667
  const { config, memory, scanner, editor, ai } = ctx;
10668
+ console.log("");
10669
+ console.log(colors.white(" \u{1F4DD} Spec Generator"));
10670
+ console.log(colors.muted(" \u2500".repeat(35)));
10671
+ console.log("");
10451
10672
  let description = input.replace(/^\/spec\s*/i, "").replace(/^\/prd\s*/i, "").replace(/^\/plan\s*/i, "").trim();
10452
10673
  if (!description) {
10453
- const desc = await p6.text({
10674
+ const desc = await p7.text({
10454
10675
  message: "What do you want to build?",
10455
10676
  placeholder: "An Uber for dog walkers"
10456
10677
  });
10457
- if (p6.isCancel(desc) || !desc) return;
10678
+ if (p7.isCancel(desc) || !desc) {
10679
+ console.log(colors.muted(" Spec generation cancelled"));
10680
+ console.log("");
10681
+ return;
10682
+ }
10458
10683
  description = desc;
10459
10684
  }
10460
10685
  const specGen = new SpecGenerator(config);
10461
10686
  try {
10462
10687
  const spec = await specGen.generate(description);
10463
10688
  if (!spec) {
10464
- showInfo("Spec generation cancelled");
10689
+ console.log("");
10690
+ console.log(colors.muted(" Spec generation cancelled"));
10691
+ console.log("");
10465
10692
  return;
10466
10693
  }
10467
10694
  console.log(specGen.formatSpec(spec));
10468
- const buildNow = await p6.confirm({
10695
+ const buildNow = await p7.confirm({
10469
10696
  message: "Build from this spec?",
10470
10697
  initialValue: true
10471
10698
  });
10472
- if (buildNow && !p6.isCancel(buildNow)) {
10699
+ if (buildNow && !p7.isCancel(buildNow)) {
10473
10700
  const builder = new ProjectBuilder(config, memory, scanner, editor, ai);
10474
10701
  let cancelled = false;
10475
10702
  const checkCancelled = () => cancelled;
@@ -10596,30 +10823,47 @@ async function handleAudit(args, ctx) {
10596
10823
  const auditor = new CodebaseAuditor(config);
10597
10824
  const git = new SmartGitManager(config);
10598
10825
  const subcommand = args?.split(" ")[0] || "";
10826
+ console.log("");
10827
+ console.log(colors.white(" \u{1F50D} Codebase Audit"));
10828
+ console.log(colors.muted(" \u2500".repeat(35)));
10599
10829
  switch (subcommand) {
10600
- case "fix": {
10830
+ case "fix":
10831
+ case "--fix": {
10832
+ console.log("");
10601
10833
  const spinner = ora3("Scanning codebase...").start();
10602
10834
  const report = await auditor.audit((file, current, total) => {
10603
10835
  spinner.text = `Scanning ${current}/${total}: ${file}`;
10604
10836
  });
10605
- spinner.stop();
10837
+ spinner.succeed("Scan complete");
10606
10838
  console.log(auditor.formatReport(report));
10607
10839
  if (report.totalIssues === 0) {
10608
- showSuccess("No issues found!");
10840
+ console.log("");
10841
+ showSuccess("No issues found! Your code is clean.");
10842
+ console.log("");
10843
+ console.log(colors.muted(" What you can do next:"));
10844
+ console.log(colors.dim(" \u2022 /review Get a full codebase review"));
10845
+ console.log(colors.dim(" \u2022 /spec Generate a product spec"));
10846
+ console.log("");
10609
10847
  return;
10610
10848
  }
10611
10849
  const fixType = args?.includes("critical") ? "critical" : args?.includes("all") ? "all" : null;
10612
10850
  let issuesToFix = report.critical;
10613
10851
  if (!fixType) {
10614
- const choice = await p6.select({
10852
+ const choice = await p7.select({
10615
10853
  message: "What would you like to fix?",
10616
10854
  options: [
10617
10855
  { value: "critical", label: `Critical issues only (${report.critical.length})` },
10618
10856
  { value: "warnings", label: `Critical + Warnings (${report.critical.length + report.warnings.length})` },
10619
- { value: "all", label: `Everything (${report.totalIssues})` }
10857
+ { value: "all", label: `Everything (${report.totalIssues})` },
10858
+ { value: "cancel", label: "\u2190 Back", hint: "Fix later" }
10620
10859
  ]
10621
10860
  });
10622
- if (p6.isCancel(choice)) return;
10861
+ if (p7.isCancel(choice) || choice === "cancel") {
10862
+ console.log("");
10863
+ console.log(colors.muted(" Auto-fix cancelled. You can run /audit fix anytime."));
10864
+ console.log("");
10865
+ return;
10866
+ }
10623
10867
  if (choice === "warnings") {
10624
10868
  issuesToFix = [...report.critical, ...report.warnings];
10625
10869
  } else if (choice === "all") {
@@ -10650,11 +10894,11 @@ async function handleAudit(args, ctx) {
10650
10894
  const files = result.changes.map((c) => c.file);
10651
10895
  await git.commitFiles(files, "fix: resolve codebase audit issues");
10652
10896
  console.log("");
10653
- const push = await p6.confirm({
10897
+ const push = await p7.confirm({
10654
10898
  message: "Push and create PR?",
10655
10899
  initialValue: true
10656
10900
  });
10657
- if (push && !p6.isCancel(push)) {
10901
+ if (push && !p7.isCancel(push)) {
10658
10902
  await git.push();
10659
10903
  const pr = await git.createPR(branchName, await git.getMainBranch(), "Fix: Codebase Audit Issues");
10660
10904
  if (pr?.url) {
@@ -10678,15 +10922,23 @@ async function handleAudit(args, ctx) {
10678
10922
  break;
10679
10923
  }
10680
10924
  default: {
10925
+ console.log("");
10681
10926
  const spinner = ora3("Scanning codebase...").start();
10682
10927
  const report = await auditor.audit((file, current, total) => {
10683
10928
  spinner.text = `Scanning ${current}/${total}: ${file}`;
10684
10929
  });
10685
- spinner.stop();
10930
+ spinner.succeed("Scan complete");
10686
10931
  console.log(auditor.formatReport(report));
10687
- if (report.totalIssues > 0) {
10688
- console.log(colors.muted(" Use /audit fix to auto-fix issues"));
10689
- console.log(colors.muted(" Use /audit report to export markdown"));
10932
+ if (report.totalIssues === 0) {
10933
+ console.log("");
10934
+ showSuccess("No issues found! Your code is clean.");
10935
+ console.log("");
10936
+ } else {
10937
+ console.log("");
10938
+ console.log(colors.muted(" What you can do:"));
10939
+ console.log(colors.dim(" /audit fix Auto-fix issues"));
10940
+ console.log(colors.dim(" /audit report Export markdown report"));
10941
+ console.log("");
10690
10942
  }
10691
10943
  }
10692
10944
  }
@@ -10767,21 +11019,56 @@ async function handleIndex(ctx) {
10767
11019
  showSuccess("Codebase indexed! Context retrieval is now smarter.");
10768
11020
  console.log(colors.muted(" Re-run /index after major changes to keep it updated."));
10769
11021
  }
11022
+ async function handleFolderChange() {
11023
+ console.log("");
11024
+ console.log(colors.white(" \u{1F4C1} Change Working Folder"));
11025
+ console.log("");
11026
+ const folder = await pickFolder("Select project folder");
11027
+ if (!folder) {
11028
+ console.log(colors.muted(" Cancelled"));
11029
+ return;
11030
+ }
11031
+ if (!await fs17.pathExists(folder)) {
11032
+ showError(`Folder not found: ${folder}`);
11033
+ return;
11034
+ }
11035
+ try {
11036
+ process.chdir(folder);
11037
+ console.log("");
11038
+ showSuccess(`Changed to: ${folder}`);
11039
+ console.log(colors.muted(" Restart CB to reload project context"));
11040
+ console.log("");
11041
+ } catch (error) {
11042
+ showError(`Cannot access folder: ${error instanceof Error ? error.message : "Unknown error"}`);
11043
+ }
11044
+ }
10770
11045
  async function initProject(ctx) {
10771
11046
  const { memory, scanner } = ctx;
10772
11047
  const structure = await scanner.detectProject();
10773
11048
  console.log("");
10774
- const name = await p6.text({
11049
+ console.log(colors.white(" \u{1F4C1} Initialize Project"));
11050
+ console.log(colors.muted(" \u2500".repeat(35)));
11051
+ console.log("");
11052
+ const name = await p7.text({
10775
11053
  message: "Project name:",
10776
- initialValue: structure.name
11054
+ initialValue: structure.name,
11055
+ placeholder: structure.name
10777
11056
  });
10778
- if (p6.isCancel(name)) return;
10779
- const description = await p6.text({
10780
- message: "Description:",
11057
+ if (p7.isCancel(name)) {
11058
+ console.log(colors.muted(" Cancelled"));
11059
+ console.log("");
11060
+ return;
11061
+ }
11062
+ const description = await p7.text({
11063
+ message: "Description (optional):",
10781
11064
  placeholder: "A brief description of your project"
10782
11065
  });
10783
- if (p6.isCancel(description)) return;
10784
- const stack = await p6.text({
11066
+ if (p7.isCancel(description)) {
11067
+ console.log(colors.muted(" Cancelled"));
11068
+ console.log("");
11069
+ return;
11070
+ }
11071
+ const stack = await p7.text({
10785
11072
  message: "Tech stack (comma-separated):",
10786
11073
  initialValue: [
10787
11074
  structure.framework,
@@ -10790,7 +11077,11 @@ async function initProject(ctx) {
10790
11077
  "Tailwind"
10791
11078
  ].filter(Boolean).join(", ")
10792
11079
  });
10793
- if (p6.isCancel(stack)) return;
11080
+ if (p7.isCancel(stack)) {
11081
+ console.log(colors.muted(" Cancelled"));
11082
+ console.log("");
11083
+ return;
11084
+ }
10794
11085
  memory.setIdentity(
10795
11086
  name,
10796
11087
  description,
@@ -10833,11 +11124,11 @@ async function undoChanges(ctx, steps) {
10833
11124
  const snapshot = snapshots[targetIndex];
10834
11125
  console.log("");
10835
11126
  console.log(colors.white(` Rolling back to: ${snapshot.description}`));
10836
- const confirm7 = await p6.confirm({
11127
+ const confirm7 = await p7.confirm({
10837
11128
  message: "Proceed with rollback?",
10838
11129
  initialValue: false
10839
11130
  });
10840
- if (confirm7 && !p6.isCancel(confirm7)) {
11131
+ if (confirm7 && !p7.isCancel(confirm7)) {
10841
11132
  const restored = await memory.rollback(snapshot.id);
10842
11133
  showSuccess(`Restored ${restored.length} file${restored.length > 1 ? "s" : ""}`);
10843
11134
  }
@@ -10845,11 +11136,11 @@ async function undoChanges(ctx, steps) {
10845
11136
  async function commitChanges(ctx, message) {
10846
11137
  const { git, memory } = ctx;
10847
11138
  if (!await git.isRepo()) {
10848
- const init = await p6.confirm({
11139
+ const init = await p7.confirm({
10849
11140
  message: "No git repo found. Initialize one?",
10850
11141
  initialValue: true
10851
11142
  });
10852
- if (init && !p6.isCancel(init)) {
11143
+ if (init && !p7.isCancel(init)) {
10853
11144
  await git.init();
10854
11145
  showSuccess("Git repository initialized");
10855
11146
  } else {
@@ -10874,13 +11165,13 @@ async function deployProject(ctx) {
10874
11165
  showError("Vercel CLI not installed. Run: npm i -g vercel");
10875
11166
  return;
10876
11167
  }
10877
- const prod = await p6.confirm({
11168
+ const prod = await p7.confirm({
10878
11169
  message: "Deploy to production?",
10879
11170
  initialValue: false
10880
11171
  });
10881
11172
  const spinner = ora3("Deploying...").start();
10882
11173
  try {
10883
- const result = await vercel.deploy(process.cwd(), prod && !p6.isCancel(prod));
11174
+ const result = await vercel.deploy(process.cwd(), prod && !p7.isCancel(prod));
10884
11175
  spinner.stop();
10885
11176
  showSuccess(`Deployed: ${result.url}`);
10886
11177
  } catch (error) {
@@ -10889,78 +11180,108 @@ async function deployProject(ctx) {
10889
11180
  }
10890
11181
  }
10891
11182
  async function reviewCodebase(ctx) {
10892
- const { ai } = ctx;
10893
- const includeAI = await p6.confirm({
11183
+ const { ai, memory } = ctx;
11184
+ console.log("");
11185
+ console.log(colors.white(" \u{1F4CA} Codebase Review"));
11186
+ console.log(colors.muted(" \u2500".repeat(35)));
11187
+ console.log("");
11188
+ const includeAI = await p7.confirm({
10894
11189
  message: "Include AI deep analysis? (slower but more thorough)",
10895
11190
  initialValue: true
10896
11191
  });
10897
- if (p6.isCancel(includeAI)) return;
11192
+ if (p7.isCancel(includeAI)) {
11193
+ console.log(colors.muted(" Review cancelled"));
11194
+ console.log("");
11195
+ return;
11196
+ }
10898
11197
  const analyzer = new CodebaseAnalyzer(
10899
11198
  process.cwd(),
10900
11199
  includeAI ? ai : void 0
10901
11200
  );
10902
- const spinner = ora3("Starting codebase review...").start();
11201
+ console.log("");
11202
+ const spinner = ora3({
11203
+ text: "Starting codebase review...",
11204
+ spinner: "dots"
11205
+ }).start();
10903
11206
  try {
10904
11207
  const report = await analyzer.fullReview((step, progress) => {
10905
11208
  spinner.text = `${step} (${Math.round(progress)}%)`;
10906
11209
  });
10907
- spinner.stop();
11210
+ spinner.succeed("Review complete!");
11211
+ console.log("");
10908
11212
  const formatted = analyzer.formatReport(report);
10909
11213
  console.log(formatted);
10910
- if (report.byCategory.patterns.length > 0) {
10911
- const fix = await p6.confirm({
10912
- message: `Fix ${report.byCategory.patterns.length} pattern violations automatically?`,
10913
- initialValue: false
11214
+ console.log(colors.muted(" \u2500".repeat(35)));
11215
+ console.log("");
11216
+ const hasIssues = report.summary.totalViolations > 0 || report.summary.totalIssues > 0;
11217
+ if (!hasIssues) {
11218
+ console.log(colors.success(" \u2713 Your codebase looks healthy!"));
11219
+ console.log("");
11220
+ console.log(colors.muted(" What you can do next:"));
11221
+ console.log(colors.dim(" \u2022 /audit Run a deeper audit with auto-fix"));
11222
+ console.log(colors.dim(" \u2022 /spec Generate a product spec"));
11223
+ console.log(colors.dim(" \u2022 /git Check your git status"));
11224
+ console.log("");
11225
+ } else {
11226
+ console.log(colors.warning(` Found ${report.summary.totalViolations + report.summary.totalIssues} issues to address`));
11227
+ console.log("");
11228
+ const action = await p7.select({
11229
+ message: "What would you like to do?",
11230
+ options: [
11231
+ {
11232
+ value: "fix",
11233
+ label: "\u{1F527} Auto-fix pattern violations",
11234
+ hint: `Fix ${report.byCategory.patterns.length} violations`
11235
+ },
11236
+ {
11237
+ value: "audit",
11238
+ label: "\u{1F50D} Run detailed audit",
11239
+ hint: "Get line-by-line fixes"
11240
+ },
11241
+ {
11242
+ value: "export",
11243
+ label: "\u{1F4C4} Export report",
11244
+ hint: "Save to file"
11245
+ },
11246
+ {
11247
+ value: "skip",
11248
+ label: "\u2190 Back to main",
11249
+ hint: "Address later"
11250
+ }
11251
+ ]
10914
11252
  });
10915
- if (fix && !p6.isCancel(fix)) {
10916
- showInfo("Use `/fix` command to auto-fix pattern violations (coming soon)");
11253
+ if (p7.isCancel(action) || action === "skip") {
11254
+ console.log("");
11255
+ console.log(colors.muted(" You can run /audit anytime to fix issues"));
11256
+ console.log("");
11257
+ return;
11258
+ }
11259
+ if (action === "fix") {
11260
+ console.log("");
11261
+ console.log(colors.muted(" Starting auto-fix..."));
11262
+ await handleAudit("--fix", ctx);
11263
+ } else if (action === "audit") {
11264
+ await handleAudit("", ctx);
11265
+ } else if (action === "export") {
11266
+ const reportPath = path18.join(process.cwd(), "codebase-review.md");
11267
+ await fs17.writeFile(reportPath, formatted.replace(/\x1b\[[0-9;]*m/g, ""));
11268
+ console.log("");
11269
+ showSuccess(`Report saved to ${reportPath}`);
10917
11270
  }
10918
11271
  }
11272
+ memory.addDecision(`Codebase reviewed: ${report.summary.healthScore}/100 health score, ${report.summary.totalViolations} violations`);
11273
+ await memory.save();
10919
11274
  } catch (error) {
10920
- spinner.stop();
10921
- showError(error instanceof Error ? error.message : "Review failed");
10922
- }
10923
- }
10924
- async function runSetup(config) {
10925
- console.log("");
10926
- console.log(colors.white(" Setup CodeBakers CLI"));
10927
- divider();
10928
- const apiKey = await p6.text({
10929
- message: "Anthropic API key:",
10930
- placeholder: "sk-ant-...",
10931
- validate: (value) => {
10932
- if (!value) return "Required";
10933
- if (!value.startsWith("sk-ant-")) return "Invalid format";
10934
- }
10935
- });
10936
- if (p6.isCancel(apiKey)) return;
10937
- config.setAnthropicKey(apiKey);
10938
- const setupGithub = await p6.confirm({
10939
- message: "Set up GitHub token? (optional)",
10940
- initialValue: false
10941
- });
10942
- if (setupGithub && !p6.isCancel(setupGithub)) {
10943
- const token = await p6.text({
10944
- message: "GitHub token:",
10945
- placeholder: "ghp_..."
10946
- });
10947
- if (!p6.isCancel(token) && token) {
10948
- config.setGithubToken(token);
10949
- }
10950
- }
10951
- const setupVercel = await p6.confirm({
10952
- message: "Set up Vercel token? (optional)",
10953
- initialValue: false
10954
- });
10955
- if (setupVercel && !p6.isCancel(setupVercel)) {
10956
- const token = await p6.text({
10957
- message: "Vercel token:"
10958
- });
10959
- if (!p6.isCancel(token) && token) {
10960
- config.setVercelToken(token);
10961
- }
11275
+ spinner.fail("Review failed");
11276
+ console.log("");
11277
+ showError(error instanceof Error ? error.message : "Unknown error");
11278
+ console.log("");
11279
+ console.log(colors.muted(" Try:"));
11280
+ console.log(colors.dim(" \u2022 Check if you're in a project directory"));
11281
+ console.log(colors.dim(" \u2022 Run /status to verify your setup"));
11282
+ console.log(colors.dim(' \u2022 Run without AI: answer "no" to deep analysis'));
11283
+ console.log("");
10962
11284
  }
10963
- showSuccess("Setup complete!");
10964
11285
  }
10965
11286
  function showHelp() {
10966
11287
  console.log(`
@@ -10980,33 +11301,78 @@ function showHelp() {
10980
11301
  `);
10981
11302
  }
10982
11303
  function showCommandHelp() {
10983
- console.log(`
10984
- ${colors.white("Modes (Shift+Tab to cycle):")}
10985
- [PLAN] Shows what will happen, then asks to execute
10986
- [QUESTION] Asks clarifying questions first
10987
- [AGENT] Runs autonomously without asking
10988
-
10989
- ${colors.white("Commands:")}
10990
- /spec <idea> \u{1F4C4} Generate spec + build from it
10991
- /parallel <req> \u{1F680} Parallel build (3x faster)
10992
- /create <name> One-click: GitHub + Supabase + Vercel
10993
- /templates List available templates
10994
- /install <id> Install a template
10995
- /test [file] Generate tests
10996
- /pr [title] Create pull request
10997
- /preview Start dev server
10998
- /review Codebase review
10999
- /run <cmd> Terminal command
11000
- /status Project status
11001
- /undo Rollback changes
11002
- /commit Git commit
11003
- /deploy Vercel deploy
11004
- /quit Exit
11005
-
11006
- ${colors.white("Examples:")}
11007
- /spec An Uber for dog walkers
11008
- /parallel Build auth + dashboard
11009
- ./design.png \u2192 Build from image
11010
- `);
11304
+ console.log("");
11305
+ 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"));
11306
+ console.log(colors.muted(" \u2502") + colors.white(" Just tell me what you want ") + colors.muted("\u2502"));
11307
+ 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"));
11308
+ console.log("");
11309
+ console.log(colors.primary(" Build & Create"));
11310
+ 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"));
11311
+ console.log(colors.white(' "Add a login page with Google auth"'));
11312
+ console.log(colors.white(' "Build login, dashboard, and settings"'));
11313
+ console.log(colors.white(' "I want to build an Uber for dog walking"'));
11314
+ console.log("");
11315
+ console.log(colors.primary(" Review & Fix"));
11316
+ 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"));
11317
+ console.log(colors.white(' "Check my code for issues"'));
11318
+ console.log(colors.white(` "What's wrong with this project?"`));
11319
+ console.log(colors.white(' "Fix the problems you find"'));
11320
+ console.log("");
11321
+ console.log(colors.primary(" Git & Deploy"));
11322
+ 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"));
11323
+ console.log(colors.white(' "Commit my changes"'));
11324
+ console.log(colors.white(' "Push to GitHub"'));
11325
+ console.log(colors.white(' "Deploy to Vercel"'));
11326
+ console.log("");
11327
+ console.log(colors.primary(" Other"));
11328
+ 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"));
11329
+ console.log(colors.white(' "Generate tests"') + colors.dim(' "Start dev server"'));
11330
+ console.log(colors.white(' "Create a PR"') + colors.dim(' "Undo that"'));
11331
+ console.log("");
11332
+ console.log(colors.dim(" Shortcuts: /help /quit /status /login"));
11333
+ console.log("");
11334
+ }
11335
+ function getSuggestions(input) {
11336
+ const lowered = input.toLowerCase();
11337
+ const suggestions = [];
11338
+ const mappings = {
11339
+ "reveiw": ["/review - Review your codebase"],
11340
+ "reviw": ["/review - Review your codebase"],
11341
+ "aduit": ["/audit - Audit code quality"],
11342
+ "auidt": ["/audit - Audit code quality"],
11343
+ "hlep": ["/help - Show all commands"],
11344
+ "helo": ["/help - Show all commands"],
11345
+ "init": ["/init - Initialize project"],
11346
+ "start": ["/preview - Start dev server", "/init - Initialize project"],
11347
+ "run": ["/run <command> - Run a terminal command"],
11348
+ "fix": ["/audit fix - Auto-fix issues"],
11349
+ "check": ["/audit - Check code quality", "/review - Review codebase"],
11350
+ "test": ["/test - Generate tests"],
11351
+ "build": ['"Build a login page" - Describe what you want'],
11352
+ "make": ['"Make a navbar" - Describe what you want'],
11353
+ "create": ["/create - Create new project", '"Create a dashboard" - Describe it'],
11354
+ "git": ["/git status - Check git", "/git commit - Commit changes"],
11355
+ "commit": ["/commit - Commit changes", '/git commit -m "message"'],
11356
+ "push": ["/git push - Push to remote"],
11357
+ "status": ["/status - Project status", "/git status - Git status"],
11358
+ "login": ["/login - Login to CodeBakers"],
11359
+ "logout": ["/logout - Logout"]
11360
+ };
11361
+ for (const [key, vals] of Object.entries(mappings)) {
11362
+ if (lowered.includes(key) || key.includes(lowered)) {
11363
+ suggestions.push(...vals);
11364
+ }
11365
+ }
11366
+ if (suggestions.length === 0) {
11367
+ if (lowered.length < 3) {
11368
+ suggestions.push("/help - Show all commands");
11369
+ } else {
11370
+ suggestions.push(
11371
+ '"' + input + '" - Try describing what you want to build',
11372
+ "/help - Show all commands"
11373
+ );
11374
+ }
11375
+ }
11376
+ return [...new Set(suggestions)].slice(0, 4);
11011
11377
  }
11012
11378
  main().catch(console.error);