/btw is a slash command that lets users ask a quick side question without interrupting the main conversation flow. It forks a lightweight, single-turn API call using the existing conversation context but with no tools available.
The response is displayed inline and can be dismissed with Space/Enter/Escape to resume the main conversation.
User types "/btw how do I format a date?"
|
v
[Command Registration] -- checks feature flag H96()
|
v
[Entry Point: ubY()] -- validates input, increments btwUseCount
|
v
[React Component: IbY] -- renders UI (spinner, question, response)
|
v
[Context Gathering: xbY()] -- collects system prompt, user context, conversation messages
|
v
[API Call: C04()] -- single-turn, no-tools, forked API call
|
v
[Display Response] -- markdown-rendered answer, dismissible with Space/Enter/Escape
The entire feature is behind a feature flag. All UI elements and the command registration check this before enabling.
function isBtwEnabled() {
return getFeatureFlag("tengu_marble_whisper2", false);
}Source (minified):
// cli.js:321710
function H96() {
return W8("tengu_marble_whisper2", !1);
}Used to detect and match the /btw command at the start of user input.
const BTW_REGEX = /^\/btw\b/gi;Source (minified):
// cli.js:321761
et9 = /^\/btw\b/gi;const btwCommand = {
type: "local-jsx",
name: "btw",
description: "Ask a quick side question without interrupting the main conversation",
isEnabled: () => isBtwEnabled(),
isHidden: false,
immediate: true, // executes immediately, no confirmation step
argumentHint: "<question>",
load: () => Promise.resolve().then(() => loadBtwModule()),
userFacingName() {
return "btw";
},
};Source (minified):
// cli.js:443857-443871
mbY = {
type: "local-jsx",
name: "btw",
description:
"Ask a quick side question without interrupting the main conversation",
isEnabled: () => H96(),
isHidden: !1,
immediate: !0,
argumentHint: "<question>",
load: () => Promise.resolve().then(() => (I4q(), C4q)),
userFacingName() {
return "btw";
},
};The command handler validates input, increments usage counter, and renders the component.
async function handleBtwCommand(emit, context, rawArgs) {
const question = rawArgs?.trim();
// Show usage if no question provided
if (!question) {
emit("Usage: /btw <your question>", { display: "system" });
return null;
}
// Increment usage count (used to suppress tooltip after first use)
updateState((state) => ({ ...state, btwUseCount: state.btwUseCount + 1 }));
// Render the side question component
return React.createElement(BtwComponent, {
question,
context,
onDone: emit,
});
}Source (minified):
// cli.js:443830-443838
async function ubY(A, q, K) {
let Y = K?.trim();
if (!Y)
return (A("Usage: /btw <your question>", { display: "system" }), null);
return (
i1((z) => ({ ...z, btwUseCount: z.btwUseCount + 1 })),
B2.createElement(IbY, { question: Y, context: q, onDone: A })
);
}An Ink (React for CLI) component that manages the full lifecycle: spinner -> fetch -> display -> dismiss.
function BtwComponent({ question, context, onDone }) {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [frame, setFrame] = useState(0);
// Animate spinner at 80ms intervals while waiting
useInterval(() => setFrame((f) => f + 1), response || error ? null : 80);
// Dismiss on Space, Enter, or Escape
useInput((input, key) => {
if (key.escape || key.return || input === " ") {
onDone(undefined, { display: "skip" });
}
});
// Fetch answer on mount
useEffect(() => {
const controller = new AbortController();
(async () => {
try {
const cacheSafeParams = await gatherContext(context);
const result = await callSideQuestion({ question, cacheSafeParams });
if (!controller.signal.aborted) {
if (result.response) setResponse(result.response);
else setError("No response received");
}
} catch (err) {
if (!controller.signal.aborted) setError(err.message || "Failed to get response");
}
})();
return () => controller.abort();
}, [question, context]);
return (
<Box flexDirection="column" paddingLeft={2} marginTop={1}>
{/* Question header */}
<Box>
<Text color="warning" bold>/btw </Text>
<Text dimColor>{question}</Text>
</Box>
{/* Response area */}
<Box marginTop={1} marginLeft={2}>
{error ? (
<Text color="error">{error}</Text>
) : response ? (
<MarkdownRenderer>{response}</MarkdownRenderer>
) : (
<Box>
<Spinner frame={frame} messageColor="warning" />
<Text color="warning">Answering...</Text>
</Box>
)}
</Box>
{/* Dismiss hint */}
{(response || error) && (
<Box marginTop={1}>
<Text dimColor>Press Space, Enter, or Escape to dismiss</Text>
</Box>
)}
</Box>
);
}Source (minified):
// cli.js:443685-443803
function IbY(A) {
let q = e(21),
{ question: K, context: Y, onDone: z } = A,
[_, w] = vT6.useState(null),
[O, $] = vT6.useState(null),
[H, j] = vT6.useState(0),
J;
if (q[0] === Symbol.for("react.memo_cache_sentinel"))
((J = () => j(bbY)), (q[0] = J));
else J = q[0];
aD(J, _ || O ? null : 80);
let M;
if (q[1] !== z)
((M = (v, N) => {
if (N.escape || N.return || v === " ") z(void 0, { display: "skip" });
}),
(q[1] = z),
(q[2] = M));
else M = q[2];
KA(M);
let D, X;
if (q[3] !== Y || q[4] !== K)
((D = () => {
let v = O3();
return (
(async function () {
try {
let L = await xbY(Y),
h = await C04({ question: K, cacheSafeParams: L });
if (!v.signal.aborted)
if (h.response) w(h.response);
else $("No response received");
} catch (L) {
let h = L;
if (!v.signal.aborted) $(h.message || "Failed to get response");
}
})(),
() => {
v.abort();
}
);
}),
(X = [K, Y]),
(q[3] = Y),
(q[4] = K),
(q[5] = D),
(q[6] = X));
else ((D = q[5]), (X = q[6]));
vT6.useEffect(D, X);
let P;
if (q[7] === Symbol.for("react.memo_cache_sentinel"))
((P = B2.createElement(T, { color: "warning", bold: !0 }, "/btw", " ")),
(q[7] = P));
else P = q[7];
let W;
if (q[8] !== K)
((W = B2.createElement(
m,
null,
P,
B2.createElement(T, { dimColor: !0 }, K),
)),
(q[8] = K),
(q[9] = W));
else W = q[9];
let Z;
if (q[10] !== O || q[11] !== H || q[12] !== _)
((Z = B2.createElement(
m,
{ marginTop: 1, marginLeft: 2 },
O
? B2.createElement(T, { color: "error" }, O)
: _
? B2.createElement(S_, null, _)
: B2.createElement(
m,
null,
B2.createElement(kQ6, { frame: H, messageColor: "warning" }),
B2.createElement(T, { color: "warning" }, "Answering..."),
),
)),
(q[10] = O),
(q[11] = H),
(q[12] = _),
(q[13] = Z));
else Z = q[13];
let f;
if (q[14] !== O || q[15] !== _)
((f =
(_ || O) &&
B2.createElement(
m,
{ marginTop: 1 },
B2.createElement(
T,
{ dimColor: !0 },
"Press Space, Enter, or Escape to dismiss",
),
)),
(q[14] = O),
(q[15] = _),
(q[16] = f));
else f = q[16];
let G;
if (q[17] !== W || q[18] !== Z || q[19] !== f)
((G = B2.createElement(
m,
{ flexDirection: "column", paddingLeft: 2, marginTop: 1 },
W,
Z,
f,
)),
(q[17] = W),
(q[18] = Z),
(q[19] = f),
(q[20] = G));
else G = q[20];
return G;
}Collects system prompt, user context (CLAUDE.md etc.), and conversation messages so the side question has full awareness of the current session.
async function gatherContext(toolUseContext) {
// Try cached context first
const cached = getCachedContext();
if (cached) {
return {
systemPrompt: cached.systemPrompt,
userContext: cached.userContext,
systemContext: cached.systemContext,
toolUseContext,
forkContextMessages: toolUseContext.messages,
};
}
// Build fresh context in parallel
const [systemPromptParts, userContext, systemContext] = await Promise.all([
buildSystemPrompt(tools, model, [], mcpClients),
getUserContext(), // CLAUDE.md, user instructions
getSystemContext(), // environment info
]);
return {
systemPrompt: formatSystemPrompt(systemPromptParts),
userContext,
systemContext,
toolUseContext,
forkContextMessages: toolUseContext.messages,
};
}Source (minified):
// cli.js:443807-443828
async function xbY(A) {
let q = hk1();
if (q)
return {
systemPrompt: q.systemPrompt,
userContext: q.userContext,
systemContext: q.systemContext,
toolUseContext: A,
forkContextMessages: A.messages,
};
let [K, Y, z] = await Promise.all([
R0(A.options.tools, A.options.mainLoopModel, [], A.options.mcpClients),
y2(),
rO(),
]);
return {
systemPrompt: Bq(K),
userContext: Y,
systemContext: z,
toolUseContext: A,
forkContextMessages: A.messages,
};
}The heart of the feature — a single-turn, tool-less, forked API call.
async function callSideQuestion({ question, cacheSafeParams }) {
const systemReminder = `<system-reminder>This is a side question from the user. You must answer this question directly in a single response.
CRITICAL CONSTRAINTS:
- You have NO tools available - you cannot read files, run commands, search, or take any actions
- This is a one-off response - there will be no follow-up turns
- You can ONLY provide information based on what you already know from the conversation context
- NEVER say things like "Let me try...", "I'll now...", "Let me check...", or promise to take any action
- If you don't know the answer, say so - do not offer to look it up or investigate
Simply answer the question with the information you have.</system-reminder>
${question}`;
const result = await queryAPI({
promptMessages: [createUserMessage({ content: systemReminder })],
cacheSafeParams,
canUseTool: async () => ({
behavior: "deny",
message: "Side questions cannot use tools",
decisionReason: { type: "other", reason: "side_question" },
}),
querySource: "side_question",
forkLabel: "side_question",
maxTurns: 1, // strictly one API round-trip
skipCacheWrite: true, // don't pollute the main conversation cache
});
// Extract text response from assistant message
const textBlock = result.messages
.find((m) => m.type === "assistant")
?.message?.content?.find((c) => c.type === "text");
return {
response: textBlock && textBlock.type === "text" ? textBlock.text.trim() : null,
usage: result.totalUsage,
};
}Source (minified):
// cli.js:321722-321755
async function C04({ question: A, cacheSafeParams: q }) {
let K = `<system-reminder>This is a side question from the user. You must answer this question directly in a single response.
CRITICAL CONSTRAINTS:
- You have NO tools available - you cannot read files, run commands, search, or take any actions
- This is a one-off response - there will be no follow-up turns
- You can ONLY provide information based on what you already know from the conversation context
- NEVER say things like "Let me try...", "I'll now...", "Let me check...", or promise to take any action
- If you don't know the answer, say so - do not offer to look it up or investigate
Simply answer the question with the information you have.</system-reminder>
${A}`,
Y = await WR({
promptMessages: [d1({ content: K })],
cacheSafeParams: q,
canUseTool: async () => ({
behavior: "deny",
message: "Side questions cannot use tools",
decisionReason: { type: "other", reason: "side_question" },
}),
querySource: "side_question",
forkLabel: "side_question",
maxTurns: 1,
skipCacheWrite: !0,
}),
_ = Y.messages
.find((O) => O.type === "assistant")
?.message?.content?.find((O) => O.type === "text");
return {
response: _ && _.type === "text" ? _.text.trim() : null,
usage: Y.totalUsage,
};
}When the spinner has been active for >30 seconds and the user has never used /btw, a tip is shown.
const showBtwTip = tipsEnabled && elapsed > 30000 && isBtwEnabled() && !getState().btwUseCount;
const tip = showClearTip && !hasTip
? "Use /clear to start fresh when switching topics and free up context"
: showBtwTip && !hasTip
? "Use /btw to ask a quick side question without interrupting Claude's current work"
: defaultTip;Source (minified):
// cli.js:321906-321914
let a = M.spinnerTipsEnabled !== !1,
s = a && t > 1800000,
n = a && t > 30000 && H96() && !D1().btwUseCount,
l =
s && !g
? "Use /clear to start fresh when switching topics and free up context"
: n && !g
? "Use /btw to ask a quick side question without interrupting Claude's current work"
: z,A dimmed hint shown in the bottom help bar when the feature is enabled.
isBtwEnabled() && <Text dimColor>/btw for side question</Text>Source (minified):
// cli.js:456982-456988
V6 =
H96() &&
L7.createElement(
m,
null,
L7.createElement(T, { dimColor: K }, "/btw for side question"),
);The btwUseCount is initialized in the global state store at 0:
// cli.js:533767-533769
{
memoryUsageCount: 0,
promptQueueUseCount: 0,
btwUseCount: 0,
todoFeatureEnabled: true,
showExpandedTodos: false,
}| Aspect | Detail |
|---|---|
| Purpose | Ask a quick question without disrupting main conversation |
| Feature flag | tengu_marble_whisper2 |
| Tools | All denied — text-only response |
| Turns | Exactly 1 (single round-trip) |
| Context | Full conversation context is forked/passed |
| Cache | Skips cache write to avoid side effects |
| State tracking | btwUseCount — suppresses tooltip after first use |
| Dismissal | Space / Enter / Escape |
| System prompt | Explicitly tells model: no tools, no actions, answer with what you know |
When porting to another project:
- Feature flag — Add a toggle to enable/disable the feature
- Command registration — Register
/btwas animmediateslash command with<question>argument - Input validation — Show usage hint if no question text provided
- Context gathering — Collect system prompt + user context + conversation messages
- API call — Fork a single-turn call with:
maxTurns: 1- All tool use denied
skipCacheWrite: true- System reminder constraining the model to text-only answers
- UI component — Spinner while loading, markdown-rendered response, dismissible overlay
- Usage tracking — Count uses to suppress the onboarding tooltip
- Spinner tip — Show tip after 30s of spinner if user hasn't tried
/btwyet - Help bar — Add
/btw for side questionhint