claudity 1.0.0 → 1.1.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/.github/workflows/publish.yml +18 -0
- package/install.sh +50 -12
- package/package.json +1 -1
- package/public/css/style.css +56 -9
- package/public/index.html +7 -7
- package/public/js/app.js +118 -15
- package/setup.sh +1 -1
- package/src/db.js +2 -2
- package/src/index.js +17 -1
- package/src/routes/api.js +23 -7
- package/src/services/chat.js +74 -22
- package/src/services/claude.js +86 -41
- package/src/services/connections.js +1 -1
- package/src/services/imessage.js +3 -2
- package/src/services/memory.js +2 -2
- package/src/services/signal.js +2 -3
- package/src/services/tools.js +7 -18
- package/src/services/whatsapp.js +2 -2
- package/src/services/workspace.js +7 -7
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
publish:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-node@v4
|
|
14
|
+
with:
|
|
15
|
+
node-version: 20
|
|
16
|
+
registry-url: https://registry.npmjs.org
|
|
17
|
+
- run: npm install
|
|
18
|
+
- run: npm publish --provenance --access public
|
package/install.sh
CHANGED
|
@@ -136,7 +136,7 @@ if xcode-select -p &>/dev/null; then
|
|
|
136
136
|
ok "already installed"
|
|
137
137
|
else
|
|
138
138
|
xcode-select --install 2>/dev/null || true
|
|
139
|
-
warn "a dialog may appear
|
|
139
|
+
warn "a dialog may appear - click install and wait"
|
|
140
140
|
elapsed=0
|
|
141
141
|
timeout=1800
|
|
142
142
|
chars="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
@@ -210,7 +210,7 @@ fi
|
|
|
210
210
|
|
|
211
211
|
step "downloading claudity"
|
|
212
212
|
INSTALL_DIR="$HOME/claudity"
|
|
213
|
-
REPO_URL="https://github.com/
|
|
213
|
+
REPO_URL="https://github.com/claudity/claudity.git"
|
|
214
214
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
|
215
215
|
spin "updating claudity" git -C "$INSTALL_DIR" pull --ff-only
|
|
216
216
|
elif [ -d "$INSTALL_DIR" ]; then
|
|
@@ -256,7 +256,7 @@ else
|
|
|
256
256
|
warn "no authentication found"
|
|
257
257
|
echo ""
|
|
258
258
|
echo -e " ${green}1${reset}) run ${green}claude login${reset} now ${dim}(recommended)${reset}"
|
|
259
|
-
echo -e " ${green}2${reset}) skip
|
|
259
|
+
echo -e " ${green}2${reset}) skip - configure later via web ui"
|
|
260
260
|
echo ""
|
|
261
261
|
echo -en " choice ${dim}[1/2]${reset}: "
|
|
262
262
|
read -r auth_choice </dev/tty
|
|
@@ -270,10 +270,10 @@ else
|
|
|
270
270
|
if [ -n "$KEYCHAIN_CREDS" ]; then
|
|
271
271
|
ok "authenticated successfully"
|
|
272
272
|
else
|
|
273
|
-
warn "credentials not detected
|
|
273
|
+
warn "credentials not detected - you can authenticate later"
|
|
274
274
|
fi
|
|
275
275
|
else
|
|
276
|
-
info "skipped
|
|
276
|
+
info "skipped - authenticate at http://localhost:6767"
|
|
277
277
|
fi
|
|
278
278
|
fi
|
|
279
279
|
fi
|
|
@@ -322,15 +322,52 @@ fi
|
|
|
322
322
|
echo ""
|
|
323
323
|
info "for signal support: ${green}brew install signal-cli${reset}"
|
|
324
324
|
|
|
325
|
-
step "
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
step "setting up auto-start"
|
|
326
|
+
PLIST_NAME="ai.claudity.server"
|
|
327
|
+
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_NAME}.plist"
|
|
328
|
+
NODE_PATH="$(which node)"
|
|
329
|
+
mkdir -p "$INSTALL_DIR/data"
|
|
330
|
+
|
|
331
|
+
launchctl bootout "gui/$(id -u)/${PLIST_NAME}" 2>/dev/null || true
|
|
332
|
+
|
|
333
|
+
cat > "$PLIST_PATH" <<PLIST
|
|
334
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
335
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
336
|
+
<plist version="1.0">
|
|
337
|
+
<dict>
|
|
338
|
+
<key>Label</key>
|
|
339
|
+
<string>${PLIST_NAME}</string>
|
|
340
|
+
<key>ProgramArguments</key>
|
|
341
|
+
<array>
|
|
342
|
+
<string>${NODE_PATH}</string>
|
|
343
|
+
<string>${INSTALL_DIR}/src/index.js</string>
|
|
344
|
+
</array>
|
|
345
|
+
<key>WorkingDirectory</key>
|
|
346
|
+
<string>${INSTALL_DIR}</string>
|
|
347
|
+
<key>RunAtLoad</key>
|
|
348
|
+
<true/>
|
|
349
|
+
<key>KeepAlive</key>
|
|
350
|
+
<true/>
|
|
351
|
+
<key>StandardOutPath</key>
|
|
352
|
+
<string>${INSTALL_DIR}/data/claudity.log</string>
|
|
353
|
+
<key>StandardErrorPath</key>
|
|
354
|
+
<string>${INSTALL_DIR}/data/claudity.log</string>
|
|
355
|
+
<key>EnvironmentVariables</key>
|
|
356
|
+
<dict>
|
|
357
|
+
<key>PATH</key>
|
|
358
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
359
|
+
</dict>
|
|
360
|
+
</dict>
|
|
361
|
+
</plist>
|
|
362
|
+
PLIST
|
|
363
|
+
|
|
364
|
+
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH" 2>/dev/null || launchctl load "$PLIST_PATH" 2>/dev/null
|
|
328
365
|
sleep 2
|
|
329
366
|
|
|
330
|
-
if
|
|
331
|
-
ok "claudity is running"
|
|
367
|
+
if curl -sf http://localhost:6767/api/auth/status >/dev/null 2>&1; then
|
|
368
|
+
ok "claudity is running and will restart automatically"
|
|
332
369
|
else
|
|
333
|
-
warn "
|
|
370
|
+
warn "service loaded but server not responding yet - check ~/claudity/data/claudity.log"
|
|
334
371
|
fi
|
|
335
372
|
|
|
336
373
|
boxlines \
|
|
@@ -338,6 +375,7 @@ boxlines \
|
|
|
338
375
|
"" \
|
|
339
376
|
"${green}http://localhost:6767${reset}" \
|
|
340
377
|
"" \
|
|
341
|
-
"${dim}
|
|
378
|
+
"${dim}claudity starts automatically on login${reset}" \
|
|
379
|
+
"${dim}logs: ~/claudity/data/claudity.log${reset}"
|
|
342
380
|
|
|
343
381
|
echo ""
|
package/package.json
CHANGED
package/public/css/style.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
@font-face {
|
|
2
2
|
font-family: 'berkeley';
|
|
3
3
|
src: url('../font/berkeley.woff2') format('woff2');
|
|
4
|
-
font-weight:
|
|
4
|
+
font-weight: 100 900;
|
|
5
5
|
font-style: normal;
|
|
6
6
|
font-display: swap;
|
|
7
7
|
}
|
|
@@ -67,7 +67,6 @@ html, body {
|
|
|
67
67
|
|
|
68
68
|
body {
|
|
69
69
|
font-family: 'berkeley', monospace;
|
|
70
|
-
-webkit-text-stroke: 0px;
|
|
71
70
|
background: var(--black);
|
|
72
71
|
color: var(--white);
|
|
73
72
|
display: grid;
|
|
@@ -114,7 +113,7 @@ nav[aria-label="agents"] header {
|
|
|
114
113
|
|
|
115
114
|
nav[aria-label="agents"] header h1 {
|
|
116
115
|
font-size: 14px;
|
|
117
|
-
-
|
|
116
|
+
font-weight: 600;
|
|
118
117
|
color: var(--accent);
|
|
119
118
|
letter-spacing: 0.05em;
|
|
120
119
|
display: flex;
|
|
@@ -124,7 +123,7 @@ nav[aria-label="agents"] header h1 {
|
|
|
124
123
|
|
|
125
124
|
nav[aria-label="agents"] header h1 span {
|
|
126
125
|
font-size: 9px;
|
|
127
|
-
-
|
|
126
|
+
font-weight: normal;
|
|
128
127
|
color: var(--accent);
|
|
129
128
|
letter-spacing: 0.06em;
|
|
130
129
|
background: rgba(var(--glow), 0.1);
|
|
@@ -366,6 +365,16 @@ section[aria-label="empty"] p {
|
|
|
366
365
|
font-size: 12px;
|
|
367
366
|
}
|
|
368
367
|
|
|
368
|
+
section[aria-label="empty"] a {
|
|
369
|
+
color: var(--accent);
|
|
370
|
+
text-decoration: none;
|
|
371
|
+
cursor: pointer;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
section[aria-label="empty"] a:hover {
|
|
375
|
+
text-decoration: underline;
|
|
376
|
+
}
|
|
377
|
+
|
|
369
378
|
section[aria-label="setup"] pre {
|
|
370
379
|
background: var(--bg-raised);
|
|
371
380
|
border: 1px solid var(--muted);
|
|
@@ -611,7 +620,7 @@ div[aria-label="messages"] > div[data-role="assistant"] > .msg-body p:last-child
|
|
|
611
620
|
}
|
|
612
621
|
|
|
613
622
|
div[aria-label="messages"] > div[data-role="assistant"] > .msg-body strong {
|
|
614
|
-
-
|
|
623
|
+
font-weight: 600;
|
|
615
624
|
}
|
|
616
625
|
|
|
617
626
|
div[aria-label="messages"] > div[data-role="assistant"] > .msg-body strong a {
|
|
@@ -744,10 +753,48 @@ div[aria-label="messages"] > div[data-activity] [data-thinking] {
|
|
|
744
753
|
opacity: 0.85;
|
|
745
754
|
}
|
|
746
755
|
|
|
756
|
+
div[aria-label="messages"] > div[data-activity] [data-activity-row] {
|
|
757
|
+
display: flex;
|
|
758
|
+
align-items: center;
|
|
759
|
+
gap: 8px;
|
|
760
|
+
margin-top: 4px;
|
|
761
|
+
}
|
|
762
|
+
|
|
747
763
|
div[aria-label="messages"] > div[data-activity] [data-status] {
|
|
748
764
|
color: var(--muted);
|
|
749
765
|
font-size: 11px;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
div[aria-label="messages"] > div[data-activity] [data-summary] {
|
|
769
|
+
color: var(--accent);
|
|
770
|
+
font-size: 11px;
|
|
750
771
|
margin-top: 4px;
|
|
772
|
+
opacity: 0.7;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
div[aria-label="messages"] > div[data-activity] [data-stop] {
|
|
776
|
+
background: transparent;
|
|
777
|
+
border: 1px solid var(--bg-hover);
|
|
778
|
+
color: var(--muted);
|
|
779
|
+
font-family: inherit;
|
|
780
|
+
font-size: 10px;
|
|
781
|
+
padding: 2px 10px;
|
|
782
|
+
border-radius: 4px;
|
|
783
|
+
cursor: pointer;
|
|
784
|
+
transition: all 0.15s;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
div[aria-label="messages"] > div[data-activity] [data-stop]:hover {
|
|
788
|
+
background: rgba(255, 80, 80, 0.1);
|
|
789
|
+
border-color: rgba(255, 80, 80, 0.3);
|
|
790
|
+
color: var(--danger);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
div[aria-label="messages"] > div[data-role="system"] {
|
|
794
|
+
color: var(--muted);
|
|
795
|
+
font-size: 11px;
|
|
796
|
+
padding: 0.25rem 0;
|
|
797
|
+
font-style: italic;
|
|
751
798
|
}
|
|
752
799
|
|
|
753
800
|
form[aria-label="input"] {
|
|
@@ -869,19 +916,19 @@ section[aria-label="connections"] [data-platforms] {
|
|
|
869
916
|
border-color: var(--muted);
|
|
870
917
|
}
|
|
871
918
|
|
|
872
|
-
[data-platforms] article[data-status="connected"] {
|
|
919
|
+
[data-platforms] article[data-conn-status="connected"] {
|
|
873
920
|
border-color: rgba(var(--glow), 0.2);
|
|
874
921
|
}
|
|
875
922
|
|
|
876
|
-
[data-platforms] article[data-status="connected"]:hover {
|
|
923
|
+
[data-platforms] article[data-conn-status="connected"]:hover {
|
|
877
924
|
border-color: rgba(var(--glow), 0.4);
|
|
878
925
|
}
|
|
879
926
|
|
|
880
|
-
[data-platforms] article[data-status="connecting"] {
|
|
927
|
+
[data-platforms] article[data-conn-status="connecting"] {
|
|
881
928
|
border-color: rgba(245, 158, 11, 0.3);
|
|
882
929
|
}
|
|
883
930
|
|
|
884
|
-
[data-platforms] article[data-status="error"] {
|
|
931
|
+
[data-platforms] article[data-conn-status="error"] {
|
|
885
932
|
border-color: rgba(239, 68, 68, 0.3);
|
|
886
933
|
}
|
|
887
934
|
|
package/public/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<meta name="theme-color" content="#000000">
|
|
9
9
|
<meta name="color-scheme" content="dark">
|
|
10
10
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
11
|
-
<link rel="stylesheet" href="css/style.css">
|
|
11
|
+
<link rel="stylesheet" href="css/style.css?cb=12">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<button type="button" aria-label="menu" aria-expanded="false" aria-controls="agent-nav" title="menu" data-action="toggle-nav">
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<section aria-label="empty" hidden role="region">
|
|
84
84
|
<div>
|
|
85
85
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M352 0c0-17.7-14.3-32-32-32S288-17.7 288 0l0 64-96 0c-53 0-96 43-96 96l0 224c0 53 43 96 96 96l256 0c53 0 96-43 96-96l0-224c0-53-43-96-96-96l-96 0 0-64zM160 368c0-13.3 10.7-24 24-24l32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0c-13.3 0-24-10.7-24-24zm120 0c0-13.3 10.7-24 24-24l32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0c-13.3 0-24-10.7-24-24zm120 0c0-13.3 10.7-24 24-24l32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0c-13.3 0-24-10.7-24-24zM224 176a48 48 0 1 1 0 96 48 48 0 1 1 0-96zm144 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zM64 224c0-17.7-14.3-32-32-32S0 206.3 0 224l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96zm544-32c-17.7 0-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32z"/></svg>
|
|
86
|
-
<p>select or create an agent to begin</p>
|
|
86
|
+
<p>select or <a data-action="create-agent-link">create an agent</a> to begin</p>
|
|
87
87
|
</div>
|
|
88
88
|
</section>
|
|
89
89
|
|
|
@@ -145,8 +145,8 @@
|
|
|
145
145
|
</select>
|
|
146
146
|
</label>
|
|
147
147
|
<label data-select>
|
|
148
|
-
<span>
|
|
149
|
-
<select name="
|
|
148
|
+
<span>effort</span>
|
|
149
|
+
<select name="effort">
|
|
150
150
|
<option value="low">low</option>
|
|
151
151
|
<option value="medium">medium</option>
|
|
152
152
|
<option value="high" selected>high</option>
|
|
@@ -183,8 +183,8 @@
|
|
|
183
183
|
</select>
|
|
184
184
|
</label>
|
|
185
185
|
<label data-select>
|
|
186
|
-
<span>
|
|
187
|
-
<select name="
|
|
186
|
+
<span>effort</span>
|
|
187
|
+
<select name="effort">
|
|
188
188
|
<option value="low">low</option>
|
|
189
189
|
<option value="medium">medium</option>
|
|
190
190
|
<option value="high">high</option>
|
|
@@ -257,6 +257,6 @@
|
|
|
257
257
|
</footer>
|
|
258
258
|
</dialog>
|
|
259
259
|
|
|
260
|
-
<script src="js/app.js"></script>
|
|
260
|
+
<script src="js/app.js?cb=12"></script>
|
|
261
261
|
</body>
|
|
262
262
|
</html>
|
package/public/js/app.js
CHANGED
|
@@ -79,8 +79,8 @@ const platforms = [
|
|
|
79
79
|
qrAuth: true,
|
|
80
80
|
fields: [],
|
|
81
81
|
setup: [
|
|
82
|
-
'requires <code>signal-cli</code>
|
|
83
|
-
'click connect below
|
|
82
|
+
'requires <code>signal-cli</code> - install with <code>brew install signal-cli</code>',
|
|
83
|
+
'click connect below - a qr code will appear',
|
|
84
84
|
'open signal on your phone → settings → linked devices → link new device',
|
|
85
85
|
'scan the qr code with your phone camera',
|
|
86
86
|
'after linking, reconnects automatically on restart'
|
|
@@ -129,7 +129,7 @@ const platforms = [
|
|
|
129
129
|
fields: [],
|
|
130
130
|
qrAuth: true,
|
|
131
131
|
setup: [
|
|
132
|
-
'click connect below
|
|
132
|
+
'click connect below - a qr code will appear',
|
|
133
133
|
'open whatsapp on your phone → linked devices → link a device',
|
|
134
134
|
'scan the qr code with your phone camera',
|
|
135
135
|
'after first scan, reconnects automatically on restart'
|
|
@@ -243,6 +243,7 @@ async function selectAgent(id) {
|
|
|
243
243
|
showSection('chat');
|
|
244
244
|
$('section[aria-label="chat"] > header h2').textContent = activeAgent.name;
|
|
245
245
|
|
|
246
|
+
clearActivity();
|
|
246
247
|
messagesDiv.innerHTML = '';
|
|
247
248
|
chatInput.value = '';
|
|
248
249
|
chatSubmit.disabled = false;
|
|
@@ -319,24 +320,37 @@ function scrollToBottom() {
|
|
|
319
320
|
function showActivity() {
|
|
320
321
|
let el = messagesDiv.querySelector('[data-activity]');
|
|
321
322
|
if (el) return el;
|
|
323
|
+
if (chatSection.hidden) return null;
|
|
322
324
|
el = document.createElement('div');
|
|
323
325
|
el.dataset.activity = '';
|
|
324
326
|
el.dataset.role = 'assistant';
|
|
325
327
|
el.setAttribute('role', 'status');
|
|
326
328
|
el.setAttribute('aria-label', 'agent is thinking');
|
|
327
329
|
el.innerHTML = '<p data-dots><span></span><span></span><span></span></p>';
|
|
330
|
+
const row = document.createElement('div');
|
|
331
|
+
row.dataset.activityRow = '';
|
|
332
|
+
const stopBtn = document.createElement('button');
|
|
333
|
+
stopBtn.dataset.stop = '';
|
|
334
|
+
stopBtn.textContent = 'stop';
|
|
335
|
+
stopBtn.addEventListener('click', () => {
|
|
336
|
+
if (activeAgent) api('POST', `/api/agents/${activeAgent.id}/stop`).catch(() => {});
|
|
337
|
+
});
|
|
338
|
+
row.appendChild(stopBtn);
|
|
339
|
+
el.appendChild(row);
|
|
328
340
|
messagesDiv.appendChild(el);
|
|
329
341
|
scrollToBottom();
|
|
330
342
|
return el;
|
|
331
343
|
}
|
|
332
344
|
|
|
333
345
|
function clearActivity() {
|
|
346
|
+
stopActivityTimer();
|
|
334
347
|
const el = messagesDiv.querySelector('[data-activity]');
|
|
335
348
|
if (el) el.remove();
|
|
336
349
|
}
|
|
337
350
|
|
|
338
351
|
function updateThinking(content) {
|
|
339
352
|
const activity = showActivity();
|
|
353
|
+
if (!activity) return;
|
|
340
354
|
let thinking = activity.querySelector('[data-thinking]');
|
|
341
355
|
if (!thinking) {
|
|
342
356
|
thinking = document.createElement('p');
|
|
@@ -349,15 +363,74 @@ function updateThinking(content) {
|
|
|
349
363
|
scrollToBottom();
|
|
350
364
|
}
|
|
351
365
|
|
|
366
|
+
let activityStartTime = null;
|
|
367
|
+
let toolStartTime = null;
|
|
368
|
+
let activityTimer = null;
|
|
369
|
+
let currentToolName = null;
|
|
370
|
+
|
|
371
|
+
function formatElapsed(seconds) {
|
|
372
|
+
const m = Math.floor(seconds / 60);
|
|
373
|
+
const s = seconds % 60;
|
|
374
|
+
return m > 0 ? `${m}m ${s}s` : `${s}s`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function startActivityTimer() {
|
|
378
|
+
if (activityTimer) return;
|
|
379
|
+
activityStartTime = activityStartTime || Date.now();
|
|
380
|
+
activityTimer = setInterval(() => {
|
|
381
|
+
const el = messagesDiv.querySelector('[data-status]');
|
|
382
|
+
if (!el) return;
|
|
383
|
+
const total = Math.floor((Date.now() - activityStartTime) / 1000);
|
|
384
|
+
const tool = currentToolName || 'thinking';
|
|
385
|
+
let text = `${tool}...`;
|
|
386
|
+
if (toolStartTime) {
|
|
387
|
+
const toolElapsed = Math.floor((Date.now() - toolStartTime) / 1000);
|
|
388
|
+
text += ` ${formatElapsed(toolElapsed)}`;
|
|
389
|
+
}
|
|
390
|
+
text += ` · total ${formatElapsed(total)}`;
|
|
391
|
+
el.textContent = text;
|
|
392
|
+
}, 1000);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function stopActivityTimer() {
|
|
396
|
+
if (activityTimer) { clearInterval(activityTimer); activityTimer = null; }
|
|
397
|
+
activityStartTime = null;
|
|
398
|
+
toolStartTime = null;
|
|
399
|
+
currentToolName = null;
|
|
400
|
+
}
|
|
401
|
+
|
|
352
402
|
function updateToolStatus(toolName) {
|
|
403
|
+
if (toolName !== currentToolName) { if (currentToolName !== null) toolStartTime = Date.now(); }
|
|
404
|
+
currentToolName = toolName;
|
|
353
405
|
const activity = showActivity();
|
|
406
|
+
if (!activity) return;
|
|
407
|
+
const row = activity.querySelector('[data-activity-row]');
|
|
354
408
|
let status = activity.querySelector('[data-status]');
|
|
355
|
-
if (!status) {
|
|
356
|
-
status = document.createElement('
|
|
409
|
+
if (!status && row) {
|
|
410
|
+
status = document.createElement('span');
|
|
357
411
|
status.dataset.status = '';
|
|
358
|
-
|
|
412
|
+
row.insertBefore(status, row.firstChild);
|
|
413
|
+
}
|
|
414
|
+
if (!activityStartTime) activityStartTime = Date.now();
|
|
415
|
+
const total = Math.floor((Date.now() - activityStartTime) / 1000);
|
|
416
|
+
const toolElapsed = toolStartTime ? Math.floor((Date.now() - toolStartTime) / 1000) : 0;
|
|
417
|
+
if (status) status.textContent = `${toolName}... ${formatElapsed(toolElapsed)} · total ${formatElapsed(total)}`;
|
|
418
|
+
startActivityTimer();
|
|
419
|
+
const dots = activity.querySelector('[data-dots]');
|
|
420
|
+
if (dots) dots.remove();
|
|
421
|
+
scrollToBottom();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function updateStatusSummary(summary) {
|
|
425
|
+
const activity = showActivity();
|
|
426
|
+
if (!activity) return;
|
|
427
|
+
let el = activity.querySelector('[data-summary]');
|
|
428
|
+
if (!el) {
|
|
429
|
+
el = document.createElement('p');
|
|
430
|
+
el.dataset.summary = '';
|
|
431
|
+
activity.appendChild(el);
|
|
359
432
|
}
|
|
360
|
-
|
|
433
|
+
el.textContent = summary;
|
|
361
434
|
const dots = activity.querySelector('[data-dots]');
|
|
362
435
|
if (dots) dots.remove();
|
|
363
436
|
scrollToBottom();
|
|
@@ -380,8 +453,11 @@ function connectSSE(agentId) {
|
|
|
380
453
|
eventSource.addEventListener('typing', (e) => {
|
|
381
454
|
try {
|
|
382
455
|
const data = JSON.parse(e.data);
|
|
383
|
-
if (data.active)
|
|
384
|
-
|
|
456
|
+
if (data.active) {
|
|
457
|
+
if (data.elapsed != null) activityStartTime = Date.now() - (data.elapsed * 1000);
|
|
458
|
+
showActivity();
|
|
459
|
+
startActivityTimer();
|
|
460
|
+
} else { clearActivity(); stopActivityTimer(); }
|
|
385
461
|
} catch {}
|
|
386
462
|
});
|
|
387
463
|
|
|
@@ -404,10 +480,19 @@ function connectSSE(agentId) {
|
|
|
404
480
|
eventSource.addEventListener('tool_call', (e) => {
|
|
405
481
|
try {
|
|
406
482
|
const data = JSON.parse(e.data);
|
|
483
|
+
if (data.elapsed != null && !activityStartTime) activityStartTime = Date.now() - (data.elapsed * 1000);
|
|
484
|
+
if (data.toolElapsed != null && !currentToolName) toolStartTime = Date.now() - (data.toolElapsed * 1000);
|
|
407
485
|
updateToolStatus(data.name);
|
|
408
486
|
} catch {}
|
|
409
487
|
});
|
|
410
488
|
|
|
489
|
+
eventSource.addEventListener('status_update', (e) => {
|
|
490
|
+
try {
|
|
491
|
+
const data = JSON.parse(e.data);
|
|
492
|
+
updateStatusSummary(data.summary);
|
|
493
|
+
} catch {}
|
|
494
|
+
});
|
|
495
|
+
|
|
411
496
|
eventSource.addEventListener('assistant_message', (e) => {
|
|
412
497
|
try {
|
|
413
498
|
clearActivity();
|
|
@@ -464,6 +549,15 @@ function connectSSE(agentId) {
|
|
|
464
549
|
}
|
|
465
550
|
});
|
|
466
551
|
|
|
552
|
+
eventSource.addEventListener('stopped', () => {
|
|
553
|
+
clearActivity();
|
|
554
|
+
const div = document.createElement('div');
|
|
555
|
+
div.dataset.role = 'system';
|
|
556
|
+
div.innerHTML = '<p>stopped</p>';
|
|
557
|
+
messagesDiv.appendChild(div);
|
|
558
|
+
scrollToBottom();
|
|
559
|
+
});
|
|
560
|
+
|
|
467
561
|
eventSource.addEventListener('error', (e) => {
|
|
468
562
|
clearActivity();
|
|
469
563
|
try {
|
|
@@ -503,7 +597,7 @@ async function renderConnections() {
|
|
|
503
597
|
let statusLabel = status;
|
|
504
598
|
if (status === 'connected' && conn.status_detail) statusLabel = conn.status_detail;
|
|
505
599
|
|
|
506
|
-
return `<article data-platform="${p.id}" data-status="${status}"${p.comingSoon ? ' data-coming-soon' : ''} title="${p.comingSoon ? p.name + '
|
|
600
|
+
return `<article data-platform="${p.id}" data-conn-status="${status}"${p.comingSoon ? ' data-coming-soon' : ''} title="${p.comingSoon ? p.name + ' - coming soon' : p.name + ' - ' + statusLabel}" role="${p.comingSoon ? 'presentation' : 'button'}" ${p.comingSoon ? '' : 'tabindex="0"'}>
|
|
507
601
|
${p.icon}
|
|
508
602
|
<div><h3>${p.name}</h3><p>${p.comingSoon ? 'coming soon' : statusLabel}</p></div>
|
|
509
603
|
<span data-indicator="${status}" aria-label="${status}"></span>
|
|
@@ -729,8 +823,17 @@ function autoResize() {
|
|
|
729
823
|
|
|
730
824
|
chatInput.addEventListener('input', autoResize);
|
|
731
825
|
|
|
826
|
+
const agentNames = ['007','agent 47','agent alman','agent p','archer','aunt arctic','bishop','bourne','bullock','cobra','dynamo','gru','macgyver','mcgruff','mortadelo','payne','perry','powers','shadow','sherlock','slylock','verloc'];
|
|
827
|
+
|
|
732
828
|
$('nav[aria-label="agents"] header button').addEventListener('click', () => {
|
|
733
829
|
$('dialog[aria-label="create agent"] form').reset();
|
|
830
|
+
$('dialog[aria-label="create agent"] input[name="name"]').placeholder = agentNames[Math.floor(Math.random() * agentNames.length)];
|
|
831
|
+
createDialog.showModal();
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
$('[data-action="create-agent-link"]').addEventListener('click', () => {
|
|
835
|
+
$('dialog[aria-label="create agent"] form').reset();
|
|
836
|
+
$('dialog[aria-label="create agent"] input[name="name"]').placeholder = agentNames[Math.floor(Math.random() * agentNames.length)];
|
|
734
837
|
createDialog.showModal();
|
|
735
838
|
});
|
|
736
839
|
|
|
@@ -741,7 +844,7 @@ $('dialog[aria-label="create agent"] form').addEventListener('submit', async (e)
|
|
|
741
844
|
name: form.name.value.trim(),
|
|
742
845
|
is_default: form.is_default.checked,
|
|
743
846
|
model: form.model.value,
|
|
744
|
-
|
|
847
|
+
effort: form.effort.value
|
|
745
848
|
};
|
|
746
849
|
try {
|
|
747
850
|
const agent = await api('POST', '/api/agents', data);
|
|
@@ -780,7 +883,7 @@ $('button[data-action="edit-agent"]').addEventListener('click', () => {
|
|
|
780
883
|
}
|
|
781
884
|
form.querySelector('[name="show_heartbeat"]').checked = !!activeAgent.show_heartbeat;
|
|
782
885
|
form.querySelector('[name="model"]').value = activeAgent.model || 'opus';
|
|
783
|
-
form.querySelector('[name="
|
|
886
|
+
form.querySelector('[name="effort"]').value = activeAgent.effort || 'high';
|
|
784
887
|
editDialog.showModal();
|
|
785
888
|
});
|
|
786
889
|
|
|
@@ -795,7 +898,7 @@ $('dialog[aria-label="edit agent"] form').addEventListener('submit', async (e) =
|
|
|
795
898
|
heartbeat_interval: heartbeatEnabled ? parseInt(form.querySelector('[name="heartbeat_interval"]').value) : null,
|
|
796
899
|
show_heartbeat: form.querySelector('[name="show_heartbeat"]').checked,
|
|
797
900
|
model: form.querySelector('[name="model"]').value,
|
|
798
|
-
|
|
901
|
+
effort: form.querySelector('[name="effort"]').value
|
|
799
902
|
};
|
|
800
903
|
try {
|
|
801
904
|
const updated = await api('PATCH', `/api/agents/${id}`, data);
|
|
@@ -1038,7 +1141,7 @@ $('form[aria-label="setup-token"]').addEventListener('submit', async (e) => {
|
|
|
1038
1141
|
const input = $('form[aria-label="setup-token"] input');
|
|
1039
1142
|
const token = input.value.trim();
|
|
1040
1143
|
if (!token) return;
|
|
1041
|
-
if (!token.startsWith('sk-ant-oat')) return showDialogError(setupTokenDialog, 'invalid setup token
|
|
1144
|
+
if (!token.startsWith('sk-ant-oat')) return showDialogError(setupTokenDialog, 'invalid setup token - must start with sk-ant-oat. if you have an api key, use the api key option instead.');
|
|
1042
1145
|
try {
|
|
1043
1146
|
await api('POST', '/api/auth/setup-token', { token });
|
|
1044
1147
|
setupTokenDialog.close();
|
|
@@ -1054,7 +1157,7 @@ $('form[aria-label="api-key"]').addEventListener('submit', async (e) => {
|
|
|
1054
1157
|
const input = $('form[aria-label="api-key"] input');
|
|
1055
1158
|
const key = input.value.trim();
|
|
1056
1159
|
if (!key) return;
|
|
1057
|
-
if (!key.startsWith('sk-ant-api')) return showDialogError(apiKeyDialog, 'invalid api key
|
|
1160
|
+
if (!key.startsWith('sk-ant-api')) return showDialogError(apiKeyDialog, 'invalid api key - must start with sk-ant-api. if you have a setup token, use the setup token option instead.');
|
|
1058
1161
|
try {
|
|
1059
1162
|
await api('POST', '/api/auth/api-key', { key });
|
|
1060
1163
|
apiKeyDialog.close();
|
package/setup.sh
CHANGED
|
@@ -77,7 +77,7 @@ else
|
|
|
77
77
|
echo -e " option 1: run ${green}claude setup-token${reset} to connect your claude subscription"
|
|
78
78
|
echo -e " option 2: add ${green}API_KEY=sk-ant-api03-...${reset} to .env"
|
|
79
79
|
echo ""
|
|
80
|
-
echo -e " ${dim}you can finish this later
|
|
80
|
+
echo -e " ${dim}you can finish this later - claudity will show a setup screen${reset}"
|
|
81
81
|
fi
|
|
82
82
|
fi
|
|
83
83
|
|
package/src/db.js
CHANGED
|
@@ -86,7 +86,7 @@ try {
|
|
|
86
86
|
} catch {}
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
|
-
db.exec("alter table agents add column
|
|
89
|
+
db.exec("alter table agents add column effort text not null default 'high'");
|
|
90
90
|
} catch {}
|
|
91
91
|
|
|
92
92
|
try {
|
|
@@ -151,7 +151,7 @@ const stmts = {
|
|
|
151
151
|
setBootstrapped: db.prepare('update agents set bootstrapped = ?, updated_at = current_timestamp where id = ?'),
|
|
152
152
|
setHeartbeatInterval: db.prepare('update agents set heartbeat_interval = ?, updated_at = current_timestamp where id = ?'),
|
|
153
153
|
setModel: db.prepare('update agents set model = ?, updated_at = current_timestamp where id = ?'),
|
|
154
|
-
|
|
154
|
+
setEffort: db.prepare('update agents set effort = ?, updated_at = current_timestamp where id = ?'),
|
|
155
155
|
setShowHeartbeat: db.prepare('update agents set show_heartbeat = ?, updated_at = current_timestamp where id = ?'),
|
|
156
156
|
|
|
157
157
|
getSession: db.prepare('select * from sessions where agent_id = ?'),
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,11 @@ const connections = require('./services/connections');
|
|
|
13
13
|
const memory = require('./services/memory');
|
|
14
14
|
const heartbeat = require('./services/heartbeat');
|
|
15
15
|
|
|
16
|
+
process.on('unhandledRejection', (err) => {
|
|
17
|
+
if (err && err.message === 'aborted') return;
|
|
18
|
+
console.error('[unhandled rejection]', err);
|
|
19
|
+
});
|
|
20
|
+
|
|
16
21
|
const app = express();
|
|
17
22
|
const PORT = process.env.PORT || 6767;
|
|
18
23
|
|
|
@@ -51,7 +56,7 @@ function migrateImessageFromEnv() {
|
|
|
51
56
|
|
|
52
57
|
autoUpdate();
|
|
53
58
|
|
|
54
|
-
app.listen(PORT, () => {
|
|
59
|
+
const server = app.listen(PORT, () => {
|
|
55
60
|
console.log(`claudity running on http://localhost:${PORT}`);
|
|
56
61
|
scheduler.start();
|
|
57
62
|
memory.startConsolidation();
|
|
@@ -59,3 +64,14 @@ app.listen(PORT, () => {
|
|
|
59
64
|
connections.start();
|
|
60
65
|
heartbeat.start();
|
|
61
66
|
});
|
|
67
|
+
|
|
68
|
+
async function shutdown() {
|
|
69
|
+
console.log('shutting down...');
|
|
70
|
+
heartbeat.stop();
|
|
71
|
+
await connections.stop();
|
|
72
|
+
server.close();
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
process.on('SIGTERM', shutdown);
|
|
77
|
+
process.on('SIGINT', shutdown);
|