ape-claw 0.1.3 → 0.1.5

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/ui/index.html CHANGED
@@ -394,6 +394,19 @@ body::after{display:none !important}
394
394
  .chat-msg-id{font-family:var(--font-mono);font-size:.6rem;color:var(--dim)}
395
395
  .chat-msg-time{font-family:var(--font-mono);font-size:.6rem;color:var(--dim);margin-left:auto}
396
396
  .chat-msg-text{font-size:.78rem;line-height:1.55;color:var(--text);word-break:break-word}
397
+ .chat-msg-text strong{color:#eefad6;font-weight:700}
398
+ .chat-msg-text code{
399
+ font-family:var(--font-mono);font-size:.7rem;
400
+ background:rgba(99,215,255,.08);border:1px solid rgba(99,215,255,.2);
401
+ color:#bce9ff;padding:1px 4px;border-radius:4px;
402
+ }
403
+ .chat-msg-text pre{
404
+ margin:6px 0;padding:8px;border-radius:6px;
405
+ background:rgba(0,0,0,.35);border:1px solid rgba(255,255,255,.1);
406
+ overflow-x:auto;
407
+ }
408
+ .chat-msg-text ul{margin:6px 0 6px 14px;padding:0}
409
+ .chat-msg-text li{margin:2px 0}
397
410
  .chat-msg-reply{
398
411
  margin:4px 0 6px;padding:6px 8px;border-radius:2px;border:1px solid var(--border);
399
412
  background:rgba(255,255,255,.02);font-size:.65rem;color:var(--dim)
@@ -818,11 +831,11 @@ footer::before{
818
831
  <!-- ═══════ SETUP / ONBOARDING ═══════ -->
819
832
  <div class="reveal reveal-1" style="padding:12px 24px 0;position:relative;z-index:1">
820
833
  <div class="setup-panel">
821
- <div class="setup-header" id="setupToggle" onclick="toggleSetup()">
834
+ <div class="setup-header open" id="setupToggle" onclick="toggleSetup()">
822
835
  <span class="icon">🚀</span> Clawllector Setup &amp; Quick Start
823
836
  <span class="chevron">▼</span>
824
837
  </div>
825
- <div class="setup-body" id="setupBody">
838
+ <div class="setup-body open" id="setupBody">
826
839
  <div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 12px;margin-bottom:16px;background:rgba(0,240,255,.05);border:1px solid rgba(0,240,255,.18);border-radius:var(--radius)">
827
840
  <strong style="font-size:.7rem;color:var(--accent2)">Shared Backend</strong>
828
841
  <span id="backendUrlStatus" style="font-size:.66rem;color:var(--dim);width:100%">Using backend: --</span>
@@ -848,12 +861,16 @@ footer::before{
848
861
  </div>
849
862
  <span class="hint-row" style="width:100%">Quick Start is for collecting NFTs and bridging. Pod + v2 is for the Library of Alexandria and Otherside automation.</span>
850
863
  </div>
864
+ <div style="padding:10px 14px;margin-bottom:12px;background:rgba(207,255,4,.04);border:1px solid rgba(207,255,4,.2);border-radius:var(--radius)">
865
+ <strong style="font-size:.72rem;color:var(--accent)">⚠ Prerequisite: <a href="https://openclaw.ai" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:underline">OpenClaw</a></strong>
866
+ <span style="font-size:.64rem;color:var(--dim);display:block;margin-top:4px">ApeClaw installs all skills into your OpenClaw workspace (<code>~/.openclaw/skills/</code>). <strong style="color:var(--text)">Install OpenClaw first</strong> from <a href="https://openclaw.ai" target="_blank" rel="noopener" style="color:var(--accent2)">openclaw.ai</a>.</span>
867
+ </div>
851
868
  <div class="setup-grid">
852
869
  <div class="setup-step" data-setup-mode="quick">
853
870
  <div class="setup-step-num">1</div>
854
871
  <h3>Install ApeClaw</h3>
855
872
  <pre>npx ape-claw skill install</pre>
856
- <p class="note">Run in any terminal. Installs the core skill and prompts to add 61 curated skills (starter pack). Skills are synced to both Cursor and OpenClaw automatically.</p>
873
+ <p class="note">Run in any terminal. Requires <a href="https://openclaw.ai" target="_blank" rel="noopener">OpenClaw</a> + Node.js. Installs the core skill and prompts to add 61 curated skills (starter pack). Skills are installed to <code>~/.openclaw/skills/</code>.</p>
857
874
  <p class="note" style="margin-top:4px">Add <code>--starter-pack</code> to skip the prompt. Add <code>--scope local</code> to install into the current project only.</p>
858
875
  </div>
859
876
  <div class="setup-step" data-setup-mode="quick">
@@ -984,39 +1001,6 @@ python3 pod/run_agent.py \
984
1001
  </div>
985
1002
  </div>
986
1003
 
987
- <!-- ═══════ UNDER CONSTRUCTION BANNER ═══════ -->
988
- <div id="constructionBanner" style="
989
- margin:16px 24px 0;
990
- padding:32px 28px;
991
- background:linear-gradient(135deg,rgba(255,77,0,.08) 0%,rgba(207,255,4,.06) 100%);
992
- border:1.5px solid rgba(255,77,0,.35);
993
- border-radius:12px;
994
- text-align:center;
995
- position:relative;
996
- z-index:2;
997
- overflow:hidden;
998
- ">
999
- <div style="font-size:2.2rem;margin-bottom:8px;filter:drop-shadow(0 0 8px rgba(255,77,0,.4))">🏗️</div>
1000
- <h2 style="
1001
- margin:0 0 8px;
1002
- font-size:1.1rem;
1003
- letter-spacing:1.5px;
1004
- text-transform:uppercase;
1005
- background:linear-gradient(90deg,#ff4d00,#cfff04);
1006
- -webkit-background-clip:text;
1007
- -webkit-text-fill-color:transparent;
1008
- background-clip:text;
1009
- ">Live Dashboard — Under Construction</h2>
1010
- <p style="margin:0 0 14px;font-size:.78rem;color:var(--dim);max-width:540px;margin-inline:auto;line-height:1.55">
1011
- The real-time telemetry panels (clawbot activity, live events, NFT gallery, bridge ops, and chat) are being connected to the production backend. Everything below is preview data.
1012
- </p>
1013
- <div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;font-size:.68rem">
1014
- <span style="padding:4px 10px;border-radius:4px;background:rgba(255,77,0,.12);color:#ff6b35;border:1px solid rgba(255,77,0,.25)">SSE Telemetry — coming soon</span>
1015
- <span style="padding:4px 10px;border-radius:4px;background:rgba(0,240,255,.08);color:var(--accent2);border:1px solid rgba(0,240,255,.2)">Chat — coming soon</span>
1016
- <span style="padding:4px 10px;border-radius:4px;background:rgba(207,255,4,.08);color:var(--accent);border:1px solid rgba(207,255,4,.2)">Skills Library — live</span>
1017
- </div>
1018
- </div>
1019
-
1020
1004
  <!-- ═══════ MAIN GRID ═══════ -->
1021
1005
  <div class="main" id="mainContent">
1022
1006
 
@@ -1120,7 +1104,7 @@ python3 pod/run_agent.py \
1120
1104
  </div>
1121
1105
  <div class="chat-auth-bar">
1122
1106
  <label>Room</label>
1123
- <input type="text" id="chatRoom" value="general" placeholder="general" spellcheck="false" autocomplete="off">
1107
+ <input type="text" id="chatRoom" value="forge" placeholder="forge" spellcheck="false" autocomplete="off">
1124
1108
  <label>Agent ID</label>
1125
1109
  <input type="text" id="chatAgentId" placeholder="agent-id" spellcheck="false" autocomplete="off">
1126
1110
  <label>Token</label>
@@ -1923,7 +1907,7 @@ function renderTerminal() {
1923
1907
  function updateStats() {
1924
1908
  const activeCount = [...agentMap.values()].filter(a => a.status==='active').length;
1925
1909
  const totalCount = agentMap.size;
1926
- const clawbotCount = Math.max(totalCount, REGISTERED_CLAWBOTS.length, 111);
1910
+ const clawbotCount = Math.max(totalCount, REGISTERED_CLAWBOTS.length);
1927
1911
  const setIf = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
1928
1912
  setIf('totalAgents', activeCount + '/' + totalCount);
1929
1913
  setIf('totalEvents', totalEvents.toLocaleString());
@@ -2207,7 +2191,7 @@ setInterval(() => {
2207
2191
  // ═══════════════════════════════════════════════════════════
2208
2192
  let chatMessages = [];
2209
2193
  let chatSseSource = null;
2210
- let chatCredentials = { room: 'general', agentId: '', agentToken: '', identityToken: '' };
2194
+ let chatCredentials = { room: 'forge', agentId: '', agentToken: '', identityToken: '' };
2211
2195
  let chatReconnectTimer = null;
2212
2196
  let chatReconnectAttempts = 0;
2213
2197
  let chatRooms = [];
@@ -2216,8 +2200,8 @@ let chatReplyToId = null;
2216
2200
  const CHAT_REACTION_EMOJIS = ['👍', '🔥', '😂', '🫡', '👀'];
2217
2201
 
2218
2202
  function normalizeRoomName(input) {
2219
- const raw = String(input || 'general').toLowerCase().trim().replace(/[^a-z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
2220
- return raw || 'general';
2203
+ const raw = String(input || 'forge').toLowerCase().trim().replace(/[^a-z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
2204
+ return raw || 'forge';
2221
2205
  }
2222
2206
 
2223
2207
  function selectChatRoom(room) {
@@ -2248,7 +2232,7 @@ function renderChatRooms() {
2248
2232
  </button>`;
2249
2233
  }).join('');
2250
2234
  el.querySelectorAll('.chat-room-chip').forEach((btn) => {
2251
- btn.addEventListener('click', () => selectChatRoom(btn.dataset.room || 'general'));
2235
+ btn.addEventListener('click', () => selectChatRoom(btn.dataset.room || 'forge'));
2252
2236
  });
2253
2237
  }
2254
2238
 
@@ -2269,12 +2253,27 @@ function chatMsgTime(ts) {
2269
2253
  }
2270
2254
 
2271
2255
  function formatChatText(text) {
2272
- const safe = escapeHtml(String(text || ''));
2273
- return safe.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>');
2256
+ let safe = escapeHtml(String(text || ''));
2257
+ // Remove common citation markers injected by some providers.
2258
+ safe = safe.replace(/\[(\d+)\]/g, '');
2259
+
2260
+ // Links first.
2261
+ safe = safe.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>');
2262
+ // Fenced code blocks.
2263
+ safe = safe.replace(/```([\s\S]*?)```/g, (_m, code) => `<pre><code>${code}</code></pre>`);
2264
+ // Inline code.
2265
+ safe = safe.replace(/`([^`]+?)`/g, '<code>$1</code>');
2266
+ // Bold.
2267
+ safe = safe.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
2268
+ // Basic bullet list support.
2269
+ safe = safe.replace(/(?:^|\n)- (.+?)(?=\n|$)/g, '<li>$1</li>');
2270
+ safe = safe.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
2271
+ // Preserve line breaks.
2272
+ return safe.replace(/\n/g, '<br>');
2274
2273
  }
2275
2274
 
2276
2275
  function chatDraftKey(room) {
2277
- return `apeclaw_chat_draft_${normalizeRoomName(room || 'general')}`;
2276
+ return `apeclaw_chat_draft_${normalizeRoomName(room || 'forge')}`;
2278
2277
  }
2279
2278
 
2280
2279
  function applyChatSlash(text) {
@@ -2364,7 +2363,7 @@ function renderChatMessages() {
2364
2363
  const byId = new Map(chatMessages.map((m) => [m.id, m]));
2365
2364
  el.innerHTML = filtered.map(m => {
2366
2365
  const isSelf = m.agentId === chatCredentials.agentId;
2367
- const room = normalizeRoomName(m.room || 'general');
2366
+ const room = normalizeRoomName(m.room || 'forge');
2368
2367
  const parent = m.replyTo ? byId.get(m.replyTo) : null;
2369
2368
  const parentPreview = parent ? `${parent.agentName || parent.agentId}: ${String(parent.text || '').slice(0, 80)}` : `Reply to ${m.replyTo || 'message'}`;
2370
2369
  const reactions = m.reactions && typeof m.reactions === 'object' ? m.reactions : {};
@@ -2423,7 +2422,7 @@ function updateChatInputState() {
2423
2422
  }
2424
2423
 
2425
2424
  function updateChatAuthStatus() {
2426
- const room = normalizeRoomName(document.getElementById('chatRoom').value.trim() || 'general');
2425
+ const room = normalizeRoomName(document.getElementById('chatRoom').value.trim() || 'forge');
2427
2426
  const id = document.getElementById('chatAgentId').value.trim();
2428
2427
  const token = document.getElementById('chatAgentToken').value.trim();
2429
2428
  const identityToken = document.getElementById('chatIdentityToken').value.trim();
@@ -2482,7 +2481,7 @@ function toggleChatTokenVisibility() {
2482
2481
  }
2483
2482
 
2484
2483
  function clearChatCredentials() {
2485
- document.getElementById('chatRoom').value = 'general';
2484
+ document.getElementById('chatRoom').value = 'forge';
2486
2485
  document.getElementById('chatAgentId').value = '';
2487
2486
  document.getElementById('chatAgentToken').value = '';
2488
2487
  document.getElementById('chatIdentityToken').value = '';
@@ -2549,11 +2548,11 @@ async function sendChatMessage() {
2549
2548
 
2550
2549
  async function loadChatHistory() {
2551
2550
  try {
2552
- const r = await fetch(apiUrl(`/api/chat?room=${encodeURIComponent(chatCredentials.room || 'general')}&limit=200`));
2551
+ const r = await fetch(apiUrl(`/api/chat?room=${encodeURIComponent(chatCredentials.room || 'forge')}&limit=200`));
2553
2552
  if (r.ok) {
2554
2553
  const data = await r.json();
2555
2554
  chatMessages = data.messages || [];
2556
- chatUnreadByRoom.set(normalizeRoomName(chatCredentials.room || 'general'), 0);
2555
+ chatUnreadByRoom.set(normalizeRoomName(chatCredentials.room || 'forge'), 0);
2557
2556
  renderChatMessages();
2558
2557
  renderChatRooms();
2559
2558
  }
@@ -2566,15 +2565,15 @@ function connectChatStream() {
2566
2565
  chatSseSource.close();
2567
2566
  chatSseSource = null;
2568
2567
  }
2569
- chatSseSource = new EventSource(apiUrl(`/api/chat/stream?room=${encodeURIComponent(chatCredentials.room || 'general')}`));
2568
+ chatSseSource = new EventSource(apiUrl(`/api/chat/stream?room=${encodeURIComponent(chatCredentials.room || 'forge')}`));
2570
2569
  chatSseSource.onopen = () => {
2571
2570
  chatReconnectAttempts = 0;
2572
2571
  };
2573
2572
  chatSseSource.onmessage = (e) => {
2574
2573
  try {
2575
2574
  const msg = JSON.parse(e.data);
2576
- const msgRoom = normalizeRoomName(msg.room || 'general');
2577
- const current = normalizeRoomName(chatCredentials.room || 'general');
2575
+ const msgRoom = normalizeRoomName(msg.room || 'forge');
2576
+ const current = normalizeRoomName(chatCredentials.room || 'forge');
2578
2577
  if (msgRoom !== current) {
2579
2578
  chatUnreadByRoom.set(msgRoom, Number(chatUnreadByRoom.get(msgRoom) || 0) + 1);
2580
2579
  renderChatRooms();
@@ -2605,7 +2604,7 @@ function connectChatStream() {
2605
2604
  function initChat() {
2606
2605
  // Restore saved credentials from localStorage
2607
2606
  try {
2608
- const savedRoom = localStorage.getItem('apeclaw_chat_room') || 'general';
2607
+ const savedRoom = localStorage.getItem('apeclaw_chat_room') || 'forge';
2609
2608
  const savedId = localStorage.getItem('apeclaw_chat_agentId') || '';
2610
2609
  const savedToken = localStorage.getItem('apeclaw_chat_agentToken') || '';
2611
2610
  const savedIdentity = localStorage.getItem('apeclaw_chat_identityToken') || '';
package/ui/js/skills.js CHANGED
@@ -427,7 +427,7 @@ window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw]
427
427
  var installCmd = 'npx ape-claw skill install ' + rawSlug;
428
428
  var html = '';
429
429
  html += '<div style="margin-bottom:16px"><strong style="color:#cfff04;font-size:14px">' + escapeHtml(it.name || it.slug) + '</strong></div>';
430
- html += '<div class="note" style="margin-bottom:12px;color:var(--muted);font-size:12px">Copy the command below and run it in your terminal to install this skill to Cursor &amp; OpenClaw.</div>';
430
+ html += '<div class="note" style="margin-bottom:12px;color:var(--muted);font-size:12px">Copy the command below and run it in your terminal to install this skill to your <a href="https://openclaw.ai" target="_blank" rel="noopener" style="color:var(--lime)">OpenClaw</a> workspace. <strong style="color:var(--lime)">Requires OpenClaw.</strong></div>';
431
431
  html += '<div style="position:relative;background:rgba(0,0,0,.5);border:1px solid rgba(207,255,4,.15);border-radius:6px;padding:14px 16px;margin-bottom:12px">';
432
432
  html += '<code style="font-size:11px;color:var(--cyan);word-break:break-all;line-height:1.6;display:block">' + escapeHtml(installCmd) + '</code>';
433
433
  html += '</div>';
@@ -747,52 +747,67 @@ window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw]
747
747
  window.addEventListener('hashchange', function () {
748
748
  try { openFromHash(); } catch (e) {}
749
749
  });
750
+ function hydrateImportedFromResults(results) {
751
+ importedAll = (Array.isArray(results) ? results : []).map(function (s, idx) {
752
+ return {
753
+ name: s.name || s.slug || 'Skill',
754
+ slug: s.slug || '',
755
+ description: s.description || s.desc || '',
756
+ desc: s.description || s.desc || '',
757
+ riskTier: s.riskTier || 2,
758
+ source: s.source || 'imported',
759
+ sourceUrl: s.sourceUrl || '',
760
+ fileName: s.fileName || '',
761
+ version: s.version || '1',
762
+ importOk: true,
763
+ vettedOk: Boolean(s.vettedOk !== false && s.vetted !== false),
764
+ addedAt: s.addedAt || s.importedAt || s.createdAt || '',
765
+ importedAt: s.importedAt || s.addedAt || '',
766
+ createdAt: s.createdAt || '',
767
+ _indexOrder: idx,
768
+ onchainTokenId: s.onchainTokenId || null,
769
+ onchainMintTx: s.onchainMintTx || null,
770
+ onchainPublishTx: s.onchainPublishTx || null
771
+ };
772
+ });
773
+ publishedBySlug = {};
774
+ for (var pi = 0; pi < importedAll.length; pi++) {
775
+ var si = importedAll[pi];
776
+ if (si && si.slug && si.onchainTokenId) {
777
+ publishedBySlug[si.slug] = {
778
+ skillId: si.onchainTokenId,
779
+ txs: { mint: si.onchainMintTx || null, publish: si.onchainPublishTx || null }
780
+ };
781
+ }
782
+ }
783
+ applyImportedFilters();
784
+ if (importedBadge) importedBadge.textContent = importedAll.length ? (importedAll.length + ' SKILLS') : 'NONE';
785
+ try { openFromHash(); } catch (e) {}
786
+ }
787
+
788
+ function loadImportedFromStaticFallback() {
789
+ return fetch('/data/skills-search.json', { headers: { 'accept': 'application/json' } })
790
+ .then(function (r) { return r.ok ? r.json() : null; })
791
+ .then(function (j) {
792
+ var results = (j && Array.isArray(j.results)) ? j.results : [];
793
+ hydrateImportedFromResults(results);
794
+ if (importedBadge && results.length) importedBadge.textContent = results.length + ' SKILLS (FALLBACK)';
795
+ })
796
+ .catch(function () {
797
+ if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
798
+ });
799
+ }
800
+
750
801
  fetch(apiBase + '/api/skills/search?limit=5000', { headers: { 'accept': 'application/json' } })
751
802
  .then(function (r) { return r.ok ? r.json() : null; })
752
803
  .then(function (j) {
753
- if (!j || !j.ok) {
754
- if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
755
- return;
756
- }
757
- var results = j.results || [];
758
- importedAll = results.map(function (s, idx) {
759
- return {
760
- name: s.name || s.slug || 'Skill',
761
- slug: s.slug || '',
762
- description: s.description || s.desc || '',
763
- desc: s.description || s.desc || '',
764
- riskTier: s.riskTier || 2,
765
- source: s.source || 'imported',
766
- sourceUrl: s.sourceUrl || '',
767
- fileName: s.fileName || '',
768
- version: s.version || '1',
769
- importOk: true,
770
- vettedOk: Boolean(s.vettedOk !== false && s.vetted !== false),
771
- addedAt: s.addedAt || s.importedAt || s.createdAt || '',
772
- importedAt: s.importedAt || s.addedAt || '',
773
- createdAt: s.createdAt || '',
774
- _indexOrder: idx,
775
- onchainTokenId: s.onchainTokenId || null,
776
- onchainMintTx: s.onchainMintTx || null,
777
- onchainPublishTx: s.onchainPublishTx || null
778
- };
779
- });
780
- publishedBySlug = {};
781
- for (var pi = 0; pi < importedAll.length; pi++) {
782
- var si = importedAll[pi];
783
- if (si && si.slug && si.onchainTokenId) {
784
- publishedBySlug[si.slug] = {
785
- skillId: si.onchainTokenId,
786
- txs: { mint: si.onchainMintTx || null, publish: si.onchainPublishTx || null }
787
- };
788
- }
789
- }
790
- applyImportedFilters();
791
- if (importedBadge) importedBadge.textContent = importedAll.length ? (importedAll.length + ' SKILLS') : 'NONE';
792
- try { openFromHash(); } catch (e) {}
804
+ var results = (j && j.ok && Array.isArray(j.results)) ? j.results : [];
805
+ // If API is unavailable or temporarily empty, use static snapshot so the page is never blank.
806
+ if (!results.length) return loadImportedFromStaticFallback();
807
+ hydrateImportedFromResults(results);
793
808
  })
794
809
  .catch(function () {
795
- if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
810
+ return loadImportedFromStaticFallback();
796
811
  });
797
812
 
798
813
  if (importedSearch) {
@@ -53,7 +53,7 @@
53
53
  var active = !l.external && isActive(l.href, cur);
54
54
  var attrs = active ? ' aria-current="page"' : "";
55
55
  if (l.external) attrs += ' target="_blank" rel="noopener"';
56
- var badge = l.external ? "EXT" : (l.icon === "forge" ? "LOCAL" : "");
56
+ var badge = l.external ? "EXT" : "";
57
57
  navHtml +=
58
58
  '<a class="sb-link" href="' + String(l.href) + '" data-tip="' + String(l.label) + '"' + attrs + ">" +
59
59
  iconSvg(l.icon) +
package/ui/skills.html CHANGED
@@ -776,7 +776,7 @@
776
776
  <a class="pill" href="#" data-copy='npx ape-claw skill install'>Copy command</a>
777
777
  <a class="pill" href="/docs?doc=CLI_GUIDE.md" data-keep-query="1">Install docs</a>
778
778
  </div>
779
- <div class="note" style="margin-top:8px">Add <code>--starter-pack</code> for 61 curated skills. Skills sync to <a href="https://cursor.com" target="_blank" rel="noopener">Cursor</a> &amp; OpenClaw automatically. Works on macOS, Linux, and Windows (PowerShell).</div>
779
+ <div class="note" style="margin-top:8px"><strong style="color:var(--lime)">Requires <a href="https://openclaw.ai" target="_blank" rel="noopener" style="color:var(--lime)">OpenClaw</a></strong> install OpenClaw first. Add <code>--starter-pack</code> for 61 curated skills. Skills are installed to <code>~/.openclaw/skills/</code>. Works on macOS, Linux, and Windows (PowerShell).</div>
780
780
  </div>
781
781
  <div class="card ac-observe">
782
782
  <div class="cardhead">
@@ -798,7 +798,7 @@
798
798
  <p>
799
799
  Install any skill by slug from the command line, or browse below and click <strong>Install</strong> to add to your agent.
800
800
  </p>
801
- <div class="code"><span class="k">npx</span> --yes github:simplefarmer69/ape-claw skill install lincoln-ai</div>
801
+ <div class="code"><span class="k">npx</span> ape-claw skill install lincoln-ai</div>
802
802
  <div class="tools" aria-label="Install instructions">
803
803
  <a class="pill" href="#" data-copy='npx ape-claw skill install lincoln-ai'>Copy command</a>
804
804
  <a class="pill" href="/docs?doc=SKILLCARDS_AND_IMPORTER.md" data-keep-query="1">Skill docs</a>
@@ -1453,22 +1453,29 @@ window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw]
1453
1453
  }, 2500);
1454
1454
  }
1455
1455
 
1456
+ var FALLBACK_STATS = { total: 10024, vetted: 9994, onchain: 10004, user: 0 };
1456
1457
  // Load global stats from API
1457
1458
  (function loadGlobalStats() {
1458
1459
  fetch(apiBase + '/api/skills/stats', { headers: { 'accept': 'application/json' } })
1459
1460
  .then(function (r) { return r.ok ? r.json() : null; })
1460
1461
  .then(function (d) {
1461
- if (!d || !d.ok) return;
1462
+ if (!d || !d.ok || !d.total) {
1463
+ setStatCountUp(statTotal, FALLBACK_STATS.total);
1464
+ setStatCountUp(statVetted, FALLBACK_STATS.vetted);
1465
+ setStatCountUp(statOnchain, FALLBACK_STATS.onchain);
1466
+ setStatCountUp(statContributed, FALLBACK_STATS.user);
1467
+ return;
1468
+ }
1462
1469
  setStatCountUp(statTotal, d.total || 0);
1463
1470
  setStatCountUp(statVetted, d.vetted || 0);
1464
1471
  setStatCountUp(statOnchain, d.onchain || 0);
1465
1472
  setStatCountUp(statContributed, d.user || 0);
1466
1473
  })
1467
1474
  .catch(function () {
1468
- setStatCountUp(statTotal, 10032);
1469
- setStatCountUp(statVetted, 10009);
1470
- setStatCountUp(statOnchain, 7056);
1471
- setStatCountUp(statContributed, 0);
1475
+ setStatCountUp(statTotal, FALLBACK_STATS.total);
1476
+ setStatCountUp(statVetted, FALLBACK_STATS.vetted);
1477
+ setStatCountUp(statOnchain, FALLBACK_STATS.onchain);
1478
+ setStatCountUp(statContributed, FALLBACK_STATS.user);
1472
1479
  });
1473
1480
  })();
1474
1481
 
@@ -1655,7 +1662,7 @@ window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw]
1655
1662
  var installCmd = 'npx ape-claw skill install ' + rawSlug;
1656
1663
  var html = '';
1657
1664
  html += '<div style="margin-bottom:16px"><strong style="color:#cfff04;font-size:14px">' + escapeHtml(it.name || it.slug) + '</strong></div>';
1658
- html += '<div class="note" style="margin-bottom:14px;color:var(--muted);font-size:12px">Run the command below in your terminal to install this skill to Cursor &amp; OpenClaw.</div>';
1665
+ html += '<div class="note" style="margin-bottom:14px;color:var(--muted);font-size:12px">Run the command below in your terminal to install this skill to your <a href="https://openclaw.ai" target="_blank" rel="noopener" style="color:var(--lime)">OpenClaw</a> workspace. <strong style="color:var(--lime)">Requires OpenClaw.</strong></div>';
1659
1666
  html += '<div style="margin-bottom:6px;color:var(--lime);font-size:11px;font-weight:700;letter-spacing:.5px">INSTALL</div>';
1660
1667
  html += '<div style="position:relative;background:rgba(0,0,0,.5);border:1px solid rgba(207,255,4,.15);border-radius:6px;padding:14px 16px;margin-bottom:14px">';
1661
1668
  html += '<code style="font-size:11px;color:var(--cyan);word-break:break-all;line-height:1.6;display:block">' + escapeHtml(installCmd) + '</code>';
@@ -1982,53 +1989,73 @@ window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw]
1982
1989
  window.addEventListener('hashchange', function () {
1983
1990
  try { openFromHash(); } catch (e) {}
1984
1991
  });
1985
- fetch(apiBase + '/api/skills/search?limit=5000', { headers: { 'accept': 'application/json' } })
1986
- .then(function (r) { return r.ok ? r.json() : null; })
1987
- .then(function (j) {
1988
- if (!j || !j.ok) {
1989
- if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
1990
- return;
1991
- }
1992
- var results = j.results || [];
1993
- importedAll = results.map(function (s, idx) {
1994
- return {
1995
- name: s.name || s.slug || 'Skill',
1996
- slug: s.slug || '',
1997
- description: s.description || s.desc || '',
1998
- desc: s.description || s.desc || '',
1999
- riskTier: s.riskTier || 2,
2000
- source: s.source || 'imported',
2001
- sourceUrl: s.sourceUrl || '',
2002
- fileName: s.fileName || '',
2003
- version: s.version || '1',
2004
- importOk: true,
2005
- vettedOk: Boolean(s.vettedOk !== false && s.vetted !== false),
2006
- addedAt: s.addedAt || s.importedAt || s.createdAt || '',
2007
- importedAt: s.importedAt || s.addedAt || '',
2008
- createdAt: s.createdAt || '',
2009
- _indexOrder: idx,
2010
- onchainTokenId: s.onchainTokenId || null,
2011
- onchainMintTx: s.onchainMintTx || null,
2012
- onchainPublishTx: s.onchainPublishTx || null
1992
+ function hydrateFromResults(results) {
1993
+ importedAll = (Array.isArray(results) ? results : []).map(function (s, idx) {
1994
+ return {
1995
+ name: s.name || s.slug || 'Skill',
1996
+ slug: s.slug || '',
1997
+ description: s.description || s.desc || '',
1998
+ desc: s.description || s.desc || '',
1999
+ riskTier: s.riskTier || 2,
2000
+ source: s.source || 'imported',
2001
+ sourceUrl: s.sourceUrl || '',
2002
+ fileName: s.fileName || '',
2003
+ version: s.version || '1',
2004
+ importOk: true,
2005
+ vettedOk: Boolean(s.vettedOk !== false && s.vetted !== false),
2006
+ addedAt: s.addedAt || s.importedAt || s.createdAt || '',
2007
+ importedAt: s.importedAt || s.addedAt || '',
2008
+ createdAt: s.createdAt || '',
2009
+ _indexOrder: idx,
2010
+ onchainTokenId: s.onchainTokenId || null,
2011
+ onchainMintTx: s.onchainMintTx || null,
2012
+ onchainPublishTx: s.onchainPublishTx || null
2013
+ };
2014
+ });
2015
+ publishedBySlug = {};
2016
+ for (var pi = 0; pi < importedAll.length; pi++) {
2017
+ var si = importedAll[pi];
2018
+ if (si && si.slug && si.onchainTokenId) {
2019
+ publishedBySlug[si.slug] = {
2020
+ skillId: si.onchainTokenId,
2021
+ txs: { mint: si.onchainMintTx || null, publish: si.onchainPublishTx || null }
2013
2022
  };
2014
- });
2015
- publishedBySlug = {};
2016
- for (var pi = 0; pi < importedAll.length; pi++) {
2017
- var si = importedAll[pi];
2018
- if (si && si.slug && si.onchainTokenId) {
2019
- publishedBySlug[si.slug] = {
2020
- skillId: si.onchainTokenId,
2021
- txs: { mint: si.onchainMintTx || null, publish: si.onchainPublishTx || null }
2022
- };
2023
- }
2024
2023
  }
2025
- applyImportedFilters();
2026
- if (importedBadge) importedBadge.textContent = importedAll.length ? (importedAll.length + ' SKILLS') : 'NONE';
2027
- try { openFromHash(); } catch (e) {}
2028
- })
2029
- .catch(function () {
2030
- if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
2031
- });
2024
+ }
2025
+ applyImportedFilters();
2026
+ if (importedBadge) importedBadge.textContent = importedAll.length ? (importedAll.length + ' SKILLS') : 'NONE';
2027
+ try { openFromHash(); } catch (e) {}
2028
+ }
2029
+
2030
+ function loadFromStaticFallback() {
2031
+ return fetch('/data/skills-search.json', { headers: { 'accept': 'application/json' } })
2032
+ .then(function (r) { return r.ok ? r.json() : null; })
2033
+ .then(function (j) {
2034
+ var results = (j && Array.isArray(j.results)) ? j.results : [];
2035
+ if (!results.length) {
2036
+ if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
2037
+ return;
2038
+ }
2039
+ hydrateFromResults(results);
2040
+ if (importedBadge) importedBadge.textContent = importedAll.length + ' SKILLS';
2041
+ setStatCountUp(statTotal, importedAll.length);
2042
+ })
2043
+ .catch(function () {
2044
+ if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
2045
+ });
2046
+ }
2047
+
2048
+ loadFromStaticFallback().then(function () {
2049
+ setTimeout(function () {
2050
+ fetch(apiBase + '/api/skills/search?limit=15000&slim=1', { headers: { 'accept': 'application/json' } })
2051
+ .then(function (r) { return r.ok ? r.json() : null; })
2052
+ .then(function (j) {
2053
+ var results = (j && j.ok && Array.isArray(j.results)) ? j.results : [];
2054
+ if (results.length > importedAll.length) hydrateFromResults(results);
2055
+ })
2056
+ .catch(function () {});
2057
+ }, 2000);
2058
+ });
2032
2059
 
2033
2060
  if (importedSearch) {
2034
2061
  importedSearch.addEventListener('input', applyImportedFilters);