@@ -37,17 +37,19 @@ const toolCategories: {
3737 description : string ;
3838 setupTitle : string ;
3939 setupBody : string ;
40+ primaryAction : string ;
4041 primaryRecipeId : string ;
4142 recipeIds : readonly string [ ] ;
4243} [ ] = [
4344 {
4445 id : "terminal" ,
4546 label : "Terminal" ,
4647 badge : "Recommended" ,
47- description : "Install Stint once for terminal, AI agent, and editor activity." ,
48- setupTitle : "Terminal setup " ,
48+ description : "Set up terminal, AI agent, and editor activity in one place ." ,
49+ setupTitle : "Install Stint " ,
4950 setupBody :
50- "Copy one command. It creates your key, installs Stint, writes config, and checks the connection." ,
51+ "One command creates your key, installs Stint, writes config, and checks the connection." ,
52+ primaryAction : "Install Stint" ,
5153 primaryRecipeId : "stint-cli-config" ,
5254 recipeIds : [ "stint-cli-config" ] ,
5355 } ,
@@ -56,9 +58,10 @@ const toolCategories: {
5658 label : "AI agents" ,
5759 badge : "Codex and Claude" ,
5860 description : "Track coding sessions from Codex or Claude Code." ,
59- setupTitle : "AI agent setup " ,
61+ setupTitle : "Install agent plugin " ,
6062 setupBody :
61- "Choose your agent below. Stint shows the marketplace plugin first, with CLI setup as the fallback." ,
63+ "Choose Codex or Claude Code below, then install the Stint marketplace plugin." ,
64+ primaryAction : "Install agent plugin" ,
6265 primaryRecipeId : "codex-config" ,
6366 recipeIds : [ "codex-config" , "claude-code-config" , "stint-cli-config" ] ,
6467 } ,
@@ -67,9 +70,10 @@ const toolCategories: {
6770 label : "Editors" ,
6871 badge : "VS Code, JetBrains, Vim" ,
6972 description : "Use familiar editor plugins with your Stint endpoint and key." ,
70- setupTitle : "Editor setup " ,
73+ setupTitle : "Install editor plugin " ,
7174 setupBody :
72- "Choose your editor below. Existing WakaTime-compatible plugins can send activity to Stint." ,
75+ "Choose your editor below, then point the plugin at your Stint endpoint and key." ,
76+ primaryAction : "Install editor plugin" ,
7377 primaryRecipeId : "vscode-config" ,
7478 recipeIds : [ "vscode-config" , "jetbrains-config" , "vim-config" ] ,
7579 } ,
@@ -107,6 +111,10 @@ function IntegrationsContent() {
107111 const agentRows = userAgents . data ?. data ?? [ ] ;
108112 const recentStintAgent = agentRows . find ( ( agent ) => isStintAgent ( agent ) ) ;
109113 const stintCLIConnected = Boolean ( recentStintAgent ) ;
114+ const latestGeneratedKeyUsed = Boolean (
115+ latestKeyId &&
116+ keys . data ?. data . some ( ( key ) => key . id === latestKeyId && key . last_used_at ) ,
117+ ) ;
110118 const configs = useMemo (
111119 ( ) => integrationConfigs ( apiURL , displayKey ) ,
112120 [ apiURL , displayKey ] ,
@@ -118,6 +126,24 @@ function IntegrationsContent() {
118126 const activeCategory =
119127 toolCategories . find ( ( category ) => category . id === activeToolCategory ) ??
120128 toolCategories [ 0 ] ;
129+ const connectionStatus =
130+ stintCLIConnected ||
131+ latestGeneratedKeyUsed ||
132+ validateMessage === "Stint is connected"
133+ ? "Stint is connected"
134+ : setupMessage || validateMessage === "Checking connection"
135+ ? "Waiting for first check-in"
136+ : validateMessage === "No check-in yet"
137+ ? "No check-in yet"
138+ : "Not connected yet" ;
139+ const connectionDetail =
140+ connectionStatus === "Stint is connected"
141+ ? `Last Stint check-in${ recentStintAgent ?. last_seen_at ? `: ${ formatLastSeen ( recentStintAgent . last_seen_at ) } ` : " found." } `
142+ : connectionStatus === "Waiting for first check-in"
143+ ? "Run the copied setup command, then verify the connection."
144+ : connectionStatus === "No check-in yet"
145+ ? "No Stint check-in has arrived yet. Run setup, then try again."
146+ : "Install Stint, then use Verify connection to confirm it is sending activity." ;
121147 const visibleClients = clients . filter ( ( client ) =>
122148 activeCategory . recipeIds . includes ( client . recipeId ) ,
123149 ) ;
@@ -185,10 +211,21 @@ function IntegrationsContent() {
185211 "generated-setup" ,
186212 stintConfiguredInstallCommand ( apiURL , apiKey ) ,
187213 ) ;
188- setSetupMessage ( "Setup command copied with your Stint key." ) ;
214+ setSetupMessage ( "Waiting for first check-in" ) ;
215+ } ;
216+ const openPrimarySetup = ( ) => {
217+ setSelectedIntegration ( activeCategory . primaryRecipeId ) ;
218+ window . history . replaceState ( null , "" , `#${ activeCategory . primaryRecipeId } ` ) ;
219+ } ;
220+ const runPrimaryAction = ( ) => {
221+ if ( activeToolCategory === "terminal" ) {
222+ void copyGeneratedSetup ( ) ;
223+ return ;
224+ }
225+ openPrimarySetup ( ) ;
189226 } ;
190227 const validateConnection = async ( ) => {
191- setValidateMessage ( "Checking for a Stint CLI check-in... " ) ;
228+ setValidateMessage ( "Checking connection " ) ;
192229 const [ agentsResult , keysResult ] = await Promise . all ( [
193230 userAgents . refetch ( ) ,
194231 keys . refetch ( ) ,
@@ -203,9 +240,7 @@ function IntegrationsContent() {
203240 ( agentsResult . data ?. data ?? [ ] ) . some ( ( agent ) => isStintAgent ( agent ) ) ||
204241 generatedKeyUsed ;
205242 setValidateMessage (
206- connected
207- ? "Yes, Stint CLI is connected."
208- : "No Stint CLI check-in yet. Run the copied command, then verify again." ,
243+ connected ? "Stint is connected" : "No check-in yet" ,
209244 ) ;
210245 } ;
211246
@@ -271,37 +306,44 @@ function IntegrationsContent() {
271306 < p className = "mb-3 max-w-2xl text-sm leading-6 text-zinc-300" >
272307 { activeCategory . setupBody }
273308 </ p >
274- < p className = "mt-3 text-sm text-zinc-400" >
275- { validateMessage ||
276- setupMessage ||
277- ( stintCLIConnected
278- ? `Yes, Stint CLI is connected${ recentStintAgent ?. last_seen_at ? ` · ${ formatLastSeen ( recentStintAgent . last_seen_at ) } ` : "" } .`
279- : activeToolCategory === "terminal"
280- ? "Copy setup, run it once, then verify the connection."
281- : "Pick a setup option below. Use Verify connection after you run Stint." ) }
282- </ p >
309+ < div className = "mt-4 rounded border border-line bg-ink p-3" >
310+ < div className = "text-[11px] font-medium uppercase tracking-[0.14em] text-zinc-500" >
311+ Connection status
312+ </ div >
313+ < div className = "mt-1 text-sm font-semibold text-zinc-100" >
314+ { connectionStatus }
315+ </ div >
316+ < p className = "mt-1 text-sm leading-5 text-zinc-500" >
317+ { connectionDetail }
318+ </ p >
319+ </ div >
283320 < div className = "mt-4 flex flex-col gap-2 sm:flex-row" >
284- { activeToolCategory === "terminal" ? (
285- < button
286- className = "inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-md bg-accent px-3 text-sm font-semibold text-ink hover:bg-sky-300 disabled:cursor-not-allowed disabled:opacity-60"
287- type = "button"
288- onClick = { ( ) => {
289- void copyGeneratedSetup ( ) ;
290- } }
291- disabled = { createIntegrationKey . isPending }
292- >
293- { copied === "generated-setup" ? (
321+ < button
322+ className = "inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-md bg-accent px-3 text-sm font-semibold text-ink hover:bg-sky-300 disabled:cursor-not-allowed disabled:opacity-60"
323+ type = "button"
324+ onClick = { runPrimaryAction }
325+ disabled = {
326+ activeToolCategory === "terminal" &&
327+ createIntegrationKey . isPending
328+ }
329+ >
330+ { activeToolCategory === "terminal" ? (
331+ copied === "generated-setup" ? (
294332 < Check size = { 15 } />
295333 ) : (
296334 < Clipboard size = { 15 } />
297- ) }
298- { createIntegrationKey . isPending
299- ? "Creating..."
300- : copied === "generated-setup"
301- ? "Copied"
302- : "Copy setup" }
303- </ button >
304- ) : null }
335+ )
336+ ) : (
337+ < ArrowRight size = { 15 } />
338+ ) }
339+ { activeToolCategory === "terminal" &&
340+ createIntegrationKey . isPending
341+ ? "Creating..."
342+ : activeToolCategory === "terminal" &&
343+ copied === "generated-setup"
344+ ? "Copied"
345+ : activeCategory . primaryAction }
346+ </ button >
305347 < button
306348 className = "inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-md border border-line px-3 text-sm text-zinc-200 hover:border-accent/50 hover:bg-white/5 disabled:opacity-60"
307349 type = "button"
@@ -431,6 +473,10 @@ function SetupDisclosure({
431473 copied : boolean ;
432474 onCopy : ( ) => void ;
433475} ) {
476+ const [ open , setOpen ] = useState ( false ) ;
477+ const disclosureLabel =
478+ config . id === "stint-cli-config" ? "Show command" : "Show setup details" ;
479+
434480 return (
435481 < div
436482 id = "integration-instructions"
@@ -440,16 +486,23 @@ function SetupDisclosure({
440486 < span id = { config . id } className = "sr-only" >
441487 { config . name }
442488 </ span >
443- < details >
444- < summary className = "flex cursor-pointer list-none items-center justify-between gap-3 text-sm font-medium text-zinc-100" >
445- Setup details
446- < CopyButton
447- id = { config . id }
448- label = "Copy"
449- copied = { copied }
450- onCopy = { onCopy }
451- />
452- </ summary >
489+ < div className = "flex items-center justify-between gap-3" >
490+ < button
491+ className = "text-left text-sm font-medium text-zinc-100 hover:text-accent focus:outline-none focus:ring-2 focus:ring-accent/60"
492+ type = "button"
493+ aria-expanded = { open }
494+ onClick = { ( ) => setOpen ( ( current ) => ! current ) }
495+ >
496+ { open ? "Hide setup details" : disclosureLabel }
497+ </ button >
498+ < CopyButton
499+ id = { config . id }
500+ label = "Copy"
501+ copied = { copied }
502+ onCopy = { onCopy }
503+ />
504+ </ div >
505+ { open ? (
453506 < div className = "mt-4 space-y-3" >
454507 { config . options . map ( ( option ) => (
455508 < SetupOptionCard key = { option . title } option = { option } />
@@ -464,7 +517,7 @@ function SetupDisclosure({
464517 </ div >
465518 ) : null }
466519 </ div >
467- </ details >
520+ ) : null }
468521 </ div >
469522 ) ;
470523}
0 commit comments