@ridit/lens 0.2.6 → 0.2.9

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/README.md CHANGED
@@ -30,33 +30,33 @@ npm install -g @ridit/lens
30
30
  ## CLI Commands
31
31
 
32
32
  ```
33
- lens chat chat with your codebase
34
- lens chat -p /path/to/repo chat in a specific repo
33
+ lens chat / vibe chat with your codebase
34
+ lens chat -p /path/to/repo chat in a specific repo
35
35
 
36
- lens review AI review of the current directory
37
- lens review /path/to/repo AI review of a specific repo
36
+ lens review / judge AI review of the current directory
37
+ lens review /path/to/repo AI review of a specific repo
38
38
 
39
- lens repo <url> analyze a remote GitHub repository
39
+ lens repo / stalk <url> analyze a remote GitHub repository
40
40
 
41
- lens task <text> apply a natural language change to the codebase
42
- lens task <text> -p /path apply change to a specific repo
41
+ lens task / cook <text> apply a natural language change to the codebase
42
+ lens task / cook <text> -p /path apply change to a specific repo
43
43
 
44
- lens commit generate a smart commit message from staged changes
45
- lens commit [files...] stage specific files and generate a commit message
46
- lens commit --auto stage all changes and commit without confirmation
47
- lens commit --confirm show preview before committing when using --auto
48
- lens commit --preview show the generated message without committing
49
- lens commit --push push to remote after committing
44
+ lens commit / crimes generate a smart commit message from staged changes
45
+ lens commit / crimes [files...] stage specific files and generate a commit message
46
+ lens commit / crimes --auto stage all changes and commit without confirmation
47
+ lens commit / crimes --confirm show preview before committing when using --auto
48
+ lens commit / crimes --preview show the generated message without committing
49
+ lens commit / crimes --push push to remote after committing
50
50
 
51
- lens timeline explore commit history
52
- lens timeline -p /path explore history of a specific repo
51
+ lens timeline / history explore commit history
52
+ lens timeline / history -p /path explore history of a specific repo
53
53
 
54
- lens provider configure AI providers
54
+ lens provider configure AI providers
55
55
  ```
56
56
 
57
57
  ## Chat Commands
58
58
 
59
- Once inside a `lens chat` session, use slash commands:
59
+ Once inside a `lens chat / vibe` session, use slash commands:
60
60
 
61
61
  ```
62
62
  /timeline browse commit history
@@ -79,7 +79,7 @@ Once inside a `lens chat` session, use slash commands:
79
79
  - **Anthropic** — Claude models
80
80
  - **OpenAI** — GPT models
81
81
  - **Gemini** — Google Gemini models
82
- - **Ollama** — local models
82
+ - **Ollama** — local models (free, fully offline)
83
83
  - **Custom** — any OpenAI-compatible API endpoint
84
84
 
85
85
  ## Extending Lens
package/dist/index.mjs CHANGED
@@ -265820,7 +265820,24 @@ var PHRASES = {
265820
265820
  "blending the universe... \uD83C\uDF0C",
265821
265821
  "whipping up brilliance... \uD83E\uDD44\uD83D\uDCA1",
265822
265822
  "folding in main character energy... \uD83C\uDF1F",
265823
- "checking internal temperatures... \uD83C\uDF21️"
265823
+ "checking internal temperatures... \uD83C\uDF21️",
265824
+ "it's giving... answers \uD83D\uDC85",
265825
+ "no cap, thinking fr fr... \uD83E\uDDE2",
265826
+ "lowkey cooking something bussin... \uD83C\uDF73",
265827
+ "understood the assignment... \uD83D\uDCCB✨",
265828
+ "ate and left no crumbs... \uD83C\uDF7D️",
265829
+ "the delulu is the solulu... \uD83C\uDF08",
265830
+ "giving it the roman empire treatment... \uD83C\uDFDB️",
265831
+ "slay mode activated... \uD83D\uDC85",
265832
+ "not me actually thinking rn... \uD83D\uDE2D",
265833
+ "this ain't it chief... wait yes it is... \uD83D\uDC51",
265834
+ "rent free in the context window... \uD83C\uDFE0",
265835
+ "it's the tokens for me... ✨",
265836
+ "vibing with the prompt... \uD83C\uDFB5",
265837
+ "main character moment incoming... \uD83C\uDFAC",
265838
+ "we move... \uD83E\uDEE1\uD83E\uDEE1",
265839
+ "rizzing up an answer... \uD83D\uDDE3️",
265840
+ "rizzing up a baddie answer... \uD83D\uDE0F"
265824
265841
  ],
265825
265842
  cloning: [
265826
265843
  "cloning at the speed of git... \uD83D\uDE80",
@@ -265846,9 +265863,7 @@ var PHRASES = {
265846
265863
  "fast-forwarding thoughts... ⏩",
265847
265864
  "merging with the multiverse... \uD83C\uDF0C",
265848
265865
  "rebasing reality... \uD83D\uDD04",
265849
- "checking out chaos... \uD83D\uDE0E",
265850
265866
  "cloning like a pro... \uD83D\uDC51",
265851
- "replicating the vibes... \uD83D\uDD01",
265852
265867
  "buffer overflow in progress... \uD83D\uDCBE",
265853
265868
  "pull request to sanity... \uD83E\uDD2F",
265854
265869
  "diverging the timeline... ⏳",
@@ -265856,7 +265871,16 @@ var PHRASES = {
265856
265871
  "fetching inspiration... \uD83D\uDCE1",
265857
265872
  "pruning branches... \uD83C\uDF3F",
265858
265873
  "syncing neurons... \uD83E\uDDE0",
265859
- "shallow clone, deep existential dread... \uD83C\uDF0A\uD83D\uDE35"
265874
+ "shallow clone, deep existential dread... \uD83C\uDF0A\uD83D\uDE35",
265875
+ "ate the repo and left no crumbs... \uD83C\uDF7D️",
265876
+ "this repo said no cap... \uD83E\uDDE2",
265877
+ "cloning the main character arc... \uD83C\uDFAC",
265878
+ "snatching the source code... \uD83D\uDC85",
265879
+ "github said understood the assignment... ✨",
265880
+ "bestie said fetch... \uD83D\uDCE1",
265881
+ "giving the repo its flowers... \uD83D\uDC90",
265882
+ "core memory unlocked: git clone... \uD83E\uDDE0",
265883
+ "no diff, no life... \uD83E\uDEE0"
265860
265884
  ],
265861
265885
  analyzing: [
265862
265886
  "reading every file (skimming)... \uD83D\uDCD6",
@@ -265872,30 +265896,33 @@ var PHRASES = {
265872
265896
  "appreciating the tech debt... \uD83D\uDCB3",
265873
265897
  "counting your TODOs... \uD83D\uDCCB",
265874
265898
  "reading between the lines... ✍️",
265875
- "anthropic-ifying your codebase... \uD83E\uDD16",
265876
265899
  "following the import trail... \uD83E\uDDED",
265877
265900
  "speed-reading your life's work... ⚡",
265878
265901
  "pretending to understand your monorepo... \uD83C\uDFE2",
265879
265902
  "debugging the cosmos... \uD83C\uDF0C",
265880
265903
  "profiling your thoughts... \uD83D\uDCCA",
265881
- "mapping mental threads... \uD83E\uDDF5",
265882
265904
  "sifting through the noise... \uD83C\uDF2A️",
265883
265905
  "predicting your next move... \uD83D\uDD2E",
265884
- "annotating life... ✍️",
265885
265906
  "absorbing the vibes... \uD83E\uDDD8",
265886
265907
  "peering into recursion... \uD83D\uDD01",
265887
- "modeling chaos... \uD83E\uDDE9",
265888
265908
  "counting edge cases... ⚠️",
265889
- "tracking call stacks... \uD83E\uDDD7",
265890
265909
  "parsing your aura... \uD83C\uDF08",
265891
265910
  "reading the README of life... \uD83D\uDCD6",
265892
265911
  "analyzing existential types... \uD83E\uDD2F",
265893
- "observing folder hierarchies... \uD83D\uDCC2",
265894
- "profiling main character energy... \uD83C\uDFAC",
265895
- "absorbing every commit... \uD83D\uDCBE",
265896
- "studying the architecture... \uD83C\uDFDB️",
265897
265912
  "checking for ghost functions... \uD83D\uDC7B",
265898
- "validating vibes... ✅"
265913
+ "validating vibes... ✅",
265914
+ "understood the codebase assignment... \uD83D\uDCCB✨",
265915
+ "this folder structure is NOT it... \uD83D\uDE2D",
265916
+ "the tech debt said rent free... \uD83C\uDFE0",
265917
+ "ate the architecture... \uD83C\uDF7D️",
265918
+ "giving the codebase the side eye... \uD83D\uDC40",
265919
+ "no cap this code goes hard... \uD83E\uDDE2\uD83D\uDD25",
265920
+ "the imports are giving chaos... \uD83C\uDF00",
265921
+ "bestie said touch grass... but first, touch files... \uD83C\uDF3F",
265922
+ "core memory unlocked: spaghetti code... \uD83C\uDF5D",
265923
+ "it's giving legacy... \uD83D\uDC80",
265924
+ "main character files identified... \uD83C\uDFAC",
265925
+ "the TODO comments said it all... \uD83D\uDE24"
265899
265926
  ],
265900
265927
  model: [
265901
265928
  "deciding which files matter... \uD83E\uDDE0",
@@ -265905,10 +265932,8 @@ var PHRASES = {
265905
265932
  "consulting the file oracle... \uD83D\uDD2E",
265906
265933
  "narrowing it down... \uD83D\uDD0D",
265907
265934
  "skimming the directory... \uD83D\uDCC2",
265908
- "choosing wisely... \uD83E\uDDE0",
265909
265935
  "not reading everything (smart)... \uD83D\uDE0E",
265910
265936
  "filtering the noise... \uD83D\uDD07",
265911
- "asking the oracle... \uD83D\uDD2E",
265912
265937
  "pinging the motherbrain... \uD83E\uDDE0",
265913
265938
  "waiting on the neurons... ⏳",
265914
265939
  "tokens incoming... \uD83D\uDCE5",
@@ -265916,7 +265941,6 @@ var PHRASES = {
265916
265941
  "inference in progress... ⚙️",
265917
265942
  "the GPU is thinking... \uD83D\uDCBB",
265918
265943
  "attention is all we need... \uD83C\uDFAF",
265919
- "cross-attending to your vibes... \uD83D\uDD04",
265920
265944
  "softmaxing the options... \uD83D\uDCCA",
265921
265945
  "sampling from the distribution... \uD83C\uDFB2",
265922
265946
  "temperature: just right... \uD83C\uDF21️",
@@ -265926,25 +265950,26 @@ var PHRASES = {
265926
265950
  "anthropic servers sweating... \uD83D\uDCA6",
265927
265951
  "matrix multiply in progress... ➗",
265928
265952
  "initializing hype mode... \uD83D\uDE80",
265929
- "compiling neurons... \uD83E\uDDE0",
265930
265953
  "loading chaotic energy... ⚡",
265931
265954
  "predicting your mood... \uD83D\uDE0F",
265932
265955
  "sampling absurdity... \uD83C\uDFB2",
265933
- "normalizing brainwaves... \uD83C\uDF0A",
265934
- "cross-validating genius... \uD83E\uDDEA",
265935
265956
  "activating main character module... \uD83C\uDFAC",
265936
265957
  "warming up GPU... \uD83D\uDCBB\uD83D\uDD25",
265937
- "spooling synapses... \uD83E\uDDF5",
265938
265958
  "calculating cringe factor... \uD83E\uDD21",
265939
- "preprocessing drama... \uD83D\uDCE6",
265940
- "injecting chaos vectors... ⚡",
265941
- "softmaxing life choices... \uD83E\uDDE0",
265942
- "attention heads online... \uD83D\uDC40",
265943
- "broadcasting vibes... \uD83D\uDCE1",
265944
265959
  "loading meme embeddings... \uD83D\uDDFF",
265945
- "quantizing mood... \uD83D\uDD22",
265946
265960
  "clipping gradients of patience... ✂️",
265947
- "inferring main plot points... \uD83D\uDCD6"
265961
+ "the model said understood... ",
265962
+ "tokens said no cap... \uD83E\uDDE2",
265963
+ "GPU said slay... \uD83D\uDC85",
265964
+ "attention heads said bestie... \uD83D\uDC40",
265965
+ "inference said we move... \uD83D\uDEB6",
265966
+ "model ate and left no crumbs... \uD83C\uDF7D️",
265967
+ "the context window said it's giving... \uD83E\uDE9F✨",
265968
+ "softmax said understood the assignment... \uD83D\uDCCA",
265969
+ "neurons said main character energy... \uD83E\uDDE0\uD83C\uDF1F",
265970
+ "it's giving transformer energy... \uD83E\uDD16",
265971
+ "the embeddings said rent free... \uD83C\uDFE0",
265972
+ "lowkey the model is built different... \uD83D\uDCAA"
265948
265973
  ],
265949
265974
  summary: [
265950
265975
  "synthesizing the chaos... \uD83E\uDDE0",
@@ -265964,24 +265989,28 @@ var PHRASES = {
265964
265989
  "writing the report card... \uD83D\uDCCA",
265965
265990
  "digesting chaos into art... \uD83C\uDFA8",
265966
265991
  "compressing existential dread... \uD83E\uDEE0",
265967
- "executing life summary... \uD83D\uDCDD",
265968
- "extracting hot takes... \uD83D\uDD25",
265969
265992
  "packaging thoughts nicely... \uD83D\uDCE6",
265970
265993
  "synthesizing main character energy... \uD83C\uDF1F",
265971
265994
  "boiling down code spaghetti... \uD83C\uDF5D",
265972
265995
  "condensing into brilliance... ✨",
265973
- "drawing the final diagram... \uD83D\uDCCA",
265974
- "reporting on universal logic... \uD83C\uDF0C",
265975
265996
  "giving verdict with style... \uD83D\uDE0E",
265976
265997
  "folding in chaos gracefully... \uD83C\uDF00",
265977
265998
  "capturing essence... \uD83D\uDCF8",
265978
265999
  "writing the epic conclusion... \uD83D\uDCDC",
265979
- "connecting all dots... \uD83D\uDD17",
265980
- "rendering summary in HD... \uD83D\uDCFA",
265981
- "wrapping thoughts in elegance... \uD83C\uDF81",
265982
266000
  "making insights bussin... \uD83E\uDD75",
265983
- "compressing noise... \uD83D\uDD07",
265984
- "finishing the mental buffet... \uD83C\uDF7D️"
266001
+ "finishing the mental buffet... \uD83C\uDF7D️",
266002
+ "the verdict said no cap... \uD83E\uDDE2",
266003
+ "ate the analysis and left no crumbs... \uD83C\uDF7D️✨",
266004
+ "understood the assignment, delivering... \uD83D\uDCCB\uD83D\uDC85",
266005
+ "it's giving final boss energy... \uD83D\uDC51",
266006
+ "the summary said slay... \uD83D\uDC85",
266007
+ "hot take incoming, no cap... \uD83D\uDD25\uD83E\uDDE2",
266008
+ "core memory unlocked: the truth... \uD83E\uDDE0",
266009
+ "main character conclusion activated... \uD83C\uDFAC",
266010
+ "giving the codebase its honest review... \uD83D\uDC80",
266011
+ "the analysis said we move... \uD83D\uDEB6✨",
266012
+ "lowkey this code needs therapy... \uD83D\uDECB️",
266013
+ "it's giving mixed reviews bestie... \uD83D\uDE2C"
265985
266014
  ],
265986
266015
  task: [
265987
266016
  "thinking about your ask... \uD83E\uDD14",
@@ -265995,30 +266024,31 @@ var PHRASES = {
265995
266024
  "breaking it down... \uD83E\uDDE9",
265996
266025
  "figuring out the approach... \uD83E\uDDED",
265997
266026
  "strategizing... \uD83D\uDCCA",
265998
- "mapping out the steps... \uD83D\uDDFA️",
265999
- "scoping the work... \uD83D\uDCCF",
266000
266027
  "locking in... \uD83D\uDD12",
266001
266028
  "challenge accepted... ⚔️",
266002
266029
  "igniting the thinking engines... \uD83D\uDD25",
266003
- "prioritizing chaos... \uD83D\uDCDD",
266004
- "breaking down mental blocks... \uD83E\uDDF1",
266005
266030
  "assembling the mental toolkit... \uD83D\uDEE0️",
266006
266031
  "deploying logic... \uD83D\uDE80",
266007
266032
  "strategizing with style... \uD83D\uDC85",
266008
- "mapping the mental maze... \uD83D\uDDFA️",
266009
- "executing plan... ⚙️",
266010
- "optimizing approach... \uD83E\uDDE0",
266011
266033
  "thinking outside the semicolon... ;",
266012
266034
  "iterating on brilliance... \uD83D\uDD04",
266013
- "analyzing all angles... \uD83D\uDD3A",
266014
- "tracking progress... \uD83D\uDCCA",
266015
266035
  "debugging plan... \uD83D\uDC1E",
266016
- "brainstorming in HD... \uD83D\uDCAD",
266017
- "navigating chaos... \uD83E\uDDED",
266018
266036
  "forming master plan... \uD83C\uDFD7️",
266019
- "locking in focus... \uD83D\uDD12",
266020
- "assembling neural units... \uD83E\uDDE9",
266021
- "finalizing strategy... "
266037
+ "finalizing strategy... ",
266038
+ "understood the assignment fr... \uD83D\uDCCB✨",
266039
+ "no cap, on it... \uD83E\uDDE2\uD83E\uDEE1",
266040
+ "ate the task and left no crumbs... \uD83C\uDF7D️",
266041
+ "it's giving solution energy... \uD83D\uDCA1",
266042
+ "main character mode: activated... \uD83C\uDFAC",
266043
+ "bestie said get it done... \uD83D\uDC85",
266044
+ "lowkey about to cook... \uD83C\uDF73\uD83D\uDD25",
266045
+ "the plan said slay... \uD83D\uDC51",
266046
+ "we move, no thoughts, only code... \uD83D\uDEB6\uD83D\uDCBB",
266047
+ "challenge said hold my tokens... ⚡",
266048
+ "core memory unlocked: the fix... \uD83E\uDDE0✨",
266049
+ "rent free in my task queue... \uD83C\uDFE0",
266050
+ "it's giving productive... ⚙️✨",
266051
+ "the solution is built different... \uD83D\uDCAA"
266022
266052
  ],
266023
266053
  commit: [
266024
266054
  "sniffing the diff... \uD83D\uDC43",
@@ -266039,50 +266069,57 @@ var PHRASES = {
266039
266069
  "no WIP commits on my watch... \uD83D\uDEAB",
266040
266070
  "writing the commit future-you will thank me for... \uD83D\uDE4F",
266041
266071
  "touching the object store... \uD83D\uDDC4️",
266042
- "packing your changes real tight... \uD83D\uDCE6",
266043
- "signing off mentally... \uD83E\uDDE0",
266044
266072
  "squash? no. this one deserves to live... \uD83E\uDDEC",
266045
266073
  "git log will remember this... \uD83D\uDCD6",
266046
266074
  "diffing the vibe before committing... \uD83D\uDD0D",
266047
266075
  "committing to the bit. and also the repo... \uD83D\uDCBE",
266048
266076
  "generating a message with zero typos (probably)... \uD83D\uDE05",
266049
266077
  "making main proud... \uD83C\uDFC6",
266050
- "annotating history with chaos... \uD83D\uDCDD",
266051
- "staging brilliance... \uD83C\uDF1F",
266052
266078
  "blaming the compiler... \uD83E\uDD16",
266053
- "git commit -m 'oops'... \uD83D\uDE05",
266054
- "crafting legendary messages... \uD83C\uDFC6",
266055
266079
  "squashing typos... ✂️",
266056
- "documenting chaos... \uD83D\uDCDC",
266057
- "ghostwriting commit energy... \uD83D\uDC7B",
266058
- "pushing existential code... \uD83D\uDE80",
266059
- "reviewing self-inflicted bugs... \uD83D\uDE2D",
266060
- "summoning git spirits... \uD83D\uDC7B",
266061
266080
  "committing main character vibes... \uD83C\uDFAC",
266062
- "annotating universal truths... \uD83C\uDF0C",
266063
- "logging brain activity... \uD83E\uDDE0",
266064
- "staging memes... \uD83D\uDDFF",
266065
- "locking in brilliance... \uD83D\uDD12",
266066
266081
  "quantifying audacity... \uD83D\uDE24",
266067
- "making history, one commit at a time... \uD83D\uDCD6",
266068
266082
  "git blame: me again... \uD83D\uDE0E",
266069
- "finalizing legendary diff... "
266083
+ "committing crimes on the crimes command... \uD83D\uDD04",
266084
+ "lens committing lens. we're in the matrix... \uD83C\uDF00",
266085
+ "self-hosting the audacity... \uD83D\uDE24",
266086
+ "git push origin self-awareness... \uD83E\uDDE0",
266087
+ "third time's the charm... \uD83E\uDD1E",
266088
+ "the diff said no cap... \uD83E\uDDE2",
266089
+ "ate the staged changes and left no crumbs... \uD83C\uDF7D️",
266090
+ "understood the commit assignment... \uD83D\uDCCB✨",
266091
+ "it's giving conventional commits... \uD83D\uDC85",
266092
+ "the git log said main character only... \uD83C\uDFAC\uD83D\uDC51",
266093
+ "bestie said push it... \uD83D\uDCE1",
266094
+ "lowkey this diff goes hard... \uD83D\uDD25",
266095
+ "the commit message said slay... \uD83D\uDC85✨",
266096
+ "core memory unlocked: touching prod... \uD83D\uDE2D",
266097
+ "it's giving feat: energy... ⚡",
266098
+ "no WIP commits, we're built different... \uD83D\uDCAA",
266099
+ "git said understood the assignment... ✨",
266100
+ "pushing to main with zero hesitation... \uD83D\uDE80",
266101
+ "the changelog said rent free... \uD83C\uDFE0"
266070
266102
  ]
266071
266103
  };
266072
- function pick2(list, lastIndex) {
266073
- let next = Math.floor(Math.random() * list.length);
266074
- if (next === lastIndex)
266075
- next = (next + 1) % list.length;
266076
- return next;
266077
- }
266078
266104
  function useThinkingPhrase(active, kind = "general", intervalMs = 4321) {
266079
266105
  const list = PHRASES[kind];
266080
266106
  const [index, setIndex] = import_react30.useState(() => Math.floor(Math.random() * list.length));
266107
+ const usedRef = import_react30.useRef(new Set);
266081
266108
  import_react30.useEffect(() => {
266082
266109
  if (!active)
266083
266110
  return;
266084
- setIndex((i) => pick2(list, i));
266085
- const id = setInterval(() => setIndex((i) => pick2(list, i)), intervalMs);
266111
+ const pickUnused = () => {
266112
+ if (usedRef.current.size >= list.length)
266113
+ usedRef.current.clear();
266114
+ let next;
266115
+ do {
266116
+ next = Math.floor(Math.random() * list.length);
266117
+ } while (usedRef.current.has(next));
266118
+ usedRef.current.add(next);
266119
+ return next;
266120
+ };
266121
+ setIndex(pickUnused());
266122
+ const id = setInterval(() => setIndex(pickUnused()), intervalMs);
266086
266123
  return () => clearInterval(id);
266087
266124
  }, [active, kind, intervalMs]);
266088
266125
  return list[index];
@@ -271711,7 +271748,7 @@ Please continue your response based on this output.`
271711
271748
  return { role: m.role, content: m.content };
271712
271749
  });
271713
271750
  }
271714
- async function callChat(provider, systemPrompt, messages, abortSignal) {
271751
+ async function callChat(provider, systemPrompt, messages, abortSignal, retries = 2) {
271715
271752
  const apiMessages = [
271716
271753
  ...buildFewShotMessages(),
271717
271754
  ...buildApiMessages(messages)
@@ -271748,22 +271785,39 @@ async function callChat(provider, systemPrompt, messages, abortSignal) {
271748
271785
  const controller = new AbortController;
271749
271786
  const timer = setTimeout(() => controller.abort(), 60000);
271750
271787
  abortSignal?.addEventListener("abort", () => controller.abort());
271751
- const res = await fetch(url, {
271752
- method: "POST",
271753
- headers,
271754
- body: JSON.stringify(body),
271755
- signal: controller.signal
271756
- });
271757
- clearTimeout(timer);
271758
- if (!res.ok)
271759
- throw new Error(`API error ${res.status}: ${await res.text()}`);
271760
- const data = await res.json();
271761
- if (provider.type === "anthropic") {
271762
- const content = data.content;
271763
- return content.filter((b) => b.type === "text").map((b) => b.text).join("");
271764
- } else {
271765
- const choices = data.choices;
271766
- return choices[0]?.message.content ?? "";
271788
+ try {
271789
+ const res = await fetch(url, {
271790
+ method: "POST",
271791
+ headers,
271792
+ body: JSON.stringify(body),
271793
+ signal: controller.signal
271794
+ });
271795
+ clearTimeout(timer);
271796
+ if (!res.ok) {
271797
+ const errText = await res.text();
271798
+ if (res.status >= 500 && retries > 0) {
271799
+ await new Promise((r) => setTimeout(r, 1500));
271800
+ return callChat(provider, systemPrompt, messages, abortSignal, retries - 1);
271801
+ }
271802
+ throw new Error(`API error ${res.status}: ${errText}`);
271803
+ }
271804
+ const data = await res.json();
271805
+ if (provider.type === "anthropic") {
271806
+ const content = data.content;
271807
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("");
271808
+ } else {
271809
+ const choices = data.choices;
271810
+ return choices[0]?.message.content ?? "";
271811
+ }
271812
+ } catch (err) {
271813
+ clearTimeout(timer);
271814
+ if (err instanceof Error && err.name === "AbortError")
271815
+ throw err;
271816
+ if (retries > 0) {
271817
+ await new Promise((r) => setTimeout(r, 1500));
271818
+ return callChat(provider, systemPrompt, messages, abortSignal, retries - 1);
271819
+ }
271820
+ throw err;
271767
271821
  }
271768
271822
  }
271769
271823
 
@@ -277516,7 +277570,7 @@ var jsx_dev_runtime26 = __toESM(require_jsx_dev_runtime(), 1);
277516
277570
  registerBuiltins();
277517
277571
  await loadAddons();
277518
277572
  var program2 = new Command;
277519
- program2.command("repo <url>").description("Analyze a remote repository").action((url) => {
277573
+ program2.command("stalk <url>").alias("repo").description("Analyze a remote repository").action((url) => {
277520
277574
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(RepoCommand, {
277521
277575
  url
277522
277576
  }, undefined, false, undefined, this));
@@ -277524,28 +277578,28 @@ program2.command("repo <url>").description("Analyze a remote repository").action
277524
277578
  program2.command("provider").description("Configure AI providers").action(() => {
277525
277579
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(InitCommand, {}, undefined, false, undefined, this));
277526
277580
  });
277527
- program2.command("review [path]").description("Review a local codebase").action((inputPath) => {
277581
+ program2.command("judge [path]").alias("review").description("Review a local codebase").action((inputPath) => {
277528
277582
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(ReviewCommand, {
277529
277583
  path: inputPath ?? "."
277530
277584
  }, undefined, false, undefined, this));
277531
277585
  });
277532
- program2.command("task <text>").description("Apply a natural language change to the codebase").option("-p, --path <path>", "Path to the repo", ".").action((text, opts) => {
277586
+ program2.command("cook <text>").alias("task").description("Apply a natural language change to the codebase").option("-p, --path <path>", "Path to the repo", ".").action((text, opts) => {
277533
277587
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(TaskCommand, {
277534
277588
  prompt: text,
277535
277589
  path: opts.path
277536
277590
  }, undefined, false, undefined, this));
277537
277591
  });
277538
- program2.command("chat").description("Chat with your codebase — ask questions or make changes").option("-p, --path <path>", "Path to the repo", ".").action((opts) => {
277592
+ program2.command("vibe").alias("chat").description("Chat with your codebase — ask questions or make changes").option("-p, --path <path>", "Path to the repo", ".").action((opts) => {
277539
277593
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(ChatCommand, {
277540
277594
  path: opts.path
277541
277595
  }, undefined, false, undefined, this));
277542
277596
  });
277543
- program2.command("timeline").description("Explore your code history — see commits, changes, and evolution").option("-p, --path <path>", "Path to the repo", ".").action((opts) => {
277597
+ program2.command("history").alias("timeline").description("Explore your code history — see commits, changes, and evolution").option("-p, --path <path>", "Path to the repo", ".").action((opts) => {
277544
277598
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(TimelineCommand, {
277545
277599
  path: opts.path
277546
277600
  }, undefined, false, undefined, this));
277547
277601
  });
277548
- program2.command("commit [files...]").description("Generate a smart conventional commit message from staged changes or specific files").option("-p, --path <path>", "Path to the repo", ".").option("--auto", "Stage all changes (or the given files) and commit without confirmation").option("--confirm", "Show preview before committing even when using --auto").option("--preview", "Show the generated message without committing").option("--push", "Push to remote after committing").action((files, opts) => {
277602
+ program2.command("crimes [files...]").alias("commit").description("Generate a smart conventional commit message from staged changes or specific files").option("-p, --path <path>", "Path to the repo", ".").option("--auto", "Stage all changes (or the given files) and commit without confirmation").option("--confirm", "Show preview before committing even when using --auto").option("--preview", "Show the generated message without committing").option("--push", "Push to remote after committing").action((files, opts) => {
277549
277603
  render_default(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(CommitCommand, {
277550
277604
  path: opts.path,
277551
277605
  files: files ?? [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.2.6",
3
+ "version": "0.2.9",
4
4
  "description": "Know Your Codebase.",
5
5
  "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
6
6
  "license": "MIT",
package/src/index.tsx CHANGED
@@ -18,7 +18,8 @@ await loadAddons();
18
18
  const program = new Command();
19
19
 
20
20
  program
21
- .command("repo <url>")
21
+ .command("stalk <url>")
22
+ .alias("repo")
22
23
  .description("Analyze a remote repository")
23
24
  .action((url) => {
24
25
  render(<RepoCommand url={url} />);
@@ -32,14 +33,16 @@ program
32
33
  });
33
34
 
34
35
  program
35
- .command("review [path]")
36
+ .command("judge [path]")
37
+ .alias("review")
36
38
  .description("Review a local codebase")
37
39
  .action((inputPath) => {
38
40
  render(<ReviewCommand path={inputPath ?? "."} />);
39
41
  });
40
42
 
41
43
  program
42
- .command("task <text>")
44
+ .command("cook <text>")
45
+ .alias("task")
43
46
  .description("Apply a natural language change to the codebase")
44
47
  .option("-p, --path <path>", "Path to the repo", ".")
45
48
  .action((text: string, opts: { path: string }) => {
@@ -47,7 +50,8 @@ program
47
50
  });
48
51
 
49
52
  program
50
- .command("chat")
53
+ .command("vibe")
54
+ .alias("chat")
51
55
  .description("Chat with your codebase — ask questions or make changes")
52
56
  .option("-p, --path <path>", "Path to the repo", ".")
53
57
  .action((opts: { path: string }) => {
@@ -55,7 +59,8 @@ program
55
59
  });
56
60
 
57
61
  program
58
- .command("timeline")
62
+ .command("history")
63
+ .alias("timeline")
59
64
  .description(
60
65
  "Explore your code history — see commits, changes, and evolution",
61
66
  )
@@ -65,7 +70,8 @@ program
65
70
  });
66
71
 
67
72
  program
68
- .command("commit [files...]")
73
+ .command("crimes [files...]")
74
+ .alias("commit")
69
75
  .description(
70
76
  "Generate a smart conventional commit message from staged changes or specific files",
71
77
  )
package/src/utils/chat.ts CHANGED
@@ -218,6 +218,7 @@ export async function callChat(
218
218
  systemPrompt: string,
219
219
  messages: Message[],
220
220
  abortSignal?: AbortSignal,
221
+ retries = 2,
221
222
  ): Promise<string> {
222
223
  const apiMessages = [
223
224
  ...buildFewShotMessages(),
@@ -259,24 +260,55 @@ export async function callChat(
259
260
  const timer = setTimeout(() => controller.abort(), 60_000);
260
261
  abortSignal?.addEventListener("abort", () => controller.abort());
261
262
 
262
- const res = await fetch(url, {
263
- method: "POST",
264
- headers,
265
- body: JSON.stringify(body),
266
- signal: controller.signal,
267
- });
268
- clearTimeout(timer);
269
- if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
270
- const data = (await res.json()) as Record<string, unknown>;
263
+ try {
264
+ const res = await fetch(url, {
265
+ method: "POST",
266
+ headers,
267
+ body: JSON.stringify(body),
268
+ signal: controller.signal,
269
+ });
270
+ clearTimeout(timer);
271
271
 
272
- if (provider.type === "anthropic") {
273
- const content = data.content as { type: string; text: string }[];
274
- return content
275
- .filter((b) => b.type === "text")
276
- .map((b) => b.text)
277
- .join("");
278
- } else {
279
- const choices = data.choices as { message: { content: string } }[];
280
- return choices[0]?.message.content ?? "";
272
+ if (!res.ok) {
273
+ const errText = await res.text();
274
+ if (res.status >= 500 && retries > 0) {
275
+ await new Promise((r) => setTimeout(r, 1500));
276
+ return callChat(
277
+ provider,
278
+ systemPrompt,
279
+ messages,
280
+ abortSignal,
281
+ retries - 1,
282
+ );
283
+ }
284
+ throw new Error(`API error ${res.status}: ${errText}`);
285
+ }
286
+
287
+ const data = (await res.json()) as Record<string, unknown>;
288
+
289
+ if (provider.type === "anthropic") {
290
+ const content = data.content as { type: string; text: string }[];
291
+ return content
292
+ .filter((b) => b.type === "text")
293
+ .map((b) => b.text)
294
+ .join("");
295
+ } else {
296
+ const choices = data.choices as { message: { content: string } }[];
297
+ return choices[0]?.message.content ?? "";
298
+ }
299
+ } catch (err) {
300
+ clearTimeout(timer);
301
+ if (err instanceof Error && err.name === "AbortError") throw err;
302
+ if (retries > 0) {
303
+ await new Promise((r) => setTimeout(r, 1500));
304
+ return callChat(
305
+ provider,
306
+ systemPrompt,
307
+ messages,
308
+ abortSignal,
309
+ retries - 1,
310
+ );
311
+ }
312
+ throw err;
281
313
  }
282
314
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
 
3
3
  const PHRASES: Record<string, string[]> = {
4
4
  general: [
@@ -12,7 +12,6 @@ const PHRASES: Record<string, string[]> = {
12
12
  "reducing the codebase... 🧠",
13
13
  "deglazing with tokens... ✨",
14
14
  "plating the thoughts... 🍽️",
15
- // new additions
16
15
  "blending the chaos... 🌀",
17
16
  "tasting the logic... 👅",
18
17
  "kneading the ideas... 🥖",
@@ -33,6 +32,23 @@ const PHRASES: Record<string, string[]> = {
33
32
  "whipping up brilliance... 🥄💡",
34
33
  "folding in main character energy... 🌟",
35
34
  "checking internal temperatures... 🌡️",
35
+ "it's giving... answers 💅",
36
+ "no cap, thinking fr fr... 🧢",
37
+ "lowkey cooking something bussin... 🍳",
38
+ "understood the assignment... 📋✨",
39
+ "ate and left no crumbs... 🍽️",
40
+ "the delulu is the solulu... 🌈",
41
+ "giving it the roman empire treatment... 🏛️",
42
+ "slay mode activated... 💅",
43
+ "not me actually thinking rn... 😭",
44
+ "this ain't it chief... wait yes it is... 👑",
45
+ "rent free in the context window... 🏠",
46
+ "it's the tokens for me... ✨",
47
+ "vibing with the prompt... 🎵",
48
+ "main character moment incoming... 🎬",
49
+ "we move... 🫡🫡",
50
+ "rizzing up an answer... 🗣️",
51
+ "rizzing up a baddie answer... 😏",
36
52
  ],
37
53
 
38
54
  cloning: [
@@ -50,7 +66,6 @@ const PHRASES: Record<string, string[]> = {
50
66
  "git is being git... 🤷",
51
67
  "praying to the network gods... 🙏",
52
68
  "downloading the internet (just your repo)... 🌐",
53
- // new additions
54
69
  "forking in style... 🍴",
55
70
  "pulling commits aggressively... 🏃‍♂️",
56
71
  "syncing the vibes... 🌊",
@@ -60,9 +75,7 @@ const PHRASES: Record<string, string[]> = {
60
75
  "fast-forwarding thoughts... ⏩",
61
76
  "merging with the multiverse... 🌌",
62
77
  "rebasing reality... 🔄",
63
- "checking out chaos... 😎",
64
78
  "cloning like a pro... 👑",
65
- "replicating the vibes... 🔁",
66
79
  "buffer overflow in progress... 💾",
67
80
  "pull request to sanity... 🤯",
68
81
  "diverging the timeline... ⏳",
@@ -71,6 +84,15 @@ const PHRASES: Record<string, string[]> = {
71
84
  "pruning branches... 🌿",
72
85
  "syncing neurons... 🧠",
73
86
  "shallow clone, deep existential dread... 🌊😵",
87
+ "ate the repo and left no crumbs... 🍽️",
88
+ "this repo said no cap... 🧢",
89
+ "cloning the main character arc... 🎬",
90
+ "snatching the source code... 💅",
91
+ "github said understood the assignment... ✨",
92
+ "bestie said fetch... 📡",
93
+ "giving the repo its flowers... 💐",
94
+ "core memory unlocked: git clone... 🧠",
95
+ "no diff, no life... 🫠",
74
96
  ],
75
97
 
76
98
  analyzing: [
@@ -87,31 +109,33 @@ const PHRASES: Record<string, string[]> = {
87
109
  "appreciating the tech debt... 💳",
88
110
  "counting your TODOs... 📋",
89
111
  "reading between the lines... ✍️",
90
- "anthropic-ifying your codebase... 🤖",
91
112
  "following the import trail... 🧭",
92
113
  "speed-reading your life's work... ⚡",
93
114
  "pretending to understand your monorepo... 🏢",
94
- // new additions
95
115
  "debugging the cosmos... 🌌",
96
116
  "profiling your thoughts... 📊",
97
- "mapping mental threads... 🧵",
98
117
  "sifting through the noise... 🌪️",
99
118
  "predicting your next move... 🔮",
100
- "annotating life... ✍️",
101
119
  "absorbing the vibes... 🧘",
102
120
  "peering into recursion... 🔁",
103
- "modeling chaos... 🧩",
104
121
  "counting edge cases... ⚠️",
105
- "tracking call stacks... 🧗",
106
122
  "parsing your aura... 🌈",
107
123
  "reading the README of life... 📖",
108
124
  "analyzing existential types... 🤯",
109
- "observing folder hierarchies... 📂",
110
- "profiling main character energy... 🎬",
111
- "absorbing every commit... 💾",
112
- "studying the architecture... 🏛️",
113
125
  "checking for ghost functions... 👻",
114
126
  "validating vibes... ✅",
127
+ "understood the codebase assignment... 📋✨",
128
+ "this folder structure is NOT it... 😭",
129
+ "the tech debt said rent free... 🏠",
130
+ "ate the architecture... 🍽️",
131
+ "giving the codebase the side eye... 👀",
132
+ "no cap this code goes hard... 🧢🔥",
133
+ "the imports are giving chaos... 🌀",
134
+ "bestie said touch grass... but first, touch files... 🌿",
135
+ "core memory unlocked: spaghetti code... 🍝",
136
+ "it's giving legacy... 💀",
137
+ "main character files identified... 🎬",
138
+ "the TODO comments said it all... 😤",
115
139
  ],
116
140
 
117
141
  model: [
@@ -122,10 +146,8 @@ const PHRASES: Record<string, string[]> = {
122
146
  "consulting the file oracle... 🔮",
123
147
  "narrowing it down... 🔍",
124
148
  "skimming the directory... 📂",
125
- "choosing wisely... 🧠",
126
149
  "not reading everything (smart)... 😎",
127
150
  "filtering the noise... 🔇",
128
- "asking the oracle... 🔮",
129
151
  "pinging the motherbrain... 🧠",
130
152
  "waiting on the neurons... ⏳",
131
153
  "tokens incoming... 📥",
@@ -133,7 +155,6 @@ const PHRASES: Record<string, string[]> = {
133
155
  "inference in progress... ⚙️",
134
156
  "the GPU is thinking... 💻",
135
157
  "attention is all we need... 🎯",
136
- "cross-attending to your vibes... 🔄",
137
158
  "softmaxing the options... 📊",
138
159
  "sampling from the distribution... 🎲",
139
160
  "temperature: just right... 🌡️",
@@ -142,27 +163,27 @@ const PHRASES: Record<string, string[]> = {
142
163
  "beaming thoughts from the datacenter... 📡",
143
164
  "anthropic servers sweating... 💦",
144
165
  "matrix multiply in progress... ➗",
145
- // new additions
146
166
  "initializing hype mode... 🚀",
147
- "compiling neurons... 🧠",
148
167
  "loading chaotic energy... ⚡",
149
168
  "predicting your mood... 😏",
150
169
  "sampling absurdity... 🎲",
151
- "normalizing brainwaves... 🌊",
152
- "cross-validating genius... 🧪",
153
170
  "activating main character module... 🎬",
154
171
  "warming up GPU... 💻🔥",
155
- "spooling synapses... 🧵",
156
172
  "calculating cringe factor... 🤡",
157
- "preprocessing drama... 📦",
158
- "injecting chaos vectors... ⚡",
159
- "softmaxing life choices... 🧠",
160
- "attention heads online... 👀",
161
- "broadcasting vibes... 📡",
162
173
  "loading meme embeddings... 🗿",
163
- "quantizing mood... 🔢",
164
174
  "clipping gradients of patience... ✂️",
165
- "inferring main plot points... 📖",
175
+ "the model said understood... ",
176
+ "tokens said no cap... 🧢",
177
+ "GPU said slay... 💅",
178
+ "attention heads said bestie... 👀",
179
+ "inference said we move... 🚶",
180
+ "model ate and left no crumbs... 🍽️",
181
+ "the context window said it's giving... 🪟✨",
182
+ "softmax said understood the assignment... 📊",
183
+ "neurons said main character energy... 🧠🌟",
184
+ "it's giving transformer energy... 🤖",
185
+ "the embeddings said rent free... 🏠",
186
+ "lowkey the model is built different... 💪",
166
187
  ],
167
188
 
168
189
  summary: [
@@ -181,27 +202,30 @@ const PHRASES: Record<string, string[]> = {
181
202
  "cooking up the analysis... 🍳",
182
203
  "almost done judging your code... 👀",
183
204
  "writing the report card... 📊",
184
- // new additions
185
205
  "digesting chaos into art... 🎨",
186
206
  "compressing existential dread... 🫠",
187
- "executing life summary... 📝",
188
- "extracting hot takes... 🔥",
189
207
  "packaging thoughts nicely... 📦",
190
208
  "synthesizing main character energy... 🌟",
191
209
  "boiling down code spaghetti... 🍝",
192
210
  "condensing into brilliance... ✨",
193
- "drawing the final diagram... 📊",
194
- "reporting on universal logic... 🌌",
195
211
  "giving verdict with style... 😎",
196
212
  "folding in chaos gracefully... 🌀",
197
213
  "capturing essence... 📸",
198
214
  "writing the epic conclusion... 📜",
199
- "connecting all dots... 🔗",
200
- "rendering summary in HD... 📺",
201
- "wrapping thoughts in elegance... 🎁",
202
215
  "making insights bussin... 🥵",
203
- "compressing noise... 🔇",
204
216
  "finishing the mental buffet... 🍽️",
217
+ "the verdict said no cap... 🧢",
218
+ "ate the analysis and left no crumbs... 🍽️✨",
219
+ "understood the assignment, delivering... 📋💅",
220
+ "it's giving final boss energy... 👑",
221
+ "the summary said slay... 💅",
222
+ "hot take incoming, no cap... 🔥🧢",
223
+ "core memory unlocked: the truth... 🧠",
224
+ "main character conclusion activated... 🎬",
225
+ "giving the codebase its honest review... 💀",
226
+ "the analysis said we move... 🚶✨",
227
+ "lowkey this code needs therapy... 🛋️",
228
+ "it's giving mixed reviews bestie... 😬",
205
229
  ],
206
230
 
207
231
  task: [
@@ -216,31 +240,31 @@ const PHRASES: Record<string, string[]> = {
216
240
  "breaking it down... 🧩",
217
241
  "figuring out the approach... 🧭",
218
242
  "strategizing... 📊",
219
- "mapping out the steps... 🗺️",
220
- "scoping the work... 📏",
221
243
  "locking in... 🔒",
222
244
  "challenge accepted... ⚔️",
223
- // new additions
224
245
  "igniting the thinking engines... 🔥",
225
- "prioritizing chaos... 📝",
226
- "breaking down mental blocks... 🧱",
227
246
  "assembling the mental toolkit... 🛠️",
228
247
  "deploying logic... 🚀",
229
248
  "strategizing with style... 💅",
230
- "mapping the mental maze... 🗺️",
231
- "executing plan... ⚙️",
232
- "optimizing approach... 🧠",
233
249
  "thinking outside the semicolon... ;",
234
250
  "iterating on brilliance... 🔄",
235
- "analyzing all angles... 🔺",
236
- "tracking progress... 📊",
237
251
  "debugging plan... 🐞",
238
- "brainstorming in HD... 💭",
239
- "navigating chaos... 🧭",
240
252
  "forming master plan... 🏗️",
241
- "locking in focus... 🔒",
242
- "assembling neural units... 🧩",
243
253
  "finalizing strategy... ✅",
254
+ "understood the assignment fr... 📋✨",
255
+ "no cap, on it... 🧢🫡",
256
+ "ate the task and left no crumbs... 🍽️",
257
+ "it's giving solution energy... 💡",
258
+ "main character mode: activated... 🎬",
259
+ "bestie said get it done... 💅",
260
+ "lowkey about to cook... 🍳🔥",
261
+ "the plan said slay... 👑",
262
+ "we move, no thoughts, only code... 🚶💻",
263
+ "challenge said hold my tokens... ⚡",
264
+ "core memory unlocked: the fix... 🧠✨",
265
+ "rent free in my task queue... 🏠",
266
+ "it's giving productive... ⚙️✨",
267
+ "the solution is built different... 💪",
244
268
  ],
245
269
 
246
270
  commit: [
@@ -262,44 +286,39 @@ const PHRASES: Record<string, string[]> = {
262
286
  "no WIP commits on my watch... 🚫",
263
287
  "writing the commit future-you will thank me for... 🙏",
264
288
  "touching the object store... 🗄️",
265
- "packing your changes real tight... 📦",
266
- "signing off mentally... 🧠",
267
289
  "squash? no. this one deserves to live... 🧬",
268
290
  "git log will remember this... 📖",
269
291
  "diffing the vibe before committing... 🔍",
270
292
  "committing to the bit. and also the repo... 💾",
271
293
  "generating a message with zero typos (probably)... 😅",
272
294
  "making main proud... 🏆",
273
- // new additions
274
- "annotating history with chaos... 📝",
275
- "staging brilliance... 🌟",
276
295
  "blaming the compiler... 🤖",
277
- "git commit -m 'oops'... 😅",
278
- "crafting legendary messages... 🏆",
279
296
  "squashing typos... ✂️",
280
- "documenting chaos... 📜",
281
- "ghostwriting commit energy... 👻",
282
- "pushing existential code... 🚀",
283
- "reviewing self-inflicted bugs... 😭",
284
- "summoning git spirits... 👻",
285
297
  "committing main character vibes... 🎬",
286
- "annotating universal truths... 🌌",
287
- "logging brain activity... 🧠",
288
- "staging memes... 🗿",
289
- "locking in brilliance... 🔒",
290
298
  "quantifying audacity... 😤",
291
- "making history, one commit at a time... 📖",
292
299
  "git blame: me again... 😎",
293
- "finalizing legendary diff... ",
300
+ "committing crimes on the crimes command... 🔄",
301
+ "lens committing lens. we're in the matrix... 🌀",
302
+ "self-hosting the audacity... 😤",
303
+ "git push origin self-awareness... 🧠",
304
+ "third time's the charm... 🤞",
305
+ "the diff said no cap... 🧢",
306
+ "ate the staged changes and left no crumbs... 🍽️",
307
+ "understood the commit assignment... 📋✨",
308
+ "it's giving conventional commits... 💅",
309
+ "the git log said main character only... 🎬👑",
310
+ "bestie said push it... 📡",
311
+ "lowkey this diff goes hard... 🔥",
312
+ "the commit message said slay... 💅✨",
313
+ "core memory unlocked: touching prod... 😭",
314
+ "it's giving feat: energy... ⚡",
315
+ "no WIP commits, we're built different... 💪",
316
+ "git said understood the assignment... ✨",
317
+ "pushing to main with zero hesitation... 🚀",
318
+ "the changelog said rent free... 🏠",
294
319
  ],
295
320
  };
296
321
 
297
- function pick(list: string[], lastIndex: number): number {
298
- let next = Math.floor(Math.random() * list.length);
299
- if (next === lastIndex) next = (next + 1) % list.length;
300
- return next;
301
- }
302
-
303
322
  export type ThinkingKind = keyof typeof PHRASES;
304
323
 
305
324
  export function useThinkingPhrase(
@@ -311,11 +330,23 @@ export function useThinkingPhrase(
311
330
  const [index, setIndex] = useState(() =>
312
331
  Math.floor(Math.random() * list.length),
313
332
  );
333
+ const usedRef = useRef<Set<number>>(new Set());
314
334
 
315
335
  useEffect(() => {
316
336
  if (!active) return;
317
- setIndex((i) => pick(list, i));
318
- const id = setInterval(() => setIndex((i) => pick(list, i)), intervalMs);
337
+
338
+ const pickUnused = () => {
339
+ if (usedRef.current.size >= list.length) usedRef.current.clear();
340
+ let next;
341
+ do {
342
+ next = Math.floor(Math.random() * list.length);
343
+ } while (usedRef.current.has(next));
344
+ usedRef.current.add(next);
345
+ return next;
346
+ };
347
+
348
+ setIndex(pickUnused());
349
+ const id = setInterval(() => setIndex(pickUnused()), intervalMs);
319
350
  return () => clearInterval(id);
320
351
  }, [active, kind, intervalMs]);
321
352
 
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import Header from '../Header';
4
-
5
- describe('Header', () => {
6
- it('renders without crashing', () => {
7
- render(<Header />);
8
- });
9
- });