chore: onboard api-creator as a Lisa project (Bun) - #4
Conversation
Replace npm with Bun as the package manager. Delete package-lock.json and generate bun.lock. Update .gitignore for coverage and Lisa backups. 🤖 Generated with Claude Code Co-Authored-By: Claude
Install @codyswann/lisa and apply TypeScript stack templates: - ESLint flat config with local overrides for initial onboarding - Prettier, commitlint, husky hooks (pre-commit, pre-push, commit-msg) - Vitest config with coverage thresholds - TypeScript strict config extending Lisa base - Knip dead code detection - ast-grep structural search - GitHub Actions: CI quality workflow, Claude workflows, dependabot - Reusable publish-to-npm workflow with OIDC 🤖 Generated with Claude Code Co-Authored-By: Claude
- Fix unused variables (prefix with _ or remove) - Auto-fix prefer-template, prefer-const, brace-style - Apply Prettier formatting across all source and test files - Fix irregular whitespace in JSDoc comment 🤖 Generated with Claude Code Co-Authored-By: Claude
- Replace npm with Bun for install/build/test steps - Add oven-sh/setup-bun@v2 step - Chain the reusable publish-to-npm.yml workflow instead of inline OIDC publish logic 🤖 Generated with Claude Code Co-Authored-By: Claude
Lisa's postinstall overwrites knip.json (copy-overwrite), which removes project-specific ignoreBinaries entries. Instead of fighting the config, declare eslint, prettier, knip, and @ast-grep/cli as direct devDependencies so knip can resolve them. Also re-add coverage/ to .prettierignore after Lisa overwrite. 🤖 Generated with Claude Code Co-Authored-By: Claude
- Reset eslint.config.local.ts to empty (test-only overrides for jsdoc, duplicate strings, publicly-writable-directories, max-lines) - Reset eslint.thresholds.json to defaults (complexity 10, 300 lines, 75/fn) - Reset vitest.thresholds.json to 70/70/70/70 coverage - Remove globals:true from vitest.config.local.ts, add coverage exclusions for src/types (pure interfaces), src/recorder (Playwright), src/cli.ts - Replace .gitignore with Lisa template + project-specific entries - Rename test/ → tests/ to match Lisa convention Split 5 oversized files (all now ≤300 lines): - cli-project-emitter.ts → + auth-emitter, commands-emitter, graphql-commands-emitter, cli-entrypoint-emitter - client-emitter.ts → + client-method-emitter - type-inferrer.ts → + property-inferrer - project-runner.ts → + endpoint-command-builder - commands/test.ts → + test-helpers Deduplicate singularize/camelToKebab/kebabToCamel into utils/naming.ts. Fix all ESLint violations: functional/immutable-data (array-literal patterns), functional/no-let, jsdoc/*, code-organization, sonarjs/*, no-param-reassign. Add 338 tests across 31 files (up from 66 tests / 9 files). Coverage: 89% stmts, 77% branches, 92% functions, 90% lines. 🤖 Generated with Claude Code Co-Authored-By: Claude
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds comprehensive CI/CD and developer tooling, substantial generator and parser refactors, new runtime CLI command builders and auth handling, expanded Git hooks and project-wide lint/test configs, and a large new/updated Vitest test suite converting old tests to a new Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes ✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 2
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟠 Major comments (33)
src/runtime/curl-parser.ts-31-45 (1)
31-45:⚠️ Potential issue | 🟠 Major
x-api-keyauth is currently discarded.
AuthConfigandhttpRequest()still supportapiKey, but this parser demotesx-api-keytoextraHeadersand then returnsnullunless a cookie or bearer token is present. API-key-only projects will fail auth setup. Please add anx-api-key-only regression test with the fix.Suggested change
function classifyHeader( hName: string, hValue: string -): { cookie?: string; token?: string; extraHeader?: [string, string] } { +): { + cookie?: string; + token?: string; + apiKey?: string; + extraHeader?: [string, string]; +} { const hLower = hName.toLowerCase(); if (hLower === "cookie") { return { cookie: hValue }; } if ( hLower === "authorization" && hValue.toLowerCase().startsWith("bearer ") ) { return { token: hValue.replace(/^[Bb]earer\s+/, "") }; } + if (hLower === "x-api-key") { + return { apiKey: hValue }; + } if ( hLower.startsWith("x-") && hValue && !hLower.startsWith("x-client-") && hLower !== "x-csrf-without-token" @@ function extractAllHeaderAuth(joined: string): { cookie: string | undefined; token: string | undefined; + apiKey: string | undefined; extraHeaders: Record<string, string>; } { @@ return matches.reduce<{ cookie: string | undefined; token: string | undefined; + apiKey: string | undefined; extraHeaders: Record<string, string>; }>( (acc, hm) => { @@ return { cookie: classified.cookie ?? acc.cookie, token: classified.token ?? acc.token, + apiKey: classified.apiKey ?? acc.apiKey, extraHeaders: classified.extraHeader ? { ...acc.extraHeaders, [classified.extraHeader[0]]: classified.extraHeader[1], } : acc.extraHeaders, }; }, - { cookie: undefined, token: undefined, extraHeaders: {} } + { cookie: undefined, token: undefined, apiKey: undefined, extraHeaders: {} } ); } @@ const { cookie: headerCookie, token, + apiKey, extraHeaders, } = extractAllHeaderAuth(joined); const cookie = bMatch ? bMatch[1] : headerCookie; - if (!cookie && !token) return null; + if (!cookie && !token && !apiKey) return null; return { ...(cookie ? { cookie } : {}), ...(token ? { token } : {}), + ...(apiKey ? { apiKey } : {}), ...(Object.keys(extraHeaders).length > 0 ? { extraHeaders } : {}), };Also applies to: 54-77, 85-119
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/curl-parser.ts` around lines 31 - 45, The parser currently ignores an x-api-key-only auth because extractAllHeaderAuth returns x-api-key inside extraHeaders and the function returns null unless cookie or token exist; update the logic in curl-parser (the block using extractAllHeaderAuth and variables cookie, token, extraHeaders) to treat an x-api-key header as an apiKey result (map extraHeaders['x-api-key'] into apiKey when present) so the returned object includes apiKey even if cookie and token are absent, and add a regression test that posts a curl containing only an x-api-key header to validate AuthConfig/httpRequest accept apiKey-only auth.package.json-4-4 (1)
4-4:⚠️ Potential issue | 🟠 MajorDon't run Lisa from
postinstallin the published npm package.For npm,
postinstallruns automatically during consumer installs, but@codyswann/lisais a devDependency, which npm omits from transitive dependency installs. This guarantees the script fails, and the2>/dev/null || truesilencing masks the error rather than fixing it. (Bun doesn't run postinstall scripts by default for security, so this is less of a risk there.)Move this to a contributor-only script or CI hook instead of the install lifecycle.
Suggested change
"scripts": { - "postinstall": "node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . 2>/dev/null || true", + "lisa:sync": "node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check .", "test": "vitest run",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 4, The postinstall lifecycle script ("postinstall": "node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . 2>/dev/null || true") must be removed from package.json so consumers won't run a devDependency during install; instead add a contributor-only script (e.g., "contrib:setup" or "setup") or move the invocation into CI/commit hooks so only maintainers run `@codyswann/lisa`; update package.json by deleting the "postinstall" entry and creating a clearly named dev script that documents how contributors should run the Lisa command locally or via CI.src/parser/endpoint-grouper.ts-140-195 (1)
140-195:⚠️ Potential issue | 🟠 MajorThis grouping path is now quadratic over large HARs.
The
new Map([...groups, mapEntry])on Line 194 copies the full groups map for every entry, and the same pattern is used for several arrays in the accumulator. Large HAR imports can contain thousands of requests, so this adds avoidable CPU and GC pressure. Keep the accumulator mutable while grouping and materialize the finalEndpoint[]once at the end.Also applies to: 228-233
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/parser/endpoint-grouper.ts` around lines 140 - 195, processEntry is creating a new Map and copying arrays for every HarEntry (e.g., new Map([...groups, mapEntry]) and spreads like [...group.originalUrls, entry.request.url]) which makes grouping quadratic; instead mutate the groups Map and existing GroupData in-place: lookup or create a GroupData object, push into its originalUrls, requestBodies/responseBodies and responseStatuses arrays, merge query params/headers into the existing maps/objects (use collectQueryParams/collectHeaders to update instead of returning new ones), and then set groups.set(groupKey, group) without copying the whole Map; apply the same in the other accumulator codepaths that use array spreads or new Map constructions (the pattern noted around the other spread at 228-233) and only materialize the final Endpoint[] once after processing all entries..github/workflows/claude-nightly-jira-triage.yml-28-28 (1)
28-28:⚠️ Potential issue | 🟠 MajorPin the reusable workflow to an immutable ref.
Using
@mainmeans this repository will execute whatever lands on that branch later, which weakens both supply-chain guarantees and reproducibility. Pin this to a commit SHA (or another immutable ref) instead.🔒 Suggested change
- uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-jira-triage.yml@main + uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-jira-triage.yml@<pinned-commit-sha>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/claude-nightly-jira-triage.yml at line 28, Replace the floating branch ref on the reusable workflow invocation so it uses an immutable ref: in the line containing the uses declaration (the string "CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-jira-triage.yml@main"), change the `@main` suffix to a commit SHA or a pinned tag/release (e.g., @<commit-sha> or `@vX.Y.Z`) so the workflow calls a stable, immutable revision.src/parser/auth-detector.ts-218-230 (1)
218-230:⚠️ Potential issue | 🟠 MajorSame O(n²) issue in
deduplicateByValue.This function also spreads the map on each iteration. Apply the same fix using
map.set()for O(n) performance.Suggested fix
function deduplicateByValue( candidates: Map<string, AuthCandidate> ): Map<string, AuthCandidate> { - return [...candidates.values()].reduce<Map<string, AuthCandidate>>( - (byValue, candidate) => { - const vKey = candidate.info.value; - const existing = byValue.get(vKey); - const shouldReplace = - !existing || - candidate.info.confidence > existing.info.confidence || - (candidate.info.confidence === existing.info.confidence && - candidate.count > existing.count); - return shouldReplace ? new Map([...byValue, [vKey, candidate]]) : byValue; - }, - new Map() - ); + const byValue = new Map<string, AuthCandidate>(); + for (const candidate of candidates.values()) { + const vKey = candidate.info.value; + const existing = byValue.get(vKey); + const shouldReplace = + !existing || + candidate.info.confidence > existing.info.confidence || + (candidate.info.confidence === existing.info.confidence && + candidate.count > existing.count); + if (shouldReplace) { + byValue.set(vKey, candidate); + } + } + return byValue; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/parser/auth-detector.ts` around lines 218 - 230, The reducer in deduplicateByValue currently creates a new Map on each iteration (new Map([...byValue, [vKey, candidate]])), causing O(n²) behavior; change it to mutate the accumulator by calling byValue.set(vKey, candidate) when shouldReplace is true and then return byValue (leave byValue untouched when not replacing) so the reduce runs in O(n). Reference symbols: deduplicateByValue, candidates, byValue, vKey, existing, candidate.info.confidence, candidate.count, and use map.set().src/parser/auth-detector.ts-146-156 (1)
146-156:⚠️ Potential issue | 🟠 MajorPerformance issue: Creating a new Map on each iteration is O(n²).
new Map([...map, entry])spreads the entire map into a new one on every iteration. For large HAR files with many auth candidates, this becomes quadratic. Usingmap.set()with mutation inside the reduce would be more efficient.Suggested fix
return allInfos.reduce((map, info) => { const key = authKey(info); const existing = map.get(key); - const entry: [string, AuthCandidate] = [ - key, - existing - ? { info: existing.info, count: existing.count + 1 } - : { info, count: 1 }, - ]; - return new Map([...map, entry]); + if (existing) { + existing.count += 1; + } else { + map.set(key, { info, count: 1 }); + } + return map; }, seen);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/parser/auth-detector.ts` around lines 146 - 156, The reduce currently creates a new Map each iteration (new Map([...map, entry])) causing O(n²) behavior; change the reducer to mutate and reuse the existing Map by calling map.set(key, entry) and returning map (or replace the reduce with an imperative for/of loop over allInfos). Keep references to authKey, AuthCandidate, allInfos.reduce and the seen initial Map, ensure the code updates the existing Map entry for key (using existing to increment count) and returns the same Map instance instead of allocating a new one each time.src/recorder/browser-session.ts-33-67 (1)
33-67:⚠️ Potential issue | 🟠 MajorLet the caller decide when to exit.
This helper resolves
startBrowserSession()and then terminates the process, so the caller never resumes afterawait startBrowserSession(). That makes the post-recording output inrecordCommandunreachable, and the directcontext.closepath also skipsuserDataDircleanup. Keep shutdown here limited to cleanup/resolve and let the command exit naturally.Also applies to: 133-139
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/recorder/browser-session.ts` around lines 33 - 67, Remove the hard process termination from cleanup; in cleanupSession (and the duplicate shutdown block around the same logic later), stop calling process.exit(0) so the function only performs cleanup and resolves the HAR path, allowing startBrowserSession callers to continue. Ensure resolve(harPath) remains invoked after attempting context.close() and after removing userDataDir (fs.rmSync), but do not invoke process.exit; leave process termination to the caller/command.src/recorder/browser-session.ts-87-93 (1)
87-93:⚠️ Potential issue | 🟠 MajorClean up the launched context if setup fails.
Any exception in this block rejects before the interactive cleanup handlers are registered, which leaves the browser context and temp profile directory behind.
Suggested guard around page setup
const context = await launchContextWithHar(userDataDir, harPath, spinner); - const page = context.pages()[0] ?? (await context.newPage()); - - spinner.succeed("Browser launched"); - attachNetworkCapture(page, { includeAssets: options.includeAssets ?? false }); - console.log(chalk.blue(`Navigating to ${options.url}...`)); - await page.goto(options.url); + try { + const page = context.pages()[0] ?? (await context.newPage()); + + spinner.succeed("Browser launched"); + attachNetworkCapture(page, { includeAssets: options.includeAssets ?? false }); + console.log(chalk.blue(`Navigating to ${options.url}...`)); + await page.goto(options.url); + } catch (error) { + await context.close().catch(() => {}); + fs.rmSync(userDataDir, { recursive: true, force: true }); + throw error; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/recorder/browser-session.ts` around lines 87 - 93, The launch and page setup (launchContextWithHar -> context, page, attachNetworkCapture, page.goto) must be wrapped in a try/catch so that if any error occurs before interactive cleanup handlers are registered you explicitly close the Playwright context and remove the temporary userDataDir; implement a catch that checks for an initialized context and calls await context.close(), deletes the userDataDir (use the existing profile cleanup helper or fs.rm with recursive:true), optionally call spinner.fail("Browser launch failed"), then rethrow the error so the caller still sees the failure..github/workflows/claude.yml-25-25 (1)
25-25:⚠️ Potential issue | 🟠 MajorPin the reusable workflow to an immutable commit SHA.
Using
@mainhere means upstream changes can silently change a write-scoped workflow in this repository. Pinning the exact commit keeps runs reproducible and narrows the supply-chain risk.Suggested change
- uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@main + uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@<pinned-commit-sha>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/claude.yml at line 25, Replace the floating ref in the workflow "uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@main" with an immutable commit SHA from the upstream repo (e.g., "uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@<commit-sha>"); obtain the correct SHA by fetching the upstream repository commit you want to pin, then update that uses: line to reference the SHA instead of `@main` and commit the change.src/recorder/auth-capture.ts-77-86 (1)
77-86:⚠️ Potential issue | 🟠 MajorHeader-only auth currently resolves to
null.
buildCapturedAuth()bails out whencount === 0, but Lines 204-205 only incrementcountafter a cookie or bearer token is seen. APIs authenticated solely viaX-API-Keyor other capturedextraHeaderswill be discarded.Suggested change
function buildCapturedAuth(state: CaptureState): AuthConfig | null { - const { cookie, token, extraHeaders, count } = state.value; - if (count === 0) return null; + const { cookie, token, extraHeaders } = state.value; + const hasAuth = + Boolean(cookie) || + Boolean(token) || + Object.keys(extraHeaders).length > 0; + if (!hasAuth) return null; return { ...(cookie ? { cookie } : {}), ...(token ? { token } : {}), @@ - if (state.value.cookie || state.value.token) { + if ( + state.value.cookie || + state.value.token || + Object.keys(state.value.extraHeaders).length > 0 + ) { state.value = { ...state.value, count: state.value.count + 1 }; const shortPath = new URL(request.url()).pathname.slice(0, 60);Also applies to: 199-205
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/recorder/auth-capture.ts` around lines 77 - 86, buildCapturedAuth currently returns null when count === 0 which drops header-only auth because count is only incremented for cookie/token; change the bailout to check actual captured auth fields instead of count — i.e., return null only if cookie, token and extraHeaders are all empty/absent (use Object.keys(extraHeaders).length to detect headers). Update buildCapturedAuth (and the analogous logic referenced around the other CaptureState handling) to rely on presence of cookie/token/extraHeaders rather than the numeric count so X-API-Key and other header-only auth are preserved.src/recorder/auth-capture.ts-323-335 (1)
323-335:⚠️ Potential issue | 🟠 MajorAlways tear down the browser profile in a
finally.Any exception after
mkdtempSync()—includingextractRootDomain(url)at Line 330 or a rejected capture path—skipscloseBrowser()and leaks the persistent profile directory.Suggested change
export async function captureAuth(url: string): Promise<AuthConfig | null> { const userDataDir = fs.mkdtempSync( path.join(os.tmpdir(), "api-creator-auth-") ); const spinner = ora("Launching browser...").start(); - const context = await launchContext(userDataDir, spinner); - const page = context.pages()[0] ?? (await context.newPage()); - const rootDomain = extractRootDomain(url); - const auth = await promptAndCapture(url, page, context, rootDomain, spinner); - - process.stdin.pause(); - console.log(""); - await closeBrowser(context, userDataDir); - - return auth; + let context: BrowserContext | null = null; + try { + context = await launchContext(userDataDir, spinner); + const page = context.pages()[0] ?? (await context.newPage()); + const rootDomain = extractRootDomain(url); + return await promptAndCapture(url, page, context, rootDomain, spinner); + } finally { + process.stdin.pause(); + console.log(""); + if (context) { + await closeBrowser(context, userDataDir); + } else { + fs.rmSync(userDataDir, { recursive: true, force: true }); + } + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/recorder/auth-capture.ts` around lines 323 - 335, captureAuth currently creates a temp profile via fs.mkdtempSync and then may throw before closeBrowser is called, leaking the profile; refactor captureAuth so that after creating userDataDir you wrap the remaining logic (launchContext, promptAndCapture, extractRootDomain, process.stdin.pause, console.log, and returning auth) in a try block and always invoke closeBrowser(context, userDataDir) in a finally block (ensuring context is declared in an outer scope so finally can reference it), preserving error propagation and existing behavior of process.stdin.pause and console output..github/workflows/claude-nightly-test-improvement.yml-27-32 (1)
27-32:⚠️ Potential issue | 🟠 MajorPin the nightly reusable workflow to an immutable commit SHA.
This job runs on a schedule with elevated permissions (
contents: write,issues: write,pull-requests: write,id-token: write) and forwards theCLAUDE_CODE_OAUTH_TOKENsecret. Using a mutable branch reference like@maincreates a supply chain risk where the workflow definition could be changed between runs. Pin to a full commit SHA instead.Suggested change
- uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-test-improvement.yml@main + uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-test-improvement.yml@<commit-sha>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/claude-nightly-test-improvement.yml around lines 27 - 32, The reusable workflow reference in the improve-tests job uses a mutable branch (`@main`); replace the branch ref in the uses field (CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-test-improvement.yml@main) with the immutable full commit SHA of the target repository to pin the workflow; update the uses value to the exact commit SHA string (e.g., ...@<full-commit-sha>) so the improve-tests job always runs the fixed workflow definition.src/generator/auth-emitter.ts-11-13 (1)
11-13:⚠️ Potential issue | 🟠 MajorHandle unsupported
AuthInfo.typevalues explicitly.The parser recognizes
query-paramandcustom-header(src/types/auth.ts), butresolveAuthType()casts the first auth mechanism directly to the localAuthTypewithout validation. This allows unsupported types to be emitted silently, when the generated auth module only handlescookie,bearer, andapi-key.Safer mapping
function resolveAuthType(authInfos: AuthInfo[]): AuthType { - const primary = authInfos.length > 0 ? authInfos[0] : null; - return (primary?.type ?? "none") as AuthType; + const primaryType = authInfos[0]?.type; + switch (primaryType) { + case "cookie": + case "bearer": + case "api-key": + return primaryType; + default: + return "none"; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/auth-emitter.ts` around lines 11 - 13, resolveAuthType currently casts the first AuthInfo.type directly to AuthType which allows unsupported values to slip through; update resolveAuthType(AuthInfo[]) to perform an explicit mapping/validation: inspect primary?.type and use a switch or mapping that returns 'cookie', 'bearer', or 'api-key' only (map 'query-param' and 'custom-header' to 'api-key'), and throw a clear error (or at minimum log and return 'none') for any unrecognized types so unsupported AuthInfo.type values are surfaced instead of being silently emitted..github/workflows/ci.yml-13-23 (1)
13-23:⚠️ Potential issue | 🟠 MajorPin the reusable workflow to a commit SHA instead of following
main.
quality.yml@mainallows a moving branch to execute workflow code with all forwarded security tokens. A malicious or compromised maintainer could inject arbitrary code without your knowledge. Pin to a full commit SHA for an immutable, reproducible CI environment.Note: This file is auto-generated by Lisa. Update the Lisa configuration that generates this file to use the pinned SHA instead of
@main.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml around lines 13 - 23, The workflow is using a mutable ref "uses: CodySwannGT/lisa/.github/workflows/quality.yml@main" which risks executing arbitrary changes; change that ref to a specific full commit SHA (replace "@main" with the commit SHA of the pinned quality.yml) so the reusable workflow is immutable, and update the Lisa config that generates this file to emit the same pinned SHA going forward (locate the "uses: CodySwannGT/lisa/.github/workflows/quality.yml@main" line in the ci.yml and the Lisa generation settings that supply that ref).src/generator/auth-emitter.ts-59-60 (1)
59-60:⚠️ Potential issue | 🟠 MajorUse
{ encoding: 'utf-8', mode: 0o600 }to restrict.authfile permissions to owner only.The file contains sensitive authentication data (cookies, bearer tokens, API keys), but
writeFileSync(..., 'utf-8')with string encoding leaves permissions to the process umask, typically creating a world-readable0644file. Set explicit owner-only permissions by passing an options object instead of a string encoding parameter:- " writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2), 'utf-8');", + " writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });",(Note: Adding
chmodSyncis redundant when mode is set inwriteFileSyncoptions.)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/auth-emitter.ts` around lines 59 - 60, The saveAuth function writes sensitive credentials to disk using writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2), 'utf-8'); change this to pass an options object so the file is created with owner-only permissions: replace the third argument with { encoding: 'utf-8', mode: 0o600 } when calling writeFileSync in saveAuth (refer to AUTH_FILE and saveAuth), and remove any redundant chmodSync calls if present.src/recorder/auth-capture.ts-247-248 (1)
247-248:⚠️ Potential issue | 🟠 MajorFix the hostname filter to prevent credential leakage to unrelated domains.
The
endsWith(rootDomain)check at line 248 is vulnerable to broad domain matching.malicious-example.comwould pass forexample.com, capturing credentials from attacker-controlled domains. Additionally,extractRootDomainat lines 288-291 uses a naive 2-part split that fails for multi-part TLDs—for example,foo.example.co.ukbecomesco.uk, which would match any.co.ukdomain.Implement a proper domain filter by either:
- Adding a dot-boundary check:
reqHost === rootDomain || reqHost.endsWith("." + rootDomain), combined with a public-suffix-aware registrable domain extraction- Using exact origin-host matching if sibling subdomains are not required
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/recorder/auth-capture.ts` around lines 247 - 248, The hostname filter in auth-capture.ts is too permissive: change the check that currently uses safeHostname(request.url()) and endsWith(rootDomain) to a strict boundary match—use reqHost === rootDomain || reqHost.endsWith("." + rootDomain) (or use exact origin-host matching if sibling subdomains are not needed) and replace the naive extractRootDomain logic with a public-suffix-aware registrable domain extraction (e.g., use a PSL library/function in place of the 2-part split in extractRootDomain) so rootDomain represents the actual registrable domain and prevents matches like malicious-example.com or incorrect co.uk handling. Ensure the code references the same symbols (safeHostname, request.url(), extractRootDomain, rootDomain) when applying the fix.src/generator/auth-emitter.ts-46-47 (1)
46-47:⚠️ Potential issue | 🟠 MajorEscape
originalUrlandnamevalues when generating code.String interpolation without escaping creates a code injection vulnerability. If either value contains a single quote, backslash, or newline, the generated TypeScript/JavaScript becomes invalid or executes injected code. Use
JSON.stringify()to properly escape these values:Suggested change
- `export const ORIGINAL_URL = '${originalUrl}';`, - `export const CLI_NAME = '${name}';`, + `export const ORIGINAL_URL = ${JSON.stringify(originalUrl)};`, + `export const CLI_NAME = ${JSON.stringify(name)};`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/auth-emitter.ts` around lines 46 - 47, The generated code directly interpolates originalUrl and name into template strings for constants ORIGINAL_URL and CLI_NAME, which can break or inject code if those values contain quotes/backslashes/newlines; change the generation to use JSON.stringify(originalUrl) and JSON.stringify(name) (or an equivalent escaping function) when building the two lines so the emitted `export const ORIGINAL_URL = ...;` and `export const CLI_NAME = ...;` contain properly escaped string literals.src/runtime/project-runner.ts-140-145 (1)
140-145:⚠️ Potential issue | 🟠 MajorPiped stdin never reaches the cURL auth path.
Lines 140-144 gate the non-TTY fallback on
options.file !== undefined, sopbpaste | api-creator <project> auth setupfalls through to browser capture unless--stdinis also passed. That breaks the implicit stdin flow this refactor is trying to add.💡 Suggested fix
- (!process.stdin.isTTY && options.file !== undefined) + !process.stdin.isTTY🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/project-runner.ts` around lines 140 - 145, The current conditional that decides whether to call handleCurlAuth prevents piped stdin from triggering cURL auth because it requires options.file !== undefined; remove that extra check so that non-TTY stdin alone will trigger the path. Update the if in project-runner.ts (the branch that currently checks options.file, options.stdin, and process.stdin.isTTY) to call handleCurlAuth when options.file is truthy OR options.stdin is truthy OR process.stdin is not a TTY (i.e., drop the options.file !== undefined gate), keeping the call to await handleCurlAuth(projectName, options) unchanged.src/commands/generate.ts-63-70 (1)
63-70:⚠️ Potential issue | 🟠 Major
--inputno longer honors the documented directory form.Lines 63-70 now pass any explicit
--inputstraight through to codegen, but the help text still says the flag accepts a recordings directory.api-creator generate --input ./recordingswill now hand a directory togenerateClientinstead of selecting the newest.har.💡 Suggested fix
- const inputPath: string = - options.input ?? (await findMostRecentHar(recordingsDir)); + let inputPath = options.input; + if (inputPath) { + const inputStats = await stat(resolve(inputPath)).catch(() => null); + if (inputStats?.isDirectory()) { + inputPath = await findMostRecentHar(resolve(inputPath)); + } + } else { + inputPath = await findMostRecentHar(recordingsDir); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/generate.ts` around lines 63 - 70, The CLI no longer treats --input as a recordings directory; update the action handler so that when options.input is provided and points to a directory it selects the newest .har inside it (use fs.stat/isDirectory or similar), otherwise treat options.input as a file path; keep the existing fallback of calling findMostRecentHar(resolve("./recordings")) when --input is omitted. Locate the action callback where options.input is read (symbols: options.input, recordingsDir, findMostRecentHar) and ensure the resolved inputPath passed to generateClient is the newest .har when a directory is given..github/workflows/auto-update-pr-branches.yml-15-30 (1)
15-30:⚠️ Potential issue | 🟠 MajorPin the reusable workflow to an immutable commit SHA.
Line 22 uses
@maindespite the job havingcontents: write,pull-requests: write, andid-token: writepermissions while passingCLAUDE_CODE_OAUTH_TOKEN. This creates a supply-chain risk—any upstream change would execute immediately with elevated privileges. Lock this to a full commit SHA and update intentionally.Suggested fix
- uses: CodySwannGT/lisa/.github/workflows/reusable-auto-update-pr-branches.yml@main + uses: CodySwannGT/lisa/.github/workflows/reusable-auto-update-pr-branches.yml@<full-commit-sha>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/auto-update-pr-branches.yml around lines 15 - 30, The workflow currently references the reusable workflow via "uses: CodySwannGT/lisa/.github/workflows/reusable-auto-update-pr-branches.yml@main" which creates a supply-chain risk given the elevated permissions and the CLAUDE_CODE_OAUTH_TOKEN secret; update the "uses" value in the autoupdate job to point to an immutable commit SHA of that reusable workflow (replace the "@main" suffix with the full commit SHA) so the autoupdate job always executes a pinned, auditable version; ensure you commit the new SHA string where the "uses" key is defined and verify the referenced commit contains the expected workflow.src/commands/generate.ts-50-55 (1)
50-55:⚠️ Potential issue | 🟠 MajorUse
path.basename()instead of splitting on"/"for cross-platform compatibility.On Windows, a path like
C:\recordings\petstore.harhas no forward slashes. The currentharPath.split("/")leaves the full path inbasename, causing the regex to match the drive letter and derive"c"as the project name. Thepathmodule is already imported—usepath.basename()to extract the filename reliably on all platforms.-import { join, resolve } from "node:path"; +import { basename as pathBasename, join, resolve } from "node:path"; … function deriveNameFromHarPath(harPath: string): string { - const segments = harPath.split("/"); - const basename = segments[segments.length - 1] ?? "api-client"; - const noExt = basename.replace(/\.har$/i, ""); + const fileName = pathBasename(harPath); + const noExt = fileName.replace(/\.har$/i, ""); const domainMatch = /^([a-zA-Z][a-zA-Z0-9-]*)/.exec(noExt);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/generate.ts` around lines 50 - 55, The deriveNameFromHarPath function currently splits harPath by "/" which breaks on Windows; replace the manual split and basename extraction with path.basename(harPath) to get the filename cross-platform, then strip the .har extension (or use path.parse(...).name) before running the domain regex; update references inside deriveNameFromHarPath accordingly so the function uses path.basename (or path.parse) instead of harPath.split("/").src/commands/test.ts-77-93 (1)
77-93:⚠️ Potential issue | 🟠 MajorCount the health-check result in the aggregate.
runTests()says it covers the health check and endpoint calls, but Lines 77-80 discard the probe result. A failing health check can still end withAll N endpoint(s) responded successfully.Possible fix
async function runTests( baseUrl: string, endpoints: ReturnType<typeof parseEndpoints>, toTest: ReturnType<typeof parseEndpoints>, authHeaders: Record<string, string> ): Promise<{ passed: number; failed: number }> { const healthEndpoint = findHealthCheckEndpoint(endpoints); + let initial = { passed: 0, failed: 0 }; if (healthEndpoint) { - await testEndpoint(baseUrl, healthEndpoint, authHeaders, "health check"); + const ok = await testEndpoint( + baseUrl, + healthEndpoint, + authHeaders, + "health check" + ); + initial = ok ? { passed: 1, failed: 0 } : { passed: 0, failed: 1 }; } console.log(chalk.blue(` Testing ${toTest.length} endpoint(s)...\n`)); return toTest.reduce( @@ - Promise.resolve({ passed: 0, failed: 0 }) + Promise.resolve(initial) ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/test.ts` around lines 77 - 93, The health-check result is currently discarded after calling testEndpoint(…, "health check"); ensure its boolean result is included in the final aggregate by capturing the health check outcome and adding it to the reduce accumulator; specifically, call const healthOk = healthEndpoint ? await testEndpoint(baseUrl, healthEndpoint, authHeaders, "health check") : false and then either (a) include healthOk in the initial Promise.resolve({ passed: healthOk ? 1 : 0, failed: healthOk ? 0 : 0 }) for the toTest.reduce or (b) add healthOk to the reduce result after it completes (e.g., increment passed/failed based on healthOk) so the returned counts reflect the health probe as well as the other endpoints.src/generator/cli-entrypoint-emitter.ts-43-50 (1)
43-50:⚠️ Potential issue | 🟠 MajorAccept header-only auth configs.
extraHeadersis populated on Lines 44-45, but Line 50 still returnsnullunless a cookie, token, orapiKeywas also found. That makesauth setupfail for valid header-only schemes likex-api-keyorx-auth-token.Possible fix
- " if (!auth.cookie && !auth.token && !auth.apiKey) return null;", + " if (", + " !auth.cookie &&", + " !auth.token &&", + " !auth.apiKey &&", + " Object.keys(extraHeaders).length === 0", + " ) return null;",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/cli-entrypoint-emitter.ts` around lines 43 - 50, The auth builder currently rejects header-only schemes because it returns null unless auth.cookie, auth.token, or auth.apiKey exist; update the final check to also accept cases where extraHeaders was populated: modify the return condition (the place that currently does "if (!auth.cookie && !auth.token && !auth.apiKey) return null;") to instead return null only when there are no cookies, tokens, apiKeys, AND extraHeaders is empty (e.g. check Object.keys(extraHeaders).length === 0). Ensure you still assign auth.extraHeaders when extraHeaders has keys and keep the existing header-filtering logic that builds extraHeaders.src/importer/paste-parser-http.ts-122-145 (1)
122-145:⚠️ Potential issue | 🟠 MajorPreserve the raw body when building the HAR entry.
Lines 122-127 call
.trim()on the request body before it is attached topostData. That strips leading/trailing whitespace and blank lines from plain-text, XML, and form payloads, so the imported HAR no longer matches the captured request.Possible fix
- const body = - emptyIdx !== -1 - ? lines - .slice(emptyIdx + 2) - .join("\n") - .trim() || undefined - : undefined; + const rawBody = + emptyIdx !== -1 ? lines.slice(emptyIdx + 2).join("\n") : undefined; + const body = rawBody === "" ? undefined : rawBody;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/importer/paste-parser-http.ts` around lines 122 - 145, The code currently trims the request body (the variable body built in paste-parser-http.ts) which removes leading/trailing whitespace; remove the .trim() so the raw body from lines.slice(emptyIdx + 2).join("\n") is preserved, keep body as undefined only if the joined string is exactly "" (e.g., use const bodyRaw = lines.slice(...).join("\n"); const body = bodyRaw === "" ? undefined : bodyRaw;), then pass that to attachPostData(makeEntry) and compute bodySize from body.length when body is defined; update any references to bodySize on the HarRequest object accordingly (see HarRequest construction, attachPostData, makeEntry).src/generator/client-method-emitter.ts-100-105 (1)
100-105:⚠️ Potential issue | 🟠 MajorQuote query parameter names using bracket notation to support hyphens, brackets, and reserved words.
Lines 103 and 142 inject
qp.namedirectly without quoting. Query parameter names likepage-size,filter[status], ordefaultwill generate invalid TypeScript/JavaScript:
- Line 103:
{ page-size?: string }is invalid syntax (needs{ "page-size"?: string })- Line 142:
options.page-sizefails at runtime (needsoptions["page-size"])Apply the fix across both locations:
Suggested changes
const queryParam = endpoint.queryParams.length > 0 ? [ - `options?: { ${endpoint.queryParams.map(qp => `${qp.name}${qp.required ? "" : "?"}: string`).join("; ")} }`, + `options?: { ${endpoint.queryParams.map(qp => `${JSON.stringify(qp.name)}${qp.required ? "" : "?"}: string`).join("; ")} }`, ] : [];- qp => - ` if (options.${qp.name} !== undefined) params.set('${qp.name}', options.${qp.name});` + qp => + ` if (options[${JSON.stringify(qp.name)}] !== undefined) params.set(${JSON.stringify(qp.name)}, options[${JSON.stringify(qp.name)}]);`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/client-method-emitter.ts` around lines 100 - 105, The generated query param type and usages currently inject qp.name unquoted causing invalid identifiers for names like page-size or filter[status]; update the queryParam construction in client-method-emitter.ts (the queryParam constant that maps endpoint.queryParams) to emit quoted property names in the type literal (e.g. wrap qp.name in quotes when building the `{ ... }` type) and update all runtime accesses (where code reads options.<qp.name>) to use bracket notation (options[qp.name]) so hyphens, brackets, and reserved words are handled safely; ensure you preserve the optional marker (`?`) logic when quoting and apply the same change where options properties are accessed (the usage around line ~142 referenced in the review).src/generator/graphql-commands-emitter.ts-127-131 (1)
127-131:⚠️ Potential issue | 🟠 MajorUse safe option-key mapping for
otherParamsto matchparametrizedVariablespattern.The setup emits
--${qp.name}forotherParamswithout applyinggetParamVarName(), whereasparametrizedVariablesconsistently apply it at both definition (line 117-125) and access (line 179). Since Commander.js converts dashed option names (like--page-size) to camelCase properties (pageSize), the mismatch between raw query parameter names and how options are accessed creates inconsistency. The handler's broadObject.keys(options).forEach(...)copy at lines 170-172 will use camelCased keys, but the original dashed names won't round-trip correctly. ApplygetParamVarName()tootherParamsat both lines 127-131 and lines 202-205 to match the pattern used forparametrizedVariables.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/graphql-commands-emitter.ts` around lines 127 - 131, The emitted options for otherParams are using raw qp.name (otherParams.map) which mismatches Commander.js camelCasing and the existing parametrizedVariables pattern; update the option key generation and later access to use getParamVarName(qp.name) so option definitions (the .option(...) calls generated from otherParams) and their consumption (the code that reads options and merges into parametrizedVariables) both use getParamVarName, matching how parametrizedVariables are created and accessed and ensuring dashed names like "page-size" become the same camelCased property (use getParamVarName in the otherParams.map location and in the later options-to-param merge logic).src/generator/client-method-emitter.ts-33-58 (1)
33-58:⚠️ Potential issue | 🟠 MajorApply identifier sanitization to derived path-parameter names.
paramNameat line 41 is built from raw path segment text without normalization. For paths like/user-groups/:id, the segmentuser-groupsbecomesuser-groupIdafter singularization, which is invalid TypeScript. This breaks both the parameter list (line 95) and template reference (line 115).Pass the segment through
cleanSegmentfromnaming.tsbefore callingsingularize, matching the approach used inpathToMethodName.This issue also applies to query parameters (line 103), which directly interpolate
qp.nameinto the options type without sanitization, and the property access pattern (line 121+) should use bracket notation for non-identifier query param names.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/client-method-emitter.ts` around lines 33 - 58, The derived path param names in extractPathParams build paramName from raw segments without sanitization causing invalid identifiers; update extractPathParams to call cleanSegment(seg) before singularize (i.e. paramBase = singularize(cleanSegment(segment))) when constructing paramName so names are valid TypeScript, and apply the same sanitization to query parameter names (qp.name) wherever they are used to form types (matching pathToMethodName behavior); finally, update template usages that access potentially non-identifier query params to use bracket notation (e.g., options[cleanedName]) or otherwise safe property access for any sanitized-but-not-identifier names.src/generator/diff-merger.ts-62-65 (1)
62-65:⚠️ Potential issue | 🟠 MajorKeep preserved custom blocks scoped to their original file.
detectExistingClient()flattens sections fromclient.tsandtypes.tsinto one array, andapplyMerge()replays that same array into both outputs. Any marker that exists in only one file gets injected into the other on merge, and shared marker names can overwrite each other across files.♻️ Suggested direction
export interface ExistingClient { methodNames: string[]; typeNames: string[]; - customSections: CustomSection[]; + clientCustomSections: CustomSection[]; + typesCustomSections: CustomSection[]; clientSource: string; typesSource: string; } @@ - const customSections = [ - ...parseCustomSections(clientSource), - ...parseCustomSections(typesSource), - ]; + const clientCustomSections = parseCustomSections(clientSource); + const typesCustomSections = parseCustomSections(typesSource); - return { methodNames, typeNames, customSections, clientSource, typesSource }; + return { + methodNames, + typeNames, + clientCustomSections, + typesCustomSections, + clientSource, + typesSource, + }; @@ - const sections = existing ? existing.customSections : []; + const clientSections = existing?.clientCustomSections ?? []; + const typeSections = existing?.typesCustomSections ?? []; - const clientCode = sections.reduce( + const clientCode = clientSections.reduce( (code, section) => restoreCustomSection(code, section), clientWithDeprecations ); - const typesCode = sections.reduce( + const typesCode = typeSections.reduce( (code, section) => restoreCustomSection(code, section), fullTypesCode );Also applies to: 187-197
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/diff-merger.ts` around lines 62 - 65, detectExistingClient() currently merges parseCustomSections(clientSource) and parseCustomSections(typesSource) into a single customSections array and applyMerge() replays that same array into both outputs, causing markers from one file to be injected into the other; instead preserve file-scoped sections by keeping the parsed sections separate and passing the correct list into each merge. Change the logic so parseCustomSections(clientSource) and parseCustomSections(typesSource) are stored as two distinct collections (e.g., clientCustomSections and typesCustomSections) and ensure applyMerge() (and any callers that currently use customSections) receives the appropriate collection for the corresponding output (client vs types), and update any merging/overwrite logic in detectExistingClient() and the related merge block that replays sections (the code around applyMerge() usage) so markers do not cross-inject or override across files.src/runtime/endpoint-command-builder.ts-160-168 (1)
160-168:⚠️ Potential issue | 🟠 MajorURL-encode path arguments before replacement.
buildResolvedPath()injects raw CLI values into the URL. A value containing/,?,#, or spaces will change the route or produce an invalid request.🔧 Minimal fix
function buildResolvedPath( pathTemplate: string, pathParams: string[], pathArgValues: string[] ): string { return pathParams.reduce( - (acc, _param, i) => acc.replace(":id", pathArgValues[i]), + (acc, _param, i) => + acc.replace(":id", encodeURIComponent(pathArgValues[i])), pathTemplate ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/endpoint-command-builder.ts` around lines 160 - 168, buildResolvedPath currently injects raw CLI values into the URL; update it to URL-encode each path argument before replacement. In the reduce callback for buildResolvedPath use the path param name (pathParams[i]) to replace the matching placeholder (e.g., `:${pathParam}`) and substitute encodeURIComponent(pathArgValues[i] ?? '') instead of the raw value so characters like /, ?, # and spaces are percent-encoded.src/generator/commands-emitter.ts-193-203 (1)
193-203:⚠️ Potential issue | 🟠 MajorOmitting
--bodycurrently hard-fails every mutating command.The generated handler always does
JSON.parse('')when neither--bodynor--jsonis passed, so every POST/PUT/PATCH command exits with “Invalid JSON” before sending the request. Match the runtime builder and only parse when a body flag is actually present.🔧 Minimal fix
const bodyLines = hasBody ? [ - " const bodyData = options.body || options.json || '';", - " let parsedBody: any;", - " try {", - " parsedBody = JSON.parse(bodyData);", - " } catch {", - " console.error('Invalid JSON body. Provide valid JSON via --body or --json.');", - GEN_PROCESS_EXIT_1, - " }", + " const bodyData = options.body ?? options.json;", + " let parsedBody: unknown;", + " if (bodyData !== undefined) {", + " try {", + " parsedBody = JSON.parse(bodyData);", + " } catch {", + " console.error('Invalid JSON body. Provide valid JSON via --body or --json.');", + GEN_PROCESS_EXIT_1, + " }", + " }", ] : [];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/commands-emitter.ts` around lines 193 - 203, The generated handler currently sets bodyData to options.body || options.json || '' and unconditionally calls JSON.parse, causing mutating commands to fail when no body flag is provided; update the body generation in commands-emitter.ts (the bodyLines block that defines bodyData and parsedBody and uses GEN_PROCESS_EXIT_1) so that you only attempt JSON.parse when a body flag is actually present (e.g., if options.body !== undefined || options.json !== undefined), otherwise leave parsedBody undefined/absent and do not call JSON.parse or trigger GEN_PROCESS_EXIT_1; ensure bodyData is taken from options.body ?? options.json and wrap the try/catch parsing logic inside that presence check.src/generator/cli-project-emitter.ts-49-64 (1)
49-64:⚠️ Potential issue | 🟠 MajorAdd
typescriptand@types/nodeas devDependencies to generated package.json.The emitted package.json defines
scripts.build: "tsc"but only includescommanderin dependencies. Generated projects will fail at the build step documented in the README (npm install && npm run build) unless users have TypeScript and Node types installed globally. Both should be included as devDependencies.Additionally, the README attribution at line 153 incorrectly points to
anthropics/api-creatorinstead of the current repository URL.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/cli-project-emitter.ts` around lines 49 - 64, The emitted package.json from emitProjectPackageJson builds a pkg object with scripts.build set to "tsc" but only includes commander in dependencies; update emitProjectPackageJson to add a devDependencies entry on the pkg object that includes "typescript" and "@types/node" (so projects can run npm install && npm run build), and also correct the README attribution URL (replace the incorrect "anthropics/api-creator" reference with the current repository URL) in the README file mentioned in the PR; ensure you update the pkg variable and scripts.build usage accordingly.src/runtime/endpoint-command-builder.ts-87-101 (1)
87-101:⚠️ Potential issue | 🟠 MajorApply the GraphQL-variable pattern to query parameters for CLI-safe flag names and mapping.
qp.nameis used verbatim for option registration and reading, which breaks parameter names containing special characters like dashes or brackets (e.g.,page-size,filter[status]). Commander normalizes long-option names, and these raw keys are not valid flag syntax.Reuse the existing pattern from GraphQL variables (lines 64-78, 228-235): transform query param names to kebab-case for the CLI flag using
camelToKebab(), then map back to the original key usingkebabToCamel()when reading from options. Update both registration (lines 87-101) and lookup (lines 244-253) to follow this pattern.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/endpoint-command-builder.ts` around lines 87 - 101, The CLI currently registers query param flags using qp.name verbatim which breaks names with special chars; in registerQueryParamOptions replace usages of qp.name for the flag with camelToKebab(qp.name) (keep the same label/description and preserve qp.defaultValue), and update the option-reading logic that currently looks up options by qp.name (the lookup block around lines 244-253) to read the kebab-cased flag from the parsed options and map it back to the original key with kebabToCamel() before assigning to the final query param object; use the same camelToKebab/kebabToCamel helpers already used for GraphQL variables to ensure CLI-safe flags and correct mapping.src/generator/commands-emitter.ts-150-154 (1)
150-154:⚠️ Potential issue | 🟠 MajorSanitize query parameter names before use in generated code.
Query parameter names like
page-size,page[size], orfoo.barwon't work as-is. Commander converts option flags with dashes to camelCase properties (e.g.,--page-size→options.pageSize), but the generated code at lines 150-154 and 206-212 usesqp.namedirectly in both the option flag and property access.Use a kebab-to-camel conversion (already available via
kebabToCamelutility and applied ingraphql-commands-emitter.ts) and bracket notation for safe property access, matching the GraphQL commands pattern:Example fix pattern from graphql-commands-emitter.ts
const optionAccessor = kebabToCamel(qp.name); // Convert name to camelCase `if (options[${JSON.stringify(optionAccessor)}] !== undefined) queryOpts[${JSON.stringify(qp.name)}] = options[${JSON.stringify(optionAccessor)}];`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/generator/commands-emitter.ts` around lines 150 - 154, The generated CLI options use raw qp.name for both the flag and property access which breaks for names like "page-size" or "page[size]"; update the endpoint.queryParams mapping in commands-emitter.ts to compute an optionAccessor via the existing kebabToCamel(qp.name) and emit option access using bracket notation (e.g., options[optionAccessor]) while keeping the flag as `--${qp.name}`; also apply the same conversion when building queryOpts (the code block analogous to graphql-commands-emitter.ts) so you set queryOpts[qp.name] = options[optionAccessor] only when options[optionAccessor] !== undefined. Ensure you reference kebabToCamel, qp.name, optionAccessor, options and queryOpts to find and fix both the option emission (previously lines ~150-154) and the property-access logic (previously lines ~206-212).
- Update claude-ci-auto-fix.yml workflow trigger to match renamed '🔍 CI Quality Checks' workflow name - Remove broken manual OIDC token wiring from publish-to-npm.yml; npm CLI v11.5.1+ handles OIDC exchange automatically via `npm publish --provenance` 🤖 Generated with Claude Code Co-Authored-By: Claude
|
Both CodeRabbit findings addressed in 616e5d1:
Both fixes upstreamed to Lisa templates ( |
Summary
@codyswann/lisaand apply TypeScript stack governance templates (ESLint, Prettier, Vitest, commitlint, Husky, knip, ast-grep)eslint.config.local.ts,vitest.config.local.ts, thresholds) for initial onboarding of pre-existing CLI codebaserelease.ymlwith Lisa's reusablepublish-to-npm.ymlworkflowci.ymlwith Lisa's reusable quality workflowTest plan
bun run buildexits 0 (tsup producesdist/cli.js)bun run typecheckexits 0bun run lintexits 0 (with relaxed thresholds for onboarding)bun run testexits 0 (all 66 existing tests pass)bun run knipexits 0bun run format:checkexits 0release.ymlruns successfully and publishes to npm via reusable workflow🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores