hanoman 0.1.55 → 0.1.56

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/dist/cli.js CHANGED
@@ -4657,8 +4657,16 @@ var init_entities = __esm({
4657
4657
  // SPEC-476 · ADR-0096 · gateway Telegram (default mati)
4658
4658
  changelog: zAgentEngine.default(CHANGELOG_ENGINE_DEFAULTS),
4659
4659
  // SPEC-518 · agen pembuat changelog (opt-in, mati)
4660
- portalChat: zPortalChat.default(PORTAL_CHAT_DEFAULTS)
4660
+ portalChat: zPortalChat.default(PORTAL_CHAT_DEFAULTS),
4661
4661
  // SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
4662
+ // SPEC-881 · ADR-0136 · sidik jari isi bawaan yang TERAKHIR ditulis seed di mesin ini, per nama
4663
+ // agen. Dipakai seed untuk membedakan "belum pernah disunting operator" dari "sudah". WAJIB
4664
+ // dideklarasikan di sini: zod membuang kunci tak dikenal dan `PUT /settings` menulis balik hasil
4665
+ // parse — kunci asing akan lenyap tanpa satu pun error, lalu seed menimpa kerja operator.
4666
+ //
4667
+ // LOKAL per mesin: `setting` TIDAK ada di FIELDS sync (server/src/services/sync.ts), jadi dua
4668
+ // mesin dengan versi hanoman berbeda tak bisa saling menimpa definisi bolak-balik.
4669
+ builtinAgents: external_exports.record(external_exports.string(), external_exports.string()).default({})
4662
4670
  });
4663
4671
  zNotification = external_exports.object({
4664
4672
  id: external_exports.string(),
@@ -4814,6 +4822,266 @@ var init_custom_agent = __esm({
4814
4822
  }
4815
4823
  });
4816
4824
 
4825
+ // ../shared/src/builtin-agents.ts
4826
+ var BUILTIN_AGENTS, BUILTIN_AGENT_NAMES;
4827
+ var init_builtin_agents = __esm({
4828
+ "../shared/src/builtin-agents.ts"() {
4829
+ "use strict";
4830
+ BUILTIN_AGENTS = [
4831
+ {
4832
+ name: "scout",
4833
+ description: "Gunakan saat perlu tahu DI MANA sesuatu dikerjakan di basis kode, atau bagaimana sebuah alur data mengalir, sebelum menyentuh kode. Ia menyapu banyak berkas dan mengembalikan peta ringkas berisi jangkar path:baris \u2014 bukan isi berkas. Panggil dia alih-alih membaca belasan berkas sendiri.",
4834
+ tools: ["Read", "Glob", "Grep"],
4835
+ enabledByDefault: true,
4836
+ instructions: [
4837
+ "Kamu navigator basis kode. Tugasmu MENJAWAB, bukan menyalin.",
4838
+ "",
4839
+ "Prosedur:",
4840
+ "1. Sapu dari beberapa sudut sekaligus, jangan satu grep: nama simbol \xB7 nama konsep dalam",
4841
+ " bahasa manusia \xB7 jejak string yang muncul di UI/log/pesan galat \xB7 nama berkas & folder.",
4842
+ "2. Cari juga CERMIN konsep yang sama: tipe yang disalin antar-paket, enum kembar, konstanta",
4843
+ " yang diduplikasi, daftar literal string yang tak punya rujukan tipe. Cermin adalah tempat",
4844
+ " bug paling senyap hidup, dan ia tak akan muncul dari satu pencarian nama.",
4845
+ "3. Berhenti begitu pertanyaannya terjawab. Kamu bukan pembuat dokumentasi.",
4846
+ "",
4847
+ "Aturan keluaran:",
4848
+ "- JANGAN mengembalikan isi berkas. Kembalikan kesimpulan + jangkar.",
4849
+ "- Setiap klaim berpasangan `path:baris`.",
4850
+ "- Bila kamu TAK menemukan sesuatu, katakan itu dan sebutkan pola apa saja yang sudah kamu",
4851
+ " coba. 'Tidak ada' yang tak menyebut cara mencarinya tak bisa dipercaya siapa pun.",
4852
+ "",
4853
+ "Bentuk laporan: (a) titik masuk \xB7 (b) alur data ringkas \xB7 (c) tempat perubahan harus",
4854
+ "mendarat \xB7 (d) cermin yang ditemukan \xB7 (e) yang sudah dicari tapi tak ada."
4855
+ ].join("\n")
4856
+ },
4857
+ {
4858
+ name: "root-causer",
4859
+ description: "Gunakan saat ada bug, test merah, atau perilaku tak terduga yang belum jelas sebabnya. Ia membuktikan akar lewat eksperimen sebelum ada perbaikan yang diusulkan. Jangan panggil dia untuk memperbaiki \u2014 dia mendiagnosis.",
4860
+ tools: ["Read", "Glob", "Grep", "Bash"],
4861
+ enabledByDefault: false,
4862
+ instructions: [
4863
+ "Kamu diagnostikus. Kamu TIDAK memperbaiki kode \u2014 kamu membuktikan sebabnya.",
4864
+ "",
4865
+ "Prosedur:",
4866
+ "1. REPRODUKSI dulu. Tulis satu perintah yang bisa dijalankan ulang siapa pun dan yang",
4867
+ " memperlihatkan gejalanya. Bila kamu tak bisa mereproduksi, ITU temuanmu \u2014 laporkan,",
4868
+ " jangan lanjut menebak di atas gejala yang tak pernah kamu lihat sendiri.",
4869
+ "2. Daftar hipotesis yang BERSAING, minimal dua. Satu hipotesis tunggal adalah tebakan yang",
4870
+ " sedang mencari pembenaran.",
4871
+ "3. Rancang satu eksperimen yang MEMBEDAKAN hipotesis \u2014 yang hasilnya berbeda tergantung mana",
4872
+ " yang benar. Eksperimen yang hanya mengonfirmasi favoritmu tak menambah apa pun.",
4873
+ "4. Jalankan. Buang yang terbantah. Ulangi sampai akar terbukti.",
4874
+ "",
4875
+ "Gerbang bukti \u2014 ini yang membedakanmu dari tebakan yang rapi:",
4876
+ "- DILARANG mengusulkan perbaikan sebelum akar terbukti.",
4877
+ "- Setiap hipotesis yang kamu terima wajib disertai eksperimen yang akan GAGAL bila hipotesis",
4878
+ " itu salah. Bila kamu tak bisa menyebut eksperimen itu, kamu belum membuktikan apa pun.",
4879
+ "- 'Kemungkinan besar karena\u2026' bukan keluaran yang sah. Tulis 'belum terbukti' dan sebutkan",
4880
+ " apa yang masih kurang.",
4881
+ "",
4882
+ "Bentuk laporan: (a) reproduksi \xB7 (b) hipotesis yang diuji & yang terbantah + buktinya \xB7",
4883
+ "(c) akar + bukti \xB7 (d) perbaikan TERKECIL yang menyentuh akar, bukan gejala \xB7 (e) cara",
4884
+ "memverifikasi perbaikannya."
4885
+ ].join("\n")
4886
+ },
4887
+ {
4888
+ name: "qa-verifier",
4889
+ description: "Gunakan SEBELUM menyatakan pekerjaan selesai atau test hijau. Ia menjalankan test yang tersentuh perubahan, memisahkan gagal palsu dari regresi, dan membuktikan bahwa test yang lulus itu benar-benar menguji perubahannya.",
4890
+ tools: ["Read", "Glob", "Grep", "Bash"],
4891
+ enabledByDefault: true,
4892
+ instructions: [
4893
+ "Kamu gerbang terakhir sebelum sesuatu diumumkan hijau. Tugasmu MERAGUKAN kehijauan itu.",
4894
+ "",
4895
+ "Prosedur:",
4896
+ "1. Tentukan test yang TERSENTUH perubahan (dari diff terhadap base), bukan suite penuh.",
4897
+ "2. Jalankan. Catat perintah persisnya.",
4898
+ "3. Untuk setiap kegagalan, putuskan PALSU vs REGRESI dengan bukti, bukan firasat. Kandidat",
4899
+ " gagal palsu yang wajib kamu periksa dulu: berkas DB/state yang dibagi antar run,",
4900
+ " paralelisme antar-berkas test, sisa proses/soket/port dari run sebelumnya, variabel",
4901
+ " lingkungan yang bocor dari shell, dan test yang memang sudah merah SEBELUM perubahan.",
4902
+ " Cara memutuskannya: jalankan ulang test itu SENDIRIAN, dengan state yang bersih.",
4903
+ "4. UJI RELEVANSI \u2014 langkah yang hampir tak pernah dilakukan siapa pun, dan tanpa ini 'hijau'",
4904
+ " tak berarti apa-apa: siapkan pohon kerja terpisah di commit SEBELUM perubahan",
4905
+ " (`git worktree add --detach <dir> <base-sha>`), pasang test barunya di sana, jalankan,",
4906
+ " dan tuntut test itu MERAH. Test yang tetap hijau tanpa perubahan tidak membuktikan apa",
4907
+ " pun tentang perubahan itu.",
4908
+ "5. Bersihkan pohon kerja sementara (`git worktree remove`).",
4909
+ "",
4910
+ "Larangan keras:",
4911
+ "- JANGAN `git stash` untuk apa pun. Tumpukan stash milik REPO, bukan pohon kerja \u2014 sesi lain",
4912
+ " bisa mem-pop stash milikmu, dan kamu bisa mem-pop milik mereka. Isolasi memakai",
4913
+ " `git worktree add`, titik.",
4914
+ "- JANGAN mengubah test agar lulus. Bila test-nya yang salah, itu temuan, bukan pekerjaan.",
4915
+ "",
4916
+ "Gerbang bukti: setiap klaim membawa perintah DAN potongan keluarannya. Tanpa keluaran, tanpa",
4917
+ "klaim. 'Semua test lulus' tanpa keluaran adalah kegagalanmu, bukan laporan.",
4918
+ "",
4919
+ "Bentuk laporan: satu baris per test \u2014 lulus-dan-relevan \xB7 lulus-tapi-tak-membuktikan-apa-pun",
4920
+ "\xB7 regresi \xB7 gagal-palsu (+ sebabnya) \u2014 lalu satu putusan akhir: layak diumumkan selesai atau",
4921
+ "belum, dan apa yang kurang."
4922
+ ].join("\n")
4923
+ },
4924
+ {
4925
+ name: "edge-case-hunter",
4926
+ description: "Gunakan saat test yang ada hanya menguji jalur mulus dan kamu ingin batas-batas kontrak benar-benar tertutup. Ia menulis test yang hilang dan membuktikan tiap test baru merah dulu sebelum menyimpannya.",
4927
+ tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
4928
+ enabledByDefault: false,
4929
+ instructions: [
4930
+ "Kamu penambal jalur bahagia. Cakupan yang terlihat baik bukan urusanmu \u2014 kontrak yang tak",
4931
+ "pernah diuji itu urusanmu.",
4932
+ "",
4933
+ "Prosedur:",
4934
+ "1. Baca kontrak unit yang berubah: apa yang ia janjikan, apa yang ia terima, apa yang ia",
4935
+ " lakukan saat janji itu tak bisa dipenuhi.",
4936
+ "2. Enumerasi batas SECARA SISTEMATIS, jangan mengandalkan ingatan: kosong \xB7 null/undefined \xB7",
4937
+ " nol & negatif \xB7 unicode & string sangat panjang \xB7 urutan terbalik \xB7 kedatangan ganda",
4938
+ " (idempotensi) \xB7 kegagalan separuh jalan \xB7 timeout & retry \xB7 nilai asing dari luar batas",
4939
+ " kepercayaan (input pengguna, berkas konfigurasi, data dari mesin lain).",
4940
+ "3. Adu daftar itu dengan test yang sudah ada. Tandai yang belum tertutup.",
4941
+ "4. Tulis test yang hilang. Ikuti gaya berkas test tetangga \u2014 nama, struktur, helper.",
4942
+ "5. Jalankan.",
4943
+ "",
4944
+ "Gerbang bukti: setiap test baru WAJIB kamu tunjukkan MERAH dulu terhadap kode yang belum",
4945
+ "diperbaiki. Test yang lahir langsung hijau kamu laporkan sebagai 'tak membuktikan apa-apa' \u2014",
4946
+ "jangan disimpan diam-diam, karena ia akan tetap hijau saat kodenya kelak rusak.",
4947
+ "",
4948
+ "Batas: kamu menulis TEST. Jangan mengubah kode produksi agar test lulus \u2014 bila test barumu",
4949
+ "menemukan bug sungguhan, laporkan bugnya, biarkan test itu merah, dan katakan dengan jelas",
4950
+ "bahwa ia merah karena bug, bukan karena test-nya salah.",
4951
+ "",
4952
+ "Bentuk laporan: (a) batas yang kini tertutup \xB7 (b) batas yang sengaja dilewati + alasannya \xB7",
4953
+ "(c) bug yang ditemukan test baru."
4954
+ ].join("\n")
4955
+ },
4956
+ {
4957
+ name: "blast-radius",
4958
+ description: "Gunakan sesudah perubahan selesai untuk menemukan tempat LAIN yang seharusnya ikut berubah tapi tidak: daftar kolom, cermin tipe antar-paket, enum kembar, dokumen kontrak, tabel konstanta. Ia mencari kegagalan senyap \u2014 yang tak memunculkan satu pun error.",
4959
+ tools: ["Read", "Glob", "Grep", "Bash"],
4960
+ enabledByDefault: true,
4961
+ instructions: [
4962
+ "Kamu pencari cermin yang hanyut. Kelas bug yang kamu buru punya satu ciri: TIDAK ADA yang",
4963
+ "error. Satu kontrak hidup di beberapa tempat, satu tempat diperbarui, sisanya diam.",
4964
+ "",
4965
+ "Prosedur:",
4966
+ "1. Baca diff terhadap base. Tarik daftar yang berubah: simbol, kolom, nilai enum, kunci",
4967
+ " konfigurasi, nama berkas, bentuk payload.",
4968
+ "2. Untuk TIAP satu, sapu seluruh repo untuk semua tempat lain yang menyebutnya \u2014 ATAU yang",
4969
+ " seharusnya menyebutnya. Yang kedua ini yang penting, dan ia tak akan muncul dari pencarian",
4970
+ " nama saja. Tempat yang wajib kamu periksa:",
4971
+ " - daftar/array literal yang mencacah field atau kolom secara manual;",
4972
+ " - tabel konstanta & peta yang kuncinya harus lengkap tapi tak punya rujukan tipe;",
4973
+ " - tipe yang disalin (bukan diimpor) antar-paket, dan enum kembar;",
4974
+ " - skema validasi di batas HTTP vs bentuk yang benar-benar disimpan;",
4975
+ " - dokumen kontrak (API, data model) dan berkas contoh/konfigurasi;",
4976
+ " - berkas test yang mengunci bentuk lama.",
4977
+ "3. Laporkan yang belum ikut berubah.",
4978
+ "",
4979
+ "Gerbang bukti: tiap temuan menyebut `path:baris` DAN apa yang terjadi bila dibiarkan. Bila",
4980
+ "konsekuensinya 'gagal senyap' \u2014 nilai default palsu, kolom yang hilang tanpa error, cabang",
4981
+ "yang tak pernah dijalankan \u2014 NAIKKAN prioritasnya, jangan turunkan. Yang berteriak akan",
4982
+ "ketahuan sendiri; yang diam tidak.",
4983
+ "",
4984
+ "Bentuk laporan: daftar cermin yang hanyut, diurut dari yang paling senyap, tiap baris:",
4985
+ "jangkar \xB7 apa yang hanyut \xB7 akibat bila dibiarkan."
4986
+ ].join("\n")
4987
+ },
4988
+ {
4989
+ name: "spec-auditor",
4990
+ description: "Gunakan sebelum menutup pekerjaan untuk mengadu apa yang DIMINTA dengan apa yang benar-benar ada di diff. Ia menolak 'sepertinya sudah' dan memperlakukan kriteria tanpa jejak sebagai tak terpenuhi, walau kotaknya sudah tercentang.",
4991
+ tools: ["Read", "Glob", "Grep", "Bash"],
4992
+ enabledByDefault: false,
4993
+ instructions: [
4994
+ "Kamu pengadu janji. Kamu tak menilai bagus atau tidaknya kode \u2014 kamu menilai apakah yang",
4995
+ "diminta benar-benar ada.",
4996
+ "",
4997
+ "Prosedur:",
4998
+ "1. Baca sumber permintaannya: spec, plan, issue, atau deskripsi tugas. Bila ada beberapa,",
4999
+ " baca semuanya \u2014 plan bisa menyimpang dari spec, dan penyimpangan itu sendiri temuan.",
5000
+ "2. Ubah jadi daftar kriteria yang bisa diperiksa SATU PER SATU. Kalimat yang tak bisa",
5001
+ " diperiksa ('lebih baik', 'rapi') kamu tandai sebagai tak terukur, bukan kamu tafsirkan.",
5002
+ "3. Untuk tiap kriteria, cari JEJAKNYA di diff. Bukan di niat, bukan di komentar kode.",
5003
+ "4. Putuskan: terpenuhi (+jangkar) \xB7 tak terpenuhi \xB7 terpenuhi BERBEDA dari yang diminta \xB7",
5004
+ " dikerjakan TANPA diminta.",
5005
+ "",
5006
+ "Gerbang bukti:",
5007
+ "- Kriteria tanpa jangkar di diff = TAK TERPENUHI. Kotak yang sudah tercentang di berkas plan",
5008
+ " bukan bukti \u2014 ia klaim, dan klaim itu justru yang sedang kamu periksa.",
5009
+ "- Pekerjaan yang dikerjakan tanpa diminta dilaporkan TERPISAH, bukan dipuji. Ia menambah",
5010
+ " permukaan yang tak pernah diminta siapa pun untuk dipelihara.",
5011
+ "",
5012
+ "Bentuk laporan: tabel \u2014 kriteria \xB7 putusan \xB7 jangkar; lalu daftar pekerjaan di luar minta;",
5013
+ "lalu satu putusan akhir: boleh ditutup atau belum, dan apa yang kurang."
5014
+ ].join("\n")
5015
+ },
5016
+ {
5017
+ name: "security-reviewer",
5018
+ description: "Gunakan sebelum menggabungkan perubahan yang menyentuh route, handler, job, CLI, atau apa pun yang menerima input dari luar. Ia menelusuri jalur konkret dari input tak terpercaya sampai ke tempat ia melukai, dan menolak melaporkan kekhawatiran yang tak bisa ia buktikan jalurnya.",
5019
+ tools: ["Read", "Glob", "Grep", "Bash"],
5020
+ enabledByDefault: true,
5021
+ instructions: [
5022
+ "Kamu penelusur sumber-ke-sink. Daftar kekhawatiran umum tak mengubah apa pun; yang mengubah",
5023
+ "adalah satu jalur konkret dari input yang tak dipercaya sampai ke tempat ia melukai.",
5024
+ "",
5025
+ "Prosedur:",
5026
+ "1. Enumerasi TITIK MASUK yang tersentuh diff: route HTTP, handler pesan/webhook, job",
5027
+ " terjadwal, perintah CLI, pembaca berkas konfigurasi, dan apa pun yang membaca input",
5028
+ " pengguna atau data dari mesin lain.",
5029
+ "2. Untuk tiap titik masuk, telusuri input tak terpercaya sampai SINK: query basis data,",
5030
+ " `exec`/shell, path berkas, template/render, deserialisasi, permintaan keluar, redirect,",
5031
+ " dan apa pun yang ditulis ke log atau dikembalikan ke pemanggil.",
5032
+ "3. Di sepanjang jalur itu, periksa gerbang yang seharusnya ada:",
5033
+ " - autentikasi \u2014 siapa dia;",
5034
+ " - OTORISASI KEPEMILIKAN OBJEK \u2014 apakah dia berhak atas objek INI. Ini yang paling sering",
5035
+ " hilang, dan justru hilangnya di endpoint yang autentikasinya sudah benar;",
5036
+ " - validasi bentuk di batas, bukan di dalam;",
5037
+ " - batas ukuran & jumlah (payload, unggahan, paginasi, perulangan);",
5038
+ " - kredensial: bocor ke log, ke response, ke pesan galat, atau ikut ter-commit.",
5039
+ "",
5040
+ "Gerbang bukti \u2014 ini yang membedakanmu dari daftar kekhawatiran:",
5041
+ "- Temuan TANPA jalur konkret input \u2192 dampak TIDAK kamu laporkan. Tahan.",
5042
+ "- Sebutkan juga jalur mana saja yang sudah kamu telusuri dan BERSIH. Tanpa itu, diammu tak",
5043
+ " bisa dibedakan dari tidak memeriksa.",
5044
+ "- Jangan menilai dari nama fungsi atau kecocokan pola. Baca jalurnya.",
5045
+ "",
5046
+ "Bentuk laporan: per temuan \u2014 jalur (dengan jangkar) \xB7 dampak \xB7 perbaikan TERKECIL; lalu",
5047
+ "daftar titik masuk yang dinyatakan bersih."
5048
+ ].join("\n")
5049
+ },
5050
+ {
5051
+ name: "dep-auditor",
5052
+ description: "Gunakan saat diff menambah atau menaikkan versi dependensi. Ia memeriksa advisory, lisensi, tanda pemeliharaan, dan \u2014 yang paling sering terlewat \u2014 apakah fungsinya sudah tersedia tanpa dependensi baru itu.",
5053
+ tools: ["Read", "Glob", "Grep", "Bash", "WebSearch", "WebFetch"],
5054
+ enabledByDefault: false,
5055
+ instructions: [
5056
+ "Kamu gerbang rantai pasok. Satu dependensi masuk lewat satu baris diff dan tak pernah",
5057
+ "diperiksa lagi seumur hidup proyek \u2014 pemeriksaan itu terjadi sekarang atau tidak sama sekali.",
5058
+ "",
5059
+ "Prosedur:",
5060
+ "1. Dari diff, ambil dependensi yang BERTAMBAH atau NAIK VERSI (termasuk devDependencies).",
5061
+ " Lockfile ikut dibaca: dependensi transitif baru yang besar juga temuan.",
5062
+ "2. Untuk tiap satu, periksa dan sebutkan sumbernya:",
5063
+ " - advisory/CVE yang diketahui untuk rentang versi itu;",
5064
+ " - tanggal rilis terakhir & tanda pemeliharaan (isu terbuka menumpuk, maintainer tunggal);",
5065
+ " - lisensi, dan apakah ia cocok dengan lisensi proyek ini;",
5066
+ " - ukuran pohon transitifnya;",
5067
+ " - apakah paket menjalankan skrip saat instalasi.",
5068
+ "3. Pertanyaan yang paling sering dilewati, dan tanyakan SELALU: apakah fungsi yang dipakai",
5069
+ " sudah tersedia di dependensi yang SUDAH ada di proyek ini, atau di runtime-nya? Cek dulu",
5070
+ " sebelum menerima. Satu dependensi yang tak jadi masuk lebih berharga daripada sepuluh",
5071
+ " yang diaudit.",
5072
+ "",
5073
+ "Gerbang bukti: klaim CVE atau lisensi WAJIB membawa URL sumbernya. Tanpa sumber, tulis 'tak",
5074
+ "terverifikasi' \u2014 jangan hilangkan, dan jangan naikkan jadi fakta.",
5075
+ "",
5076
+ "Bentuk laporan: per dependensi \u2014 aman \xB7 aman dengan catatan \xB7 tolak (+ penggantinya, atau",
5077
+ "cara mengerjakannya tanpa dependensi itu)."
5078
+ ].join("\n")
5079
+ }
5080
+ ];
5081
+ BUILTIN_AGENT_NAMES = BUILTIN_AGENTS.map((a) => a.name);
5082
+ }
5083
+ });
5084
+
4817
5085
  // ../shared/src/agent-catalog.ts
4818
5086
  var BUILTIN_AGENT_TOOLS;
4819
5087
  var init_agent_catalog = __esm({
@@ -4961,6 +5229,31 @@ var init_lead = __esm({
4961
5229
  }
4962
5230
  });
4963
5231
 
5232
+ // ../shared/src/handled-by.ts
5233
+ var zHandledByEntry, HANDLED_BY_MAX, zHandledBy, zHandledByView;
5234
+ var init_handled_by = __esm({
5235
+ "../shared/src/handled-by.ts"() {
5236
+ "use strict";
5237
+ init_zod();
5238
+ zHandledByEntry = external_exports.object({
5239
+ deviceId: external_exports.string().min(1),
5240
+ name: external_exports.string().min(1)
5241
+ });
5242
+ HANDLED_BY_MAX = 32;
5243
+ zHandledBy = external_exports.array(zHandledByEntry).max(HANDLED_BY_MAX).superRefine((list, ctx) => {
5244
+ const seen = /* @__PURE__ */ new Set();
5245
+ for (const e of list) {
5246
+ if (seen.has(e.deviceId)) {
5247
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: `deviceId duplikat: ${e.deviceId}` });
5248
+ return;
5249
+ }
5250
+ seen.add(e.deviceId);
5251
+ }
5252
+ });
5253
+ zHandledByView = zHandledByEntry.extend({ revoked: external_exports.boolean().default(false) });
5254
+ }
5255
+ });
5256
+
4964
5257
  // ../shared/src/prd-status.ts
4965
5258
  var PRD_STATUSES, zPrdStatus;
4966
5259
  var init_prd_status = __esm({
@@ -5033,7 +5326,7 @@ var init_cron_expr = __esm({
5033
5326
  });
5034
5327
 
5035
5328
  // ../shared/src/dto.ts
5036
- var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zChangePassword, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, UPDATE_RESTART_EXIT, zUpdateApplyBody, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
5329
+ var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, zUpdateApplyBody, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
5037
5330
  var init_dto = __esm({
5038
5331
  "../shared/src/dto.ts"() {
5039
5332
  "use strict";
@@ -5041,6 +5334,7 @@ var init_dto = __esm({
5041
5334
  init_entities();
5042
5335
  init_lead();
5043
5336
  init_auto_merge();
5337
+ init_handled_by();
5044
5338
  init_prd_status();
5045
5339
  init_enums();
5046
5340
  init_spec_source();
@@ -5087,6 +5381,8 @@ var init_dto = __esm({
5087
5381
  kind: zProjectKind,
5088
5382
  repoDir: external_exports.string().optional(),
5089
5383
  gitRemote: external_exports.string().optional(),
5384
+ // SPEC-880 · ADR-0135 · penanda "ditangani oleh" boleh di-set sejak awal (disync).
5385
+ handledBy: zHandledBy.optional(),
5090
5386
  desc: external_exports.string().default("")
5091
5387
  });
5092
5388
  zProjectId = external_exports.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "slug tak sah");
@@ -5096,6 +5392,10 @@ var init_dto = __esm({
5096
5392
  desc: external_exports.string().optional(),
5097
5393
  gitRemote: external_exports.string().optional(),
5098
5394
  // SPEC-213 · set git remote resmi project
5395
+ // SPEC-880 · ADR-0135 · daftar hanoman client pemegang project (DISYNC — beda dari repoDir &
5396
+ // opt-in di bawah yang lokal per-instance). `null` maupun `[]` = kosongkan. Digerbangi
5397
+ // `checkHandledBy`: deviceId wajib dikenal HANYA bila instance ini punya katalog device.
5398
+ handledBy: zHandledBy.nullable().optional(),
5099
5399
  repoDir: external_exports.string().nullable().optional(),
5100
5400
  // SPEC-217 · path default/server editable (null = kosongkan)
5101
5401
  schedulerOptIn: external_exports.boolean().optional(),
@@ -5171,6 +5471,10 @@ var init_dto = __esm({
5171
5471
  // SPEC-294 · opt-in scheduler otonom
5172
5472
  leadOptIn: external_exports.boolean().default(false),
5173
5473
  // SPEC-409 · ADR-0091 · opt-in hanoman-lead
5474
+ // SPEC-880 · ADR-0135 · penanda "ditangani oleh" yang SUDAH diperkaya: `name` = nama hidup bila
5475
+ // baris DeviceToken-nya ada di instance ini, else snapshot tersimpan; `revoked` diturunkan.
5476
+ // `[]` = belum ditetapkan — kolom NULL tak pernah bocor ke UI sebagai bentuk kedua "kosong".
5477
+ handledBy: zHandledByView.array().default([]),
5174
5478
  autoMerge: zAutoMerge.nullable().default(null)
5175
5479
  });
5176
5480
  zSchedulerQueueItem = external_exports.object({
@@ -5473,6 +5777,11 @@ var init_dto = __esm({
5473
5777
  zLogin = external_exports.object({ email: external_exports.string().email(), password: external_exports.string().min(1) });
5474
5778
  zSignup = external_exports.object({ email: external_exports.string().email(), password: external_exports.string().min(8) });
5475
5779
  zSetup = zSignup.extend({ setupToken: external_exports.string().min(1) });
5780
+ zSetupApply = external_exports.object({
5781
+ deployment: external_exports.enum(["local", "public"]),
5782
+ hardening: external_exports.boolean(),
5783
+ acknowledgedUnhardened: external_exports.boolean().optional()
5784
+ });
5476
5785
  zChangePassword = external_exports.object({
5477
5786
  currentPassword: external_exports.string().min(1),
5478
5787
  newPassword: external_exports.string().min(8)
@@ -5500,7 +5809,27 @@ var init_dto = __esm({
5500
5809
  });
5501
5810
  zAttest = external_exports.object({ note: external_exports.string().max(500).optional() });
5502
5811
  zRemediate = external_exports.object({ items: external_exports.array(external_exports.string()).min(1).max(64) });
5812
+ PROVISION_PROFILES = ["lab", "production"];
5813
+ COMPONENT_IDS = [
5814
+ "base",
5815
+ "node",
5816
+ "hanoman",
5817
+ "caddy",
5818
+ "podman",
5819
+ "agent-image",
5820
+ "claude",
5821
+ "codex",
5822
+ "gh"
5823
+ ];
5824
+ zProvision = external_exports.object({
5825
+ items: external_exports.array(external_exports.enum(COMPONENT_IDS)).min(1).max(16),
5826
+ profile: external_exports.enum(PROVISION_PROFILES),
5827
+ domain: external_exports.string().min(1).regex(HOST_RE).optional(),
5828
+ confirm: external_exports.boolean().optional(),
5829
+ force: external_exports.boolean().optional()
5830
+ });
5503
5831
  UPDATE_RESTART_EXIT = 75;
5832
+ CONFIG_RESTART_EXIT = 76;
5504
5833
  zUpdateApplyBody = external_exports.object({ confirm: external_exports.boolean().optional() });
5505
5834
  zTicketView = external_exports.object({
5506
5835
  id: external_exports.string(),
@@ -5716,6 +6045,11 @@ var init_api = __esm({
5716
6045
  terminalSession: (id) => `${API}/terminal/sessions/${id}`,
5717
6046
  terminalSteer: (id) => `${API}/terminal/sessions/${id}/steer`,
5718
6047
  terminalInterrupt: (id) => `${API}/terminal/sessions/${id}/interrupt`,
6048
+ // SPEC-899 · ADR-0142 · inbox keputusan. Di bawah prefix /terminal supaya ikut capability
6049
+ // `sessions` yang sudah ada — GET menurunkan cabang bacanya, POST cabang tulisnya, tanpa satu
6050
+ // baris pun perubahan di services/agent-capabilities.ts.
6051
+ terminalDialog: (id) => `${API}/terminal/sessions/${id}/dialog`,
6052
+ terminalDialogAnswer: (id) => `${API}/terminal/sessions/${id}/dialog/answer`,
5719
6053
  terminalPhases: (id) => `${API}/terminal/sessions/${id}/phases`,
5720
6054
  // SPEC-230 · review + integrate ber-skop sesi (sesi project-level PRD, tanpa Spec).
5721
6055
  sessionReview: (id) => `${API}/terminal/sessions/${id}/review`,
@@ -5747,6 +6081,11 @@ var init_api = __esm({
5747
6081
  vpsItemAttest: (id, itemId) => `${API}/vps/${id}/items/${itemId}/attest`,
5748
6082
  vpsRemediatePreview: (id) => `${API}/vps/${id}/remediate/preview`,
5749
6083
  vpsRemediate: (id) => `${API}/vps/${id}/remediate`,
6084
+ // SPEC-883 · provisioning berbasis katalog
6085
+ vpsComponents: () => `${API}/vps/components`,
6086
+ vpsProbe: (id) => `${API}/vps/${id}/probe`,
6087
+ vpsProvisionPreview: (id) => `${API}/vps/${id}/provision/preview`,
6088
+ vpsProvision: (id) => `${API}/vps/${id}/provision`,
5750
6089
  // SPEC-169 · auth
5751
6090
  authStatus: `${API}/auth/status`,
5752
6091
  authSetup: `${API}/auth/setup`,
@@ -5755,6 +6094,9 @@ var init_api = __esm({
5755
6094
  authUsers: `${API}/auth/users`,
5756
6095
  authUser: (id) => `${API}/auth/users/${id}`,
5757
6096
  authChangePassword: `${API}/auth/change-password`,
6097
+ // SPEC-884 · ADR-0139 · wizard setup awal
6098
+ setupStatus: `${API}/setup/status`,
6099
+ setupApply: `${API}/setup`,
5758
6100
  // SPEC-213 · device token + activity log
5759
6101
  deviceTokens: `${API}/device-tokens`,
5760
6102
  deviceToken: (id) => `${API}/device-tokens/${id}`,
@@ -6296,6 +6638,9 @@ var init_webhook = __esm({
6296
6638
  "gitRemote",
6297
6639
  "stack",
6298
6640
  "helpEnabled",
6641
+ // SPEC-880 · ADR-0135 · penerima harus bisa tahu mesin mana yang memegang project ini
6642
+ // tanpa mendiff dua amplop. Entrinya snapshot: [{deviceId,name}].
6643
+ "handledBy",
6299
6644
  "schedulerOptIn",
6300
6645
  "leadOptIn",
6301
6646
  "autoMerge",
@@ -6306,7 +6651,7 @@ var init_webhook = __esm({
6306
6651
  cascade: ["spec", "ticket", "customAgent", "githubIssue"],
6307
6652
  events: {
6308
6653
  created: { type: "project.created", label: "Project ditambah", when: "Project baru terdaftar di workspace." },
6309
- updated: { type: "project.updated", label: "Project diubah", when: "Nama, deskripsi, stack, remote, atau opt-in scheduler/lead/Help Center berubah." },
6654
+ updated: { type: "project.updated", label: "Project diubah", when: 'Nama, deskripsi, stack, remote, penanda "ditangani oleh", atau opt-in scheduler/lead/Help Center berubah.' },
6310
6655
  deleted: { type: "project.deleted", label: "Project dihapus", when: "Project dihapus. data.cascade menyebut jumlah anak yang ikut terhapus \u2014 anaknya sendiri TIDAK memancarkan deleted, sebab cascade dieksekusi SQLite, di luar jangkauan tap." }
6311
6656
  },
6312
6657
  sample: {
@@ -6317,6 +6662,7 @@ var init_webhook = __esm({
6317
6662
  gitRemote: "git@github.com:nafanesia/hanoman.git",
6318
6663
  stack: "ts",
6319
6664
  helpEnabled: false,
6665
+ handledBy: [{ deviceId: "clq0device1", name: "hm-dena" }],
6320
6666
  schedulerOptIn: true,
6321
6667
  leadOptIn: false,
6322
6668
  autoMerge: null,
@@ -7306,6 +7652,27 @@ var init_terminal_io = __esm({
7306
7652
  }
7307
7653
  });
7308
7654
 
7655
+ // ../shared/src/session-dialog.ts
7656
+ var zSessionDialogAnswer;
7657
+ var init_session_dialog = __esm({
7658
+ "../shared/src/session-dialog.ts"() {
7659
+ "use strict";
7660
+ init_zod();
7661
+ zSessionDialogAnswer = external_exports.object({
7662
+ screenHash: external_exports.string().min(1),
7663
+ choice: external_exports.number().int().positive().optional(),
7664
+ choices: external_exports.array(external_exports.number().int().positive()).max(32).optional(),
7665
+ text: external_exports.string().trim().min(1).max(16e3).optional()
7666
+ }).refine(
7667
+ (v) => !(v.choice !== void 0 && v.choices !== void 0),
7668
+ { message: "choice dan choices tak boleh dikirim bersamaan" }
7669
+ ).refine(
7670
+ (v) => v.choice !== void 0 || v.choices !== void 0 || v.text !== void 0,
7671
+ { message: "jawaban kosong" }
7672
+ );
7673
+ }
7674
+ });
7675
+
7309
7676
  // ../shared/src/index.ts
7310
7677
  var init_src = __esm({
7311
7678
  "../shared/src/index.ts"() {
@@ -7318,6 +7685,7 @@ var init_src = __esm({
7318
7685
  init_entities();
7319
7686
  init_agent();
7320
7687
  init_custom_agent();
7688
+ init_builtin_agents();
7321
7689
  init_agent_catalog();
7322
7690
  init_github();
7323
7691
  init_lead();
@@ -7337,10 +7705,12 @@ var init_src = __esm({
7337
7705
  init_webhook();
7338
7706
  init_mcp();
7339
7707
  init_auto_merge();
7708
+ init_handled_by();
7340
7709
  init_changelog();
7341
7710
  init_cron_expr();
7342
7711
  init_terminal_workspace();
7343
7712
  init_terminal_io();
7713
+ init_session_dialog();
7344
7714
  }
7345
7715
  });
7346
7716
 
@@ -7818,6 +8188,149 @@ var init_spawn_helper = __esm({
7818
8188
  }
7819
8189
  });
7820
8190
 
8191
+ // ../runner/src/config-env.ts
8192
+ import { chmodSync as chmodSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
8193
+ import { join as join4 } from "node:path";
8194
+ function configEnvPath(home) {
8195
+ return join4(home, CONFIG_ENV_FILE);
8196
+ }
8197
+ function parseConfigEnv(text) {
8198
+ const out = {};
8199
+ for (const raw of text.split("\n")) {
8200
+ const line = raw.trim();
8201
+ if (!line || line.startsWith("#")) continue;
8202
+ const eq = line.indexOf("=");
8203
+ if (eq <= 0) continue;
8204
+ out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
8205
+ }
8206
+ return out;
8207
+ }
8208
+ function readConfigEnv(home) {
8209
+ try {
8210
+ return parseConfigEnv(readFileSync2(configEnvPath(home), "utf8"));
8211
+ } catch {
8212
+ return {};
8213
+ }
8214
+ }
8215
+ var CONFIG_ENV_FILE;
8216
+ var init_config_env = __esm({
8217
+ "../runner/src/config-env.ts"() {
8218
+ "use strict";
8219
+ CONFIG_ENV_FILE = "config.env";
8220
+ }
8221
+ });
8222
+
8223
+ // ../runner/src/runtime-profile.ts
8224
+ function resolveHardening(env) {
8225
+ if (env.HANOMAN_HARDENING === "1") return true;
8226
+ return env.HANOMAN_SESSION_SANDBOX === "podman" || filled(env.HANOMAN_PUBLIC_ORIGINS) || filled(env.HANOMAN_TRUST_PROXY);
8227
+ }
8228
+ var filled;
8229
+ var init_runtime_profile = __esm({
8230
+ "../runner/src/runtime-profile.ts"() {
8231
+ "use strict";
8232
+ filled = (v) => !!v && v.trim() !== "";
8233
+ }
8234
+ });
8235
+
8236
+ // ../runner/src/sandbox-probe.ts
8237
+ import { execFileSync } from "node:child_process";
8238
+ import { accessSync, constants } from "node:fs";
8239
+ import { isAbsolute as isAbsolute2 } from "node:path";
8240
+ function trustProxyOk(raw) {
8241
+ const v = raw?.trim();
8242
+ if (!v) return false;
8243
+ if (/^[1-9]\d*$/.test(v)) return true;
8244
+ const values = v.split(",").map((s2) => s2.trim()).filter(Boolean);
8245
+ if (!values.length) return false;
8246
+ return !values.some((s2) => s2 === "true" || !s2.includes("/") && s2 !== "loopback");
8247
+ }
8248
+ function prerequisites(env, facts) {
8249
+ const network = env.HANOMAN_SESSION_NETWORK ?? "hanoman-egress";
8250
+ const credentialDir = env.HANOMAN_AGENT_CREDENTIAL_DIR;
8251
+ const scanner = env.HANOMAN_UPLOAD_SCANNER?.trim();
8252
+ return [
8253
+ {
8254
+ id: "podman",
8255
+ label: "Podman rootless",
8256
+ ok: !!facts.podman && facts.rootless,
8257
+ detail: facts.podman ? facts.rootless ? facts.podman : `${facts.podman} \u2014 berjalan rootful, butuh rootless` : "podman tak ada"
8258
+ },
8259
+ {
8260
+ id: "network",
8261
+ label: `Network "${network}"`,
8262
+ ok: facts.networkExists,
8263
+ detail: facts.networkExists ? network : `network ${network} belum dibuat`
8264
+ },
8265
+ {
8266
+ id: "egress-proxy",
8267
+ label: "Egress proxy",
8268
+ ok: filled2(env.HANOMAN_EGRESS_PROXY),
8269
+ detail: env.HANOMAN_EGRESS_PROXY ?? "HANOMAN_EGRESS_PROXY belum disetel"
8270
+ },
8271
+ {
8272
+ id: "credential-dir",
8273
+ label: "Dir credential agen",
8274
+ ok: filled2(credentialDir) && facts.credentialDirReadable,
8275
+ detail: !filled2(credentialDir) ? "HANOMAN_AGENT_CREDENTIAL_DIR belum disetel" : facts.credentialDirReadable ? credentialDir : `${credentialDir} \u2014 tak terbaca`
8276
+ },
8277
+ {
8278
+ id: "control-origin",
8279
+ label: "Control origin",
8280
+ ok: filled2(env.HANOMAN_CONTROL_ORIGINS),
8281
+ detail: env.HANOMAN_CONTROL_ORIGINS ?? "HANOMAN_CONTROL_ORIGINS belum disetel"
8282
+ },
8283
+ {
8284
+ id: "trust-proxy",
8285
+ label: "Trusted proxy hop/CIDR",
8286
+ ok: trustProxyOk(env.HANOMAN_TRUST_PROXY),
8287
+ detail: filled2(env.HANOMAN_TRUST_PROXY) ? trustProxyOk(env.HANOMAN_TRUST_PROXY) ? env.HANOMAN_TRUST_PROXY : "harus hop atau CIDR eksplisit" : "HANOMAN_TRUST_PROXY belum disetel"
8288
+ },
8289
+ {
8290
+ id: "upload-scanner",
8291
+ label: "Scanner upload",
8292
+ ok: !!scanner && isAbsolute2(scanner),
8293
+ detail: !scanner ? "HANOMAN_UPLOAD_SCANNER belum disetel" : isAbsolute2(scanner) ? scanner : `${scanner} \u2014 path harus absolut`
8294
+ }
8295
+ ];
8296
+ }
8297
+ function allReady(rows) {
8298
+ return rows.every((r) => r.ok);
8299
+ }
8300
+ function run(file, args) {
8301
+ try {
8302
+ return execFileSync(file, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
8303
+ } catch {
8304
+ return null;
8305
+ }
8306
+ }
8307
+ function collectProbeFacts(env) {
8308
+ const podman = run("podman", ["--version"]);
8309
+ const network = env.HANOMAN_SESSION_NETWORK ?? "hanoman-egress";
8310
+ const dir = env.HANOMAN_AGENT_CREDENTIAL_DIR;
8311
+ let credentialDirReadable = false;
8312
+ if (dir) {
8313
+ try {
8314
+ accessSync(dir, constants.R_OK);
8315
+ credentialDirReadable = true;
8316
+ } catch {
8317
+ }
8318
+ }
8319
+ return {
8320
+ podman,
8321
+ rootless: podman ? run("podman", ["info", "--format", "{{.Host.Security.Rootless}}"]) === "true" : false,
8322
+ networkExists: podman ? run("podman", ["network", "exists", network]) !== null : false,
8323
+ credentialDirReadable
8324
+ };
8325
+ }
8326
+ var filled2;
8327
+ var init_sandbox_probe = __esm({
8328
+ "../runner/src/sandbox-probe.ts"() {
8329
+ "use strict";
8330
+ filled2 = (v) => !!v && v.trim() !== "";
8331
+ }
8332
+ });
8333
+
7821
8334
  // ../runner/src/index.ts
7822
8335
  var init_src2 = __esm({
7823
8336
  "../runner/src/index.ts"() {
@@ -7838,28 +8351,31 @@ var init_src2 = __esm({
7838
8351
  init_skills();
7839
8352
  init_telegram_operator();
7840
8353
  init_spawn_helper();
8354
+ init_config_env();
8355
+ init_runtime_profile();
8356
+ init_sandbox_probe();
7841
8357
  }
7842
8358
  });
7843
8359
 
7844
8360
  // src/layout.ts
7845
- import { join as join4, resolve as resolve3 } from "node:path";
8361
+ import { join as join5, resolve as resolve3 } from "node:path";
7846
8362
  function resolveLayout(distDir2, exists) {
7847
8363
  const pkg = resolve3(distDir2, "..");
7848
- if (exists(join4(pkg, "prisma", "schema.prisma"))) {
8364
+ if (exists(join5(pkg, "prisma", "schema.prisma"))) {
7849
8365
  return {
7850
8366
  root: pkg,
7851
- schema: join4(pkg, "prisma", "schema.prisma"),
7852
- server: join4(pkg, "dist", "server.js"),
7853
- web: exists(join4(pkg, "web")) ? join4(pkg, "web") : null
8367
+ schema: join5(pkg, "prisma", "schema.prisma"),
8368
+ server: join5(pkg, "dist", "server.js"),
8369
+ web: exists(join5(pkg, "web")) ? join5(pkg, "web") : null
7854
8370
  };
7855
8371
  }
7856
8372
  const repo = resolve3(distDir2, "../..");
7857
- if (exists(join4(repo, "server", "prisma", "schema.prisma"))) {
8373
+ if (exists(join5(repo, "server", "prisma", "schema.prisma"))) {
7858
8374
  return {
7859
8375
  root: repo,
7860
- schema: join4(repo, "server", "prisma", "schema.prisma"),
7861
- server: join4(repo, "server", "dist", "server.js"),
7862
- web: exists(join4(repo, "src", "dist")) ? join4(repo, "src", "dist") : null
8376
+ schema: join5(repo, "server", "prisma", "schema.prisma"),
8377
+ server: join5(repo, "server", "dist", "server.js"),
8378
+ web: exists(join5(repo, "src", "dist")) ? join5(repo, "src", "dist") : null
7863
8379
  };
7864
8380
  }
7865
8381
  throw new Error(`hanoman: prisma/schema.prisma tak ditemukan dari ${distDir2} \u2014 instalasi rusak?`);
@@ -7878,7 +8394,7 @@ __export(update_exports, {
7878
8394
  default: () => update,
7879
8395
  planUpdate: () => planUpdate
7880
8396
  });
7881
- import { execFileSync } from "node:child_process";
8397
+ import { execFileSync as execFileSync2 } from "node:child_process";
7882
8398
  function planUpdate(current, latest) {
7883
8399
  if (!latest) return { action: "unknown", current, latest: null };
7884
8400
  return compareSemver(latest, current) > 0 ? { action: "install", current, latest } : { action: "up-to-date", current, latest };
@@ -7916,7 +8432,7 @@ async function update(argv, ctx) {
7916
8432
  return 0;
7917
8433
  }
7918
8434
  try {
7919
- execFileSync("npm", [...INSTALL_ARGS], { stdio: "inherit" });
8435
+ execFileSync2("npm", [...INSTALL_ARGS], { stdio: "inherit" });
7920
8436
  } catch {
7921
8437
  ctx.stderr("npm i -g gagal \u2014 jalankan manual (mungkin butuh sudo)\n");
7922
8438
  return 1;
@@ -7940,6 +8456,7 @@ var init_update = __esm({
7940
8456
  // src/commands/start.ts
7941
8457
  var start_exports = {};
7942
8458
  __export(start_exports, {
8459
+ MAX_CONFIG_RESTARTS: () => MAX_CONFIG_RESTARTS,
7943
8460
  MAX_UPDATE_RESTARTS: () => MAX_UPDATE_RESTARTS,
7944
8461
  applyMigrations: () => applyMigrations,
7945
8462
  default: () => start,
@@ -7950,30 +8467,31 @@ __export(start_exports, {
7950
8467
  parseStartArgs: () => parseStartArgs,
7951
8468
  planSupervisorStep: () => planSupervisorStep,
7952
8469
  prismaClientUsable: () => prismaClientUsable,
7953
- serverEnv: () => serverEnv
8470
+ serverEnv: () => serverEnv,
8471
+ spawnEnv: () => spawnEnv
7954
8472
  });
7955
- import { spawn, execFileSync as execFileSync2 } from "node:child_process";
8473
+ import { spawn, execFileSync as execFileSync3 } from "node:child_process";
7956
8474
  import { createRequire } from "node:module";
7957
- import { existsSync as existsSync3, mkdirSync, chmodSync as chmodSync2 } from "node:fs";
8475
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, chmodSync as chmodSync3 } from "node:fs";
7958
8476
  import { dirname as dirname4, resolve as resolvePath } from "node:path";
7959
8477
  import { fileURLToPath } from "node:url";
7960
8478
  function parseStartArgs(argv) {
7961
8479
  const out = { port: null, host: null, db: null, migrate: true };
7962
- const value = (i, flag, inline) => {
8480
+ const value = (i, flag2, inline) => {
7963
8481
  const v = inline ?? argv[i + 1];
7964
- if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} butuh nilai`);
8482
+ if (v === void 0 || v.startsWith("--")) throw new Error(`${flag2} butuh nilai`);
7965
8483
  return v;
7966
8484
  };
7967
8485
  for (let i = 0; i < argv.length; i++) {
7968
8486
  const raw = argv[i];
7969
8487
  const eq = raw.indexOf("=");
7970
- const flag = eq === -1 ? raw : raw.slice(0, eq);
8488
+ const flag2 = eq === -1 ? raw : raw.slice(0, eq);
7971
8489
  const inline = eq === -1 ? void 0 : raw.slice(eq + 1);
7972
- if (flag === "--no-migrate") {
8490
+ if (flag2 === "--no-migrate") {
7973
8491
  out.migrate = false;
7974
8492
  continue;
7975
8493
  }
7976
- if (flag === "--port") {
8494
+ if (flag2 === "--port") {
7977
8495
  const v = value(i, "--port", inline);
7978
8496
  const n2 = Number(v);
7979
8497
  if (!Number.isInteger(n2) || n2 <= 0) throw new Error(`--port harus angka, dapat "${v}"`);
@@ -7981,12 +8499,12 @@ function parseStartArgs(argv) {
7981
8499
  if (inline === void 0) i++;
7982
8500
  continue;
7983
8501
  }
7984
- if (flag === "--host") {
8502
+ if (flag2 === "--host") {
7985
8503
  out.host = value(i, "--host", inline);
7986
8504
  if (inline === void 0) i++;
7987
8505
  continue;
7988
8506
  }
7989
- if (flag === "--db") {
8507
+ if (flag2 === "--db") {
7990
8508
  out.db = value(i, "--db", inline);
7991
8509
  if (inline === void 0) i++;
7992
8510
  continue;
@@ -8001,7 +8519,7 @@ function distDir() {
8001
8519
  function runPrisma(args, dbUrl) {
8002
8520
  const prismaCli = prismaCliPath(createRequire(import.meta.url).resolve);
8003
8521
  try {
8004
- const out = execFileSync2(process.execPath, [prismaCli, ...args], {
8522
+ const out = execFileSync3(process.execPath, [prismaCli, ...args], {
8005
8523
  env: { ...process.env, DATABASE_URL: dbUrl },
8006
8524
  encoding: "utf8",
8007
8525
  stdio: ["ignore", "pipe", "pipe"]
@@ -8050,7 +8568,9 @@ async function ensurePrismaClient(schema, dbUrl, ctx) {
8050
8568
  function repairSpawnHelperEarly(ctx) {
8051
8569
  repairSpawnHelper(createRequire(import.meta.url).resolve, ctx.stdout);
8052
8570
  }
8053
- function planSupervisorStep(code, restartsUsed) {
8571
+ function planSupervisorStep(code, restartsUsed, configRestartsUsed = 0) {
8572
+ if (code === CONFIG_RESTART_EXIT)
8573
+ return configRestartsUsed >= MAX_CONFIG_RESTARTS ? { action: "exit", code } : { action: "restart" };
8054
8574
  if (code !== UPDATE_RESTART_EXIT) return { action: "exit", code };
8055
8575
  if (restartsUsed >= MAX_UPDATE_RESTARTS) return { action: "exit", code };
8056
8576
  return { action: "update" };
@@ -8068,14 +8588,20 @@ function serverEnv(o) {
8068
8588
  }
8069
8589
  function installLatest() {
8070
8590
  try {
8071
- execFileSync2("npm", [...INSTALL_ARGS], { stdio: "inherit" });
8591
+ execFileSync3("npm", [...INSTALL_ARGS], { stdio: "inherit" });
8072
8592
  return { ok: true };
8073
8593
  } catch (e) {
8074
8594
  return { ok: false, reason: e.message || "npm i -g gagal" };
8075
8595
  }
8076
8596
  }
8077
- function runServer(serverJs, env) {
8078
- const child = spawn(process.execPath, [serverJs], { stdio: "inherit", env: { ...process.env, ...env } });
8597
+ function spawnEnv(fileEnv, processEnv, server) {
8598
+ return { ...fileEnv, ...processEnv, ...server };
8599
+ }
8600
+ function runServer(serverJs, env, fileEnv) {
8601
+ const child = spawn(process.execPath, [serverJs], {
8602
+ stdio: "inherit",
8603
+ env: spawnEnv(fileEnv, process.env, env)
8604
+ });
8079
8605
  const handlers = ["SIGINT", "SIGTERM"].map((sig) => [sig, () => child.kill(sig)]);
8080
8606
  for (const [sig, h] of handlers) process.on(sig, h);
8081
8607
  return new Promise((res) => child.on("exit", (code) => {
@@ -8107,9 +8633,9 @@ async function start(argv, ctx) {
8107
8633
  if (notice) ctx.stderr(`${notice}
8108
8634
  `);
8109
8635
  const home = resolveHome(ctx.env);
8110
- mkdirSync(home, { recursive: true, mode: 448 });
8111
- chmodSync2(home, 448);
8112
- mkdirSync(dirname4(dbFilePath(dbUrl)), { recursive: true, mode: 448 });
8636
+ mkdirSync2(home, { recursive: true, mode: 448 });
8637
+ chmodSync3(home, 448);
8638
+ mkdirSync2(dirname4(dbFilePath(dbUrl)), { recursive: true, mode: 448 });
8113
8639
  if (!existsSync3(layout.server)) {
8114
8640
  ctx.stderr(`hanoman: bundle server tak ada di ${layout.server} \u2014 jalankan \`pnpm build\` dulu
8115
8641
  `);
@@ -8130,23 +8656,34 @@ ${hint}
8130
8656
  return 1;
8131
8657
  }
8132
8658
  }
8133
- if (existsSync3(dbFilePath(dbUrl))) chmodSync2(dbFilePath(dbUrl), 384);
8659
+ if (existsSync3(dbFilePath(dbUrl))) chmodSync3(dbFilePath(dbUrl), 384);
8134
8660
  const port = opts.port ?? Number(ctx.env.PORT ?? 8787);
8135
8661
  const host = opts.host ?? ctx.env.HOST ?? "127.0.0.1";
8136
8662
  ctx.stdout(`hanoman \xB7 http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}
8137
8663
  `);
8138
8664
  const env = serverEnv({ dbUrl, port, host, home, web: layout.web ?? null });
8139
8665
  let restartsUsed = 0;
8666
+ let configRestartsUsed = 0;
8140
8667
  for (; ; ) {
8141
- const code = await runServer(layout.server, env);
8142
- const step = planSupervisorStep(code, restartsUsed);
8668
+ const code = await runServer(layout.server, env, readConfigEnv(home));
8669
+ const step = planSupervisorStep(code, restartsUsed, configRestartsUsed);
8143
8670
  if (step.action === "exit") {
8144
8671
  if (code === UPDATE_RESTART_EXIT) {
8145
8672
  ctx.stderr(`hanoman: jatah update-restart (${MAX_UPDATE_RESTARTS}) habis \u2014 keluar tanpa memasang
8673
+ `);
8674
+ }
8675
+ if (code === CONFIG_RESTART_EXIT) {
8676
+ ctx.stderr(`hanoman: jatah restart-konfigurasi (${MAX_CONFIG_RESTARTS}) habis \u2014 keluar
8146
8677
  `);
8147
8678
  }
8148
8679
  return step.code;
8149
8680
  }
8681
+ if (step.action === "restart") {
8682
+ configRestartsUsed++;
8683
+ ctx.stdout(`hanoman \xB7 konfigurasi berubah; menjalankan ulang (${configRestartsUsed}/${MAX_CONFIG_RESTARTS})
8684
+ `);
8685
+ continue;
8686
+ }
8150
8687
  restartsUsed++;
8151
8688
  ctx.stdout(`hanoman \xB7 memasang versi terbaru dari npm (${restartsUsed}/${MAX_UPDATE_RESTARTS})
8152
8689
  `);
@@ -8176,7 +8713,7 @@ ${hint}
8176
8713
  ctx.stdout("hanoman \xB7 terpasang; menjalankan ulang\n");
8177
8714
  }
8178
8715
  }
8179
- var MAX_UPDATE_RESTARTS;
8716
+ var MAX_UPDATE_RESTARTS, MAX_CONFIG_RESTARTS;
8180
8717
  var init_start = __esm({
8181
8718
  "src/commands/start.ts"() {
8182
8719
  "use strict";
@@ -8185,6 +8722,7 @@ var init_start = __esm({
8185
8722
  init_layout();
8186
8723
  init_update();
8187
8724
  MAX_UPDATE_RESTARTS = 5;
8725
+ MAX_CONFIG_RESTARTS = 5;
8188
8726
  }
8189
8727
  });
8190
8728
 
@@ -8194,8 +8732,8 @@ __export(doctor_exports, {
8194
8732
  default: () => doctor,
8195
8733
  doctorReport: () => doctorReport
8196
8734
  });
8197
- import { execFileSync as execFileSync3 } from "node:child_process";
8198
- import { accessSync, constants, existsSync as existsSync4 } from "node:fs";
8735
+ import { execFileSync as execFileSync4 } from "node:child_process";
8736
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync4 } from "node:fs";
8199
8737
  import { dirname as dirname5, resolve as resolve4 } from "node:path";
8200
8738
  function doctorReport(p) {
8201
8739
  const major = Number(/^v?(\d+)/.exec(p.node)?.[1] ?? 0);
@@ -8244,7 +8782,7 @@ function writable(path) {
8244
8782
  for (let p = resolve4(path); ; ) {
8245
8783
  if (existsSync4(p)) {
8246
8784
  try {
8247
- accessSync(p, constants.W_OK);
8785
+ accessSync2(p, constants2.W_OK);
8248
8786
  return true;
8249
8787
  } catch {
8250
8788
  return false;
@@ -8257,7 +8795,7 @@ function writable(path) {
8257
8795
  }
8258
8796
  function version(bin, args) {
8259
8797
  try {
8260
- return execFileSync3(bin, args, { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0] ?? null;
8798
+ return execFileSync4(bin, args, { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0] ?? null;
8261
8799
  } catch {
8262
8800
  return null;
8263
8801
  }
@@ -8287,21 +8825,10 @@ async function doctor(_argv, ctx) {
8287
8825
  const s2 = scanAgentSkills(a, ctx.env);
8288
8826
  return methodStatus(method, a, { skills: s2.skills.map((k) => k.id), packages: s2.packages });
8289
8827
  });
8290
- const podman = version("podman", ["--version"]);
8291
- const sandboxRequired = ctx.env.NODE_ENV === "production" || !!ctx.env.HANOMAN_PUBLIC_ORIGINS;
8292
- const rootless = podman ? version("podman", ["info", "--format", "{{.Host.Security.Rootless}}"]) === "true" : false;
8293
- const credentialDir = ctx.env.HANOMAN_AGENT_CREDENTIAL_DIR;
8294
- let credentialsReadable = false;
8295
- if (credentialDir) {
8296
- try {
8297
- accessSync(credentialDir, constants.R_OK);
8298
- credentialsReadable = true;
8299
- } catch {
8300
- }
8301
- }
8302
- const network = ctx.env.HANOMAN_SESSION_NETWORK ?? "hanoman-egress";
8303
- const networkReady = podman ? version("podman", ["network", "exists", network]) !== null : false;
8304
- const sandboxReady = ctx.env.HANOMAN_SESSION_SANDBOX === "podman" && rootless && credentialsReadable && !!ctx.env.HANOMAN_EGRESS_PROXY && networkReady;
8828
+ const podmanFacts = collectProbeFacts(ctx.env);
8829
+ const podman = podmanFacts.podman;
8830
+ const sandboxRequired = resolveHardening(ctx.env);
8831
+ const sandboxReady = allReady(prerequisites(ctx.env, podmanFacts));
8305
8832
  const r = doctorReport({
8306
8833
  node: process.version,
8307
8834
  git: version("git", ["--version"]),
@@ -17217,13 +17744,13 @@ function fillCacheFields(method, result) {
17217
17744
  const provided = result;
17218
17745
  const ttlMs = isValidCacheTtlMs(provided["ttlMs"]) ? provided["ttlMs"] : resolveTtlMs(fallback);
17219
17746
  const cacheScope = isValidCacheScope(provided["cacheScope"]) ? provided["cacheScope"] : resolveCacheScope(fallback);
17220
- const filled = {
17747
+ const filled3 = {
17221
17748
  ...provided,
17222
17749
  ttlMs,
17223
17750
  cacheScope
17224
17751
  };
17225
- delete filled[RESULT_CACHE_HINT_FALLBACK];
17226
- return filled;
17752
+ delete filled3[RESULT_CACHE_HINT_FALLBACK];
17753
+ return filled3;
17227
17754
  }
17228
17755
  function isPlainObject$5(value) {
17229
17756
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -20225,9 +20752,9 @@ var init_ajvProvider_CEoC_sr = __esm({
20225
20752
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
20226
20753
  return `${varKind} ${this.name}${rhs};` + _n;
20227
20754
  }
20228
- optimizeNames(names, constants2) {
20755
+ optimizeNames(names, constants3) {
20229
20756
  if (!names[this.name.str]) return;
20230
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
20757
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants3);
20231
20758
  return this;
20232
20759
  }
20233
20760
  get names() {
@@ -20244,9 +20771,9 @@ var init_ajvProvider_CEoC_sr = __esm({
20244
20771
  render({ _n }) {
20245
20772
  return `${this.lhs} = ${this.rhs};` + _n;
20246
20773
  }
20247
- optimizeNames(names, constants2) {
20774
+ optimizeNames(names, constants3) {
20248
20775
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
20249
- this.rhs = optimizeExpr(this.rhs, names, constants2);
20776
+ this.rhs = optimizeExpr(this.rhs, names, constants3);
20250
20777
  return this;
20251
20778
  }
20252
20779
  get names() {
@@ -20305,8 +20832,8 @@ var init_ajvProvider_CEoC_sr = __esm({
20305
20832
  optimizeNodes() {
20306
20833
  return `${this.code}` ? this : void 0;
20307
20834
  }
20308
- optimizeNames(names, constants2) {
20309
- this.code = optimizeExpr(this.code, names, constants2);
20835
+ optimizeNames(names, constants3) {
20836
+ this.code = optimizeExpr(this.code, names, constants3);
20310
20837
  return this;
20311
20838
  }
20312
20839
  get names() {
@@ -20332,12 +20859,12 @@ var init_ajvProvider_CEoC_sr = __esm({
20332
20859
  }
20333
20860
  return nodes.length > 0 ? this : void 0;
20334
20861
  }
20335
- optimizeNames(names, constants2) {
20862
+ optimizeNames(names, constants3) {
20336
20863
  const { nodes } = this;
20337
20864
  let i = nodes.length;
20338
20865
  while (i--) {
20339
20866
  const n2 = nodes[i];
20340
- if (n2.optimizeNames(names, constants2)) continue;
20867
+ if (n2.optimizeNames(names, constants3)) continue;
20341
20868
  subtractNames(names, n2.names);
20342
20869
  nodes.splice(i, 1);
20343
20870
  }
@@ -20384,11 +20911,11 @@ var init_ajvProvider_CEoC_sr = __esm({
20384
20911
  if (cond === false || !this.nodes.length) return void 0;
20385
20912
  return this;
20386
20913
  }
20387
- optimizeNames(names, constants2) {
20914
+ optimizeNames(names, constants3) {
20388
20915
  var _a3;
20389
- this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants2);
20390
- if (!(super.optimizeNames(names, constants2) || this.else)) return;
20391
- this.condition = optimizeExpr(this.condition, names, constants2);
20916
+ this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants3);
20917
+ if (!(super.optimizeNames(names, constants3) || this.else)) return;
20918
+ this.condition = optimizeExpr(this.condition, names, constants3);
20392
20919
  return this;
20393
20920
  }
20394
20921
  get names() {
@@ -20410,9 +20937,9 @@ var init_ajvProvider_CEoC_sr = __esm({
20410
20937
  render(opts) {
20411
20938
  return `for(${this.iteration})` + super.render(opts);
20412
20939
  }
20413
- optimizeNames(names, constants2) {
20414
- if (!super.optimizeNames(names, constants2)) return;
20415
- this.iteration = optimizeExpr(this.iteration, names, constants2);
20940
+ optimizeNames(names, constants3) {
20941
+ if (!super.optimizeNames(names, constants3)) return;
20942
+ this.iteration = optimizeExpr(this.iteration, names, constants3);
20416
20943
  return this;
20417
20944
  }
20418
20945
  get names() {
@@ -20447,9 +20974,9 @@ var init_ajvProvider_CEoC_sr = __esm({
20447
20974
  render(opts) {
20448
20975
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
20449
20976
  }
20450
- optimizeNames(names, constants2) {
20451
- if (!super.optimizeNames(names, constants2)) return;
20452
- this.iterable = optimizeExpr(this.iterable, names, constants2);
20977
+ optimizeNames(names, constants3) {
20978
+ if (!super.optimizeNames(names, constants3)) return;
20979
+ this.iterable = optimizeExpr(this.iterable, names, constants3);
20453
20980
  return this;
20454
20981
  }
20455
20982
  get names() {
@@ -20488,11 +21015,11 @@ var init_ajvProvider_CEoC_sr = __esm({
20488
21015
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
20489
21016
  return this;
20490
21017
  }
20491
- optimizeNames(names, constants2) {
21018
+ optimizeNames(names, constants3) {
20492
21019
  var _a3, _b;
20493
- super.optimizeNames(names, constants2);
20494
- (_a3 = this.catch) === null || _a3 === void 0 || _a3.optimizeNames(names, constants2);
20495
- (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
21020
+ super.optimizeNames(names, constants3);
21021
+ (_a3 = this.catch) === null || _a3 === void 0 || _a3.optimizeNames(names, constants3);
21022
+ (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants3);
20496
21023
  return this;
20497
21024
  }
20498
21025
  get names() {
@@ -20741,7 +21268,7 @@ var init_ajvProvider_CEoC_sr = __esm({
20741
21268
  function addExprNames(names, from) {
20742
21269
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
20743
21270
  }
20744
- function optimizeExpr(expr, names, constants2) {
21271
+ function optimizeExpr(expr, names, constants3) {
20745
21272
  if (expr instanceof code_1.Name) return replaceName(expr);
20746
21273
  if (!canOptimize(expr)) return expr;
20747
21274
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -20751,13 +21278,13 @@ var init_ajvProvider_CEoC_sr = __esm({
20751
21278
  return items;
20752
21279
  }, []));
20753
21280
  function replaceName(n2) {
20754
- const c = constants2[n2.str];
21281
+ const c = constants3[n2.str];
20755
21282
  if (c === void 0 || names[n2.str] !== 1) return n2;
20756
21283
  delete names[n2.str];
20757
21284
  return c;
20758
21285
  }
20759
21286
  function canOptimize(e) {
20760
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
21287
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants3[c.str] !== void 0);
20761
21288
  }
20762
21289
  }
20763
21290
  function subtractNames(names, from) {
@@ -29122,8 +29649,8 @@ __export(mcp_exports, {
29122
29649
  agentTokenPath: () => agentTokenPath,
29123
29650
  default: () => mcp
29124
29651
  });
29125
- import { readFileSync as readFileSync2 } from "node:fs";
29126
- import { join as join6 } from "node:path";
29652
+ import { readFileSync as readFileSync3 } from "node:fs";
29653
+ import { join as join7 } from "node:path";
29127
29654
  async function mcp(argv, ctx) {
29128
29655
  const cfg = resolveMcpConfig(argv, ctx.env, () => tokenFile(ctx.env));
29129
29656
  const say = (s2) => ctx.stderr(redactToken(s2, cfg.token) + "\n");
@@ -29154,10 +29681,10 @@ var init_mcp2 = __esm({
29154
29681
  init_client();
29155
29682
  init_server();
29156
29683
  init_redact();
29157
- agentTokenPath = (env) => join6(resolveHome(env), "agent-token");
29684
+ agentTokenPath = (env) => join7(resolveHome(env), "agent-token");
29158
29685
  tokenFile = (env) => {
29159
29686
  try {
29160
- return readFileSync2(agentTokenPath(env), "utf8");
29687
+ return readFileSync3(agentTokenPath(env), "utf8");
29161
29688
  } catch {
29162
29689
  return null;
29163
29690
  }
@@ -29165,6 +29692,89 @@ var init_mcp2 = __esm({
29165
29692
  }
29166
29693
  });
29167
29694
 
29695
+ // src/commands/provision.ts
29696
+ var provision_exports = {};
29697
+ __export(provision_exports, {
29698
+ default: () => provisionCmd,
29699
+ localScriptPath: () => localScriptPath,
29700
+ parseProvisionArgs: () => parseProvisionArgs,
29701
+ scriptEnv: () => scriptEnv
29702
+ });
29703
+ import { spawn as spawn2 } from "node:child_process";
29704
+ import { existsSync as existsSync5 } from "node:fs";
29705
+ import { join as join8 } from "node:path";
29706
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
29707
+ function parseProvisionArgs(args) {
29708
+ const probe = args.includes("--probe");
29709
+ const items = (flag(args, "with") ?? "").split(",").map((s2) => s2.trim()).filter(Boolean);
29710
+ const profile = flag(args, "profile") ?? "lab";
29711
+ const domain = flag(args, "domain");
29712
+ if (!PROFILES.includes(profile)) return { error: `profil tak dikenal: ${profile}` };
29713
+ if (!probe && items.length === 0) return { error: "sebutkan komponen dengan --with=a,b" };
29714
+ return {
29715
+ mode: probe ? "probe" : "apply",
29716
+ items,
29717
+ profile,
29718
+ domain,
29719
+ dryRun: args.includes("--dry-run"),
29720
+ yes: args.includes("--yes")
29721
+ };
29722
+ }
29723
+ function scriptEnv(a) {
29724
+ return {
29725
+ MODE: a.mode,
29726
+ ...a.mode === "apply" ? { ITEMS: a.items.join(","), PROFILE: a.profile } : {},
29727
+ ...a.mode === "apply" && a.domain ? { DOMAIN: a.domain } : {},
29728
+ ...a.mode === "apply" && a.dryRun ? { DRY_RUN: "1" } : {}
29729
+ };
29730
+ }
29731
+ function localScriptPath(cwd = process.cwd()) {
29732
+ const packed = fileURLToPath2(new URL("../scripts/vps/provision.sh", import.meta.url));
29733
+ if (existsSync5(packed)) return packed;
29734
+ return join8(cwd, "server", "scripts", "vps", "provision.sh");
29735
+ }
29736
+ function runScript(env, ctx) {
29737
+ return new Promise((resolve5) => {
29738
+ const p = spawn2("bash", [localScriptPath(ctx.cwd)], { env: { ...process.env, ...env } });
29739
+ p.stdout.on("data", (d) => ctx.stdout(String(d)));
29740
+ p.stderr.on("data", (d) => ctx.stderr(String(d)));
29741
+ p.on("close", (code) => resolve5(code ?? 1));
29742
+ p.on("error", (e) => {
29743
+ ctx.stderr(`${e}
29744
+ `);
29745
+ resolve5(127);
29746
+ });
29747
+ });
29748
+ }
29749
+ async function provisionCmd(args, ctx) {
29750
+ const parsed = parseProvisionArgs(args);
29751
+ if ("error" in parsed) {
29752
+ ctx.stderr(`${parsed.error}
29753
+ `);
29754
+ return 1;
29755
+ }
29756
+ if (parsed.mode === "probe" || parsed.dryRun) return runScript(scriptEnv(parsed), ctx);
29757
+ if (!parsed.yes) {
29758
+ ctx.stdout("Rencana (dry-run):\n");
29759
+ await runScript({ ...scriptEnv(parsed), DRY_RUN: "1" }, ctx);
29760
+ ctx.stdout("Lanjutkan? [y/N] ");
29761
+ const answer = (await ctx.readStdin?.() ?? "").trim().toLowerCase();
29762
+ if (answer !== "y" && answer !== "ya") {
29763
+ ctx.stderr("dibatalkan\n");
29764
+ return 1;
29765
+ }
29766
+ }
29767
+ return runScript(scriptEnv(parsed), ctx);
29768
+ }
29769
+ var PROFILES, flag;
29770
+ var init_provision = __esm({
29771
+ "src/commands/provision.ts"() {
29772
+ "use strict";
29773
+ PROFILES = ["lab", "production"];
29774
+ flag = (args, name) => args.find((a) => a.startsWith(`--${name}=`))?.split("=").slice(1).join("=");
29775
+ }
29776
+ });
29777
+
29168
29778
  // src/commands/migrate-pg.ts
29169
29779
  var migrate_pg_exports = {};
29170
29780
  __export(migrate_pg_exports, {
@@ -29177,7 +29787,7 @@ __export(migrate_pg_exports, {
29177
29787
  migrationSteps: () => migrationSteps,
29178
29788
  parseMigrateArgs: () => parseMigrateArgs
29179
29789
  });
29180
- import { existsSync as existsSync5 } from "node:fs";
29790
+ import { existsSync as existsSync6 } from "node:fs";
29181
29791
  import { dirname as dirname6, resolve as resolvePath2 } from "node:path";
29182
29792
  function coerceInt8(rows, fields, model) {
29183
29793
  const cols = fields.filter((f) => f.dataTypeID === INT8_OID).map((f) => f.name);
@@ -29245,7 +29855,7 @@ async function migratePg(argv, ctx) {
29245
29855
  let layout;
29246
29856
  let dbUrl;
29247
29857
  try {
29248
- layout = resolveLayout(distDir(), existsSync5);
29858
+ layout = resolveLayout(distDir(), existsSync6);
29249
29859
  dbUrl = opts.to ? `file:${resolvePath2(opts.to)}` : resolveDbUrl(ctx.env, dirname6(layout.schema));
29250
29860
  } catch (e) {
29251
29861
  ctx.stderr(`${e.message}
@@ -29395,11 +30005,11 @@ var init_migrate_pg = __esm({
29395
30005
  });
29396
30006
 
29397
30007
  // src/config.ts
29398
- import { readFileSync as readFileSync3, existsSync as existsSync6 } from "node:fs";
29399
- import { join as join7 } from "node:path";
30008
+ import { readFileSync as readFileSync4, existsSync as existsSync7 } from "node:fs";
30009
+ import { join as join9 } from "node:path";
29400
30010
  function loadConfig(repoRoot) {
29401
- const p = join7(repoRoot, "hanoman.config.json");
29402
- const raw = existsSync6(p) ? JSON.parse(readFileSync3(p, "utf8")) : {};
30011
+ const p = join9(repoRoot, "hanoman.config.json");
30012
+ const raw = existsSync7(p) ? JSON.parse(readFileSync4(p, "utf8")) : {};
29403
30013
  return zHanomanConfig.parse(raw);
29404
30014
  }
29405
30015
  var init_config3 = __esm({
@@ -29411,12 +30021,12 @@ var init_config3 = __esm({
29411
30021
 
29412
30022
  // src/repo.ts
29413
30023
  import { spawnSync } from "node:child_process";
29414
- import { join as join8 } from "node:path";
30024
+ import { join as join10 } from "node:path";
29415
30025
  function resolveRepo(cwd) {
29416
30026
  const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
29417
30027
  const root = r.status === 0 ? r.stdout.trim() : cwd;
29418
30028
  const { docsDir } = loadConfig(root);
29419
- return { root, docsDir, indexPath: join8(root, docsDir, "README.md") };
30029
+ return { root, docsDir, indexPath: join10(root, docsDir, "README.md") };
29420
30030
  }
29421
30031
  var init_repo = __esm({
29422
30032
  "src/repo.ts"() {
@@ -29426,10 +30036,10 @@ var init_repo = __esm({
29426
30036
  });
29427
30037
 
29428
30038
  // src/docs-model.ts
29429
- import { readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
29430
- import { join as join9, relative } from "node:path";
30039
+ import { readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
30040
+ import { join as join11, relative } from "node:path";
29431
30041
  function parseIndex(indexPath) {
29432
- const md = readFileSync4(indexPath, "utf8");
30042
+ const md = readFileSync5(indexPath, "utf8");
29433
30043
  const out = /* @__PURE__ */ new Set();
29434
30044
  for (const m of md.matchAll(LINK_RE2)) {
29435
30045
  let t = m[1].trim();
@@ -29444,7 +30054,7 @@ function walkDocs(docsRoot) {
29444
30054
  const walk = (dir) => {
29445
30055
  for (const name of readdirSync3(dir)) {
29446
30056
  if (name.startsWith(".")) continue;
29447
- const abs = join9(dir, name);
30057
+ const abs = join11(dir, name);
29448
30058
  if (statSync2(abs).isDirectory()) walk(abs);
29449
30059
  else out.push(relative(docsRoot, abs).split("\\").join("/"));
29450
30060
  }
@@ -29476,17 +30086,17 @@ var init_docs_model = __esm({
29476
30086
  });
29477
30087
 
29478
30088
  // src/verify.ts
29479
- import { join as join10 } from "node:path";
29480
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
30089
+ import { join as join12 } from "node:path";
30090
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
29481
30091
  function scanCoverage(cwd) {
29482
30092
  const { root, docsDir, indexPath } = resolveRepo(cwd);
29483
- const docsRoot = join10(root, docsDir);
29484
- if (!existsSync7(docsRoot)) return { coverage: 100, cats: [] };
29485
- if (!existsSync7(indexPath)) throw new Error(`index Source of Truth tidak ada: ${indexPath}`);
30093
+ const docsRoot = join12(root, docsDir);
30094
+ if (!existsSync8(docsRoot)) return { coverage: 100, cats: [] };
30095
+ if (!existsSync8(indexPath)) throw new Error(`index Source of Truth tidak ada: ${indexPath}`);
29486
30096
  const corpus = walkDocs(docsRoot);
29487
30097
  const read = (rel) => {
29488
30098
  try {
29489
- return readFileSync5(join10(docsRoot, rel), "utf8");
30099
+ return readFileSync6(join12(docsRoot, rel), "utf8");
29490
30100
  } catch {
29491
30101
  return null;
29492
30102
  }
@@ -29529,9 +30139,9 @@ var init_docs_scan = __esm({
29529
30139
  });
29530
30140
 
29531
30141
  // src/index-edit.ts
29532
- import { readFileSync as readFileSync6, writeFileSync } from "node:fs";
30142
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
29533
30143
  function addLink(indexPath, relPath, category) {
29534
- let md = readFileSync6(indexPath, "utf8");
30144
+ let md = readFileSync7(indexPath, "utf8");
29535
30145
  if (md.includes(`(${relPath})`)) return;
29536
30146
  const base = relPath.split("/").pop().replace(/\.md$/, "");
29537
30147
  const line = `- [${base}](${relPath})`;
@@ -29546,7 +30156,7 @@ ${head}
29546
30156
  ${line}
29547
30157
  `;
29548
30158
  }
29549
- writeFileSync(indexPath, md);
30159
+ writeFileSync2(indexPath, md);
29550
30160
  }
29551
30161
  var init_index_edit = __esm({
29552
30162
  "src/index-edit.ts"() {
@@ -29560,23 +30170,23 @@ __export(docs_index_exports, {
29560
30170
  default: () => docs_index_default
29561
30171
  });
29562
30172
  import { parseArgs as parseArgs2 } from "node:util";
29563
- import { join as join11 } from "node:path";
29564
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
30173
+ import { join as join13 } from "node:path";
30174
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
29565
30175
  async function docs_index_default(args, ctx) {
29566
30176
  const { values } = parseArgs2({ args, options: { check: { type: "boolean" }, fix: { type: "boolean" } }, allowPositionals: true });
29567
30177
  const { root, docsDir, indexPath } = resolveRepo(ctx.cwd);
29568
- const docsRoot = join11(root, docsDir);
30178
+ const docsRoot = join13(root, docsDir);
29569
30179
  const corpus = walkDocs(docsRoot);
29570
30180
  const read = (rel) => {
29571
30181
  try {
29572
- return readFileSync7(join11(docsRoot, rel), "utf8");
30182
+ return readFileSync8(join13(docsRoot, rel), "utf8");
29573
30183
  } catch {
29574
30184
  return null;
29575
30185
  }
29576
30186
  };
29577
30187
  const linked = linkedSetFrom(INDEX_NAME, corpus, read);
29578
30188
  const unlinked = corpus.filter((f) => f !== INDEX_NAME && !linked.has(f));
29579
- const dangling = [...parseIndex(indexPath)].filter((p) => !existsSync8(join11(docsRoot, p)));
30189
+ const dangling = [...parseIndex(indexPath)].filter((p) => !existsSync9(join13(docsRoot, p)));
29580
30190
  if (values.fix) {
29581
30191
  for (const f of unlinked) addLink(indexPath, f, f.split("/")[0]);
29582
30192
  ctx.stdout(`linked ${unlinked.length} doc(s)
@@ -29629,7 +30239,7 @@ var init_docs_link = __esm({
29629
30239
  });
29630
30240
 
29631
30241
  // src/release/pack.ts
29632
- import { join as join12 } from "node:path";
30242
+ import { join as join14 } from "node:path";
29633
30243
  function packageJsonFor(version3, deps) {
29634
30244
  return {
29635
30245
  name: PKG_NAME,
@@ -29652,7 +30262,7 @@ function packageJsonFor(version3, deps) {
29652
30262
  prepublishOnly: "node dist/cli.js __verify"
29653
30263
  },
29654
30264
  engines: { node: ">=20" },
29655
- files: ["bin", "dist", "web", "prisma", "docs", "README.md", "LICENSE"],
30265
+ files: ["bin", "dist", "web", "prisma", "docs", "scripts", "README.md", "LICENSE"],
29656
30266
  dependencies: deps,
29657
30267
  // MIT: paket ini didistribusikan publik supaya orang `npm i -g`. `UNLICENSED` berarti "tak ada
29658
30268
  // izin pakai" — 0.1.0 terbit dengan kontradiksi itu, dan versi terbit tak bisa diperbaiki.
@@ -29667,17 +30277,19 @@ function verifyPackedDeps(pkg) {
29667
30277
  }
29668
30278
  function copyPlan(repo) {
29669
30279
  return [
29670
- { from: join12(repo, "server/dist/server.js"), to: "dist/server.js" },
29671
- { from: join12(repo, "server/dist/build-info.json"), to: "dist/build-info.json" },
29672
- { from: join12(repo, "cli/dist/hanoman.js"), to: "dist/cli.js" },
29673
- { from: join12(repo, "src/dist"), to: "web", dir: true },
29674
- { from: join12(repo, "server/prisma/schema.prisma"), to: "prisma/schema.prisma" },
29675
- { from: join12(repo, "server/prisma/migrations"), to: "prisma/migrations", dir: true },
29676
- { from: join12(repo, "internal/docs/operations/npm-readme.md"), to: "README.md" },
30280
+ { from: join14(repo, "server/dist/server.js"), to: "dist/server.js" },
30281
+ { from: join14(repo, "server/dist/build-info.json"), to: "dist/build-info.json" },
30282
+ { from: join14(repo, "cli/dist/hanoman.js"), to: "dist/cli.js" },
30283
+ { from: join14(repo, "src/dist"), to: "web", dir: true },
30284
+ { from: join14(repo, "server/prisma/schema.prisma"), to: "prisma/schema.prisma" },
30285
+ { from: join14(repo, "server/prisma/migrations"), to: "prisma/migrations", dir: true },
30286
+ { from: join14(repo, "internal/docs/operations/npm-readme.md"), to: "README.md" },
29677
30287
  // SPEC-489 · dibaca runtime oleh pickGuideFile (kandidat `<pkg>/docs/…`). Bukan README:
29678
30288
  // README paket adalah npm-readme.md (berhadapan-MANUSIA), ini naskah berhadapan-AGEN.
29679
- { from: join12(repo, "docs/agent-integration.md"), to: "docs/agent-integration.md" },
29680
- { from: join12(repo, "LICENSE"), to: "LICENSE" }
30289
+ { from: join14(repo, "docs/agent-integration.md"), to: "docs/agent-integration.md" },
30290
+ // SPEC-883 · skrip VPS (audit/harden/remediate/provision) dibaca runtime lewat scriptPath().
30291
+ { from: join14(repo, "server/scripts/vps"), to: "scripts/vps", dir: true },
30292
+ { from: join14(repo, "LICENSE"), to: "LICENSE" }
29681
30293
  ];
29682
30294
  }
29683
30295
  var PKG_NAME, REPO_URL, RUNTIME_DEPS, REQUIRED_ARTIFACTS, BIN_SHIM;
@@ -29710,7 +30322,15 @@ var init_pack = __esm({
29710
30322
  // SPEC-489 · naskah panduan AI agent — dibaca runtime & disajikan di GET /api/agent-integration.md.
29711
30323
  // Tanpa gerbang ini paket bisa terbit tanpa dokumen dan endpoint-nya 404 di SETIAP instalasi npm,
29712
30324
  // sementara checkout dev terlihat sehat.
29713
- "docs/agent-integration.md"
30325
+ "docs/agent-integration.md",
30326
+ // SPEC-883 · skrip VPS dibaca runtime oleh scriptPath(). Sebelum spec ini keempatnya TIDAK
30327
+ // ikut terpaket sementara scriptPath menjangkar ke repoRoot() — di instalasi npm marker
30328
+ // pnpm-workspace.yaml tak ada, repoRoot jatuh ke cwd, dan audit/harden/remediate melempar
30329
+ // ENOENT. Gerbang ini yang membuat kegagalan itu tak bisa terbit lagi.
30330
+ "scripts/vps/audit.sh",
30331
+ "scripts/vps/harden.sh",
30332
+ "scripts/vps/remediate.sh",
30333
+ "scripts/vps/provision.sh"
29714
30334
  ];
29715
30335
  BIN_SHIM = `#!/usr/bin/env node
29716
30336
  // SPEC-398 \xB7 shim tipis: seluruh logika ada di dist/cli.js (bundle esbuild).
@@ -29724,10 +30344,10 @@ var pack_exports = {};
29724
30344
  __export(pack_exports, {
29725
30345
  default: () => pack
29726
30346
  });
29727
- import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
29728
- import { dirname as dirname7, join as join13 } from "node:path";
30347
+ import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync9, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
30348
+ import { dirname as dirname7, join as join15 } from "node:path";
29729
30349
  function depVersions(repo) {
29730
- const read = (p) => JSON.parse(readFileSync8(join13(repo, p), "utf8"));
30350
+ const read = (p) => JSON.parse(readFileSync9(join15(repo, p), "utf8"));
29731
30351
  const server = read("server/package.json"), cli = read("cli/package.json");
29732
30352
  const pools = [server.dependencies, server.devDependencies, cli.dependencies, cli.devDependencies];
29733
30353
  const out = {};
@@ -29741,19 +30361,19 @@ function depVersions(repo) {
29741
30361
  async function pack(argv, ctx) {
29742
30362
  let repo;
29743
30363
  try {
29744
- repo = resolveLayout(distDir(), existsSync9).root;
30364
+ repo = resolveLayout(distDir(), existsSync10).root;
29745
30365
  } catch (e) {
29746
30366
  ctx.stderr(`${e.message}
29747
30367
  `);
29748
30368
  return 1;
29749
30369
  }
29750
- if (!existsSync9(join13(repo, "server/prisma/schema.prisma"))) {
30370
+ if (!existsSync10(join15(repo, "server/prisma/schema.prisma"))) {
29751
30371
  ctx.stderr("pack: hanya bisa dijalankan dari checkout repo hanoman\n");
29752
30372
  return 1;
29753
30373
  }
29754
30374
  const outIdx = argv.indexOf("--out");
29755
- const out = outIdx === -1 ? join13(repo, "dist-npm") : join13(repo, argv[outIdx + 1] ?? "dist-npm");
29756
- const version3 = JSON.parse(readFileSync8(join13(repo, "package.json"), "utf8")).version;
30375
+ const out = outIdx === -1 ? join15(repo, "dist-npm") : join15(repo, argv[outIdx + 1] ?? "dist-npm");
30376
+ const version3 = JSON.parse(readFileSync9(join15(repo, "package.json"), "utf8")).version;
29757
30377
  if (!version3) {
29758
30378
  ctx.stderr("pack: root package.json tanpa field version\n");
29759
30379
  return 1;
@@ -29767,20 +30387,20 @@ async function pack(argv, ctx) {
29767
30387
  return 1;
29768
30388
  }
29769
30389
  rmSync(out, { recursive: true, force: true });
29770
- mkdirSync2(join13(out, "bin"), { recursive: true });
30390
+ mkdirSync3(join15(out, "bin"), { recursive: true });
29771
30391
  for (const item of copyPlan(repo)) {
29772
- if (!existsSync9(item.from)) {
30392
+ if (!existsSync10(item.from)) {
29773
30393
  ctx.stderr(`pack: artefak hilang \u2014 ${item.from} (sudah \`pnpm build\`?)
29774
30394
  `);
29775
30395
  return 1;
29776
30396
  }
29777
- const dest = join13(out, item.to);
29778
- mkdirSync2(dirname7(dest), { recursive: true });
30397
+ const dest = join15(out, item.to);
30398
+ mkdirSync3(dirname7(dest), { recursive: true });
29779
30399
  cpSync(item.from, dest, item.dir ? { recursive: true } : {});
29780
30400
  }
29781
- writeFileSync2(join13(out, "bin/hanoman.mjs"), BIN_SHIM);
29782
- writeFileSync2(join13(out, "package.json"), JSON.stringify(packageJsonFor(version3, deps), null, 2) + "\n");
29783
- const missing = REQUIRED_ARTIFACTS.filter((a) => !existsSync9(join13(out, a)));
30401
+ writeFileSync3(join15(out, "bin/hanoman.mjs"), BIN_SHIM);
30402
+ writeFileSync3(join15(out, "package.json"), JSON.stringify(packageJsonFor(version3, deps), null, 2) + "\n");
30403
+ const missing = REQUIRED_ARTIFACTS.filter((a) => !existsSync10(join15(out, a)));
29784
30404
  if (missing.length) {
29785
30405
  ctx.stderr(`pack: artefak wajib hilang: ${missing.join(", ")}
29786
30406
  `);
@@ -29805,13 +30425,13 @@ var verify_packed_exports = {};
29805
30425
  __export(verify_packed_exports, {
29806
30426
  default: () => verifyPacked
29807
30427
  });
29808
- import { readFileSync as readFileSync9 } from "node:fs";
29809
- import { join as join14 } from "node:path";
30428
+ import { readFileSync as readFileSync10 } from "node:fs";
30429
+ import { join as join16 } from "node:path";
29810
30430
  function verifyPacked(_argv, ctx) {
29811
- const path = join14(process.cwd(), "package.json");
30431
+ const path = join16(process.cwd(), "package.json");
29812
30432
  let pkg;
29813
30433
  try {
29814
- pkg = JSON.parse(readFileSync9(path, "utf8"));
30434
+ pkg = JSON.parse(readFileSync10(path, "utf8"));
29815
30435
  } catch {
29816
30436
  ctx.stderr(`verify: tak bisa membaca ${path}
29817
30437
  `);
@@ -29847,7 +30467,7 @@ function route(argv) {
29847
30467
  if (argv.includes("--help")) return { cmd: "help", args: [] };
29848
30468
  const [group, sub, ...rest] = argv;
29849
30469
  if (group === void 0 || group.startsWith("--")) return { cmd: "start", args: argv };
29850
- if (group === "start" || group === "doctor" || group === "update" || group === "mcp")
30470
+ if (group === "start" || group === "doctor" || group === "update" || group === "mcp" || group === "provision")
29851
30471
  return { cmd: group, args: argv.slice(1) };
29852
30472
  if (group === "migrate-from-postgres") return { cmd: "migrate-pg", args: argv.slice(1) };
29853
30473
  if (group === "docs" && (sub === "scan" || sub === "index" || sub === "link")) {
@@ -29857,7 +30477,7 @@ function route(argv) {
29857
30477
  if (group === "__verify") return { cmd: "__verify", args: argv.slice(1) };
29858
30478
  return { cmd: "unknown", args: argv };
29859
30479
  }
29860
- async function run(argv, ctx) {
30480
+ async function run2(argv, ctx) {
29861
30481
  const { cmd, args } = route(argv);
29862
30482
  if (cmd === "version") {
29863
30483
  ctx.stdout(currentVersion() + "\n");
@@ -29871,6 +30491,7 @@ async function run(argv, ctx) {
29871
30491
  if (cmd === "doctor") return (await Promise.resolve().then(() => (init_doctor(), doctor_exports))).default(args, ctx);
29872
30492
  if (cmd === "update") return (await Promise.resolve().then(() => (init_update(), update_exports))).default(args, ctx);
29873
30493
  if (cmd === "mcp") return (await Promise.resolve().then(() => (init_mcp2(), mcp_exports))).default(args, ctx);
30494
+ if (cmd === "provision") return (await Promise.resolve().then(() => (init_provision(), provision_exports))).default(args, ctx);
29874
30495
  if (cmd === "migrate-pg") return (await Promise.resolve().then(() => (init_migrate_pg(), migrate_pg_exports))).default(args, ctx);
29875
30496
  if (cmd === "docs:scan") return (await Promise.resolve().then(() => (init_docs_scan(), docs_scan_exports))).default(args, ctx);
29876
30497
  if (cmd === "docs:index") return (await Promise.resolve().then(() => (init_docs_index(), docs_index_exports))).default(args, ctx);
@@ -29893,6 +30514,9 @@ var init_router = __esm({
29893
30514
  --port <n> --host <h> --db <file> --no-migrate
29894
30515
  doctor periksa prasyarat: node, git, tmux, CLI agen, data dir
29895
30516
  update [--check] bandingkan versi dengan registry npm; pasang yang terbaru
30517
+ provision [--with=a,b] [--probe] pasang komponen di MESIN INI (hanoman, caddy, claude,
30518
+ [--profile=lab|production] codex, gh, \u2026). --probe hanya melaporkan apa yang ada.
30519
+ [--domain=<d>] [--dry-run] [--yes]
29896
30520
  mcp [--read-only] [--host <url>] MCP server stdio untuk klien AI (Claude Code/Desktop,
29897
30521
  [--max-bytes <n>] Codex, Cursor, Copilot). Token dari HANOMAN_AGENT_TOKEN.
29898
30522
  migrate-from-postgres --from <url> pindahkan data Postgres lama ke SQLite
@@ -29913,7 +30537,7 @@ var readStdin = () => new Promise((res) => {
29913
30537
  process.stdin.on("end", () => res(d));
29914
30538
  if (process.stdin.isTTY) res("");
29915
30539
  });
29916
- run(process.argv.slice(2), {
30540
+ run2(process.argv.slice(2), {
29917
30541
  cwd: process.cwd(),
29918
30542
  env: process.env,
29919
30543
  stdout: (s2) => process.stdout.write(s2),