
    TjG                        S SK JrJrJrJr  S SKrS SKr\" \5      rSSSSSSS	S
.SSSSSSSS	S.SSSSSSSS
.SSSSSSSS
./r	S r
S rSr\R                  S 5      S! 5       r\R                  S"5      S# 5       r\R                  S$S%/S&9S' 5       rS(rS)S*S+S,S-S..S)S/S0S,S-S..S)S1S2S,S-S..S3S4S5S6S7S..S3S8S9S6S7S..S3S:S;S6S7S..S3S<S=S6S7S../rS> rS? r\R                  S@SA/S&9SB 5       r\R                  S@S%/S&9SC 5       rSDr1 SEkrSF r\R                  SGSA/S&9SH 5       r\R                  SIS%/S&9SJ 5       r\R                  SKSL/S&9SM 5       r\R                  SKSN/S&9SO 5       r\R                  SPS%/S&9SQ 5       r\SR:X  a  \R?                  SSSTSUSV9  gg)W    )Flaskjsonifyrequestrender_template_stringNzhermes-agent-carlozHermes Agentz2Native Hermes-Agent-Oberflaeche der Carlo-Instanz.u   ⚕z/https://carlo.agent.agentsolutions-mallorca.comzhermes-carloz/opt/struktur/hermes-carlo)idnamedesciconurl	containercomposezHermes WorkspacezDNousResearch Hermes Agent. Carlos persoenliche KI-Instanz mit Swarm.u   ⚡z0https://hermes-carlo.agentsolutions-mallorca.comzJhttps://cockpit-carlo.agentsolutions-mallorca.com/personas/?instance=carlo)r   r   r	   r
   r   r   persona_urlr   zhermes-werkstandardzHermes WerkstandardzFUnveraenderte Nousresearch Upstream-Instanz. Referenz fuer Carlo/Mari.u   🧠z0https://werkstandard.agentsolutions-mallorca.comz!/opt/struktur/hermes-werkstandardmirofishMiroFishzCMulti-Agenten-Simulation zur Zukunftsvorhersage und Marktforschung.u   🐟z,https://mirofish.agentsolutions-mallorca.comz/opt/struktur/mirofishc                      [         R                  " SSSSU /SSSS9nUR                  R                  5       nUS:X  a  g	US
:X  a  gU=(       d    S$ !    g= f)Ndockerinspectz--formatz{{.State.Status}}T   capture_outputtexttimeoutrunningonlineexitedoffline	unbekannt)
subprocessrunstdoutstrip)r   resultstatuss      $/opt/struktur/dashboard/app-carlo.py
get_statusr%      sj    y*.A9MdA
 $$&Yx([(s   <A A 
A Ac                     U S   (       aD  U S   nUS:X  a  SU S3nOUS:X  a  SU S3nOUS:X  a  SU S3n[         R                  " WS	S	S	S
S9nO[         R                  " SXS   /S	S	SS9nUR                  S:H  $ )Nr   startzcd z && docker compose up -dstopz && docker compose stoprestartz && docker compose restartT<   )shellr   r   r   r   r      r   r   )r   r   
returncode)serviceactioncompose_dircmdr"   s        r$   docker_actionr2      s    yi(W}$<=Cv}$;<Cy }$>?C44Y[\v{34dB
 !!    u*  <!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cockpit Carlo</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { background: #0f1117; color: #e2e8f0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; min-height: 100vh; }
  header { padding: 2rem 3rem; border-bottom: 1px solid #1e2330; }
  header h1 { font-size: 1.5rem; font-weight: 600; color: #fff; }
  header span { font-size: 0.85rem; color: #64748b; }
  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; padding: 3rem; }
  .card { background: #161b27; border: 1px solid #1e2330; border-radius: 12px; padding: 1.5rem; }
  .card-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 1rem; }
  .card-icon { font-size: 2rem; }
  .badge { font-size: 0.7rem; padding: 0.25rem 0.7rem; border-radius: 99px; font-weight: 600; }
  .badge.online { background: #1a3a2a; color: #4ade80; border: 1px solid #166534; }
  .badge.offline { background: #3a1a1a; color: #f87171; border: 1px solid #7f1d1d; }
  .badge.loading { background: #2a2a1a; color: #fbbf24; border: 1px solid #78350f; }
  .badge.planned { background: #1a2a3a; color: #93c5fd; border: 1px solid #1e3a5f; }
  .card-title { font-size: 1.1rem; font-weight: 600; color: #fff; margin-bottom: 0.4rem; }
  .card-desc { font-size: 0.85rem; color: #64748b; line-height: 1.5; margin-bottom: 1.2rem; }
  .card-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .btn { font-size: 0.75rem; padding: 0.4rem 0.9rem; border-radius: 6px; border: none; cursor: pointer; font-weight: 500; transition: all 0.15s; }
  .btn-open { background: #1e3a5f; color: #60a5fa; }
  .btn-open:hover { background: #1e4a7f; }
  .btn-restart { background: #1e3a2a; color: #4ade80; }
  .btn-restart:hover { background: #1e5a3a; }
  .btn-stop { background: #3a1e1e; color: #f87171; }
  .btn-stop:hover { background: #5a1e1e; }
  .btn-start { background: #1a3a2a; color: #4ade80; }
  .btn-start:hover { background: #1a5a3a; }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }

  .modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:1000; align-items:center; justify-content:center; }
  .modal-overlay.visible { display:flex; }
  .modal { background:#1e2330; border:1px solid #2d3748; border-radius:14px; padding:2rem; min-width:420px; max-width:95vw; }
  .modal h2 { font-size:1.1rem; font-weight:700; color:#fff; margin-bottom:1.2rem; }
  .modal select { width:100%; padding:0.5rem 0.75rem; background:#0f1117; border:1px solid #2d3748; border-radius:7px; color:#e2e8f0; font-size:0.9rem; margin-bottom:1rem; }
  .modal-actions { display:flex; gap:0.75rem; justify-content:flex-end; margin-top:0.5rem; }
  .btn-cancel { background:#1e2330; border:1px solid #2d3748; color:#94a3b8; padding:0.45rem 1.1rem; border-radius:7px; cursor:pointer; font-size:0.85rem; }
  .btn-save { background:#1e3a5f; color:#60a5fa; border:none; padding:0.45rem 1.1rem; border-radius:7px; cursor:pointer; font-size:0.85rem; font-weight:600; }
  .current-model { font-size:0.8rem; color:#64748b; margin-bottom:1rem; }

  footer { text-align: center; padding: 2rem; color: #334155; font-size: 0.8rem; }
</style>
</head>
<body>
<header style="display:flex; align-items:center; justify-content:space-between;">
  <div>
    <h1>Cockpit Carlo</h1>
    <span>Werkzeuge & Dienste — VPS 31.70.68.228</span>
  </div>
  <a href="https://agentsolutions-mallorca.com" style="font-size:0.85rem; color:#3b82f6; text-decoration:none; border:1px solid #1e3a5f; padding:0.4rem 1rem; border-radius:8px;">&#8592; Zurück</a>
</header>

<div class="grid" id="grid">
  {% for s in services %}
  <div class="card" id="card-{{s.id}}">
    <div class="card-header">
      <div class="card-icon">{{s.icon}}</div>
      <span class="badge loading" id="badge-{{s.id}}">{% if s.get('external') %}extern{% elif s.get('planned') %}geplant{% else %}...{% endif %}</span>
    </div>
    <div class="card-title">{{s.name}}</div>
    <div class="card-desc">{{s.desc}}</div>
    <div class="card-actions">
      <a href="{{s.url}}" target="_blank"><button class="btn btn-open">Öffnen</button></a>
      {% if s.get('persona_url') %}<a href="{{s.persona_url}}" target="_blank"><button class="btn" style="background:#7c3aed;color:#fff">Personas</button></a>{% endif %}
      {% if not s.get('external') %}
      <button class="btn btn-restart" onclick="action('{{s.id}}','restart')">Restart</button>
      <button class="btn btn-stop" onclick="action('{{s.id}}','stop')">Stop</button>
      <button class="btn btn-start" onclick="action('{{s.id}}','start')">Start</button>
      {% if s.id == 'hermes-carlo' %}
      <button class="btn" style="background:#3a1a1a;color:#f87171;font-weight:700;" onclick="hermesJobsReset()">&#128465; Jobs Reset</button>
      {% endif %}
      {% if s.id == 'mirofish' %}
      <button class="btn" style="background:#1e2a3a;color:#a78bfa;" onclick="openMirofishModal()">🧠 Modell</button>
      {% endif %}
      {% endif %}
    </div>
  </div>
  {% endfor %}
</div>
<footer>Agent Solutions · VPS IONOS · 31.70.68.228</footer>
<script>
function updateStatus() {
  fetch('/api/status').then(r=>r.json()).then(data => {
    data.forEach(s => {
      const b = document.getElementById('badge-'+s.id);
      b.textContent = s.status;
      b.className = 'badge ' + (s.status === 'online' ? 'online' : s.status === 'offline' ? 'offline' : s.status === 'geplant' ? 'planned' : 'loading');
    });
  });
}
function action(id, act) {
  const b = document.getElementById('badge-'+id);
  b.textContent = '...';
  b.className = 'badge loading';
  fetch('/api/action', {
    method: 'POST',
    headers: {'Content-Type':'application/json'},
    body: JSON.stringify({id, action: act})
  }).then(()=> setTimeout(updateStatus, 3000));
}
updateStatus();
setInterval(updateStatus, 15000);
</script>

<div class="modal-overlay" id="mirofish-modal">
  <div class="modal">
    <h2>🐟 MiroFish — Modell wechseln</h2>
    <div class="current-model">Aktuell: <span id="modal-current-model">...</span></div>
    <select id="modal-model-select"></select>
    <div class="modal-actions">
      <button class="btn-cancel" onclick="closeMirofishModal()">Abbrechen</button>
      <button class="btn-save" id="btn-modal-save" onclick="saveMirofishModel()">Speichern &amp; Neustart</button>
    </div>
  </div>
</div>

<script>
async function openMirofishModal() {
  document.getElementById('mirofish-modal').classList.add('visible');
  const res = await fetch('/api/mirofish/model');
  const data = await res.json();
  document.getElementById('modal-current-model').textContent = data.current;
  const sel = document.getElementById('modal-model-select');
  sel.innerHTML = data.models.map(m =>
    `<option value="${m.model}" ${m.model===data.current?'selected':''}>[${m.provider}] ${m.label}</option>`
  ).join('');
}
function closeMirofishModal() {
  document.getElementById('mirofish-modal').classList.remove('visible');
}
async function saveMirofishModel() {
  const btn = document.getElementById('btn-modal-save');
  btn.disabled = true; btn.textContent = '⏳ Neustart...';
  const model = document.getElementById('modal-model-select').value;
  const res = await fetch('/api/mirofish/model', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({model})});
  const data = await res.json();
  if (data.ok) { btn.textContent = '✅ Gespeichert'; setTimeout(closeMirofishModal, 1500); }
  else { btn.textContent = '❌ Fehler'; btn.disabled = false; }
}
</script>

</body>
</html>/c                  &    [        [        [        S9$ )N)services)r   HTMLSERVICES r3   r$   indexr:      s    !$::r3   z/api/statusc            
          [        [         V s/ s HF  o S   U R                  S5      (       a  SO%U R                  S5      (       a  [        U S   5      OSS.PMH     sn 5      $ s  sn f )Nr   plannedgeplantr   extern)r   r#   )r   r8   getr%   )ss    r$   r#   r#      s      ]e  f  ]e  XYT7155;K;Kipqpupu  wB  qC  qCR\]^_j]kRl  IQ  S  ]e  f  g  g  fs   AA"z/api/actionPOST)methodsc                     ^ [         R                  m[        U4S j[         5       S 5      n U (       d  [	        SS05      S4$ [        U TS   5      n[	        SU05      $ )Nc              3   B   >#    U  H  oS    TS    :X  d  M  Uv   M     g7f)r   Nr9   ).0r@   datas     r$   	<genexpr>action.<locals>.<genexpr>   s      Ax!T7d4j+@AAxs   	okFi  r/   )r   jsonnextr8   r   r2   )r.   rI   rF   s     @r$   r/   r/      sT    <<DAxA4HGe}%s**	wX	/BD":r3   z/opt/struktur/mirofish/.envOpenAIu%   GPT-4.1-mini (empfohlen, ~3€/15Rdn)zgpt-4.1-mini-2025-04-14zhttps://api.openai.com/v1zsk-proj-VBaZD9yxd4_eAzrXGNGyvr2Tn0dM1WxUl8yhjvvETdl5B_cJUpTfuXlr32gihSJ355aHfrcUqUT3BlbkFJKt2AXBxHRy3l7HrUwfrFOeT7ku7Z5cw8HRuXd8O2umxFWKvK1g6Zn6m2i9ZPq_44ah2egN0n8A)providerlabelmodelbase_urlapi_keyu   GPT-4.1 (~15€/15Rdn)zgpt-4.1-2025-04-14u   GPT-4o (~30€/15Rdn)zgpt-4o
OpenRouteru   DeepSeek-V3 (~2€/15Rdn)zdeepseek/deepseek-chatzhttps://openrouter.ai/api/v1zIsk-or-v1-3425729d152eb4a647e1db7b0411de55d01f178f846ba37caf1465df0f3e5c8fu   DeepSeek-R1 (~5€/15Rdn)zdeepseek/deepseek-reasoneru   Qwen2.5-72B (~2€/15Rdn)zqwen/qwen2.5-72b-instructu   Kimi K2 (~2€/15Rdn)zmoonshotai/kimi-k2c                       [        [        5       n U  HG  nUR                  S5      (       d  M  UR                  5       R	                  SS5      S   s  sS S S 5        $    S S S 5        g! , (       d  f       g= f!    g= f)NzLLM_MODEL_NAME==   r   )openMIROFISH_ENV
startswithr!   split)flines     r$   _read_mirofish_modelr\      ss    ,1??#455::<--c15a88        s9   A9 A($A(	A9 A(A9 (
A62A9 6A9 9A=c                    [        [        5       nUR                  5       nS S S 5        U S   U S   U S   U S   U S   S.n/ nW Hx  nSnUR                  5        HE  u  pxUR	                  US-   5      (       d  M   UR                  US-   U-   [        S5      -   5        Sn  O   U(       a  Mg  UR                  U5        Mz     [        [        S	5       nUR                  U5        S S S 5        g ! , (       d  f       N= f! , (       d  f       g = f)
NrQ   rP   rO   )LLM_API_KEYLLM_BASE_URLLLM_MODEL_NAMEOPENAI_API_KEYOPENAI_API_BASE_URLFrT   
   Tw)rV   rW   	readlinesitemsrX   appendchr
writelines)		model_cfgrZ   lineskeys	new_linesr[   replacedkvs	            r$   _write_mirofish_envrq      s    	l	q 
  )3(4(1(3(4D IJJLDAq3w''  S1s2w!67	 !
 xT"  
lC	 A	Y 
!	 ' 
	& 
!	 s   C+C<+
C9<
D
z/api/mirofish/modelGETc                  :    [        5       n [        U [        S.5      $ )N)currentmodels)r\   r   MIROFISH_MODELS)rt   s    r$   mirofish_model_getrw     s    "$Gw/BCCr3   c                    ^ [         R                  n U R                  S5      m[        U4S j[         5       S 5      nU(       d  [        SSS.5      S4$ [        U5        [        R                  " SSS	S
9  [        STS.5      $ )NrO   c              3   <   >#    U  H  oS    T:X  d  M  Uv   M     g7f)rO   Nr9   )rE   mmodel_ids     r$   rG   %mirofish_model_set.<locals>.<genexpr>
  s     E?ajH.D?s   	FzUnbekanntes ModellrI   error  z<cd /opt/struktur/mirofish && docker compose restart mirofishTr*   )r+   r   )rI   rO   )	r   rJ   r?   rK   rv   r   rq   r   r   )rF   cfgr{   s     @r$   mirofish_model_setr     sp    <<Dxx H
E?Et
LCe.BCDcIINNQY]gij$233r3   z)/opt/struktur/hermes-carlo/data/kanban.db>   donetodotriagebacklogblockedin_progressc                  Z    SS K n U R                  [        5      nU R                  Ul        U$ )Nr   )sqlite3connect	KANBAN_DBRowrow_factory)r   conns     r$   _kanban_connr     s$    ??9%D{{DKr3   z/api/kanban/tasksc                  X   [         R                  R                  S5      n [        5       nU (       a"  UR	                  SU 45      R                  5       nOUR	                  S5      R                  5       nUR                  5         [        U Vs/ s H  n[        U5      PM     sn5      $ s  snf )Nr#   zSELECT id, title, body, status, priority, created_by, created_at FROM tasks WHERE status=? ORDER BY priority DESC, created_at DESCzsSELECT id, title, body, status, priority, created_by, created_at FROM tasks ORDER BY priority DESC, created_at DESC)	r   argsr?   r   executefetchallcloser   dict)r#   r   rowsrs       r$   kanban_tasks_listr     s    \\h'F>D|| QI
 (* 	
 || B

(* 	 	JJLT*TDGT*++*s   B'z/api/kanban/taskc                  \   SS K n SS KnSS Kn[        R                  =(       d    0 nUR                  S5      =(       d    SR                  5       nU(       d  [        SSS.5      S4$ UR                  S5      =(       d    UR                  S	5      =(       d    SR                  5       n[        UR                  S
S5      5      nUR                  SS5      nU[        ;  a  SnSUR                  SSUR                  5       5      S S -   n[        5       n	 U	R                  SXXWUS[        UR                  5       5      S45        U	R                  5         U	R!                  5         [        SXUS.5      $ ! [         a3  n
U	R!                  5         [        S[#        U
5      S.5      S4s S n
A
$ S n
A
ff = f)Nr   title Fztitle fehltr}   r   bodydescriptionpriority2   r#   r   proj_z	[^a-z0-9]_(   zINSERT OR IGNORE INTO tasks (id, title, body, status, priority, created_by, created_at, workspace_kind) VALUES (?,?,?,?,?,?,?,?)carloscratch  T)rI   r   r   r#   )r   retimer   rJ   r?   r!   r   intVALID_STATUSESsublowerr   r   commit	Exceptionr   str)r   r   r   rF   r   r   r   r#   task_idr   es              r$   kanban_task_creater   -  s`   <<2DXXg$"++-Eem<=sBB ADHH]$;ArHHJD488J+,Hxx),F^#|S%++-@"EEG>D< OT8Wc$))+>NPYZ	
 	 	JJL$gPQQ	  <

ec!f56;;<s   ?E. .
F+8(F& F+&F+z/api/kanban/task/<task_id>PATCHc                 `   SS K nSS Kn[        R                  =(       d    0 nUR	                  5        VVs0 s H  u  pEUS;   d  M  XE_M     nnnSU;   a$  US   [
        ;  a  [        SS[
         3S.5      S4$ U(       d  [        SSS.5      S4$ S	R                  S
 U 5       5      n[        UR                  5       5      U /-   n[        5       n	U	R                  SU S3U5        U	R                  5         U	R                  5         [        SU [        UR                  5       5      S.5      $ s  snnf )Nr   )r   r   r#   r   r#   Fu   Ungültiger Status. Erlaubt: r}   r   u   Keine gültigen Felderz, c              3   *   #    U  H	  o S 3v   M     g7f)z=?Nr9   )rE   ro   s     r$   rG   %kanban_task_update.<locals>.<genexpr>P  s     5WS8Ws   zUPDATE tasks SET z WHERE id=?T)rI   r   updated)r   r   r   rJ   rf   r   r   joinlistvaluesr   r   r   r   rl   )
r   r   r   rF   ro   rp   allowed
set_clauser   r   s
             r$   kanban_task_updater   G  s   <<2D $

]5\0\tqtG]7wx0Fe0MnM].^_`beeee.FGH#MM5W55J'.."#wi/F>DLL$ZL<fEKKMJJL$g$w||~:NOPP ^s   D*D*DELETEc                     [        5       nUR                  SU 45        UR                  5         UR                  5         [	        SU S.5      $ )NzDELETE FROM tasks WHERE id=?T)rI   r   )r   r   r   r   r   )r   r   s     r$   kanban_task_deleter   X  s<    >DLL/'<KKMJJL$g.//r3   z/api/hermes-resetc                  .   SS K n SS Kn  U R                  R                  SSSS9nU R                  R	                  USS9 n[        SS	05      sS S S 5        $ ! , (       d  f       g = f! [         a#  n[        S
[        U5      S.5      S4s S nA$ S nAff = f)Nr   zhttp://127.0.0.1:9099/resetrA   r3   )methodrF   rc   )r   rI   TFr}   r   )urllib.requesturllib.errorr   Requesturlopenr   r   r   )urllibreqr   r   s       r$   hermes_resetr   a  s    '<nn$$%B6X[$\^^##C#4D$<( 544 <ec!f56;;<s:   5A' A	A' 
A$ A' $A' '
B1B	BB__main__z	127.0.0.1i  F)hostportdebug) flaskr   r   r   r   r   rJ   __name__appr8   r%   r2   r7   router:   r#   r/   rW   rv   r\   rq   rw   r   r   r   r   r   r   r   r   r   r   r9   r3   r$   <module>r      sa   A A Ho  Au  @E  N  N\  iE  F#5  @F  PU  ^P  _m  ~J  Ws  t *?  JR  \b  k]  lA  Nq  rz3x  CI  R@  OY  f~  	 ""Sj 3; ; =g g =6(+ , -(OZs  D_  pV  W(@[o  E`  qW  X(?[c  E`  qW  X(C[s  Ec  p{  |(C[w  Ec  p{  |(C[v  Ec  p{  |(?[o  Ec  p{  |	 .  5'2D 3D  6(34 44 8	P 0, 1, x0R 1R2 '';Q <Q  '(<0 =0 1< 2< zGG4uG5 r3   