/deploy api production.
They type /, pick your command, and fill in its options in place. Reintersect sends your app the
command with typed option values, and your app answers with content, an optional live block and a
short notice. The answer can be public, posted in the chat as your app, or private, shown only to the
member who ran the command.
Using Effect? See Commands and
Deferred answers in the Effect guide.
Define a command
src/deployCommand.tsx
options.service is a string, options.env is "staging" | "production" and options.dryRun is
boolean | undefined: the types come from the schemas you declare. Because the answer inserts
Deploy, the app must include that block, the same as for message actions and
events. See Composing an app.
Options
The name (
"deploy") is what members type after /: a lowercase letter, then up to 31 lowercase
letters, digits or -. Names are unique within the app. An app has at most 25 commands.
Option schemas
Options are one object schema, the same kind your blocks use for props: Zodz.object({...}),
ArkType type({...}), or Valibot v.object({...}) wrapped in toStandardJsonSchema(). Each
property is an option, and an optional property (.optional() in Zod and Valibot, "dryRun?" in
ArkType) is an optional option. Every option needs a description, which the member sees in the
composer: .describe() in Zod and ArkType, v.description() in Valibot.
The SDK reads each property’s JSON Schema and checks the values against your schema before your
handler runs, so options holds the schema’s output. A value that fails is answered with
422 invalid_options, and the member sees a failure card.
Choice labels
Each value of a choice shows its JSON Schematitle, or the value with a capital first letter when it
has none, so "early-access" reads “Early access”. To pick the labels, give each literal a title:
Pickers
member(), circle() and appObject(kind) mark a Zod or ArkType schema as a picker. They add
metadata that survives into the JSON Schema, so the option stays a plain property of your object
schema. In Valibot, add the same metadata with v.metadata(memberOption), v.metadata(circleOption)
or v.metadata(appObjectOption(kind)).
The schema you mark decides what your handler receives. An object schema parses the whole picked
value, so your handler gets that schema’s output:
Order
Options appear in the composer in the order you declare them, with required options first. ArkType object schemas sort their keys, so their options appear required first, then alphabetically. To keep the order you wrote, pass a record with one schema per key, and wrap optional ones inoptional():
Shapes that can’t be options
defineCommand throws when an option can’t be shown in the composer, and names the option: a nested
object that isn’t a picker, an array, a union of anything other than string literals, or a missing
description. reintersect check reports the same error. A command has at most 25 options. Option
names follow the same rule as message action ids: 1 to 64 characters from A-Z, a-z, 0-9, _,
. and -.
Optional options a member skips are absent from options.
Reintersect checks every value before it calls you. It looks members and circles up again and
supplies their names itself, so a member can’t send a name of their choosing. A choice must be one
of the values you declared.
For appObject, kind names one of your app’s reference kinds, and that
reference needs a search function so members can find objects. The id is
the url your search returned for the pick. Treat it like any other input: look the object up in
your own system and check that the member may use it.
Handler context
Answers
Return an answer, a deferral, or nothing:- Public
- Private
- Notice only
Timing
Reintersect waits up to 30 seconds for an answer and doesn’t retry. If your app fails, answers with an error or takes longer, the member sees a failure card with Run again. After 10 seconds the member sees that your app is still working.Deferring
For work that takes longer, defer. Passdefer a function that returns the answer:
202 at once, runs the function with waitUntil, and sends its
answer with POST /v0/invocations/respond. Until the answer
arrives, your app’s row reads “Deploys is thinking…”. You have 15 minutes.
By default only the member who ran the command sees your app thinking. Pass { visibility: "public" }
as the second argument, as above, to show that row to everyone in the circle or direct message. It’s
opt-in because the row shows which command ran and with which options, so declare it only when the
answer will be public too.
The deferral’s visibility only decides who sees your app thinking. The answer keeps its own
visibility: a private answer after a public deferral still reaches only the member who ran the
command, and the thinking row goes away for everyone else. Before your app answers the delivery, only
the member who ran the command sees that it’s running.
If the function throws, or returns an answer the SDK can’t send, the SDK answers for you with a
private danger notice: “Couldn’t finish this. Try again.” The member sees the failure card with
Run again right away, and the error goes to your logs. To say what went wrong in your own words,
catch the failure and return your own notice:
defer() with no function, or
defer(undefined, { visibility: "public" }), and pass invocation.interactionToken along. Answer
from anywhere with the app client:
ReintersectAppInteractionExpiredError. Hosted runtimes keep waitUntil work alive for a limited
time after the response, so hand work that can take minutes to a queue.
Where commands show
Members run commands from the composer in circle chats and in direct messages, in workspaces where the app is installed. A development install shows its commands only to its developer. In a circle, running a command invites your app there, like a mention or a message action: a member can run it in any public or private circle they are in, and your app reaches that circle from then on. A development install runs only in circles its developer is in. Commands never run in circles connected to another workspace or a Slack channel. A command sends your app only what the member picked, so adding or changing commands never needs an admin’s approval. See Publishing new versions.Over the wire
Without the SDK, a command arrives as acommand.run delivery with
{ command, options, surface, invocationId, interactionToken }. Answer 200 with
{ visibility, content?, block?: { type, props }, notice? }, or 202 with { visibility } (who sees
your app thinking: "public" or "private") and answer later. See the delivery protocol.
Testing
{ status: "deferred", visibility, interactionToken }. Call host.settle() and read
the answer with host.expectPushed("invocations.respond"). When the deferred task fails, that answer
is the SDK’s failure notice. See Testing.