ps-claw 1.1.2 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ps-claw",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "PS Claw - AI Agent Gateway with multi-provider support, web UI, and CLI. Lightweight fork of OpenClaw.",
5
5
  "keywords": [
6
6
  "ai",
@@ -350,7 +350,112 @@ body{font-family:'Syne',sans-serif;background:var(--bg);color:var(--txt);display
350
350
  #sidebar.open{transform:none}
351
351
  #menu-btn{display:flex}
352
352
  .model-grid{grid-template-columns:1fr 1fr}
353
+ .store-grid{grid-template-columns:1fr 1fr !important}
354
+ #tabs{overflow-x:auto}
353
355
  }
356
+
357
+ /* ── STORE (Play Store-like) ── */
358
+ .store-banner{
359
+ background:linear-gradient(135deg,var(--acc3) 0%,var(--acc) 50%,#9b8fff 100%);
360
+ border-radius:var(--rad);padding:28px 32px;margin-bottom:20px;
361
+ display:flex;align-items:center;gap:24px;color:#fff;
362
+ box-shadow:0 8px 32px rgba(124,106,247,.3);position:relative;overflow:hidden
363
+ }
364
+ .store-banner::after{
365
+ content:'🦞';position:absolute;right:24px;top:50%;transform:translateY(-50%);
366
+ font-size:90px;opacity:.18
367
+ }
368
+ .store-banner h2{font-size:24px;font-weight:800;margin-bottom:6px;letter-spacing:-.5px}
369
+ .store-banner p{font-size:13px;opacity:.9;max-width:560px;line-height:1.6}
370
+ .store-toolbar{display:flex;flex-direction:column;gap:12px;margin-bottom:20px}
371
+ #store-search{
372
+ width:100%;padding:11px 16px;background:var(--surface2);border:1px solid var(--border);
373
+ border-radius:var(--rad-sm);color:var(--txt);font-size:14px;outline:none;
374
+ font-family:'Syne',sans-serif;transition:border-color .15s
375
+ }
376
+ #store-search:focus{border-color:var(--acc)}
377
+ .store-cats{display:flex;gap:8px;flex-wrap:wrap}
378
+ .store-cat{
379
+ padding:6px 14px;border-radius:20px;border:1px solid var(--border);
380
+ background:transparent;color:var(--txt3);cursor:pointer;font-size:12px;font-weight:700;
381
+ transition:all .15s;font-family:'Syne',sans-serif
382
+ }
383
+ .store-cat:hover{color:var(--txt2);border-color:var(--border2)}
384
+ .store-cat.on{background:var(--acc);border-color:var(--acc);color:#fff}
385
+ .store-section-title{
386
+ font-size:16px;font-weight:800;margin:20px 0 14px;letter-spacing:-.3px;
387
+ display:flex;align-items:center;gap:8px
388
+ }
389
+ .store-grid{
390
+ display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
391
+ gap:14px;margin-bottom:24px
392
+ }
393
+ .store-card{
394
+ background:var(--surface2);border:1px solid var(--border);
395
+ border-radius:var(--rad);padding:16px;cursor:pointer;
396
+ transition:all .2s;display:flex;flex-direction:column;gap:8px;
397
+ position:relative;overflow:hidden
398
+ }
399
+ .store-card:hover{border-color:var(--border2);transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.3)}
400
+ .store-card.installed{border-color:rgba(34,201,138,.4)}
401
+ .store-card-head{display:flex;align-items:center;gap:10px}
402
+ .store-icon{
403
+ width:42px;height:42px;border-radius:11px;flex-shrink:0;
404
+ background:linear-gradient(135deg,var(--surface3),var(--surface));
405
+ display:flex;align-items:center;justify-content:center;font-size:22px;
406
+ border:1px solid var(--border2)
407
+ }
408
+ .store-card.featured .store-icon{background:linear-gradient(135deg,var(--acc3),var(--acc));border-color:transparent}
409
+ .store-name{font-size:14px;font-weight:700;line-height:1.2}
410
+ .store-cat-tag{font-size:10px;color:var(--txt3);text-transform:uppercase;letter-spacing:.5px;font-weight:700}
411
+ .store-desc{font-size:12px;color:var(--txt2);line-height:1.55;flex:1;min-height:36px}
412
+ .store-meta{display:flex;align-items:center;justify-content:space-between;font-size:11px;color:var(--txt3);margin-top:4px}
413
+ .store-rating{display:flex;align-items:center;gap:3px;color:var(--yellow);font-weight:700}
414
+ .store-installed-badge{
415
+ position:absolute;top:10px;right:10px;
416
+ background:rgba(34,201,138,.15);color:var(--green);
417
+ font-size:10px;font-weight:700;padding:3px 8px;border-radius:10px
418
+ }
419
+ .store-actions{display:flex;gap:6px;margin-top:6px}
420
+ .store-actions .btn{flex:1}
421
+ .store-empty{text-align:center;padding:40px 20px;color:var(--txt3)}
422
+ .store-empty-icon{font-size:48px;margin-bottom:12px}
423
+
424
+ /* ── QUICKSTART MODAL ── */
425
+ #qs-bg{
426
+ display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:300;
427
+ align-items:center;justify-content:center;backdrop-filter:blur(8px)
428
+ }
429
+ #qs-bg.open{display:flex}
430
+ #qs-modal{
431
+ background:var(--surface);border:1px solid var(--border2);border-radius:var(--rad);
432
+ padding:32px;width:560px;max-width:95vw;max-height:90vh;overflow-y:auto;
433
+ box-shadow:0 24px 80px rgba(0,0,0,.6)
434
+ }
435
+ #qs-modal h2{font-size:22px;font-weight:800;margin-bottom:8px;letter-spacing:-.4px}
436
+ #qs-modal .qs-sub{font-size:13px;color:var(--txt2);margin-bottom:24px;line-height:1.6}
437
+ .qs-step{font-size:11px;color:var(--acc2);font-weight:700;text-transform:uppercase;letter-spacing:.8px;margin-bottom:6px}
438
+ .qs-provs{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:20px}
439
+ .qs-prov{
440
+ padding:14px;background:var(--surface2);border:1.5px solid var(--border);
441
+ border-radius:var(--rad-sm);cursor:pointer;transition:all .15s;text-align:left;
442
+ font-family:'Syne',sans-serif;color:var(--txt);font-size:13px;font-weight:600
443
+ }
444
+ .qs-prov:hover{border-color:var(--border2)}
445
+ .qs-prov.sel{border-color:var(--acc);background:rgba(124,106,247,.08)}
446
+ .qs-prov .qs-prov-name{display:block;font-size:14px;margin-bottom:3px}
447
+ .qs-prov .qs-prov-desc{font-size:11px;color:var(--txt3);font-weight:500}
448
+ .qs-final{background:var(--surface2);border:1px solid var(--border);border-radius:var(--rad-sm);padding:16px;margin-top:14px}
449
+ .qs-final-row{display:flex;justify-content:space-between;font-size:13px;padding:6px 0;border-bottom:1px solid var(--border)}
450
+ .qs-final-row:last-child{border:none}
451
+
452
+ /* ── BROWSER USE ── */
453
+ .bu-status-ok{background:rgba(34,201,138,.08);border:1px solid rgba(34,201,138,.3);color:var(--green);padding:12px 16px;border-radius:var(--rad-sm);font-size:13px;font-weight:600}
454
+ .bu-status-warn{background:rgba(240,160,72,.08);border:1px solid rgba(240,160,72,.3);color:var(--orange);padding:12px 16px;border-radius:var(--rad-sm);font-size:13px;font-weight:600}
455
+ .bu-status-err{background:rgba(240,90,90,.08);border:1px solid rgba(240,90,90,.3);color:var(--red);padding:12px 16px;border-radius:var(--rad-sm);font-size:13px;font-weight:600}
456
+ .bu-result-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--rad-sm);padding:14px;font-family:'JetBrains Mono',monospace;font-size:12px;white-space:pre-wrap;max-height:400px;overflow-y:auto;color:var(--txt);line-height:1.6}
457
+ .bu-result-err{background:rgba(240,90,90,.08);border:1px solid rgba(240,90,90,.3);color:var(--red)}
458
+
354
459
  </style>
355
460
  </head>
356
461
  <body>
@@ -396,6 +501,8 @@ body{font-family:'Syne',sans-serif;background:var(--bg);color:var(--txt);display
396
501
  <div class="tab active" data-t="chat" onclick="goTab('chat')">💬 Chat</div>
397
502
  <div class="tab" data-t="keys" onclick="goTab('keys')">🔑 API Keys</div>
398
503
  <div class="tab" data-t="models" onclick="goTab('models')">🤖 Modelos</div>
504
+ <div class="tab" data-t="store" onclick="goTab('store')">🛒 Loja</div>
505
+ <div class="tab" data-t="browser" onclick="goTab('browser')">🌐 Browser</div>
399
506
  <div class="tab" data-t="gateways" onclick="goTab('gateways')">🔌 Gateways</div>
400
507
  <div class="tab" data-t="settings" onclick="goTab('settings')">⚙️ Config</div>
401
508
  </div>
@@ -595,6 +702,80 @@ body{font-family:'Syne',sans-serif;background:var(--bg);color:var(--txt);display
595
702
  </div>
596
703
  </div>
597
704
  </div>
705
+
706
+ <!-- STORE (Play Store-like) -->
707
+ <div class="page" id="store-page">
708
+ <div class="page-inner" style="max-width:1100px">
709
+ <div class="sp-header">
710
+ <div class="sp-title">🛒 Loja PS Claw</div>
711
+ <div class="sp-sub">Catálogo do <strong style="color:var(--acc2)">Claw Hub</strong> — instale plugins, MCPs, skills e integrações com 1 clique. Tudo baixado direto do dashboard.</div>
712
+ </div>
713
+
714
+ <!-- banner de destaque -->
715
+ <div id="store-banner" class="store-banner"></div>
716
+
717
+ <!-- search + filtros -->
718
+ <div class="store-toolbar">
719
+ <input id="store-search" type="text" placeholder="🔎 Buscar plugins..." oninput="renderStore()"/>
720
+ <div id="store-cats" class="store-cats"></div>
721
+ </div>
722
+
723
+ <!-- seção: Em Destaque -->
724
+ <h3 class="store-section-title">⭐ Em Destaque</h3>
725
+ <div id="store-featured" class="store-grid"></div>
726
+
727
+ <!-- seção: Todos -->
728
+ <h3 class="store-section-title">📦 Todos os Plugins</h3>
729
+ <div id="store-all" class="store-grid"></div>
730
+ </div>
731
+ </div>
732
+
733
+ <!-- BROWSER USE -->
734
+ <div class="page" id="browser-page">
735
+ <div class="page-inner" style="max-width:900px">
736
+ <div class="sp-header">
737
+ <div class="sp-title">🌐 Browser Use</div>
738
+ <div class="sp-sub">Controle um navegador por IA: diga o que quer fazer e a IA abre o site, clica, preenche formulários e extrai dados automaticamente.</div>
739
+ </div>
740
+
741
+ <div class="card" id="bu-status-card">
742
+ <h3>📋 Status da Integração</h3>
743
+ <div id="bu-status-content"><p style="color:var(--txt2)">Verificando...</p></div>
744
+ <div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">
745
+ <button class="btn btn-primary" onclick="buInstall()">⬇️ Instalar Browser Use</button>
746
+ <button class="btn btn-ghost" onclick="buStatus()">🔄 Re-verificar</button>
747
+ <a class="btn btn-ghost" href="https://github.com/browser-use/browser-use" target="_blank" style="text-decoration:none">📖 Documentação</a>
748
+ </div>
749
+ </div>
750
+
751
+ <div class="card">
752
+ <h3>🤖 Rodar Tarefa no Browser</h3>
753
+ <div class="field">
754
+ <label>URL inicial (opcional)</label>
755
+ <input type="text" id="bu-url" placeholder="https://exemplo.com"/>
756
+ </div>
757
+ <div class="field">
758
+ <label>Tarefa em linguagem natural</label>
759
+ <textarea id="bu-task" placeholder="Ex: Acesse o site, faça login com user admin e senha 1234, depois baixe o relatório de vendas do mês passado." style="min-height:100px"></textarea>
760
+ </div>
761
+ <div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
762
+ <button class="btn btn-primary" onclick="buRun()">▶️ Executar Tarefa</button>
763
+ <span id="bu-run-status" style="font-size:12px;color:var(--txt3)"></span>
764
+ </div>
765
+ <div id="bu-result" style="margin-top:14px"></div>
766
+ </div>
767
+
768
+ <div class="card">
769
+ <h3>💡 Exemplos de Tarefas</h3>
770
+ <div class="chips" style="justify-content:flex-start">
771
+ <div class="chip" onclick="document.getElementById('bu-url').value='https://news.ycombinator.com';document.getElementById('bu-task').value='Liste as 5 notícias mais populares da página inicial com título e link.'">📋 Extrair notícias</div>
772
+ <div class="chip" onclick="document.getElementById('bu-url').value='https://google.com';document.getElementById('bu-task').value='Pesquise por \"PS Claw AI agent\" e liste os 3 primeiros resultados com título e URL.'">🔎 Pesquisar no Google</div>
773
+ <div class="chip" onclick="document.getElementById('bu-url').value='';document.getElementById('bu-task').value='Acesse amazon.com e encontre os 5 livros mais vendidos de tecnologia esta semana.'">📚 Top produtos</div>
774
+ <div class="chip" onclick="document.getElementById('bu-url').value='https://github.com';document.getElementById('bu-task').value='Vá em trending, liste os 5 repositórios mais populares do dia com nome, linguagem e estrelas.'">📊 GitHub Trending</div>
775
+ </div>
776
+ </div>
777
+ </div>
778
+ </div>
598
779
  </div><!-- /main -->
599
780
 
600
781
  <!-- MODAL ADD GATEWAY -->
@@ -611,6 +792,64 @@ body{font-family:'Syne',sans-serif;background:var(--bg);color:var(--txt);display
611
792
  </div>
612
793
  </div>
613
794
 
795
+ <!-- QUICKSTART MODAL (primeiro uso) -->
796
+ <div id="qs-bg">
797
+ <div id="qs-modal">
798
+ <div style="text-align:center;margin-bottom:18px">
799
+ <div style="font-size:48px;margin-bottom:6px">🦞</div>
800
+ <h2>Bem-vindo ao PS Claw!</h2>
801
+ <div class="qs-sub">Vamos configurar tudo em menos de 1 minuto. Você só precisa de <strong style="color:var(--acc2)">uma</strong> API key.</div>
802
+ </div>
803
+
804
+ <div class="qs-step">Passo 1 — Escolha o provedor</div>
805
+ <div class="qs-provs" id="qs-provs">
806
+ <button class="qs-prov sel" data-p="google" onclick="qsSelProv('google')">
807
+ <span class="qs-prov-name">🔵 Google Gemini</span>
808
+ <span class="qs-prov-desc">Grátis para sempre · recomendado</span>
809
+ </button>
810
+ <button class="qs-prov" data-p="anthropic" onclick="qsSelProv('anthropic')">
811
+ <span class="qs-prov-name">🟠 Anthropic Claude</span>
812
+ <span class="qs-prov-desc">US$5 de crédito inicial</span>
813
+ </button>
814
+ <button class="qs-prov" data-p="openai" onclick="qsSelProv('openai')">
815
+ <span class="qs-prov-name">🟢 OpenAI GPT-4o</span>
816
+ <span class="qs-prov-desc">US$5 de crédito inicial</span>
817
+ </button>
818
+ <button class="qs-prov" data-p="mistral" onclick="qsSelProv('mistral')">
819
+ <span class="qs-prov-name">🟣 Mistral</span>
820
+ <span class="qs-prov-desc">Trial de crédito · Europa</span>
821
+ </button>
822
+ </div>
823
+
824
+ <div class="qs-step">Passo 2 — Cole sua API Key</div>
825
+ <div class="field">
826
+ <label>API Key</label>
827
+ <input type="password" id="qs-key" placeholder="Cole aqui..." oninput="qsClearTest()"/>
828
+ <div style="font-size:11px;color:var(--txt3);margin-top:6px" id="qs-key-hint">
829
+ Obtenha grátis em: <a href="https://aistudio.google.com/apikey" target="_blank" style="color:var(--acc2)">aistudio.google.com/apikey</a>
830
+ </div>
831
+ </div>
832
+ <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="qsTestKey()">🔍 Testar chave</button>
833
+ <span id="qs-test-result" style="font-size:12px;margin-left:10px"></span>
834
+
835
+ <div class="qs-step" style="margin-top:18px">Passo 3 — Modelo padrão</div>
836
+ <div class="field">
837
+ <select id="qs-model"></select>
838
+ </div>
839
+
840
+ <div class="qs-step" style="margin-top:18px">Passo 4 — Perfil (opcional)</div>
841
+ <div class="row">
842
+ <div class="field"><label>Seu nome</label><input type="text" id="qs-name" placeholder="Você"/></div>
843
+ <div class="field"><label>Nome do agente</label><input type="text" id="qs-agent" placeholder="PS Claw"/></div>
844
+ </div>
845
+
846
+ <div class="modal-acts" style="margin-top:24px">
847
+ <button class="btn btn-ghost" onclick="closeQs()">Pular por agora</button>
848
+ <button class="btn btn-primary" onclick="qsFinish()">✅ Finalizar e conversar</button>
849
+ </div>
850
+ </div>
851
+ </div>
852
+
614
853
  <div id="toast"></div>
615
854
 
616
855
  <script>
@@ -669,11 +908,13 @@ function goTab(t){
669
908
  document.querySelectorAll('.tab').forEach(e=>e.classList.toggle('active',e.dataset.t===t));
670
909
  document.querySelectorAll('.page').forEach(e=>e.classList.remove('active'));
671
910
  document.getElementById(t+'-page').classList.add('active');
672
- document.getElementById('tb-title').textContent={chat:'Chat',keys:'API Keys',models:'Modelos',gateways:'Gateways',settings:'Configurações'}[t]||t;
911
+ document.getElementById('tb-title').textContent={chat:'Chat',keys:'API Keys',models:'Modelos',store:'Loja',browser:'Browser Use',gateways:'Gateways',settings:'Configurações'}[t]||t;
673
912
  if(t==='models') renderModels();
674
913
  if(t==='gateways') renderGws();
675
914
  if(t==='keys') loadKeys();
676
915
  if(t==='settings') loadCfg();
916
+ if(t==='store') loadStore();
917
+ if(t==='browser') buStatus();
677
918
  }
678
919
 
679
920
  // ─── CHAT ─────────────────────────────────────────────────────────────────
@@ -1048,11 +1289,264 @@ function autoResize(el){el.style.height='auto';el.style.height=Math.min(el.scrol
1048
1289
  function sendQ(t){document.getElementById('msg-in').value=t;goTab('chat');sendMsg();}
1049
1290
  function toggleSb(){document.getElementById('sidebar').classList.toggle('open');}
1050
1291
 
1292
+ // ─── QUICKSTART (primeiro uso) ─────────────────────────────────────────────
1293
+ let qsProv = 'google';
1294
+ const QS_LINKS = {
1295
+ google:'https://aistudio.google.com/apikey',
1296
+ anthropic:'https://console.anthropic.com/api-keys',
1297
+ openai:'https://platform.openai.com/api-keys',
1298
+ mistral:'https://console.mistral.ai/api-keys',
1299
+ };
1300
+ const QS_PH = {
1301
+ google:'AIzaSy...',
1302
+ anthropic:'sk-ant-api03-...',
1303
+ openai:'sk-proj-...',
1304
+ mistral:'...',
1305
+ };
1306
+ function qsSelProv(p){
1307
+ qsProv = p;
1308
+ document.querySelectorAll('.qs-prov').forEach(b => b.classList.toggle('sel', b.dataset.p === p));
1309
+ // atualiza hint
1310
+ document.getElementById('qs-key-hint').innerHTML =
1311
+ `Obtenha em: <a href="${QS_LINKS[p]}" target="_blank" style="color:var(--acc2)">${QS_LINKS[p].replace('https://','')}</a>`;
1312
+ document.getElementById('qs-key').placeholder = QS_PH[p];
1313
+ qsClearTest();
1314
+ // atualiza lista de modelos
1315
+ const sel = document.getElementById('qs-model');
1316
+ const models = MODELS.filter(m => m.p === p);
1317
+ sel.innerHTML = models.map(m => `<option value="${m.id}">${m.name} — ${m.desc}</option>`).join('');
1318
+ }
1319
+ function qsClearTest(){
1320
+ const r = document.getElementById('qs-test-result');
1321
+ r.textContent = '';
1322
+ r.style.color = 'var(--txt3)';
1323
+ }
1324
+ async function qsTestKey(){
1325
+ const key = document.getElementById('qs-key').value.trim();
1326
+ const r = document.getElementById('qs-test-result');
1327
+ if (!key){ r.textContent='⚠️ Cole a chave primeiro'; r.style.color='var(--orange)'; return; }
1328
+ r.textContent = '🔍 Testando...'; r.style.color = 'var(--txt3)';
1329
+ try {
1330
+ let ok = false;
1331
+ if (qsProv === 'anthropic') {
1332
+ const x = await fetch(px('https://api.anthropic.com/v1/models'), { headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' } });
1333
+ ok = x.ok;
1334
+ } else if (qsProv === 'openai') {
1335
+ const x = await fetch(px('https://api.openai.com/v1/models'), { headers: { Authorization: 'Bearer ' + key } });
1336
+ ok = x.ok;
1337
+ } else if (qsProv === 'google') {
1338
+ const x = await fetch(px(`https://generativelanguage.googleapis.com/v1beta/models?key=${key}`));
1339
+ ok = x.ok;
1340
+ } else if (qsProv === 'mistral') {
1341
+ const x = await fetch(px('https://api.mistral.ai/v1/models'), { headers: { Authorization: 'Bearer ' + key } });
1342
+ ok = x.ok;
1343
+ }
1344
+ if (ok) { r.textContent = '✅ Chave válida!'; r.style.color = 'var(--green)'; }
1345
+ else { r.textContent = '❌ Chave inválida ou erro'; r.style.color = 'var(--red)'; }
1346
+ } catch (e) {
1347
+ r.textContent = '❌ ' + e.message; r.style.color = 'var(--red)';
1348
+ }
1349
+ }
1350
+ function openQs(){ document.getElementById('qs-bg').classList.add('open'); qsSelProv('google'); }
1351
+ function closeQs(){ document.getElementById('qs-bg').classList.remove('open'); }
1352
+ function qsFinish(){
1353
+ const key = document.getElementById('qs-key').value.trim();
1354
+ if (!key) { toast('⚠️ Cole sua API key'); return; }
1355
+ const model = document.getElementById('qs-model').value;
1356
+ const name = document.getElementById('qs-name').value.trim() || 'Você';
1357
+ const agent = document.getElementById('qs-agent').value.trim() || 'PS Claw';
1358
+ S.keys[qsProv] = key;
1359
+ S.model = model;
1360
+ S.cfg.name = name;
1361
+ S.cfg.agent = agent;
1362
+ S.onboarded = true;
1363
+ save();
1364
+ updateStatus();
1365
+ const m = MODELS.find(x => x.id === model);
1366
+ if (m) document.getElementById('model-name-badge').textContent = m.name;
1367
+ closeQs();
1368
+ toast('🎉 PS Claw configurado! Pode conversar.');
1369
+ goTab('chat');
1370
+ }
1371
+
1372
+ // ─── STORE (Claw Hub catalog) ──────────────────────────────────────────────
1373
+ let storeData = null;
1374
+ let storeCatFilter = 'all';
1375
+ let storeBusy = {};
1376
+
1377
+ async function loadStore(){
1378
+ try {
1379
+ const r = await fetch('/api/store');
1380
+ storeData = await r.json();
1381
+ } catch {
1382
+ storeData = { plugins: [], featured: [], categories: [] };
1383
+ }
1384
+ renderStore();
1385
+ }
1386
+
1387
+ function renderStore(){
1388
+ if (!storeData) return;
1389
+ // banner
1390
+ const banner = document.getElementById('store-banner');
1391
+ const instCount = storeData.plugins.filter(p => p.installed).length;
1392
+ banner.innerHTML = `
1393
+ <div style="position:relative;z-index:1;flex:1">
1394
+ <h2>🛒 Claw Hub — Loja de Plugins</h2>
1395
+ <p>${storeData.plugins.length} plugins disponíveis · ${storeData.categories.length} categorias · ${instCount} instalados.<br>
1396
+ Instale MCPs, integrações e skills com 1 clique — tudo baixado direto do dashboard.</p>
1397
+ </div>`;
1398
+ // categorias
1399
+ const cats = document.getElementById('store-cats');
1400
+ cats.innerHTML = `<button class="store-cat ${storeCatFilter==='all'?'on':''}" onclick="storeSetCat('all')">Todos</button>` +
1401
+ storeData.categories.map(c => `<button class="store-cat ${storeCatFilter===c?'on':''}" onclick="storeSetCat('${c}')">${h(c)}</button>`).join('');
1402
+
1403
+ const search = (document.getElementById('store-search')?.value || '').toLowerCase();
1404
+ const filterFn = p => {
1405
+ if (storeCatFilter !== 'all' && p.category !== storeCatFilter) return false;
1406
+ if (search && !(p.name.toLowerCase().includes(search) || p.desc.toLowerCase().includes(search) || p.category.toLowerCase().includes(search))) return false;
1407
+ return true;
1408
+ };
1409
+
1410
+ // featured
1411
+ const featured = storeData.featured.filter(filterFn);
1412
+ document.getElementById('store-featured').innerHTML = featured.length
1413
+ ? featured.map(p => storeCardHTML(p, true)).join('')
1414
+ : `<div class="store-empty" style="grid-column:1/-1"><div class="store-empty-icon">🔍</div>Nenhum plugin em destaque corresponde à busca.</div>`;
1415
+
1416
+ // all
1417
+ const all = storeData.plugins.filter(filterFn);
1418
+ document.getElementById('store-all').innerHTML = all.length
1419
+ ? all.map(p => storeCardHTML(p, false)).join('')
1420
+ : `<div class="store-empty" style="grid-column:1/-1"><div class="store-empty-icon">📦</div>Nenhum plugin encontrado.</div>`;
1421
+ }
1422
+
1423
+ function storeSetCat(c){ storeCatFilter = c; renderStore(); }
1424
+
1425
+ function storeCardHTML(p, featured){
1426
+ return `
1427
+ <div class="store-card ${featured?'featured':''} ${p.installed?'installed':''}">
1428
+ ${p.installed ? '<span class="store-installed-badge">✓ instalado</span>' : ''}
1429
+ <div class="store-card-head">
1430
+ <div class="store-icon">${p.icon || '📦'}</div>
1431
+ <div style="flex:1;min-width:0">
1432
+ <div class="store-name">${h(p.name)}</div>
1433
+ <div class="store-cat-tag">${h(p.category)}</div>
1434
+ </div>
1435
+ </div>
1436
+ <div class="store-desc">${h(p.desc)}</div>
1437
+ <div class="store-meta">
1438
+ <span class="store-rating">★ ${p.rating || '—'}</span>
1439
+ <span>${h(p.downloads || '')} downloads</span>
1440
+ </div>
1441
+ <div class="store-actions">
1442
+ ${p.installed
1443
+ ? `<button class="btn btn-ghost btn-sm" onclick="storeUninst('${p.id}')">🗑️ Remover</button>`
1444
+ : `<button class="btn btn-primary btn-sm" id="store-btn-${p.id}" onclick="storeInstall('${p.id}')">⬇️ Instalar</button>`}
1445
+ <a class="btn btn-ghost btn-sm" href="${p.homepage || '#'}" target="_blank" style="text-decoration:none;text-align:center">🔗</a>
1446
+ </div>
1447
+ </div>`;
1448
+ }
1449
+
1450
+ async function storeInstall(id){
1451
+ const btn = document.getElementById('store-btn-' + id);
1452
+ if (btn) { btn.disabled = true; btn.textContent = '⏳ Instalando...'; }
1453
+ toast('⬇️ Instalando plugin... pode levar alguns segundos', 4000);
1454
+ try {
1455
+ const r = await fetch('/api/store/install', {
1456
+ method: 'POST',
1457
+ headers: { 'Content-Type': 'application/json' },
1458
+ body: JSON.stringify({ id }),
1459
+ });
1460
+ const data = await r.json();
1461
+ if (data.ok) {
1462
+ toast('✅ ' + data.message);
1463
+ await loadStore();
1464
+ } else {
1465
+ toast('❌ ' + (data.message || 'Falha'));
1466
+ if (btn) { btn.disabled = false; btn.textContent = '⬇️ Instalar'; }
1467
+ }
1468
+ } catch (e) {
1469
+ toast('❌ Erro: ' + e.message);
1470
+ if (btn) { btn.disabled = false; btn.textContent = '⬇️ Instalar'; }
1471
+ }
1472
+ }
1473
+
1474
+ async function storeUninst(id){
1475
+ if (!confirm('Remover plugin?')) return;
1476
+ try {
1477
+ const r = await fetch('/api/store/uninstall', {
1478
+ method: 'POST',
1479
+ headers: { 'Content-Type': 'application/json' },
1480
+ body: JSON.stringify({ id }),
1481
+ });
1482
+ const data = await r.json();
1483
+ toast(data.ok ? '🗑️ ' + data.message : '❌ ' + data.message);
1484
+ await loadStore();
1485
+ } catch (e) { toast('❌ ' + e.message); }
1486
+ }
1487
+
1488
+ // ─── BROWSER USE ───────────────────────────────────────────────────────────
1489
+ async function buStatus(){
1490
+ const el = document.getElementById('bu-status-content');
1491
+ el.innerHTML = '<p style="color:var(--txt2)">Verificando...</p>';
1492
+ try {
1493
+ const r = await fetch('/api/browser-use/status');
1494
+ const d = await r.json();
1495
+ if (d.ready) {
1496
+ el.innerHTML = `<div class="bu-status-ok">✅ ${d.message}</div>`;
1497
+ } else if (d.installed === false) {
1498
+ el.innerHTML = `<div class="bu-status-warn">⚠️ ${d.message}</div>`;
1499
+ } else {
1500
+ el.innerHTML = `<div class="bu-status-err">❌ ${d.message}</div>`;
1501
+ }
1502
+ } catch (e) {
1503
+ el.innerHTML = `<div class="bu-status-err">❌ Erro ao verificar: ${e.message}</div>`;
1504
+ }
1505
+ }
1506
+
1507
+ async function buInstall(){
1508
+ toast('⬇️ Instalando browser-use + playwright (pode levar 1-2 min)...', 5000);
1509
+ try {
1510
+ const r = await fetch('/api/browser-use/install', { method: 'POST' });
1511
+ const d = await r.json();
1512
+ toast(d.ok ? '✅ ' + d.message : '❌ ' + d.message, 5000);
1513
+ buStatus();
1514
+ } catch (e) { toast('❌ ' + e.message); }
1515
+ }
1516
+
1517
+ async function buRun(){
1518
+ const task = document.getElementById('bu-task').value.trim();
1519
+ const url = document.getElementById('bu-url').value.trim();
1520
+ const statusEl = document.getElementById('bu-run-status');
1521
+ const resultEl = document.getElementById('bu-result');
1522
+ if (!task) { toast('⚠️ Descreva a tarefa'); return; }
1523
+ statusEl.textContent = '⏳ Executando... pode levar 1-2 minutos';
1524
+ resultEl.innerHTML = '<div class="bu-result-box">⏳ Aguardando IA terminar a tarefa no browser...</div>';
1525
+ try {
1526
+ const r = await fetch('/api/browser-use/run', {
1527
+ method: 'POST',
1528
+ headers: { 'Content-Type': 'application/json' },
1529
+ body: JSON.stringify({ task, url }),
1530
+ });
1531
+ const d = await r.json();
1532
+ statusEl.textContent = d.ok ? '✅ Concluído' : '❌ Falhou';
1533
+ resultEl.innerHTML = `<div class="bu-result-box ${d.ok?'':'bu-result-err'}">${h(d.message || 'Sem resposta')}</div>`;
1534
+ } catch (e) {
1535
+ statusEl.textContent = '❌ Erro';
1536
+ resultEl.innerHTML = `<div class="bu-result-box bu-result-err">❌ ${h(e.message)}</div>`;
1537
+ }
1538
+ }
1539
+
1051
1540
  // ─── BOOT ─────────────────────────────────────────────────────────────────
1052
1541
  renderChatList();renderMsgs();updateStatus();
1053
1542
  const bm=MODELS.find(m=>m.id===S.model);
1054
1543
  if(bm)document.getElementById('model-name-badge').textContent=bm.name;
1055
1544
  loadKeys();
1545
+
1546
+ // Quickstart no primeiro uso (web)
1547
+ if (!S.onboarded && !S.model && !Object.values(S.keys||{}).some(Boolean)) {
1548
+ setTimeout(() => openQs(), 500);
1549
+ }
1056
1550
  </script>
1057
1551
  </body>
1058
1552
  </html>