hanoman 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,6 +85,17 @@ punya riwayat migrasi hanoman — biasanya bukan DB hanoman versi ini (sisa prot
85
85
  berkas tool lain yang kebetulan bernama sama). hanoman **tidak** mengubah isinya. Pindahkan berkas
86
86
  itu lalu jalankan ulang, atau tunjuk berkas lain dengan `hanoman --db /path/baru.db`.
87
87
 
88
+ ## Kalau terminal sesi terbuka tapi kosong
89
+
90
+ Sesi hanoman hidup di dalam **tmux**; layarnya dialirkan ke browser lewat node-pty. Dua sebab:
91
+
92
+ - **tmux belum terpasang** — `brew install tmux` (macOS) atau paket distro Anda.
93
+ - **`spawn-helper` node-pty tak executable** — node-pty menerbitkan biner pendampingnya dengan mode
94
+ `0644`, jadi `posix_spawnp` gagal dan tak satu byte pun mengalir ke terminal walau sesinya hidup.
95
+ Sejak `0.1.3` `hanoman` memperbaikinya sendiri saat start (sekali per instalasi). Bila instalasi
96
+ global itu milik root dan hanoman dijalankan sebagai pengguna biasa, chmod-nya ditolak — perbaiki
97
+ manual: `sudo chmod +x "$(npm root -g)"/hanoman/node_modules/node-pty/prebuilds/*/spawn-helper`.
98
+
88
99
  ## Lisensi
89
100
 
90
101
  MIT — lihat `LICENSE`.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.2",
3
- "sha": "f55334d",
4
- "builtAt": "2026-07-30T07:38:46.598Z"
2
+ "version": "0.1.4",
3
+ "sha": "ac19a3e",
4
+ "builtAt": "2026-07-30T09:10:14.186Z"
5
5
  }
package/dist/cli.js CHANGED
@@ -256,14 +256,17 @@ __export(start_exports, {
256
256
  default: () => start,
257
257
  distDir: () => distDir,
258
258
  ensurePrismaClient: () => ensurePrismaClient,
259
+ ensureSpawnHelpersExecutable: () => ensureSpawnHelpersExecutable,
259
260
  migrateFailureHint: () => migrateFailureHint,
260
261
  parseStartArgs: () => parseStartArgs,
261
- prismaClientUsable: () => prismaClientUsable
262
+ prismaClientUsable: () => prismaClientUsable,
263
+ repairSpawnHelper: () => repairSpawnHelper,
264
+ spawnHelperPaths: () => spawnHelperPaths
262
265
  });
263
266
  import { spawn, execFileSync } from "node:child_process";
264
267
  import { createRequire } from "node:module";
265
- import { existsSync, mkdirSync } from "node:fs";
266
- import { dirname as dirname2, resolve as resolvePath } from "node:path";
268
+ import { existsSync, mkdirSync, readdirSync, statSync, chmodSync } from "node:fs";
269
+ import { dirname as dirname2, join as join3, resolve as resolvePath } from "node:path";
267
270
  import { fileURLToPath } from "node:url";
268
271
  function parseStartArgs(argv) {
269
272
  const out = { port: null, host: null, db: null, migrate: true };
@@ -355,6 +358,44 @@ async function ensurePrismaClient(schema, dbUrl, ctx) {
355
358
  return false;
356
359
  }
357
360
  }
361
+ function spawnHelperPaths(ptyDir, listDir, exists) {
362
+ const dirs = [join3(ptyDir, "build", "Release")];
363
+ for (const name of listDir(join3(ptyDir, "prebuilds"))) dirs.push(join3(ptyDir, "prebuilds", name));
364
+ return dirs.map((d) => join3(d, "spawn-helper")).filter(exists);
365
+ }
366
+ function ensureSpawnHelpersExecutable(paths2, ops) {
367
+ const fixed = [];
368
+ for (const p of paths2) {
369
+ try {
370
+ const mode = ops.mode(p) & 4095;
371
+ if ((mode & 73) === 73) continue;
372
+ ops.chmod(p, mode | 73);
373
+ fixed.push(p);
374
+ } catch {
375
+ }
376
+ }
377
+ return fixed;
378
+ }
379
+ function repairSpawnHelper(ctx) {
380
+ let ptyDir;
381
+ try {
382
+ ptyDir = dirname2(createRequire(import.meta.url).resolve("node-pty/package.json"));
383
+ } catch {
384
+ return;
385
+ }
386
+ const paths2 = spawnHelperPaths(ptyDir, (d) => {
387
+ try {
388
+ return readdirSync(d);
389
+ } catch {
390
+ return [];
391
+ }
392
+ }, existsSync);
393
+ const fixed = ensureSpawnHelpersExecutable(paths2, {
394
+ mode: (p) => statSync(p).mode,
395
+ chmod: (p, m) => chmodSync(p, m)
396
+ });
397
+ if (fixed.length) ctx.stdout("hanoman \xB7 memperbaiki izin `spawn-helper` node-pty (sekali per instalasi)\n");
398
+ }
358
399
  async function start(argv, ctx) {
359
400
  let opts;
360
401
  try {
@@ -386,6 +427,7 @@ async function start(argv, ctx) {
386
427
  return 1;
387
428
  }
388
429
  if (!await ensurePrismaClient(layout.schema, dbUrl, ctx)) return 1;
430
+ repairSpawnHelper(ctx);
389
431
  if (opts.migrate) {
390
432
  ctx.stdout(`hanoman \xB7 menerapkan migrasi ke ${dbFilePath(dbUrl)}
391
433
  `);
@@ -5918,9 +5960,9 @@ var init_migrate_pg = __esm({
5918
5960
 
5919
5961
  // src/config.ts
5920
5962
  import { readFileSync, existsSync as existsSync4 } from "node:fs";
5921
- import { join as join3 } from "node:path";
5963
+ import { join as join4 } from "node:path";
5922
5964
  function loadConfig(repoRoot) {
5923
- const p = join3(repoRoot, "hanoman.config.json");
5965
+ const p = join4(repoRoot, "hanoman.config.json");
5924
5966
  const raw = existsSync4(p) ? JSON.parse(readFileSync(p, "utf8")) : {};
5925
5967
  return zHanomanConfig.parse(raw);
5926
5968
  }
@@ -5933,12 +5975,12 @@ var init_config2 = __esm({
5933
5975
 
5934
5976
  // src/repo.ts
5935
5977
  import { spawnSync } from "node:child_process";
5936
- import { join as join4 } from "node:path";
5978
+ import { join as join5 } from "node:path";
5937
5979
  function resolveRepo(cwd) {
5938
5980
  const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
5939
5981
  const root = r.status === 0 ? r.stdout.trim() : cwd;
5940
5982
  const { docsDir } = loadConfig(root);
5941
- return { root, docsDir, indexPath: join4(root, docsDir, "README.md") };
5983
+ return { root, docsDir, indexPath: join5(root, docsDir, "README.md") };
5942
5984
  }
5943
5985
  var init_repo = __esm({
5944
5986
  "src/repo.ts"() {
@@ -5948,8 +5990,8 @@ var init_repo = __esm({
5948
5990
  });
5949
5991
 
5950
5992
  // src/docs-model.ts
5951
- import { readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
5952
- import { join as join5, relative } from "node:path";
5993
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
5994
+ import { join as join6, relative } from "node:path";
5953
5995
  function parseIndex(indexPath) {
5954
5996
  const md = readFileSync2(indexPath, "utf8");
5955
5997
  const out = /* @__PURE__ */ new Set();
@@ -5964,10 +6006,10 @@ function parseIndex(indexPath) {
5964
6006
  function walkDocs(docsRoot) {
5965
6007
  const out = [];
5966
6008
  const walk = (dir) => {
5967
- for (const name of readdirSync(dir)) {
6009
+ for (const name of readdirSync2(dir)) {
5968
6010
  if (name.startsWith(".")) continue;
5969
- const abs = join5(dir, name);
5970
- if (statSync(abs).isDirectory()) walk(abs);
6011
+ const abs = join6(dir, name);
6012
+ if (statSync2(abs).isDirectory()) walk(abs);
5971
6013
  else out.push(relative(docsRoot, abs).split("\\").join("/"));
5972
6014
  }
5973
6015
  };
@@ -5998,17 +6040,17 @@ var init_docs_model = __esm({
5998
6040
  });
5999
6041
 
6000
6042
  // src/verify.ts
6001
- import { join as join6 } from "node:path";
6043
+ import { join as join7 } from "node:path";
6002
6044
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
6003
6045
  function scanCoverage(cwd) {
6004
6046
  const { root, docsDir, indexPath } = resolveRepo(cwd);
6005
- const docsRoot = join6(root, docsDir);
6047
+ const docsRoot = join7(root, docsDir);
6006
6048
  if (!existsSync5(docsRoot)) return { coverage: 100, cats: [] };
6007
6049
  if (!existsSync5(indexPath)) throw new Error(`index Source of Truth tidak ada: ${indexPath}`);
6008
6050
  const corpus = walkDocs(docsRoot);
6009
6051
  const read = (rel) => {
6010
6052
  try {
6011
- return readFileSync3(join6(docsRoot, rel), "utf8");
6053
+ return readFileSync3(join7(docsRoot, rel), "utf8");
6012
6054
  } catch {
6013
6055
  return null;
6014
6056
  }
@@ -6082,23 +6124,23 @@ __export(docs_index_exports, {
6082
6124
  default: () => docs_index_default
6083
6125
  });
6084
6126
  import { parseArgs as parseArgs2 } from "node:util";
6085
- import { join as join7 } from "node:path";
6127
+ import { join as join8 } from "node:path";
6086
6128
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
6087
6129
  async function docs_index_default(args, ctx) {
6088
6130
  const { values } = parseArgs2({ args, options: { check: { type: "boolean" }, fix: { type: "boolean" } }, allowPositionals: true });
6089
6131
  const { root, docsDir, indexPath } = resolveRepo(ctx.cwd);
6090
- const docsRoot = join7(root, docsDir);
6132
+ const docsRoot = join8(root, docsDir);
6091
6133
  const corpus = walkDocs(docsRoot);
6092
6134
  const read = (rel) => {
6093
6135
  try {
6094
- return readFileSync5(join7(docsRoot, rel), "utf8");
6136
+ return readFileSync5(join8(docsRoot, rel), "utf8");
6095
6137
  } catch {
6096
6138
  return null;
6097
6139
  }
6098
6140
  };
6099
6141
  const linked = linkedSetFrom(INDEX_NAME, corpus, read);
6100
6142
  const unlinked = corpus.filter((f) => f !== INDEX_NAME && !linked.has(f));
6101
- const dangling = [...parseIndex(indexPath)].filter((p) => !existsSync6(join7(docsRoot, p)));
6143
+ const dangling = [...parseIndex(indexPath)].filter((p) => !existsSync6(join8(docsRoot, p)));
6102
6144
  if (values.fix) {
6103
6145
  for (const f of unlinked) addLink(indexPath, f, f.split("/")[0]);
6104
6146
  ctx.stdout(`linked ${unlinked.length} doc(s)
@@ -6151,7 +6193,7 @@ var init_docs_link = __esm({
6151
6193
  });
6152
6194
 
6153
6195
  // src/release/pack.ts
6154
- import { join as join8 } from "node:path";
6196
+ import { join as join9 } from "node:path";
6155
6197
  function packageJsonFor(version2, deps) {
6156
6198
  return {
6157
6199
  name: PKG_NAME,
@@ -6164,7 +6206,15 @@ function packageJsonFor(version2, deps) {
6164
6206
  // seketika dengan "@prisma/client did not initialize yet" (terukur di `npm i -g` nyata).
6165
6207
  // Non-fatal (`|| true`): bila npm melewati script (mis. --ignore-scripts), `hanoman start`
6166
6208
  // mendeteksi & menggenerate sendiri (ensurePrismaClient).
6167
- scripts: { postinstall: "prisma generate --schema prisma/schema.prisma || true" },
6209
+ scripts: {
6210
+ postinstall: "prisma generate --schema prisma/schema.prisma || true",
6211
+ // SPEC-403 · gerbang terakhir sebelum byte meninggalkan mesin. `hanoman@0.1.3` terbit tanpa
6212
+ // `prisma` karena `dist-npm/package.json` DIMUTASI sesudah dirakit — `npm i -g --prefix <dir>
6213
+ // <tarball>` dengan cwd di `dist-npm` menulis ulang berkas itu (terukur, bisa diulang), dan
6214
+ // smoke test itulah yang merusaknya. npm menjalankan `prepublishOnly` tepat sebelum publish,
6215
+ // jadi inilah satu-satunya lapis yang melihat isi berkas SEBENARNYA yang akan dikirim.
6216
+ prepublishOnly: "node dist/cli.js __verify"
6217
+ },
6168
6218
  engines: { node: ">=20" },
6169
6219
  files: ["bin", "dist", "web", "prisma", "README.md", "LICENSE"],
6170
6220
  dependencies: deps,
@@ -6173,16 +6223,22 @@ function packageJsonFor(version2, deps) {
6173
6223
  license: "MIT"
6174
6224
  };
6175
6225
  }
6226
+ function verifyPackedDeps(pkg) {
6227
+ const deps = pkg?.dependencies ?? {};
6228
+ return RUNTIME_DEPS.filter((d) => !deps[d]).map(
6229
+ (d) => `dependency wajib hilang dari package.json paket: ${d}`
6230
+ );
6231
+ }
6176
6232
  function copyPlan(repo) {
6177
6233
  return [
6178
- { from: join8(repo, "server/dist/server.js"), to: "dist/server.js" },
6179
- { from: join8(repo, "server/dist/build-info.json"), to: "dist/build-info.json" },
6180
- { from: join8(repo, "cli/dist/hanoman.js"), to: "dist/cli.js" },
6181
- { from: join8(repo, "src/dist"), to: "web", dir: true },
6182
- { from: join8(repo, "server/prisma/schema.prisma"), to: "prisma/schema.prisma" },
6183
- { from: join8(repo, "server/prisma/migrations"), to: "prisma/migrations", dir: true },
6184
- { from: join8(repo, "internal/docs/operations/npm-readme.md"), to: "README.md" },
6185
- { from: join8(repo, "LICENSE"), to: "LICENSE" }
6234
+ { from: join9(repo, "server/dist/server.js"), to: "dist/server.js" },
6235
+ { from: join9(repo, "server/dist/build-info.json"), to: "dist/build-info.json" },
6236
+ { from: join9(repo, "cli/dist/hanoman.js"), to: "dist/cli.js" },
6237
+ { from: join9(repo, "src/dist"), to: "web", dir: true },
6238
+ { from: join9(repo, "server/prisma/schema.prisma"), to: "prisma/schema.prisma" },
6239
+ { from: join9(repo, "server/prisma/migrations"), to: "prisma/migrations", dir: true },
6240
+ { from: join9(repo, "internal/docs/operations/npm-readme.md"), to: "README.md" },
6241
+ { from: join9(repo, "LICENSE"), to: "LICENSE" }
6186
6242
  ];
6187
6243
  }
6188
6244
  var PKG_NAME, REPO_URL, RUNTIME_DEPS, REQUIRED_ARTIFACTS, BIN_SHIM;
@@ -6225,9 +6281,9 @@ __export(pack_exports, {
6225
6281
  default: () => pack
6226
6282
  });
6227
6283
  import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
6228
- import { dirname as dirname5, join as join9 } from "node:path";
6284
+ import { dirname as dirname5, join as join10 } from "node:path";
6229
6285
  function depVersions(repo) {
6230
- const read = (p) => JSON.parse(readFileSync6(join9(repo, p), "utf8"));
6286
+ const read = (p) => JSON.parse(readFileSync6(join10(repo, p), "utf8"));
6231
6287
  const server = read("server/package.json"), cli = read("cli/package.json");
6232
6288
  const pools = [server.dependencies, server.devDependencies, cli.dependencies, cli.devDependencies];
6233
6289
  const out = {};
@@ -6247,13 +6303,13 @@ async function pack(argv, ctx) {
6247
6303
  `);
6248
6304
  return 1;
6249
6305
  }
6250
- if (!existsSync7(join9(repo, "server/prisma/schema.prisma"))) {
6306
+ if (!existsSync7(join10(repo, "server/prisma/schema.prisma"))) {
6251
6307
  ctx.stderr("pack: hanya bisa dijalankan dari checkout repo hanoman\n");
6252
6308
  return 1;
6253
6309
  }
6254
6310
  const outIdx = argv.indexOf("--out");
6255
- const out = outIdx === -1 ? join9(repo, "dist-npm") : join9(repo, argv[outIdx + 1] ?? "dist-npm");
6256
- const version2 = JSON.parse(readFileSync6(join9(repo, "package.json"), "utf8")).version;
6311
+ const out = outIdx === -1 ? join10(repo, "dist-npm") : join10(repo, argv[outIdx + 1] ?? "dist-npm");
6312
+ const version2 = JSON.parse(readFileSync6(join10(repo, "package.json"), "utf8")).version;
6257
6313
  if (!version2) {
6258
6314
  ctx.stderr("pack: root package.json tanpa field version\n");
6259
6315
  return 1;
@@ -6267,20 +6323,20 @@ async function pack(argv, ctx) {
6267
6323
  return 1;
6268
6324
  }
6269
6325
  rmSync(out, { recursive: true, force: true });
6270
- mkdirSync2(join9(out, "bin"), { recursive: true });
6326
+ mkdirSync2(join10(out, "bin"), { recursive: true });
6271
6327
  for (const item of copyPlan(repo)) {
6272
6328
  if (!existsSync7(item.from)) {
6273
6329
  ctx.stderr(`pack: artefak hilang \u2014 ${item.from} (sudah \`pnpm build\`?)
6274
6330
  `);
6275
6331
  return 1;
6276
6332
  }
6277
- const dest = join9(out, item.to);
6333
+ const dest = join10(out, item.to);
6278
6334
  mkdirSync2(dirname5(dest), { recursive: true });
6279
6335
  cpSync(item.from, dest, item.dir ? { recursive: true } : {});
6280
6336
  }
6281
- writeFileSync2(join9(out, "bin/hanoman.mjs"), BIN_SHIM);
6282
- writeFileSync2(join9(out, "package.json"), JSON.stringify(packageJsonFor(version2, deps), null, 2) + "\n");
6283
- const missing = REQUIRED_ARTIFACTS.filter((a) => !existsSync7(join9(out, a)));
6337
+ writeFileSync2(join10(out, "bin/hanoman.mjs"), BIN_SHIM);
6338
+ writeFileSync2(join10(out, "package.json"), JSON.stringify(packageJsonFor(version2, deps), null, 2) + "\n");
6339
+ const missing = REQUIRED_ARTIFACTS.filter((a) => !existsSync7(join10(out, a)));
6284
6340
  if (missing.length) {
6285
6341
  ctx.stderr(`pack: artefak wajib hilang: ${missing.join(", ")}
6286
6342
  `);
@@ -6300,6 +6356,39 @@ var init_pack2 = __esm({
6300
6356
  }
6301
6357
  });
6302
6358
 
6359
+ // src/commands/verify-packed.ts
6360
+ var verify_packed_exports = {};
6361
+ __export(verify_packed_exports, {
6362
+ default: () => verifyPacked
6363
+ });
6364
+ import { readFileSync as readFileSync7 } from "node:fs";
6365
+ import { join as join11 } from "node:path";
6366
+ function verifyPacked(_argv, ctx) {
6367
+ const path = join11(process.cwd(), "package.json");
6368
+ let pkg;
6369
+ try {
6370
+ pkg = JSON.parse(readFileSync7(path, "utf8"));
6371
+ } catch {
6372
+ ctx.stderr(`verify: tak bisa membaca ${path}
6373
+ `);
6374
+ return 1;
6375
+ }
6376
+ const keluhan = verifyPackedDeps(pkg);
6377
+ if (keluhan.length === 0) return 0;
6378
+ for (const k of keluhan) ctx.stderr(`verify: ${k}
6379
+ `);
6380
+ ctx.stderr(
6381
+ "\nPublish DIBATALKAN. `dist-npm/package.json` tercemar sesudah dirakit \u2014 jangan sunting\nberkas itu, rakit ulang: `pnpm release`. Penyebab yang sudah terukur: menjalankan\n`npm install`/`npm i -g` dengan cwd di dalam `dist-npm/` menulis ulang package.json-nya.\n"
6382
+ );
6383
+ return 1;
6384
+ }
6385
+ var init_verify_packed = __esm({
6386
+ "src/commands/verify-packed.ts"() {
6387
+ "use strict";
6388
+ init_pack();
6389
+ }
6390
+ });
6391
+
6303
6392
  // src/router.ts
6304
6393
  import { createRequire as createRequire2 } from "node:module";
6305
6394
  function currentVersion() {
@@ -6320,6 +6409,7 @@ function route(argv) {
6320
6409
  return { cmd: `docs:${sub}`, args: rest };
6321
6410
  }
6322
6411
  if (group === "__pack") return { cmd: "__pack", args: argv.slice(1) };
6412
+ if (group === "__verify") return { cmd: "__verify", args: argv.slice(1) };
6323
6413
  return { cmd: "unknown", args: argv };
6324
6414
  }
6325
6415
  async function run(argv, ctx) {
@@ -6340,6 +6430,7 @@ async function run(argv, ctx) {
6340
6430
  if (cmd === "docs:index") return (await Promise.resolve().then(() => (init_docs_index(), docs_index_exports))).default(args, ctx);
6341
6431
  if (cmd === "docs:link") return (await Promise.resolve().then(() => (init_docs_link(), docs_link_exports))).default(args, ctx);
6342
6432
  if (cmd === "__pack") return (await Promise.resolve().then(() => (init_pack2(), pack_exports))).default(args, ctx);
6433
+ if (cmd === "__verify") return (await Promise.resolve().then(() => (init_verify_packed(), verify_packed_exports))).default(args, ctx);
6343
6434
  ctx.stderr(`unknown command: ${argv.join(" ")}
6344
6435
 
6345
6436
  ${HELP}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanoman",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Orchestrator + dashboard workflow docs-driven untuk sesi Claude Code / Codex",
5
5
  "type": "module",
6
6
  "repository": {
@@ -11,7 +11,8 @@
11
11
  "hanoman": "bin/hanoman.mjs"
12
12
  },
13
13
  "scripts": {
14
- "postinstall": "prisma generate --schema prisma/schema.prisma || true"
14
+ "postinstall": "prisma generate --schema prisma/schema.prisma || true",
15
+ "prepublishOnly": "node dist/cli.js __verify"
15
16
  },
16
17
  "engines": {
17
18
  "node": ">=20"