hanoman 0.1.43 → 0.1.44
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/server.js
CHANGED
|
@@ -6092,6 +6092,9 @@ var init_api = __esm({
|
|
|
6092
6092
|
ideCompare: (id, from, to) => `${API}/projects/${id}/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`,
|
|
6093
6093
|
ideCompareFile: (id, from, to, path2) => `${API}/projects/${id}/compare/file?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&path=${encodeURIComponent(path2)}`,
|
|
6094
6094
|
ideGit: (id) => `${API}/projects/${id}/git`,
|
|
6095
|
+
// ADR-0121 · operasi berkas Explorer: satu path untuk buat/rename/hapus, satu untuk unggah.
|
|
6096
|
+
ideEntry: (id, path2) => `${API}/projects/${id}/entry${path2 ? `?path=${encodeURIComponent(path2)}` : ""}`,
|
|
6097
|
+
ideUpload: (id) => `${API}/projects/${id}/upload`,
|
|
6095
6098
|
ideGitMerge: (id) => `${API}/projects/${id}/git/merge`,
|
|
6096
6099
|
// SPEC-229 · merge git graph isolasi
|
|
6097
6100
|
// SPEC-234 · status working tree (staged/unstaged) + diff satu file working tree
|
|
@@ -6266,11 +6269,11 @@ function isExternalLink(target2) {
|
|
|
6266
6269
|
return !target2 || /^(https?:|#|mailto:)/.test(target2);
|
|
6267
6270
|
}
|
|
6268
6271
|
function resolveLink(fromRel, target2) {
|
|
6269
|
-
const
|
|
6270
|
-
if (!
|
|
6271
|
-
if (
|
|
6272
|
+
const clean2 = target2.trim().split(/\s+/)[0].split("#")[0].split("\\").join("/");
|
|
6273
|
+
if (!clean2) return "";
|
|
6274
|
+
if (clean2.startsWith("/")) return clean2.slice(1).split("/").filter((p) => p && p !== ".").join("/");
|
|
6272
6275
|
const dir2 = fromRel.includes("/") ? fromRel.slice(0, fromRel.lastIndexOf("/")) : "";
|
|
6273
|
-
const parts = (dir2 ? dir2.split("/") : []).concat(
|
|
6276
|
+
const parts = (dir2 ? dir2.split("/") : []).concat(clean2.replace(/^\.\//, "").split("/"));
|
|
6274
6277
|
const out3 = [];
|
|
6275
6278
|
for (const p of parts) {
|
|
6276
6279
|
if (p === "" || p === ".") continue;
|
|
@@ -11626,9 +11629,9 @@ var init_config3 = __esm({
|
|
|
11626
11629
|
// src/services/telegram/protocol.ts
|
|
11627
11630
|
import { createHash as createHash2 } from "node:crypto";
|
|
11628
11631
|
function normalizeTelegramCommand(text) {
|
|
11629
|
-
const
|
|
11630
|
-
const match =
|
|
11631
|
-
if (!match) return
|
|
11632
|
+
const clean2 = text.trim();
|
|
11633
|
+
const match = clean2.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
11634
|
+
if (!match) return clean2;
|
|
11632
11635
|
const args = match[2]?.trim().replace(/\s+/g, " ");
|
|
11633
11636
|
return `/${match[1].toLowerCase()}${args ? ` ${args}` : ""}`;
|
|
11634
11637
|
}
|
|
@@ -11694,19 +11697,19 @@ function inboundDigest(input) {
|
|
|
11694
11697
|
return createHash2("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
11695
11698
|
}
|
|
11696
11699
|
function sanitizeTelegramOutput(text, exactSecrets = []) {
|
|
11697
|
-
let
|
|
11700
|
+
let clean2 = text.replace(ANSI, "").replace(CONTROLS, "");
|
|
11698
11701
|
for (const secret of [...exactSecrets].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
11699
|
-
|
|
11702
|
+
clean2 = clean2.split(secret).join("[REDACTED]");
|
|
11700
11703
|
}
|
|
11701
|
-
for (const pattern of CREDENTIAL_PATTERNS)
|
|
11702
|
-
return
|
|
11704
|
+
for (const pattern of CREDENTIAL_PATTERNS) clean2 = clean2.replace(pattern, "[REDACTED]");
|
|
11705
|
+
return clean2.trim();
|
|
11703
11706
|
}
|
|
11704
11707
|
function splitTelegramText(text, max = 4096, exactSecrets = []) {
|
|
11705
11708
|
if (!Number.isInteger(max) || max < 1) throw new Error("max harus integer positif");
|
|
11706
|
-
const
|
|
11707
|
-
if (!
|
|
11709
|
+
const clean2 = sanitizeTelegramOutput(text, exactSecrets);
|
|
11710
|
+
if (!clean2) return [];
|
|
11708
11711
|
const chunks = [];
|
|
11709
|
-
let rest =
|
|
11712
|
+
let rest = clean2;
|
|
11710
11713
|
while (rest.length > max) {
|
|
11711
11714
|
let cut = max;
|
|
11712
11715
|
const before2 = rest.charCodeAt(cut - 1);
|
|
@@ -18558,11 +18561,11 @@ var require_clean = __commonJS({
|
|
|
18558
18561
|
"../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/clean.js"(exports, module) {
|
|
18559
18562
|
"use strict";
|
|
18560
18563
|
var parse3 = require_parse();
|
|
18561
|
-
var
|
|
18564
|
+
var clean2 = (version2, options2) => {
|
|
18562
18565
|
const s2 = parse3(version2.trim().replace(/^[=v]+/, ""), options2);
|
|
18563
18566
|
return s2 ? s2.version : null;
|
|
18564
18567
|
};
|
|
18565
|
-
module.exports =
|
|
18568
|
+
module.exports = clean2;
|
|
18566
18569
|
}
|
|
18567
18570
|
});
|
|
18568
18571
|
|
|
@@ -19959,7 +19962,7 @@ var require_semver2 = __commonJS({
|
|
|
19959
19962
|
var identifiers = require_identifiers();
|
|
19960
19963
|
var parse3 = require_parse();
|
|
19961
19964
|
var valid = require_valid();
|
|
19962
|
-
var
|
|
19965
|
+
var clean2 = require_clean();
|
|
19963
19966
|
var inc = require_inc();
|
|
19964
19967
|
var diff = require_diff();
|
|
19965
19968
|
var major = require_major();
|
|
@@ -19998,7 +20001,7 @@ var require_semver2 = __commonJS({
|
|
|
19998
20001
|
module.exports = {
|
|
19999
20002
|
parse: parse3,
|
|
20000
20003
|
valid,
|
|
20001
|
-
clean,
|
|
20004
|
+
clean: clean2,
|
|
20002
20005
|
inc,
|
|
20003
20006
|
diff,
|
|
20004
20007
|
major,
|
|
@@ -24356,7 +24359,7 @@ var require_multipart2 = __commonJS({
|
|
|
24356
24359
|
var Busboy = require_main();
|
|
24357
24360
|
var os = __require("node:os");
|
|
24358
24361
|
var fp = require_fastify_plugin();
|
|
24359
|
-
var { createWriteStream } = __require("node:fs");
|
|
24362
|
+
var { createWriteStream: createWriteStream2 } = __require("node:fs");
|
|
24360
24363
|
var { unlink: unlink6 } = __require("node:fs/promises");
|
|
24361
24364
|
var path2 = __require("node:path");
|
|
24362
24365
|
var { generateId } = require_generateId();
|
|
@@ -24736,7 +24739,7 @@ var require_multipart2 = __commonJS({
|
|
|
24736
24739
|
continue;
|
|
24737
24740
|
}
|
|
24738
24741
|
const filepath = path2.join(tmpdir5, generateId() + path2.extname(part.filename || "file" + i++));
|
|
24739
|
-
const target2 =
|
|
24742
|
+
const target2 = createWriteStream2(filepath);
|
|
24740
24743
|
try {
|
|
24741
24744
|
this.tmpUploads.push(filepath);
|
|
24742
24745
|
await pump2(part.file, target2);
|
|
@@ -24983,6 +24986,7 @@ async function resolveRepoEntry(root, rel, opts = {}) {
|
|
|
24983
24986
|
for (let i = 0; i < parts.length; i++) {
|
|
24984
24987
|
current = join6(current, parts[i]);
|
|
24985
24988
|
const stat2 = await lstat(current).catch((error) => {
|
|
24989
|
+
if (error.code === "ENOENT" && opts.allowMissingTail) return null;
|
|
24986
24990
|
if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
|
|
24987
24991
|
return denied("komponen tidak ada");
|
|
24988
24992
|
});
|
|
@@ -29373,10 +29377,10 @@ async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
|
29373
29377
|
const [sha, parents, author, at, subject, refs2] = line.split(US);
|
|
29374
29378
|
const tags = [], branchRefs = [];
|
|
29375
29379
|
for (const d of (refs2 ?? "").split(",").map((r) => r.trim()).filter(Boolean)) {
|
|
29376
|
-
const
|
|
29377
|
-
if (
|
|
29378
|
-
if (
|
|
29379
|
-
else branchRefs.push(
|
|
29380
|
+
const clean2 = d.replace(/^HEAD -> /, "");
|
|
29381
|
+
if (clean2 === "HEAD") continue;
|
|
29382
|
+
if (clean2.startsWith("tag: ")) tags.push(clean2.slice(5));
|
|
29383
|
+
else branchRefs.push(clean2);
|
|
29380
29384
|
}
|
|
29381
29385
|
return {
|
|
29382
29386
|
sha,
|
|
@@ -29852,6 +29856,85 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
29852
29856
|
return { base: report.base, results };
|
|
29853
29857
|
}
|
|
29854
29858
|
|
|
29859
|
+
// src/services/repo-fs.ts
|
|
29860
|
+
import { lstat as lstat2, rename as rename2, rm as rm3 } from "node:fs/promises";
|
|
29861
|
+
import { createWriteStream } from "node:fs";
|
|
29862
|
+
import { join as join9 } from "node:path";
|
|
29863
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
29864
|
+
import { pipeline } from "node:stream/promises";
|
|
29865
|
+
var EntryExistsError = class extends Error {
|
|
29866
|
+
code = "ENTRY_EXISTS";
|
|
29867
|
+
};
|
|
29868
|
+
var EntryMissingError = class extends Error {
|
|
29869
|
+
code = "ENTRY_MISSING";
|
|
29870
|
+
};
|
|
29871
|
+
var EntryTargetInsideError = class extends Error {
|
|
29872
|
+
code = "ENTRY_TARGET_INSIDE";
|
|
29873
|
+
};
|
|
29874
|
+
function assertNotGit(rel) {
|
|
29875
|
+
if (rel.split(/[\\/]/).includes(".git"))
|
|
29876
|
+
throw new PathContainmentError("repository path ditolak: tidak boleh menyentuh .git");
|
|
29877
|
+
}
|
|
29878
|
+
var clean = (s2) => s2.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
29879
|
+
function joinRel(dir2, name2) {
|
|
29880
|
+
const d = clean(dir2), n2 = clean(name2);
|
|
29881
|
+
if (!n2) throw new PathContainmentError("repository path ditolak: nama berkas kosong");
|
|
29882
|
+
return d ? `${d}/${n2}` : n2;
|
|
29883
|
+
}
|
|
29884
|
+
async function entryKind(repoDir, rel) {
|
|
29885
|
+
assertNotGit(rel);
|
|
29886
|
+
const { path: path2 } = await resolveRepoEntry(repoDir, rel, { allowMissingTail: true });
|
|
29887
|
+
const st = await lstat2(path2).catch(() => null);
|
|
29888
|
+
if (!st) return null;
|
|
29889
|
+
return st.isDirectory() ? "dir" : "file";
|
|
29890
|
+
}
|
|
29891
|
+
async function createEntry(repoDir, rel, kind) {
|
|
29892
|
+
if (await entryKind(repoDir, rel)) throw new EntryExistsError(`sudah ada: ${rel}`);
|
|
29893
|
+
await writeRepoFileAtomic(repoDir, kind === "dir" ? `${clean(rel)}/.gitkeep` : rel, "");
|
|
29894
|
+
return { path: rel };
|
|
29895
|
+
}
|
|
29896
|
+
async function renameEntry(repoDir, from, to) {
|
|
29897
|
+
const kind = await entryKind(repoDir, from);
|
|
29898
|
+
if (!kind) throw new EntryMissingError(`tidak ada: ${from}`);
|
|
29899
|
+
if (await entryKind(repoDir, to)) throw new EntryExistsError(`sudah ada: ${to}`);
|
|
29900
|
+
if (kind === "dir" && `${clean(to)}/`.startsWith(`${clean(from)}/`))
|
|
29901
|
+
throw new EntryTargetInsideError("tujuan di dalam sumber");
|
|
29902
|
+
await ensureRepoParents(repoDir, to);
|
|
29903
|
+
const src = await resolveRepoEntry(repoDir, from);
|
|
29904
|
+
const dst = await resolveRepoEntry(repoDir, to, { allowMissingFinal: true });
|
|
29905
|
+
await rename2(src.path, dst.path);
|
|
29906
|
+
return { from, to };
|
|
29907
|
+
}
|
|
29908
|
+
async function deleteEntry(repoDir, rel) {
|
|
29909
|
+
const kind = await entryKind(repoDir, rel);
|
|
29910
|
+
if (!kind) throw new EntryMissingError(`tidak ada: ${rel}`);
|
|
29911
|
+
const { path: path2 } = await resolveRepoEntry(repoDir, rel);
|
|
29912
|
+
await rm3(path2, { recursive: kind === "dir", force: false });
|
|
29913
|
+
return { path: rel, kind };
|
|
29914
|
+
}
|
|
29915
|
+
async function saveUpload(repoDir, rel, source, opts = {}) {
|
|
29916
|
+
assertNotGit(rel);
|
|
29917
|
+
await ensureRepoParents(repoDir, rel);
|
|
29918
|
+
const entry = await resolveRepoEntry(repoDir, rel, { allowMissingFinal: true });
|
|
29919
|
+
const current = await lstat2(entry.path).catch(() => null);
|
|
29920
|
+
if (current && !opts.overwrite) return { status: "exists" };
|
|
29921
|
+
if (current && !current.isFile())
|
|
29922
|
+
throw new PathContainmentError("repository path ditolak: target bukan file regular");
|
|
29923
|
+
const temp = join9(entry.parent, `.hanoman-${randomUUID5()}.tmp`);
|
|
29924
|
+
try {
|
|
29925
|
+
await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
|
|
29926
|
+
if (opts.isTruncated?.()) {
|
|
29927
|
+
await rm3(temp, { force: true });
|
|
29928
|
+
return { status: "too-large" };
|
|
29929
|
+
}
|
|
29930
|
+
await rename2(temp, entry.path);
|
|
29931
|
+
return { status: "written" };
|
|
29932
|
+
} catch (error) {
|
|
29933
|
+
await rm3(temp, { force: true });
|
|
29934
|
+
throw error;
|
|
29935
|
+
}
|
|
29936
|
+
}
|
|
29937
|
+
|
|
29855
29938
|
// src/routes/ide.ts
|
|
29856
29939
|
async function repoOf(id) {
|
|
29857
29940
|
const p = await prisma.project.findUnique({ where: { id } });
|
|
@@ -29870,6 +29953,19 @@ async function lockInputs(id) {
|
|
|
29870
29953
|
sessionBranches: new Set(sessions)
|
|
29871
29954
|
};
|
|
29872
29955
|
}
|
|
29956
|
+
function entryError(reply, e) {
|
|
29957
|
+
if (e instanceof EntryMissingError) return reply.code(404).send({ error: "not found" });
|
|
29958
|
+
if (e instanceof EntryExistsError) return reply.code(409).send({ error: "sudah ada" });
|
|
29959
|
+
if (e instanceof EntryTargetInsideError) return reply.code(400).send({ error: "tujuan di dalam sumber" });
|
|
29960
|
+
return reply.code(400).send({ error: e.message });
|
|
29961
|
+
}
|
|
29962
|
+
var UPLOAD_LIMITS = {
|
|
29963
|
+
fileSize: 100 * 1024 * 1024,
|
|
29964
|
+
files: 1e3,
|
|
29965
|
+
fields: 10,
|
|
29966
|
+
fieldSize: 1024 * 1024
|
|
29967
|
+
};
|
|
29968
|
+
var UPLOAD_TOTAL_MAX = 2 * 1024 * 1024 * 1024;
|
|
29873
29969
|
async function ide_default(app2) {
|
|
29874
29970
|
app2.get("/projects/:id/tree", async (req, reply) => {
|
|
29875
29971
|
const repoDir = await repoOf(req.params.id);
|
|
@@ -29934,6 +30030,102 @@ async function ide_default(app2) {
|
|
|
29934
30030
|
return reply.code(400).send({ error: e.message });
|
|
29935
30031
|
}
|
|
29936
30032
|
});
|
|
30033
|
+
app2.post("/projects/:id/entry", async (req, reply) => {
|
|
30034
|
+
const repoDir = await repoOf(req.params.id);
|
|
30035
|
+
if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
|
|
30036
|
+
if (!repoDir) return reply.code(400).send({ error: "project tidak punya repoDir" });
|
|
30037
|
+
const b = req.body;
|
|
30038
|
+
if (!b?.path || typeof b.path !== "string") return reply.code(400).send({ error: "path wajib" });
|
|
30039
|
+
if (b.kind !== "file" && b.kind !== "dir") return reply.code(400).send({ error: "kind harus file atau dir" });
|
|
30040
|
+
try {
|
|
30041
|
+
return reply.code(201).send(await createEntry(repoDir, b.path, b.kind));
|
|
30042
|
+
} catch (e) {
|
|
30043
|
+
return entryError(reply, e);
|
|
30044
|
+
}
|
|
30045
|
+
});
|
|
30046
|
+
app2.patch("/projects/:id/entry", async (req, reply) => {
|
|
30047
|
+
const repoDir = await repoOf(req.params.id);
|
|
30048
|
+
if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
|
|
30049
|
+
if (!repoDir) return reply.code(400).send({ error: "project tidak punya repoDir" });
|
|
30050
|
+
const b = req.body;
|
|
30051
|
+
if (!b?.from || !b?.to || typeof b.from !== "string" || typeof b.to !== "string")
|
|
30052
|
+
return reply.code(400).send({ error: "from & to wajib" });
|
|
30053
|
+
try {
|
|
30054
|
+
return await renameEntry(repoDir, b.from, b.to);
|
|
30055
|
+
} catch (e) {
|
|
30056
|
+
return entryError(reply, e);
|
|
30057
|
+
}
|
|
30058
|
+
});
|
|
30059
|
+
app2.delete("/projects/:id/entry", async (req, reply) => {
|
|
30060
|
+
const repoDir = await repoOf(req.params.id);
|
|
30061
|
+
if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
|
|
30062
|
+
if (!repoDir) return reply.code(400).send({ error: "project tidak punya repoDir" });
|
|
30063
|
+
const { path: path2 } = req.query;
|
|
30064
|
+
if (!path2) return reply.code(400).send({ error: "path wajib" });
|
|
30065
|
+
try {
|
|
30066
|
+
return await deleteEntry(repoDir, path2);
|
|
30067
|
+
} catch (e) {
|
|
30068
|
+
return entryError(reply, e);
|
|
30069
|
+
}
|
|
30070
|
+
});
|
|
30071
|
+
app2.post("/projects/:id/upload", async (req, reply) => {
|
|
30072
|
+
const repoDir = await repoOf(req.params.id);
|
|
30073
|
+
if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
|
|
30074
|
+
if (!repoDir) return reply.code(400).send({ error: "project tidak punya repoDir" });
|
|
30075
|
+
if (!req.isMultipart?.()) return reply.code(400).send({ error: "butuh multipart/form-data" });
|
|
30076
|
+
const totalMax = Number(process.env.HANOMAN_IDE_UPLOAD_MAX_BYTES) || UPLOAD_TOTAL_MAX;
|
|
30077
|
+
let dir2 = "", overwrite = false, manifest = null, seen = 0, total = 0;
|
|
30078
|
+
const written = [];
|
|
30079
|
+
const skipped2 = [];
|
|
30080
|
+
for await (const part of req.parts({ limits: UPLOAD_LIMITS })) {
|
|
30081
|
+
if (part.type === "field") {
|
|
30082
|
+
if (part.fieldname === "dir") dir2 = String(part.value ?? "");
|
|
30083
|
+
else if (part.fieldname === "overwrite") overwrite = part.value === "1" || part.value === "true";
|
|
30084
|
+
else if (part.fieldname === "manifest") {
|
|
30085
|
+
try {
|
|
30086
|
+
const parsed = JSON.parse(String(part.value));
|
|
30087
|
+
if (!Array.isArray(parsed) || parsed.some((p) => typeof p !== "string")) throw new Error("bentuk");
|
|
30088
|
+
manifest = parsed;
|
|
30089
|
+
} catch {
|
|
30090
|
+
return reply.code(400).send({ error: "manifest tak sah" });
|
|
30091
|
+
}
|
|
30092
|
+
}
|
|
30093
|
+
continue;
|
|
30094
|
+
}
|
|
30095
|
+
const name2 = manifest ? manifest[seen] : part.filename;
|
|
30096
|
+
seen++;
|
|
30097
|
+
if (name2 === void 0) {
|
|
30098
|
+
part.file.resume();
|
|
30099
|
+
return reply.code(400).send({ error: "manifest tak cocok dengan berkas" });
|
|
30100
|
+
}
|
|
30101
|
+
let rel = name2;
|
|
30102
|
+
try {
|
|
30103
|
+
rel = joinRel(dir2, name2);
|
|
30104
|
+
if (total >= totalMax) {
|
|
30105
|
+
part.file.resume();
|
|
30106
|
+
skipped2.push({ path: rel, reason: "budget" });
|
|
30107
|
+
continue;
|
|
30108
|
+
}
|
|
30109
|
+
const r = await saveUpload(repoDir, rel, part.file, {
|
|
30110
|
+
overwrite,
|
|
30111
|
+
isTruncated: () => part.file.truncated === true
|
|
30112
|
+
});
|
|
30113
|
+
if (r.status === "written") {
|
|
30114
|
+
written.push(rel);
|
|
30115
|
+
total += Number(part.file.bytesRead ?? 0);
|
|
30116
|
+
} else {
|
|
30117
|
+
part.file.resume();
|
|
30118
|
+
skipped2.push({ path: rel, reason: r.status });
|
|
30119
|
+
}
|
|
30120
|
+
} catch {
|
|
30121
|
+
part.file.resume();
|
|
30122
|
+
skipped2.push({ path: name2, reason: "denied" });
|
|
30123
|
+
}
|
|
30124
|
+
}
|
|
30125
|
+
if (manifest && seen !== manifest.length)
|
|
30126
|
+
return reply.code(400).send({ error: "manifest tak cocok dengan berkas" });
|
|
30127
|
+
return { written, skipped: skipped2 };
|
|
30128
|
+
});
|
|
29937
30129
|
app2.get("/projects/:id/graph", async (req, reply) => {
|
|
29938
30130
|
const repoDir = await repoOf(req.params.id);
|
|
29939
30131
|
if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
|
|
@@ -30172,14 +30364,14 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
30172
30364
|
// src/routes/fs.ts
|
|
30173
30365
|
import { readdir } from "node:fs/promises";
|
|
30174
30366
|
import { homedir as homedir4 } from "node:os";
|
|
30175
|
-
import { resolve as resolve13, dirname as dirname9, join as
|
|
30367
|
+
import { resolve as resolve13, dirname as dirname9, join as join10 } from "node:path";
|
|
30176
30368
|
async function fs_default(app2) {
|
|
30177
30369
|
app2.get("/fs/browse", async (req, reply) => {
|
|
30178
30370
|
const q = req.query.path;
|
|
30179
30371
|
const dir2 = q && q.trim() ? resolve13(q.trim()) : homedir4();
|
|
30180
30372
|
try {
|
|
30181
30373
|
const ents = await readdir(dir2, { withFileTypes: true });
|
|
30182
|
-
const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
30374
|
+
const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join10(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
30183
30375
|
const parent = dirname9(dir2);
|
|
30184
30376
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries };
|
|
30185
30377
|
} catch {
|
|
@@ -30625,15 +30817,15 @@ function ownsWorktree(repoDir, cwd) {
|
|
|
30625
30817
|
init_src2();
|
|
30626
30818
|
init_db();
|
|
30627
30819
|
import { execFile as execFile8 } from "node:child_process";
|
|
30628
|
-
import { readdir as readdir2, rm as
|
|
30820
|
+
import { readdir as readdir2, rm as rm4 } from "node:fs/promises";
|
|
30629
30821
|
import { promisify as promisify9 } from "node:util";
|
|
30630
|
-
import { join as
|
|
30822
|
+
import { join as join11, resolve as resolve15 } from "node:path";
|
|
30631
30823
|
init_notifications();
|
|
30632
30824
|
var TICK_MS = 6e4;
|
|
30633
30825
|
var trashDirOf = (repoDir) => resolve15(repoDir, ".worktrees", ".trash");
|
|
30634
30826
|
var exec8 = promisify9(execFile8);
|
|
30635
30827
|
var prodReaperDeps = {
|
|
30636
|
-
rm: (path2) =>
|
|
30828
|
+
rm: (path2) => rm4(path2, { recursive: true, force: true }),
|
|
30637
30829
|
prune: async (repoDir) => {
|
|
30638
30830
|
try {
|
|
30639
30831
|
await exec8("git", ["worktree", "prune"], { cwd: repoDir, timeout: 3e4 });
|
|
@@ -30687,7 +30879,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
30687
30879
|
}
|
|
30688
30880
|
let removed = 0;
|
|
30689
30881
|
for (const entry of entries) {
|
|
30690
|
-
const path2 =
|
|
30882
|
+
const path2 = join11(dir2, entry);
|
|
30691
30883
|
const known = pending.get(path2);
|
|
30692
30884
|
const row = known ?? { path: path2, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
|
|
30693
30885
|
pending.set(path2, row);
|
|
@@ -31306,7 +31498,7 @@ init_config2();
|
|
|
31306
31498
|
import { spawn as spawn3 } from "node:child_process";
|
|
31307
31499
|
import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
31308
31500
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
31309
|
-
import { dirname as dirname10, join as
|
|
31501
|
+
import { dirname as dirname10, join as join12 } from "node:path";
|
|
31310
31502
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
31311
31503
|
function consoleArgv(t) {
|
|
31312
31504
|
return [
|
|
@@ -31321,8 +31513,8 @@ function consoleArgv(t) {
|
|
|
31321
31513
|
];
|
|
31322
31514
|
}
|
|
31323
31515
|
function askpassScript() {
|
|
31324
|
-
const dir2 = mkdtempSync(
|
|
31325
|
-
const path2 =
|
|
31516
|
+
const dir2 = mkdtempSync(join12(tmpdir3(), "hanoman-askpass-"));
|
|
31517
|
+
const path2 = join12(dir2, "askpass.sh");
|
|
31326
31518
|
writeFileSync4(path2, `#!/bin/sh
|
|
31327
31519
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
31328
31520
|
`, { mode: 448 });
|
|
@@ -31383,15 +31575,15 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
31383
31575
|
// src/services/vps-audit.ts
|
|
31384
31576
|
init_db();
|
|
31385
31577
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
31386
|
-
import { join as
|
|
31578
|
+
import { join as join14 } from "node:path";
|
|
31387
31579
|
|
|
31388
31580
|
// src/runner/deps.ts
|
|
31389
31581
|
import { existsSync as existsSync11 } from "node:fs";
|
|
31390
|
-
import { dirname as dirname11, join as
|
|
31582
|
+
import { dirname as dirname11, join as join13 } from "node:path";
|
|
31391
31583
|
function repoRootFrom(startDir) {
|
|
31392
31584
|
let dir2 = startDir;
|
|
31393
31585
|
for (let i = 0; i < 8; i++) {
|
|
31394
|
-
if (existsSync11(
|
|
31586
|
+
if (existsSync11(join13(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
31395
31587
|
const parent = dirname11(dir2);
|
|
31396
31588
|
if (parent === dir2) break;
|
|
31397
31589
|
dir2 = parent;
|
|
@@ -31497,7 +31689,7 @@ function parseHealth(out3) {
|
|
|
31497
31689
|
return { uptime: h.uptime ?? "", disk: h.disk ?? "", mem: h.mem ?? "", load: h.load ?? "" };
|
|
31498
31690
|
}
|
|
31499
31691
|
var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
31500
|
-
var scriptPath = (f) =>
|
|
31692
|
+
var scriptPath = (f) => join14(repoRoot(), "server", "scripts", "vps", f);
|
|
31501
31693
|
async function itemStatesOf(vpsId) {
|
|
31502
31694
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
31503
31695
|
return Object.fromEntries(rows.map((s2) => [s2.itemId, { na: s2.na, attested: s2.attested }]));
|
|
@@ -31646,11 +31838,11 @@ init_config2();
|
|
|
31646
31838
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
31647
31839
|
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
31648
31840
|
import { homedir as homedir5 } from "node:os";
|
|
31649
|
-
import { join as
|
|
31650
|
-
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ??
|
|
31841
|
+
import { join as join15 } from "node:path";
|
|
31842
|
+
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? join15(homedir5(), ".hanoman");
|
|
31651
31843
|
function ensureHanomanKey() {
|
|
31652
31844
|
const dir2 = keyDir();
|
|
31653
|
-
const privPath =
|
|
31845
|
+
const privPath = join15(dir2, "id_ed25519");
|
|
31654
31846
|
const pubPath = `${privPath}.pub`;
|
|
31655
31847
|
if (!existsSync12(privPath)) {
|
|
31656
31848
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
@@ -31888,7 +32080,7 @@ init_config2();
|
|
|
31888
32080
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
31889
32081
|
import { readFileSync as readFileSync11 } from "node:fs";
|
|
31890
32082
|
import { homedir as homedir7 } from "node:os";
|
|
31891
|
-
import { join as
|
|
32083
|
+
import { join as join16 } from "node:path";
|
|
31892
32084
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
31893
32085
|
var TTL_MS2 = 3e4;
|
|
31894
32086
|
var lastOk = null;
|
|
@@ -31901,7 +32093,7 @@ var LABELS = {
|
|
|
31901
32093
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
31902
32094
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
31903
32095
|
function credsFile() {
|
|
31904
|
-
return
|
|
32096
|
+
return join16(effectiveStr("CLAUDE_CONFIG_DIR") ?? join16(homedir7(), ".claude"), ".credentials.json");
|
|
31905
32097
|
}
|
|
31906
32098
|
function readAccessToken() {
|
|
31907
32099
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
@@ -31975,14 +32167,14 @@ async function getLimits() {
|
|
|
31975
32167
|
// src/services/codex-limits.ts
|
|
31976
32168
|
import { open as open3, readdir as readdir3, stat } from "node:fs/promises";
|
|
31977
32169
|
import { homedir as homedir8 } from "node:os";
|
|
31978
|
-
import { join as
|
|
32170
|
+
import { join as join17 } from "node:path";
|
|
31979
32171
|
var TTL_MS3 = 3e4;
|
|
31980
32172
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
31981
32173
|
var TAIL_BYTES = 512 * 1024;
|
|
31982
32174
|
var MAX_FILES = 8;
|
|
31983
32175
|
var cache2 = null;
|
|
31984
32176
|
var freshUntil2 = 0;
|
|
31985
|
-
var codexSessionsDir = () =>
|
|
32177
|
+
var codexSessionsDir = () => join17(process.env.CODEX_HOME ?? join17(homedir8(), ".codex"), "sessions");
|
|
31986
32178
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
31987
32179
|
function windowLabel(minutes) {
|
|
31988
32180
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -32015,7 +32207,7 @@ async function recentRollouts(dir2) {
|
|
|
32015
32207
|
return [];
|
|
32016
32208
|
}
|
|
32017
32209
|
const stamped = await Promise.all(entries.map(async (rel) => {
|
|
32018
|
-
const full =
|
|
32210
|
+
const full = join17(dir2, rel);
|
|
32019
32211
|
try {
|
|
32020
32212
|
return { full, mtime: (await stat(full)).mtimeMs };
|
|
32021
32213
|
} catch {
|
|
@@ -32671,17 +32863,17 @@ async function session_results_default(app2) {
|
|
|
32671
32863
|
// src/services/session-history.ts
|
|
32672
32864
|
init_db();
|
|
32673
32865
|
init_pty();
|
|
32674
|
-
import { randomUUID as
|
|
32866
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
32675
32867
|
|
|
32676
32868
|
// src/services/transcript-store.ts
|
|
32677
32869
|
init_config2();
|
|
32678
32870
|
init_src2();
|
|
32679
|
-
import { randomUUID as
|
|
32871
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
32680
32872
|
import { mkdir as mkdir3, writeFile as writeFile2, readFile as readFile3, unlink as unlink3 } from "node:fs/promises";
|
|
32681
|
-
import { join as
|
|
32873
|
+
import { join as join18, resolve as resolve17, basename as basename4 } from "node:path";
|
|
32682
32874
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
32683
32875
|
function transcriptDir() {
|
|
32684
|
-
return resolve17(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ??
|
|
32876
|
+
return resolve17(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join18(resolveHome(), "transcripts"));
|
|
32685
32877
|
}
|
|
32686
32878
|
function clamp(text) {
|
|
32687
32879
|
const buf = Buffer.from(text, "utf8");
|
|
@@ -32698,21 +32890,21 @@ async function saveTranscript(text) {
|
|
|
32698
32890
|
const { body, truncated } = clamp(text);
|
|
32699
32891
|
const dir2 = transcriptDir();
|
|
32700
32892
|
await mkdir3(dir2, { recursive: true, mode: 448 });
|
|
32701
|
-
const key = `${
|
|
32702
|
-
await writeFile2(
|
|
32893
|
+
const key = `${randomUUID6()}.log`;
|
|
32894
|
+
await writeFile2(join18(dir2, key), body, { encoding: "utf8", mode: 384 });
|
|
32703
32895
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
32704
32896
|
}
|
|
32705
32897
|
async function readTranscript(key) {
|
|
32706
32898
|
if (!key) return null;
|
|
32707
32899
|
try {
|
|
32708
|
-
return await readFile3(
|
|
32900
|
+
return await readFile3(join18(transcriptDir(), basename4(key)), "utf8");
|
|
32709
32901
|
} catch {
|
|
32710
32902
|
return null;
|
|
32711
32903
|
}
|
|
32712
32904
|
}
|
|
32713
32905
|
async function deleteTranscript(key) {
|
|
32714
32906
|
if (!key) return;
|
|
32715
|
-
await unlink3(
|
|
32907
|
+
await unlink3(join18(transcriptDir(), basename4(key))).catch(() => {
|
|
32716
32908
|
});
|
|
32717
32909
|
}
|
|
32718
32910
|
|
|
@@ -32743,7 +32935,7 @@ async function titleFor(specId) {
|
|
|
32743
32935
|
async function beginSession(b) {
|
|
32744
32936
|
await prisma.sessionHistory.create({
|
|
32745
32937
|
data: {
|
|
32746
|
-
id:
|
|
32938
|
+
id: randomUUID7(),
|
|
32747
32939
|
sessionId: b.sessionId,
|
|
32748
32940
|
projectId: b.projectId,
|
|
32749
32941
|
specId: b.specId ?? null,
|
|
@@ -32998,10 +33190,10 @@ init_notifications();
|
|
|
32998
33190
|
init_uploads();
|
|
32999
33191
|
|
|
33000
33192
|
// src/services/upload-pipeline.ts
|
|
33001
|
-
import { randomUUID as
|
|
33193
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
33002
33194
|
import { spawn as spawn4 } from "node:child_process";
|
|
33003
|
-
import { mkdir as mkdir4, rename as
|
|
33004
|
-
import { basename as basename5, isAbsolute as isAbsolute4, join as
|
|
33195
|
+
import { mkdir as mkdir4, rename as rename3, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
|
|
33196
|
+
import { basename as basename5, isAbsolute as isAbsolute4, join as join19 } from "node:path";
|
|
33005
33197
|
|
|
33006
33198
|
// ../node_modules/.pnpm/strtok3@10.3.5/node_modules/strtok3/lib/stream/Errors.js
|
|
33007
33199
|
var defaultMessages = "End-Of-Stream";
|
|
@@ -40365,7 +40557,7 @@ var dist_default = constructor_default;
|
|
|
40365
40557
|
// src/services/upload-pipeline.ts
|
|
40366
40558
|
init_db();
|
|
40367
40559
|
init_uploads();
|
|
40368
|
-
var
|
|
40560
|
+
var UPLOAD_LIMITS2 = {
|
|
40369
40561
|
fileBytes: 5 * 1024 * 1024,
|
|
40370
40562
|
ticketBytes: 10 * 1024 * 1024,
|
|
40371
40563
|
projectBytes: 250 * 1024 * 1024,
|
|
@@ -40417,7 +40609,7 @@ function scannerFromEnv(path2) {
|
|
|
40417
40609
|
const timer9 = setTimeout(() => {
|
|
40418
40610
|
child.kill("SIGKILL");
|
|
40419
40611
|
reject2(new UploadError("UPLOAD_SCAN", "scanner timeout"));
|
|
40420
|
-
},
|
|
40612
|
+
}, UPLOAD_LIMITS2.scanMs);
|
|
40421
40613
|
child.once("error", (error) => {
|
|
40422
40614
|
clearTimeout(timer9);
|
|
40423
40615
|
reject2(error);
|
|
@@ -40434,9 +40626,9 @@ function safeFilename(input, extension2) {
|
|
|
40434
40626
|
return `${stem || "upload"}${extension2}`;
|
|
40435
40627
|
}
|
|
40436
40628
|
async function processUpload(input, deps = {}) {
|
|
40437
|
-
if (input.buffer.byteLength === 0 || input.buffer.byteLength >
|
|
40629
|
+
if (input.buffer.byteLength === 0 || input.buffer.byteLength > UPLOAD_LIMITS2.fileBytes)
|
|
40438
40630
|
throw new UploadError("UPLOAD_QUOTA", "file quota exceeded");
|
|
40439
|
-
if (input.ticketBytes + input.buffer.byteLength >
|
|
40631
|
+
if (input.ticketBytes + input.buffer.byteLength > UPLOAD_LIMITS2.ticketBytes)
|
|
40440
40632
|
throw new UploadError("UPLOAD_QUOTA", "ticket quota exceeded");
|
|
40441
40633
|
const detected = await fileTypeFromBuffer(input.buffer);
|
|
40442
40634
|
if (!detected || !(detected.mime in TYPES) || detected.mime !== input.clientMime)
|
|
@@ -40447,14 +40639,14 @@ async function processUpload(input, deps = {}) {
|
|
|
40447
40639
|
let height;
|
|
40448
40640
|
let normalized;
|
|
40449
40641
|
try {
|
|
40450
|
-
const source = dist_default(input.buffer, { failOn: "error", limitInputPixels:
|
|
40451
|
-
const metadata2 = await timeout2(source.metadata(),
|
|
40642
|
+
const source = dist_default(input.buffer, { failOn: "error", limitInputPixels: UPLOAD_LIMITS2.pixels, sequentialRead: true });
|
|
40643
|
+
const metadata2 = await timeout2(source.metadata(), UPLOAD_LIMITS2.decodeMs, "UPLOAD_DECODE");
|
|
40452
40644
|
width = metadata2.width ?? 0;
|
|
40453
40645
|
height = metadata2.height ?? 0;
|
|
40454
|
-
if (!width || !height || width >
|
|
40646
|
+
if (!width || !height || width > UPLOAD_LIMITS2.dimension || height > UPLOAD_LIMITS2.dimension || width * height > UPLOAD_LIMITS2.pixels) throw new UploadError("UPLOAD_PIXELS", "image dimensions exceed policy");
|
|
40455
40647
|
normalized = await timeout2(
|
|
40456
|
-
dist_default(input.buffer, { failOn: "error", limitInputPixels:
|
|
40457
|
-
|
|
40648
|
+
dist_default(input.buffer, { failOn: "error", limitInputPixels: UPLOAD_LIMITS2.pixels, sequentialRead: true }).rotate().toFormat(type.format).toBuffer(),
|
|
40649
|
+
UPLOAD_LIMITS2.decodeMs,
|
|
40458
40650
|
"UPLOAD_DECODE"
|
|
40459
40651
|
);
|
|
40460
40652
|
} catch (error) {
|
|
@@ -40462,21 +40654,21 @@ async function processUpload(input, deps = {}) {
|
|
|
40462
40654
|
throw new UploadError("UPLOAD_DECODE", "image decode failed");
|
|
40463
40655
|
}
|
|
40464
40656
|
const usage2 = await (deps.usage ?? defaultUsage)(input.projectId);
|
|
40465
|
-
if (usage2.project + normalized.byteLength >
|
|
40657
|
+
if (usage2.project + normalized.byteLength > UPLOAD_LIMITS2.projectBytes || usage2.global + normalized.byteLength > UPLOAD_LIMITS2.globalBytes)
|
|
40466
40658
|
throw new UploadError("UPLOAD_QUOTA", "storage quota exceeded");
|
|
40467
40659
|
const storageDir = deps.storageDir ?? uploadDir();
|
|
40468
|
-
const quarantineDir =
|
|
40660
|
+
const quarantineDir = join19(storageDir, ".quarantine");
|
|
40469
40661
|
await mkdir4(quarantineDir, { recursive: true, mode: 448 });
|
|
40470
40662
|
await mkdir4(storageDir, { recursive: true, mode: 448 });
|
|
40471
|
-
const quarantine =
|
|
40472
|
-
const storageKey = `${
|
|
40663
|
+
const quarantine = join19(quarantineDir, `${randomUUID8()}.upload`);
|
|
40664
|
+
const storageKey = `${randomUUID8()}${type.extension}`;
|
|
40473
40665
|
await writeFile3(quarantine, normalized, { mode: 384, flag: "wx" });
|
|
40474
40666
|
try {
|
|
40475
|
-
await timeout2((deps.scanner ?? scannerFromEnv)(quarantine),
|
|
40667
|
+
await timeout2((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS2.scanMs, "UPLOAD_SCAN");
|
|
40476
40668
|
const finalUsage = await (deps.usage ?? defaultUsage)(input.projectId);
|
|
40477
|
-
if (finalUsage.project + normalized.byteLength >
|
|
40669
|
+
if (finalUsage.project + normalized.byteLength > UPLOAD_LIMITS2.projectBytes || finalUsage.global + normalized.byteLength > UPLOAD_LIMITS2.globalBytes)
|
|
40478
40670
|
throw new UploadError("UPLOAD_QUOTA", "storage quota exceeded");
|
|
40479
|
-
await
|
|
40671
|
+
await rename3(quarantine, join19(storageDir, storageKey));
|
|
40480
40672
|
} catch (error) {
|
|
40481
40673
|
await unlink4(quarantine).catch(() => {
|
|
40482
40674
|
});
|
|
@@ -41594,10 +41786,10 @@ init_config2();
|
|
|
41594
41786
|
init_pty();
|
|
41595
41787
|
init_session_sandbox();
|
|
41596
41788
|
import { execFile as execFile10 } from "node:child_process";
|
|
41597
|
-
import { randomUUID as
|
|
41789
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
41598
41790
|
import { mkdirSync as mkdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
41599
41791
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
41600
|
-
import { join as
|
|
41792
|
+
import { join as join20 } from "node:path";
|
|
41601
41793
|
var binFor = (agent) => agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
41602
41794
|
function leadArgv(o) {
|
|
41603
41795
|
if (o.agent === "codex") {
|
|
@@ -41625,11 +41817,11 @@ function leadProcess(prompt, o, env = process.env) {
|
|
|
41625
41817
|
const mode = env.HANOMAN_SESSION_SANDBOX ?? (env.NODE_ENV === "production" ? "required" : "off");
|
|
41626
41818
|
if (mode === "off") return { file, args: directArgs, cwd: o.cwd, cleanup: () => {
|
|
41627
41819
|
} };
|
|
41628
|
-
const promptDir =
|
|
41820
|
+
const promptDir = join20(tmpdir4(), "hanoman-prompts");
|
|
41629
41821
|
mkdirSync9(promptDir, { recursive: true, mode: 448 });
|
|
41630
|
-
const promptFile =
|
|
41822
|
+
const promptFile = join20(promptDir, `oneshot-${randomUUID9()}`);
|
|
41631
41823
|
writeFileSync5(promptFile, prompt, { flag: "wx", mode: 384 });
|
|
41632
|
-
const workspace = o.cwd ??
|
|
41824
|
+
const workspace = o.cwd ?? join20(tmpdir4(), `hanoman-oneshot-${randomUUID9()}`);
|
|
41633
41825
|
if (!o.cwd) mkdirSync9(workspace, { recursive: false, mode: 448 });
|
|
41634
41826
|
try {
|
|
41635
41827
|
const argsWithoutPrompt = directArgs.slice(0, -1);
|
|
@@ -43418,7 +43610,7 @@ init_db();
|
|
|
43418
43610
|
init_src();
|
|
43419
43611
|
import { readFileSync as readFileSync13 } from "node:fs";
|
|
43420
43612
|
import { homedir as homedir9 } from "node:os";
|
|
43421
|
-
import { join as
|
|
43613
|
+
import { join as join21 } from "node:path";
|
|
43422
43614
|
var home = () => process.env.HOME || homedir9();
|
|
43423
43615
|
var readJson2 = (path2) => {
|
|
43424
43616
|
try {
|
|
@@ -43435,7 +43627,7 @@ var serversOf = (node) => {
|
|
|
43435
43627
|
var codexServers = () => {
|
|
43436
43628
|
let text;
|
|
43437
43629
|
try {
|
|
43438
|
-
text = readFileSync13(
|
|
43630
|
+
text = readFileSync13(join21(home(), ".codex", "config.toml"), "utf8");
|
|
43439
43631
|
} catch {
|
|
43440
43632
|
return [];
|
|
43441
43633
|
}
|
|
@@ -43448,12 +43640,12 @@ var codexServers = () => {
|
|
|
43448
43640
|
};
|
|
43449
43641
|
function mcpServerNames(repoDir) {
|
|
43450
43642
|
const names = [];
|
|
43451
|
-
const claudeJson = readJson2(
|
|
43643
|
+
const claudeJson = readJson2(join21(home(), ".claude.json"));
|
|
43452
43644
|
names.push(...serversOf(claudeJson));
|
|
43453
43645
|
if (repoDir) {
|
|
43454
43646
|
const projects = claudeJson?.projects;
|
|
43455
43647
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
43456
|
-
names.push(...serversOf(readJson2(
|
|
43648
|
+
names.push(...serversOf(readJson2(join21(repoDir, ".mcp.json"))));
|
|
43457
43649
|
}
|
|
43458
43650
|
names.push(...codexServers());
|
|
43459
43651
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -44906,17 +45098,17 @@ init_db();
|
|
|
44906
45098
|
|
|
44907
45099
|
// src/services/bootstrap.ts
|
|
44908
45100
|
import { createHash as createHash9, randomBytes as randomBytes9, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
44909
|
-
import { chmod, lstat as
|
|
44910
|
-
import { join as
|
|
45101
|
+
import { chmod, lstat as lstat3, mkdir as mkdir5, readFile as readFile4, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
|
|
45102
|
+
import { join as join22 } from "node:path";
|
|
44911
45103
|
var TTL_MS5 = 15 * 6e4;
|
|
44912
45104
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
44913
45105
|
var BootstrapError = class extends Error {
|
|
44914
45106
|
code = "BOOTSTRAP_PROOF";
|
|
44915
45107
|
};
|
|
44916
|
-
var tokenPath = (home3) =>
|
|
45108
|
+
var tokenPath = (home3) => join22(home3, SETUP_TOKEN_FILE);
|
|
44917
45109
|
async function readStored(home3) {
|
|
44918
45110
|
try {
|
|
44919
|
-
const info = await
|
|
45111
|
+
const info = await lstat3(tokenPath(home3));
|
|
44920
45112
|
if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
|
|
44921
45113
|
const [token, expires] = (await readFile4(tokenPath(home3), "utf8")).trim().split("\n");
|
|
44922
45114
|
const expiresAt = Date.parse(expires ?? "");
|
|
@@ -44929,7 +45121,7 @@ async function readStored(home3) {
|
|
|
44929
45121
|
}
|
|
44930
45122
|
async function ensureSetupToken(home3, now = Date.now()) {
|
|
44931
45123
|
await mkdir5(home3, { recursive: true, mode: 448 });
|
|
44932
|
-
const homeInfo = await
|
|
45124
|
+
const homeInfo = await lstat3(home3);
|
|
44933
45125
|
if (homeInfo.isSymbolicLink() || !homeInfo.isDirectory()) throw new BootstrapError("invalid setup home");
|
|
44934
45126
|
await chmod(home3, 448);
|
|
44935
45127
|
try {
|
|
@@ -45123,7 +45315,12 @@ var IDE_SUBS = /* @__PURE__ */ new Set([
|
|
|
45123
45315
|
"remotes",
|
|
45124
45316
|
"compare",
|
|
45125
45317
|
"archive",
|
|
45126
|
-
"pr-url"
|
|
45318
|
+
"pr-url",
|
|
45319
|
+
// ADR-0121 · operasi berkas Explorer. `rw()` menurunkan read/write DARI METHOD, jadi
|
|
45320
|
+
// POST/PATCH/DELETE menuntut ide:write — capability yang sudah memberi hak menimpa isi
|
|
45321
|
+
// berkas apa pun lewat PUT /file (hindari kelas bug SPEC-405: prefix tanpa lihat method).
|
|
45322
|
+
"entry",
|
|
45323
|
+
"upload"
|
|
45127
45324
|
]);
|
|
45128
45325
|
function capabilityForRoute(method, path2) {
|
|
45129
45326
|
const read2 = method === "GET" || method === "HEAD";
|
|
@@ -45828,7 +46025,7 @@ init_tap();
|
|
|
45828
46025
|
// src/services/webhooks/emit.ts
|
|
45829
46026
|
init_src();
|
|
45830
46027
|
init_db();
|
|
45831
|
-
import { randomUUID as
|
|
46028
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
45832
46029
|
function skipped(def2, row) {
|
|
45833
46030
|
if (!def2.skipWhen || !row) return false;
|
|
45834
46031
|
return row[def2.skipWhen.field] === def2.skipWhen.equals;
|
|
@@ -45894,7 +46091,7 @@ async function emitWebhook(i) {
|
|
|
45894
46091
|
const targets = matchingEndpoints(type, projectId);
|
|
45895
46092
|
if (!targets.length) return;
|
|
45896
46093
|
const name2 = projectId ? (await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } }))?.name ?? null : null;
|
|
45897
|
-
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${
|
|
46094
|
+
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${randomUUID10().replace(/-/g, "")}`);
|
|
45898
46095
|
if (!env) return;
|
|
45899
46096
|
await enqueueEnvelope(env, targets);
|
|
45900
46097
|
} catch (e) {
|
|
@@ -46206,14 +46403,14 @@ init_session_sandbox();
|
|
|
46206
46403
|
init_src2();
|
|
46207
46404
|
|
|
46208
46405
|
// src/services/secure-home.ts
|
|
46209
|
-
import { chmod as chmod2, lstat as
|
|
46406
|
+
import { chmod as chmod2, lstat as lstat4, mkdir as mkdir6 } from "node:fs/promises";
|
|
46210
46407
|
import { isAbsolute as isAbsolute6, resolve as resolve18 } from "node:path";
|
|
46211
46408
|
var HomePermissionError = class extends Error {
|
|
46212
46409
|
code = "HOME_SYMLINK";
|
|
46213
46410
|
};
|
|
46214
46411
|
async function assertNoSymlink(path2, allowMissing) {
|
|
46215
46412
|
try {
|
|
46216
|
-
const info = await
|
|
46413
|
+
const info = await lstat4(resolve18(path2));
|
|
46217
46414
|
if (info.isSymbolicLink()) throw new HomePermissionError(`symlink ditolak: ${path2}`);
|
|
46218
46415
|
} catch (error) {
|
|
46219
46416
|
if (error.code === "ENOENT" && allowMissing) return;
|