@omerrgocmen/crewctl 1.3.1 → 1.3.3

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.
Files changed (92) hide show
  1. package/README.md +193 -193
  2. package/orchestrator/README.md +473 -473
  3. package/orchestrator/config.default.json +96 -90
  4. package/orchestrator/roles/executor.md +57 -57
  5. package/orchestrator/roles/operator.md +77 -77
  6. package/orchestrator/roles/planner.md +58 -58
  7. package/orchestrator/roles/reviewer.md +63 -63
  8. package/orchestrator/skills/acceptance-criteria.md +18 -18
  9. package/orchestrator/skills/accessibility-audit.md +16 -16
  10. package/orchestrator/skills/accessible-forms.md +16 -16
  11. package/orchestrator/skills/api-design.md +27 -27
  12. package/orchestrator/skills/api-documentation.md +16 -16
  13. package/orchestrator/skills/architecture-decision-record.md +18 -18
  14. package/orchestrator/skills/authentication-design.md +16 -16
  15. package/orchestrator/skills/authorization-review.md +16 -16
  16. package/orchestrator/skills/backward-compatibility.md +17 -17
  17. package/orchestrator/skills/changelog-writing.md +16 -16
  18. package/orchestrator/skills/ci-pipeline-design.md +16 -16
  19. package/orchestrator/skills/cli-design.md +16 -16
  20. package/orchestrator/skills/code-review.md +27 -27
  21. package/orchestrator/skills/configuration-management.md +16 -16
  22. package/orchestrator/skills/container-review.md +16 -16
  23. package/orchestrator/skills/contract-testing.md +16 -16
  24. package/orchestrator/skills/dashboard-design.md +16 -16
  25. package/orchestrator/skills/database-migration.md +16 -16
  26. package/orchestrator/skills/database-schema-design.md +16 -16
  27. package/orchestrator/skills/debugging.md +26 -26
  28. package/orchestrator/skills/dependency-review.md +16 -16
  29. package/orchestrator/skills/design-review.md +29 -29
  30. package/orchestrator/skills/design-system.md +16 -16
  31. package/orchestrator/skills/docs-api-reference.md +16 -16
  32. package/orchestrator/skills/docs-troubleshooting.md +16 -16
  33. package/orchestrator/skills/docs-tutorial.md +16 -16
  34. package/orchestrator/skills/docs-writing.md +25 -25
  35. package/orchestrator/skills/empty-error-loading-states.md +16 -16
  36. package/orchestrator/skills/end-to-end-testing.md +16 -16
  37. package/orchestrator/skills/error-handling.md +16 -16
  38. package/orchestrator/skills/frontend-design.md +28 -28
  39. package/orchestrator/skills/git-commit-writing.md +16 -16
  40. package/orchestrator/skills/graphql-design.md +16 -16
  41. package/orchestrator/skills/incident-runbook.md +18 -18
  42. package/orchestrator/skills/input-validation.md +16 -16
  43. package/orchestrator/skills/integration-testing.md +16 -16
  44. package/orchestrator/skills/interaction-design.md +16 -16
  45. package/orchestrator/skills/landing-page-design.md +16 -16
  46. package/orchestrator/skills/observability-design.md +16 -16
  47. package/orchestrator/skills/openapi-contract.md +16 -16
  48. package/orchestrator/skills/performance-profiling.md +16 -16
  49. package/orchestrator/skills/privacy-review.md +16 -16
  50. package/orchestrator/skills/property-based-testing.md +16 -16
  51. package/orchestrator/skills/pull-request-writing.md +16 -16
  52. package/orchestrator/skills/refactoring.md +16 -16
  53. package/orchestrator/skills/release-readiness.md +16 -16
  54. package/orchestrator/skills/responsive-design.md +16 -16
  55. package/orchestrator/skills/secrets-management.md +16 -16
  56. package/orchestrator/skills/secure-file-upload.md +16 -16
  57. package/orchestrator/skills/security-review.md +26 -26
  58. package/orchestrator/skills/semantic-versioning.md +17 -17
  59. package/orchestrator/skills/seo-on-page.md +16 -16
  60. package/orchestrator/skills/seo-structured-data.md +16 -16
  61. package/orchestrator/skills/seo-technical-audit.md +16 -16
  62. package/orchestrator/skills/sql-query-review.md +16 -16
  63. package/orchestrator/skills/supply-chain-security.md +16 -16
  64. package/orchestrator/skills/test-strategy.md +16 -16
  65. package/orchestrator/skills/threat-modeling.md +16 -16
  66. package/orchestrator/skills/unit-testing.md +16 -16
  67. package/orchestrator/skills/write-tests.md +28 -28
  68. package/orchestrator/src/checkpoints.js +183 -183
  69. package/orchestrator/src/cli-registry.js +125 -3
  70. package/orchestrator/src/cli.js +140 -140
  71. package/orchestrator/src/doctor.js +64 -64
  72. package/orchestrator/src/engine.js +2112 -1914
  73. package/orchestrator/src/schedule.js +124 -124
  74. package/orchestrator/src/server.js +942 -817
  75. package/orchestrator/src/skill-registry.js +272 -272
  76. package/orchestrator/src/store.js +583 -478
  77. package/orchestrator/web/OrbitControls.js +1417 -1417
  78. package/orchestrator/web/board.html +146 -146
  79. package/orchestrator/web/code.html +213 -213
  80. package/orchestrator/web/flow.html +741 -741
  81. package/orchestrator/web/index.html +784 -622
  82. package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
  83. package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
  84. package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
  85. package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
  86. package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
  87. package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
  88. package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
  89. package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
  90. package/orchestrator/web/site.webmanifest +12 -12
  91. package/orchestrator/web/three.module.min.js +6 -6
  92. package/package.json +51 -51
@@ -1,146 +1,146 @@
1
- <!doctype html>
2
- <html lang="tr">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>Pano · CrewCtl</title>
7
- <link rel="icon" href="/favicon.ico" sizes="any">
8
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
9
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10
- <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
- <link rel="manifest" href="/site.webmanifest">
12
- <style>
13
- *{box-sizing:border-box}
14
- :root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
15
- --headerbg:#0a0f1acc;--input:#0b111c;--nodebg:#0b1220;--shadow:0 14px 40px #0008;
16
- --appbg:radial-gradient(1200px 620px at 50% -14%,#5e8bff16,transparent),radial-gradient(1000px 520px at 90% -6%,#a98bff12,transparent),var(--bg)}
17
- [data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
18
- --headerbg:#ffffffe6;--input:#f4f7fd;--nodebg:#f7f9fd;--shadow:0 14px 36px #7a90b820;
19
- --appbg:radial-gradient(1200px 620px at 50% -14%,#3a63f012,transparent),radial-gradient(1000px 520px at 90% -6%,#7350f00e,transparent),var(--bg)}
20
- body{margin:0;background:var(--appbg);background-attachment:fixed;color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
21
- button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;font:inherit;transition:border-color .18s,transform .1s,box-shadow .18s}
22
- button:hover{border-color:var(--acc);box-shadow:0 4px 14px color-mix(in srgb,var(--acc) 16%,transparent)}button:active{transform:translateY(1px)}
23
- button.sm{padding:4px 8px;font-size:12px}button.ok{color:var(--ok)}button.danger{color:var(--err)}
24
- a.btn{text-decoration:none;display:inline-block}
25
- /* Header */
26
- header{height:58px;display:flex;align-items:center;gap:12px;padding:0 16px;background:var(--headerbg);backdrop-filter:blur(10px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
27
- h1{font-size:15px;margin:0;letter-spacing:.2px}.brand{color:var(--acc);font-weight:800}.sep{color:var(--dim)}.grow{flex:1}
28
- .pill{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:5px 11px;color:var(--dim);font-size:12px;display:inline-flex;align-items:center;gap:6px}
29
- .dot{width:8px;height:8px;border-radius:50%;background:var(--dim)}
30
- .dot.on{background:var(--ok);box-shadow:0 0 10px var(--ok);animation:beat 1.6s ease-in-out infinite}
31
- @keyframes beat{50%{box-shadow:0 0 3px var(--ok)}}
32
- .muted{color:var(--dim);font-size:12px}
33
- /* Zamanlama şeridi */
34
- .sched-strip{margin:14px 14px 0;background:linear-gradient(120deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:14px;padding:11px 14px;box-shadow:var(--shadow)}
35
- .sched-strip .kick{font-size:10px;letter-spacing:1.2px;text-transform:uppercase;color:var(--acc);font-weight:800;display:flex;align-items:center;gap:7px}
36
- .sched-list{display:flex;gap:9px;flex-wrap:wrap;margin-top:8px}
37
- .sched-chip{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);background:var(--panel);border-radius:999px;padding:5px 11px;font-size:11.5px;color:var(--dim)}
38
- .sched-chip b{color:var(--tx);font-weight:600}.sched-chip .when{color:var(--acc);font-variant-numeric:tabular-nums}
39
- .sched-chip.off{opacity:.55}
40
- /* Board */
41
- main{padding:14px}
42
- .board{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;align-items:start}
43
- .col{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:11px;min-width:0;box-shadow:var(--shadow)}
44
- .col-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}
45
- .col-head h2{margin:0;font-size:11px;letter-spacing:.7px;text-transform:uppercase;color:var(--dim)}
46
- .col-head .count{margin-left:auto;font-size:10px;border:1px solid var(--line);border-radius:999px;padding:1px 7px;color:var(--dim);font-variant-numeric:tabular-nums}
47
- .col .accent{width:8px;height:8px;border-radius:50%;background:var(--dim);flex:none}
48
- .col.pending .accent{background:var(--acc)}.col.running .accent{background:var(--ok);box-shadow:0 0 9px var(--ok);animation:beat 1.4s ease-in-out infinite}.col.done .accent{background:var(--ok)}.col.failed .accent{background:var(--err)}
49
- .cards{display:flex;flex-direction:column;gap:9px;min-height:40px}
50
- .tcard{border:1px solid var(--line);background:var(--panel2);border-radius:10px;padding:10px;animation:pop .3s cubic-bezier(.2,.8,.2,1)}
51
- @keyframes pop{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
52
- .tcard .top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
53
- .tcard .id{font:10px ui-monospace,Consolas,monospace;color:var(--dim)}
54
- .badge{font-size:10px;border:1px solid var(--line);border-radius:999px;padding:1px 6px;color:var(--dim)}
55
- .badge.sched{color:var(--acc);border-color:color-mix(in srgb,var(--acc) 45%,var(--line))}
56
- .tcard .prompt{margin:6px 0 0;font-size:13px;line-height:1.45;word-break:break-word;display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden}
57
- .tcard .acts{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}
58
- .col.running .tcard{border-color:color-mix(in srgb,var(--ok) 42%,var(--line));box-shadow:0 0 0 1px color-mix(in srgb,var(--ok) 20%,transparent)}
59
- .col.failed .tcard{border-color:color-mix(in srgb,var(--err) 34%,var(--line))}
60
- .col-empty{color:var(--dim);font-size:12px;padding:18px 8px;text-align:center;border:1px dashed var(--line);border-radius:10px}
61
- .run-state{font-size:11px;color:var(--ok);margin-top:6px;display:flex;align-items:center;gap:6px}
62
- .run-state .rd{width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);animation:beat 1.3s ease-in-out infinite}
63
- @media(max-width:1100px){.board{grid-template-columns:repeat(2,minmax(0,1fr))}}
64
- @media(max-width:620px){.board{grid-template-columns:1fr}}
65
- @media(prefers-reduced-motion:reduce){.dot.on,.col.running .accent,.run-state .rd{animation:none}.tcard{animation:none}}
66
- </style>
67
- <link rel="stylesheet" href="app.css">
68
- <script src="app.js" defer></script>
69
- </head>
70
- <body>
71
- <header>
72
- <h1><span class="brand">CrewCtl</span> <span class="sep">·</span> Pano</h1>
73
- <span class="pill"><span id="dot" class="dot"></span><span id="engineState">durdu</span></span>
74
- <span id="modePill" class="pill">boşta</span>
75
- <div class="grow"></div>
76
- <a class="btn" href="flow.html"><button>Ekip Akışı</button></a>
77
- <a class="btn" href="code.html"><button>Canlı Kod</button></a>
78
- <button id="themeBtn" onclick="toggleTheme()" title="Açık / koyu tema"><svg class="i i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><svg class="i i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>
79
- <a class="btn" href="index.html"><button>← Komuta Merkezi</button></a>
80
- </header>
81
-
82
- <div class="sched-strip" id="schedStrip" hidden>
83
- <div class="kick"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg> Yaklaşan zamanlanmış görevler</div>
84
- <div class="sched-list" id="schedList"></div>
85
- </div>
86
-
87
- <main>
88
- <div class="board">
89
- <div class="col pending"><div class="col-head"><span class="accent"></span><h2>Bekleyen</h2><span class="count" id="cnt-pending">0</span></div><div class="cards" id="col-pending"></div></div>
90
- <div class="col running"><div class="col-head"><span class="accent"></span><h2>Çalışıyor</h2><span class="count" id="cnt-running">0</span></div><div class="cards" id="col-running"></div></div>
91
- <div class="col done"><div class="col-head"><span class="accent"></span><h2>Tamamlanan</h2><span class="count" id="cnt-done">0</span></div><div class="cards" id="col-done"></div></div>
92
- <div class="col failed"><div class="col-head"><span class="accent"></span><h2>Başarısız</h2><span class="count" id="cnt-failed">0</span></div><div class="cards" id="col-failed"></div></div>
93
- </div>
94
- </main>
95
-
96
- <script>
97
- const $=s=>document.querySelector(s);
98
- function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
99
- const CLI_LABEL={codex:'Codex',claude:'Claude Code',gemini:'Gemini',opencode:'OpenCode'};
100
- function cliLabel(id){return CLI_LABEL[id]||(String(id||'').charAt(0).toUpperCase()+String(id||'').slice(1))}
101
- const SCHED_DAYS=['Paz','Pzt','Sal','Çar','Per','Cum','Cmt'];
102
- /* ---- Tema ---- */
103
- function applyTheme(t){document.documentElement.setAttribute('data-theme',t);try{localStorage.setItem('cliTheme',t)}catch{}}
104
- function toggleTheme(){applyTheme(document.documentElement.getAttribute('data-theme')==='light'?'dark':'light')}
105
- (function(){let t='dark';try{t=localStorage.getItem('cliTheme')||'dark'}catch{}applyTheme(t)})();
106
-
107
- const BOARD={queue:{pending:[],approval:[],done:[],failed:[]},status:{},schedules:[]};
108
- function api(path,method='GET'){return fetch(path,{method}).then(async r=>{const j=await r.json().catch(()=>({}));if(!r.ok)throw new Error(j.error||('HTTP '+r.status));return j})}
109
-
110
- /* ---- Kart ---- */
111
- function taskCard(t,state){const opLabel=t.operatorCli?cliLabel(t.operatorCli):'';const sched=t.scheduleId?'<span class="badge sched">⏱ zamanlanmış</span>':'';
112
- let acts='';
113
- if(state==='pending')acts=`<button class="danger sm" onclick="delTask('${esc(t.id)}')">Sil</button>`;
114
- else if((state==='done'||state==='failed')&&(t.delivery&&(t.delivery.files||[]).length))acts=`<a class="btn" href="code.html?task=${esc(t.id)}"><button class="sm">Kodu gör</button></a>`;
115
- return `<div class="tcard"><div class="top"><span class="id">${esc(t.id)}</span><div class="grow"></div><span class="badge">${esc(t.executionMode||'auto')}</span>${opLabel?`<span class="badge">${esc(opLabel)}</span>`:''}${sched}</div><div class="prompt">${esc(t.prompt||'')}</div>${acts?`<div class="acts">${acts}</div>`:''}</div>`}
116
- function runningCard(cur){const stage=cur.stage?`<span class="badge">${esc(cur.stage)}</span>`:'';return `<div class="tcard"><div class="top"><span class="id">${esc(cur.id||'')}</span><div class="grow"></div>${stage}</div><div class="prompt">${esc(cur.prompt||'')}</div><div class="run-state"><span class="rd"></span>${esc(cur.agent?cur.agent+' çalışıyor':'operatör çalışıyor')}</div></div>`}
117
- function fill(id,html,count){$('#col-'+id).innerHTML=html||`<div class="col-empty">—</div>`;$('#cnt-'+id).textContent=count}
118
- function renderBoard(){const q=BOARD.queue||{},cur=BOARD.status?.current||null,runId=cur&&cur.id;
119
- const pending=(q.pending||[]).filter(t=>!runId||t.id!==runId);
120
- fill('pending',pending.map(t=>taskCard(t,'pending')).join(''),pending.length);
121
- fill('running',cur?runningCard(cur):'',cur?1:0);
122
- fill('done',(q.done||[]).map(t=>taskCard(t,'done')).join(''),(q.done||[]).length);
123
- fill('failed',(q.failed||[]).map(t=>taskCard(t,'failed')).join(''),(q.failed||[]).length)}
124
- function applyStatus(s){BOARD.status=s||{};$('#dot').className='dot'+(s&&s.running?' on':'');$('#engineState').textContent=s&&s.running?'çalışıyor':'durdu';$('#modePill').textContent=s&&s.current?`${esc(s.current.agent||'operatör')} · ${esc(s.current.stage||'')}`:(s&&s.busy?'meşgul':'boşta');renderBoard()}
125
-
126
- /* ---- Zamanlama şeridi ---- */
127
- function schedTriggerText(s){const t=s.trigger||{};if(t.type==='interval'){const n=t.everyMinutes||0;return n%60===0&&n>=60?`Her ${n/60} saatte bir`:`Her ${n} dk`}if(t.type==='daily')return `Her gün ${esc(t.at||'')}`;if(t.type==='weekly')return `${(t.days||[]).map(d=>SCHED_DAYS[d]).join(', ')} ${esc(t.at||'')}`;return '—'}
128
- function schedWhen(s){if(s.enabled===false)return 'devre dışı';if(!s.nextRunAt)return '—';try{return new Date(s.nextRunAt).toLocaleString('tr-TR',{dateStyle:'short',timeStyle:'short'})}catch{return '—'}}
129
- function renderSchedules(){const list=(BOARD.schedules||[]).slice().sort((a,b)=>{const na=a.nextRunAt?Date.parse(a.nextRunAt):Infinity,nb=b.nextRunAt?Date.parse(b.nextRunAt):Infinity;return na-nb});const strip=$('#schedStrip');if(!list.length){strip.hidden=true;return}strip.hidden=false;$('#schedList').innerHTML=list.map(s=>`<span class="sched-chip${s.enabled===false?' off':''}" title="${esc(s.prompt||'')}"><b>${schedTriggerText(s)}</b><span class="when">${schedWhen(s)}</span></span>`).join('')}
130
-
131
- /* ---- Eylemler ---- */
132
- async function delTask(id){if(window.appConfirm&&!await appConfirm({title:'Görev silinsin mi?',body:'Bu bekleyen görev kuyruktan kaldırılacak.',confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;try{await api('/api/tasks/'+encodeURIComponent(id),'DELETE')}catch(e){alert(e.message)}}
133
-
134
- /* ---- SSE ---- */
135
- const es=new EventSource('/api/events');
136
- function dispatch(type,data){if(type==='status')applyStatus(data);else if(type==='queue'){BOARD.queue=data;renderBoard()}else if(type==='schedules'){BOARD.schedules=data;renderSchedules()}}
137
- for(const type of ['status','queue','schedules'])es.addEventListener(type,event=>{try{dispatch(type,JSON.parse(event.data))}catch{}});
138
- window.addEventListener('pagehide',()=>{try{es.close()}catch{}});
139
- window.addEventListener('pageshow',x=>{if(x.persisted)location.reload()});
140
-
141
- /* ---- Açılış ---- */
142
- async function boot(){try{const st=await api('/api/state');if(st.status)applyStatus(st.status);if(st.queue)BOARD.queue=st.queue;BOARD.schedules=st.schedules||[];renderBoard();renderSchedules()}catch(e){/* sessizce yeniden dene SSE ile */}}
143
- boot();
144
- </script>
145
- </body>
146
- </html>
1
+ <!doctype html>
2
+ <html lang="tr">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Pano · CrewCtl</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
+ <link rel="manifest" href="/site.webmanifest">
12
+ <style>
13
+ *{box-sizing:border-box}
14
+ :root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
15
+ --headerbg:#0a0f1acc;--input:#0b111c;--nodebg:#0b1220;--shadow:0 14px 40px #0008;
16
+ --appbg:radial-gradient(1200px 620px at 50% -14%,#5e8bff16,transparent),radial-gradient(1000px 520px at 90% -6%,#a98bff12,transparent),var(--bg)}
17
+ [data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
18
+ --headerbg:#ffffffe6;--input:#f4f7fd;--nodebg:#f7f9fd;--shadow:0 14px 36px #7a90b820;
19
+ --appbg:radial-gradient(1200px 620px at 50% -14%,#3a63f012,transparent),radial-gradient(1000px 520px at 90% -6%,#7350f00e,transparent),var(--bg)}
20
+ body{margin:0;background:var(--appbg);background-attachment:fixed;color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
21
+ button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;font:inherit;transition:border-color .18s,transform .1s,box-shadow .18s}
22
+ button:hover{border-color:var(--acc);box-shadow:0 4px 14px color-mix(in srgb,var(--acc) 16%,transparent)}button:active{transform:translateY(1px)}
23
+ button.sm{padding:4px 8px;font-size:12px}button.ok{color:var(--ok)}button.danger{color:var(--err)}
24
+ a.btn{text-decoration:none;display:inline-block}
25
+ /* Header */
26
+ header{height:58px;display:flex;align-items:center;gap:12px;padding:0 16px;background:var(--headerbg);backdrop-filter:blur(10px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
27
+ h1{font-size:15px;margin:0;letter-spacing:.2px}.brand{color:var(--acc);font-weight:800}.sep{color:var(--dim)}.grow{flex:1}
28
+ .pill{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:5px 11px;color:var(--dim);font-size:12px;display:inline-flex;align-items:center;gap:6px}
29
+ .dot{width:8px;height:8px;border-radius:50%;background:var(--dim)}
30
+ .dot.on{background:var(--ok);box-shadow:0 0 10px var(--ok);animation:beat 1.6s ease-in-out infinite}
31
+ @keyframes beat{50%{box-shadow:0 0 3px var(--ok)}}
32
+ .muted{color:var(--dim);font-size:12px}
33
+ /* Zamanlama şeridi */
34
+ .sched-strip{margin:14px 14px 0;background:linear-gradient(120deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:14px;padding:11px 14px;box-shadow:var(--shadow)}
35
+ .sched-strip .kick{font-size:10px;letter-spacing:1.2px;text-transform:uppercase;color:var(--acc);font-weight:800;display:flex;align-items:center;gap:7px}
36
+ .sched-list{display:flex;gap:9px;flex-wrap:wrap;margin-top:8px}
37
+ .sched-chip{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);background:var(--panel);border-radius:999px;padding:5px 11px;font-size:11.5px;color:var(--dim)}
38
+ .sched-chip b{color:var(--tx);font-weight:600}.sched-chip .when{color:var(--acc);font-variant-numeric:tabular-nums}
39
+ .sched-chip.off{opacity:.55}
40
+ /* Board */
41
+ main{padding:14px}
42
+ .board{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;align-items:start}
43
+ .col{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:11px;min-width:0;box-shadow:var(--shadow)}
44
+ .col-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}
45
+ .col-head h2{margin:0;font-size:11px;letter-spacing:.7px;text-transform:uppercase;color:var(--dim)}
46
+ .col-head .count{margin-left:auto;font-size:10px;border:1px solid var(--line);border-radius:999px;padding:1px 7px;color:var(--dim);font-variant-numeric:tabular-nums}
47
+ .col .accent{width:8px;height:8px;border-radius:50%;background:var(--dim);flex:none}
48
+ .col.pending .accent{background:var(--acc)}.col.running .accent{background:var(--ok);box-shadow:0 0 9px var(--ok);animation:beat 1.4s ease-in-out infinite}.col.done .accent{background:var(--ok)}.col.failed .accent{background:var(--err)}
49
+ .cards{display:flex;flex-direction:column;gap:9px;min-height:40px}
50
+ .tcard{border:1px solid var(--line);background:var(--panel2);border-radius:10px;padding:10px;animation:pop .3s cubic-bezier(.2,.8,.2,1)}
51
+ @keyframes pop{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
52
+ .tcard .top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
53
+ .tcard .id{font:10px ui-monospace,Consolas,monospace;color:var(--dim)}
54
+ .badge{font-size:10px;border:1px solid var(--line);border-radius:999px;padding:1px 6px;color:var(--dim)}
55
+ .badge.sched{color:var(--acc);border-color:color-mix(in srgb,var(--acc) 45%,var(--line))}
56
+ .tcard .prompt{margin:6px 0 0;font-size:13px;line-height:1.45;word-break:break-word;display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden}
57
+ .tcard .acts{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}
58
+ .col.running .tcard{border-color:color-mix(in srgb,var(--ok) 42%,var(--line));box-shadow:0 0 0 1px color-mix(in srgb,var(--ok) 20%,transparent)}
59
+ .col.failed .tcard{border-color:color-mix(in srgb,var(--err) 34%,var(--line))}
60
+ .col-empty{color:var(--dim);font-size:12px;padding:18px 8px;text-align:center;border:1px dashed var(--line);border-radius:10px}
61
+ .run-state{font-size:11px;color:var(--ok);margin-top:6px;display:flex;align-items:center;gap:6px}
62
+ .run-state .rd{width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);animation:beat 1.3s ease-in-out infinite}
63
+ @media(max-width:1100px){.board{grid-template-columns:repeat(2,minmax(0,1fr))}}
64
+ @media(max-width:620px){.board{grid-template-columns:1fr}}
65
+ @media(prefers-reduced-motion:reduce){.dot.on,.col.running .accent,.run-state .rd{animation:none}.tcard{animation:none}}
66
+ </style>
67
+ <link rel="stylesheet" href="app.css">
68
+ <script src="app.js" defer></script>
69
+ </head>
70
+ <body>
71
+ <header>
72
+ <h1><span class="brand">CrewCtl</span> <span class="sep">·</span> Pano</h1>
73
+ <span class="pill"><span id="dot" class="dot"></span><span id="engineState">durdu</span></span>
74
+ <span id="modePill" class="pill">boşta</span>
75
+ <div class="grow"></div>
76
+ <a class="btn" href="flow.html"><button>Ekip Akışı</button></a>
77
+ <a class="btn" href="code.html"><button>Canlı Kod</button></a>
78
+ <button id="themeBtn" onclick="toggleTheme()" title="Açık / koyu tema"><svg class="i i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><svg class="i i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>
79
+ <a class="btn" href="index.html"><button>← Komuta Merkezi</button></a>
80
+ </header>
81
+
82
+ <div class="sched-strip" id="schedStrip" hidden>
83
+ <div class="kick"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg> Yaklaşan zamanlanmış görevler</div>
84
+ <div class="sched-list" id="schedList"></div>
85
+ </div>
86
+
87
+ <main>
88
+ <div class="board">
89
+ <div class="col pending"><div class="col-head"><span class="accent"></span><h2>Bekleyen</h2><span class="count" id="cnt-pending">0</span></div><div class="cards" id="col-pending"></div></div>
90
+ <div class="col running"><div class="col-head"><span class="accent"></span><h2>Çalışıyor</h2><span class="count" id="cnt-running">0</span></div><div class="cards" id="col-running"></div></div>
91
+ <div class="col done"><div class="col-head"><span class="accent"></span><h2>Tamamlanan</h2><span class="count" id="cnt-done">0</span></div><div class="cards" id="col-done"></div></div>
92
+ <div class="col failed"><div class="col-head"><span class="accent"></span><h2>Başarısız</h2><span class="count" id="cnt-failed">0</span></div><div class="cards" id="col-failed"></div></div>
93
+ </div>
94
+ </main>
95
+
96
+ <script>
97
+ const $=s=>document.querySelector(s);
98
+ function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
99
+ const CLI_LABEL={codex:'Codex',claude:'Claude Code',gemini:'Gemini',opencode:'OpenCode'};
100
+ function cliLabel(id){return CLI_LABEL[id]||(String(id||'').charAt(0).toUpperCase()+String(id||'').slice(1))}
101
+ const SCHED_DAYS=['Paz','Pzt','Sal','Çar','Per','Cum','Cmt'];
102
+ /* ---- Tema ---- */
103
+ function applyTheme(t){document.documentElement.setAttribute('data-theme',t);try{localStorage.setItem('cliTheme',t)}catch{}}
104
+ function toggleTheme(){applyTheme(document.documentElement.getAttribute('data-theme')==='light'?'dark':'light')}
105
+ (function(){let t='dark';try{t=localStorage.getItem('cliTheme')||'dark'}catch{}applyTheme(t)})();
106
+
107
+ const BOARD={queue:{pending:[],approval:[],done:[],failed:[]},status:{},schedules:[]};
108
+ function api(path,method='GET'){return fetch(path,{method}).then(async r=>{const j=await r.json().catch(()=>({}));if(!r.ok)throw new Error(j.error||('HTTP '+r.status));return j})}
109
+
110
+ /* ---- Kart ---- */
111
+ function taskCard(t,state){const opLabel=t.operatorCli?cliLabel(t.operatorCli):'';const sched=t.scheduleId?'<span class="badge sched">⏱ zamanlanmış</span>':'';
112
+ let acts='';
113
+ if(state==='pending')acts=`<button class="danger sm" onclick="delTask('${esc(t.id)}')">Sil</button>`;
114
+ else if((state==='done'||state==='failed')&&(t.delivery&&(t.delivery.files||[]).length))acts=`<a class="btn" href="code.html?task=${esc(t.id)}"><button class="sm">Kodu gör</button></a>`;
115
+ return `<div class="tcard"><div class="top"><span class="id">${esc(t.id)}</span><div class="grow"></div><span class="badge">${esc(t.executionMode||'auto')}</span>${opLabel?`<span class="badge">${esc(opLabel)}</span>`:''}${sched}</div><div class="prompt">${esc(t.prompt||'')}</div>${acts?`<div class="acts">${acts}</div>`:''}</div>`}
116
+ function runningCard(cur){const stage=cur.stage?`<span class="badge">${esc(cur.stage)}</span>`:'';return `<div class="tcard"><div class="top"><span class="id">${esc(cur.id||'')}</span><div class="grow"></div>${stage}</div><div class="prompt">${esc(cur.prompt||'')}</div><div class="run-state"><span class="rd"></span>${esc(cur.agent?cur.agent+' çalışıyor':'operatör çalışıyor')}</div></div>`}
117
+ function fill(id,html,count){$('#col-'+id).innerHTML=html||`<div class="col-empty">—</div>`;$('#cnt-'+id).textContent=count}
118
+ function renderBoard(){const q=BOARD.queue||{},cur=BOARD.status?.current||null,runId=cur&&cur.id;
119
+ const pending=(q.pending||[]).filter(t=>!runId||t.id!==runId);
120
+ fill('pending',pending.map(t=>taskCard(t,'pending')).join(''),pending.length);
121
+ fill('running',cur?runningCard(cur):'',cur?1:0);
122
+ fill('done',(q.done||[]).map(t=>taskCard(t,'done')).join(''),(q.done||[]).length);
123
+ fill('failed',(q.failed||[]).map(t=>taskCard(t,'failed')).join(''),(q.failed||[]).length)}
124
+ function applyStatus(s){BOARD.status=s||{};$('#dot').className='dot'+(s&&s.running?' on':'');$('#engineState').textContent=s&&s.running?'çalışıyor':'durdu';$('#modePill').textContent=s&&s.current?`${esc(s.current.agent||'operatör')} · ${esc(s.current.stage||'')}`:(s&&s.busy?'meşgul':'boşta');renderBoard()}
125
+
126
+ /* ---- Zamanlama şeridi ---- */
127
+ function schedTriggerText(s){const t=s.trigger||{};if(t.type==='interval'){const n=t.everyMinutes||0;return n%60===0&&n>=60?`Her ${n/60} saatte bir`:`Her ${n} dk`}if(t.type==='daily')return `Her gün ${esc(t.at||'')}`;if(t.type==='weekly')return `${(t.days||[]).map(d=>SCHED_DAYS[d]).join(', ')} ${esc(t.at||'')}`;return '—'}
128
+ function schedWhen(s){if(s.enabled===false)return 'devre dışı';if(!s.nextRunAt)return '—';try{return new Date(s.nextRunAt).toLocaleString('tr-TR',{dateStyle:'short',timeStyle:'short'})}catch{return '—'}}
129
+ function renderSchedules(){const list=(BOARD.schedules||[]).slice().sort((a,b)=>{const na=a.nextRunAt?Date.parse(a.nextRunAt):Infinity,nb=b.nextRunAt?Date.parse(b.nextRunAt):Infinity;return na-nb});const strip=$('#schedStrip');if(!list.length){strip.hidden=true;return}strip.hidden=false;$('#schedList').innerHTML=list.map(s=>`<span class="sched-chip${s.enabled===false?' off':''}" title="${esc(s.prompt||'')}"><b>${schedTriggerText(s)}</b><span class="when">${schedWhen(s)}</span></span>`).join('')}
130
+
131
+ /* ---- Eylemler ---- */
132
+ async function delTask(id){if(window.appConfirm&&!await appConfirm({title:'Görev silinsin mi?',body:'Bu bekleyen görev kuyruktan kaldırılacak.',confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;try{await api('/api/tasks/'+encodeURIComponent(id),'DELETE')}catch(e){alert(e.message)}}
133
+
134
+ /* ---- SSE ---- */
135
+ const es=new EventSource('/api/events');
136
+ function dispatch(type,data){if(type==='status')applyStatus(data);else if(type==='queue'){BOARD.queue=data;renderBoard()}else if(type==='schedules'){BOARD.schedules=data;renderSchedules()}}
137
+ for(const type of ['status','queue','schedules'])es.addEventListener(type,event=>{try{dispatch(type,JSON.parse(event.data))}catch{}});
138
+ window.addEventListener('pagehide',()=>{try{es.close()}catch{}});
139
+ window.addEventListener('pageshow',x=>{if(x.persisted)location.reload()});
140
+
141
+ /* ---- Açılış ---- */
142
+ async function boot(){try{const st=await api('/api/state');if(st.status)applyStatus(st.status);if(st.queue)BOARD.queue=st.queue;BOARD.schedules=st.schedules||[];renderBoard();renderSchedules()}catch(e){/* sessizce yeniden dene SSE ile */}}
143
+ boot();
144
+ </script>
145
+ </body>
146
+ </html>