@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 +19 -10
- package/orchestrator/README.md +29 -21
- package/orchestrator/src/cli-registry.js +38 -4
- package/orchestrator/src/engine.js +4 -0
- package/orchestrator/src/server.js +32 -5
- package/orchestrator/src/store.js +15 -0
- package/package.json +4 -4
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
|
-
[](https://github.com/omergocmen/CrewCtl/stargazers)
|
|
10
|
+
[](https://github.com/omergocmen/CrewCtl/network/members)
|
|
11
|
+
[](https://github.com/omergocmen/CrewCtl/issues)
|
|
12
|
+
[](https://github.com/omergocmen/CrewCtl/commits)
|
|
13
13
|

|
|
14
14
|

|
|
15
15
|
[](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
|
-
|
|
133
|
-
|
|
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/
|
|
137
|
-
cd
|
|
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
|
-
|
|
143
|
-
|
|
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
|
|
package/orchestrator/README.md
CHANGED
|
@@ -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
|
-
[](https://github.com/omergocmen/CrewCtl/stargazers)
|
|
8
|
+
[](https://github.com/omergocmen/CrewCtl/network/members)
|
|
9
|
+
[](https://github.com/omergocmen/CrewCtl/issues)
|
|
10
|
+
[](https://github.com/omergocmen/CrewCtl/commits)
|
|
11
11
|

|
|
12
12
|

|
|
13
13
|
[](LICENSE)
|
|
14
14
|

|
|
15
|
-
[](https://github.com/omergocmen/
|
|
15
|
+
[](https://github.com/omergocmen/CrewCtl/pulls)
|
|
16
16
|
|
|
17
|
-
🔗 **Repo:** [github.com/omergocmen/
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
`
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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}
|
|
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.
|
|
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/
|
|
20
|
+
"url": "git+https://github.com/omergocmen/CrewCtl.git"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://github.com/omergocmen/
|
|
22
|
+
"homepage": "https://github.com/omergocmen/CrewCtl#readme",
|
|
23
23
|
"bugs": {
|
|
24
|
-
"url": "https://github.com/omergocmen/
|
|
24
|
+
"url": "https://github.com/omergocmen/CrewCtl/issues"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"orchestrator/src/",
|