@omerrgocmen/crewctl 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -6,10 +6,10 @@
6
6
 
7
7
  _A local, self-hosted, zero-dependency multi-agent AI orchestrator with an operator-led team and a live web command center._
8
8
 
9
- [![GitHub stars](https://img.shields.io/github/stars/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/stargazers)
10
- [![GitHub forks](https://img.shields.io/github/forks/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/network/members)
11
- [![GitHub issues](https://img.shields.io/github/issues/omergocmen/cli)](https://github.com/omergocmen/cli/issues)
12
- [![Last commit](https://img.shields.io/github/last-commit/omergocmen/cli)](https://github.com/omergocmen/cli/commits)
9
+ [![GitHub stars](https://img.shields.io/github/stars/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/stargazers)
10
+ [![GitHub forks](https://img.shields.io/github/forks/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/network/members)
11
+ [![GitHub issues](https://img.shields.io/github/issues/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/issues)
12
+ [![Last commit](https://img.shields.io/github/last-commit/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/commits)
13
13
  ![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?logo=node.js&logoColor=white)
14
14
  ![Dependencies](https://img.shields.io/badge/runtime_dependencies-0-brightgreen)
15
15
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](orchestrator/LICENSE)
@@ -118,6 +118,10 @@ En hızlı yol — **tek komut, kurulum gerektirmez:**
118
118
  npx @omerrgocmen/crewctl
119
119
  ~~~
120
120
 
121
+ > **Kaynak depo notu:** Bu komutu CrewCtl kaynak deposunun kendi kökünde değil, yönetmek
122
+ > istediğiniz proje klasöründe çalıştırın. Klonlanmış CrewCtl deposunda npm aynı isimli
123
+ > yerel manifesti önceliklendirebilir; geliştirme kopyasını başlatmak için `npm start` kullanın.
124
+
121
125
  Ya da global kurun:
122
126
 
123
127
  ~~~bash
@@ -129,18 +133,23 @@ Arayüz varsayılan olarak [http://localhost:4317](http://localhost:4317) adresi
129
133
 
130
134
  > **Veri konumu:** config, kuyruk ve görev geçmişi kullanıcı klasörünüzdeki <code>~/.crewctl</code> altında tutulur (ortam değişkeni <code>CREWCTL_HOME</code> ile değiştirilebilir). Çalışma klasörü varsayılan olarak komutu çalıştırdığınız dizindir; panelden değiştirilebilir. Makineye özel <code>config.json</code> ilk çalıştırmada otomatik üretilir.
131
135
 
132
- <details>
133
- <summary>Kaynaktan çalıştırma (geliştirme)</summary>
136
+ ### Developer modu / uzun yol (Git clone)
137
+
138
+ Kaynak kodu geliştirmek, testleri çalıştırmak veya npm paketi yerine doğrudan Git kopyasını
139
+ kullanmak için:
134
140
 
135
141
  ~~~bash
136
- git clone https://github.com/omergocmen/cli.git
137
- cd cli
142
+ git clone https://github.com/omergocmen/CrewCtl.git
143
+ cd CrewCtl
144
+ npm install
138
145
  npm run doctor
146
+ npm test
139
147
  npm start
140
148
  ~~~
141
149
 
142
- Kaynaktan çalıştırıldığında veri, mevcut davranışla uyumlu olarak <code>orchestrator/</code> klasöründe tutulur.
143
- </details>
150
+ `npm install` runtime bağımlılığı indirmez; lock/yerel npm ortamını hazırlar. Kaynaktan
151
+ çalıştırıldığında geliştirme verileri <code>orchestrator/</code> klasöründe tutulur. Sunucu
152
+ çalışırken panel [http://localhost:4317](http://localhost:4317) adresindedir.
144
153
 
145
154
  ### CLI kullanımı
146
155
 
@@ -4,17 +4,17 @@
4
4
  >
5
5
  > _A zero‑dependency, local, self‑hosted **multi‑agent AI orchestrator** that runs your installed CLI coding agents (OpenAI Codex, Claude Code, Google Gemini, OpenCode) as one **operator‑led team**, with a live web dashboard._
6
6
 
7
- [![GitHub stars](https://img.shields.io/github/stars/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/stargazers)
8
- [![GitHub forks](https://img.shields.io/github/forks/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/network/members)
9
- [![GitHub issues](https://img.shields.io/github/issues/omergocmen/cli)](https://github.com/omergocmen/cli/issues)
10
- [![Last commit](https://img.shields.io/github/last-commit/omergocmen/cli)](https://github.com/omergocmen/cli/commits)
7
+ [![GitHub stars](https://img.shields.io/github/stars/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/stargazers)
8
+ [![GitHub forks](https://img.shields.io/github/forks/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/network/members)
9
+ [![GitHub issues](https://img.shields.io/github/issues/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/issues)
10
+ [![Last commit](https://img.shields.io/github/last-commit/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/commits)
11
11
  ![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?logo=node.js&logoColor=white)
12
12
  ![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
14
14
  ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
15
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-ff69b4)](https://github.com/omergocmen/cli/pulls)
15
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-ff69b4)](https://github.com/omergocmen/CrewCtl/pulls)
16
16
 
17
- 🔗 **Repo:** [github.com/omergocmen/cli](https://github.com/omergocmen/cli)
17
+ 🔗 **Repo:** [github.com/omergocmen/CrewCtl](https://github.com/omergocmen/CrewCtl)
18
18
 
19
19
  Elinizde zaten **Codex CLI**, **Claude Code**, **Gemini CLI** veya **OpenCode** varsa; bu araç onları
20
20
  ayrı ayrı kullanmak yerine **tek bir yapay zeka geliştirici takımı** gibi koordine eder. Bir CLI
@@ -82,11 +82,15 @@ ayrı ayrı kullanmak yerine **tek bir yapay zeka geliştirici takımı** gibi k
82
82
 
83
83
  **Tek komut — kurulum gerektirmez:**
84
84
 
85
- ```bash
85
+ ```bash
86
86
  npx @omerrgocmen/crewctl # paneli anında başlatır (komutsuz = start)
87
- ```
88
-
89
- Ya da global kurun:
87
+ ```
88
+
89
+ > **Kaynak depo notu:** `npx` komutunu CrewCtl kaynak deposunun kendi kökünde değil, yönetmek
90
+ > istediğiniz proje klasöründe çalıştırın. Klonlanmış geliştirme kopyasında `npm start`
91
+ > kullanılır; aksi halde npm aynı isimli yerel manifesti seçip bin shim'ini bulamayabilir.
92
+
93
+ Ya da global kurun:
90
94
 
91
95
  ```bash
92
96
  npm install -g @omerrgocmen/crewctl
@@ -100,19 +104,23 @@ crewctl doctor # salt-okunur ortam kontrolü
100
104
  > (`CREWCTL_HOME` ile değiştirilebilir). Çalışma klasörü varsayılan olarak komutu çalıştırdığınız
101
105
  > dizindir; panelden değiştirilebilir.
102
106
 
103
- <details>
104
- <summary>Kaynaktan çalıştırma (geliştirme)</summary>
105
-
106
- ```bash
107
- git clone https://github.com/omergocmen/cli.git
108
- cd cli
107
+ ### Developer modu / uzun yol (Git clone)
108
+
109
+ Kaynak kodu geliştirmek, testleri çalıştırmak veya npm paketi yerine doğrudan Git kopyasını
110
+ kullanmak için:
111
+
112
+ ```bash
113
+ git clone https://github.com/omergocmen/CrewCtl.git
114
+ cd CrewCtl
115
+ npm install
109
116
  npm run cli -- doctor # salt-okunur ortam kontrolü
117
+ npm test # tam regresyon zinciri
110
118
  npm start # sunucuyu başlatır ve tarayıcıyı açar
111
- ```
112
-
113
- Kaynaktan çalıştırıldığında (repo `test/` klasörü mevcutken) veri, mevcut davranışla uyumlu olarak
114
- `orchestrator/` klasöründe tutulur.
115
- </details>
119
+ ```
120
+
121
+ `npm install` runtime bağımlılığı indirmez; lock/yerel npm ortamını hazırlar. Kaynaktan
122
+ çalıştırıldığında (repo `test/` klasörü mevcutken) geliştirme verileri `orchestrator/`
123
+ klasöründe tutulur.
116
124
 
117
125
  `crewctl doctor` ayarları değiştirmez. Yalnızca keşif sonucunu `config.json` dosyasına uygulamak
118
126
  istediğinizde açıkça `crewctl doctor --fix` kullanın.
@@ -59,6 +59,34 @@ const DEFINITIONS = {
59
59
  const RESOLVED = new Map();
60
60
  let OPEN_CODE_MODELS = [];
61
61
 
62
+ // En fazla bir CLI'yi (or. codex) ayni anda hem canli saglik/model probe'u hem de motorun
63
+ // operatör kosumu calistirirsa, codex gibi bazi CLI'lar ayni is dizininde ikinci exec oturumunu
64
+ // SIGTERM ile keser. Probe'lar "en iyi caba"dir; motor onceliklidir. Aktif probe cocuklarini
65
+ // izleyip motor bir goreve baslarken hepsini sonlandiriyoruz.
66
+ const ACTIVE_PROBES = new Set();
67
+ function registerProbe(child) {
68
+ if (!child) return () => {};
69
+ ACTIVE_PROBES.add(child);
70
+ return () => ACTIVE_PROBES.delete(child);
71
+ }
72
+ // Uctan uca proses agacini oldur (codex kabuk altinda alt proses baslatabilir).
73
+ function terminateProbeTree(child) {
74
+ if (!child) return;
75
+ try {
76
+ if (isWin && child.pid) spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { windowsHide: true, stdio: "ignore" });
77
+ else child.kill("SIGKILL");
78
+ } catch {}
79
+ }
80
+ // Motor bir goreve baslarken cagirir: uctaki tum canli probe'lari sonlandirir ki operatörun
81
+ // CLI'si (codex vb.) temiz bir alanda calissin. Probe'lar sonuc olarak "timeout/failed" doner;
82
+ // bu zararsizdir, motor bosaldiginda yeniden denenir.
83
+ function abortActiveProbes() {
84
+ const count = ACTIVE_PROBES.size;
85
+ for (const child of ACTIVE_PROBES) terminateProbeTree(child);
86
+ ACTIVE_PROBES.clear();
87
+ return count;
88
+ }
89
+
62
90
  function selectOpenCodeModel(models) {
63
91
  const list = Array.isArray(models) ? models.map(String).filter(Boolean) : [];
64
92
  const priorities = [
@@ -366,10 +394,12 @@ function testInstalledCli(id, options = {}) {
366
394
  prepared.cleanup();
367
395
  return resolve({ id, installed: true, version: found.version, health: classifyHealthFailure(error.message) });
368
396
  }
397
+ const unregister = registerProbe(child);
369
398
  const timer = setTimeout(() => {
370
399
  if (settled) return;
371
400
  settled = true;
372
- try { child.kill(); } catch {}
401
+ unregister();
402
+ terminateProbeTree(child);
373
403
  prepared.cleanup();
374
404
  resolve({ id, installed: true, version: found.version, health: { status: "timeout", label: "Yanıt zaman aşımına uğradı", detail: `Sağlık testi ${Math.round(timeoutMs / 1000)} saniyede tamamlanmadı.` } });
375
405
  }, timeoutMs);
@@ -381,6 +411,7 @@ function testInstalledCli(id, options = {}) {
381
411
  if (settled) return;
382
412
  settled = true;
383
413
  clearTimeout(timer);
414
+ unregister();
384
415
  prepared.cleanup();
385
416
  const failure = classifyHealthFailure(error.message);
386
417
  resolve({ id, installed: true, version: found.version, health: failure });
@@ -389,6 +420,7 @@ function testInstalledCli(id, options = {}) {
389
420
  if (settled) return;
390
421
  settled = true;
391
422
  clearTimeout(timer);
423
+ unregister();
392
424
  prepared.cleanup();
393
425
  const raw = `${stdout}\n${stderr}`.trim();
394
426
  if (code === 0 && /HEALTH_OK/i.test(raw)) resolve({ id, installed: true, version: found.version, health: { status: "ready", label: "Hazır", detail: "Gerçek sağlık testi başarılı." } });
@@ -409,17 +441,19 @@ function listCodexModels(options = {}) {
409
441
  const timeoutMs = Math.max(5000, Number(options.timeoutMs || 20000));
410
442
  return new Promise((resolve, reject) => {
411
443
  let buffer = "", settled = false, modelRequestSent = false;
412
- let child;
444
+ let child, unregister = () => {};
413
445
  const finish = (error, value) => {
414
446
  if (settled) return;
415
447
  settled = true;
416
448
  clearTimeout(timer);
417
- try { child?.kill(); } catch {}
449
+ unregister();
450
+ terminateProbeTree(child);
418
451
  if (error) reject(error); else resolve(value);
419
452
  };
420
453
  const timer = setTimeout(() => finish(new Error(`Codex model listesi ${Math.round(timeoutMs / 1000)} saniyede alınamadı.`)), timeoutMs);
421
454
  try { child = spawn(command.file, command.args, { shell: command.shell, windowsHide: true, windowsVerbatimArguments: !!command.verbatim }); }
422
455
  catch (error) { finish(error); return; }
456
+ unregister = registerProbe(child);
423
457
  child.stdout?.setEncoding("utf8");
424
458
  child.stderr?.setEncoding("utf8");
425
459
  child.stdout?.on("data", (chunk) => {
@@ -576,4 +610,4 @@ function ensureValidOperator(cfg, discovered) {
576
610
  return changed;
577
611
  }
578
612
 
579
- module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, healthCheckAll, listCodexModels, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
613
+ module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, healthCheckAll, listCodexModels, abortActiveProbes, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
@@ -757,6 +757,10 @@ class Engine extends EventEmitter {
757
757
  this.emit("status", this.status());
758
758
  this.publish("activity", { ...base, kind: "process.started", cmd: agent.cmd, args: rawArgs, cwd });
759
759
 
760
+ // Motor onceliklidir: canli CLI saglik/model probe'lari (or. codex app-server / codex exec)
761
+ // ayni CLI'yi ayni anda calistirirsa codex ikinci exec oturumunu SIGTERM ile keser. Bu yuzden
762
+ // operatör/agent kosumuna girmeden uctaki tum probe'lari sonlandiriyoruz.
763
+ cliRegistry.abortActiveProbes();
760
764
  let child;
761
765
  try { child = spawn(file, args, { cwd, env: cliRegistry.agentEnvironment(agent), windowsHide: true, shell: command.shell, windowsVerbatimArguments: !!command.verbatim }); }
762
766
  catch (error) { return reject(error); }
@@ -78,12 +78,22 @@ function broadcastSchedules() {
78
78
  broadcast("schedules", store.loadConfig().schedules || []);
79
79
  }
80
80
 
81
+ // Pano/kuyruk listesi yalnizca ozet alanlari gosterir; teamState (tum agent ciktilari/mesajlari),
82
+ // conversation ve changes gibi agir ic alanlar liste yukunu gereksiz sisirir ve her SSE queue
83
+ // yayininda serilestirilir. Detay gorunumleri tam gorevi ayri /api/tasks/:id ile ceker; burada
84
+ // bu agir alanlari cikartip yayin boyutunu kuculturuz. (planPreview/delivery/summary korunur.)
85
+ function liteTask(task) {
86
+ const { teamState, conversation, changes, ...rest } = task;
87
+ return rest;
88
+ }
81
89
  function snapshot() {
82
90
  return {
83
- pending: store.listTasks("pending"),
84
- approval: store.listTasks("approval"),
85
- done: store.listTasks("done").filter((task) => task.kind !== "operator-chat").slice(-30).reverse(),
86
- failed: store.listTasks("failed").slice(-30).reverse(),
91
+ pending: store.listTasks("pending").map(liteTask),
92
+ approval: store.listTasks("approval").map(liteTask),
93
+ // Operator-chat gorevleri de done'a yazildigi icin filtreden sonra 30 gercek gorev kalsin
94
+ // diye biraz genis bir pencere (80) okunur; yine de sabit ve gecmisten bagimsizdir.
95
+ done: store.listRecentTasks("done", 80).filter((task) => task.kind !== "operator-chat").slice(-30).reverse().map(liteTask),
96
+ failed: store.listRecentTasks("failed", 30).reverse().map(liteTask),
87
97
  };
88
98
  }
89
99
 
@@ -122,8 +132,15 @@ function applyHealthToConfig(results) {
122
132
  }
123
133
 
124
134
  let healthRunning = false;
135
+ // Motor bir gorev calistirirken (or. operatör codex) ayni CLI'ye karsi canli probe acmak
136
+ // codex'in ikinci exec/app-server oturumunu SIGTERM ile kesmesine yol acar. Probe'lar en iyi
137
+ // cabadir; motor mesgulken canli probe atlanip son bilinen sonuc dondurulur.
138
+ function engineBusy() {
139
+ try { return Boolean(engine.status().current); } catch { return false; }
140
+ }
125
141
  async function getCodexModels(force = false) {
126
142
  if (!force && codexModelCache.models.length && Date.now() - codexModelCache.checkedAt < CODEX_MODEL_CACHE_TTL_MS) return codexModelCache.models;
143
+ if (engineBusy()) return codexModelCache.models;
127
144
  const models = await cliRegistry.listCodexModels({ timeoutMs: 20000 });
128
145
  codexModelCache = { checkedAt: Date.now(), models };
129
146
  const cfg = store.loadConfig();
@@ -139,6 +156,10 @@ async function refreshCliHealth(force = false) {
139
156
  broadcast("cli-health", cliStatus);
140
157
  return cliStatus;
141
158
  }
159
+ if (engineBusy()) {
160
+ broadcast("cli-health", cliStatus);
161
+ return cliStatus;
162
+ }
142
163
  healthRunning = true;
143
164
  cliStatus = cliStatus.map((cli) => ({ ...cli, health: { status: "testing", label: "Test ediliyor", detail: "Gerçek CLI sağlık testi çalışıyor." } }));
144
165
  broadcast("cli-health", cliStatus);
@@ -642,7 +663,13 @@ function startupBanner() {
642
663
  console.log(` ${DIM}Ayrıntı için: npm run doctor${X}`);
643
664
  }
644
665
  console.log(`\n ${B}▶ Panel: ${C}${url}${X}`);
645
- console.log(` ${DIM}Panelde 'Başlat'a basıp bir görev gönderin. (tarayıcı otomatik açılmazsa yukarıdaki adresi açın)${X}\n`);
666
+ console.log(` ${DIM}Panelde 'Başlat'a basıp bir görev gönderin. (tarayıcı otomatik açılmazsa yukarıdaki adresi açın)${X}`);
667
+ // npx gecici onbellekten calisiyorsa kalici 'crewctl' komutu kurulmamistir; kullaniciya nasil
668
+ // kalici hale getirecegini hatirlat (aksi halde kapatinca 'crewctl' bulunamaz).
669
+ if (/[\\/]_npx[\\/]/.test(__dirname)) {
670
+ console.log(` ${DIM}Kalıcı 'crewctl' komutu için: ${Y}npm i -g @omerrgocmen/crewctl${X}`);
671
+ }
672
+ console.log("");
646
673
  openBrowser(url);
647
674
  }
648
675
 
@@ -209,6 +209,20 @@ function listTasks(state) {
209
209
  .filter(Boolean)
210
210
  .sort((a, b) => (a.id < b.id ? -1 : 1));
211
211
  }
212
+ // Yalnizca en yeni `limit` gorevi okur. Gorev id'si zaman damgasiyla basladigi icin dosya adi
213
+ // siralamasi = kronolojik siralama; boylece done/failed gecmisi buyudukce (yuzlerce/binlerce
214
+ // dosya) her pano yenilemesinde TUM dosyalari okuyup ayristirma maliyeti sabit N'e iner.
215
+ function listRecentTasks(state, limit) {
216
+ const dir = path.join(Q, state);
217
+ if (!fs.existsSync(dir)) return [];
218
+ let names = fs.readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
219
+ if (limit && names.length > limit) names = names.slice(-limit);
220
+ return names
221
+ .map((f) => {
222
+ try { return JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")); } catch { return null; }
223
+ })
224
+ .filter(Boolean);
225
+ }
212
226
  function nextPending() {
213
227
  return listTasks("pending")[0] || null;
214
228
  }
@@ -344,6 +358,7 @@ module.exports = {
344
358
  writeRole,
345
359
  deleteRole,
346
360
  listTasks,
361
+ listRecentTasks,
347
362
  nextPending,
348
363
  addTask,
349
364
  addChatTask,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omerrgocmen/crewctl",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "CrewCtl — Zero-dependency Node.js orchestrator that runs installed CLI coding agents as one operator-led team.",
5
5
  "keywords": [
6
6
  "ai",
@@ -17,11 +17,11 @@
17
17
  "type": "commonjs",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/omergocmen/cli.git"
20
+ "url": "git+https://github.com/omergocmen/CrewCtl.git"
21
21
  },
22
- "homepage": "https://github.com/omergocmen/cli#readme",
22
+ "homepage": "https://github.com/omergocmen/CrewCtl#readme",
23
23
  "bugs": {
24
- "url": "https://github.com/omergocmen/cli/issues"
24
+ "url": "https://github.com/omergocmen/CrewCtl/issues"
25
25
  },
26
26
  "files": [
27
27
  "orchestrator/src/",