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.
- package/dist/index.js +1524 -1158
- 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
|
|
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
|
-
|
|
765
|
-
- \
|
|
766
|
-
- \
|
|
767
|
-
- \
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
- \u2705
|
|
771
|
-
- \u2705
|
|
772
|
-
- \u2705
|
|
773
|
-
- \u2705
|
|
774
|
-
- \u2705
|
|
775
|
-
- \u2705
|
|
776
|
-
- \u2705
|
|
777
|
-
- \u2705
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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: "
|
|
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
|
|
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((
|
|
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
|
|
1503
|
-
if (
|
|
1504
|
-
issues.push(
|
|
1505
|
-
} else if (
|
|
1506
|
-
suggestions.push(
|
|
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 (
|
|
1576
|
-
const f = await this.loadFile(
|
|
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 (
|
|
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("#
|
|
1739
|
-
//
|
|
1740
|
-
warning: chalk.hex("#
|
|
1741
|
-
//
|
|
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("#
|
|
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:
|
|
1751
|
-
cross:
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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.
|
|
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(
|
|
2132
|
+
console.log(` ${colors.warning(sym.warning)} ${colors.warning(message)}`);
|
|
1782
2133
|
}
|
|
1783
2134
|
function showInfo(message) {
|
|
1784
|
-
console.log(colors.
|
|
2135
|
+
console.log(` ${colors.info(sym.info)} ${colors.info(message)}`);
|
|
1785
2136
|
}
|
|
1786
2137
|
function divider() {
|
|
1787
|
-
console.log(colors.muted(
|
|
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
|
|
2752
|
-
const pkgPath =
|
|
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((
|
|
3105
|
+
return packages.filter((p8) => !installed[p8]);
|
|
2760
3106
|
}
|
|
2761
|
-
spin(
|
|
3107
|
+
spin(text5) {
|
|
2762
3108
|
if (this.spinner) {
|
|
2763
|
-
this.spinner.text =
|
|
3109
|
+
this.spinner.text = text5 + " (ESC to cancel)";
|
|
2764
3110
|
} else {
|
|
2765
|
-
this.spinner = ora(
|
|
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 [
|
|
4429
|
+
for (const [path19, count] of pathMap) {
|
|
4084
4430
|
if (count > 1) {
|
|
4085
|
-
conflicts.push(`Multiple agents created: ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4790
|
+
this.anthropic = new Anthropic5({ apiKey });
|
|
4432
4791
|
this.memory = memory;
|
|
4433
|
-
this.scanner = scanner;
|
|
4434
4792
|
}
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
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
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
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((
|
|
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(
|
|
6699
|
-
return
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
7753
|
-
const disabled = this.patterns.filter((
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
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((
|
|
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(
|
|
8939
|
-
const words =
|
|
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
|
-
([
|
|
9285
|
-
).map(([
|
|
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((
|
|
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
|
-
([
|
|
9447
|
-
(word) => word.length > 2 && (
|
|
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
|
|
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(
|
|
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
|
|
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] === "
|
|
9668
|
-
await
|
|
9819
|
+
if (args[0] === "login") {
|
|
9820
|
+
await auth.login();
|
|
9669
9821
|
return;
|
|
9670
9822
|
}
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
9714
|
-
|
|
9715
|
-
|
|
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 (
|
|
9891
|
+
if (p7.isCancel(input)) {
|
|
9719
9892
|
running = false;
|
|
9720
|
-
|
|
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
|
|
9726
|
-
if (
|
|
9727
|
-
console.log(suggestions.formatSuggestion(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
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
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
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
|
-
|
|
9809
|
-
|
|
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
|
|
10065
|
+
const proceed = await p7.confirm({
|
|
9864
10066
|
message: `Process this ${detected.type}?`,
|
|
9865
10067
|
initialValue: true
|
|
9866
10068
|
});
|
|
9867
|
-
if (!proceed ||
|
|
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
|
|
10074
|
+
await handleAutomaticBuild(enhancedCmd, ctx);
|
|
9870
10075
|
break;
|
|
9871
10076
|
}
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
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(`
|
|
9965
|
-
for (const f of result.filesCreated) {
|
|
9966
|
-
console.log(colors.muted(`
|
|
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.
|
|
9971
|
-
for (const f of result.filesModified) {
|
|
9972
|
-
console.log(colors.muted(`
|
|
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(
|
|
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
|
-
|
|
9981
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
10369
|
+
if (p7.isCancel(nameInput)) return;
|
|
10054
10370
|
projectName = nameInput;
|
|
10055
10371
|
}
|
|
10056
10372
|
console.log("");
|
|
10057
|
-
const github = await
|
|
10373
|
+
const github = await p7.confirm({
|
|
10058
10374
|
message: "Create GitHub repository?",
|
|
10059
10375
|
initialValue: !!config.getGithubToken()
|
|
10060
10376
|
});
|
|
10061
|
-
const supabase = await
|
|
10377
|
+
const supabase = await p7.confirm({
|
|
10062
10378
|
message: "Create Supabase project?",
|
|
10063
10379
|
initialValue: !!config.getSupabaseToken()
|
|
10064
10380
|
});
|
|
10065
|
-
const vercel = await
|
|
10381
|
+
const vercel = await p7.confirm({
|
|
10066
10382
|
message: "Deploy to Vercel?",
|
|
10067
10383
|
initialValue: !!config.getVercelToken()
|
|
10068
10384
|
});
|
|
10069
|
-
if (
|
|
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
|
|
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 (
|
|
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
|
|
10532
|
+
const apply = await p7.confirm({
|
|
10214
10533
|
message: "Apply all files?",
|
|
10215
10534
|
initialValue: true
|
|
10216
10535
|
});
|
|
10217
|
-
if (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
|
|
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 (
|
|
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
|
|
10590
|
+
const save = await p7.confirm({
|
|
10378
10591
|
message: `Save to ${test.testPath}?`,
|
|
10379
10592
|
initialValue: true
|
|
10380
10593
|
});
|
|
10381
|
-
if (
|
|
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
|
|
10636
|
+
const stop = await p7.confirm({
|
|
10420
10637
|
message: "Preview is running. Stop it?",
|
|
10421
10638
|
initialValue: false
|
|
10422
10639
|
});
|
|
10423
|
-
if (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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
10695
|
+
const buildNow = await p7.confirm({
|
|
10469
10696
|
message: "Build from this spec?",
|
|
10470
10697
|
initialValue: true
|
|
10471
10698
|
});
|
|
10472
|
-
if (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.
|
|
10837
|
+
spinner.succeed("Scan complete");
|
|
10606
10838
|
console.log(auditor.formatReport(report));
|
|
10607
10839
|
if (report.totalIssues === 0) {
|
|
10608
|
-
|
|
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
|
|
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 (
|
|
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
|
|
10897
|
+
const push = await p7.confirm({
|
|
10654
10898
|
message: "Push and create PR?",
|
|
10655
10899
|
initialValue: true
|
|
10656
10900
|
});
|
|
10657
|
-
if (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.
|
|
10930
|
+
spinner.succeed("Scan complete");
|
|
10686
10931
|
console.log(auditor.formatReport(report));
|
|
10687
|
-
if (report.totalIssues
|
|
10688
|
-
console.log(
|
|
10689
|
-
|
|
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
|
-
|
|
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 (
|
|
10779
|
-
|
|
10780
|
-
|
|
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 (
|
|
10784
|
-
|
|
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 (
|
|
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
|
|
11127
|
+
const confirm7 = await p7.confirm({
|
|
10837
11128
|
message: "Proceed with rollback?",
|
|
10838
11129
|
initialValue: false
|
|
10839
11130
|
});
|
|
10840
|
-
if (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
|
|
11139
|
+
const init = await p7.confirm({
|
|
10849
11140
|
message: "No git repo found. Initialize one?",
|
|
10850
11141
|
initialValue: true
|
|
10851
11142
|
});
|
|
10852
|
-
if (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
|
|
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 && !
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
11210
|
+
spinner.succeed("Review complete!");
|
|
11211
|
+
console.log("");
|
|
10908
11212
|
const formatted = analyzer.formatReport(report);
|
|
10909
11213
|
console.log(formatted);
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
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 (
|
|
10916
|
-
|
|
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.
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
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
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
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);
|