Skip to content

Add verbose output with tiered logging levels (-v and -vv)#3

Merged
wcole1-godaddy merged 4 commits intocodex/agent-first-cli-retrofitfrom
jundai/add-verbose-flag-alias
Feb 23, 2026
Merged

Add verbose output with tiered logging levels (-v and -vv)#3
wcole1-godaddy merged 4 commits intocodex/agent-first-cli-retrofitfrom
jundai/add-verbose-flag-alias

Conversation

@jundai-godaddy
Copy link
Copy Markdown
Collaborator

Summary

This PR adds comprehensive verbose output support with two levels of verbosity, along with fixes for ES module compatibility issues that were preventing verbose mode from working.

Features Added

Tiered Verbosity Levels

  • Level 1 (-v, --info): Basic output showing HTTP method, URL, status code, and duration
    → POST https://api.godaddy.com/v2/oauth2/token
    ← 200 POST https://api.godaddy.com/v2/oauth2/token (234ms)
  • Level 2 (-vv, --debug): Full output including headers, request/response bodies with automatic sensitive field redaction
    [Full JSON output with headers, request body, response body]

Automatic HTTP Logging

  • Introduced loggedFetch() wrapper that automatically logs HTTP requests
  • Extracts logging details from actual requests/responses, eliminating hardcoded duplication
  • Automatic timing and status code capture
  • Smart JSON response parsing for level 2 verbosity
  • No need to manually specify method/status in logging calls - prevents drift between code and logs

Security Enhancements

  • Automatic redaction of access_token and accessToken fields in response logs
  • Recursive redaction for nested objects
  • Follows existing redaction patterns for Authorization headers
  • Keeps logs clean and secure while still being useful for debugging

Bug Fixes

ES Module Compatibility Issues

  • Fixed __dirname is not defined error: Added __dirname and __filename shims in require-shim.js for dependencies expecting CommonJS globals
  • Fixed worker thread error: Marked pino-pretty as external dependency to avoid bundling worker thread code that breaks at runtime
  • Fixed pino transport issue: Changed from pino transport API to direct stream usage to eliminate worker thread dependency

These fixes ensure that verbose mode actually works when the CLI is installed globally.

CLI Options

  • -v, --verbose: Incrementing flag (can be used multiple times: -vv)
  • --info: Alias for level 1 verbosity (-v)
  • --debug: Alias for level 2 verbosity (-vv)

Examples

# Basic logging - just URL, status, and time
godaddy -v auth login
godaddy --info auth login

# Full logging - headers, bodies, everything
godaddy -vv auth login
godaddy --debug auth login

# Multiple v flags also work
godaddy -vv auth login

Implementation Highlights

  • DRY principle: Single loggedFetch() wrapper handles all HTTP logging
  • No hardcoded values: Method/status extracted from actual requests/responses
  • Level-aware: Only parses JSON bodies when verbosity level requires it
  • Visual feedback: Shows (verbose output enabled) or (verbose output enabled: full details) when enabled

Testing

Tested with:

  • godaddy -v auth login - Basic logging works
  • godaddy -vv auth login - Full logging works with redacted tokens
  • godaddy --info env get - Alias works
  • godaddy --debug env get - Alias works
  • No worker thread errors when installed globally

🤖 Generated with Claude Code

logger.debug(`→ ${options.method} ${options.url}`);
}
};

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to oauthTokenUrl
as clear text.

Copilot Autofix

AI about 1 month ago

To fix this, we should avoid logging potentially sensitive URLs in clear text, especially for OAuth token exchanges, while preserving the usefulness of HTTP logs. The minimal, backward-compatible change is to sanitize or truncate URLs before including them in log messages. A common pattern is to log only the path and host (or even just a label) and to strip query parameters entirely, since they frequently contain secrets.

Concretely, in src/services/logger.ts, we can introduce a small helper function (within the snippet shown) such as sanitizeUrlForLogging(url: string): string that attempts to parse the URL and returns a version with no query string or fragment, and falls back to the original string if parsing fails. Then we adjust logHttpRequest and logHttpResponse to use this sanitized URL string in their human-readable messages (→ ... and ← ...) instead of the raw options.url. The structured log fields (url: options.url) can reasonably continue to store the full URL if the logging backend is configured with appropriate redaction or access control, but if you want stricter protection you can swap those to the sanitized URL as well; here, to minimize functionality changes while addressing CodeQL’s complaint (which targets the string interpolation sink), we will sanitize only the interpolated strings.

Implementation details:

  • Add a sanitizeUrlForLogging function somewhere above logHttpRequest in src/services/logger.ts. It will:
    • Try new URL(url) (which is globally available in Node 18+ and modern JS runtimes; no new imports needed).
    • Return ${parsed.origin}${parsed.pathname} (no search, no hash).
    • On failure, return the original url to avoid breaking logging on unusual inputs.
  • Update:
    • In logHttpRequest, for both verbosity branches, replace options.url in template strings with sanitizeUrlForLogging(options.url).
    • In logHttpResponse, do the same in both template strings.
  • No external dependencies are required; we use the built-in URL class.
Suggested changeset 1
src/services/logger.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/services/logger.ts b/src/services/logger.ts
--- a/src/services/logger.ts
+++ b/src/services/logger.ts
@@ -54,6 +54,15 @@
 
 export const getLogger = () => logger;
 
+const sanitizeUrlForLogging = (url: string): string => {
+	try {
+		const parsed = new URL(url);
+		return `${parsed.origin}${parsed.pathname}`;
+	} catch {
+		return url;
+	}
+};
+
 // HTTP request logging utilities
 export const logHttpRequest = (options: {
 	method: string;
@@ -61,6 +70,7 @@
 	headers?: Record<string, string>;
 	body?: unknown;
 }) => {
+	const safeUrl = sanitizeUrlForLogging(options.url);
 	if (verbosityLevel >= 2) {
 		logger.debug(
 			{
@@ -70,10 +80,10 @@
 				headers: options.headers,
 				body: options.body,
 			},
-			`→ ${options.method} ${options.url}`,
+			`→ ${options.method} ${safeUrl}`,
 		);
 	} else if (verbosityLevel === 1) {
-		logger.debug(`→ ${options.method} ${options.url}`);
+		logger.debug(`→ ${options.method} ${safeUrl}`);
 	}
 };
 
@@ -86,6 +94,7 @@
 	body?: unknown;
 	duration?: number;
 }) => {
+	const safeUrl = sanitizeUrlForLogging(options.url);
 	if (verbosityLevel >= 2) {
 		logger.debug(
 			{
@@ -98,13 +107,13 @@
 				body: options.body,
 				duration: options.duration,
 			},
-			`← ${options.status} ${options.method} ${options.url} ${
+			`← ${options.status} ${options.method} ${safeUrl} ${
 				options.duration ? `(${options.duration}ms)` : ""
 			}`,
 		);
 	} else if (verbosityLevel === 1) {
 		logger.debug(
-			`← ${options.status} ${options.method} ${options.url} ${
+			`← ${options.status} ${options.method} ${safeUrl} ${
 				options.duration ? `(${options.duration}ms)` : ""
 			}`,
 		);
EOF
@@ -54,6 +54,15 @@

export const getLogger = () => logger;

const sanitizeUrlForLogging = (url: string): string => {
try {
const parsed = new URL(url);
return `${parsed.origin}${parsed.pathname}`;
} catch {
return url;
}
};

// HTTP request logging utilities
export const logHttpRequest = (options: {
method: string;
@@ -61,6 +70,7 @@
headers?: Record<string, string>;
body?: unknown;
}) => {
const safeUrl = sanitizeUrlForLogging(options.url);
if (verbosityLevel >= 2) {
logger.debug(
{
@@ -70,10 +80,10 @@
headers: options.headers,
body: options.body,
},
`→ ${options.method} ${options.url}`,
`→ ${options.method} ${safeUrl}`,
);
} else if (verbosityLevel === 1) {
logger.debug(`→ ${options.method} ${options.url}`);
logger.debug(`→ ${options.method} ${safeUrl}`);
}
};

@@ -86,6 +94,7 @@
body?: unknown;
duration?: number;
}) => {
const safeUrl = sanitizeUrlForLogging(options.url);
if (verbosityLevel >= 2) {
logger.debug(
{
@@ -98,13 +107,13 @@
body: options.body,
duration: options.duration,
},
`← ${options.status} ${options.method} ${options.url} ${
`← ${options.status} ${options.method} ${safeUrl} ${
options.duration ? `(${options.duration}ms)` : ""
}`,
);
} else if (verbosityLevel === 1) {
logger.debug(
`← ${options.status} ${options.method} ${options.url} ${
`← ${options.status} ${options.method} ${safeUrl} ${
options.duration ? `(${options.duration}ms)` : ""
}`,
);
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +113 to +115

if (Array.isArray(obj)) {
return obj.map(redactSensitiveFields);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to oauthTokenUrl
as clear text.

Copilot Autofix

AI about 1 month ago

General fix approach: avoid logging sensitive data directly; for HTTP logs, that usually means (a) redacting or omitting tokens and secrets in headers, bodies, and URLs, and (b) logging only non-sensitive parts (e.g., method, status, host, and path) or a sanitized URL. In this case, the specific problem is that the string interpolation in logHttpResponse includes options.url unchanged.

Best fix in this codebase without changing behavior more than necessary:

  1. Keep the structured log object unchanged (it already includes url and the redacted body), because pino’s redaction and redactSensitiveFields handle JSON data; this may still contain url but structured logs are already somewhat controlled.
  2. Change the human-readable message strings used in logger.debug for responses to omit or sanitize the URL, so no clear-text token-bearing URLs appear in logs.
  3. Reuse that same sanitized format for both verbosity branches in logHttpResponse to keep behavior consistent.
  4. Optionally (and very low impact) also adjust the request-side message (logHttpRequest) similarly for symmetry, but the specific CodeQL alert is on logHttpResponse, so the minimum required harmless change is there.

Concretely:

  • In src/services/logger.ts, in logHttpResponse, replace:
    • The message template in the verbosityLevel >= 2 branch from
      `← ${options.status} ${options.method} ${options.url} ${...}`
      to something like
      `← ${options.status} ${options.method} ${options.duration ? ... : ""}`
      (no URL).
    • The message template in the verbosityLevel === 1 branch similarly, omitting options.url.
  • No new imports or helpers are needed; we only modify the interpolated string.

This preserves the core functionality (status, method, and latency logging) while removing the sensitive url from the log message.


Suggested changeset 1
src/services/logger.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/services/logger.ts b/src/services/logger.ts
--- a/src/services/logger.ts
+++ b/src/services/logger.ts
@@ -98,13 +98,13 @@
 				body: options.body,
 				duration: options.duration,
 			},
-			`← ${options.status} ${options.method} ${options.url} ${
+			`← ${options.status} ${options.method} ${
 				options.duration ? `(${options.duration}ms)` : ""
 			}`,
 		);
 	} else if (verbosityLevel === 1) {
 		logger.debug(
-			`← ${options.status} ${options.method} ${options.url} ${
+			`← ${options.status} ${options.method} ${
 				options.duration ? `(${options.duration}ms)` : ""
 			}`,
 		);
EOF
@@ -98,13 +98,13 @@
body: options.body,
duration: options.duration,
},
`← ${options.status} ${options.method} ${options.url} ${
`← ${options.status} ${options.method} ${
options.duration ? `(${options.duration}ms)` : ""
}`,
);
} else if (verbosityLevel === 1) {
logger.debug(
`← ${options.status} ${options.method} ${options.url} ${
`← ${options.status} ${options.method} ${
options.duration ? `(${options.duration}ms)` : ""
}`,
);
Copilot is powered by AI and may make mistakes. Always verify output.
jundai-godaddy and others added 2 commits February 22, 2026 20:19
Added standard -v and --verbose flags for enabling verbose output, while
keeping --debug as an alias. This follows common CLI conventions where -v
is the standard flag for verbose logging.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit adds comprehensive verbose output support with two levels of
verbosity, along with fixes for ES module compatibility issues.

- **Level 1 (-v, --info)**: Basic output showing HTTP method, URL, status,
  and duration
- **Level 2 (-vv, --debug)**: Full output including headers, request/response
  bodies with sensitive field redaction

- Introduced `loggedFetch()` wrapper that automatically logs HTTP requests
- Extracts logging details from actual requests/responses, eliminating
  hardcoded duplication
- Automatic timing and status code capture
- Smart JSON response parsing for level 2 verbosity

- Automatic redaction of `access_token` and `accessToken` fields in logs
- Recursive redaction for nested objects
- Follows existing redaction patterns for Authorization headers

- Added `__dirname` and `__filename` shims in require-shim.js for
  dependencies expecting CommonJS globals
- Marked pino-pretty as external dependency to avoid bundling worker thread
  code that breaks at runtime
- Changed from pino transport API to direct stream usage to eliminate worker
  thread dependency

- Added visual indicator when verbose output is enabled
- Clear messaging distinguishing between basic and full verbosity levels

- `-v, --verbose`: Incrementing flag (use multiple times for higher levels)
- `--info`: Alias for level 1 verbosity
- `--debug`: Alias for level 2 verbosity

- DRY principle: Single `loggedFetch()` wrapper handles all HTTP logging
- No hardcoded method/status values in logging calls
- Automatic body parsing based on content-type
- Level-aware logging prevents unnecessary work at lower verbosity

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@wcole1-godaddy wcole1-godaddy force-pushed the jundai/add-verbose-flag-alias branch from 91bbd50 to 9d5fb5e Compare February 23, 2026 01:22
@wcole1-godaddy wcole1-godaddy changed the base branch from main to codex/agent-first-cli-retrofit February 23, 2026 01:23
@wcole1-godaddy wcole1-godaddy merged commit 342cdcd into codex/agent-first-cli-retrofit Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants