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.
@@ -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 click install and wait"
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/flavormingo/claudity.git"
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 configure later via web ui"
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 you can authenticate later"
273
+ warn "credentials not detected - you can authenticate later"
274
274
  fi
275
275
  else
276
- info "skipped authenticate at http://localhost:6767"
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 "starting claudity..."
326
- cd "$INSTALL_DIR" && node src/index.js &
327
- CLAUDITY_PID=$!
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 kill -0 "$CLAUDITY_PID" 2>/dev/null; then
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 "failed to start run manually with: cd ~/claudity && npm start"
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}next time: cd ~/claudity && npm start${reset}"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudity",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "evolving agents that live where you chat ★ powered by claude code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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: normal;
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
- -webkit-text-stroke: 0.5px currentColor;
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
- -webkit-text-stroke: 0;
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
- -webkit-text-stroke: 0.4px currentColor;
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>thinking</span>
149
- <select name="thinking">
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>thinking</span>
187
- <select name="thinking">
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> install with <code>brew install signal-cli</code>',
83
- 'click connect below a qr code will appear',
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 a qr code will appear',
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('p');
409
+ if (!status && row) {
410
+ status = document.createElement('span');
357
411
  status.dataset.status = '';
358
- activity.appendChild(status);
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
- status.textContent = `using ${toolName}...`;
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) showActivity();
384
- else clearActivity();
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 + ' coming soon' : p.name + ' ' + statusLabel}" role="${p.comingSoon ? 'presentation' : 'button'}" ${p.comingSoon ? '' : 'tabindex="0"'}>
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
- thinking: form.thinking.value
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="thinking"]').value = activeAgent.thinking || 'high';
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
- thinking: form.querySelector('[name="thinking"]').value
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 must start with sk-ant-oat. if you have an api key, use the api key option instead.');
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 must start with sk-ant-api. if you have a setup token, use the setup token option instead.');
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 claudity will show a setup screen${reset}"
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 thinking text not null default 'high'");
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
- setThinking: db.prepare('update agents set thinking = ?, updated_at = current_timestamp where id = ?'),
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);