@sandagent/daemon 0.8.5

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.
Files changed (48) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +378 -0
  3. package/dist/bundle.mjs +219213 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +20 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +3 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/nextjs.d.ts +20 -0
  13. package/dist/nextjs.d.ts.map +1 -0
  14. package/dist/nextjs.js +45 -0
  15. package/dist/nextjs.js.map +1 -0
  16. package/dist/router.d.ts +13 -0
  17. package/dist/router.d.ts.map +1 -0
  18. package/dist/router.js +58 -0
  19. package/dist/router.js.map +1 -0
  20. package/dist/routes/coding.d.ts +22 -0
  21. package/dist/routes/coding.d.ts.map +1 -0
  22. package/dist/routes/coding.js +84 -0
  23. package/dist/routes/coding.js.map +1 -0
  24. package/dist/routes/fs.d.ts +83 -0
  25. package/dist/routes/fs.d.ts.map +1 -0
  26. package/dist/routes/fs.js +135 -0
  27. package/dist/routes/fs.js.map +1 -0
  28. package/dist/routes/git.d.ts +37 -0
  29. package/dist/routes/git.d.ts.map +1 -0
  30. package/dist/routes/git.js +106 -0
  31. package/dist/routes/git.js.map +1 -0
  32. package/dist/routes/health.d.ts +7 -0
  33. package/dist/routes/health.d.ts.map +1 -0
  34. package/dist/routes/health.js +5 -0
  35. package/dist/routes/health.js.map +1 -0
  36. package/dist/routes/volumes.d.ts +16 -0
  37. package/dist/routes/volumes.d.ts.map +1 -0
  38. package/dist/routes/volumes.js +21 -0
  39. package/dist/routes/volumes.js.map +1 -0
  40. package/dist/server.d.ts +8 -0
  41. package/dist/server.d.ts.map +1 -0
  42. package/dist/server.js +37 -0
  43. package/dist/server.js.map +1 -0
  44. package/dist/utils.d.ts +25 -0
  45. package/dist/utils.d.ts.map +1 -0
  46. package/dist/utils.js +51 -0
  47. package/dist/utils.js.map +1 -0
  48. package/package.json +54 -0
@@ -0,0 +1,106 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { AppError, ensureDir, ok, resolveUnderRoot, resolveVolumeRoot, } from "../utils.js";
4
+ const exec = promisify(execFile);
5
+ const ALLOWED_GIT_COMMANDS = new Set([
6
+ "status",
7
+ "log",
8
+ "diff",
9
+ "show",
10
+ "branch",
11
+ "checkout",
12
+ "add",
13
+ "commit",
14
+ "reset",
15
+ "init",
16
+ "rev-parse",
17
+ "fetch",
18
+ "pull",
19
+ "push",
20
+ "merge",
21
+ "rebase",
22
+ "remote",
23
+ "tag",
24
+ "ls-files",
25
+ ]);
26
+ async function runGit(cwd, args) {
27
+ try {
28
+ const { stdout, stderr } = await exec("git", ["-C", cwd, ...args], {
29
+ maxBuffer: 10 * 1024 * 1024,
30
+ });
31
+ return { stdout, stderr, code: 0 };
32
+ }
33
+ catch (err) {
34
+ const e = err;
35
+ if (e.code === "ENOENT")
36
+ throw new AppError(500, "git not found");
37
+ return {
38
+ stdout: e.stdout ?? "",
39
+ stderr: e.stderr ?? "",
40
+ code: typeof e.code === "number" ? e.code : 1,
41
+ };
42
+ }
43
+ }
44
+ export async function gitStatus(state, body) {
45
+ const root = resolveVolumeRoot(state, body.volume);
46
+ const repo = resolveUnderRoot(root, body.repo);
47
+ const result = await runGit(repo, ["status", "--short", "--branch"]);
48
+ return ok(result);
49
+ }
50
+ export async function gitExec(state, body) {
51
+ if (!body.args?.length)
52
+ throw new AppError(400, "args cannot be empty");
53
+ if (!ALLOWED_GIT_COMMANDS.has(body.args[0])) {
54
+ throw new AppError(400, `unsupported git command: ${body.args[0]}`);
55
+ }
56
+ const root = resolveVolumeRoot(state, body.volume);
57
+ const repo = resolveUnderRoot(root, body.repo);
58
+ return ok(await runGit(repo, body.args));
59
+ }
60
+ export async function gitClone(state, body) {
61
+ const root = resolveVolumeRoot(state, body.volume);
62
+ const parent = resolveUnderRoot(root, body.repo_parent);
63
+ await ensureDir(parent);
64
+ const args = ["clone"];
65
+ if (body.depth)
66
+ args.push("--depth", String(body.depth));
67
+ if (body.branch)
68
+ args.push("--branch", body.branch);
69
+ args.push(body.url);
70
+ if (body.target_dir)
71
+ args.push(body.target_dir);
72
+ const command = await runGit(parent, args);
73
+ // Infer repo path
74
+ const dirName = body.target_dir ??
75
+ body.url
76
+ .replace(/\/$/, "")
77
+ .split(/[/:]/)
78
+ .pop()
79
+ .replace(/\.git$/, "");
80
+ const repoPath = resolveUnderRoot(parent, dirName);
81
+ // List tracked files
82
+ const lsResult = await runGit(repoPath, ["ls-files"]).catch(() => ({
83
+ stdout: "",
84
+ stderr: "",
85
+ code: 1,
86
+ }));
87
+ const allFiles = lsResult.stdout.split("\n").filter(Boolean);
88
+ const limit = Math.min(body.list_files_limit ?? 200, 5000);
89
+ return ok({
90
+ repo_path: repoPath,
91
+ tracked_files_count: allFiles.length,
92
+ tracked_files: allFiles.slice(0, limit),
93
+ tracked_files_truncated: allFiles.length > limit,
94
+ command,
95
+ });
96
+ }
97
+ export async function gitInit(state, body) {
98
+ const root = resolveVolumeRoot(state, body.volume);
99
+ const repo = resolveUnderRoot(root, body.repo);
100
+ await ensureDir(repo);
101
+ const args = ["init"];
102
+ if (body.initial_branch)
103
+ args.push("-b", body.initial_branch);
104
+ return ok(await runGit(repo, args));
105
+ }
106
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { AppState } from "../utils.js";
2
+ export declare function healthHandler(state: AppState): import("../utils.js").ApiEnvelope<{
3
+ status: string;
4
+ root: string;
5
+ volumesRoot: string;
6
+ }>;
7
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ;;;;GAE5C"}
@@ -0,0 +1,5 @@
1
+ import { ok } from "../utils.js";
2
+ export function healthHandler(state) {
3
+ return ok({ status: "ok", root: state.root, volumesRoot: state.volumesRoot });
4
+ }
5
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,MAAM,UAAU,aAAa,CAAC,KAAe;IAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { AppState } from "../utils.js";
2
+ export declare function volumesList(state: AppState): Promise<import("../utils.js").ApiEnvelope<{
3
+ volumes: string[];
4
+ }>>;
5
+ export declare function volumesEnsure(state: AppState, body: {
6
+ volume: string;
7
+ }): Promise<import("../utils.js").ApiEnvelope<{
8
+ path: string;
9
+ }>>;
10
+ export declare function volumesRemove(state: AppState, body: {
11
+ volume: string;
12
+ recursive?: boolean;
13
+ }): Promise<import("../utils.js").ApiEnvelope<{
14
+ path: string;
15
+ }>>;
16
+ //# sourceMappingURL=volumes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volumes.d.ts","sourceRoot":"","sources":["../../src/routes/volumes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,wBAAsB,WAAW,CAAC,KAAK,EAAE,QAAQ;;IAOhD;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;IAI5E;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE;;IAK9C"}
@@ -0,0 +1,21 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { ensureDir, ok, resolveVolumeRoot } from "../utils.js";
3
+ export async function volumesList(state) {
4
+ const entries = await fs.readdir(state.volumesRoot, { withFileTypes: true });
5
+ const volumes = entries
6
+ .filter((e) => e.isDirectory())
7
+ .map((e) => e.name)
8
+ .sort();
9
+ return ok({ volumes });
10
+ }
11
+ export async function volumesEnsure(state, body) {
12
+ const root = resolveVolumeRoot(state, body.volume);
13
+ await ensureDir(root);
14
+ return ok({ path: root });
15
+ }
16
+ export async function volumesRemove(state, body) {
17
+ const root = resolveVolumeRoot(state, body.volume);
18
+ await fs.rm(root, { recursive: body.recursive ?? true });
19
+ return ok({ path: root });
20
+ }
21
+ //# sourceMappingURL=volumes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volumes.js","sourceRoot":"","sources":["../../src/routes/volumes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAe;IAC/C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,OAAO;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,IAAI,EAAE,CAAC;IACV,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAe,EAAE,IAAwB;IAC3E,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAe,EACf,IAA6C;IAE7C,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC,CAAC;IACzD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,8 @@
1
+ import * as http from "node:http";
2
+ export interface DaemonConfig {
3
+ host: string;
4
+ port: number;
5
+ root: string;
6
+ }
7
+ export declare function createDaemon(config: DaemonConfig): http.Server;
8
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAMlC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC,MAAM,CA8B9D"}
package/dist/server.js ADDED
@@ -0,0 +1,37 @@
1
+ import * as http from "node:http";
2
+ import { URL } from "node:url";
3
+ import { DaemonRouter } from "./router.js";
4
+ import { sandagentRun } from "./routes/coding.js";
5
+ import { fail } from "./utils.js";
6
+ export function createDaemon(config) {
7
+ const router = new DaemonRouter({ root: config.root });
8
+ const env = process.env;
9
+ return http.createServer(async (req, res) => {
10
+ const method = req.method ?? "GET";
11
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
12
+ const pathname = url.pathname;
13
+ // Streaming: /api/coding/run
14
+ if (method === "POST" && pathname === "/api/coding/run") {
15
+ const body = JSON.parse((await readBody(req)) || "{}");
16
+ return sandagentRun(body, res, env);
17
+ }
18
+ // Standard JSON routes
19
+ const params = method === "GET"
20
+ ? Object.fromEntries(url.searchParams)
21
+ : JSON.parse((await readBody(req)) || "{}");
22
+ const result = await router.handle(method, pathname, params);
23
+ const status = result?.status ?? 404;
24
+ const body = result?.body ?? fail(`not found: ${method} ${pathname}`);
25
+ res.writeHead(status, { "Content-Type": "application/json" });
26
+ res.end(JSON.stringify(body));
27
+ });
28
+ }
29
+ function readBody(req) {
30
+ return new Promise((resolve, reject) => {
31
+ const chunks = [];
32
+ req.on("data", (c) => chunks.push(c));
33
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
34
+ req.on("error", reject);
35
+ });
36
+ }
37
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQlC,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,OAAO,CAAC,GAA6B,CAAC;IAElD,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,GAAG,CAAC,GAAG,IAAI,GAAG,EACd,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAC5C,CAAC;QACF,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAE9B,6BAA6B;QAC7B,IAAI,MAAM,KAAK,MAAM,IAAI,QAAQ,KAAK,iBAAiB,EAAE,CAAC;YACxD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YACvD,OAAO,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QAED,uBAAuB;QACvB,MAAM,MAAM,GACV,MAAM,KAAK,KAAK;YACd,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC;YACtC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;QAEhD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC,cAAc,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAC;QACtE,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,GAAyB;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC/D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,25 @@
1
+ export interface ApiEnvelope<T = unknown> {
2
+ ok: boolean;
3
+ data: T | null;
4
+ error: string | null;
5
+ }
6
+ export declare function ok<T>(data: T): ApiEnvelope<T>;
7
+ export declare function fail(error: string): ApiEnvelope<null>;
8
+ export interface AppState {
9
+ root: string;
10
+ volumesRoot: string;
11
+ }
12
+ /**
13
+ * Resolve volume root: if volume given, use volumesRoot/volume; otherwise use root.
14
+ */
15
+ export declare function resolveVolumeRoot(state: AppState, volume?: string): string;
16
+ /**
17
+ * Resolve a user-provided path safely under a root, preventing traversal.
18
+ */
19
+ export declare function resolveUnderRoot(root: string, raw: string): string;
20
+ export declare class AppError extends Error {
21
+ status: number;
22
+ constructor(status: number, message: string);
23
+ }
24
+ export declare function ensureDir(dir: string): Promise<void>;
25
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAE7C;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAErD;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAYlE;AAQD,qBAAa,QAAS,SAAQ,KAAK;IAExB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM;CAIlB;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D"}
package/dist/utils.js ADDED
@@ -0,0 +1,51 @@
1
+ // Shared types and utilities
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ export function ok(data) {
5
+ return { ok: true, data, error: null };
6
+ }
7
+ export function fail(error) {
8
+ return { ok: false, data: null, error };
9
+ }
10
+ /**
11
+ * Resolve volume root: if volume given, use volumesRoot/volume; otherwise use root.
12
+ */
13
+ export function resolveVolumeRoot(state, volume) {
14
+ if (!volume)
15
+ return state.root;
16
+ validateVolumeName(volume);
17
+ return path.join(state.volumesRoot, volume);
18
+ }
19
+ /**
20
+ * Resolve a user-provided path safely under a root, preventing traversal.
21
+ */
22
+ export function resolveUnderRoot(root, raw) {
23
+ if (!raw.trim())
24
+ throw new AppError(400, "empty path");
25
+ const normalized = path.normalize(raw);
26
+ // Reject absolute paths and traversal
27
+ if (path.isAbsolute(normalized) && !normalized.startsWith(root)) {
28
+ throw new AppError(400, `invalid path: ${raw}`);
29
+ }
30
+ const resolved = path.resolve(root, normalized.replace(/^\/+/, ""));
31
+ if (!resolved.startsWith(root)) {
32
+ throw new AppError(400, `path traversal rejected: ${raw}`);
33
+ }
34
+ return resolved;
35
+ }
36
+ function validateVolumeName(name) {
37
+ if (!name || !/^[A-Za-z0-9._-]+$/.test(name)) {
38
+ throw new AppError(400, `invalid volume: ${name}`);
39
+ }
40
+ }
41
+ export class AppError extends Error {
42
+ status;
43
+ constructor(status, message) {
44
+ super(message);
45
+ this.status = status;
46
+ }
47
+ }
48
+ export async function ensureDir(dir) {
49
+ await fs.mkdir(dir, { recursive: true });
50
+ }
51
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAE7B,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAQlC,MAAM,UAAU,EAAE,CAAI,IAAO;IAC3B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,KAAa;IAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC;AAOD;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAe,EAAE,MAAe;IAChE,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAC/B,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,GAAW;IACxD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACvC,sCAAsC;IACtC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,4BAA4B,GAAG,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,mBAAmB,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,MAAM,OAAO,QAAS,SAAQ,KAAK;IAExB;IADT,YACS,MAAc,EACrB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,WAAM,GAAN,MAAM,CAAQ;IAIvB,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@sandagent/daemon",
3
+ "version": "0.8.5",
4
+ "description": "SandAgent Daemon - Unified API gateway for sandbox services (file, git, volumes)",
5
+ "type": "module",
6
+ "bin": {
7
+ "sandagent-daemon": "./dist/bundle.mjs"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./nextjs": {
17
+ "import": "./dist/nextjs.js",
18
+ "types": "./dist/nextjs.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/vikadata/sandagent.git",
27
+ "directory": "apps/daemon"
28
+ },
29
+ "license": "Apache-2.0",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "dependencies": {
37
+ "@sandagent/runner-core": "0.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.10.0",
41
+ "esbuild": "^0.27.2",
42
+ "typescript": "^5.3.0",
43
+ "vitest": "^1.6.1"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc && pnpm bundle",
47
+ "bundle": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/bundle.mjs",
48
+ "dev": "tsc --watch",
49
+ "clean": "rm -rf dist",
50
+ "typecheck": "tsc --noEmit",
51
+ "lint": "echo 'no lint configured'",
52
+ "test": "vitest run --passWithNoTests"
53
+ }
54
+ }