mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-09-03 08:18:26 +00:00
Fix build issue with patching Ink, bump version to 1.0.15
This reverts commit 256b562cca.
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,18 +28,36 @@
|
||||
|
||||
"@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=="],
|
||||
@@ -48,46 +66,136 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"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=="],
|
||||
@@ -96,12 +204,24 @@
|
||||
|
||||
"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=="],
|
||||
@@ -110,6 +230,12 @@
|
||||
|
||||
"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=="],
|
||||
@@ -118,20 +244,32 @@
|
||||
|
||||
"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
@@ -1,619 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ccstatusline",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"description": "A customizable status line formatter for Claude Code CLI",
|
||||
"module": "src/ccstatusline.ts",
|
||||
"type": "module",
|
||||
@@ -11,22 +11,23 @@
|
||||
"dist/"
|
||||
],
|
||||
"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",
|
||||
"start": "bun run patch && bun run src/ccstatusline.ts",
|
||||
"patch": "patch-package",
|
||||
"build": "bun run patch && rm -rf dist/* && bun build src/ccstatusline.ts --target=node --outfile=dist/ccstatusline.js --target-version=14",
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19.1.9",
|
||||
"patch-package": "^8.0.0"
|
||||
},
|
||||
"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": [
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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') {
|
||||
+23
-752
@@ -1,73 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { runTUI } from './tui.tsx';
|
||||
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;
|
||||
}
|
||||
import { loadSettings } from './utils/config';
|
||||
import {
|
||||
type StatusJSON,
|
||||
renderStatusLine,
|
||||
getTokenMetrics,
|
||||
getSessionDuration,
|
||||
type RenderContext
|
||||
} from './utils/renderer';
|
||||
|
||||
async function readStdin(): Promise<string | null> {
|
||||
// Check if stdin is a TTY (terminal) - if it is, there's no piped data
|
||||
@@ -98,687 +38,11 @@ async function readStdin(): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
async function renderMultipleLines(data: StatusJSON) {
|
||||
const settings = await loadSettings();
|
||||
|
||||
// Get all lines to render (support both old items format and new lines format)
|
||||
let lines: StatusItem[][] = [];
|
||||
if (settings.lines) {
|
||||
lines = settings.lines;
|
||||
} else if (settings.items) {
|
||||
// Legacy support for single line
|
||||
lines = [settings.items];
|
||||
} else {
|
||||
lines = [[]];
|
||||
}
|
||||
let lines = settings.lines || (settings.items ? [settings.items] : [[]]);
|
||||
|
||||
// Get token metrics if needed (check all lines)
|
||||
const hasTokenItems = lines.some(line =>
|
||||
@@ -792,21 +56,28 @@ async function renderStatusLine(data: StatusJSON) {
|
||||
line.some(item => item.type === 'session-clock')
|
||||
);
|
||||
|
||||
let tokenMetrics: any = null;
|
||||
let tokenMetrics = null;
|
||||
if (hasTokenItems && data.transcript_path) {
|
||||
tokenMetrics = await getTokenMetrics(data.transcript_path);
|
||||
}
|
||||
|
||||
let sessionDuration: string | null = null;
|
||||
let sessionDuration = 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 (let i = 0; i < lines.length; i++) {
|
||||
const lineItems = lines[i];
|
||||
for (const lineItems of lines) {
|
||||
if (lineItems && lineItems.length > 0) {
|
||||
const line = renderSingleLine(lineItems, settings, data, tokenMetrics, sessionDuration);
|
||||
const line = renderStatusLine(lineItems, settings, context);
|
||||
// Replace all spaces with non-breaking spaces to prevent VSCode trimming
|
||||
const outputLine = line.replace(/ /g, '\u00A0');
|
||||
console.log(outputLine);
|
||||
@@ -822,7 +93,7 @@ async function main() {
|
||||
if (input && input.trim() !== '') {
|
||||
try {
|
||||
const data: StatusJSON = JSON.parse(input);
|
||||
await renderStatusLine(data);
|
||||
await renderMultipleLines(data);
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error);
|
||||
process.exit(1);
|
||||
|
||||
+116
-398
@@ -3,10 +3,16 @@ 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 './config';
|
||||
import { isInstalled, installStatusLine, uninstallStatusLine, getExistingStatusLine } from './claude-settings';
|
||||
import { loadSettings, saveSettings, type Settings, type StatusItem, type StatusItemType, type FlexMode } from './utils/config';
|
||||
import { isInstalled, installStatusLine, uninstallStatusLine, getExistingStatusLine } from './utils/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 {
|
||||
@@ -20,27 +26,8 @@ function getPackageVersion(): string {
|
||||
}
|
||||
|
||||
// Get default color for each item type (matching ccstatusline.ts defaults)
|
||||
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';
|
||||
}
|
||||
}
|
||||
// Re-export from shared renderer module
|
||||
const getDefaultColor = getItemDefaultColor;
|
||||
|
||||
// Check if terminal width detection is available
|
||||
function canDetectTerminalWidth(): boolean {
|
||||
@@ -92,337 +79,31 @@ interface StatusLinePreviewProps {
|
||||
settings?: Settings;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Create render context for preview
|
||||
const context: RenderContext = {
|
||||
terminalWidth,
|
||||
isPreview: true
|
||||
};
|
||||
// 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;
|
||||
return renderLine(items, settings || {}, context);
|
||||
};
|
||||
|
||||
|
||||
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 - account for 2-space prefix in display
|
||||
const availableWidth = boxWidth - 2; // Account for 2-space indent
|
||||
// Render each configured line
|
||||
// Pass the full terminal width - the renderer will handle preview adjustments
|
||||
const renderedLines = lines.map(lineItems =>
|
||||
lineItems.length > 0 ? renderSingleLine(lineItems, availableWidth, widthDetectionAvailable, settings) : ''
|
||||
lineItems.length > 0 ? renderSingleLine(lineItems, terminalWidth, widthDetectionAvailable, settings) : ''
|
||||
).filter(line => line !== ''); // Remove empty lines
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Text>{topLine}</Text>
|
||||
<Text>{middleLine}</Text>
|
||||
<Text>{bottomLine}</Text>
|
||||
<Box borderStyle="round" borderColor="gray" width="100%" paddingLeft={1}>
|
||||
<Text>></Text>
|
||||
</Box>
|
||||
{renderedLines.map((line, index) => (
|
||||
<Text key={index}> {line}</Text>
|
||||
))}
|
||||
@@ -518,7 +199,7 @@ const MainMenu: React.FC<MainMenuProps> = ({ onSelect, isClaudeInstalled, hasCha
|
||||
const items = [
|
||||
{ label: '📝 Edit Lines', value: 'lines' },
|
||||
{ label: '🎨 Configure Colors', value: 'colors' },
|
||||
{ label: '⚡ Flex Options', value: 'flex' },
|
||||
{ label: '📏 Terminal Width Options', value: 'terminalWidth' },
|
||||
{ label: '🔧 Global Options', value: 'globalOptions' },
|
||||
{ label: isClaudeInstalled ? '🗑️ Uninstall from Claude Code' : '📦 Install to Claude Code', value: 'install' },
|
||||
];
|
||||
@@ -536,9 +217,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>
|
||||
@@ -597,11 +278,15 @@ 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 || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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);
|
||||
@@ -634,11 +319,15 @@ 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 || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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);
|
||||
@@ -666,8 +355,10 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
// Cancel editing
|
||||
setEditingMaxWidth(false);
|
||||
setMaxWidthInput('');
|
||||
} else if (key.backspace || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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);
|
||||
}
|
||||
@@ -694,8 +385,10 @@ const ItemsEditor: React.FC<ItemsEditorProps> = ({ items, onUpdate, onBack, line
|
||||
// Cancel editing
|
||||
setEditingTimeout(false);
|
||||
setTimeoutInput('');
|
||||
} else if (key.backspace || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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);
|
||||
}
|
||||
@@ -864,7 +557,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');
|
||||
@@ -1012,10 +705,16 @@ interface ColorMenuProps {
|
||||
}
|
||||
|
||||
const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
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 [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 [highlightedItemId, setHighlightedItemId] = useState<string | null>(colorableItems[0]?.id || null);
|
||||
const [editingBackground, setEditingBackground] = useState(false);
|
||||
|
||||
@@ -1028,7 +727,7 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Normal keyboard handling when there are items
|
||||
if (key.escape) {
|
||||
if (editingBackground) {
|
||||
@@ -1036,6 +735,20 @@ 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);
|
||||
@@ -1099,6 +812,7 @@ 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';
|
||||
@@ -1167,25 +881,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') {
|
||||
@@ -1203,6 +917,7 @@ 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>
|
||||
@@ -1221,21 +936,19 @@ 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"</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>
|
||||
<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>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface FlexOptionsProps {
|
||||
interface TerminalWidthOptionsProps {
|
||||
settings: Settings;
|
||||
onUpdate: (settings: Settings) => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const FlexOptions: React.FC<FlexOptionsProps> = ({ settings, onUpdate, onBack }) => {
|
||||
const TerminalWidthOptions: React.FC<TerminalWidthOptionsProps> = ({ 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);
|
||||
@@ -1267,9 +980,11 @@ const FlexOptions: React.FC<FlexOptionsProps> = ({ settings, onUpdate, onBack })
|
||||
setThresholdInput(String(compactThreshold));
|
||||
setEditingThreshold(false);
|
||||
setValidationError(null);
|
||||
} else if (key.backspace || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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) {
|
||||
@@ -1292,7 +1007,7 @@ const FlexOptions: React.FC<FlexOptionsProps> = ({ settings, onUpdate, onBack })
|
||||
},
|
||||
{
|
||||
value: 'full-minus-40' as FlexMode,
|
||||
label: 'Full width minus 40',
|
||||
label: 'Full width minus 40 (default)',
|
||||
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.'
|
||||
},
|
||||
{
|
||||
@@ -1330,8 +1045,9 @@ const FlexOptions: React.FC<FlexOptionsProps> = ({ settings, onUpdate, onBack })
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Text bold>Flex Separator Width Options</Text>
|
||||
<Text dimColor>Select how flex separators calculate available width</Text>
|
||||
<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>
|
||||
|
||||
{editingThreshold ? (
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
@@ -1393,13 +1109,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',
|
||||
@@ -1418,8 +1134,10 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
} else if (key.escape) {
|
||||
setPaddingInput(settings.defaultPadding || '');
|
||||
setEditingPadding(false);
|
||||
} else if (key.backspace || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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);
|
||||
}
|
||||
@@ -1434,8 +1152,10 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
} else if (key.escape) {
|
||||
setSeparatorInput(settings.defaultSeparator || '');
|
||||
setEditingSeparator(false);
|
||||
} else if (key.backspace || key.delete) {
|
||||
} else if (key.backspace) {
|
||||
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);
|
||||
}
|
||||
@@ -1504,7 +1224,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>
|
||||
@@ -1528,25 +1248,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>
|
||||
{(() => {
|
||||
@@ -1562,7 +1282,7 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
|
||||
})()}
|
||||
<Text dimColor> - (b) cycle, (c) clear</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Text>Override FG Color: </Text>
|
||||
{(() => {
|
||||
@@ -1577,11 +1297,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.
|
||||
@@ -1597,9 +1317,7 @@ 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"</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>
|
||||
<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>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
@@ -1613,7 +1331,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' | 'flex' | 'globalOptions' | 'confirm'>('main');
|
||||
const [screen, setScreen] = useState<'main' | 'lines' | 'items' | 'colors' | 'terminalWidth' | '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);
|
||||
@@ -1709,8 +1427,8 @@ const App: React.FC = () => {
|
||||
case 'colors':
|
||||
setScreen('colors');
|
||||
break;
|
||||
case 'flex':
|
||||
setScreen('flex');
|
||||
case 'terminalWidth':
|
||||
setScreen('terminalWidth');
|
||||
break;
|
||||
case 'globalOptions':
|
||||
setScreen('globalOptions');
|
||||
@@ -1754,14 +1472,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,
|
||||
'flex': 2,
|
||||
'terminalWidth': 2,
|
||||
'globalOptions': 3,
|
||||
'install': 4
|
||||
};
|
||||
@@ -1769,7 +1487,7 @@ const App: React.FC = () => {
|
||||
}
|
||||
handleMainMenuSelect(value);
|
||||
}}
|
||||
isClaudeInstalled={isClaudeInstalled}
|
||||
isClaudeInstalled={isClaudeInstalled}
|
||||
hasChanges={hasChanges}
|
||||
initialSelection={menuSelections.main || 0}
|
||||
/>
|
||||
@@ -1831,14 +1549,14 @@ const App: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{screen === 'flex' && (
|
||||
<FlexOptions
|
||||
{screen === 'terminalWidth' && (
|
||||
<TerminalWidthOptions
|
||||
settings={settings}
|
||||
onUpdate={(updatedSettings) => {
|
||||
setSettings(updatedSettings);
|
||||
}}
|
||||
onBack={() => {
|
||||
// Save that we came from 'flex' menu (index 2)
|
||||
// Save that we came from 'terminalWidth' menu (index 2)
|
||||
setMenuSelections({ ...menuSelections, main: 2 });
|
||||
setScreen('main');
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,826 @@
|
||||
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