@sergeychuvayev/claude-fleet 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/ask.js CHANGED
@@ -1,24 +1,36 @@
1
1
  'use strict'
2
+ // An isolated scope, matching teams.js and blocks.js.
3
+ ;(() => {
4
+ const { $, esc, key, age, update, status, render, toast, modalIsOpen, openModal, closeModal } = window.Fleet
5
+ const api = (...args) => window.FleetControl.api(...args)
2
6
  // Ask panel: one question, searched across every transcript on this machine.
3
7
  // Keyword matches render the moment the server has them; the written answer
4
8
  // arrives a few seconds later and reorders the cards by what Claude found relevant.
5
9
  let askJob = null, askPoll = null, askRequest = 0
6
10
  const isMac = /Mac|iPhone|iPad/.test(navigator.platform)
7
- $('ask-shortcut').textContent = isMac ? '⌘K' : 'Ctrl K'
8
11
 
9
12
  const openAsk = () => { renderAsk(); openModal('ask-backdrop', '#ask-input') }
10
- $('ask-welcome').addEventListener('click', event => {
13
+
14
+ // ── What the rest of the page may use ───────────────────────────────────────
15
+ // Published before the boot wiring below, as app.js and control.js do. Nothing
16
+ // consumes this yet; it exists so opening the panel is a call rather than a
17
+ // synthesised click, and so this file leaks one name like every other.
18
+ window.FleetAsk = { openAsk }
19
+
20
+ // ── Boot ────────────────────────────────────────────────────────────────────
21
+ if ($('ask-shortcut')) $('ask-shortcut').textContent = isMac ? '⌘K' : 'Ctrl K'
22
+ $('ask-welcome')?.addEventListener('click', event => {
11
23
  const suggestion = event.target.closest('[data-question]')
12
24
  if (!suggestion) return
13
25
  $('ask-input').value = suggestion.dataset.question
14
26
  $('ask-input').focus()
15
27
  })
16
- $('ask-sessions').addEventListener('click', () => modalIsOpen('ask-backdrop') ? closeModal() : openAsk())
28
+ $('ask-sessions')?.addEventListener('click', () => modalIsOpen('ask-backdrop') ? closeModal() : openAsk())
17
29
  document.addEventListener('keydown', event => {
18
30
  if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && event.key.toLowerCase() === 'k') { event.preventDefault(); openAsk() }
19
31
  })
20
32
 
21
- $('ask-form').addEventListener('submit', async event => {
33
+ $('ask-form')?.addEventListener('submit', async event => {
22
34
  event.preventDefault()
23
35
  const question = $('ask-input').value.trim()
24
36
  if (!question) return
@@ -55,7 +67,9 @@ async function pollAsk() {
55
67
 
56
68
  const REL_WORD = { high: 'strong match', medium: 'related', low: 'loosely related' }
57
69
  const dateOf = ms => ms ? new Date(ms).toLocaleDateString([], { month: 'short', day: 'numeric' }) : ''
58
- const liveSession = id => snapshot?.sessions.find(s => s.sessionId === id) || null
70
+ // Read through the accessor, never a captured binding: the poll replaces the
71
+ // snapshot object every couple of seconds.
72
+ const liveSession = id => window.Fleet.snapshot()?.sessions.find(s => s.sessionId === id) || null
59
73
 
60
74
  function hitCard(hit, match) {
61
75
  const live = liveSession(hit.sessionId)
@@ -97,7 +111,7 @@ function renderAsk() {
97
111
  update('ask-results', `<div class="ask-head"><span class="ask-question">“${esc(job.question)}”</span>${stats}</div>${status}${answer}${cards}`)
98
112
  }
99
113
 
100
- $('ask-results').addEventListener('click', async event => {
114
+ $('ask-results')?.addEventListener('click', async event => {
101
115
  const button = event.target.closest('button')
102
116
  if (!button) return
103
117
  if (button.dataset.openSession) {
@@ -117,3 +131,5 @@ $('ask-results').addEventListener('click', async event => {
117
131
  // Live sessions may appear or vanish while the results are on screen; refresh the
118
132
  // "open now" state and the Open button from the latest snapshot.
119
133
  document.addEventListener('fleet-snapshot', () => { if (askJob && modalIsOpen('ask-backdrop')) renderAsk() })
134
+
135
+ })()
package/public/blocks.js CHANGED
@@ -1,4 +1,6 @@
1
1
  'use strict'
2
+ // An isolated scope, matching teams.js. This file borrows nothing from the others.
3
+ window.FleetBlocks = (() => {
2
4
  // Warp-style conversation blocks. Every message, tool call and result is its own
3
5
  // block with a sticky header, a copy action and collapse. Rendering is incremental:
4
6
  // a block is only rebuilt when its content signature changes, so a streaming turn
@@ -190,4 +192,5 @@ function renderBlocks(container, messages, { streamingId = null, onCopy = () =>
190
192
  for (const element of [...container.children]) if (!seen.has(element.dataset?.block)) element.remove()
191
193
  }
192
194
 
193
- window.FleetBlocks = { renderBlocks, proseHtml, codeHtml, highlight }
195
+ return { renderBlocks, proseHtml, codeHtml, highlight }
196
+ })()
package/public/control.js CHANGED
@@ -1,4 +1,9 @@
1
1
  'use strict'
2
+ // An isolated scope, matching teams.js and blocks.js. What this file borrows from
3
+ // app.js is destructured once, here, instead of being picked out of a global scope
4
+ // the two files happened to share.
5
+ ;(() => {
6
+ const { $, esc, update, toast, modalIsOpen, openModal, closeModal } = window.Fleet
2
7
  let controlToken=null, controlSession=null, controlId=null, controlFetch=null, controlVersion=0
3
8
  const drafts=new Map()
4
9
  const inFlight=new Set()
@@ -60,7 +65,23 @@ function openLaunch(source=null) {
60
65
  $('launch-cwd').readOnly=!!source
61
66
  openModal('launch-backdrop', '[name=prompt]')
62
67
  }
63
- $('new-session').addEventListener('click',()=>modalIsOpen('launch-backdrop') ? closeModal() : openLaunch())
68
+ // ── What the rest of the page may use ───────────────────────────────────────
69
+ // Published before the boot wiring below, for the same reason app.js does it there:
70
+ // teams.js destructures this at load, and an element missing from the wiring must
71
+ // not cost it the whole namespace. The two values teams.js has to change are handed
72
+ // out as setters rather than as variables it reaches in and assigns.
73
+ window.FleetControl = {
74
+ selectControl, isWorking, updateLaunchTeam, renderUpdate,
75
+ // teams.js posts to the same endpoints through the same helper, and app.js and
76
+ // ask.js borrow it back: this file owns the token every write is signed with.
77
+ api,
78
+ launchTeams: () => launchTeams,
79
+ setLaunchTeams(teams) { launchTeams = teams },
80
+ setLaunchRequestId(id) { launchRequestId = id },
81
+ }
82
+
83
+ // ── Boot ────────────────────────────────────────────────────────────────────
84
+ $('new-session')?.addEventListener('click',()=>modalIsOpen('launch-backdrop') ? closeModal() : openLaunch())
64
85
  document.addEventListener('keydown', event => {
65
86
  if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && event.key.toLowerCase() === 'n') {
66
87
  event.preventDefault()
@@ -68,8 +89,8 @@ document.addEventListener('keydown', event => {
68
89
  else openLaunch()
69
90
  }
70
91
  })
71
- $('launch-form').addEventListener('input',()=>{launchRequestId=null})
72
- $('launch-form').addEventListener('submit',async event=>{
92
+ $('launch-form')?.addEventListener('input',()=>{launchRequestId=null})
93
+ $('launch-form')?.addEventListener('submit',async event=>{
73
94
  event.preventDefault()
74
95
  if(window.FleetTeams?.isEditing()){window.FleetTeams.save();return}
75
96
  const button=$('launch-submit'); if(button.disabled)return
@@ -94,9 +115,9 @@ function selectControl(session) {
94
115
  $('control-panel').innerHTML=''
95
116
  if(next){
96
117
  $('control-panel').innerHTML=`<div class="conversation-header"><h3 id="conversation-title">Conversation</h3><button type="button" id="close-agent" class="button close-agent" title="Remove this conversation from Fleet">Close</button><label class="mode-picker"><span class="sr-only">Model for this agent</span><select id="model-choice" title="Applies from your next message"></select></label><label class="mode-picker"><span class="sr-only">Approvals for this agent</span><select id="approval-mode"><option value="auto">Auto approvals</option><option value="ask">Ask every time</option><option value="all">Approve everything</option></select></label><span id="agent-context" class="subtle context-chip"></span><span id="agent-state" class="subtle">Connecting…</span></div><div id="conversation" class="conversation" role="log" aria-label="Agent conversation" aria-live="off"><p class="note">Loading conversation…</p></div><div id="agent-error" class="form-error" role="status" hidden></div><div id="approvals"></div><form id="composer" class="composer"><label class="sr-only" for="message-input">Message this agent</label><ul id="slash-picker" class="slash-picker" role="listbox" aria-label="Commands and skills" hidden></ul><div id="attach-tray" class="attach-tray" hidden></div><textarea id="message-input" rows="3" maxlength="16000" placeholder="What should this agent do next? · press / for commands · paste an image" role="combobox" aria-expanded="false" aria-controls="slash-picker" aria-autocomplete="list"></textarea><div class="composer-footer"><span id="composer-hint" class="note">Enter to send · Shift + Enter for a new line</span><button id="stop-agent" type="button" class="button stop" hidden>■ Stop</button><button id="send-message" class="button resume" type="submit">Send ↗</button></div><p id="send-error" class="form-error" role="alert" hidden></p></form>`
97
- window.FleetLayout?.watchConversation($('conversation'))
118
+ window.Fleet.watchConversation($('conversation'))
98
119
  catalog=[];catalogFor=null;closePicker();renderTray()
99
- window.FleetLayout?.syncDetails?.()
120
+ window.Fleet.syncDetails()
100
121
  $('message-input').value=drafts.get(next)?.text || ''
101
122
  $('message-input').addEventListener('input',()=>drafts.set(next,{text:$('message-input').value,requestId:crypto.randomUUID()}))
102
123
  $('message-input').addEventListener('keydown',event=>{
@@ -129,7 +150,7 @@ function selectControl(session) {
129
150
  }else if(session){
130
151
  $('control-panel').innerHTML=`<div class="external-note"><strong>Opened outside Fleet</strong><p>${session.alive ? 'This session is running in a terminal. Use its terminal to send messages, or launch a new Fleet-managed agent.' : 'This process has stopped. Continue its saved conversation here with a new message.'}</p>${!session.alive && session.sessionId && session.cwd ? '<button id="resume-in-fleet" class="button">Continue in Fleet ↗</button>' : ''}</div>`
131
152
  $('resume-in-fleet')?.addEventListener('click',()=>openLaunch(session))
132
- window.FleetLayout?.syncDetails?.()
153
+ window.Fleet.syncDetails()
133
154
  }
134
155
  }
135
156
  async function refreshControl() {
@@ -439,7 +460,7 @@ async function waitForRestart(deadline = Date.now() + 60000) {
439
460
  renderUpdate()
440
461
  toast('Fleet installed the update but did not come back. Start it again.')
441
462
  }
442
- $('update-pill').addEventListener('click', async () => {
463
+ $('update-pill')?.addEventListener('click', async () => {
443
464
  if (fleetUpdateBusy || !fleetUpdate || !fleetUpdate.canInstall) return
444
465
  fleetUpdateBusy = true
445
466
  renderUpdate()
@@ -463,3 +484,4 @@ pollUpdate()
463
484
  // time to answer, then settle into a slow poll for long-lived windows.
464
485
  setTimeout(pollUpdate, 9000)
465
486
  setInterval(pollUpdate, 60 * 60 * 1000)
487
+ })()
package/public/index.html CHANGED
@@ -1,10 +1,11 @@
1
1
  <!doctype html>
2
2
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="dark"><title>Fleet — Claude Code control room</title><link rel="manifest" href="/manifest.webmanifest"><link rel="icon" href="/icons/fleet-192.png" type="image/png"><link rel="apple-touch-icon" href="/icons/fleet-192.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="Fleet"><link rel="stylesheet" href="/theme.css"><link rel="stylesheet" href="/styles.css"><script src="/vendor/libs.js" defer></script><script src="/app.js" defer></script><script src="/blocks.js" defer></script><script src="/control.js" defer></script><script src="/teams.js" defer></script><script src="/ask.js" defer></script></head>
3
- <body><header class="topbar"><a class="brand" href="/" aria-label="Fleet home"><span class="brandmark">✳</span> fleet <span class="brand-sub">/ CLAUDE CODE</span></a><span class="app-version" id="app-version" title="The version of Fleet this server is running"></span><div class="connection"><span id="connection-dot" class="dot busy"></span><span id="connection">Connecting</span><span class="local">LOCAL CONTROL ROOM</span></div><div class="refresh-info"><button id="update-pill" class="button update-pill" hidden></button><span id="updated">Waiting for first snapshot</span><button id="refresh" class="button">↻ Refresh</button><button id="ask-sessions" class="button" aria-expanded="false" aria-controls="ask-backdrop" title="Ask a question across every session on this machine">⌕ Ask <kbd id="ask-shortcut">⌘K</kbd></button><button id="new-session" class="button resume" aria-expanded="false" aria-controls="launch-backdrop">+ New agent</button></div></header>
3
+ <body><header class="topbar"><a class="brand" href="/" aria-label="Fleet home"><span class="brandmark">✳</span> fleet <span class="brand-sub">/ CLAUDE CODE</span></a><span class="app-version" id="app-version" title="The version of Fleet this server is running"></span><div class="refresh-info"><button id="update-pill" class="button update-pill" hidden></button><button id="refresh" class="button">↻ Refresh</button><button id="ask-sessions" class="button" aria-expanded="false" aria-controls="ask-backdrop" title="Ask a question across every session on this machine">⌕ Ask <kbd id="ask-shortcut">⌘K</kbd></button><button id="new-session" class="button resume" aria-expanded="false" aria-controls="launch-backdrop">+ New agent</button></div></header>
4
4
  <main>
5
5
  <div id="error" class="error" role="status" hidden></div>
6
6
  <section class="workspace" aria-label="Sessions"><div class="sessions-pane"><div class="section-heading"><h2>Sessions <span id="shown-count">0</span></h2></div><div class="filters" id="filters" aria-label="Filter sessions by status"></div><div class="archive-bar" id="archive-bar" role="group" aria-label="Archive" hidden></div><div class="list-head"><span>SESSION / PROJECT</span><span>CONTEXT</span></div><div id="session-list" class="session-list"><div class="empty">Loading your sessions…</div></div></div><div id="splitter" class="splitter" role="separator" aria-orientation="vertical" aria-label="Resize the session inspector" aria-valuemin="25" aria-valuemax="75" aria-valuenow="58" tabindex="0" title="Drag to resize · double-click to reset"></div><aside id="detail" class="detail" aria-label="Session details"><section id="control-panel" aria-label="Agent controls"></section><button type="button" id="details-toggle" class="details-toggle" aria-expanded="true" aria-controls="detail-content" hidden><span class="details-chevron" aria-hidden="true">›</span>Session details</button><div id="detail-content"></div></aside></section>
7
7
  </main>
8
+ <footer class="statusbar" id="statusbar"><div class="status-usage" id="status-usage"></div><div class="status-fleet" id="status-fleet"></div><div class="status-conn"><span id="connection-dot" class="dot busy"></span><span id="connection">Connecting</span><span id="updated">Waiting for first snapshot</span><span class="local">LOCAL CONTROL ROOM</span></div></footer>
8
9
  <div class="modal-backdrop" id="ask-backdrop" hidden>
9
10
  <section class="modal modal-ask" role="dialog" aria-modal="true" aria-labelledby="ask-title">
10
11
  <header class="modal-head">
@@ -31,6 +32,7 @@
31
32
  <section class="modal modal-launch launch-panel" role="dialog" aria-modal="true" aria-labelledby="launch-title">
32
33
  <header class="modal-head"><div class="modal-heading"><span class="modal-spark" aria-hidden="true">✳</span><div><span class="modal-eyebrow">A FRESH PAIR OF HANDS</span><h2 id="launch-title">Give your next task a home.</h2><p>A little context. A clear task. Off you go.</p></div></div><button type="button" id="close-launch" class="modal-close" data-close-modal aria-label="Close new agent form">✕</button></header>
33
34
  <form id="launch-form" class="modal-body">
35
+ <p id="launch-blocked" class="rate-warning" role="status" hidden></p>
34
36
  <div class="launch-layout">
35
37
  <div class="launch-task"><label for="launch-prompt">What are we working on?</label><textarea id="launch-prompt" name="prompt" rows="8" placeholder="There’s something I’d love your help with…&#10;&#10;Describe the task, what a good result looks like, and anything your agent should know." required maxlength="16000"></textarea><p class="note launch-task-note">Big ideas, small fixes. Every task starts here.</p></div>
36
38
  <div class="launch-fields">