Skip to content

release: 0.1.0-alpha.1 - #148

Closed
stainless-app[bot] wants to merge 16 commits into
mainfrom
release-please--branches--main--changes--next--components--terminal49
Closed

release: 0.1.0-alpha.1#148
stainless-app[bot] wants to merge 16 commits into
mainfrom
release-please--branches--main--changes--next--components--terminal49

Conversation

@stainless-app

@stainless-app stainless-app Bot commented Oct 23, 2025

Copy link
Copy Markdown

Automated Release PR

0.1.0-alpha.1 (2025-10-23)

Full Changelog: v0.0.1-alpha.0...v0.1.0-alpha.1

Features

  • routing endpoints (#137) (444d73a)
  • Update API documentation with consistent code style (6feca12)

Bug Fixes

  • mcp: fix cli argument parsing logic (ede6ca2)
  • mcp: resolve a linting issue in server code (b15b8ce)

Performance Improvements

Chores

  • Auto-generate Postman collection from openapi.json [skip ci] (0014457)
  • Auto-generate Postman collection from openapi.json [skip ci] (2de5f6d)
  • Auto-generate Postman collection from openapi.json [skip ci] (b187f8b)
  • Auto-generate Postman collection from openapi.json [skip ci] (ee25375)
  • Auto-generate Postman collection from openapi.json [skip ci] (dfdc873)
  • Auto-generate Postman collection from openapi.json [skip ci] (3c2edec)
  • consistent documentation in define Authorization token (41e65e3)
  • docs: add Kimmie's suggestions (24c8beb)
  • docs: add map styling guide (2d652db)
  • docs: embedding the map (d5ee3c5)
  • docs: remove FAQ (27f7446)
  • docs: remove frontmatter (829a486)
  • docs: tell users to reach out to support for a publishable api key (275c651)
  • extract some types in mcp docs (c401b96)
  • internal: codegen related update (f84ecdf)
  • internal: fix incremental formatting in some cases (38dfa5d)
  • internal: ignore .eslintcache (a3a91d5)
  • internal: remove .eslintcache (4c240a6)
  • internal: remove deprecated compilerOptions.baseUrl from tsconfig.json (2dc6c24)
  • internal: use npm pack for build uploads (9053cb7)
  • jsdoc: fix @link annotations to refer only to parts of the package‘s public interface (30fcefa)
  • mcp: allow pointing docs_search tool at other URLs (7203f41)
  • sync repo (d6d221b)
  • Update authentication header in API documentation (e5a16d8)
  • Update links in documentation to use relative paths (787edf5)
  • update lockfile (8fe064e)
  • update old documentation links to be relatives (b2d37fa)
  • update SDK settings (18778c3)

Documentation

  • fix relative paths removing "/docs" (17234ba)

Refactors

  • docs: rename assets to terminal49-map (a3304fc)

This pull request is managed by Stainless's GitHub App.

The semver version number is based on included commit messages. Alternatively, you can manually set the version number in the title of this pull request.

For a better experience, it is recommended to use either rebase-merge or squash-merge when merging this pull request.

🔗 Stainless website
📚 Read the docs
🙋 Reach out for help or questions

@vercel

vercel Bot commented Oct 23, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
api Error Error Oct 23, 2025 2:02am

@macroscopeapp

macroscopeapp Bot commented Oct 23, 2025

Copy link
Copy Markdown

Release version 0.1.0-alpha.1 and add a Terminal49 TypeScript client with MCP server CLI, tools, API resources, tests, and CI workflows

This release introduces a generated TypeScript API client for Terminal49, a packaged MCP server with a CLI and transport options, and a full set of API resources and MCP tools for key endpoints; it also adds testing, build scripts, and CI workflows with distribution configs and documentation.

  • Add Terminal49 client entrypoints, core modules, and resource implementations with pagination and error types in src.
  • Add MCP server package with CLI, stdio/HTTP transports, dynamic/code/docs tools, capability filtering, and endpoint-specific tool handlers in packages/mcp-server.
  • Add CI workflows for lint, build, test and release checks in .github/workflows.
  • Add tests for internal utilities, client behavior, and MCP server tooling under tests and packages/mcp-server/tests.
  • Add build, publish, and formatting scripts and TypeScript configs; add package manifests and lockfiles; add version constant set to 0.1.0-alpha.1.
  • Add documentation including README, API reference, contributing, security, changelog, and license.
  • Delete docs/images/4_port_route.png and expand ignore files.

Key files: src/index.ts, src/client.ts, packages/mcp-server/src/index.ts, packages/mcp-server/src/server.ts, packages/mcp-server/src/tools/index.ts, package.json, .github/workflows/ci.yml

📍Where to Start

Start with the MCP server CLI entrypoint main in packages/mcp-server/src/index.ts to see option parsing and server launch flow, then review server initialization in newMcpServer within packages/mcp-server/src/server.ts and tool registration in packages/mcp-server/src/tools/index.ts.


📊 Macroscope summarized 8fac8a8. 12 files reviewed, 47 issues evaluated, 28 issues filtered, 12 comments posted

🗂️ Filtered Issues

jest.config.ts — 0 comments posted, 3 evaluated, 3 filtered
  • line 4: Misconfigured Jest transformers: the config sets preset: 'ts-jest/presets/default-esm' (which configures Jest for ESM with ts-jest) but also overrides transform to use @swc/jest for ^.+\.(t|j)sx?$. This creates a contract mismatch: the default-esm preset marks .ts files as ESM and expects ts-jest’s ESM transformer, while @swc/jest will be used instead. At runtime this can lead to ESM/CJS incompatibilities (e.g., Cannot use import statement outside a module, SyntaxError: Named export not found, or unresolved ESM loader behaviors), and it also means any ts-jest-specific features (TS diagnostics, globals['ts-jest'] options, path mapping integration) will not apply even though the preset suggests they will. If you intend to use @swc/jest, remove the ts-jest preset and explicitly configure ESM handling for swc; if you intend to use ts-jest, remove the @swc/jest transform. [ Low confidence ]
  • line 7: @swc/jest is used without ESM and TSX/JSX configuration. With preset: 'ts-jest/presets/default-esm', Jest treats .ts files as ESM. However, the @swc/jest transformer is invoked with only { sourceMaps: 'inline' }. Without explicit module: { type: 'es6' } (or equivalent) and appropriate parser settings for TypeScript/TSX/JSX, swc may emit CommonJS by default and/or fail to parse TSX/JSX files. This mismatch commonly causes runtime errors like Cannot use import statement outside a module or parse errors for TSX. If sticking with @swc/jest, configure it to emit ESM for .ts/.tsx and enable the TypeScript/TSX parser; alternatively use the ts-jest transformer that the preset expects. [ Low confidence ]
  • line 20: Overbroad testPathIgnorePatterns: ['scripts'] will ignore any test whose path contains the substring scripts anywhere, potentially skipping unrelated tests unintentionally (e.g., a folder feature-scripts-tests/). Jest treats these as regex patterns. If the intent was to ignore a top-level scripts/ directory, prefer ['<rootDir>/scripts/'] or a more precise regex like ['/scripts/']. As written, valid tests can silently not run, changing externally visible behavior of the test suite. [ Low confidence ]
scripts/utils/check-is-in-git-install.sh — 0 comments posted, 1 evaluated, 1 filtered
  • line 4: The comment describing the npm cache path appears to contradict the implementation. The comment says npm uses a path like $HOME/.npm/_cacache/git-cloneXXXXXX, whose parent directory basename would be _cacache, not tmp. However, the code checks [ "$(basename "$(dirname "$PWD")")" = 'tmp' ]. This mismatch creates uncertainty about whether the check is correct for npm's actual directory layout. If npm indeed nests clones under _cacache/tmp/git-cloneXXXXXX, the code would be correct but the comment is misleading; if npm does not use tmp, the check will incorrectly fail. Align the comment with the actual expected parent directory (e.g., _cacache/tmp) or adjust the condition to match the documented path, so that the intended contract is clear and the check remains accurate. [ Low confidence ]
scripts/utils/git-swap.sh — 1 comment posted, 4 evaluated, 2 filtered
  • line 7: The script assumes it is executed from the repository root. If run from another working directory, find . will delete everything in that directory except dist and node_modules, and subsequent mv/rmdir will operate on whatever ./dist happens to be there. No guards enforce or verify the intended working directory, causing potential catastrophic data loss outside the repo. [ Low confidence ]
  • line 10: The mv dist/* . glob does not include dotfiles (e.g., .env, .npmrc, .eslintrc) and will therefore leave hidden files in dist. As a result, rmdir dist will fail because the directory is not empty (causing exit due to set -e). Additionally, if dist is empty, in Bash without nullglob enabled, dist/* remains a literal and mv errors with "cannot stat 'dist/*'", which will also abort due to set -e. This leads to incomplete moves and non-deterministic failures. [ Low confidence ]
scripts/utils/upload-artifact.sh — 1 comment posted, 10 evaluated, 9 filtered
  • line 2: The script uses set -u but does not validate that required environment variables URL, AUTH, and SHA are set before use. With set -u, expanding an unset variable causes the script to exit immediately. This can cause abrupt termination before any user-friendly error if URL or AUTH are missing (at lines 4–6), and can also cause the success path to terminate when echoing SHA (line 23). Add explicit checks (e.g., : "${URL:?}") or friendly guard messages for all required env vars. [ Low confidence ]
  • line 4: The curl -X POST request declares Content-Type: application/json but sends no request body. If the API expects a JSON payload, this may fail and return no .url. Provide the required JSON body (e.g., -d '{...}') or remove/adjust the content type to match the server contract. [ Low confidence ]
  • line 8: The script assumes jq is installed and available. If jq is missing, jq will fail and due to set -e the script will exit without a clear message. Add a dependency check (e.g., command -v jq >/dev/null || { echo "jq is required"; exit 1; }). [ Low confidence ]
  • line 10: The check for SIGNED_URL only treats the literal string "null" as failure. It does not handle an empty string or non-HTTP value. If .url is "" or malformed, the script proceeds to upload and fails later. Strengthen the guard to ensure SIGNED_URL is non-empty and matches an expected scheme (e.g., [[ -n "$SIGNED_URL" && "$SIGNED_URL" =~ ^https?:// ]]). [ Low confidence ]
  • line 15: npm pack is executed in dist without verifying that the directory exists or contains a valid package to pack. If dist is missing or not an npm package, the command fails and the script exits. Add a guard to ensure dist exists and optionally run npm pack from the project root, moving the resulting tarball to dist. [ Low confidence ]
  • line 17: curl does not follow redirects for non-GET methods unless -L is specified. If the signed URL responds with a 3xx (e.g., 307 Temporary Redirect) to an upload endpoint, the PUT will not follow and will fail. Add -L to the PUT request to follow redirects: curl -v -L -X PUT .... [ Low confidence ]
  • line 18: The upload sets Content-Type: application/gzip for an npm .tgz tarball. While it is gzip-compressed, many storage services (e.g., S3) expect application/octet-stream or application/x-tar for such files. If the signed URL enforces a specific content type, this mismatch can cause rejection. Make the content type configurable or align with expected type. [ Low confidence ]
  • line 21: Success detection only matches HTTP/[0-9.]* 200. Some services return other success codes for PUT uploads (e.g., 201 Created or 204 No Content). The current check will treat these successful uploads as failures. Broaden the match to include common success codes (e.g., grep -Eq 'HTTP/[0-9.]* (200|201|204)'). [ Low confidence ]
  • line 23: Referencing SHA in the success message without ensuring it is set can cause the script to exit due to set -u, even after a successful upload, resulting in a non-zero termination and possibly hiding the success from callers. Guard SHA or avoid using it if unset (e.g., if [[ -n "${SHA:-}" ]]; then echo ...; fi). [ Low confidence ]
src/client.ts — 7 comments posted, 12 evaluated, 5 filtered
  • line 125: ClientOptions.apiKey documentation implies the value should be the full Authorization header value with the Token prefix (e.g., Token YOUR_API_KEY), while the implementation builds the header as Authorization: <apiKey>. Elsewhere, the error message example suggests passing a raw API key ('My API Key'). This contradiction can cause runtime authentication failures if users pass a raw key per the example, resulting in a missing Token prefix. Align documentation and implementation or add the Token prefix automatically when not present. [ Low confidence ]
  • line 323: authHeaders constructs the Authorization header as just the API key value ({ Authorization: this.apiKey }) without the required scheme/prefix. Documentation in ClientOptions states the correct header format is Authorization: Token YOUR_API_KEY. Omitting the 'Token ' prefix will cause authentication to fail at runtime for endpoints expecting this scheme. [ Low confidence ]
  • line 417: methodRequest() spreads opts into an object literal without guarding against undefined or null: return { method, path, ...opts };. Because opts is optional (opts?: PromiseOrValue<RequestOptions>), calls like client.get('/path') will resolve opts to undefined, causing a runtime TypeError: Cannot convert undefined or null to object when object-spreading. This breaks the common case of calling HTTP methods without options. [ Low confidence ]
  • line 711: Unsafe non-null assertion for path leading to runtime failure: buildRequest calls this.buildURL(path!, ...) assuming path is always present. While the convenience methods (get, post, etc.) ensure a string path, callers can invoke request() directly and pass a FinalRequestOptions without a path. In that case, path is undefined and buildURL will throw at runtime when operating on a non-string. Add an explicit validation for path and throw a controlled error (e.g., Terminal49Error) before using it. [ Low confidence ]
  • line 724: Critical fields can be unintentionally overridden by user-provided fetchOptions due to unsafe spread with as any: In buildRequest, the request is constructed with method, headers, signal, and body, but afterwards ...(this.fetchOptions as any) and ...(options.fetchOptions as any) are spread. Because of the as any casts, a user can supply method, headers, body, or signal inside fetchOptions, which will override the carefully constructed values, breaking invariants (e.g., wrong HTTP method casing, missing auth/idempotency headers, or loss of user-specified cancellation signal wiring). This can lead to subtle runtime failures, incorrect authentication, or logging assumptions about req.headers being a Headers instance. To fix, defensively omit disallowed keys from the spreads (e.g., destructure and discard method, headers, body, signal) or enforce the MergedRequestInit type at runtime by filtering keys. [ Low confidence ]
src/core/error.ts — 1 comment posted, 5 evaluated, 3 filtered
  • line 5: Subclassing Error without setting a custom name leads to instances having error.name === 'Error' rather than the subclass name. With Terminal49Error defined as export class Terminal49Error extends Error {}, any subclass like APIError that doesn't set this.name will also inherit 'Error' as the name. This can cause incorrect runtime behavior if logging, error filtering, or handling relies on error.name to distinguish error types. Consider setting this.name = 'Terminal49Error' in a constructor or using this.name = new.target.name. [ Low confidence ]
  • line 96: The constructor destructuring constructor({ message }: { message?: string } = {}) will throw a runtime TypeError if called with null or a non-object at runtime (e.g., from JavaScript or from improperly typed TS), because destructuring null/non-object is invalid. This causes a crash rather than a graceful default message. A safer pattern is constructor(arg?: { message?: string }) { const message = arg?.message; ... }. [ Low confidence ]
  • line 97: Passing message: '' (empty string) to APIUserAbortError results in the default message 'Request was aborted.' because the constructor uses message || 'Request was aborted.'. This prevents callers from intentionally setting an empty message and may unexpectedly override falsy but intentional values. [ Code style ]
src/internal/shims.ts — 2 comments posted, 9 evaluated, 5 filtered
  • line 35: The guard in makeReadableStream only checks whether globalThis.ReadableStream is undefined. If ReadableStream exists but is not a constructible function (e.g., incorrectly polyfilled to a non-constructor), new ReadableStream(...args) will throw a generic TypeError at runtime without a clear message. Strengthening the guard to verify it's a function/constructor (e.g., typeof ReadableStream === 'function') or catching and rethrowing with a clearer error would prevent unclear runtime failures. [ Low confidence ]
  • line 65: No input validation: accessing stream[Symbol.asyncIterator] and stream.getReader() assumes stream is a non-null object with these properties. If stream is null, undefined, or a non-stream object, this will throw a TypeError at runtime. Given the parameter type is any and there are no guards, such inputs are reachable. [ Low confidence ]
  • line 95: CancelReadableStream lets any errors thrown during cancellation propagate. In the retry path (makeRequest), this function is awaited before scheduling a retry. If cancellation throws (e.g., an underlying stream bug, a disturbed/locked stream, or an iterator return() rejection), the retry will be aborted and the error will surface, violating the intended best-effort cleanup semantics. Wrap the cancellation logic in a try/catch and swallow/log errors to ensure the retry proceeds. [ Low confidence ]
  • line 98: CancelReadableStream checks if (stream[Symbol.asyncIterator]) and then calls stream[Symbol.asyncIterator]() without verifying it is callable. If stream has a truthy Symbol.asyncIterator property that is not a function, this will throw a TypeError at runtime. Guard with typeof stream[Symbol.asyncIterator] === 'function' before invoking. [ Low confidence ]
  • line 103: CancelReadableStream assumes that any non-async-iterable stream has a getReader method and calls stream.getReader() unconditionally. If a non-standard object is passed (the function accepts any), this will throw. Add a type/feature check (e.g., if (typeof stream.getReader === 'function')) and otherwise no-op to keep the function best-effort and safe. [ Low confidence ]

Comment thread src/core/error.ts
this.error = error;
}

private static makeMessage(status: number | undefined, error: any, message: string | undefined) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APIError.makeMessage uses JSON.stringify on error.message and error without guarding against serialization errors. If these contain circular references or BigInt, JSON.stringify will throw, causing the APIError constructor to throw and masking the original error.

Consider wrapping the JSON.stringify calls in a try/catch and falling back to a safe string (e.g., String(value) or a generic placeholder). This would align with the safer pattern used by castToError and prevent crashes in error-handling paths.

-  private static makeMessage(status: number | undefined, error: any, message: string | undefined) {
-    const msg =
-      error?.message ?
-        typeof error.message === 'string' ?
-          error.message
-        : JSON.stringify(error.message)
-      : error ? JSON.stringify(error)
-      : message;
-
-    if (status && msg) {
-      return `${status} ${msg}`;
-    }
-    if (status) {
-      return `${status} status code (no body)`;
-    }
-    if (msg) {
-      return msg;
-    }
-    return '(no status code or body)';
-  }
+  private static makeMessage(status: number | undefined, error: any, message: string | undefined) {
+    const safeStringify = (v: any): string | undefined => {
+      if (v == null) return undefined;
+      try {
+        return typeof v === 'string' ? v : JSON.stringify(v);
+      } catch {
+        try {
+          return String(v);
+        } catch {
+          return undefined;
+        }
+      }
+    };
+
+    const msgFromErrorMessage =
+      error && 'message' in (error as any)
+        ? typeof (error as any).message === 'string'
+          ? (error as any).message
+          : safeStringify((error as any).message)
+        : undefined;
+
+    const msg = msgFromErrorMessage ?? (error ? safeStringify(error) : message);
+
+    if (status && msg) {
+      return `${status} ${msg}`;
+    }
+    if (status) {
+      return `${status} status code (no body)`;
+    }
+    if (msg) {
+      return msg;
+    }
+    return '(no status code or body)';
+  }

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/client.ts
}

if (typeof query === 'object' && query && !Array.isArray(query)) {
url.search = this.stringifyQuery(query as Record<string, unknown>);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildURL() replaces the entire query string via url.search = ..., which discards any existing query parameters present in path. If path already includes queries, they are not merged with defaultQuery/query, leading to lost caller-specified params.

Consider merging query into url.searchParams instead of overwriting url.search. This preserves existing params from path while allowing defaultQuery/query to override or add keys.

-      url.search = this.stringifyQuery(query as Record<string, unknown>);
+      const q = this.stringifyQuery(query as Record<string, unknown>);
+      if (q.length > 0) {
+        const newParams = new URLSearchParams(q);
+        for (const [key, value] of newParams) {
+          url.searchParams.set(key, value);
+        }
+      }

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread scripts/utils/git-swap.sh
# we want the final file structure for git installs to match the npm installs, so we

# delete everything except ./dist and ./node_modules
find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' +

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The find deletion on the top level also removes the repository’s .git directory and other dotfiles/directories (e.g., .npmrc, .env, .github). This breaks Git in the repo and can remove important configuration.

Consider excluding hidden entries (e.g., add ! -name '.*', and/or explicitly ! -name '.git') so Git metadata and other dotfiles are preserved during the cleanup.

-find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' +
+find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' ! -name '.*' -exec rm -rf '{}' +

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/internal/shims.ts
if (stream[Symbol.asyncIterator]) return stream;

const reader = stream.getReader();
return {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async iterator returned by stream in file:src/internal/shims.ts doesn’t track a finished state, so after observing { done: true } (line 67) or calling return() (line 68), further next() calls still invoke reader.read() on a released reader and return() still calls reader.cancel(), both of which can throw. Consider tracking a local finished flag and making next() immediately return { done: true, value: undefined } and return() a no-op once the iterator is complete.

+   let finished = false;
-         if (result?.done) reader.releaseLock(); // release lock when stream becomes closed
+         if (result?.done) { finished = true; reader.releaseLock(); } // release lock when stream becomes closed
-         reader.releaseLock(); // release lock when stream becomes errored
+         finished = true;
+         reader.releaseLock(); // release lock when stream becomes errored
-     async return() {
-       const cancelPromise = reader.cancel();
-       reader.releaseLock();
-       await cancelPromise;
-       return { done: true, value: undefined };
-     },
+     async return() {
+       if (finished) {
+         return { done: true, value: undefined };
+       }
+       const cancelPromise = reader.cancel();
+       reader.releaseLock();
+       await cancelPromise;
+       finished = true;
+       return { done: true, value: undefined };
+     },

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/internal/shims.ts
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export function ReadableStreamToAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
if (stream[Symbol.asyncIterator]) return stream;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning stream when it only implements Symbol.asyncIterator but isn’t itself an async iterator breaks the AsyncIterableIterator<T> contract and can cause runtime errors when callers call next() on the returned value.

Consider invoking stream[Symbol.asyncIterator]() and returning that iterator, so the function consistently returns an object with next() per the declared return type.

-  if (stream[Symbol.asyncIterator]) return stream;
+  if (typeof stream?.[Symbol.asyncIterator] === 'function') return stream[Symbol.asyncIterator]();

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/client.ts
controller: AbortController,
): Promise<Response> {
const { signal, method, ...options } = init || {};
if (signal) signal.addEventListener('abort', () => controller.abort());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abort propagation misses an already-aborted init.signal. If the signal is aborted before fetchWithTimeout runs, the 'abort' listener won't fire, so the internal controller isn't aborted and the request proceeds, breaking the cancellation contract.

Consider aborting the internal AbortController upfront when signal?.aborted is true, before setting the timeout and calling fetch, in addition to registering the 'abort' listener.

+    if (signal?.aborted) controller.abort();

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -exuo pipefail

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set -x and curl -v will print sensitive data (the Authorization header and potentially the signed URL). This can leak secrets to logs and CI output.

Consider disabling tracing when running commands that include secrets, or removing -x entirely for this script. Similarly, avoid -v; you can use curl's --fail/-sS/-w options to check status without printing headers, and adjust the status check to read the exit code or a minimal status string.

-set -exuo pipefail
+set -euo pipefail
-
-UPLOAD_RESPONSE=$(curl -v -X PUT \
+UPLOAD_RESPONSE=$(curl -sS -o /dev/null -w "HTTP %{http_code}" -X PUT \
   -H "Content-Type: application/gzip" \
-  --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1)
+  --data-binary "@dist/$TARBALL" "$SIGNED_URL")
-
-if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
+if echo "$UPLOAD_RESPONSE" | grep -q "200"; then

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/client.ts
controller: AbortController,
): Promise<Response> {
const { signal, method, ...options } = init || {};
if (signal) signal.addEventListener('abort', () => controller.abort());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchWithTimeout adds an abort listener to init.signal but never removes it. Over many calls with a long-lived signal, listeners accumulate and leak memory.

Consider storing the handler in a variable, handling the already-aborted case, and removing the listener in the finally block so each registration has a matching cleanup.

-    if (signal) signal.addEventListener('abort', () => controller.abort());
+    let abortHandler: (() => void) | undefined;
+    if (signal) {
+      if (signal.aborted) {
+        controller.abort();
+      } else {
+        abortHandler = () => controller.abort();
+        signal.addEventListener('abort', abortHandler);
+      }
+    }
-
-    const timeout = setTimeout(() => controller.abort(), ms);
+
+    const timeout = setTimeout(() => controller.abort(), ms);
@@
-      clearTimeout(timeout);
+      clearTimeout(timeout);
+      if (signal && abortHandler) {
+        signal.removeEventListener('abort', abortHandler);
+      }

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/client.ts
options.timeout = options.timeout ?? this.timeout;
const { bodyHeaders, body } = this.buildBody({ options });
const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildRequest normalizes options.timeout but then calls buildHeaders with inputOptions. This can produce stale externally visible headers (like X-Stainless-Timeout) and auth-related headers that don’t match the actual request configuration.

Consider passing the normalized options into buildHeaders so headers reflect the final request options, consistent with how buildBody already uses the normalized options.

-    const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });
+    const reqHeaders = await this.buildHeaders({ options, method, bodyHeaders, retryCount });

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Comment thread src/client.ts
baseURL: baseURL || `https://api.terminal49.com/v2`,
};

this.baseURL = options.baseURL!;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor only throws when apiKey === undefined, so an empty string (e.g., '') passes through and results in Authorization: ''. This contradicts the error message ("missing or empty") and causes confusing downstream auth failures instead of failing fast.

Consider treating empty or whitespace-only API keys as invalid too, e.g., check for apiKey == null || apiKey.trim() === '' so initialization fails early with a clear error.

-    if (apiKey === undefined) {
+    if (apiKey == null || apiKey.trim() === '') {

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

@dodeja dodeja closed this Oct 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant