Skip to content

Execute shortcuts from server catalog#17

Merged
akcorca merged 7 commits into
mainfrom
codex/dynamic-server-shortcuts
May 25, 2026
Merged

Execute shortcuts from server catalog#17
akcorca merged 7 commits into
mainfrom
codex/dynamic-server-shortcuts

Conversation

@akcorca
Copy link
Copy Markdown
Contributor

@akcorca akcorca commented May 25, 2026

Summary\n- dispatch product shortcuts through the server-owned /api/client command catalog\n- add a catalog execution engine for HTTP, multipart, download, websocket, and shaped output commands\n- remove resource/action hardcoded shortcut dispatch from the Go CLI\n\n## Validation\n- go test ./...

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 카탈로그 기반 CLI 명령 실행 지원 추가
    • 에이전트 작업 모니터링 기능 강화 (폴링 및 상태 스냅샷)
    • HTTP, 다운로드, 멀티파트, WebSocket 전송 방식 지원
  • 문서

    • 아키텍처 설명 업데이트 - 카탈로그 디스커버리 및 명령 실행 계획 중심으로 재정리
  • 리팩토링

    • 리소스 라우팅 로직 개선으로 카탈로그 기반 리소스 유연하게 처리
    • 도움말 출력 로직 최적화

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Warning

Review limit reached

@akcorca, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 1 review of capacity. Refill in 30 minutes and 56 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d31c1ed1-4c07-4a0f-8377-e869bead8d05

📥 Commits

Reviewing files that changed from the base of the PR and between 631d815 and dbb34f0.

📒 Files selected for processing (17)
  • VERSION
  • docs/architecture.md
  • internal/craken/catalog_binding.go
  • internal/craken/catalog_command.go
  • internal/craken/catalog_contract.go
  • internal/craken/catalog_poll.go
  • internal/craken/catalog_websocket.go
  • internal/craken/client.go
  • internal/craken/command.go
  • internal/craken/command_test.go
  • internal/craken/generic.go
  • internal/craken/output.go
  • internal/craken/realtime.go
  • internal/craken/resolve.go
  • internal/craken/resources.go
  • internal/craken/util.go
  • internal/craken/wiki_agent.go

Walkthrough

Server /api/client catalog을 기반으로 CLI 명령을 동적으로 발견 및 실행하도록 라우팅을 전환합니다. 고정된 자원 switch에서 catalog engine으로 변경되며 경로 바인딩, 요청 구성, HTTP/download/multipart/websocket 분기 실행을 포함합니다. 테스트 인프라가 공통 catalog 응답으로 마이그레이션됩니다.

Changes

Catalog-기반 동적 명령 실행

Layer / File(s) Summary
Catalog 데이터 모델
internal/craken/generic.go, internal/craken/resources.go
route 스키마에 pathParams/queryParams 필드가 추가되고 cliCommand 및 실행 구조체들이 execution/variants/binding 메타데이터를 확장하여 JSON 역직렬화 호환성을 높입니다. resources.go에서 hardcoded 자원 처리 로직 360줄이 제거되어 methodUpper 유틸만 유지됩니다.
Catalog 명령 실행 엔진
internal/craken/catalog_command.go
502줄의 신규 파일로 catalog 조회 후 자원/동작으로 명령 선택, variant 조건 병합, 라우트 경로 바인딩, transport별 실행 분기(HTTP/download/multipart/websocket)를 구현합니다. 경로 파라미터는 옵션/플래그/리터럴/resolver(workspace/channel/participant/agent)로부터 수집되며, HTTP 요청은 body/query/header를 동적으로 구성합니다.
명령 라우팅 및 도움말 통합
internal/craken/command.go
Run switch의 default 분기가 unknown resource 에러에서 runCatalogCommand() 호출로 변경되고, help 출력에서 pathParameters와 pathParams 중 비어 있지 않은 쪽을 우선 사용하는 fallback 로직이 추가됩니다.
테스트 Catalog 헬퍼 및 마이그레이션
internal/craken/command_test.go
writeTestCatalog, testCommand, testRoute 헬퍼 함수가 도입되어 모든 테스트가 공통 /api/client catalog 응답을 재사용하도록 변경되며, 각 핸들러 시작 시점에 catalog 응답 처리가 추가되어 테스트 중복을 제거합니다.
에이전트 잡 폴링
internal/craken/wiki_agent.go
watchAgentJob이 interval/max-polls 옵션으로 /agent-jobs/{jobID}를 반복 조회하고, verbose가 아니면 상태/플랜/에러 변경 시에만 JSON 스냅샷을 출력합니다. 폴링은 cancelled/completed/failed 상태 또는 최대 횟수 도달 시 종료됩니다.
아키텍처 문서 및 정리
docs/architecture.md
CLI가 /api/client catalog를 통해 단축 명령을 발견/실행하고, 기본/운영 도움말이 서버 소유 명령 문법을 렌더링하며 로컬 옵션으로 보강한다는 내용으로 재서술됩니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • corca-ai/craken-cli#5: /api/client catalog의 help 렌더링 및 --format text 출력 변경(printCatalogHelp, clientCatalog.Commands)이 이 PR의 catalog 구조/출력 흐름과 직접 연계됩니다.
  • corca-ai/craken-cli#13: internal/craken/command.go의 focused help 렌더링이 이 PR의 catalog 라우팅 및 파라미터 필드 fallback 로직과 같은 맥락에서 catalog 메타데이터를 사용합니다.
  • corca-ai/craken-cli#4: server-driven /api/client catalog의 help 및 명령 실행 로직이 이 PR과 중첩되며, internal/craken/command*.go 수정이 공통입니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 풀 리퀘스트의 주요 변경사항을 명확하게 요약하고 있습니다. '서버 카탈로그에서 단축 실행'이라는 표현은 hardcoded 리소스/액션 디스패치에서 서버 소유의 /api/client 카탈로그 기반 명령 실행으로의 전환이라는 핵심 목표를 정확하게 반영합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/dynamic-server-shortcuts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@akcorca akcorca force-pushed the codex/dynamic-server-shortcuts branch from 631d815 to fdb2828 Compare May 25, 2026 11:46
@akcorca akcorca force-pushed the codex/dynamic-server-shortcuts branch from fdb2828 to fd35e68 Compare May 25, 2026 11:46
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/craken/catalog_command.go`:
- Around line 441-458: runCatalogDownloadCommand currently calls client.raw with
an empty requestSpec and only the path, so catalog query parameters and header
overrides (e.g., Accept) are ignored; update runCatalogDownloadCommand to build
and pass a populated requestSpec (including QueryParams from the route/catalog
binding and Headers such as an overridden "Accept" from cmd.string or the route)
to client.raw instead of requestSpec{} so the download request includes the
catalog's query and header overrides (locate changes around
runCatalogDownloadCommand, client.raw call, requestSpec construction, and where
cmd.string("output", "") is read).
- Around line 86-105: The mergeExecution function currently replaces
base.PathParams, base.QueryParams, and base.BodyFields when variant provides
them, which drops existing base bindings (e.g., workspaceId); instead, when
variant.PathParams/QueryParams/BodyFields are non-nil, merge their key/value
pairs into the corresponding base maps (creating the base map if nil) so variant
values override specific keys but other base entries remain; keep the existing
behavior for scalar fields (OperationID, Transport, Output) but update the
map-handling logic in mergeExecution to perform an in-place merge rather than
whole-map replacement.
- Around line 495-501: The optionText function currently returns an unused error
which triggers golangci-lint unparam; change the signature from func
optionText(cmd command, names []string) (string, error) to func optionText(cmd
command, names []string) string, have it return the found value or "" (keeping
the same loop and return ""), and then update all callers of optionText to stop
expecting an error (remove error variables / checks) and use the returned string
directly; references to command.string remain unchanged inside the function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 944c8bc6-cd6d-4029-8a5a-54c6ac2fd7e9

📥 Commits

Reviewing files that changed from the base of the PR and between 431aece and 631d815.

📒 Files selected for processing (7)
  • docs/architecture.md
  • internal/craken/catalog_command.go
  • internal/craken/command.go
  • internal/craken/command_test.go
  • internal/craken/generic.go
  • internal/craken/resources.go
  • internal/craken/wiki_agent.go
💤 Files with no reviewable changes (1)
  • internal/craken/wiki_agent.go
📜 Review details
🧰 Additional context used
🪛 GitHub Actions: CI / 0_test.txt
internal/craken/catalog_command.go

[error] 495-495: golangci-lint reported: optionText - result 1 (error) is always nil (unparam)

🪛 GitHub Actions: CI / test
internal/craken/catalog_command.go

[error] 495-495: golangci-lint: optionText - result 1 (error) is always nil (unparam)

🪛 GitHub Check: test
internal/craken/catalog_command.go

[failure] 495-495:
optionText - result 1 (error) is always nil (unparam)

🔇 Additional comments (1)
docs/architecture.md (1)

5-5: LGTM!

Also applies to: 9-9

Comment on lines +86 to +105
func mergeExecution(base commandExecution, variant commandExecutionVariant) commandExecution {
if variant.OperationID != "" {
base.OperationID = variant.OperationID
}
if variant.Transport != "" {
base.Transport = variant.Transport
}
if variant.Output != "" {
base.Output = variant.Output
}
if variant.PathParams != nil {
base.PathParams = variant.PathParams
}
if variant.QueryParams != nil {
base.QueryParams = variant.QueryParams
}
if variant.BodyFields != nil {
base.BodyFields = variant.BodyFields
}
return base
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

variant 바인딩을 통째로 덮어쓰면 기본 실행 계획이 사라집니다.

여기서는 variant의 PathParams/QueryParams/BodyFields가 있을 때 base 맵을 병합하지 않고 전체 교체합니다. 그래서 variant가 일부 필드만 오버라이드해도 base에 있던 workspaceId 같은 공통 바인딩이 사라져 실행이 깨집니다.

예시 수정
 func mergeExecution(base commandExecution, variant commandExecutionVariant) commandExecution {
 	if variant.OperationID != "" {
 		base.OperationID = variant.OperationID
 	}
 	if variant.Transport != "" {
 		base.Transport = variant.Transport
 	}
 	if variant.Output != "" {
 		base.Output = variant.Output
 	}
-	if variant.PathParams != nil {
-		base.PathParams = variant.PathParams
-	}
-	if variant.QueryParams != nil {
-		base.QueryParams = variant.QueryParams
-	}
-	if variant.BodyFields != nil {
-		base.BodyFields = variant.BodyFields
-	}
+	base.PathParams = mergeCommandBindings(base.PathParams, variant.PathParams)
+	base.QueryParams = mergeCommandBindings(base.QueryParams, variant.QueryParams)
+	base.BodyFields = mergeCommandBindings(base.BodyFields, variant.BodyFields)
 	return base
 }
+
+func mergeCommandBindings(base, override map[string]commandBinding) map[string]commandBinding {
+	if len(base) == 0 && len(override) == 0 {
+		return nil
+	}
+	out := make(map[string]commandBinding, len(base)+len(override))
+	for key, value := range base {
+		out[key] = value
+	}
+	for key, value := range override {
+		out[key] = value
+	}
+	return out
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/craken/catalog_command.go` around lines 86 - 105, The mergeExecution
function currently replaces base.PathParams, base.QueryParams, and
base.BodyFields when variant provides them, which drops existing base bindings
(e.g., workspaceId); instead, when variant.PathParams/QueryParams/BodyFields are
non-nil, merge their key/value pairs into the corresponding base maps (creating
the base map if nil) so variant values override specific keys but other base
entries remain; keep the existing behavior for scalar fields (OperationID,
Transport, Output) but update the map-handling logic in mergeExecution to
perform an in-place merge rather than whole-map replacement.

Comment on lines +441 to +458
func runCatalogDownloadCommand(ctx context.Context, client *client, route route, cmd command, path string, stdout io.Writer) error {
response, err := client.raw(ctx, route.Method, path, requestSpec{})
if err != nil {
return err
}
defer func() { _ = response.Body.Close() }()
bytes, err := io.ReadAll(response.Body)
if err != nil {
return err
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
return fmt.Errorf("%s %s failed with %d: %s", route.Method, path, response.StatusCode, string(bytes))
}
if output := cmd.string("output", ""); output != "" {
return os.WriteFile(filepath.Clean(output), bytes, 0o600)
}
_, err = stdout.Write(bytes)
return err
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

download 요청이 catalog query/header 바인딩을 전혀 반영하지 않습니다.

이 경로는 client.raw를 빈 requestSpec와 순수 path로 호출해서, catalog의 queryParams--accept 같은 헤더 오버라이드가 모두 무시됩니다. 다운로드 명령이 버전/포맷 쿼리에 의존하면 잘못된 리소스를 받게 됩니다.

예시 수정 방향
-func runCatalogDownloadCommand(ctx context.Context, client *client, route route, cmd command, path string, stdout io.Writer) error {
-	response, err := client.raw(ctx, route.Method, path, requestSpec{})
+func runCatalogDownloadCommand(
+	ctx context.Context,
+	client *client,
+	route route,
+	plan commandExecution,
+	cmd command,
+	path string,
+	resolved map[string]string,
+	consumed map[string]bool,
+	stdout io.Writer,
+) error {
+	values, err := catalogValues(ctx, client, cmd, plan.QueryParams, resolved, consumed)
+	if err != nil {
+		return err
+	}
+	if plan.QueryParams == nil {
+		values = requestValuesFromOptions(cmd, consumed)
+	}
+	response, err := client.raw(ctx, route.Method, appendQuery(path, values), requestSpec{
+		Headers: requestHeadersFromOptions(cmd),
+	})
 	if err != nil {
 		return err
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/craken/catalog_command.go` around lines 441 - 458,
runCatalogDownloadCommand currently calls client.raw with an empty requestSpec
and only the path, so catalog query parameters and header overrides (e.g.,
Accept) are ignored; update runCatalogDownloadCommand to build and pass a
populated requestSpec (including QueryParams from the route/catalog binding and
Headers such as an overridden "Accept" from cmd.string or the route) to
client.raw instead of requestSpec{} so the download request includes the
catalog's query and header overrides (locate changes around
runCatalogDownloadCommand, client.raw call, requestSpec construction, and where
cmd.string("output", "") is read).

Comment thread internal/craken/catalog_command.go Outdated
@akcorca akcorca merged commit 80508d8 into main May 25, 2026
2 checks passed
@akcorca akcorca deleted the codex/dynamic-server-shortcuts branch May 25, 2026 23:22
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.

1 participant