Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.

feat: add colored logging with logtape#338

Merged
Germandrummer92 merged 11 commits into
mainfrom
feature/colored-logging
Jan 22, 2026
Merged

feat: add colored logging with logtape#338
Germandrummer92 merged 11 commits into
mainfrom
feature/colored-logging

Conversation

@Germandrummer92

@Germandrummer92 Germandrummer92 commented Jan 19, 2026

Copy link
Copy Markdown
Member

Summary

Introduces colored logging for the CLI using logtape

@Germandrummer92
Germandrummer92 force-pushed the feature/colored-logging branch from c38e38a to f2763e1 Compare January 19, 2026 13:11
Comment thread package.json Outdated
@Germandrummer92 Germandrummer92 changed the title feat: add colored logging with consola feat: add colored logging with pino Jan 19, 2026
@Germandrummer92
Germandrummer92 force-pushed the feature/colored-logging branch 2 times, most recently from 40543f3 to 0eab33c Compare January 19, 2026 14:04
@Germandrummer92 Germandrummer92 changed the title feat: add colored logging with pino feat: add colored logging with logtape Jan 20, 2026
@Germandrummer92
Germandrummer92 force-pushed the feature/colored-logging branch from 353c805 to d257e34 Compare January 21, 2026 12:13
- Add pino@10.2.0 and pino-pretty@13.1.3 for CLI-friendly colored log output
- Create centralized logger module (src/logger.ts)
- Replace all console.log/error/warn calls with logger.info/error/warn
- Update tests to mock the new logger

Pino is the de facto standard for high-performance Node.js logging with:
- Structured JSON output (production)
- Pretty colored output (development via pino-pretty)
- Configurable log levels via LOG_LEVEL env var

diff --git c/package.json i/package.json
index e5a2f56..984399f 100644
--- c/package.json
+++ i/package.json
@@ -4,7 +4,13 @@
   "description": "a command line client for octomind apis",
   "main": "./dist/index.js",
   "packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48",
-  "files": ["dist", "src", "README.md", "LICENSE", "package.json"],
+  "files": [
+    "dist",
+    "src",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
   "engines": {
     "node": ">=22.0.0"
   },
@@ -46,6 +52,8 @@
     "openapi-fetch": "0.15.0",
     "ora": "9.0.0",
     "otplib": "13.1.1",
+    "pino": "10.2.0",
+    "pino-pretty": "13.1.3",
     "shell-quote": "1.8.3",
     "simple-git": "3.30.0",
     "tabtab": "3.0.2",
diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml
index 43fc574..266eae6 100644
--- c/pnpm-lock.yaml
+++ i/pnpm-lock.yaml
@@ -32,6 +32,12 @@ importers:
       otplib:
         specifier: 13.1.1
         version: 13.1.1
+      pino:
+        specifier: 10.2.0
+        version: 10.2.0
+      pino-pretty:
+        specifier: 13.1.3
+        version: 13.1.3
       shell-quote:
         specifier: 1.8.3
         version: 1.8.3
@@ -553,6 +559,9 @@ packages:
   '@otplib/uri@13.1.1':
     resolution: {integrity: sha512-UMdz/41JIKPLw6/VeExc3MJaCHZYEZXlF5WVyhtWT5nfzQKAcgp6HI8Tar0lDN+lXXKCjbGwlyCKOWCMAISNXg==}

+  '@pinojs/redact@0.4.0':
+    resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
+
   '@playwright/test@1.57.0':
     resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==}
     engines: {node: '>=18'}
@@ -874,6 +883,10 @@ packages:
   async@3.2.6:
     resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}

+  atomic-sleep@1.0.0:
+    resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
+    engines: {node: '>=8.0.0'}
+
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}

@@ -937,6 +950,9 @@ packages:
   colorette@1.4.0:
     resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}

+  colorette@2.0.20:
+    resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
   commander@14.0.2:
     resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
     engines: {node: '>=20'}
@@ -955,6 +971,9 @@ packages:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}

+  dateformat@4.6.3:
+    resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
+
   debug@4.4.1:
     resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
     engines: {node: '>=6.0'}
@@ -997,6 +1016,9 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true

+  end-of-stream@1.4.5:
+    resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
+
   enquirer@2.4.1:
     resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
     engines: {node: '>=8.6'}
@@ -1044,6 +1066,9 @@ packages:
     resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
     engines: {node: '>=4'}

+  fast-copy@4.0.2:
+    resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==}
+
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}

@@ -1051,6 +1076,9 @@ packages:
     resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
     engines: {node: '>=8.6.0'}

+  fast-safe-stringify@2.1.1:
+    resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+
   fast-uri@3.1.0:
     resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}

@@ -1140,6 +1168,9 @@ packages:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}

+  help-me@5.0.0:
+    resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
+
   https-proxy-agent@7.0.6:
     resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
     engines: {node: '>= 14'}
@@ -1240,6 +1271,10 @@ packages:
     resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
     hasBin: true

+  joycon@3.1.1:
+    resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
+    engines: {node: '>=10'}
+
   js-levenshtein@1.1.6:
     resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
     engines: {node: '>=0.10.0'}
@@ -1344,6 +1379,13 @@ packages:
   obug@2.1.1:
     resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}

+  on-exit-leak-free@2.1.2:
+    resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
+    engines: {node: '>=14.0.0'}
+
+  once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
   onetime@2.0.1:
     resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==}
     engines: {node: '>=4'}
@@ -1429,6 +1471,20 @@ packages:
     resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
     engines: {node: '>=12'}

+  pino-abstract-transport@3.0.0:
+    resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
+
+  pino-pretty@13.1.3:
+    resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==}
+    hasBin: true
+
+  pino-std-serializers@7.1.0:
+    resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
+
+  pino@10.2.0:
+    resolution: {integrity: sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==}
+    hasBin: true
+
   playwright-core@1.57.0:
     resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==}
     engines: {node: '>=18'}
@@ -1458,6 +1514,12 @@ packages:
   process-nextick-args@2.0.1:
     resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}

+  process-warning@5.0.0:
+    resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
+
+  pump@3.0.3:
+    resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
+
   punycode.js@2.3.1:
     resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
     engines: {node: '>=6'}
@@ -1465,6 +1527,9 @@ packages:
   queue-microtask@1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}

+  quick-format-unescaped@4.0.4:
+    resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
+
   readable-stream@2.3.8:
     resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}

@@ -1472,6 +1537,10 @@ packages:
     resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
     engines: {node: '>= 20.19.0'}

+  real-require@0.2.0:
+    resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
+    engines: {node: '>= 12.13.0'}
+
   remeda@2.33.4:
     resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==}

@@ -1517,9 +1586,16 @@ packages:
   safe-buffer@5.1.2:
     resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}

+  safe-stable-stringify@2.5.0:
+    resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+    engines: {node: '>=10'}
+
   safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}

+  secure-json-parse@4.1.0:
+    resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
+
   shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
@@ -1549,10 +1625,17 @@ packages:
     resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
     engines: {node: '>=14.16'}

+  sonic-boom@4.2.0:
+    resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
+
   source-map-js@1.2.1:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}

+  split2@4.2.0:
+    resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+    engines: {node: '>= 10.x'}
+
   stackback@0.0.2:
     resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}

@@ -1598,6 +1681,10 @@ packages:
     resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
     engines: {node: '>=18'}

+  strip-json-comments@5.0.3:
+    resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
+    engines: {node: '>=14.16'}
+
   supports-color@10.2.2:
     resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
     engines: {node: '>=18'}
@@ -1609,6 +1696,10 @@ packages:
   tabtab@3.0.2:
     resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==}

+  thread-stream@4.0.0:
+    resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
+    engines: {node: '>=20'}
+
   through@2.3.8:
     resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}

@@ -1820,6 +1911,9 @@ packages:
     engines: {node: '>=8'}
     hasBin: true

+  wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
   wsl-utils@0.3.1:
     resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
     engines: {node: '>=20'}
@@ -2209,6 +2303,8 @@ snapshots:
     dependencies:
       '@otplib/core': 13.1.1

+  '@pinojs/redact@0.4.0': {}
+
   '@playwright/test@1.57.0':
     dependencies:
       playwright: 1.57.0
@@ -2484,6 +2580,8 @@ snapshots:

   async@3.2.6: {}

+  atomic-sleep@1.0.0: {}
+
   balanced-match@1.0.2: {}

   bluebird@3.7.2: {}
@@ -2538,6 +2636,8 @@ snapshots:

   colorette@1.4.0: {}

+  colorette@2.0.20: {}
+
   commander@14.0.2: {}

   commander@7.2.0: {}
@@ -2552,6 +2652,8 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2

+  dateformat@4.6.3: {}
+
   debug@4.4.1:
     dependencies:
       ms: 2.1.3
@@ -2581,6 +2683,10 @@ snapshots:
     dependencies:
       jake: 10.9.4

+  end-of-stream@1.4.5:
+    dependencies:
+      once: 1.4.0
+
   enquirer@2.4.1:
     dependencies:
       ansi-colors: 4.1.3
@@ -2681,6 +2787,8 @@ snapshots:
       iconv-lite: 0.4.24
       tmp: 0.0.33

+  fast-copy@4.0.2: {}
+
   fast-deep-equal@3.1.3: {}

   fast-glob@3.3.3:
@@ -2691,6 +2799,8 @@ snapshots:
       merge2: 1.4.1
       micromatch: 4.0.8

+  fast-safe-stringify@2.1.1: {}
+
   fast-uri@3.1.0: {}

   fastq@1.20.1:
@@ -2780,6 +2890,8 @@ snapshots:

   has-flag@3.0.0: {}

+  help-me@5.0.0: {}
+
   https-proxy-agent@7.0.6(supports-color@10.2.2):
     dependencies:
       agent-base: 7.1.4
@@ -2861,6 +2973,8 @@ snapshots:

   jiti@2.6.1: {}

+  joycon@3.1.1: {}
+
   js-levenshtein@1.1.6: {}

   js-tokens@4.0.0: {}
@@ -2953,6 +3067,12 @@ snapshots:

   obug@2.1.1: {}

+  on-exit-leak-free@2.1.2: {}
+
+  once@1.4.0:
+    dependencies:
+      wrappy: 1.0.2
+
   onetime@2.0.1:
     dependencies:
       mimic-fn: 1.2.0
@@ -3079,6 +3199,42 @@ snapshots:

   picomatch@4.0.3: {}

+  pino-abstract-transport@3.0.0:
+    dependencies:
+      split2: 4.2.0
+
+  pino-pretty@13.1.3:
+    dependencies:
+      colorette: 2.0.20
+      dateformat: 4.6.3
+      fast-copy: 4.0.2
+      fast-safe-stringify: 2.1.1
+      help-me: 5.0.0
+      joycon: 3.1.1
+      minimist: 1.2.8
+      on-exit-leak-free: 2.1.2
+      pino-abstract-transport: 3.0.0
+      pump: 3.0.3
+      secure-json-parse: 4.1.0
+      sonic-boom: 4.2.0
+      strip-json-comments: 5.0.3
+
+  pino-std-serializers@7.1.0: {}
+
+  pino@10.2.0:
+    dependencies:
+      '@pinojs/redact': 0.4.0
+      atomic-sleep: 1.0.0
+      on-exit-leak-free: 2.1.2
+      pino-abstract-transport: 3.0.0
+      pino-std-serializers: 7.1.0
+      process-warning: 5.0.0
+      quick-format-unescaped: 4.0.4
+      real-require: 0.2.0
+      safe-stable-stringify: 2.5.0
+      sonic-boom: 4.2.0
+      thread-stream: 4.0.0
+
   playwright-core@1.57.0: {}

   playwright@1.57.0:
@@ -3103,10 +3259,19 @@ snapshots:

   process-nextick-args@2.0.1: {}

+  process-warning@5.0.0: {}
+
+  pump@3.0.3:
+    dependencies:
+      end-of-stream: 1.4.5
+      once: 1.4.0
+
   punycode.js@2.3.1: {}

   queue-microtask@1.2.3: {}

+  quick-format-unescaped@4.0.4: {}
+
   readable-stream@2.3.8:
     dependencies:
       core-util-is: 1.0.3
@@ -3119,6 +3284,8 @@ snapshots:

   readdirp@5.0.0: {}

+  real-require@0.2.0: {}
+
   remeda@2.33.4: {}

   require-from-string@2.0.2: {}
@@ -3182,8 +3349,12 @@ snapshots:

   safe-buffer@5.1.2: {}

+  safe-stable-stringify@2.5.0: {}
+
   safer-buffer@2.1.2: {}

+  secure-json-parse@4.1.0: {}
+
   shebang-command@2.0.0:
     dependencies:
       shebang-regex: 3.0.0
@@ -3208,8 +3379,14 @@ snapshots:

   slash@5.1.0: {}

+  sonic-boom@4.2.0:
+    dependencies:
+      atomic-sleep: 1.0.0
+
   source-map-js@1.2.1: {}

+  split2@4.2.0: {}
+
   stackback@0.0.2: {}

   std-env@3.10.0: {}
@@ -3250,6 +3427,8 @@ snapshots:

   strip-final-newline@4.0.0: {}

+  strip-json-comments@5.0.3: {}
+
   supports-color@10.2.2: {}

   supports-color@5.5.0:
@@ -3267,6 +3446,10 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

+  thread-stream@4.0.0:
+    dependencies:
+      real-require: 0.2.0
+
   through@2.3.8: {}

   tinybench@2.9.0: {}
@@ -3430,6 +3613,8 @@ snapshots:
       siginfo: 2.0.0
       stackback: 0.0.2

+  wrappy@1.0.2: {}
+
   wsl-utils@0.3.1:
     dependencies:
       is-wsl: 3.1.0
diff --git c/src/completion.ts i/src/completion.ts
index f5a0c77..3e0b0ac 100644
--- c/src/completion.ts
+++ i/src/completion.ts
@@ -3,6 +3,7 @@ import { parse } from "shell-quote";
 import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab";

 import { BINARY_NAME } from "./cli";
+import { logger } from "./logger";
 import { loadConfig } from "./config";
 import { getTestReports } from "./tools";
 import { getEnvironments } from "./tools/environments";
@@ -193,11 +194,11 @@ export const installCompletion = async () => {
   await install({
     name: BINARY_NAME,
     completer: BINARY_NAME,
-  }).catch((err) => console.error("INSTALL ERROR", err));
+  }).catch((err) => logger.error({ err }, "INSTALL ERROR"));
 };

 export const uninstallCompletion = async () => {
   await uninstall({
     name: BINARY_NAME,
-  }).catch((err) => console.error("UNINSTALL ERROR", err));
+  }).catch((err) => logger.error({ err }, "UNINSTALL ERROR"));
 };
diff --git c/src/config.ts i/src/config.ts
index 9268bd7..bf42479 100644
--- c/src/config.ts
+++ i/src/config.ts
@@ -3,6 +3,8 @@ import fs from "fs/promises";
 import { homedir } from "os";
 import { join } from "path";

+import { logger } from "./logger";
+
 const CONFIG_DIR = ".config";

 export async function getConfigPath(ensureDir?: boolean): Promise<string> {
@@ -45,9 +47,9 @@ export async function loadConfig(force?: boolean): Promise<Config> {
   } catch (error) {
     // only exit on overwrite attempt
     if (force) {
-      console.error(
-        "❌ Error parsing configuration:",
-        (error as Error).message,
+      logger.error(
+        { err: error as Error },
+        "❌ Error parsing configuration",
       );
       process.exit(1);
     }
@@ -59,10 +61,10 @@ export async function saveConfig(newConfig: Config): Promise<void> {
   try {
     const configPath = await getConfigPath(true);
     await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8");
-    console.log(`✅ Configuration saved to ${configPath}`);
+    logger.info(`✅ Configuration saved to ${configPath}`);
     configLoaded = false;
   } catch (error) {
-    console.error("❌ Error saving configuration:", (error as Error).message);
+    logger.error({ err: error as Error }, "❌ Error saving configuration");
     process.exit(1);
   }
 }
diff --git c/src/debugtopus/index.ts i/src/debugtopus/index.ts
index 05ad042..32e7e63 100644
--- c/src/debugtopus/index.ts
+++ i/src/debugtopus/index.ts
@@ -16,6 +16,7 @@ import {
   findOctomindFolder,
   getAbsoluteFilePathInOctomindRoot,
 } from "../helpers";
+import { logger } from "../logger";
 import {
   getEnvironments,
   getPlaywrightCode,
@@ -91,7 +92,7 @@ const prepareDirectories = async (
       const appDir = dirname(nodeModule.filename);
       packageRootDir = getPackageRootLevel(appDir);
     } else {
-      console.warn("package was not installed as valid nodeJS module");
+      logger.warn("package was not installed as valid nodeJS module");
       packageRootDir = process.cwd();
     }
   }
@@ -169,17 +170,17 @@ const runTests = async ({
     command += " --ui";
   }

-  console.log(`executing command : '${command}'`);
+  logger.info(`executing command : '${command}'`);

   const { stderr } = await promisify(exec)(command, {
     cwd: packageRootDir,
   });

   if (stderr) {
-    console.error(stderr);
+    logger.error(stderr);
     process.exit(1);
   } else {
-    console.log(`success, you can find your artifacts at ${outputDir}`);
+    logger.info(`success, you can find your artifacts at ${outputDir}`);
   }
 };

diff --git c/src/debugtopus/installation.ts i/src/debugtopus/installation.ts
index 6582abd..080346a 100644
--- c/src/debugtopus/installation.ts
+++ i/src/debugtopus/installation.ts
@@ -5,6 +5,8 @@ import { promisify } from "util";

 import { chromium } from "@playwright/test";

+import { logger } from "../logger";
+
 export const ensureChromiumIsInstalled = async (
   packageRootDir: string,
 ): Promise<void> => {
@@ -20,7 +22,7 @@ export const ensureChromiumIsInstalled = async (
         reject(error);
         return;
       }
-      console.log(
+      logger.info(
         "Couldn't find any chromium binary, executing 'npx playwright install chromium'",
       );

@@ -32,7 +34,7 @@ export const ensureChromiumIsInstalled = async (
       );

       playwrightInstallExecution.child?.stdout?.on("data", (data) =>
-        console.log(data),
+        logger.info(data),
       );

       await playwrightInstallExecution;
diff --git c/src/logger.ts i/src/logger.ts
new file mode 100644
index 0000000..946ed95
--- /dev/null
+++ i/src/logger.ts
@@ -0,0 +1,15 @@
+import pino from "pino";
+
+const isProduction = process.env.NODE_ENV === "production";
+
+export const logger = pino({
+  level: process.env.LOG_LEVEL || "info",
+  transport: isProduction
+    ? undefined
+    : {
+        target: "pino-pretty",
+        options: {
+          colorize: true,
+        },
+      },
+});
diff --git c/src/plw/index.ts i/src/plw/index.ts
index b26e846..ea22afc 100644
--- c/src/plw/index.ts
+++ i/src/plw/index.ts
@@ -3,6 +3,7 @@ import { randomUUID } from "crypto";
 import { createInterface } from "readline";

 import { loadConfig } from "../config";
+import { logger } from "../logger";

 interface StreamResult {
   lines: string[];
@@ -39,7 +40,7 @@ const spawnAndStreamLines = async (

     rl.on("line", (line: string) => {
       lines.push(line);
-      console.log(`-- ${line}`);
+      logger.info(`-- ${line}`);
       lineCount++;

       if (lineCount >= maxLines) {
@@ -58,7 +59,7 @@ const spawnAndStreamLines = async (

     child.on("exit", (code, signal) => {
       if (lineCount < maxLines) {
-        console.log(`Process exited with code ${code}, signal ${signal}`);
+        logger.info(`Process exited with code ${code}, signal ${signal}`);
       }
       resolve({ lines, detached: false, code, signal });
     });
@@ -111,19 +112,19 @@ export const startPrivateLocationWorker = async (options: {
   });

   if (!(await checkDockerDaemon())) {
-    console.error(
+    logger.error(
       "Docker daemon is not running. Please start Docker and try again.",
     );
     return;
   }

-  console.log(
+  logger.info(
     `executing command : '${command.replace(/APIKEY=[^\s&]*/g, "APIKEY=***")}'`,
   );
   const args = command.split(" ");
   const result = await spawnAndStreamLines(args[0], args.slice(1), 10);
-  console.log(`Captured ${result.lines.length} lines`);
-  console.log(`Process detached: ${result.detached}`);
+  logger.info(`Captured ${result.lines.length} lines`);
+  logger.info(`Process detached: ${result.detached}`);
 };

 export const stopPLW = async (): Promise<void> => {
@@ -131,9 +132,9 @@ export const stopPLW = async (): Promise<void> => {
   const args = command.split(" ");
   const result = await spawnAndStreamLines(args[0], args.slice(1), 10);
   if (result.code === 0) {
-    console.log("Private Location Worker stopped successfully.");
+    logger.info("Private Location Worker stopped successfully.");
   } else {
-    console.error(
+    logger.error(
       `Failed to stop Private Location Worker. Exit code: ${result.code}`,
     );
   }
diff --git c/src/tools/client.ts i/src/tools/client.ts
index 979aa2a..6e5146c 100644
--- c/src/tools/client.ts
+++ i/src/tools/client.ts
@@ -2,6 +2,7 @@ import createClient, { Client, Middleware } from "openapi-fetch";

 import type { components, paths } from "../api"; // generated by openapi-typescript
 import { loadConfig } from "../config";
+import { logger } from "../logger";
 import { version } from "../version";

 export const BASE_URL =
@@ -65,7 +66,7 @@ const createAuthMiddleware = ({
       return response;
     },
     onError({ error }) {
-      console.error(error);
+      logger.error(error);
       process.exit(1);
     },
   };
@@ -77,9 +78,9 @@ export { client, paths };

 export const handleError = (error: ErrorResponse) => {
   if (error) {
-    console.error(error);
+    logger.error(error);
     if (typeof error === "string" && error.startsWith("403")) {
-      console.error(
+      logger.error(
         "You are not authorized. Check your API key or do a 'octomind init' to set it up.",
       );
     }
@@ -91,5 +92,5 @@ export type ListOptions = {
   json?: boolean;
 };
 export const logJson = (result: unknown): void => {
-  console.log(JSON.stringify(result, null, 2));
+  logger.info(JSON.stringify(result, null, 2));
 };
diff --git c/src/tools/discoveries.ts i/src/tools/discoveries.ts
index a8f2449..7aa3d14 100644
--- c/src/tools/discoveries.ts
+++ i/src/tools/discoveries.ts
@@ -1,4 +1,5 @@
 import type { components } from "../api"; // generated by openapi-typescript
+import { logger } from "../logger";
 import { getUrl } from "../url";
 import { client, handleError, ListOptions, logJson } from "./client";

@@ -47,9 +48,9 @@ export const batchGeneration = async (
     throw new Error("Batch generation ID is missing");
   }

-  console.log("Batch generation created successfully!");
-  console.log(`Batch generation ID: ${response.batchGenerationId}`);
-  console.log(
+  logger.info("Batch generation created successfully!");
+  logger.info(`Batch generation ID: ${response.batchGenerationId}`);
+  logger.info(
     `Batch generation URL: ${await getUrl({
       batchGenerationId: response.batchGenerationId,
       entityType: "batch-generation",
@@ -95,13 +96,13 @@ export const createDiscovery = async (
     return;
   }

-  console.log("Discovery created successfully!");
-  console.log(
+  logger.info("Discovery created successfully!");
+  logger.info(
     `${await getUrl({
       testCaseId: response.testCaseId ?? "",
       entityType: "discovery",
     })}`,
   );
-  console.log(`Discovery ID: ${response.discoveryId}`);
-  console.log(`Test Case ID: ${response.testCaseId}`);
+  logger.info(`Discovery ID: ${response.discoveryId}`);
+  logger.info(`Test Case ID: ${response.testCaseId}`);
 };
diff --git c/src/tools/environments.ts i/src/tools/environments.ts
index 6712707..c38925f 100644
--- c/src/tools/environments.ts
+++ i/src/tools/environments.ts
@@ -1,4 +1,5 @@
 import type { components, paths } from "../api"; // generated by openapi-typescript
+import { logger } from "../logger";
 import { client, handleError, ListOptions, logJson } from "./client";

 export type GetEnvironmentsOptions =
@@ -26,12 +27,12 @@ export const listEnvironments = async (
     return;
   }

-  console.log("Environments:");
+  logger.info("Environments:");
   environments.forEach((environment) => {
-    console.log(`- Name: ${environment.name}`);
-    console.log(`  ID: ${environment.id}`);
-    console.log(`  Discovery URL: ${environment.discoveryUrl}`);
-    console.log(`  Updated At: ${environment.updatedAt}`);
+    logger.info(`- Name: ${environment.name}`);
+    logger.info(`  ID: ${environment.id}`);
+    logger.info(`  Discovery URL: ${environment.discoveryUrl}`);
+    logger.info(`  Updated At: ${environment.updatedAt}`);
   });
 };

@@ -99,11 +100,11 @@ export const createEnvironment = async (
     return;
   }

-  console.log("Environment created successfully!");
-  console.log(`- Name: ${response.name}`);
-  console.log(`  ID: ${response.id}`);
-  console.log(`  Discovery URL: ${response.discoveryUrl}`);
-  console.log(`  Updated At: ${response.updatedAt}`);
+  logger.info("Environment created successfully!");
+  logger.info(`- Name: ${response.name}`);
+  logger.info(`  ID: ${response.id}`);
+  logger.info(`  Discovery URL: ${response.discoveryUrl}`);
+  logger.info(`  Updated At: ${response.updatedAt}`);
 };

 export const updateEnvironment = async (
@@ -152,11 +153,11 @@ export const updateEnvironment = async (
     return;
   }

-  console.log("Environment updated successfully!");
-  console.log(`- Name: ${response.name}`);
-  console.log(`  ID: ${response.id}`);
-  console.log(`  Discovery URL: ${response.discoveryUrl}`);
-  console.log(`  Updated At: ${response.updatedAt}`);
+  logger.info("Environment updated successfully!");
+  logger.info(`- Name: ${response.name}`);
+  logger.info(`  ID: ${response.id}`);
+  logger.info(`  Discovery URL: ${response.discoveryUrl}`);
+  logger.info(`  Updated At: ${response.updatedAt}`);
 };

 export const getEnvironment = async (
@@ -176,11 +177,11 @@ export const getEnvironment = async (
     return;
   }

-  console.log("Environment:");
-  console.log(`- Name: ${environment.name}`);
-  console.log(`  ID: ${environment.id}`);
-  console.log(`  Discovery URL: ${environment.discoveryUrl}`);
-  console.log(`  Updated At: ${environment.updatedAt}`);
+  logger.info("Environment:");
+  logger.info(`- Name: ${environment.name}`);
+  logger.info(`  ID: ${environment.id}`);
+  logger.info(`  Discovery URL: ${environment.discoveryUrl}`);
+  logger.info(`  Updated At: ${environment.updatedAt}`);
 };

 export const deleteEnvironment = async (
@@ -205,5 +206,5 @@ export const deleteEnvironment = async (
     return;
   }

-  console.log("Environment deleted successfully!");
+  logger.info("Environment deleted successfully!");
 };
diff --git c/src/tools/init.ts i/src/tools/init.ts
index a815fd2..57ad8b2 100644
--- c/src/tools/init.ts
+++ i/src/tools/init.ts
@@ -1,5 +1,6 @@
 import { Config, loadConfig, saveConfig } from "../config";
 import { promptUser } from "../helpers";
+import { logger } from "../logger";
 import { getTestTargets, listTestTargets } from "./test-targets";

 const selectTestTarget = async (): Promise<string> => {
@@ -7,7 +8,7 @@ const selectTestTarget = async (): Promise<string> => {
   await listTestTargets({});

   if (testTargets.length === 1) {
-    console.log(
+    logger.info(
       `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`,
     );
     return testTargets[0].id;
@@ -23,12 +24,12 @@ const selectTestTarget = async (): Promise<string> => {
     testTargetIndexAsInt < 1 ||
     testTargetIndexAsInt > testTargets.length
   ) {
-    console.log("❌ could not find a test target with the index you provided");
+    logger.info("❌ could not find a test target with the index you provided");
     process.exit(1);
   }
   const testTargetId = testTargets[testTargetIndexAsInt - 1].id;
   if (!testTargetId) {
-    console.log("❌ could not find a test target with the index you provided");
+    logger.info("❌ could not find a test target with the index you provided");
     process.exit(1);
   }

@@ -43,7 +44,7 @@ export const switchTestTarget = async () => {
     testTargetId,
   };
   await saveConfig(newConfig);
-  console.log(`✨ Switched to test target: ${testTargetId}`);
+  logger.info(`✨ Switched to test target: ${testTargetId}`);
 };

 export const init = async (options: {
@@ -52,12 +53,12 @@ export const init = async (options: {
   force?: boolean;
 }) => {
   try {
-    console.log("🚀 Initializing configuration...\n");
+    logger.info("🚀 Initializing configuration...\n");

     const existingConfig = await loadConfig(options.force);

     if (existingConfig.apiKey && !options.force) {
-      console.log("⚠️  Configuration already exists.");
+      logger.info("⚠️  Configuration already exists.");
       const overwrite = await promptUser(
         "Do you want to overwrite it? (y/N): ",
       );
@@ -66,7 +67,7 @@ export const init = async (options: {
         overwrite.toLowerCase() !== "y" &&
         overwrite.toLowerCase() !== "yes"
       ) {
-        console.log("Configuration unchanged.");
+        logger.info("Configuration unchanged.");
         return;
       }
     }
@@ -76,7 +77,7 @@ export const init = async (options: {
         "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ",
       );
       if (!options.apiKey) {
-        console.log("❌ API key is required.");
+        logger.info("❌ API key is required.");
         process.exit(1);
       }
     }
@@ -99,9 +100,9 @@ export const init = async (options: {

     await saveConfig(newConfig);

-    console.log("\n✨ Initialization complete!");
+    logger.info("\n✨ Initialization complete!");
   } catch (error) {
-    console.error("❌ Error during initialization:", (error as Error).message);
+    logger.error({ err: error as Error }, "❌ Error during initialization");
     process.exit(1);
   }
 };
diff --git c/src/tools/notifications.ts i/src/tools/notifications.ts
index a1b9339..93f9319 100644
--- c/src/tools/notifications.ts
+++ i/src/tools/notifications.ts
@@ -1,4 +1,5 @@
 import type { components, paths } from "../api"; // generated by openapi-typescript
+import { logger } from "../logger";
 import { getUrl } from "../url";
 import { client, handleError, ListOptions, logJson } from "./client";

@@ -27,28 +28,28 @@ export const listNotifications = async (
     return;
   }

-  console.log("Notifications:");
+  logger.info("Notifications:");
   for (const notification of response) {
-    console.log(`\nID: ${notification.id}`);
-    console.log(`Type: ${notification.type}`);
-    console.log(`Created At: ${notification.createdAt}`);
+    logger.info(`\nID: ${notification.id}`);
+    logger.info(`Type: ${notification.type}`);
+    logger.info(`Created At: ${notification.createdAt}`);
     if (notification.payload?.testReportId) {
-      console.log(`Test Report ID: ${notification.payload.testReportId}`);
-      console.log(
+      logger.info(`Test Report ID: ${notification.payload.testReportId}`);
+      logger.info(
         `URL: ${await getUrl({ testReportId: notification.payload.testReportId, entityType: "test-report" })}`,
       );
     }
     if (notification.payload?.testCaseId) {
-      console.log(`Test Case ID: ${notification.payload.testCaseId}`);
-      console.log(
+      logger.info(`Test Case ID: ${notification.payload.testCaseId}`);
+      logger.info(
         `URL: ${await getUrl({ testCaseId: notification.payload.testCaseId, entityType: "test-case" })}`,
       );
     }
     if (notification.payload?.failed !== undefined) {
-      console.log(`Failed: ${notification.payload.failed}`);
+      logger.info(`Failed: ${notification.payload.failed}`);
     }
     if (notification.ack) {
-      console.log(`Acknowledged: ${notification.ack}`);
+      logger.info(`Acknowledged: ${notification.ack}`);
     }
   }
 };
diff --git c/src/tools/private-locations.ts i/src/tools/private-locations.ts
index b91a414..7c0ba62 100644
--- c/src/tools/private-locations.ts
+++ i/src/tools/private-locations.ts
@@ -1,4 +1,5 @@
 import type { components } from "../api"; // generated by openapi-typescript
+import { logger } from "../logger";
 import { client, handleError, ListOptions, logJson } from "./client";

 export type SuccessResponse = components["schemas"]["SuccessResponse"];
@@ -35,7 +36,7 @@ export const registerLocation = async (
     return;
   }

-  console.log("Registration result:", response.success ? "Success" : "Failed");
+  logger.info(`Registration result: ${response.success ? "Success" : "Failed"}`);
 };

 export const unregisterLocation = async (
@@ -60,9 +61,8 @@ export const unregisterLocation = async (
     return;
   }

-  console.log(
-    "Unregistration result:",
-    response.success ? "Success" : "Failed",
+  logger.info(
+    `Unregistration result: ${response.success ? "Success" : "Failed"}`,
   );
 };

@@ -79,10 +79,10 @@ export const listPrivateLocations = async (
     return;
   }

-  console.log("Private Locations:");
+  logger.info("Private Locations:");
   response.forEach((location) => {
-    console.log(`- Name: ${location.name}`);
-    console.log(`  Status: ${location.status}`);
-    console.log(`  Address: ${location.address}`);
+    logger.info(`- Name: ${location.name}`);
+    logger.info(`  Status: ${location.status}`);
+    logger.info(`  Address: ${location.address}`);
   });
 };
diff --git c/src/tools/sync/git.ts i/src/tools/sync/git.ts
index 765b3bd..3a7fc1f 100644
--- c/src/tools/sync/git.ts
+++ i/src/tools/sync/git.ts
@@ -2,6 +2,7 @@ import path from "path";

 import { simpleGit } from "simple-git";

+import { logger } from "../../logger";
 import { ExecutionContext } from "./types";

 const FALLBACK_DEFAULT_BRANCH = "refs/heads/main";
@@ -33,7 +34,7 @@ export const parseGitRemote = async (): Promise<{
     const revParse = await simpleGit().revparse(["--show-toplevel"]);
     return { repo: path.basename(revParse) };
   } catch (error) {
-    console.error(error);
+    logger.error({ err: error }, "Failed to parse git remote");
     return {};
   }
 };
@@ -59,9 +60,9 @@ export const getDefaultBranch = async (
         return symbolicRefBranch;
       }
     } catch (e) {
-      console.warn(
+      logger.warn(
+        { err: e },
         "could not identify symbolic ref, falling back to origin parsing",
-        e,
       );
     }
   }
@@ -69,7 +70,7 @@ export const getDefaultBranch = async (
   try {
     const origin = await simpleGit().remote(["show", "origin"]);
     if (!origin) {
-      console.warn("could not identify default branch, falling back to 'main'");
+      logger.warn("could not identify default branch, falling back to 'main'");
       return FALLBACK_DEFAULT_BRANCH;
     }

@@ -79,9 +80,9 @@ export const getDefaultBranch = async (
       ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}`
       : FALLBACK_DEFAULT_BRANCH;
   } catch (e) {
-    console.warn(
+    logger.warn(
+      { err: e },
       "could not identify default branch, falling back to 'main'",
-      e,
     );
   }

@@ -108,9 +109,9 @@ export const getGitContext = async (): Promise<GitContext | undefined> => {
     };
     return ctx;
   } catch (e) {
-    console.warn(
+    logger.warn(
+      { err: e },
       "could not identify git context, falling back to undefined",
-      e,
     );
     return undefined;
   }
diff --git c/src/tools/sync/yaml.ts i/src/tools/sync/yaml.ts
index 7451476..edf2d28 100644
--- c/src/tools/sync/yaml.ts
+++ i/src/tools/sync/yaml.ts
@@ -4,6 +4,7 @@ import path from "path";

 import yaml from "yaml";

+import { logger } from "../../logger";
 import { PushTestTargetBody } from "../../schemas/octomindExternalAPI";
 import { SyncTestCase, TestTargetSyncData } from "./types";

@@ -189,12 +190,12 @@ export const readTestCasesFromDir = (startDir: string): SyncTestCase[] => {
       if (result.success) {
         testCases.push(result.data);
       } else {
-        console.warn(
+        logger.warn(
           `Failed to read test case from ${file}: ${result.error.message}`,
         );
       }
     } catch {
-      console.error(`Failed to read test case from ${file}`);
+      logger.error(`Failed to read test case from ${file}`);
     }
   }

diff --git c/src/tools/test-cases.ts i/src/tools/test-cases.ts
index 742fb06..54a3831 100644
--- c/src/tools/test-cases.ts
+++ i/src/tools/test-cases.ts
@@ -3,6 +3,7 @@ import path from "path";

 import type { components, paths } from "../api"; // generated by openapi-typescript
 import { findOctomindFolder } from "../helpers";
+import { logger } from "../logger";
 import { getUrl } from "../url";
 import { client, handleError, ListOptions, logJson } from "./client";
 import { getEnvironments } from "./environments";
@@ -37,7 +38,7 @@ export const deleteTestCase = async (
     if (options.json) {
       logJson(data);
     }
-    console.log("Test Case deleted successfully");
+    logger.info("Test Case deleted successfully");
     return;
   }

@@ -46,7 +47,7 @@ export const deleteTestCase = async (
   const existingTestCase = testCasesById[options.testCaseId];

   if (!existingTestCase) {
-    console.log(
+    logger.info(
       `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`,
     );
     return;
@@ -54,7 +55,7 @@ export const deleteTestCase = async (

   const existingTestCasePath = buildFilename(existingTestCase, octomindRoot);
   await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath));
-  console.log("Test Case deleted successfully");
+  logger.info("Test Case deleted successfully");
 };

 export const listTestCase = async (
@@ -81,35 +82,35 @@ export const listTestCase = async (
     return;
   }

-  console.log("Test Case Details:");
-  console.log(`ID: ${response.id}`);
-  console.log(`Description: ${response.description}`);
-  console.log(`Status: ${response.status}`);
-  console.log(`Run Status: ${response.runStatus}`);
-  console.log(`Created At: ${response.createdAt}`);
-  console.log(`Updated At: ${response.updatedAt}`);
+  logger.info("Test Case Details:");
+  logger.info(`ID: ${response.id}`);
+  logger.info(`Description: ${response.description}`);
+  logger.info(`Status: ${response.status}`);
+  logger.info(`Run Status: ${response.runStatus}`);
+  logger.info(`Created At: ${response.createdAt}`);
+  logger.info(`Updated At: ${response.updatedAt}`);

   if (response.elements && response.elements.length > 0) {
-    console.log("\nElements:");
+    logger.info("\nElements:");
     response.elements.forEach((element, index) => {
-      console.log(`\nElement ${index + 1}:`);
+      logger.info(`\nElement ${index + 1}:`);
       if (element.interaction) {
-        console.log(`  Action: ${element.interaction.action}`);
+        logger.info(`  Action: ${element.interaction.action}`);
         if (element.interaction.calledWith) {
-          console.log(`  Called With: ${element.interaction.calledWith}`);
+          logger.info(`  Called With: ${element.interaction.calledWith}`);
         }
       }
       if (element.assertion) {
-        console.log(`  Expectation: ${element.assertion.expectation}`);
+        logger.info(`  Expectation: ${element.assertion.expectation}`);
         if (element.assertion.calledWith) {
-          console.log(`  Called With: ${element.assertion.calledWith}`);
+          logger.info(`  Called With: ${element.assertion.calledWith}`);
         }
       }
-      console.log("  Selectors:");
+      logger.info("  Selectors:");
       element.selectors?.forEach((selector) => {
-        console.log(`    - ${selector.selectorType}: ${selector.selector}`);
+        logger.info(`    - ${selector.selectorType}: ${selector.selector}`);
         if (selector.options?.name) {
-          console.log(`      Name: ${selector.options.name}`);
+          logger.info(`      Name: ${selector.options.name}`);
         }
       });
     });
@@ -193,7 +194,7 @@ export const getTestCaseCode = async (
   if (!data) {
     throw new Error("no test code found");
   }
-  console.log(data.testCode);
+  logger.info(data.testCode);
 };

 export const listTestCases = async (
@@ -209,16 +210,16 @@ export const listTestCases = async (
     return;
   }

-  console.log("Test Cases:");
+  logger.info("Test Cases:");
   for (let idx = 0; idx < testCases.length; idx++) {
     const testCase = testCases[idx];
     const idxString = `${idx + 1}. `.padEnd(
       testCases.length.toString().length + 2,
     );
     const paddingString = " ".repeat(idxString.length);
-    console.log(`${idxString}Description: ${testCase.description}`);
-    console.log(`${paddingString}ID: ${testCase.id}`);
-    console.log(
+    logger.info(`${idxString}Description: ${testCase.description}`);
+    logger.info(`${paddingString}ID: ${testCase.id}`);
+    logger.info(
       `${paddingString}${await getUrl({
         testCaseId: testCase.id,
         entityType: "test-case",
diff --git c/src/tools/test-reports.ts i/src/tools/test-reports.ts
index 1e5e509..2571e8e 100644
--- c/src/tools/test-reports.ts
+++ i/src/tools/test-reports.ts
@@ -1,4 +1,5 @@
 import type { components, paths } from "../api"; // generated by openapi-typescript
+import { logger } from "../logger";
 import { getUrl } from "../url";
 import { client, handleError, ListOptions, logJson } from "./client";

@@ -39,19 +40,19 @@ export const executeTests = async (
   }

   if (data) {
-    console.log("Test execution started successfully!");
-    console.log("Test Report URL:", data.testReportUrl);
-    console.log("Report Status:", data.testReport?.status);
+    logger.info("Test execution started successfully!");
+    logger.info(`Test Report URL: ${data.testReportUrl}`);
+    logger.info(`Report Status: ${data.testReport?.status}`);
     const numberOfTestResults = data.testReport?.testResults?.length ?? 0;
     if (numberOfTestResults > 0) {
-      console.log("\nTest Results:");
+      logger.info("\nTest Results:");
       data.testReport?.testResults?.forEach((result) => {
-        console.log(`- Test ${result.testCaseVersionId}: ${result.status}`);
+        logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`);
         if (result.errorMessage) {
-          console.log(`  Error: ${result.errorMessage}`);
+          logger.info(`  Error: ${result.errorMessage}`);
         }
         if (result.traceUrl) {
-          console.log(`  Trace: ${result.traceUrl}`);
+          logger.info(`  Trace: ${result.traceUrl}`);
         }
       });
     }
@@ -100,15 +101,15 @@ export const listTestReport = async (
   }

   const response = data as TestReport;
-  console.log("Test Report Details:");
-  console.log("Status:", response.status);
-  console.log("Execution URL:", response.executionUrl);
+  logger.info("Test Report Details:");
+  logger.info(`Status: ${response.status}`);
+  logger.info(`Execution URL: ${response.executionUrl}`);
   const numberOfTestResults = response.testResults?.length ?? 0;
   if (numberOfTestResults > 0) {
-    console.log("\nTest Results:");
+    logger.info("\nTest Results:");
     for (const result of response.testResults ?? []) {
-      console.log(`- Test ${result.testCaseVersionId}: ${result.status}`);
-      console.log(
+      logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`);
+      logger.info(
         `  ${await getUrl({
           testReportId: options.testReportId,
           testResultId: result.id ?? "",
@@ -116,10 +117,10 @@ export const listTestReport = async (
         })}`,
       );
       if (result.errorMessage) {
-        console.log(`  Error: ${result.errorMessage}`);
+        logger.info(`  Error: ${result.errorMessage}`);
       }
       if (result.traceUrl) {
-        console.log(`  Trace: ${result.traceUrl}`);
+        logger.info(`  Trace: ${result.traceUrl}`);
       }
     }
   }
diff --git c/src/tools/test-targets.ts i/src/tools/test-targets.ts
index cf39a33..2cb64e4 100644
--- c/src/tools/test-targets.ts
+++ i/src/tools/test-targets.ts
@@ -4,6 +4,7 @@ import ora from "ora";

 import { OCTOMIND_FOLDER_NAME } from "../constants";
 import { confirmAction, findOctomindFolder } from "../helpers";
+import { logger } from "../logger";
 import { getUrl } from "../url";
 import { client, handleError, ListOptions, logJson } from "./client";
 import { push } from "./sync/push";
@@ -49,7 +50,7 @@ export const listTestTargets = async (options: ListOptions): Promise<void> => {
     return;
   }

-  console.log("Test Targets:");
+  logger.info("Test Targets:");

   for (let idx = 0; idx < testTargets.length; idx++) {
     const testTarget = testTargets[idx];
@@ -57,9 +58,9 @@ export const listTestTargets = async (options: ListOptions): Promise<void> => {
       testTargets.length.toString().length + 2,
     );
     const paddingString = " ".repeat(idxString.length);
-    console.log(`${idxString}ID: ${testTarget.id}`);
-    console.log(`${paddingString}App: ${testTarget.app}`);
-    console.log(
+    logger.info(`${idxString}ID: ${testTarget.id}`);
+    logger.info(`${paddingString}App: ${testTarget.app}`);
+    logger.info(
       `${paddingString}${await getUrl({
         testTargetId: testTarget.id,
         entityType: "test-target",
@@ -98,7 +99,7 @@ export const pullTestTarget = async (
     path.join(process.cwd(), OCTOMIND_FOLDER_NAME);
   await writeYaml(data, destination);

-  console.log("Test Target pulled successfully");
+  logger.info("Test Target pulled successfully");
 };

 export const pushTestTarget = async (
@@ -124,7 +125,7 @@ export const pushTestTarget = async (
       `Push local changes to test target "${testTarget.app}" with id "${testTarget.id}"?`,
     );
     if (!confirmed) {
-      console.log("Push cancelled.");
+      logger.info("Push cancelled.");
       return;
     }
   }
diff --git c/src/tools/update.ts i/src/tools/update.ts
index 2091739..6b89dc1 100644
--- c/src/tools/update.ts
+++ i/src/tools/update.ts
@@ -5,6 +5,8 @@ import path from "path";

 import which from "which";

+import { logger } from "../logger";
+
 export const update = async (): Promise<void> => {
   const pathToRoot = path.posix.normalize(
     path.join(homedir(), "/.local/packages"),
@@ -12,20 +14,20 @@ export const update = async (): Promise<void> => {
   const pathToPackageJson = path.join(pathToRoot, "package.json");

   if (!fs.existsSync(pathToPackageJson)) {
-    console.error(
+    logger.error(
       "Cannot find package.json at ~/.local/packages. If you installed globally via npm, run: npm update -g @octomind/octomind",
     );
     process.exit(1);
   }

   if (!(await which("npm"))) {
-    console.error(
+    logger.error(
       `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`,
     );
     process.exit(1);
   }

-  console.log(`updating package.json at ${pathToPackageJson}`);
+  logger.info(`updating package.json at ${pathToPackageJson}`);

   const result = child_process.execSync(
     "npm install @octomind/octomind@latest",
@@ -34,6 +36,6 @@ export const update = async (): Promise<void> => {
     },
   );

-  console.log(`${result}`);
-  console.log("\n✔ update complete");
+  logger.info(`${result}`);
+  logger.info("\n✔ update complete");
 };
diff --git c/src/tools/yamlMutations/create.ts i/src/tools/yamlMutations/create.ts
index d819b50..946d268 100644
--- c/src/tools/yamlMutations/create.ts
+++ i/src/tools/yamlMutations/create.ts
@@ -9,6 +9,7 @@ import {
   findOctomindFolder,
   getAbsoluteFilePathInOctomindRoot,
 } from "../../helpers";
+import { logger } from "../../logger";
 import { BASE_URL, client, handleError } from "../client";
 import { checkForConsistency } from "../sync/consistency";
 import { draftPush } from "../sync/push";
@@ -75,7 +76,7 @@ const openBrowser = async ({

   await open(localEditingUrl.href);

-  console.log(
+  logger.info(
     `Navigating to local url, open it manually if a browser didn't open already: ${localEditingUrl}`,
   );
 };
@@ -88,7 +89,7 @@ const writeOutput = async ({
   createResult: SyncTestCase | "cancelled";
 }): Promise<void> => {
   if (createResult === "cancelled") {
-    console.log("Cancelled editing test case, exiting");
+    logger.info("Cancelled editing test case, exiting");
     return;
   }

@@ -101,8 +102,8 @@ const writeOutput = async ({
     yaml.stringify(createResult),
   );

-  console.log(`Created test case successfully`);
-  console.log(diff);
+  logger.info(`Created test case successfully`);
+  logger.info(diff);
 };

 export const create = async (options: CreateOptions): Promise<void> => {
diff --git c/src/tools/yamlMutations/edit.ts i/src/tools/yamlMutations/edit.ts
index b8fb58e..fe8a00c 100644
--- c/src/tools/yamlMutations/edit.ts
+++ i/src/tools/yamlMutations/edit.ts
@@ -7,6 +7,7 @@ import {
   findOctomindFolder,
   getAbsoluteFilePathInOctomindRoot,
 } from "../../helpers";
+import { logger } from "../../logger";
 import { BASE_URL, client, handleError } from "../client";
 import { checkForConsistency } from "../sync/consistency";
 import { draftPush } from "../sync/push";
@@ -84,7 +85,7 @@ export const edit = async (options: EditOptions): Promise<void> => {

   await open(localEditingUrl.href);

-  console.log(
+  logger.info(
     `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`,
   );

@@ -95,7 +96,7 @@ export const edit = async (options: EditOptions): Promise<void> => {
   );

   if (editResult === "cancelled") {
-    console.log("Cancelled editing test case, exiting");
+    logger.info("Cancelled editing test case, exiting");
     return;
   }

@@ -108,6 +109,6 @@ export const edit = async (options: EditOptions): Promise<void> => {
     yaml.stringify(editResult),
   );

-  console.log(`Edited test case successfully`);
-  console.log(diff);
+  logger.info(`Edited test case successfully`);
+  logger.info(diff);
 };
diff --git c/tests/config.spec.ts i/tests/config.spec.ts
index c1c9b9c..0856939 100644
--- c/tests/config.spec.ts
+++ i/tests/config.spec.ts
@@ -5,13 +5,14 @@ import path from "path";
 import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

 import { Config, loadConfig, resetConfig } from "../src/config";
+import { mockLogger } from "./mocks";

 vi.mock("fs/promises");
 const mockedFs = vi.mocked(fs);

 describe("Config", () => {
   beforeEach(() => {
-    console.error = vi.fn();
+    mockLogger.error.mockClear();
     resetConfig();
   });

@@ -68,7 +69,7 @@ describe("Config", () => {
       const result = await loadConfig();

       expect(result).toEqual({});
-      expect(console.error).not.toHaveBeenCalled();
+      expect(mockLogger.error).not.toHaveBeenCalled();
     });

     it("should error and exit when config file does not exist and force is true", async () => {
@@ -89,7 +90,7 @@ describe("Config", () => {
       await expect(loadConfig(MOCK_FORCE_OPTION)).rejects.toThrow(
         "Process exit with code: 1",
       );
-      expect(console.error).toHaveBeenCalled();
+      expect(mockLogger.error).toHaveBeenCalled();
       expect(mockExit).toHaveBeenCalledWith(1);

       mockExit.mockRestore();
diff --git c/tests/mocks.ts i/tests/mocks.ts
index 42342b8..02e672c 100644
--- c/tests/mocks.ts
+++ i/tests/mocks.ts
@@ -1,3 +1,4 @@
+import { vi } from "vitest";
 import {
   EnvironmentResponse,
   TestCaseResponse,
@@ -5,6 +6,19 @@ import {
 } from "../src/tools";
 import { SyncDataByStableId, SyncTestCase } from "../src/tools/sync/types";

+export const mockLogger = {
+  info: vi.fn(),
+  error: vi.fn(),
+  warn: vi.fn(),
+  debug: vi.fn(),
+  log: vi.fn(),
+  success: vi.fn(),
+};
+
+vi.mock("../src/logger", () => ({
+  logger: mockLogger,
+}));
+
 export const createMockSyncTestCase = (
   overrides?: Partial<SyncTestCase>,
 ): SyncTestCase => ({
diff --git c/tests/setup.ts i/tests/setup.ts
index cd22023..aa85cd5 100644
--- c/tests/setup.ts
+++ i/tests/setup.ts
@@ -1,5 +1,7 @@
 import { beforeEach, vi } from "vitest";

+import { mockLogger } from "./mocks";
+
 vi.mock("ora", () => ({
   default: vi.fn(() => ({
     start: vi.fn().mockReturnThis(),
@@ -9,7 +11,10 @@ vi.mock("ora", () => ({
 }));

 beforeEach(() => {
-  console.log = vi.fn();
-  console.warn = vi.fn();
-  console.error = vi.fn();
+  mockLogger.info.mockClear();
+  mockLogger.warn.mockClear();
+  mockLogger.error.mockClear();
+  mockLogger.debug.mockClear();
+  mockLogger.log.mockClear();
+  mockLogger.success.mockClear();
 });
diff --git c/tests/tools/init.spec.ts i/tests/tools/init.spec.ts
index a29a295..fc1b5b3 100644
--- c/tests/tools/init.spec.ts
+++ i/tests/tools/init.spec.ts
@@ -4,6 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config";
 import { promptUser } from "../../src/helpers";
 import { init } from "../../src/tools/init";
 import { getTestTargets } from "../../src/tools/test-targets";
+import { mockLogger } from "../mocks";

 vi.mock("../../src/config");
 vi.mock("../../src/helpers");
@@ -15,7 +16,7 @@ describe("init", () => {
   let getTestTargetsMock: Mock<typeof getTestTargets>;

   beforeEach(() => {
-    console.log = vi.fn();
+    mockLogger.info.mockClear();

     loadConfigMock = vi.mocked(loadConfig);
     loadConfigMock.mockResolvedValue({ apiKey: "apiKey" });
@@ -36,10 +37,10 @@ describe("init", () => {
       apiKey: "newApiKey",
       testTargetId: "testTargetId",
     });
-    expect(console.log).toHaveBeenCalledWith(
+    expect(mockLogger.info).toHaveBeenCalledWith(
       "Only one test target found, using it: testTargetApp (testTargetId)",
     );
-    expect(console.log).toHaveBeenNthCalledWith(
+    expect(mockLogger.info).toHaveBeenNthCalledWith(
       3,
       "\n✨ Initialization complete!",
     );
@@ -56,7 +57,7 @@ describe("init", () => {
       apiKey: "newApiKey",
       testTargetId: "testTargetId1",
     });
-    expect(console.log).toHaveBeenNthCalledWith(
+    expect(mockLogger.info).toHaveBeenNthCalledWith(
       2,
       "\n✨ Initialization complete!",
     );
diff --git c/tests/tools/sync/git.spec.ts i/tests/tools/sync/git.spec.ts
index 4dc35df..4b2a10c 100644
--- c/tests/tools/sync/git.spec.ts
+++ i/tests/tools/sync/git.spec.ts
@@ -7,6 +7,7 @@ import {
   getGitContext,
   parseGitRemote,
 } from "../../../src/tools/sync/git";
+import { mockLogger } from "../../mocks";

 vi.mock("simple-git");

@@ -17,30 +18,29 @@ describe("git", () => {
     mockGit = mock<SimpleGit>();
     vi.mocked(simpleGit).mockReturnValue(mockGit);
     mockGit.raw.mockResolvedValue("refs/remotes/origin/main");
-    console.error = vi.fn();
+    mockLogger.error.mockClear();
+    mockLogger.warn.mockClear();
   });

   describe("getDefaultBranch", () => {
     it("should fallback if both origin and symbolic ref fail", async () => {
-      console.warn = vi.fn();
       mockGit.raw.mockResolvedValue("");
       mockGit.remote.mockResolvedValue("");

       const defaultBranch = await getDefaultBranch();

       expect(defaultBranch).toEqual("refs/heads/main");
-      expect(console.warn).toHaveBeenCalled();
+      expect(mockLogger.warn).toHaveBeenCalled();
     });

     it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => {
-      console.warn = vi.fn();
       mockGit.raw.mockResolvedValue("");
       mockGit.remote.mockRejectedValue("");

       const defaultBranch = await getDefaultBranch();

       expect(defaultBranch).toEqual("refs/heads/main");
-      expect(console.warn).toHaveBeenCalled();
+      expect(mockLogger.warn).toHaveBeenCalled();
     });
   });

diff --git c/tests/tools/test-cases.spec.ts i/tests/tools/test-cases.spec.ts
index 0731b48..696d4bf 100644
--- c/tests/tools/test-cases.spec.ts
+++ i/tests/tools/test-cases.spec.ts
@@ -8,6 +8,7 @@ import { findOctomindFolder } from "../../src/helpers";
 import { client, handleError } from "../../src/tools/client";
 import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml";
 import { deleteTestCase } from "../../src/tools/test-cases";
+import { mockLogger } from "../mocks";

 vi.mock("../../src/tools/client");
 vi.mock("../../src/helpers");
@@ -18,7 +19,7 @@ describe("test-cases", () => {

   beforeEach(() => {
     clientDELETE = vi.mocked(client.DELETE);
-    console.log = vi.fn();
+    mockLogger.info.mockClear();
   });

   describe("deleteTestCase via API", () => {
@@ -36,7 +37,7 @@ describe("test-cases", () => {
         testCaseId: "test-case-id",
       });
       expect(handleError).toHaveBeenCalledWith(undefined);
-      expect(console.log).toHaveBeenCalledWith(
+      expect(mockLogger.info).toHaveBeenCalledWith(
         "Test Case deleted successfully",
       );
     });
@@ -84,7 +85,7 @@ describe("test-cases", () => {
         testCaseId,
       });

-      expect(console.log).toHaveBeenCalledWith(
+      expect(mockLogger.info).toHaveBeenCalledWith(
         "Test Case deleted successfully",
       );
       await expect(fsPromises.access(filePath)).rejects.toThrow();
@@ -98,7 +99,7 @@ describe("test-cases", () => {
         testCaseId: "non-existent-id",
       });

-      expect(console.log).toHaveBeenCalledWith(
+      expect(mockLogger.info).toHaveBeenCalledWith(
         `No test case with id non-existent-id found in folder ${tmpDir}`,
       );
       expect(clientDELETE).not.toHaveBeenCalled();
diff --git c/tests/tools/test-targets.spec.ts i/tests/tools/test-targets.spec.ts
index 92e0bcb..37dadc4 100644
--- c/tests/tools/test-targets.spec.ts
+++ i/tests/tools/test-targets.spec.ts
@@ -6,6 +6,7 @@ import { pushTestTarget } from "../../src/tools";
 import { client } from "../../src/tools/client";
 import { getGitContext } from "../../src/tools/sync/git";
 import { readTestCasesFromDir } from "../../src/tools/sync/yaml";
+import { mockLogger } from "../mocks";

 vi.mock("../../src/helpers");
 vi.mock("../../src/tools/sync/git");
@@ -35,7 +36,7 @@ describe("push", () => {
       error: undefined,
       response: mock(),
     });
-    console.log = vi.fn();
+    mockLogger.info.mockClear();
   });

   it("pushes to main if on default branch", async () => {
@@ -114,7 +115,7 @@ describe("push", () => {
       });

       expect(client.POST).not.toHaveBeenCalled();
-      expect(console.log).toHaveBeenCalledWith("Push cancelled.");
+      expect(mockLogger.info).toHaveBeenCalledWith("Push cancelled.");
     });
   });
 });
diff --git c/tests/tools/yamlMutations/create.spec.ts i/tests/tools/yamlMutations/create.spec.ts
index eadbc73..b1e46dd 100644
--- c/tests/tools/yamlMutations/create.spec.ts
+++ i/tests/tools/yamlMutations/create.spec.ts
@@ -18,6 +18,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation
 import {
   createMockDraftPushResponse,
   createMockSyncTestCase,
+  mockLogger,
 } from "../../mocks";

 vi.mock("open");
@@ -32,7 +33,7 @@ describe("create", () => {
   const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000";

   beforeEach(() => {
-    console.log = vi.fn();
+    mockLogger.info.mockClear();

     vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID);
     vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind");
@@ -110,7 +111,7 @@ describe("create", () => {

     await create({ testTargetId: "someId", name: "Test Name" });

-    expect(console.log).toHaveBeenCalledWith(
+    expect(mockLogger.info).toHaveBeenCalledWith(
       "Cancelled editing test case, exiting",
     );
   });
@@ -118,7 +119,7 @@ describe("create", () => {
   it("exits gracefully when creation is finished", async () => {
     await create({ testTargetId: "someId", name: "Test Name" });

-    expect(console.log).toHaveBeenCalledWith("Created test case successfully");
+    expect(mockLogger.info).toHaveBeenCalledWith("Created test case successfully");
     expect(writeSingleTestCaseYaml).toHaveBeenCalledWith(
       "/mock/.octomind/new-test.yaml",
       expect.objectContaining({ id: GENERATED_TEST_ID }),
diff --git c/tests/tools/yamlMutations/edit.spec.ts i/tests/tools/yamlMutations/edit.spec.ts
index dcb928c..4799668 100644
--- c/tests/tools/yamlMutations/edit.spec.ts
+++ i/tests/tools/yamlMutations/edit.spec.ts
@@ -15,6 +15,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation
 import {
   createMockDraftPushResponse,
   createMockSyncTestCase,
+  mockLogger,
 } from "../../mocks";

 vi.mock("open");
@@ -29,7 +30,7 @@ describe("edit", () => {
   const testCase = createMockSyncTestCase({ id: "test-id" });

   beforeEach(() => {
-    console.log = vi.fn();
+    mockLogger.info.mockClear();

     vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind");
     vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue(
@@ -96,7 +97,7 @@ describe("edit", () => {

     await edit({ testTargetId: "someId", filePath: "test.yaml" });

-    expect(console.log).toHaveBeenCalledWith(
+    expect(mockLogger.info).toHaveBeenCalledWith(
       "Cancelled editing test case, exiting",
     );
   });
@@ -108,7 +109,7 @@ describe("edit", () => {

     await edit({ testTargetId: "someId", filePath: "test.yaml" });

-    expect(console.log).toHaveBeenCalledWith("Edited test case successfully");
+    expect(mockLogger.info).toHaveBeenCalledWith("Edited test case successfully");
   });

   it.each([

diff --git c/package.json i/package.json
index 12b46dd..3eb85d8 100644
--- c/package.json
+++ i/package.json
@@ -4,7 +4,13 @@
   "description": "a command line client for octomind apis",
   "main": "./dist/index.js",
   "packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48",
-  "files": ["dist", "src", "README.md", "LICENSE", "package.json"],
+  "files": [
+    "dist",
+    "src",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
   "engines": {
     "node": ">=22.0.0"
   },
@@ -46,6 +52,8 @@
     "openapi-fetch": "0.15.0",
     "ora": "9.0.0",
     "otplib": "13.1.1",
+    "pino": "10.2.0",
+    "pino-pretty": "13.1.3",
     "shell-quote": "1.8.3",
     "simple-git": "3.30.0",
     "tabtab": "3.0.2",
diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml
index 43f…
diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml
index 619d022..31c08e7 100644
--- c/pnpm-lock.yaml
+++ i/pnpm-lock.yaml
@@ -35,9 +35,6 @@ importers:
       pino:
         specifier: 10.2.0
         version: 10.2.0
-      pino-pretty:
-        specifier: 13.1.3
-        version: 13.1.3
       shell-quote:
         specifier: 1.8.3
         version: 1.8.3
@@ -950,9 +947,6 @@ packages:
   colorette@1.4.0:
     resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}

-  colorette@2.0.20:
-    resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
-
   commander@14.0.2:
     resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
     engines: {node: '>=20'}
@@ -971,9 +965,6 @@ packages:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}

-  dateformat@4.6.3:
-    resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
-
   debug@4.4.1:
     resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
     engines: {node: '>=6.0'}
@@ -1016,9 +1007,6 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true

-  end-of-stream@1.4.5:
-    resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
-
   enquirer@2.4.1:
     resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
     engines: {node: '>=8.6'}
@@ -1066,9 +1054,6 @@ packages:
     resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
     engines: {node: '>=4'}

-  fast-copy@4.0.2:
-    resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==}
-
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}

@@ -1076,9 +1061,6 @@ packages:
     resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
     engines: {node: '>=8.6.0'}

-  fast-safe-stringify@2.1.1:
-    resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
-
   fast-uri@3.1.0:
     resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}

@@ -1168,9 +1150,6 @@ packages:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}

-  help-me@5.0.0:
-    resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
-
   https-proxy-agent@7.0.6:
     resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
     engines: {node: '>= 14'}
@@ -1271,10 +1250,6 @@ packages:
     resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
     hasBin: true

-  joycon@3.1.1:
-    resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
-    engines: {node: '>=10'}
-
   js-levenshtein@1.1.6:
     resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
     engines: {node: '>=0.10.0'}
@@ -1383,9 +1358,6 @@ packages:
     resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
     engines: {node: '>=14.0.0'}

-  once@1.4.0:
-    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
-
   onetime@2.0.1:
     resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==}
     engines: {node: '>=4'}
@@ -1474,10 +1446,6 @@ packages:
   pino-abstract-transport@3.0.0:
     resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}

-  pino-pretty@13.1.3:
-    resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==}
-    hasBin: true
-
   pino-std-serializers@7.1.0:
     resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}

@@ -1517,9 +1485,6 @@ packages:
   process-warning@5.0.0:
     resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}

-  pump@3.0.3:
-    resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
-
   punycode.js@2.3.1:
     resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
     engines: {node: '>=6'}
@@ -1593,9 +1558,6 @@ packages:
   safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}

-  secure-json-parse@4.1.0:
-    resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
-
   shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
@@ -1681,10 +1643,6 @@ packages:
     resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
     engines: {node: '>=18'}

-  strip-json-comments@5.0.3:
-    resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
-    engines: {node: '>=14.16'}
-
   supports-color@10.2.2:
     resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
     engines: {node: '>=18'}
@@ -1911,9 +1869,6 @@ packages:
     engines: {node: '>=8'}
     hasBin: true

-  wrappy@1.0.2:
-    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-
   wsl-utils@0.3.1:
     resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
     engines: {node: '>=20'}
@@ -2636,8 +2591,6 @@ snapshots:

   colorette@1.4.0: {}

-  colorette@2.0.20: {}
-
   commander@14.0.2: {}

   commander@7.2.0: {}
@@ -2652,8 +2605,6 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2

-  dateformat@4.6.3: {}
-
   debug@4.4.1:
     dependencies:
       ms: 2.1.3
@@ -2683,10 +2634,6 @@ snapshots:
     dependencies:
       jake: 10.9.4

-  end-of-stream@1.4.5:
-    dependencies:
-      once: 1.4.0
-
   enquirer@2.4.1:
     dependencies:
       ansi-colors: 4.1.3
@@ -2787,8 +2734,6 @@ snapshots:
       iconv-lite: 0.4.24
       tmp: 0.0.33

-  fast-copy@4.0.2: {}
-
   fast-deep-equal@3.1.3: {}

   fast-glob@3.3.3:
@@ -2799,8 +2744,6 @@ snapshots:
       merge2: 1.4.1
       micromatch: 4.0.8

-  fast-safe-stringify@2.1.1: {}
-
   fast-uri@3.1.0: {}

   fastq@1.20.1:
@@ -2890,8 +2833,6 @@ snapshots:

   has-flag@3.0.0: {}

-  help-me@5.0.0: {}
-
   https-proxy-agent@7.0.6(supports-color@10.2.2):
     dependencies:
       agent-base: 7.1.4
@@ -2973,8 +2914,6 @@ snapshots:

   jiti@2.6.1: {}

-  joycon@3.1.1: {}
-
   js-levenshtein@1.1.6: {}

   js-tokens@4.0.0: {}
@@ -3069,10 +3008,6 @@ snapshots:

   on-exit-leak-free@2.1.2: {}

-  once@1.4.0:
-    dependencies:
-      wrappy: 1.0.2
-
   onetime@2.0.1:
     dependencies:
       mimic-fn: 1.2.0
@@ -3202,22 +3137,6 @@ snapshots:
     dependencies:
       split2: 4.2.0

-  pino-pretty@13.1.3:
-    dependencies:
-      colorette: 2.0.20
-      dateformat: 4.6.3
-      fast-copy: 4.0.2
-      fast-safe-stringify: 2.1.1
-      help-me: 5.0.0
-      joycon: 3.1.1
-      minimist: 1.2.8
-      on-exit-leak-free: 2.1.2
-      pino-abstract-transport: 3.0.0
-      pump: 3.0.3
-      secure-json-parse: 4.1.0
-      sonic-boom: 4.2.0
-      strip-json-comments: 5.0.3
-
   pino-std-serializers@7.1.0: {}

   pino@10.2.0:
@@ -3260,11 +3179,6 @@ snapshots:

   process-warning@5.0.0: {}

-  pump@3.0.3:
-    dependencies:
-      end-of-stream: 1.4.5
-      once: 1.4.0
-
   punycode.js@2.3.1: {}

   queue-microtask@1.2.3: {}
@@ -3352,8 +3266,6 @@ snapshots:

   safer-buffer@2.1.2: {}

-  secure-json-parse@4.1.0: {}
-
   shebang-command@2.0.0:
     dependencies:
       shebang-regex: 3.0.0
@@ -3426,8 +3338,6 @@ snapshots:

   strip-final-newline@4.0.0: {}

-  strip-json-comments@5.0.3: {}
-
   supports-color@10.2.2: {}

   supports-color@5.5.0:
@@ -3612,8 +3522,6 @@ snapshots:
       siginfo: 2.0.0
       stackback: 0.0.2

-  wrappy@1.0.2: {}
-
   wsl-utils@0.3.1:
     dependencies:
       is-wsl: 3.1.0
diff --git c/src/logger.ts i/src/logger.ts
index 946ed95..3181857 100644
--- c/src/logger.ts
+++ i/src/logger.ts
@@ -1,15 +1,5 @@
 import pino from "pino";

-const isProduction = process.env.NODE_ENV === "production";
-
 export const logger = pino({
   level: process.env.LOG_LEVEL || "info",
-  transport: isProduction
-    ? undefined
-    : {
-        target: "pino-pretty",
-        options: {
-          colorize: true,
-        },
-      },
 });
@Germandrummer92
Germandrummer92 force-pushed the feature/colored-logging branch from fb41339 to 711db0c Compare January 22, 2026 14:22
@Germandrummer92
Germandrummer92 merged commit ae9787e into main Jan 22, 2026
3 checks passed
@Germandrummer92
Germandrummer92 deleted the feature/colored-logging branch January 22, 2026 14:24
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants