Skip to content

feat: add global search command#23

Open
dnikolaev wants to merge 1 commit into
chatwoot:mainfrom
summeet-co:feat/global-search
Open

feat: add global search command#23
dnikolaev wants to merge 1 commit into
chatwoot:mainfrom
summeet-co:feat/global-search

Conversation

@dnikolaev

Copy link
Copy Markdown

Summary

Adds chatwoot search "<query>" — a global search across conversations, contacts, messages, and help-center articles via the account-scoped /search endpoint, with --only=<bucket> to restrict to a single bucket. Text, JSON, CSV, and quiet output; --only is honored consistently across every format.

Implements the proposal in #22 — happy to discuss the top-level-command-vs-flag question there before merge.

Tests

internal/sdk/search_test.go and internal/cmd/search_test.go cover request params, four-bucket decoding, --only (text + JSON), and the text/JSON/CSV/quiet/no-result paths.

/search isn't in the bundled application_swagger.json (only /contacts/search is), so these use httptest rather than the swagger contract harness — consistent with the existing internal/cmd/*_test.go.

Notes

README usage, the agent skill (command table + read-only allowlist), and CHANGELOG updated.

@dnikolaev dnikolaev force-pushed the feat/global-search branch from 5d23d1c to 43d584f Compare June 18, 2026 04:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d23d1c97f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/sdk/search.go Outdated
PortalSlug string `json:"portal_slug"`
AccountID int `json:"account_id"`
CategoryName string `json:"category_name"`
Status int `json:"status"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Treat article search status as a string

Chatwoot article responses use the same enum status strings already modeled by HelpCenterArticle.Status (internal/sdk/help_center.go:66), so when /search returns an article with "status":"published" this int field makes JSON decoding fail before anything is printed and before --only can discard article results. That means otherwise valid global searches start returning failed to decode response whenever the query matches an article.

Useful? React with 👍 / 👎.

Comment thread internal/cmd/search.go
if c.Only == "" {
return fmt.Errorf("csv output needs a single bucket; use --only=<bucket> or -o json")
}
return printSearchSection(app, resp, c.Only, false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve CSV format for empty search buckets

In the -o csv --only=... path, empty buckets still go through printSearchSection, whose no-result branches write prose such as No conversations found. instead of calling PrintTable with zero rows. A successful search where the selected bucket is empty therefore produces non-CSV output, breaking scripts that requested CSV; the existing row printers would emit valid headers even for empty slices.

Useful? React with 👍 / 👎.

Add `chatwoot search <query>`: a global search across conversations,
contacts, messages, and help-center articles via the account-scoped
/search endpoint. Supports `--only` to restrict output to a single bucket,
and text, JSON, CSV, and quiet rendering (type-prefixed IDs without
`--only`, plain IDs with it).

The /search endpoint is not part of Chatwoot's published OpenAPI spec, so
the SDK service and command are covered by httptest-based tests rather than
the swagger contract harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dnikolaev dnikolaev force-pushed the feat/global-search branch from 43d584f to 8a10e70 Compare June 18, 2026 05:39
@dnikolaev

Copy link
Copy Markdown
Author

Thanks for the review @chatgpt-codex-connector — both points are addressed in 8a10e70:

  • P1 — article status type: ArticleSearchResult.Status is now a string, matching the Article status enum (and the existing HelpCenterArticle.Status), so a search that matches an article no longer fails to decode. Test fixtures updated to "published" to cover the regression.
  • P2 — CSV for empty buckets: with -o csv --only=<bucket>, an empty bucket now emits the header row instead of the No X found. prose, keeping scripted output valid CSV. Added TestSearchCSVEmptyBucketEmitsHeader.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a10e70d39

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/search.go
Comment on lines +18 to +21
resp, err := app.Client.Search().Global(sdk.SearchOptions{
Query: c.Query,
Page: c.Page,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route --only searches to bucket endpoints

When --only is provided this still calls the global /search endpoint, which Chatwoot's SearchController#index handles as search('all'); the bucket routes (/search/messages, /search/contacts, etc.) are the ones that avoid running the other searches. On large accounts, or when an unrelated bucket is slow/errors, chatwoot search --only=messages ... can time out or fail even though the requested messages search would succeed, so Only should affect the SDK request rather than just filtering the rendered payload.

Useful? React with 👍 / 👎.

@dnikolaev

Copy link
Copy Markdown
Author

@scmmishra @vishnu-narayanan — could I get your steer on this one before I implement it?

Codex (above) suggests routing --only to the per-bucket endpoints (/search/messages, /search/contacts, …) instead of the current approach, which calls the aggregate /search (SearchController#indexsearch('all')) and filters the requested bucket client-side.

The trade-off as I see it:

  • Per-type routing (Codex's suggestion): the server runs only the requested bucket, so --only=messages isn't slowed down or failed by the other three searches. Output stays the same (the per-bucket conversation route even returns additional_attributes, which the aggregate omits).
  • Current client-side filter: one endpoint and one response path — simplest — but a slow/erroring unrelated bucket can still affect an --only call.

One caveat either way: neither /search nor the per-bucket /search/{type} routes are in the published OpenAPI spec (only /contacts/search is), so part of this is how much you'd like the CLI to lean on those dashboard-internal endpoints — related to the question in #22.

Happy to implement per-type routing if you'd prefer it — just let me know which direction you want.

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