mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-09-04 04:17:33 +00:00
Revert "Code deduplication and cleanup, fix forward delete button on text editing, add ability to show separators in color editing menu, fix truncation issues"
This reverts commit 3e2c3eaa90.
This commit is contained in:
@@ -4,15 +4,15 @@
|
||||
"": {
|
||||
"name": "ccstatusline",
|
||||
"dependencies": {
|
||||
"@types/react": "^19.1.9",
|
||||
"chalk": "^5.5.0",
|
||||
"ink": "^6.1.0",
|
||||
"ink-select-input": "^6.2.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"react": "^19.1.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19.1.9",
|
||||
"patch-package": "^8.0.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
@@ -28,36 +28,18 @@
|
||||
|
||||
"@types/react": ["@types/react@19.1.9", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA=="],
|
||||
|
||||
"@yarnpkg/lockfile": ["@yarnpkg/lockfile@1.1.0", "", {}, "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
|
||||
|
||||
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"chalk": ["chalk@5.5.0", "", {}, "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg=="],
|
||||
|
||||
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
|
||||
|
||||
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
|
||||
@@ -66,136 +48,46 @@
|
||||
|
||||
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
||||
|
||||
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-toolkit": ["es-toolkit@1.39.8", "", {}, "sha512-A8QO9TfF+rltS8BXpdu8OS+rpGgEdnRhqIVxO/ZmNvnXBYgOdSsxukT55ELyP94gZIntWJ+Li9QRrT2u1Kitpg=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||
|
||||
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-yarn-workspace-root": ["find-yarn-workspace-root@2.0.0", "", { "dependencies": { "micromatch": "^4.0.2" } }, "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ=="],
|
||||
|
||||
"fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ink": ["ink@6.1.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-YQ+lbMD79y3FBAJXXZnuRajLEgaMFp102361eY5NrBIEVCi9oFo7gNZU4z2LBWlcjZFiTt7jetlkIbKCCH4KJA=="],
|
||||
|
||||
"ink-select-input": ["ink-select-input@6.2.0", "", { "dependencies": { "figures": "^6.1.0", "to-rotated": "^1.0.0" }, "peerDependencies": { "ink": ">=5.0.0", "react": ">=18.0.0" } }, "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ=="],
|
||||
|
||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
"ink-text-input": ["ink-text-input@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||
|
||||
"is-in-ci": ["is-in-ci@1.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||
|
||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"json-stable-stringify": ["json-stable-stringify@1.3.0", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "isarray": "^2.0.5", "jsonify": "^0.0.1", "object-keys": "^1.1.1" } }, "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
|
||||
|
||||
"jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="],
|
||||
|
||||
"klaw-sync": ["klaw-sync@6.0.0", "", { "dependencies": { "graceful-fs": "^4.1.11" } }, "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
|
||||
|
||||
"os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
|
||||
|
||||
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
||||
|
||||
"patch-package": ["patch-package@8.0.0", "", { "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", "ci-info": "^3.7.0", "cross-spawn": "^7.0.3", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^9.0.0", "json-stable-stringify": "^1.0.2", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", "open": "^7.4.2", "rimraf": "^2.6.3", "semver": "^7.5.3", "slash": "^2.0.0", "tmp": "^0.0.33", "yaml": "^2.2.2" }, "bin": { "patch-package": "index.js" } }, "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
|
||||
|
||||
"react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
|
||||
@@ -204,24 +96,12 @@
|
||||
|
||||
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
||||
|
||||
"rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"slash": ["slash@2.0.0", "", {}, "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="],
|
||||
|
||||
"slice-ansi": ["slice-ansi@7.1.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="],
|
||||
|
||||
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||
@@ -230,12 +110,6 @@
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"to-rotated": ["to-rotated@1.0.0", "", {}, "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
@@ -244,32 +118,20 @@
|
||||
|
||||
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||
|
||||
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
|
||||
|
||||
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
||||
|
||||
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
||||
|
||||
"patch-package/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.0.0", "", { "dependencies": { "get-east-asian-width": "^1.0.0" } }, "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA=="],
|
||||
|
||||
"patch-package/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+619
@@ -0,0 +1,619 @@
|
||||
{
|
||||
"name": "ccstatusline",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ccstatusline",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "^19.1.9",
|
||||
"chalk": "^5.5.0",
|
||||
"ink": "^6.1.0",
|
||||
"ink-select-input": "^6.2.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"react": "^19.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ccstatusline": "dist/ccstatusline.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@alcalzone/ansi-tokenize": {
|
||||
"version": "0.1.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bun": {
|
||||
"version": "1.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.19.tgz",
|
||||
"integrity": "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bun-types": "1.2.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.9",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "7.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"environment": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/auto-bind": {
|
||||
"version": "5.0.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bun-types": {
|
||||
"version": "1.2.19",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.5.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-boxes": {
|
||||
"version": "3.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"slice-ansi": "^5.0.0",
|
||||
"string-width": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate/node_modules/slice-ansi": {
|
||||
"version": "5.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.0.0",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/code-excerpt": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"convert-to-spaces": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-to-spaces": {
|
||||
"version": "2.0.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.4.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/environment": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.39.8",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"benchmarks"
|
||||
]
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "6.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-unicode-supported": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.3.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/indent-string": {
|
||||
"version": "5.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ink": {
|
||||
"version": "6.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.1.3",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"auto-bind": "^5.0.1",
|
||||
"chalk": "^5.3.0",
|
||||
"cli-boxes": "^3.0.0",
|
||||
"cli-cursor": "^4.0.0",
|
||||
"cli-truncate": "^4.0.0",
|
||||
"code-excerpt": "^4.0.0",
|
||||
"es-toolkit": "^1.22.0",
|
||||
"indent-string": "^5.0.0",
|
||||
"is-in-ci": "^1.0.0",
|
||||
"patch-console": "^2.0.0",
|
||||
"react-reconciler": "^0.32.0",
|
||||
"scheduler": "^0.23.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"slice-ansi": "^7.1.0",
|
||||
"stack-utils": "^2.0.6",
|
||||
"string-width": "^7.2.0",
|
||||
"type-fest": "^4.27.0",
|
||||
"widest-line": "^5.0.0",
|
||||
"wrap-ansi": "^9.0.0",
|
||||
"ws": "^8.18.0",
|
||||
"yoga-layout": "~3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=19.0.0",
|
||||
"react": ">=19.0.0",
|
||||
"react-devtools-core": "^4.19.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-devtools-core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ink-select-input": {
|
||||
"version": "6.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"figures": "^6.1.0",
|
||||
"to-rotated": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ink": ">=5.0.0",
|
||||
"react": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ink-text-input": {
|
||||
"version": "6.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"type-fest": "^4.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ink": ">=5",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-in-ci": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"is-in-ci": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-console": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-reconciler": {
|
||||
"version": "0.32.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-reconciler/node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^5.1.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "7.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/to-rotated": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.2",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/widest-line": {
|
||||
"version": "5.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"string-width": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "9.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"string-width": "^7.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yoga-layout": {
|
||||
"version": "3.2.1",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ccstatusline",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.4",
|
||||
"description": "A customizable status line formatter for Claude Code CLI",
|
||||
"module": "src/ccstatusline.ts",
|
||||
"type": "module",
|
||||
@@ -13,21 +13,20 @@
|
||||
"scripts": {
|
||||
"start": "bun run src/ccstatusline.ts",
|
||||
"build": "rm -rf dist/* && bun build src/ccstatusline.ts --target=node --outfile=dist/ccstatusline.js --packages=external --target-version=14",
|
||||
"prepublishOnly": "bun run build",
|
||||
"postinstall": "patch-package"
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19.1.9",
|
||||
"patch-package": "^8.0.0"
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^19.1.9",
|
||||
"chalk": "^5.5.0",
|
||||
"ink": "^6.1.0",
|
||||
"ink-select-input": "^6.2.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"react": "^19.1.1"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
diff --git a/node_modules/ink/build/parse-keypress.js b/node_modules/ink/build/parse-keypress.js
|
||||
index 1e13e81..e100611 100644
|
||||
--- a/node_modules/ink/build/parse-keypress.js
|
||||
+++ b/node_modules/ink/build/parse-keypress.js
|
||||
@@ -161,9 +161,9 @@ const parseKeypress = (s = '') => {
|
||||
key.meta = s.charAt(0) === '\x1b';
|
||||
}
|
||||
else if (s === '\x7f' || s === '\x1b\x7f') {
|
||||
- // TODO(vadimdemedes): `enquirer` detects delete key as backspace, but I had to split them up to avoid breaking changes in Ink. Merge them back together in the next major version.
|
||||
- // delete
|
||||
- key.name = 'delete';
|
||||
+ // On macOS, \x7f is what the backspace key sends, not delete
|
||||
+ // The actual forward delete sends escape sequences like \x1b[3~
|
||||
+ key.name = 'backspace';
|
||||
key.meta = s.charAt(0) === '\x1b';
|
||||
}
|
||||
else if (s === '\x1b' || s === '\x1b\x1b') {
|
||||
+752
-23
@@ -1,13 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { runTUI } from './tui.tsx';
|
||||
import { loadSettings } from './utils/config';
|
||||
import {
|
||||
type StatusJSON,
|
||||
renderStatusLine,
|
||||
getTokenMetrics,
|
||||
getSessionDuration,
|
||||
type RenderContext
|
||||
} from './utils/renderer';
|
||||
import { loadSettings, type StatusItem } from './config';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
|
||||
// Ensure fs.promises compatibility for older Node versions
|
||||
const readFile = fs.promises?.readFile || promisify(fs.readFile);
|
||||
|
||||
// Force chalk to use colors even when piped
|
||||
chalk.level = 3;
|
||||
|
||||
// Helper function to apply foreground, background colors and bold
|
||||
function applyColors(text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string {
|
||||
let result = text;
|
||||
|
||||
// Standard color application
|
||||
// Ignore 'dim' color - it causes issues with terminal rendering
|
||||
if (foregroundColor && foregroundColor !== 'dim') {
|
||||
const fgFunc = (chalk as any)[foregroundColor];
|
||||
if (fgFunc) {
|
||||
result = fgFunc(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundColor && backgroundColor !== 'none') {
|
||||
const bgFunc = (chalk as any)[backgroundColor];
|
||||
if (bgFunc) {
|
||||
result = bgFunc(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (bold) {
|
||||
result = chalk.bold(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface StatusJSON {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
model: {
|
||||
id: string;
|
||||
display_name: string;
|
||||
};
|
||||
workspace: {
|
||||
current_dir: string;
|
||||
project_dir: string;
|
||||
};
|
||||
version?: string;
|
||||
}
|
||||
|
||||
interface TokenUsage {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
}
|
||||
|
||||
interface TranscriptLine {
|
||||
message?: {
|
||||
usage?: TokenUsage;
|
||||
};
|
||||
isSidechain?: boolean;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
async function readStdin(): Promise<string | null> {
|
||||
// Check if stdin is a TTY (terminal) - if it is, there's no piped data
|
||||
@@ -38,11 +98,687 @@ async function readStdin(): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function renderMultipleLines(data: StatusJSON) {
|
||||
// Helper to get default color for an item type
|
||||
function getItemDefaultColor(type: string): string {
|
||||
switch (type) {
|
||||
case 'model': return 'cyan';
|
||||
case 'git-branch': return 'magenta';
|
||||
case 'git-changes': return 'yellow';
|
||||
case 'session-clock': return 'yellow';
|
||||
case 'version': return 'green';
|
||||
case 'tokens-input': return 'blue';
|
||||
case 'tokens-output': return 'white';
|
||||
case 'tokens-cached': return 'cyan';
|
||||
case 'tokens-total': return 'cyan';
|
||||
case 'context-length': return 'gray';
|
||||
case 'context-percentage': return 'blue';
|
||||
case 'context-percentage-usable': return 'green';
|
||||
case 'terminal-width': return 'gray';
|
||||
case 'custom-text': return 'white';
|
||||
case 'custom-command': return 'white';
|
||||
case 'separator': return 'gray';
|
||||
default: return 'white';
|
||||
}
|
||||
}
|
||||
|
||||
function getTerminalWidth(): number | null {
|
||||
try {
|
||||
// First try to get the tty of the parent process
|
||||
const tty = execSync('ps -o tty= -p $(ps -o ppid= -p $$)', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
shell: '/bin/sh'
|
||||
}).trim();
|
||||
|
||||
// Check if we got a valid tty (not ?? which means no tty)
|
||||
if (tty && tty !== '??' && tty !== '?') {
|
||||
// Now get the terminal size
|
||||
const width = execSync(
|
||||
`stty size < /dev/${tty} | awk '{print $2}'`,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
shell: '/bin/sh'
|
||||
}
|
||||
).trim();
|
||||
|
||||
const parsed = parseInt(width, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Command failed, width detection not available
|
||||
}
|
||||
|
||||
// Fallback: try tput cols which might work in some environments
|
||||
try {
|
||||
const width = execSync('tput cols 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
|
||||
const parsed = parseInt(width, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
} catch {
|
||||
// tput also failed
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGitBranch(): string | null {
|
||||
try {
|
||||
const branch = execSync('git branch --show-current 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
return branch || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getGitChanges(): { insertions: number; deletions: number } | null {
|
||||
try {
|
||||
let totalInsertions = 0;
|
||||
let totalDeletions = 0;
|
||||
|
||||
// Get unstaged changes
|
||||
const unstagedStat = execSync('git diff --shortstat 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
|
||||
// Get staged changes
|
||||
const stagedStat = execSync('git diff --cached --shortstat 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
|
||||
// Parse unstaged changes
|
||||
if (unstagedStat) {
|
||||
const insertMatch = unstagedStat.match(/(\d+) insertion/);
|
||||
const deleteMatch = unstagedStat.match(/(\d+) deletion/);
|
||||
totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
|
||||
totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
|
||||
}
|
||||
|
||||
// Parse staged changes
|
||||
if (stagedStat) {
|
||||
const insertMatch = stagedStat.match(/(\d+) insertion/);
|
||||
const deleteMatch = stagedStat.match(/(\d+) deletion/);
|
||||
totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
|
||||
totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
|
||||
}
|
||||
|
||||
// Always return the changes, even if they're zero
|
||||
return { insertions: totalInsertions, deletions: totalDeletions };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getSessionDuration(transcriptPath: string): Promise<string | null> {
|
||||
try {
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = await readFile(transcriptPath, 'utf-8');
|
||||
const lines = content.trim().split('\n').filter(line => line.trim());
|
||||
|
||||
if (lines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let firstTimestamp: Date | null = null;
|
||||
let lastTimestamp: Date | null = null;
|
||||
|
||||
// Find first valid timestamp
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const data = JSON.parse(line);
|
||||
if (data.timestamp) {
|
||||
firstTimestamp = new Date(data.timestamp);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid lines
|
||||
}
|
||||
}
|
||||
|
||||
// Find last valid timestamp (iterate backwards)
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
const data = JSON.parse(lines[i]!);
|
||||
if (data.timestamp) {
|
||||
lastTimestamp = new Date(data.timestamp);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid lines
|
||||
}
|
||||
}
|
||||
|
||||
if (!firstTimestamp || !lastTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate duration in milliseconds
|
||||
const durationMs = lastTimestamp.getTime() - firstTimestamp.getTime();
|
||||
|
||||
// Convert to minutes
|
||||
const totalMinutes = Math.floor(durationMs / (1000 * 60));
|
||||
|
||||
if (totalMinutes < 1) {
|
||||
return '<1m';
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
if (hours === 0) {
|
||||
return `${minutes}m`;
|
||||
} else if (minutes === 0) {
|
||||
return `${hours}hr`;
|
||||
} else {
|
||||
return `${hours}hr ${minutes}m`;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTokenMetrics(transcriptPath: string): Promise<{
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cachedTokens: number;
|
||||
totalTokens: number;
|
||||
contextLength: number;
|
||||
}> {
|
||||
try {
|
||||
// Use Node.js-compatible file reading
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
|
||||
}
|
||||
|
||||
const content = await readFile(transcriptPath, 'utf-8');
|
||||
const lines = content.trim().split('\n');
|
||||
|
||||
let inputTokens = 0;
|
||||
let outputTokens = 0;
|
||||
let cachedTokens = 0;
|
||||
let contextLength = 0;
|
||||
|
||||
// Parse each line and sum up token usage for totals
|
||||
let mostRecentMainChainEntry: TranscriptLine | null = null;
|
||||
let mostRecentTimestamp: Date | null = null;
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const data: TranscriptLine = JSON.parse(line);
|
||||
if (data.message?.usage) {
|
||||
inputTokens += data.message.usage.input_tokens || 0;
|
||||
outputTokens += data.message.usage.output_tokens || 0;
|
||||
cachedTokens += data.message.usage.cache_read_input_tokens || 0;
|
||||
cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
|
||||
|
||||
// Track the most recent entry with isSidechain: false (or undefined, which defaults to main chain)
|
||||
if (data.isSidechain !== true && data.timestamp) {
|
||||
const entryTime = new Date(data.timestamp);
|
||||
if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
|
||||
mostRecentTimestamp = entryTime;
|
||||
mostRecentMainChainEntry = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid JSON lines
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate context length from the most recent main chain message
|
||||
if (mostRecentMainChainEntry?.message?.usage) {
|
||||
const usage = mostRecentMainChainEntry.message.usage;
|
||||
contextLength = (usage.input_tokens || 0) +
|
||||
(usage.cache_read_input_tokens || 0) +
|
||||
(usage.cache_creation_input_tokens || 0);
|
||||
}
|
||||
|
||||
const totalTokens = inputTokens + outputTokens + cachedTokens;
|
||||
|
||||
return { inputTokens, outputTokens, cachedTokens, totalTokens, contextLength };
|
||||
} catch {
|
||||
return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
function renderSingleLine(items: StatusItem[], settings: any, data: StatusJSON, tokenMetrics: any, sessionDuration: string | null): string {
|
||||
// Helper to apply colors with optional background and bold override
|
||||
const applyColorsWithOverride = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
|
||||
// Override foreground color takes precedence over EVERYTHING, including passed foreground color
|
||||
let fgColor = foregroundColor;
|
||||
if (settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
|
||||
fgColor = settings.overrideForegroundColor;
|
||||
}
|
||||
|
||||
// Override background color takes precedence over EVERYTHING, including passed background color
|
||||
let bgColor = backgroundColor;
|
||||
if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
|
||||
bgColor = settings.overrideBackgroundColor;
|
||||
}
|
||||
|
||||
const shouldBold = settings.globalBold || bold;
|
||||
return applyColors(text, fgColor, bgColor, shouldBold);
|
||||
};
|
||||
const detectedWidth = getTerminalWidth();
|
||||
// Calculate terminal width based on flex mode settings
|
||||
let terminalWidth: number | null = null;
|
||||
if (detectedWidth) {
|
||||
const flexMode = settings.flexMode || 'full-minus-40';
|
||||
|
||||
if (flexMode === 'full') {
|
||||
// Use full width minus 4 for terminal padding
|
||||
terminalWidth = detectedWidth - 4;
|
||||
} else if (flexMode === 'full-minus-40') {
|
||||
// Always subtract 40 for auto-compact message
|
||||
terminalWidth = detectedWidth - 40;
|
||||
} else if (flexMode === 'full-until-compact') {
|
||||
// Check context percentage to decide
|
||||
const threshold = settings.compactThreshold || 60;
|
||||
const contextPercentage = tokenMetrics ?
|
||||
Math.min(100, (tokenMetrics.contextLength / 200000) * 100) : 0;
|
||||
|
||||
if (contextPercentage >= threshold) {
|
||||
// Context is high, leave space for auto-compact
|
||||
terminalWidth = detectedWidth - 40;
|
||||
} else {
|
||||
// Context is low, use full width minus 4 for padding
|
||||
terminalWidth = detectedWidth - 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
const elements: { content: string, type: string, item?: StatusItem }[] = [];
|
||||
let hasFlexSeparator = false;
|
||||
|
||||
// Helper function to format token counts
|
||||
const formatTokens = (count: number): string => {
|
||||
if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
|
||||
if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;
|
||||
return count.toString();
|
||||
};
|
||||
|
||||
// Build elements based on configured items
|
||||
for (const item of items) {
|
||||
switch (item.type) {
|
||||
case 'model':
|
||||
if (data.model) {
|
||||
const text = item.rawValue ? data.model.display_name : `Model: ${data.model.display_name}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'cyan', item.backgroundColor, item.bold), type: 'model', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'git-branch':
|
||||
const branch = getGitBranch();
|
||||
if (branch) {
|
||||
const text = item.rawValue ? branch : `⎇ ${branch}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'magenta', item.backgroundColor, item.bold), type: 'git-branch', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'git-changes':
|
||||
const changes = getGitChanges();
|
||||
if (changes !== null) {
|
||||
// Compact format: (+42,-10)
|
||||
const changeStr = `(+${changes.insertions},-${changes.deletions})`;
|
||||
elements.push({ content: applyColorsWithOverride(changeStr, item.color || 'yellow', item.backgroundColor, item.bold), type: 'git-changes', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-input':
|
||||
if (tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(tokenMetrics.inputTokens) : `In: ${formatTokens(tokenMetrics.inputTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'blue', item.backgroundColor, item.bold), type: 'tokens-input', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-output':
|
||||
if (tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(tokenMetrics.outputTokens) : `Out: ${formatTokens(tokenMetrics.outputTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'white', item.backgroundColor, item.bold), type: 'tokens-output', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-cached':
|
||||
if (tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(tokenMetrics.cachedTokens) : `Cached: ${formatTokens(tokenMetrics.cachedTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'cyan', item.backgroundColor, item.bold), type: 'tokens-cached', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-total':
|
||||
if (tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(tokenMetrics.totalTokens) : `Total: ${formatTokens(tokenMetrics.totalTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'cyan', item.backgroundColor, item.bold), type: 'tokens-total', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'context-length':
|
||||
if (tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(tokenMetrics.contextLength) : `Ctx: ${formatTokens(tokenMetrics.contextLength)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'gray', item.backgroundColor, item.bold), type: 'context-length', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'context-percentage':
|
||||
if (tokenMetrics) {
|
||||
const percentage = Math.min(100, (tokenMetrics.contextLength / 200000) * 100);
|
||||
const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'blue', item.backgroundColor, item.bold), type: 'context-percentage', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'context-percentage-usable':
|
||||
if (tokenMetrics) {
|
||||
// Calculate percentage out of 160,000 (80% of full context for auto-compact)
|
||||
const percentage = Math.min(100, (tokenMetrics.contextLength / 160000) * 100);
|
||||
const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'green', item.backgroundColor, item.bold), type: 'context-percentage-usable', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'terminal-width':
|
||||
const width = terminalWidth || getTerminalWidth();
|
||||
if (width) {
|
||||
const text = item.rawValue ? `${width}` : `Term: ${width}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'gray', item.backgroundColor, item.bold), type: 'terminal-width', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'session-clock':
|
||||
if (sessionDuration) {
|
||||
const text = item.rawValue ? sessionDuration : `Session: ${sessionDuration}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'yellow', item.backgroundColor, item.bold), type: 'session-clock', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'version':
|
||||
const versionString = data.version || 'Unknown';
|
||||
const versionText = item.rawValue ? versionString : `Version: ${versionString}`;
|
||||
elements.push({ content: applyColorsWithOverride(versionText, item.color || 'green', item.backgroundColor, item.bold), type: 'version', item });
|
||||
break;
|
||||
|
||||
case 'separator':
|
||||
// Only add separator if there are already elements and the last one isn't a separator
|
||||
const lastElement = elements[elements.length - 1];
|
||||
if (elements.length > 0 && lastElement && lastElement.type !== 'separator') {
|
||||
const sepChar = item.character || '|';
|
||||
// Handle special separator cases
|
||||
let sepText;
|
||||
if (sepChar === ',') {
|
||||
sepText = `${sepChar} `;
|
||||
} else if (sepChar === ' ') {
|
||||
sepText = ' ';
|
||||
} else {
|
||||
sepText = ` ${sepChar} `;
|
||||
}
|
||||
// Use item color if specified, otherwise default to gray
|
||||
const sepContent = applyColorsWithOverride(sepText, item.color || 'gray', item.backgroundColor, item.bold);
|
||||
elements.push({ content: sepContent, type: 'separator', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'flex-separator':
|
||||
elements.push({ content: 'FLEX', type: 'flex-separator', item });
|
||||
hasFlexSeparator = true;
|
||||
break;
|
||||
|
||||
case 'custom-text':
|
||||
const customText = item.customText || '';
|
||||
elements.push({ content: applyColorsWithOverride(customText, item.color || 'white', item.backgroundColor, item.bold), type: 'custom-text', item });
|
||||
break;
|
||||
|
||||
case 'custom-command':
|
||||
if (item.commandPath) {
|
||||
try {
|
||||
// Execute the command with JSON input via stdin
|
||||
const timeout = item.timeout || 1000; // Default to 1000ms if not specified
|
||||
const output = execSync(item.commandPath, {
|
||||
encoding: 'utf8',
|
||||
input: JSON.stringify(data),
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
timeout: timeout
|
||||
}).trim();
|
||||
|
||||
if (output) {
|
||||
let finalOutput = output;
|
||||
|
||||
// Handle max width truncation
|
||||
if (item.maxWidth && item.maxWidth > 0) {
|
||||
// Remove ANSI codes to measure actual length
|
||||
const plainLength = output.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
if (plainLength > item.maxWidth) {
|
||||
// Truncate while preserving ANSI codes
|
||||
let truncated = '';
|
||||
let currentLength = 0;
|
||||
let inAnsiCode = false;
|
||||
let ansiBuffer = '';
|
||||
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const char = output[i];
|
||||
if (char === '\x1b') {
|
||||
inAnsiCode = true;
|
||||
ansiBuffer = char;
|
||||
} else if (inAnsiCode) {
|
||||
ansiBuffer += char;
|
||||
if (char === 'm') {
|
||||
truncated += ansiBuffer;
|
||||
inAnsiCode = false;
|
||||
ansiBuffer = '';
|
||||
}
|
||||
} else {
|
||||
if (currentLength < item.maxWidth) {
|
||||
truncated += char;
|
||||
currentLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finalOutput = truncated;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply color if not preserving original colors
|
||||
if (!item.preserveColors) {
|
||||
// Strip existing ANSI codes and apply new color
|
||||
const stripped = finalOutput.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
elements.push({ content: applyColorsWithOverride(stripped, item.color || 'white', item.backgroundColor, item.bold), type: 'custom-command', item });
|
||||
} else {
|
||||
// Preserve original colors from command output - ignore any color property
|
||||
elements.push({ content: finalOutput, type: 'custom-command', item });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Command failed or timed out - silently skip
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.length === 0) return '';
|
||||
|
||||
// Remove trailing separators
|
||||
while (elements.length > 0 && elements[elements.length - 1]?.type === 'separator') {
|
||||
elements.pop();
|
||||
}
|
||||
|
||||
// Apply default padding and separators
|
||||
const finalElements: string[] = [];
|
||||
const padding = settings.defaultPadding || '';
|
||||
const defaultSep = settings.defaultSeparator || '';
|
||||
|
||||
elements.forEach((elem, index) => {
|
||||
// Add default separator between any two items (but not before first item)
|
||||
if (defaultSep && index > 0) {
|
||||
// Check if we should inherit colors from the previous element
|
||||
if (settings.inheritSeparatorColors && index > 0) {
|
||||
const prevElem = elements[index - 1];
|
||||
if (prevElem && prevElem.item) {
|
||||
// Apply the previous element's colors to the separator (already handles override)
|
||||
// Use the item's color if set, otherwise get the default color for that item type
|
||||
const itemColor = prevElem.item.color || getItemDefaultColor(prevElem.item.type);
|
||||
const coloredSep = applyColorsWithOverride(defaultSep, itemColor, prevElem.item.backgroundColor, prevElem.item.bold);
|
||||
finalElements.push(coloredSep);
|
||||
} else {
|
||||
finalElements.push(defaultSep);
|
||||
}
|
||||
} else if ((settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') ||
|
||||
(settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none')) {
|
||||
// Apply override colors even when not inheriting colors
|
||||
const coloredSep = applyColorsWithOverride(defaultSep, undefined, undefined);
|
||||
finalElements.push(coloredSep);
|
||||
} else {
|
||||
finalElements.push(defaultSep);
|
||||
}
|
||||
}
|
||||
|
||||
// Add element with padding (separators don't get padding)
|
||||
if (elem.type === 'separator' || elem.type === 'flex-separator') {
|
||||
finalElements.push(elem.content);
|
||||
} else {
|
||||
// Apply padding with colors (using overrides if set)
|
||||
const hasColorOverride = (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') ||
|
||||
(settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none');
|
||||
|
||||
if (padding && (elem.item?.backgroundColor || hasColorOverride)) {
|
||||
// Apply colors to padding - applyColorsWithOverride will handle the overrides
|
||||
const paddedContent = applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor) +
|
||||
elem.content +
|
||||
applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor);
|
||||
finalElements.push(paddedContent);
|
||||
} else {
|
||||
// No colors or no padding
|
||||
finalElements.push(padding + elem.content + padding);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build the final status line
|
||||
let statusLine = '';
|
||||
|
||||
if (hasFlexSeparator && terminalWidth) {
|
||||
// Split elements by flex separators
|
||||
const parts: string[][] = [[]];
|
||||
let currentPart = 0;
|
||||
|
||||
for (let i = 0; i < finalElements.length; i++) {
|
||||
const elem = finalElements[i];
|
||||
if (elem === 'FLEX' || (elements[i] && elements[i]?.type === 'flex-separator')) {
|
||||
currentPart++;
|
||||
parts[currentPart] = [];
|
||||
} else {
|
||||
parts[currentPart]?.push(elem!);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total length of all non-flex content
|
||||
const partLengths = parts.map(part => {
|
||||
const joined = part.join('');
|
||||
return joined.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
});
|
||||
const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
|
||||
|
||||
// Calculate space to distribute among flex separators
|
||||
const flexCount = parts.length - 1; // Number of flex separators
|
||||
const totalSpace = Math.max(0, terminalWidth - totalContentLength);
|
||||
const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
|
||||
const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
|
||||
|
||||
// Build the status line with distributed spacing
|
||||
statusLine = '';
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part) {
|
||||
statusLine += part.join('');
|
||||
}
|
||||
if (i < parts.length - 1) {
|
||||
// Add flex spacing
|
||||
const spaces = spacePerFlex + (i < extraSpace ? 1 : 0);
|
||||
statusLine += ' '.repeat(spaces);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No flex separator OR no width detected
|
||||
if (hasFlexSeparator && !terminalWidth) {
|
||||
// Treat flex separators as normal separators when width detection fails
|
||||
statusLine = finalElements.map(e => e === 'FLEX' ? chalk.gray(' | ') : e).join('');
|
||||
} else {
|
||||
// Just join all elements normally
|
||||
statusLine = finalElements.join('');
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate if the line exceeds the terminal width
|
||||
if (detectedWidth && detectedWidth > 0) {
|
||||
// Remove ANSI escape codes to get actual length
|
||||
const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
|
||||
if (plainLength > detectedWidth) {
|
||||
// Need to truncate - preserve ANSI codes while truncating
|
||||
let truncated = '';
|
||||
let currentLength = 0;
|
||||
let inAnsiCode = false;
|
||||
let ansiBuffer = '';
|
||||
const targetLength = detectedWidth - 7; // Truncate to width-7 for proper fit
|
||||
|
||||
for (let i = 0; i < statusLine.length; i++) {
|
||||
const char = statusLine[i];
|
||||
|
||||
if (char === '\x1b') {
|
||||
inAnsiCode = true;
|
||||
ansiBuffer = char;
|
||||
} else if (inAnsiCode) {
|
||||
ansiBuffer += char;
|
||||
if (char === 'm') {
|
||||
truncated += ansiBuffer;
|
||||
inAnsiCode = false;
|
||||
ansiBuffer = '';
|
||||
}
|
||||
} else {
|
||||
if (currentLength < targetLength) {
|
||||
truncated += char;
|
||||
currentLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statusLine = truncated + '...';
|
||||
}
|
||||
}
|
||||
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
async function renderStatusLine(data: StatusJSON) {
|
||||
const settings = await loadSettings();
|
||||
|
||||
// Get all lines to render (support both old items format and new lines format)
|
||||
let lines = settings.lines || (settings.items ? [settings.items] : [[]]);
|
||||
let lines: StatusItem[][] = [];
|
||||
if (settings.lines) {
|
||||
lines = settings.lines;
|
||||
} else if (settings.items) {
|
||||
// Legacy support for single line
|
||||
lines = [settings.items];
|
||||
} else {
|
||||
lines = [[]];
|
||||
}
|
||||
|
||||
// Get token metrics if needed (check all lines)
|
||||
const hasTokenItems = lines.some(line =>
|
||||
@@ -56,28 +792,21 @@ async function renderMultipleLines(data: StatusJSON) {
|
||||
line.some(item => item.type === 'session-clock')
|
||||
);
|
||||
|
||||
let tokenMetrics = null;
|
||||
let tokenMetrics: any = null;
|
||||
if (hasTokenItems && data.transcript_path) {
|
||||
tokenMetrics = await getTokenMetrics(data.transcript_path);
|
||||
}
|
||||
|
||||
let sessionDuration = null;
|
||||
let sessionDuration: string | null = null;
|
||||
if (hasSessionClock && data.transcript_path) {
|
||||
sessionDuration = await getSessionDuration(data.transcript_path);
|
||||
}
|
||||
|
||||
// Create render context
|
||||
const context: RenderContext = {
|
||||
data,
|
||||
tokenMetrics,
|
||||
sessionDuration,
|
||||
isPreview: false
|
||||
};
|
||||
|
||||
// Render each line
|
||||
for (const lineItems of lines) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineItems = lines[i];
|
||||
if (lineItems && lineItems.length > 0) {
|
||||
const line = renderStatusLine(lineItems, settings, context);
|
||||
const line = renderSingleLine(lineItems, settings, data, tokenMetrics, sessionDuration);
|
||||
// Replace all spaces with non-breaking spaces to prevent VSCode trimming
|
||||
const outputLine = line.replace(/ /g, '\u00A0');
|
||||
console.log(outputLine);
|
||||
@@ -93,7 +822,7 @@ async function main() {
|
||||
if (input && input.trim() !== '') {
|
||||
try {
|
||||
const data: StatusJSON = JSON.parse(input);
|
||||
await renderMultipleLines(data);
|
||||
await renderStatusLine(data);
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error);
|
||||
process.exit(1);
|
||||
|
||||
+400
-118
@@ -3,16 +3,10 @@ import { render, Box, Text, useInput, useApp } from 'ink';
|
||||
import SelectInput from 'ink-select-input';
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { loadSettings, saveSettings, type Settings, type StatusItem, type StatusItemType, type FlexMode } from './utils/config';
|
||||
import { isInstalled, installStatusLine, uninstallStatusLine, getExistingStatusLine } from './utils/claude-settings';
|
||||
import { loadSettings, saveSettings, type Settings, type StatusItem, type StatusItemType, type FlexMode } from './config';
|
||||
import { isInstalled, installStatusLine, uninstallStatusLine, getExistingStatusLine } from './claude-settings';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
renderStatusLine as renderLine,
|
||||
getItemDefaultColor,
|
||||
applyColors,
|
||||
type RenderContext
|
||||
} from './utils/renderer';
|
||||
|
||||
// Get package version
|
||||
function getPackageVersion(): string {
|
||||
@@ -26,8 +20,27 @@ function getPackageVersion(): string {
|
||||
}
|
||||
|
||||
// Get default color for each item type (matching ccstatusline.ts defaults)
|
||||
// Re-export from shared renderer module
|
||||
const getDefaultColor = getItemDefaultColor;
|
||||
function getDefaultColor(type: string): string {
|
||||
switch (type) {
|
||||
case 'model': return 'cyan';
|
||||
case 'git-branch': return 'magenta';
|
||||
case 'git-changes': return 'yellow';
|
||||
case 'session-clock': return 'yellow';
|
||||
case 'version': return 'green';
|
||||
case 'tokens-input': return 'blue';
|
||||
case 'tokens-output': return 'white';
|
||||
case 'tokens-cached': return 'cyan';
|
||||
case 'tokens-total': return 'cyan';
|
||||
case 'context-length': return 'gray';
|
||||
case 'context-percentage': return 'blue';
|
||||
case 'context-percentage-usable': return 'green';
|
||||
case 'terminal-width': return 'gray';
|
||||
case 'custom-text': return 'white';
|
||||
case 'custom-command': return 'white';
|
||||
case 'separator': return 'gray';
|
||||
default: return 'white';
|
||||
}
|
||||
}
|
||||
|
||||
// Check if terminal width detection is available
|
||||
function canDetectTerminalWidth(): boolean {
|
||||
@@ -79,31 +92,337 @@ interface StatusLinePreviewProps {
|
||||
settings?: Settings;
|
||||
}
|
||||
|
||||
const renderSingleLine = (items: StatusItem[], terminalWidth: number, widthDetectionAvailable: boolean, settings?: Settings): string => {
|
||||
// Create render context for preview
|
||||
const context: RenderContext = {
|
||||
terminalWidth,
|
||||
isPreview: true
|
||||
};
|
||||
|
||||
return renderLine(items, settings || {}, context);
|
||||
// Helper function to apply foreground, background colors and bold
|
||||
const applyColors = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
|
||||
let result = text;
|
||||
|
||||
// Ignore 'dim' color - it causes issues with terminal rendering
|
||||
if (foregroundColor && foregroundColor !== 'dim') {
|
||||
const fgFunc = (chalk as any)[foregroundColor];
|
||||
if (fgFunc) {
|
||||
result = fgFunc(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundColor && backgroundColor !== 'none') {
|
||||
const bgFunc = (chalk as any)[backgroundColor];
|
||||
if (bgFunc) {
|
||||
result = bgFunc(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (bold) {
|
||||
result = chalk.bold(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const renderSingleLine = (items: StatusItem[], terminalWidth: number, widthDetectionAvailable: boolean, settings?: Settings): string => {
|
||||
// Helper to apply colors with optional background and foreground override
|
||||
const applyItemColors = (text: string, item: StatusItem, defaultColor?: string): string => {
|
||||
// Override foreground color takes precedence over EVERYTHING
|
||||
let fgColor = item.color || defaultColor;
|
||||
if (settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
|
||||
fgColor = settings.overrideForegroundColor;
|
||||
}
|
||||
|
||||
// Override background color takes precedence over EVERYTHING
|
||||
let bgColor = item.backgroundColor;
|
||||
if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
|
||||
bgColor = settings.overrideBackgroundColor;
|
||||
}
|
||||
|
||||
const shouldBold = settings?.globalBold || item.bold;
|
||||
return applyColors(text, fgColor, bgColor, shouldBold);
|
||||
};
|
||||
// Calculate effective width based on flex mode settings
|
||||
let effectiveWidth: number | null = null;
|
||||
if (widthDetectionAvailable && terminalWidth) {
|
||||
const flexMode = settings?.flexMode || 'full-minus-40';
|
||||
|
||||
if (flexMode === 'full') {
|
||||
effectiveWidth = terminalWidth - 2; // Subtract 2 for terminal padding in preview
|
||||
} else if (flexMode === 'full-minus-40') {
|
||||
effectiveWidth = terminalWidth - 40;
|
||||
} else if (flexMode === 'full-until-compact') {
|
||||
// For preview, always show full width
|
||||
effectiveWidth = terminalWidth - 2; // Subtract 2 for terminal padding in preview
|
||||
}
|
||||
}
|
||||
// Always ensure we have a width for truncation
|
||||
const width = terminalWidth; // Always use terminal width for truncation
|
||||
const flexWidth = effectiveWidth; // Use this for flex separator calculations
|
||||
const rawElements: { content: string; type: string; item?: StatusItem }[] = [];
|
||||
let hasFlexSeparator = false;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
switch (item.type) {
|
||||
case 'model':
|
||||
const modelText = item.rawValue ? 'Claude' : 'Model: Claude';
|
||||
rawElements.push({ content: applyItemColors(modelText, item, 'cyan'), type: 'model', item });
|
||||
break;
|
||||
case 'git-branch':
|
||||
const branchText = '⎇ main';
|
||||
rawElements.push({ content: applyItemColors(branchText, item, 'magenta'), type: 'git-branch', item });
|
||||
break;
|
||||
case 'git-changes':
|
||||
const changesText = '(+42,-10)';
|
||||
rawElements.push({ content: applyItemColors(changesText, item, 'yellow'), type: 'git-changes', item });
|
||||
break;
|
||||
case 'tokens-input':
|
||||
const inputText = item.rawValue ? '15.2k' : 'In: 15.2k';
|
||||
rawElements.push({ content: applyItemColors(inputText, item, 'blue'), type: 'tokens-input', item });
|
||||
break;
|
||||
case 'tokens-output':
|
||||
const outputText = item.rawValue ? '3.4k' : 'Out: 3.4k';
|
||||
rawElements.push({ content: applyItemColors(outputText, item, 'white'), type: 'tokens-output', item });
|
||||
break;
|
||||
case 'tokens-cached':
|
||||
const cachedText = item.rawValue ? '12k' : 'Cached: 12k';
|
||||
rawElements.push({ content: applyItemColors(cachedText, item, 'cyan'), type: 'tokens-cached', item });
|
||||
break;
|
||||
case 'tokens-total':
|
||||
const totalText = item.rawValue ? '30.6k' : 'Total: 30.6k';
|
||||
rawElements.push({ content: applyItemColors(totalText, item, 'cyan'), type: 'tokens-total', item });
|
||||
break;
|
||||
case 'context-length':
|
||||
const ctxText = item.rawValue ? '18.6k' : 'Ctx: 18.6k';
|
||||
rawElements.push({ content: applyItemColors(ctxText, item, 'gray'), type: 'context-length', item });
|
||||
break;
|
||||
case 'context-percentage':
|
||||
const ctxPctText = item.rawValue ? '9.3%' : 'Ctx: 9.3%';
|
||||
rawElements.push({ content: applyItemColors(ctxPctText, item, 'blue'), type: 'context-percentage', item });
|
||||
break;
|
||||
case 'context-percentage-usable':
|
||||
const ctxUsableText = item.rawValue ? '11.6%' : 'Ctx(u): 11.6%';
|
||||
rawElements.push({ content: applyItemColors(ctxUsableText, item, 'green'), type: 'context-percentage-usable', item });
|
||||
break;
|
||||
case 'session-clock':
|
||||
const sessionText = item.rawValue ? '2hr 15m' : 'Session: 2hr 15m';
|
||||
rawElements.push({ content: applyItemColors(sessionText, item, 'yellow'), type: 'session-clock', item });
|
||||
break;
|
||||
case 'version':
|
||||
const versionText = item.rawValue ? '1.0.72' : 'Version: 1.0.72';
|
||||
rawElements.push({ content: applyItemColors(versionText, item, 'green'), type: 'version', item });
|
||||
break;
|
||||
case 'terminal-width':
|
||||
const detectedWidth = canDetectTerminalWidth() ? terminalWidth : '??';
|
||||
const termText = item.rawValue ? `${detectedWidth}` : `Term: ${detectedWidth}`;
|
||||
rawElements.push({ content: applyItemColors(termText, item, 'gray'), type: 'terminal-width', item });
|
||||
break;
|
||||
case 'separator':
|
||||
const sepChar = item.character || '|';
|
||||
// Handle special separator cases
|
||||
let sepContent;
|
||||
if (sepChar === ',') {
|
||||
sepContent = `${sepChar} `;
|
||||
} else if (sepChar === ' ') {
|
||||
sepContent = ' ';
|
||||
} else {
|
||||
sepContent = ` ${sepChar} `;
|
||||
}
|
||||
rawElements.push({ content: applyItemColors(sepContent, item, 'gray'), type: 'separator', item });
|
||||
break;
|
||||
case 'flex-separator':
|
||||
rawElements.push({ content: 'FLEX', type: 'flex-separator', item });
|
||||
hasFlexSeparator = true;
|
||||
break;
|
||||
case 'custom-text':
|
||||
const customText = item.customText || '';
|
||||
rawElements.push({ content: applyItemColors(customText, item, 'white'), type: 'custom-text', item });
|
||||
break;
|
||||
case 'custom-command':
|
||||
const cmdText = item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? '...' : ''}]` : '[No command]';
|
||||
// Only apply color if not preserving colors
|
||||
if (!item.preserveColors) {
|
||||
rawElements.push({ content: applyItemColors(cmdText, item, 'white'), type: 'custom-command', item });
|
||||
} else {
|
||||
// When preserving colors, just show the text without applying our colors
|
||||
rawElements.push({ content: cmdText, type: 'custom-command', item });
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Apply default padding and separators
|
||||
const elements: string[] = [];
|
||||
const padding = settings?.defaultPadding || '';
|
||||
const defaultSep = settings?.defaultSeparator || '';
|
||||
|
||||
// Debug: Check if we have a default separator
|
||||
// console.log('Default separator:', JSON.stringify(defaultSep), 'Length:', defaultSep.length);
|
||||
|
||||
rawElements.forEach((elem, index) => {
|
||||
// Add default separator between any two items (but not before first item)
|
||||
if (defaultSep && index > 0) {
|
||||
// Check if we should inherit colors from the previous element
|
||||
if (settings?.inheritSeparatorColors && index > 0) {
|
||||
const prevElem = rawElements[index - 1];
|
||||
if (prevElem && prevElem.item) {
|
||||
// Apply the previous element's colors to the separator (with overrides)
|
||||
const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none'
|
||||
? settings.overrideForegroundColor
|
||||
: (prevElem.item.color || getDefaultColor(prevElem.item.type));
|
||||
const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
|
||||
? settings.overrideBackgroundColor
|
||||
: prevElem.item.backgroundColor;
|
||||
const shouldBold = settings?.globalBold || prevElem.item.bold;
|
||||
const coloredSep = applyColors(defaultSep, fgColor, bgColor, shouldBold);
|
||||
elements.push(coloredSep);
|
||||
} else {
|
||||
elements.push(defaultSep);
|
||||
}
|
||||
} else if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none' ||
|
||||
settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
|
||||
// Apply override colors even when not inheriting colors
|
||||
const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none'
|
||||
? settings.overrideForegroundColor
|
||||
: undefined;
|
||||
const coloredSep = applyColors(defaultSep, fgColor, settings.overrideBackgroundColor, settings?.globalBold);
|
||||
elements.push(coloredSep);
|
||||
} else {
|
||||
elements.push(defaultSep);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the element itself
|
||||
if (elem.type === 'separator' || elem.type === 'flex-separator') {
|
||||
elements.push(elem.content);
|
||||
} else {
|
||||
// Apply padding with the same colors as the item (or overrides)
|
||||
const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none'
|
||||
? settings.overrideForegroundColor
|
||||
: undefined;
|
||||
const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
|
||||
? settings.overrideBackgroundColor
|
||||
: elem.item?.backgroundColor;
|
||||
|
||||
if (padding && (bgColor || fgColor)) {
|
||||
// Apply colors to padding
|
||||
const paddedContent = applyColors(padding, fgColor, bgColor, settings?.globalBold) +
|
||||
elem.content +
|
||||
applyColors(padding, fgColor, bgColor, settings?.globalBold);
|
||||
elements.push(paddedContent);
|
||||
} else {
|
||||
// No colors or no padding
|
||||
elements.push(padding + elem.content + padding);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build the status line with flex separator support
|
||||
let statusLine = '';
|
||||
if (hasFlexSeparator && flexWidth) {
|
||||
const parts: string[][] = [[]];
|
||||
let currentPart = 0;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (item && item.type === 'flex-separator') {
|
||||
currentPart++;
|
||||
parts[currentPart] = [];
|
||||
} else {
|
||||
const element = elements[i];
|
||||
if (element !== 'FLEX' && parts[currentPart]) {
|
||||
parts[currentPart]!.push(element as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total length of all non-flex content
|
||||
const partLengths = parts.map(part => {
|
||||
const joined = part.join('');
|
||||
return joined.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
});
|
||||
const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
|
||||
|
||||
// Calculate space to distribute among flex separators
|
||||
const flexCount = parts.length - 1; // Number of flex separators
|
||||
const totalSpace = Math.max(0, flexWidth - totalContentLength);
|
||||
const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
|
||||
const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
|
||||
|
||||
// Build the status line with distributed spacing
|
||||
statusLine = '';
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part) {
|
||||
statusLine += part.join('');
|
||||
}
|
||||
if (i < parts.length - 1) {
|
||||
// Add flex spacing
|
||||
const spaces = spacePerFlex + (i < extraSpace ? 1 : 0);
|
||||
statusLine += ' '.repeat(spaces);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No width detection available - treat flex separators as normal separators
|
||||
statusLine = elements.map(e => e === 'FLEX' ? chalk.gray(' | ') : e).join('');
|
||||
}
|
||||
|
||||
// Truncate if the line exceeds the maximum width
|
||||
if (width && width > 0) {
|
||||
// Remove ANSI escape codes to get actual length
|
||||
const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
|
||||
if (plainLength > width) {
|
||||
// Need to truncate - preserve ANSI codes while truncating
|
||||
let truncated = '';
|
||||
let currentLength = 0;
|
||||
let inAnsiCode = false;
|
||||
let ansiBuffer = '';
|
||||
const targetLength = width - 5; // Reserve 3 chars for ellipsis + 2 for proper fit in preview
|
||||
|
||||
for (let i = 0; i < statusLine.length; i++) {
|
||||
const char = statusLine[i];
|
||||
|
||||
if (char === '\x1b') {
|
||||
inAnsiCode = true;
|
||||
ansiBuffer = char;
|
||||
} else if (inAnsiCode) {
|
||||
ansiBuffer += char;
|
||||
if (char === 'm') {
|
||||
truncated += ansiBuffer;
|
||||
inAnsiCode = false;
|
||||
ansiBuffer = '';
|
||||
}
|
||||
} else {
|
||||
if (currentLength < targetLength) {
|
||||
truncated += char;
|
||||
currentLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statusLine = truncated + '...';
|
||||
}
|
||||
}
|
||||
|
||||
return statusLine;
|
||||
};
|
||||
|
||||
const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, terminalWidth, settings }) => {
|
||||
const widthDetectionAvailable = canDetectTerminalWidth();
|
||||
// Build the Claude Code input box - account for ink's padding
|
||||
const boxWidth = Math.min(terminalWidth - 4, process.stdout.columns - 4 || 76);
|
||||
const topLine = chalk.gray('╭' + '─'.repeat(Math.max(0, boxWidth - 2)) + '╮');
|
||||
const middleLine = chalk.gray('│') + ' > ' + ' '.repeat(Math.max(0, boxWidth - 5)) + chalk.gray('│');
|
||||
const bottomLine = chalk.gray('╰' + '─'.repeat(Math.max(0, boxWidth - 2)) + '╯');
|
||||
|
||||
// Render each configured line
|
||||
// Pass the full terminal width - the renderer will handle preview adjustments
|
||||
// Render each configured line - account for 2-space prefix in display
|
||||
const availableWidth = boxWidth - 2; // Account for 2-space indent
|
||||
const renderedLines = lines.map(lineItems =>
|
||||
lineItems.length > 0 ? renderSingleLine(lineItems, terminalWidth, widthDetectionAvailable, settings) : ''
|
||||
lineItems.length > 0 ? renderSingleLine(lineItems, availableWidth, widthDetectionAvailable, settings) : ''
|
||||
).filter(line => line !== ''); // Remove empty lines
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Box borderStyle="round" borderColor="gray" width="100%" paddingLeft={1}>
|
||||
<Text>></Text>
|
||||
</Box>
|
||||
<Text>{topLine}</Text>
|
||||
<Text>{middleLine}</Text>
|
||||
<Text>{bottomLine}</Text>
|
||||
{renderedLines.map((line, index) => (
|
||||
<Text key={index}> {line}</Text>
|
||||
))}
|
||||
@@ -199,7 +518,7 @@ const MainMenu: React.FC<MainMenuProps> = ({ onSelect, isClaudeInstalled, hasCha
|
||||
const items = [
|
||||
{ label: '📝 Edit Lines', value: 'lines' },
|
||||
{ label: '🎨 Configure Colors', value: 'colors' },
|
||||
{ label: '📏 Terminal Width Options', value: 'terminalWidth' },
|
||||
{ label: '⚡ Flex Options', value: 'flex' },
|
||||
{ label: '🔧 Global Options', value: 'globalOptions' },
|
||||
{ label: isClaudeInstalled ? '🗑️ Uninstall from Claude Code' : '📦 Install to Claude Code', value: 'install' },
|
||||
];
|
||||
@@ -217,9 +536,9 @@ const MainMenu: React.FC<MainMenuProps> = ({ onSelect, isClaudeInstalled, hasCha
|
||||
<Box flexDirection='column'>
|
||||
<Text bold>Main Menu</Text>
|
||||
<Box marginTop={1}>
|
||||
<SelectInput
|
||||
items={items}
|
||||
onSelect={(item) => onSelect(item.value)}
|
||||
<SelectInput
|
||||
items={items}
|
||||
onSelect={(item) => onSelect(item.value)}
|
||||
initialIndex={Math.min(initialSelection, items.length - 1)}
|
||||
/>
|
||||
</Box>
|
||||
@@ -278,15 +597,11 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
} else if (key.ctrl && key.rightArrow) {
|
||||
// Move to end
|
||||
setTextCursorPos(textInput.length);
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
if (textCursorPos > 0) {
|
||||
setTextInput(textInput.slice(0, textCursorPos - 1) + textInput.slice(textCursorPos));
|
||||
setTextCursorPos(textCursorPos - 1);
|
||||
}
|
||||
} else if (key.delete) {
|
||||
if (textCursorPos < textInput.length) {
|
||||
setTextInput(textInput.slice(0, textCursorPos) + textInput.slice(textCursorPos + 1));
|
||||
}
|
||||
} else if (input && input.length === 1) {
|
||||
setTextInput(textInput.slice(0, textCursorPos) + input + textInput.slice(textCursorPos));
|
||||
setTextCursorPos(textCursorPos + 1);
|
||||
@@ -319,15 +634,11 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
} else if (key.ctrl && key.rightArrow) {
|
||||
// Move to end
|
||||
setCommandCursorPos(commandInput.length);
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
if (commandCursorPos > 0) {
|
||||
setCommandInput(commandInput.slice(0, commandCursorPos - 1) + commandInput.slice(commandCursorPos));
|
||||
setCommandCursorPos(commandCursorPos - 1);
|
||||
}
|
||||
} else if (key.delete) {
|
||||
if (commandCursorPos < commandInput.length) {
|
||||
setCommandInput(commandInput.slice(0, commandCursorPos) + commandInput.slice(commandCursorPos + 1));
|
||||
}
|
||||
} else if (input) {
|
||||
setCommandInput(commandInput.slice(0, commandCursorPos) + input + commandInput.slice(commandCursorPos));
|
||||
setCommandCursorPos(commandCursorPos + input.length);
|
||||
@@ -355,10 +666,8 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
// Cancel editing
|
||||
setEditingMaxWidth(false);
|
||||
setMaxWidthInput('');
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
setMaxWidthInput(maxWidthInput.slice(0, -1));
|
||||
} else if (key.delete) {
|
||||
// For simple number inputs, forward delete does nothing since there's no cursor position
|
||||
} else if (input && /\d/.test(input)) {
|
||||
setMaxWidthInput(maxWidthInput + input);
|
||||
}
|
||||
@@ -385,10 +694,8 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
// Cancel editing
|
||||
setEditingTimeout(false);
|
||||
setTimeoutInput('');
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
setTimeoutInput(timeoutInput.slice(0, -1));
|
||||
} else if (key.delete) {
|
||||
// For simple number inputs, forward delete does nothing since there's no cursor position
|
||||
} else if (input && /\d/.test(input)) {
|
||||
setTimeoutInput(timeoutInput + input);
|
||||
}
|
||||
@@ -557,7 +864,7 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
// Get the color for this item (use custom color if set, otherwise default)
|
||||
const colorName = item.color || getDefaultColor(item.type);
|
||||
const colorFunc = (chalk as any)[colorName] || chalk.white;
|
||||
|
||||
|
||||
switch (item.type) {
|
||||
case 'model':
|
||||
return colorFunc('Model');
|
||||
@@ -705,16 +1012,10 @@ interface ColorMenuProps {
|
||||
}
|
||||
|
||||
const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
const [showSeparators, setShowSeparators] = useState(false);
|
||||
|
||||
const colorableItems = items.filter(item => {
|
||||
// Include separators only if showSeparators is true
|
||||
if (item.type === 'separator') {
|
||||
return showSeparators;
|
||||
}
|
||||
return ['model', 'git-branch', 'git-changes', 'tokens-input', 'tokens-output', 'tokens-cached', 'tokens-total', 'context-length', 'context-percentage', 'context-percentage-usable', 'session-clock', 'terminal-width', 'version', 'custom-text', 'custom-command'].includes(item.type) &&
|
||||
!(item.type === 'custom-command' && item.preserveColors); // Exclude custom-command items with preserveColors
|
||||
});
|
||||
const colorableItems = items.filter(item =>
|
||||
['model', 'git-branch', 'git-changes', 'tokens-input', 'tokens-output', 'tokens-cached', 'tokens-total', 'context-length', 'context-percentage', 'context-percentage-usable', 'session-clock', 'terminal-width', 'version', 'custom-text', 'custom-command'].includes(item.type) &&
|
||||
!(item.type === 'custom-command' && item.preserveColors) // Exclude custom-command items with preserveColors
|
||||
);
|
||||
const [highlightedItemId, setHighlightedItemId] = useState<string | null>(colorableItems[0]?.id || null);
|
||||
const [editingBackground, setEditingBackground] = useState(false);
|
||||
|
||||
@@ -727,7 +1028,7 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Normal keyboard handling when there are items
|
||||
if (key.escape) {
|
||||
if (editingBackground) {
|
||||
@@ -735,20 +1036,6 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
} else {
|
||||
onBack();
|
||||
}
|
||||
} else if (input === 's' || input === 'S') {
|
||||
// Toggle show separators
|
||||
setShowSeparators(!showSeparators);
|
||||
// Reset highlight to first item if there are items
|
||||
const newColorableItems = items.filter(item => {
|
||||
if (item.type === 'separator') {
|
||||
return !showSeparators; // Will be toggled
|
||||
}
|
||||
return ['model', 'git-branch', 'git-changes', 'tokens-input', 'tokens-output', 'tokens-cached', 'tokens-total', 'context-length', 'context-percentage', 'context-percentage-usable', 'session-clock', 'terminal-width', 'version', 'custom-text', 'custom-command'].includes(item.type) &&
|
||||
!(item.type === 'custom-command' && item.preserveColors);
|
||||
});
|
||||
if (newColorableItems.length > 0) {
|
||||
setHighlightedItemId(newColorableItems[0]!.id);
|
||||
}
|
||||
} else if (input === 'f' || input === 'F') {
|
||||
if (colorableItems.length > 0) {
|
||||
setEditingBackground(!editingBackground);
|
||||
@@ -812,7 +1099,6 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
case 'session-clock': return 'Session Clock';
|
||||
case 'terminal-width': return 'Terminal Width';
|
||||
case 'version': return 'Version';
|
||||
case 'separator': return `Separator (${item.character || '|'})`;
|
||||
case 'custom-text': return `Custom Text (${item.customText || 'Empty'})`;
|
||||
case 'custom-command': {
|
||||
const cmd = item.commandPath ? item.commandPath.substring(0, 20) + (item.commandPath.length > 20 ? '...' : '') : 'No command';
|
||||
@@ -881,25 +1167,25 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
const colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
|
||||
'gray', 'redBright', 'greenBright', 'yellowBright', 'blueBright',
|
||||
'magentaBright', 'cyanBright', 'whiteBright'];
|
||||
|
||||
|
||||
// Background colors list (includes 'none' as first option)
|
||||
// Note: chalk doesn't support bgDim, so we exclude it from background colors
|
||||
const bgColors = ['none', 'bgBlack', 'bgRed', 'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta',
|
||||
'bgCyan', 'bgWhite', 'bgGray', 'bgRedBright', 'bgGreenBright', 'bgYellowBright',
|
||||
const bgColors = ['none', 'bgBlack', 'bgRed', 'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta',
|
||||
'bgCyan', 'bgWhite', 'bgGray', 'bgRedBright', 'bgGreenBright', 'bgYellowBright',
|
||||
'bgBlueBright', 'bgMagentaBright', 'bgCyanBright', 'bgWhiteBright'];
|
||||
|
||||
// Get current color for highlighted item
|
||||
const selectedItem = highlightedItemId && highlightedItemId !== 'back'
|
||||
? colorableItems.find(item => item.id === highlightedItemId)
|
||||
const selectedItem = highlightedItemId && highlightedItemId !== 'back'
|
||||
? colorableItems.find(item => item.id === highlightedItemId)
|
||||
: null;
|
||||
const currentColor = editingBackground
|
||||
const currentColor = editingBackground
|
||||
? (selectedItem?.backgroundColor || 'none')
|
||||
: (selectedItem ? (selectedItem.color || getDefaultColor(selectedItem.type)) : 'white');
|
||||
|
||||
|
||||
const colorList = editingBackground ? bgColors : colors;
|
||||
const colorIndex = colorList.indexOf(currentColor);
|
||||
const colorNumber = colorIndex === -1 ? (editingBackground ? 1 : 8) : colorIndex + 1;
|
||||
|
||||
|
||||
let colorDisplay;
|
||||
if (editingBackground) {
|
||||
if (currentColor === 'none') {
|
||||
@@ -917,7 +1203,6 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
<Box flexDirection='column'>
|
||||
<Text bold>Configure Colors {editingBackground && chalk.yellow('[Background Mode]')}</Text>
|
||||
<Text dimColor>↑↓ to select, Enter to cycle {editingBackground ? 'background' : 'foreground'}, (f) to toggle bg/fg, (b)old, (r)eset, ESC to go back</Text>
|
||||
<Text dimColor>(s)how separators: {showSeparators ? chalk.green('ON') : chalk.gray('OFF')}</Text>
|
||||
{selectedItem && (
|
||||
<Box marginTop={1}>
|
||||
<Text>
|
||||
@@ -936,19 +1221,21 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
</Box>
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
<Text color='yellow'>⚠ VSCode Users: </Text>
|
||||
<Text dimColor wrap='wrap'>If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio" (`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors. You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors.</Text>
|
||||
<Text dimColor wrap='wrap'>If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio"</Text>
|
||||
<Text dimColor wrap='wrap'>(`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors.</Text>
|
||||
<Text dimColor wrap='wrap'>You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface TerminalWidthOptionsProps {
|
||||
interface FlexOptionsProps {
|
||||
settings: Settings;
|
||||
onUpdate: (settings: Settings) => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const TerminalWidthOptions: React.FC<TerminalWidthOptionsProps> = ({ settings, onUpdate, onBack }) => {
|
||||
const FlexOptions: React.FC<FlexOptionsProps> = ({ settings, onUpdate, onBack }) => {
|
||||
const [selectedOption, setSelectedOption] = useState<FlexMode>(settings.flexMode || 'full-minus-40');
|
||||
const [compactThreshold, setCompactThreshold] = useState(settings.compactThreshold || 60);
|
||||
const [editingThreshold, setEditingThreshold] = useState(false);
|
||||
@@ -980,11 +1267,9 @@ const TerminalWidthOptions: React.FC<TerminalWidthOptionsProps> = ({ settings, o
|
||||
setThresholdInput(String(compactThreshold));
|
||||
setEditingThreshold(false);
|
||||
setValidationError(null);
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
setThresholdInput(thresholdInput.slice(0, -1));
|
||||
setValidationError(null);
|
||||
} else if (key.delete) {
|
||||
// For simple number inputs, forward delete does nothing since there's no cursor position
|
||||
} else if (input && /\d/.test(input)) {
|
||||
const newValue = thresholdInput + input;
|
||||
if (newValue.length <= 2) {
|
||||
@@ -1007,7 +1292,7 @@ const TerminalWidthOptions: React.FC<TerminalWidthOptionsProps> = ({ settings, o
|
||||
},
|
||||
{
|
||||
value: 'full-minus-40' as FlexMode,
|
||||
label: 'Full width minus 40 (default)',
|
||||
label: 'Full width minus 40',
|
||||
description: 'Leaves a gap to the right of the status line to accommodate the auto-compact message. This prevents wrapping but may leave unused space. This limitation exists because we cannot detect when the message will appear.'
|
||||
},
|
||||
{
|
||||
@@ -1045,9 +1330,8 @@ const TerminalWidthOptions: React.FC<TerminalWidthOptionsProps> = ({ settings, o
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Text bold>Terminal Width Options</Text>
|
||||
<Text color="white">These settings affect where long lines are truncated, and where right-alignment occurs when using flex separators</Text>
|
||||
<Text dimColor wrap="wrap">These settings are necessary because claude code does not currently provide an available width variable for the statusline and features like IDE integration, auto-compaction notices, and rate limit messages can all cause the statusline to wrap if we do not truncate it</Text>
|
||||
<Text bold>Flex Separator Width Options</Text>
|
||||
<Text dimColor>Select how flex separators calculate available width</Text>
|
||||
|
||||
{editingThreshold ? (
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
@@ -1109,13 +1393,13 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
const [separatorInput, setSeparatorInput] = useState(settings.defaultSeparator || '');
|
||||
const [inheritColors, setInheritColors] = useState(settings.inheritSeparatorColors || false);
|
||||
const [globalBold, setGlobalBold] = useState(settings.globalBold || false);
|
||||
|
||||
|
||||
// Background color override
|
||||
const bgColors = ['none', 'bgBlack', 'bgRed', 'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta',
|
||||
'bgCyan', 'bgWhite', 'bgGray', 'bgRedBright', 'bgGreenBright', 'bgYellowBright',
|
||||
const bgColors = ['none', 'bgBlack', 'bgRed', 'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta',
|
||||
'bgCyan', 'bgWhite', 'bgGray', 'bgRedBright', 'bgGreenBright', 'bgYellowBright',
|
||||
'bgBlueBright', 'bgMagentaBright', 'bgCyanBright', 'bgWhiteBright'];
|
||||
const currentBgIndex = bgColors.indexOf(settings.overrideBackgroundColor || 'none');
|
||||
|
||||
|
||||
// Foreground color override
|
||||
const fgColors = ['none', 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
|
||||
'gray', 'redBright', 'greenBright', 'yellowBright', 'blueBright',
|
||||
@@ -1134,10 +1418,8 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
} else if (key.escape) {
|
||||
setPaddingInput(settings.defaultPadding || '');
|
||||
setEditingPadding(false);
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
setPaddingInput(paddingInput.slice(0, -1));
|
||||
} else if (key.delete) {
|
||||
// For simple text inputs without cursor, forward delete does nothing
|
||||
} else if (input) {
|
||||
setPaddingInput(paddingInput + input);
|
||||
}
|
||||
@@ -1152,10 +1434,8 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
} else if (key.escape) {
|
||||
setSeparatorInput(settings.defaultSeparator || '');
|
||||
setEditingSeparator(false);
|
||||
} else if (key.backspace) {
|
||||
} else if (key.backspace || key.delete) {
|
||||
setSeparatorInput(separatorInput.slice(0, -1));
|
||||
} else if (key.delete) {
|
||||
// For simple text inputs without cursor, forward delete does nothing
|
||||
} else if (input) {
|
||||
setSeparatorInput(separatorInput + input);
|
||||
}
|
||||
@@ -1224,7 +1504,7 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
<Text bold>Global Options</Text>
|
||||
<Text dimColor>Configure automatic padding and separators between items</Text>
|
||||
<Box marginTop={1} />
|
||||
|
||||
|
||||
{editingPadding ? (
|
||||
<Box flexDirection="column">
|
||||
<Box>
|
||||
@@ -1248,25 +1528,25 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
<Text color="cyan">{settings.defaultPadding ? `"${settings.defaultPadding}"` : '(none)'}</Text>
|
||||
<Text dimColor> - Press (p) to edit</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Text>Default Separator: </Text>
|
||||
<Text color="cyan">{settings.defaultSeparator ? `"${settings.defaultSeparator}"` : '(none)'}</Text>
|
||||
<Text dimColor> - Press (s) to edit</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Text> Inherit Colors: </Text>
|
||||
<Text color={inheritColors ? "green" : "red"}>{inheritColors ? '✓ Enabled' : '✗ Disabled'}</Text>
|
||||
<Text dimColor> - Press (i) to toggle</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Text> Global Bold: </Text>
|
||||
<Text color={globalBold ? "green" : "red"}>{globalBold ? '✓ Enabled' : '✗ Disabled'}</Text>
|
||||
<Text dimColor> - Press (o) to toggle</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Text>Override BG Color: </Text>
|
||||
{(() => {
|
||||
@@ -1282,7 +1562,7 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
})()}
|
||||
<Text dimColor> - (b) cycle, (c) clear</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Text>Override FG Color: </Text>
|
||||
{(() => {
|
||||
@@ -1297,11 +1577,11 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
})()}
|
||||
<Text dimColor> - (f) cycle, (g) clear</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box marginTop={2}>
|
||||
<Text dimColor>Press ESC to go back</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
<Text dimColor wrap='wrap'>
|
||||
Note: These settings are applied during rendering and don't add items to your widget list.
|
||||
@@ -1317,7 +1597,9 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
</Text>
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
<Text color='yellow'>⚠ VSCode Users: </Text>
|
||||
<Text dimColor wrap='wrap'>If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio" (`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors. You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors.</Text>
|
||||
<Text dimColor wrap='wrap'>If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio"</Text>
|
||||
<Text dimColor wrap='wrap'>(`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors.</Text>
|
||||
<Text dimColor wrap='wrap'>You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
@@ -1331,7 +1613,7 @@ const App: React.FC = () => {
|
||||
const [settings, setSettings] = useState<Settings | null>(null);
|
||||
const [originalSettings, setOriginalSettings] = useState<Settings | null>(null);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [screen, setScreen] = useState<'main' | 'lines' | 'items' | 'colors' | 'terminalWidth' | 'globalOptions' | 'confirm'>('main');
|
||||
const [screen, setScreen] = useState<'main' | 'lines' | 'items' | 'colors' | 'flex' | 'globalOptions' | 'confirm'>('main');
|
||||
const [selectedLine, setSelectedLine] = useState(0);
|
||||
const [menuSelections, setMenuSelections] = useState<Record<string, number>>({});
|
||||
const [confirmDialog, setConfirmDialog] = useState<{ message: string; action: () => Promise<void> } | null>(null);
|
||||
@@ -1427,8 +1709,8 @@ const App: React.FC = () => {
|
||||
case 'colors':
|
||||
setScreen('colors');
|
||||
break;
|
||||
case 'terminalWidth':
|
||||
setScreen('terminalWidth');
|
||||
case 'flex':
|
||||
setScreen('flex');
|
||||
break;
|
||||
case 'globalOptions':
|
||||
setScreen('globalOptions');
|
||||
@@ -1472,14 +1754,14 @@ const App: React.FC = () => {
|
||||
|
||||
<Box marginTop={2}>
|
||||
{screen === 'main' && (
|
||||
<MainMenu
|
||||
<MainMenu
|
||||
onSelect={(value) => {
|
||||
// Only persist menu selection if not exiting
|
||||
if (value !== 'save' && value !== 'exit') {
|
||||
const menuMap: Record<string, number> = {
|
||||
'lines': 0,
|
||||
'colors': 1,
|
||||
'terminalWidth': 2,
|
||||
'flex': 2,
|
||||
'globalOptions': 3,
|
||||
'install': 4
|
||||
};
|
||||
@@ -1487,7 +1769,7 @@ const App: React.FC = () => {
|
||||
}
|
||||
handleMainMenuSelect(value);
|
||||
}}
|
||||
isClaudeInstalled={isClaudeInstalled}
|
||||
isClaudeInstalled={isClaudeInstalled}
|
||||
hasChanges={hasChanges}
|
||||
initialSelection={menuSelections.main || 0}
|
||||
/>
|
||||
@@ -1549,14 +1831,14 @@ const App: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{screen === 'terminalWidth' && (
|
||||
<TerminalWidthOptions
|
||||
{screen === 'flex' && (
|
||||
<FlexOptions
|
||||
settings={settings}
|
||||
onUpdate={(updatedSettings) => {
|
||||
setSettings(updatedSettings);
|
||||
}}
|
||||
onBack={() => {
|
||||
// Save that we came from 'terminalWidth' menu (index 2)
|
||||
// Save that we came from 'flex' menu (index 2)
|
||||
setMenuSelections({ ...menuSelections, main: 2 });
|
||||
setScreen('main');
|
||||
}}
|
||||
|
||||
@@ -1,826 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import type { StatusItem } from './config';
|
||||
|
||||
// Ensure fs.promises compatibility for older Node versions
|
||||
const readFile = fs.promises?.readFile || promisify(fs.readFile);
|
||||
|
||||
// Force chalk to use colors even when piped
|
||||
chalk.level = 3;
|
||||
|
||||
export interface StatusJSON {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
model: {
|
||||
id: string;
|
||||
display_name: string;
|
||||
};
|
||||
workspace: {
|
||||
current_dir: string;
|
||||
project_dir: string;
|
||||
};
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface TokenUsage {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
}
|
||||
|
||||
export interface TranscriptLine {
|
||||
message?: {
|
||||
usage?: TokenUsage;
|
||||
};
|
||||
isSidechain?: boolean;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export interface TokenMetrics {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cachedTokens: number;
|
||||
totalTokens: number;
|
||||
contextLength: number;
|
||||
}
|
||||
|
||||
export interface RenderContext {
|
||||
data?: StatusJSON;
|
||||
tokenMetrics?: TokenMetrics | null;
|
||||
sessionDuration?: string | null;
|
||||
gitBranch?: string | null;
|
||||
gitChanges?: { insertions: number; deletions: number } | null;
|
||||
terminalWidth?: number | null;
|
||||
isPreview?: boolean;
|
||||
}
|
||||
|
||||
// Helper function to apply foreground, background colors and bold
|
||||
export function applyColors(text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string {
|
||||
let result = text;
|
||||
|
||||
// Standard color application
|
||||
// Ignore 'dim' color - it causes issues with terminal rendering
|
||||
if (foregroundColor && foregroundColor !== 'dim') {
|
||||
const fgFunc = (chalk as any)[foregroundColor];
|
||||
if (fgFunc) {
|
||||
result = fgFunc(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundColor && backgroundColor !== 'none') {
|
||||
const bgFunc = (chalk as any)[backgroundColor];
|
||||
if (bgFunc) {
|
||||
result = bgFunc(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (bold) {
|
||||
result = chalk.bold(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper to get default color for an item type
|
||||
export function getItemDefaultColor(type: string): string {
|
||||
switch (type) {
|
||||
case 'model': return 'cyan';
|
||||
case 'git-branch': return 'magenta';
|
||||
case 'git-changes': return 'yellow';
|
||||
case 'session-clock': return 'yellow';
|
||||
case 'version': return 'green';
|
||||
case 'tokens-input': return 'blue';
|
||||
case 'tokens-output': return 'white';
|
||||
case 'tokens-cached': return 'cyan';
|
||||
case 'tokens-total': return 'cyan';
|
||||
case 'context-length': return 'gray';
|
||||
case 'context-percentage': return 'blue';
|
||||
case 'context-percentage-usable': return 'green';
|
||||
case 'terminal-width': return 'gray';
|
||||
case 'custom-text': return 'white';
|
||||
case 'custom-command': return 'white';
|
||||
case 'separator': return 'gray';
|
||||
default: return 'white';
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to format token counts
|
||||
export function formatTokens(count: number): string {
|
||||
if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
|
||||
if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
export function getTerminalWidth(): number | null {
|
||||
try {
|
||||
// First try to get the tty of the parent process
|
||||
const tty = execSync('ps -o tty= -p $(ps -o ppid= -p $$)', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
shell: '/bin/sh'
|
||||
}).trim();
|
||||
|
||||
// Check if we got a valid tty (not ?? which means no tty)
|
||||
if (tty && tty !== '??' && tty !== '?') {
|
||||
// Now get the terminal size
|
||||
const width = execSync(
|
||||
`stty size < /dev/${tty} | awk '{print $2}'`,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
shell: '/bin/sh'
|
||||
}
|
||||
).trim();
|
||||
|
||||
const parsed = parseInt(width, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Command failed, width detection not available
|
||||
}
|
||||
|
||||
// Fallback: try tput cols which might work in some environments
|
||||
try {
|
||||
const width = execSync('tput cols 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
|
||||
const parsed = parseInt(width, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
} catch {
|
||||
// tput also failed
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getGitBranch(): string | null {
|
||||
try {
|
||||
const branch = execSync('git branch --show-current 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
return branch || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getGitChanges(): { insertions: number; deletions: number } | null {
|
||||
try {
|
||||
let totalInsertions = 0;
|
||||
let totalDeletions = 0;
|
||||
|
||||
// Get unstaged changes
|
||||
const unstagedStat = execSync('git diff --shortstat 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
|
||||
// Get staged changes
|
||||
const stagedStat = execSync('git diff --cached --shortstat 2>/dev/null', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
|
||||
// Parse unstaged changes
|
||||
if (unstagedStat) {
|
||||
const insertMatch = unstagedStat.match(/(\d+) insertion/);
|
||||
const deleteMatch = unstagedStat.match(/(\d+) deletion/);
|
||||
totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
|
||||
totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
|
||||
}
|
||||
|
||||
// Parse staged changes
|
||||
if (stagedStat) {
|
||||
const insertMatch = stagedStat.match(/(\d+) insertion/);
|
||||
const deleteMatch = stagedStat.match(/(\d+) deletion/);
|
||||
totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
|
||||
totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
|
||||
}
|
||||
|
||||
// Always return the changes, even if they're zero
|
||||
return { insertions: totalInsertions, deletions: totalDeletions };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSessionDuration(transcriptPath: string): Promise<string | null> {
|
||||
try {
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = await readFile(transcriptPath, 'utf-8');
|
||||
const lines = content.trim().split('\n').filter(line => line.trim());
|
||||
|
||||
if (lines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let firstTimestamp: Date | null = null;
|
||||
let lastTimestamp: Date | null = null;
|
||||
|
||||
// Find first valid timestamp
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const data = JSON.parse(line);
|
||||
if (data.timestamp) {
|
||||
firstTimestamp = new Date(data.timestamp);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid lines
|
||||
}
|
||||
}
|
||||
|
||||
// Find last valid timestamp (iterate backwards)
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
const data = JSON.parse(lines[i]!);
|
||||
if (data.timestamp) {
|
||||
lastTimestamp = new Date(data.timestamp);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid lines
|
||||
}
|
||||
}
|
||||
|
||||
if (!firstTimestamp || !lastTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate duration in milliseconds
|
||||
const durationMs = lastTimestamp.getTime() - firstTimestamp.getTime();
|
||||
|
||||
// Convert to minutes
|
||||
const totalMinutes = Math.floor(durationMs / (1000 * 60));
|
||||
|
||||
if (totalMinutes < 1) {
|
||||
return '<1m';
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
if (hours === 0) {
|
||||
return `${minutes}m`;
|
||||
} else if (minutes === 0) {
|
||||
return `${hours}hr`;
|
||||
} else {
|
||||
return `${hours}hr ${minutes}m`;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenMetrics(transcriptPath: string): Promise<TokenMetrics> {
|
||||
try {
|
||||
// Use Node.js-compatible file reading
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
|
||||
}
|
||||
|
||||
const content = await readFile(transcriptPath, 'utf-8');
|
||||
const lines = content.trim().split('\n');
|
||||
|
||||
let inputTokens = 0;
|
||||
let outputTokens = 0;
|
||||
let cachedTokens = 0;
|
||||
let contextLength = 0;
|
||||
|
||||
// Parse each line and sum up token usage for totals
|
||||
let mostRecentMainChainEntry: TranscriptLine | null = null;
|
||||
let mostRecentTimestamp: Date | null = null;
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const data: TranscriptLine = JSON.parse(line);
|
||||
if (data.message?.usage) {
|
||||
inputTokens += data.message.usage.input_tokens || 0;
|
||||
outputTokens += data.message.usage.output_tokens || 0;
|
||||
cachedTokens += data.message.usage.cache_read_input_tokens || 0;
|
||||
cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
|
||||
|
||||
// Track the most recent entry with isSidechain: false (or undefined, which defaults to main chain)
|
||||
if (data.isSidechain !== true && data.timestamp) {
|
||||
const entryTime = new Date(data.timestamp);
|
||||
if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
|
||||
mostRecentTimestamp = entryTime;
|
||||
mostRecentMainChainEntry = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid JSON lines
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate context length from the most recent main chain message
|
||||
if (mostRecentMainChainEntry?.message?.usage) {
|
||||
const usage = mostRecentMainChainEntry.message.usage;
|
||||
contextLength = (usage.input_tokens || 0) +
|
||||
(usage.cache_read_input_tokens || 0) +
|
||||
(usage.cache_creation_input_tokens || 0);
|
||||
}
|
||||
|
||||
const totalTokens = inputTokens + outputTokens + cachedTokens;
|
||||
|
||||
return { inputTokens, outputTokens, cachedTokens, totalTokens, contextLength };
|
||||
} catch {
|
||||
return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export function renderStatusLine(
|
||||
items: StatusItem[],
|
||||
settings: any,
|
||||
context: RenderContext
|
||||
): string {
|
||||
// Helper to apply colors with optional background and bold override
|
||||
const applyColorsWithOverride = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
|
||||
// Override foreground color takes precedence over EVERYTHING, including passed foreground color
|
||||
let fgColor = foregroundColor;
|
||||
if (settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
|
||||
fgColor = settings.overrideForegroundColor;
|
||||
}
|
||||
|
||||
// Override background color takes precedence over EVERYTHING, including passed background color
|
||||
let bgColor = backgroundColor;
|
||||
if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
|
||||
bgColor = settings.overrideBackgroundColor;
|
||||
}
|
||||
|
||||
const shouldBold = settings.globalBold || bold;
|
||||
return applyColors(text, fgColor, bgColor, shouldBold);
|
||||
};
|
||||
|
||||
const detectedWidth = context.terminalWidth || getTerminalWidth();
|
||||
|
||||
// Calculate terminal width based on flex mode settings
|
||||
let terminalWidth: number | null = null;
|
||||
if (detectedWidth) {
|
||||
const flexMode = settings.flexMode || 'full-minus-40';
|
||||
|
||||
if (context.isPreview) {
|
||||
// In preview mode, account for box borders and padding (6 chars total)
|
||||
if (flexMode === 'full') {
|
||||
terminalWidth = detectedWidth - 6; // Subtract 6 for box borders and padding in preview
|
||||
} else if (flexMode === 'full-minus-40') {
|
||||
terminalWidth = detectedWidth - 43; // -40 for auto-compact + 3 for preview
|
||||
} else if (flexMode === 'full-until-compact') {
|
||||
// For preview, always show full width minus preview padding
|
||||
terminalWidth = detectedWidth - 6;
|
||||
}
|
||||
} else {
|
||||
// In actual rendering mode
|
||||
if (flexMode === 'full') {
|
||||
// Use full width minus 4 for terminal padding
|
||||
terminalWidth = detectedWidth - 4;
|
||||
} else if (flexMode === 'full-minus-40') {
|
||||
// Always subtract 41 for auto-compact message
|
||||
terminalWidth = detectedWidth - 41;
|
||||
} else if (flexMode === 'full-until-compact') {
|
||||
// Check context percentage to decide
|
||||
const threshold = settings.compactThreshold || 60;
|
||||
const contextPercentage = context.tokenMetrics ?
|
||||
Math.min(100, (context.tokenMetrics.contextLength / 200000) * 100) : 0;
|
||||
|
||||
if (contextPercentage >= threshold) {
|
||||
// Context is high, leave space for auto-compact
|
||||
terminalWidth = detectedWidth - 40;
|
||||
} else {
|
||||
// Context is low, use full width minus 4 for padding
|
||||
terminalWidth = detectedWidth - 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const elements: { content: string, type: string, item?: StatusItem }[] = [];
|
||||
let hasFlexSeparator = false;
|
||||
|
||||
// Build elements based on configured items
|
||||
for (const item of items) {
|
||||
switch (item.type) {
|
||||
case 'model':
|
||||
if (context.isPreview) {
|
||||
const modelText = item.rawValue ? 'Claude' : 'Model: Claude';
|
||||
elements.push({ content: applyColorsWithOverride(modelText, item.color || 'cyan', item.backgroundColor, item.bold), type: 'model', item });
|
||||
} else if (context.data?.model) {
|
||||
const text = item.rawValue ? context.data.model.display_name : `Model: ${context.data.model.display_name}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'cyan', item.backgroundColor, item.bold), type: 'model', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'git-branch':
|
||||
if (context.isPreview) {
|
||||
const branchText = item.rawValue ? 'main' : '⎇ main';
|
||||
elements.push({ content: applyColorsWithOverride(branchText, item.color || 'magenta', item.backgroundColor, item.bold), type: 'git-branch', item });
|
||||
} else {
|
||||
const branch = context.gitBranch || getGitBranch();
|
||||
if (branch) {
|
||||
const text = item.rawValue ? branch : `⎇ ${branch}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'magenta', item.backgroundColor, item.bold), type: 'git-branch', item });
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'git-changes':
|
||||
if (context.isPreview) {
|
||||
const changesText = '(+42,-10)';
|
||||
elements.push({ content: applyColorsWithOverride(changesText, item.color || 'yellow', item.backgroundColor, item.bold), type: 'git-changes', item });
|
||||
} else {
|
||||
const changes = context.gitChanges || getGitChanges();
|
||||
if (changes !== null) {
|
||||
const changeStr = `(+${changes.insertions},-${changes.deletions})`;
|
||||
elements.push({ content: applyColorsWithOverride(changeStr, item.color || 'yellow', item.backgroundColor, item.bold), type: 'git-changes', item });
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-input':
|
||||
if (context.isPreview) {
|
||||
const inputText = item.rawValue ? '15.2k' : 'In: 15.2k';
|
||||
elements.push({ content: applyColorsWithOverride(inputText, item.color || 'blue', item.backgroundColor, item.bold), type: 'tokens-input', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(context.tokenMetrics.inputTokens) : `In: ${formatTokens(context.tokenMetrics.inputTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'blue', item.backgroundColor, item.bold), type: 'tokens-input', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-output':
|
||||
if (context.isPreview) {
|
||||
const outputText = item.rawValue ? '3.4k' : 'Out: 3.4k';
|
||||
elements.push({ content: applyColorsWithOverride(outputText, item.color || 'white', item.backgroundColor, item.bold), type: 'tokens-output', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(context.tokenMetrics.outputTokens) : `Out: ${formatTokens(context.tokenMetrics.outputTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'white', item.backgroundColor, item.bold), type: 'tokens-output', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-cached':
|
||||
if (context.isPreview) {
|
||||
const cachedText = item.rawValue ? '12k' : 'Cached: 12k';
|
||||
elements.push({ content: applyColorsWithOverride(cachedText, item.color || 'cyan', item.backgroundColor, item.bold), type: 'tokens-cached', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(context.tokenMetrics.cachedTokens) : `Cached: ${formatTokens(context.tokenMetrics.cachedTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'cyan', item.backgroundColor, item.bold), type: 'tokens-cached', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tokens-total':
|
||||
if (context.isPreview) {
|
||||
const totalText = item.rawValue ? '30.6k' : 'Total: 30.6k';
|
||||
elements.push({ content: applyColorsWithOverride(totalText, item.color || 'cyan', item.backgroundColor, item.bold), type: 'tokens-total', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(context.tokenMetrics.totalTokens) : `Total: ${formatTokens(context.tokenMetrics.totalTokens)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'cyan', item.backgroundColor, item.bold), type: 'tokens-total', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'context-length':
|
||||
if (context.isPreview) {
|
||||
const ctxText = item.rawValue ? '18.6k' : 'Ctx: 18.6k';
|
||||
elements.push({ content: applyColorsWithOverride(ctxText, item.color || 'gray', item.backgroundColor, item.bold), type: 'context-length', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
const text = item.rawValue ? formatTokens(context.tokenMetrics.contextLength) : `Ctx: ${formatTokens(context.tokenMetrics.contextLength)}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'gray', item.backgroundColor, item.bold), type: 'context-length', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'context-percentage':
|
||||
if (context.isPreview) {
|
||||
const ctxPctText = item.rawValue ? '9.3%' : 'Ctx: 9.3%';
|
||||
elements.push({ content: applyColorsWithOverride(ctxPctText, item.color || 'blue', item.backgroundColor, item.bold), type: 'context-percentage', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
const percentage = Math.min(100, (context.tokenMetrics.contextLength / 200000) * 100);
|
||||
const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'blue', item.backgroundColor, item.bold), type: 'context-percentage', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'context-percentage-usable':
|
||||
if (context.isPreview) {
|
||||
const ctxUsableText = item.rawValue ? '11.6%' : 'Ctx(u): 11.6%';
|
||||
elements.push({ content: applyColorsWithOverride(ctxUsableText, item.color || 'green', item.backgroundColor, item.bold), type: 'context-percentage-usable', item });
|
||||
} else if (context.tokenMetrics) {
|
||||
// Calculate percentage out of 160,000 (80% of full context for auto-compact)
|
||||
const percentage = Math.min(100, (context.tokenMetrics.contextLength / 160000) * 100);
|
||||
const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'green', item.backgroundColor, item.bold), type: 'context-percentage-usable', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'terminal-width':
|
||||
const width = terminalWidth || getTerminalWidth();
|
||||
if (context.isPreview) {
|
||||
const detectedWidth = width || '??';
|
||||
const termText = item.rawValue ? `${detectedWidth}` : `Term: ${detectedWidth}`;
|
||||
elements.push({ content: applyColorsWithOverride(termText, item.color || 'gray', item.backgroundColor, item.bold), type: 'terminal-width', item });
|
||||
} else if (width) {
|
||||
const text = item.rawValue ? `${width}` : `Term: ${width}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'gray', item.backgroundColor, item.bold), type: 'terminal-width', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'session-clock':
|
||||
if (context.isPreview) {
|
||||
const sessionText = item.rawValue ? '2hr 15m' : 'Session: 2hr 15m';
|
||||
elements.push({ content: applyColorsWithOverride(sessionText, item.color || 'yellow', item.backgroundColor, item.bold), type: 'session-clock', item });
|
||||
} else if (context.sessionDuration) {
|
||||
const text = item.rawValue ? context.sessionDuration : `Session: ${context.sessionDuration}`;
|
||||
elements.push({ content: applyColorsWithOverride(text, item.color || 'yellow', item.backgroundColor, item.bold), type: 'session-clock', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'version':
|
||||
if (context.isPreview) {
|
||||
const versionText = item.rawValue ? '1.0.72' : 'Version: 1.0.72';
|
||||
elements.push({ content: applyColorsWithOverride(versionText, item.color || 'green', item.backgroundColor, item.bold), type: 'version', item });
|
||||
} else if (context.data?.version) {
|
||||
const versionString = context.data.version || 'Unknown';
|
||||
const versionText = item.rawValue ? versionString : `Version: ${versionString}`;
|
||||
elements.push({ content: applyColorsWithOverride(versionText, item.color || 'green', item.backgroundColor, item.bold), type: 'version', item });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'separator':
|
||||
// Always add separators - users should be able to add multiple if they want
|
||||
const sepChar = item.character || '|';
|
||||
// Handle special separator cases
|
||||
let sepText;
|
||||
if (sepChar === ',') {
|
||||
sepText = `${sepChar} `;
|
||||
} else if (sepChar === ' ') {
|
||||
sepText = ' ';
|
||||
} else {
|
||||
sepText = ` ${sepChar} `;
|
||||
}
|
||||
// Use item color if specified, otherwise default to gray
|
||||
const sepContent = applyColorsWithOverride(sepText, item.color || 'gray', item.backgroundColor, item.bold);
|
||||
elements.push({ content: sepContent, type: 'separator', item });
|
||||
break;
|
||||
|
||||
case 'flex-separator':
|
||||
elements.push({ content: 'FLEX', type: 'flex-separator', item });
|
||||
hasFlexSeparator = true;
|
||||
break;
|
||||
|
||||
case 'custom-text':
|
||||
const customText = item.customText || '';
|
||||
elements.push({ content: applyColorsWithOverride(customText, item.color || 'white', item.backgroundColor, item.bold), type: 'custom-text', item });
|
||||
break;
|
||||
|
||||
case 'custom-command':
|
||||
if (context.isPreview) {
|
||||
const cmdText = item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? '...' : ''}]` : '[No command]';
|
||||
// Only apply color if not preserving colors
|
||||
if (!item.preserveColors) {
|
||||
elements.push({ content: applyColorsWithOverride(cmdText, item.color || 'white', item.backgroundColor, item.bold), type: 'custom-command', item });
|
||||
} else {
|
||||
// When preserving colors, just show the text without applying our colors
|
||||
elements.push({ content: cmdText, type: 'custom-command', item });
|
||||
}
|
||||
} else if (item.commandPath && context.data) {
|
||||
try {
|
||||
// Execute the command with JSON input via stdin
|
||||
const timeout = item.timeout || 1000; // Default to 1000ms if not specified
|
||||
const output = execSync(item.commandPath, {
|
||||
encoding: 'utf8',
|
||||
input: JSON.stringify(context.data),
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
timeout: timeout
|
||||
}).trim();
|
||||
|
||||
if (output) {
|
||||
let finalOutput = output;
|
||||
|
||||
// Handle max width truncation
|
||||
if (item.maxWidth && item.maxWidth > 0) {
|
||||
// Remove ANSI codes to measure actual length
|
||||
const plainLength = output.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
if (plainLength > item.maxWidth) {
|
||||
// Truncate while preserving ANSI codes
|
||||
let truncated = '';
|
||||
let currentLength = 0;
|
||||
let inAnsiCode = false;
|
||||
let ansiBuffer = '';
|
||||
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const char = output[i];
|
||||
if (char === '\x1b') {
|
||||
inAnsiCode = true;
|
||||
ansiBuffer = char;
|
||||
} else if (inAnsiCode) {
|
||||
ansiBuffer += char;
|
||||
if (char === 'm') {
|
||||
truncated += ansiBuffer;
|
||||
inAnsiCode = false;
|
||||
ansiBuffer = '';
|
||||
}
|
||||
} else {
|
||||
if (currentLength < item.maxWidth) {
|
||||
truncated += char;
|
||||
currentLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finalOutput = truncated;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply color if not preserving original colors
|
||||
if (!item.preserveColors) {
|
||||
// Strip existing ANSI codes and apply new color
|
||||
const stripped = finalOutput.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
elements.push({ content: applyColorsWithOverride(stripped, item.color || 'white', item.backgroundColor, item.bold), type: 'custom-command', item });
|
||||
} else {
|
||||
// Preserve original colors from command output - ignore any color property
|
||||
elements.push({ content: finalOutput, type: 'custom-command', item });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Command failed or timed out - silently skip
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.length === 0) return '';
|
||||
|
||||
// Remove trailing separators
|
||||
while (elements.length > 0 && elements[elements.length - 1]?.type === 'separator') {
|
||||
elements.pop();
|
||||
}
|
||||
|
||||
// Apply default padding and separators
|
||||
const finalElements: string[] = [];
|
||||
const padding = settings.defaultPadding || '';
|
||||
const defaultSep = settings.defaultSeparator || '';
|
||||
|
||||
elements.forEach((elem, index) => {
|
||||
// Add default separator between any two items (but not before first item, and not around flex separators)
|
||||
const prevElem = index > 0 ? elements[index - 1] : null;
|
||||
const shouldAddSeparator = defaultSep && index > 0 &&
|
||||
elem.type !== 'flex-separator' &&
|
||||
prevElem?.type !== 'flex-separator';
|
||||
|
||||
if (shouldAddSeparator) {
|
||||
// Check if we should inherit colors from the previous element
|
||||
if (settings.inheritSeparatorColors && index > 0) {
|
||||
const prevElem = elements[index - 1];
|
||||
if (prevElem && prevElem.item) {
|
||||
// Apply the previous element's colors to the separator (already handles override)
|
||||
// Use the item's color if set, otherwise get the default color for that item type
|
||||
const itemColor = prevElem.item.color || getItemDefaultColor(prevElem.item.type);
|
||||
const coloredSep = applyColorsWithOverride(defaultSep, itemColor, prevElem.item.backgroundColor, prevElem.item.bold);
|
||||
finalElements.push(coloredSep);
|
||||
} else {
|
||||
finalElements.push(defaultSep);
|
||||
}
|
||||
} else if ((settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') ||
|
||||
(settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none')) {
|
||||
// Apply override colors even when not inheriting colors
|
||||
const coloredSep = applyColorsWithOverride(defaultSep, undefined, undefined);
|
||||
finalElements.push(coloredSep);
|
||||
} else {
|
||||
finalElements.push(defaultSep);
|
||||
}
|
||||
}
|
||||
|
||||
// Add element with padding (separators don't get padding)
|
||||
if (elem.type === 'separator' || elem.type === 'flex-separator') {
|
||||
finalElements.push(elem.content);
|
||||
} else {
|
||||
// Apply padding with colors (using overrides if set)
|
||||
const hasColorOverride = (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') ||
|
||||
(settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none');
|
||||
|
||||
if (padding && (elem.item?.backgroundColor || hasColorOverride)) {
|
||||
// Apply colors to padding - applyColorsWithOverride will handle the overrides
|
||||
const paddedContent = applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor) +
|
||||
elem.content +
|
||||
applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor);
|
||||
finalElements.push(paddedContent);
|
||||
} else {
|
||||
// No colors or no padding
|
||||
finalElements.push(padding + elem.content + padding);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build the final status line
|
||||
let statusLine = '';
|
||||
|
||||
if (hasFlexSeparator && terminalWidth) {
|
||||
// Split elements by flex separators
|
||||
const parts: string[][] = [[]];
|
||||
let currentPart = 0;
|
||||
|
||||
for (let i = 0; i < finalElements.length; i++) {
|
||||
const elem = finalElements[i];
|
||||
if (elem === 'FLEX') {
|
||||
currentPart++;
|
||||
parts[currentPart] = [];
|
||||
} else {
|
||||
parts[currentPart]?.push(elem!);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total length of all non-flex content
|
||||
const partLengths = parts.map(part => {
|
||||
const joined = part.join('');
|
||||
return joined.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
});
|
||||
const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
|
||||
|
||||
// Calculate space to distribute among flex separators
|
||||
const flexCount = parts.length - 1; // Number of flex separators
|
||||
const totalSpace = Math.max(0, terminalWidth - totalContentLength);
|
||||
const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
|
||||
const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
|
||||
|
||||
// Build the status line with distributed spacing
|
||||
statusLine = '';
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part) {
|
||||
statusLine += part.join('');
|
||||
}
|
||||
if (i < parts.length - 1) {
|
||||
// Add flex spacing
|
||||
const spaces = spacePerFlex + (i < extraSpace ? 1 : 0);
|
||||
statusLine += ' '.repeat(spaces);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No flex separator OR no width detected
|
||||
if (hasFlexSeparator && !terminalWidth) {
|
||||
// Treat flex separators as normal separators when width detection fails
|
||||
statusLine = finalElements.map(e => e === 'FLEX' ? chalk.gray(' | ') : e).join('');
|
||||
} else {
|
||||
// Just join all elements normally
|
||||
statusLine = finalElements.join('');
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate if the line exceeds the terminal width
|
||||
// Use terminalWidth if available (already accounts for flex mode adjustments), otherwise use detectedWidth
|
||||
const maxWidth = terminalWidth || detectedWidth;
|
||||
if (maxWidth && maxWidth > 0) {
|
||||
// Remove ANSI escape codes to get actual length
|
||||
const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
|
||||
if (plainLength > maxWidth) {
|
||||
// Need to truncate - preserve ANSI codes while truncating
|
||||
let truncated = '';
|
||||
let currentLength = 0;
|
||||
let inAnsiCode = false;
|
||||
let ansiBuffer = '';
|
||||
const targetLength = context.isPreview ? maxWidth - 3 : maxWidth - 3; // Reserve 3 chars for ellipsis
|
||||
|
||||
for (let i = 0; i < statusLine.length; i++) {
|
||||
const char = statusLine[i];
|
||||
|
||||
if (char === '\x1b') {
|
||||
inAnsiCode = true;
|
||||
ansiBuffer = char;
|
||||
} else if (inAnsiCode) {
|
||||
ansiBuffer += char;
|
||||
if (char === 'm') {
|
||||
truncated += ansiBuffer;
|
||||
inAnsiCode = false;
|
||||
ansiBuffer = '';
|
||||
}
|
||||
} else {
|
||||
if (currentLength < targetLength) {
|
||||
truncated += char;
|
||||
currentLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statusLine = truncated + '...';
|
||||
}
|
||||
}
|
||||
|
||||
return statusLine;
|
||||
}
|
||||
Reference in New Issue
Block a user