feat: add blocks preview command to open blocks in Block Kit Builder - #620
Conversation
Design for reworking `slack blocks preview` input handling (--blocks flag with `-` stdin sentinel) and folding in the seven code-review findings. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Baseline commit of the in-progress `slack blocks preview` feature before the input-rework and review-fix tasks. Opens Block Kit blocks in the Block Kit Builder, gated behind the block-kit-builder experiment. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Adds Os.Stdin() and IOStreams.IsStdinTTY(), mirroring Stdout()/IsTTY(), so callers can distinguish piped/redirected stdin from an interactive terminal even when stdout is still a TTY. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Replaces the positional argument with a --blocks flag, supports the - stdin sentinel and auto-detected piped stdin, and errors on a bare interactive invocation instead of blocking forever on stdin. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Reading blocks from stdin consumes the pipe, so the interactive team picker cannot prompt. Detect stdin input with more than one auth and no --team flag and return an actionable error instead of failing on EOF. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
An org-grid workspace install has an enterprise ID but is not an enterprise install; key off IsEnterpriseInstall so those installs open the Builder in the workspace context. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Guard against empty or scheme-less hosts that would otherwise yield a malformed URL, and wrap the url.Parse error with a structured code. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Hide the blocks command while gated behind the block-kit-builder experiment and drop its generated doc pages, which docgen omits for hidden commands. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Surface and allow the blocks command only when the CLI is invoked by an AI coding tool, reusing the existing useragent.GetAIAgent detection. Replaces the block-kit-builder experiment: the parent command is hidden from help/docs unless an AI tool is detected, and preview's PreRunE now returns command_unavailable for non-AI callers. Removes the BlockKitBuilder experiment. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
These agent-workflow planning artifacts under docs/superpowers/ are not part of the shipped product and are unreferenced. Remove them from the branch. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…tools The blocks preview command was gated two ways on AI-coding-tool detection: visibility (parent command Hidden) and execution (preview PreRunE returning command_unavailable for non-AI callers). Drop the execution gate so anyone can run the command, while keeping the visibility gate so it stays hidden from help and docs unless an AI coding tool is detected. Remove the now-orphaned command_unavailable error code (added by the same commit that introduced the gate, used nowhere else) and regenerate errors.md. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Remove the auto-detected stdin pipe from blocks preview so blocks are read from standard input only via the explicit --blocks - sentinel, matching the prevailing convention of comparable tools (kubectl -f -, gh --input -). This drops the one place the command diverged from those tools. Fix the file-input example to use the sentinel form and correct the inaccurate "piped from a file" wording (< is redirection, not a pipe), and update the Long description and remediation text to match the new behavior. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Remove comments that restate self-explanatory function names and bodies, and trim the rest to the non-obvious rationale. Comments-only change. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
- Guard `--blocks -` with IsStdinTTY() so an interactive terminal returns a clear error instead of blocking forever on stdin. - Return ErrCredentialsNotFound when no teams are logged in, instead of showing an empty team prompt. - Preserve the user's JSON key order in the Block Kit Builder URL by compacting the original input instead of re-marshalling the parsed value. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #620 +/- ##
==========================================
+ Coverage 71.91% 72.04% +0.13%
==========================================
Files 231 233 +2
Lines 19492 19659 +167
==========================================
+ Hits 14018 14164 +146
- Misses 4243 4254 +11
- Partials 1231 1241 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
lukegalbraithrussell
left a comment
There was a problem hiding this comment.
Approving as docs/ code owner
zimeg
left a comment
There was a problem hiding this comment.
@WilliamBergamin Awesome changes to see 🧱 🔍
I'm approving now with solid confidences but do hope some of the changes following are interesting! Nothing is blocking 🔨 FWIW! Comments of discussion include:
- Making this command unhidden: This command is nice to surface next release!
- Wording around Block Kit Builder: Quibbles on wording for polish if it reads well?
- Missing authentications: Are default teams for BKB alright to use?
- Refactor of utilities and inlines: JSON and reading STDIN might be good to move to internal packages?
- Error handling: Refactors might find kind errors wrapped between package!
🚢 Please feel free to add what seems most useful! I like the direction this goes! 🧭
| m.On("Stdout").Return(os.Stdout) | ||
| m.On("Stdin").Return(os.Stdin) |
There was a problem hiding this comment.
🧮 quibble: Alphabetical swap of "stdin" and "stdout" here and later in the mocks might be nice for later but no blocker!
| Stdout() File | ||
|
|
||
| // Stdin returns the file descriptor for stdin | ||
| Stdin() File |
There was a problem hiding this comment.
🌟 praise: Thanks for introducing standard input operations!
| // aiAgentFunc is a package variable so it can be stubbed in tests. | ||
| var aiAgentFunc = useragent.GetAIAgent |
There was a problem hiding this comment.
| // aiAgentFunc is a package variable so it can be stubbed in tests. | |
| var aiAgentFunc = useragent.GetAIAgent |
🪓 thought: I'd be alright making this command visible to standard use either now or later? This command seems so useful to casual block kit building IMHO!
There was a problem hiding this comment.
My aim here was to make it available only to AI tools for now since it would let us iterate on the command and introduce breaking changes until we decide to make it visible to everyone
| case flagChanged && flagValue == "-": | ||
| return readStdinBlocks(clients) |
There was a problem hiding this comment.
| case flagChanged && flagValue == "-": | |
| return readStdinBlocks(clients) | |
| case !flagChanged: | |
| return readStdinBlocks(clients) |
⚙️ question: Would an omitted --blocks flag be meaningful instead of the "-" value for gathering stdin inputs? I find the commands afterwards clean, but might not be familiar with all hopes for the command!
$ slack blocks preview < blocks.json
There was a problem hiding this comment.
So this was and pattern I considered originally but it creates weird edge cases with flags, like how do we properly handle
$ slack blocks preview --team TA1B2C3 < blocks.json
Maybe if we support no flags with the following then we don't support flags with this type of pattern 🤔
| return "", true, missingBlocksError() | ||
| } | ||
| return input, true, nil | ||
| } |
There was a problem hiding this comment.
🚂 suggestion: We can perhaps separate the input itself and mixing errors from command details with moving this function to:
internal/iostreams/reader.go
| func teamOrEnterpriseID(auth *types.SlackAuth) string { | ||
| if auth.IsEnterpriseInstall { | ||
| return auth.EnterpriseID | ||
| } | ||
| return auth.TeamID | ||
| } | ||
|
|
||
| func buildBlockKitBuilderURL(apiHost string, id string, blocksJSON string) (string, error) { |
There was a problem hiding this comment.
👁️🗨️ suggestion: We might pass auth to this buildBlockKitBuilderURL function to avoid added variables IMHO
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Previously the preview command auto-scoped the Block Kit Builder URL to the sole logged-in team, and silently ignored credential read failures when --team was set. Now the builder is team-less unless --team is passed explicitly, and a failure to read authentications for a --team lookup is returned instead of swallowed. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
zimeg
left a comment
There was a problem hiding this comment.
@WilliamBergamin Thanks for landing these changes! I think it moves things in an evermore better direction and agree that we can iterate while keeping this command hidden if needed 🚢 💨
| { | ||
| Meaning: "Preview blocks read from a redirect and scoped to a team", | ||
| Command: "blocks preview --team T0123456 --blocks - < blocks.json", | ||
| }, |
There was a problem hiding this comment.
💡 praise: Thanks for including this as an example! In ongoing iteration this might be a preferred pattern for me to use.
Changelog
Add the
slack blocks previewcommand to open Block Kit blocks in the Block Kit Builder.Summary
This adds a new
blocks previewcommand. It takes a set of Block Kit blocks and opens them in the Block Kit Builder in a web browser.Blocks are passed with the
--blocksflag. The value is a JSON array of blocks or a JSON object with ablocksarray. Pass-to read the blocks from standard input.The command is hidden from the help output unless an AI coding tool is detected. It is meant to be used by AI tools that generate Block Kit layouts.
Preview
(No UI change. The command prints the Builder URL and opens it in the browser.)
Testing
Build the CLI first:
Then run the examples below with
SLACK_DISABLE_TELEMETRY=true.Expected to work:
Preview a bare blocks array passed as a flag value.
Preview a full blocks object passed as a flag value.
Preview blocks read from a file on standard input.
Expected to error:
Pass a value that is not valid JSON. This returns an
unable_to_parse_jsonerror.Pass valid JSON that is not a blocks payload. This returns an
invalid_blockserror.Unit tests:
Notes
/block-kit-builder/{id}/builderwas verified in an authenticated browser.Requirements