@rafaeelricco/commit-tools 0.1.6 → 0.2.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/README.md +7 -11
- package/dist/index.js +33 -546
- package/dist/index.js.map +1 -422
- package/dist/model-selector-6VVZJUA5.js +3 -0
- package/dist/model-selector-6VVZJUA5.js.map +1 -0
- package/package.json +13 -12
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import*as t from"react";import{Box as d,Text as c,useInput as y,useApp as A}from"ink";import M from"chalk";function B(l,a){let e=l.toLowerCase(),h=a.toLowerCase(),o=0,f=0;for(;o<e.length&&f<h.length;)e[o]===h[f]&&o++,f++;return o===e.length}function I(l,a){let e=a-1;for(;e>0&&l[e-1]===" ";)e--;for(;e>0&&l[e-1]!==" ";)e--;return[l.slice(0,e)+l.slice(a),e]}function R(l,a){let e=a-1;for(;e>0&&l[e-1]===" ";)e--;for(;e>0&&l[e-1]!==" ";)e--;return e}function C(l,a){let e=a;for(;e<l.length&&l[e]===" ";)e++;for(;e<l.length&&l[e]!==" ";)e++;return e}var P=({models:l,onSelect:a,onCancel:e})=>{let{exit:h}=A(),[o,f]=t.useState(""),[s,x]=t.useState(0),[m,p]=t.useState(0),g=l.filter(n=>{let r=o.trim();return r?B(r,n.id)||n.description&&B(r,n.description):!0});y((n,r)=>{if(r.escape){e(),h();return}if(r.upArrow){p(i=>Math.max(0,i-1));return}if(r.downArrow){p(i=>Math.min(g.length-1,i+1));return}if(r.return){let i=g[m];i?a(i.id):e(),h();return}if(r.meta&&r.backspace||r.ctrl&&n==="w"){let[i,S]=I(o,s);f(i),x(Math.max(0,S));return}if(r.ctrl&&n==="u"){f(o.slice(s)),x(0);return}if(r.meta&&r.leftArrow){x(Math.max(0,R(o,s)));return}if(r.meta&&r.rightArrow){x(Math.min(o.length,C(o,s)));return}if(r.leftArrow){x(i=>Math.max(0,i-1));return}if(r.rightArrow){x(i=>Math.min(o.length,i+1));return}if(r.backspace||r.delete){s>0&&(f(o.slice(0,s-1)+o.slice(s)),x(i=>Math.max(0,i-1)));return}n&&!r.ctrl&&!r.meta&&(f(o.slice(0,s)+n+o.slice(s)),x(i=>i+n.length))}),t.useEffect(()=>{p(0)},[o]);let b=5,T=Math.max(0,Math.min(m-2,g.length-b)),w=g.slice(T,T+b),u="";for(let n=0;n<o.length;n++)n===s?u+=M.inverse(o[n]):u+=o[n];return s===o.length&&(u+=M.inverse(" ")),t.createElement(d,{flexDirection:"column"},t.createElement(d,null,t.createElement(c,{color:"gray"},"\u2502")),t.createElement(d,null,t.createElement(c,{color:"cyan"},"\u25C7 "),t.createElement(c,null,"Search and select model")),t.createElement(d,null,t.createElement(c,{color:"gray"},"\u2502 "),t.createElement(c,{color:"cyan"},"> "),o.length===0?t.createElement(c,{color:"gray"},M.inverse(" ")," type to search"):t.createElement(c,null,u)),t.createElement(d,{flexDirection:"row"},t.createElement(d,{flexDirection:"column",marginRight:1},Array.from({length:Math.max(1,w.length)+2}).map((n,r)=>t.createElement(c,{key:r,color:"gray"},"\u2502"))),t.createElement(d,{borderStyle:"round",borderColor:"gray",paddingX:0,paddingY:0,flexDirection:"column",width:64},w.length===0?t.createElement(d,null,t.createElement(c,{color:"red"}," No models found.")):w.map(n=>{let i=g.indexOf(n)===m;return t.createElement(d,{key:n.id},t.createElement(c,{color:i?"cyan":"gray"},i?" \u25CF ":" \u25CB "),i?t.createElement(c,{color:"cyan"},n.id):t.createElement(c,null,n.id))}))),t.createElement(d,null,t.createElement(c,{color:"gray"},"\u2502")))};export{P as ModelSelector};
|
|
3
|
+
//# sourceMappingURL=model-selector-6VVZJUA5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/ui/components/model-selector.tsx"],"sourcesContent":["export { ModelSelector, type Model, type ModelSelectorProps };\n\nimport * as React from \"react\";\n\nimport { Box, Text, useInput, useApp } from \"ink\";\n\nimport chalk from \"chalk\";\n\ntype Model = {\n id: string;\n description: string;\n};\n\ntype ModelSelectorProps = {\n models: Model[];\n onSelect: (modelId: string) => void;\n onCancel: () => void;\n};\n\nfunction fuzzyMatch(pattern: string, target: string): boolean {\n const p = pattern.toLowerCase();\n const t = target.toLowerCase();\n let pi = 0;\n let ti = 0;\n\n while (pi < p.length && ti < t.length) {\n if (p[pi] === t[ti]) pi++;\n ti++;\n }\n\n return pi === p.length;\n}\n\nfunction deleteWordLeft(value: string, cursor: number): [string, number] {\n let i = cursor - 1;\n while (i > 0 && value[i - 1] === \" \") i--;\n while (i > 0 && value[i - 1] !== \" \") i--;\n return [value.slice(0, i) + value.slice(cursor), i];\n}\n\nfunction wordBoundaryLeft(value: string, cursor: number): number {\n let i = cursor - 1;\n while (i > 0 && value[i - 1] === \" \") i--;\n while (i > 0 && value[i - 1] !== \" \") i--;\n return i;\n}\n\nfunction wordBoundaryRight(value: string, cursor: number): number {\n let i = cursor;\n while (i < value.length && value[i] === \" \") i++;\n while (i < value.length && value[i] !== \" \") i++;\n return i;\n}\n\nconst ModelSelector = ({ models, onSelect, onCancel }: ModelSelectorProps) => {\n const { exit } = useApp();\n const [query, setQuery] = React.useState(\"\");\n const [cursorOffset, setCursorOffset] = React.useState(0);\n const [selectedIndex, setSelectedIndex] = React.useState(0);\n\n const filteredModels = models.filter((m) => {\n const q = query.trim();\n if (!q) return true;\n return fuzzyMatch(q, m.id) || (m.description && fuzzyMatch(q, m.description));\n });\n\n useInput((input, key) => {\n if (key.escape) {\n onCancel();\n exit();\n return;\n }\n if (key.upArrow) {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n return;\n }\n if (key.downArrow) {\n setSelectedIndex((prev) => Math.min(filteredModels.length - 1, prev + 1));\n return;\n }\n if (key.return) {\n const selectedModel = filteredModels[selectedIndex];\n if (selectedModel) {\n onSelect(selectedModel.id);\n } else {\n onCancel();\n }\n exit();\n return;\n }\n\n // Option+Delete or Ctrl+W — delete word left\n if ((key.meta && key.backspace) || (key.ctrl && input === \"w\")) {\n const [next, pos] = deleteWordLeft(query, cursorOffset);\n setQuery(next);\n setCursorOffset(Math.max(0, pos));\n return;\n }\n\n // Ctrl+U — delete to start of line\n if (key.ctrl && input === \"u\") {\n setQuery(query.slice(cursorOffset));\n setCursorOffset(0);\n return;\n }\n\n // Option+Left/Right — move cursor by word\n if (key.meta && key.leftArrow) {\n setCursorOffset(Math.max(0, wordBoundaryLeft(query, cursorOffset)));\n return;\n }\n if (key.meta && key.rightArrow) {\n setCursorOffset(Math.min(query.length, wordBoundaryRight(query, cursorOffset)));\n return;\n }\n\n // Left/Right arrow - move cursor 1 char\n if (key.leftArrow) {\n setCursorOffset((prev) => Math.max(0, prev - 1));\n return;\n }\n if (key.rightArrow) {\n setCursorOffset((prev) => Math.min(query.length, prev + 1));\n return;\n }\n\n // Regular backspace — delete one char\n if (key.backspace || key.delete) {\n if (cursorOffset > 0) {\n setQuery(query.slice(0, cursorOffset - 1) + query.slice(cursorOffset));\n setCursorOffset((prev) => Math.max(0, prev - 1));\n }\n return;\n }\n\n // Regular character input\n if (input && !key.ctrl && !key.meta) {\n setQuery(query.slice(0, cursorOffset) + input + query.slice(cursorOffset));\n setCursorOffset((prev) => prev + input.length);\n }\n });\n\n React.useEffect(() => {\n setSelectedIndex(0);\n }, [query]);\n\n const windowSize = 5;\n const startIndex = Math.max(0, Math.min(selectedIndex - 2, filteredModels.length - windowSize));\n const visibleModels = filteredModels.slice(startIndex, startIndex + windowSize);\n\n let renderedQuery = \"\";\n for (let i = 0; i < query.length; i++) {\n if (i === cursorOffset) {\n renderedQuery += chalk.inverse(query[i]);\n } else {\n renderedQuery += query[i];\n }\n }\n if (cursorOffset === query.length) {\n renderedQuery += chalk.inverse(\" \");\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color=\"gray\">│</Text>\n </Box>\n <Box>\n <Text color=\"cyan\">◇ </Text>\n <Text>Search and select model</Text>\n </Box>\n\n <Box>\n <Text color=\"gray\">│ </Text>\n <Text color=\"cyan\">{\"> \"}</Text>\n {query.length === 0 ?\n <Text color=\"gray\">{chalk.inverse(\" \")} type to search</Text>\n : <Text>{renderedQuery}</Text>}\n </Box>\n\n <Box flexDirection=\"row\">\n <Box flexDirection=\"column\" marginRight={1}>\n {Array.from({ length: Math.max(1, visibleModels.length) + 2 }).map((_, i) => (\n <Text key={i} color=\"gray\">\n │\n </Text>\n ))}\n </Box>\n <Box borderStyle=\"round\" borderColor=\"gray\" paddingX={0} paddingY={0} flexDirection=\"column\" width={64}>\n {visibleModels.length === 0 ?\n <Box>\n <Text color=\"red\"> No models found.</Text>\n </Box>\n : visibleModels.map((model) => {\n const globalIdx = filteredModels.indexOf(model);\n const isSelected = globalIdx === selectedIndex;\n\n return (\n <Box key={model.id}>\n <Text color={isSelected ? \"cyan\" : \"gray\"}>{isSelected ? \" ● \" : \" ○ \"}</Text>\n {isSelected ?\n <Text color=\"cyan\">{model.id}</Text>\n : <Text>{model.id}</Text>}\n </Box>\n );\n })\n }\n </Box>\n </Box>\n\n <Box>\n <Text color=\"gray\">│</Text>\n </Box>\n </Box>\n );\n};\n"],"mappings":"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","names":["React","Box","Text","useInput","useApp","chalk","fuzzyMatch","pattern","target","p","t","pi","ti","deleteWordLeft","value","cursor","i","wordBoundaryLeft","wordBoundaryRight","ModelSelector","models","onSelect","onCancel","exit","useApp","query","setQuery","cursorOffset","setCursorOffset","selectedIndex","setSelectedIndex","filteredModels","m","q","useInput","input","key","prev","selectedModel","next","pos","windowSize","startIndex","visibleModels","renderedQuery","chalk","Box","Text","_","model","isSelected"]}
|
package/package.json
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rafaeelricco/commit-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"module": "index.ts",
|
|
3
|
+
"version": "0.2.0",
|
|
5
4
|
"type": "module",
|
|
6
5
|
"bin": {
|
|
7
6
|
"commit": "./dist/index.js"
|
|
8
7
|
},
|
|
9
8
|
"scripts": {
|
|
10
|
-
"start": "
|
|
11
|
-
"setup": "
|
|
12
|
-
"doctor": "
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"build": "bun build ./index.ts --outdir ./dist --target bun --minify --sourcemap",
|
|
16
|
-
"install:global": "bun link",
|
|
9
|
+
"start": "tsx --env-file=.env index.ts",
|
|
10
|
+
"setup": "pnpm run start -- setup",
|
|
11
|
+
"doctor": "pnpm run start -- doctor",
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"install:global": "npm link",
|
|
17
14
|
"format": "npx prettier . --write",
|
|
18
15
|
"typecheck": "tsc --noEmit"
|
|
19
16
|
},
|
|
@@ -28,14 +25,16 @@
|
|
|
28
25
|
"dist"
|
|
29
26
|
],
|
|
30
27
|
"devDependencies": {
|
|
31
|
-
"@types/bun": "latest",
|
|
32
28
|
"@types/express": "^5.0.6",
|
|
33
29
|
"@types/ink-text-input": "^2.0.5",
|
|
34
30
|
"@types/luxon": "^3.7.1",
|
|
31
|
+
"@types/node": "^22",
|
|
35
32
|
"@types/react": "^19.2.14",
|
|
36
33
|
"prettier": "^3.8.1",
|
|
37
34
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
38
|
-
"react-devtools-core": "^7.0.1"
|
|
35
|
+
"react-devtools-core": "^7.0.1",
|
|
36
|
+
"tsup": "^8",
|
|
37
|
+
"tsx": "^4"
|
|
39
38
|
},
|
|
40
39
|
"peerDependencies": {
|
|
41
40
|
"typescript": "^5"
|
|
@@ -43,6 +42,7 @@
|
|
|
43
42
|
"dependencies": {
|
|
44
43
|
"@clack/prompts": "^1.0.1",
|
|
45
44
|
"@google/generative-ai": "^0.24.1",
|
|
45
|
+
"chalk": "^5.6.2",
|
|
46
46
|
"cli-table3": "^0.6.5",
|
|
47
47
|
"fluture": "^14.0.0",
|
|
48
48
|
"google-auth-library": "^10.5.0",
|
|
@@ -52,5 +52,6 @@
|
|
|
52
52
|
"openai": "^6.25.0",
|
|
53
53
|
"picocolors": "^1.1.1",
|
|
54
54
|
"react": "^19.2.4"
|
|
55
|
-
}
|
|
55
|
+
},
|
|
56
|
+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
|
56
57
|
}
|