@sandagent/daemon 0.9.4 → 0.9.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.
- package/README.md +49 -0
- package/dist/bundle.mjs +91 -0
- package/dist/index.js +91 -0
- package/dist/multipart.d.ts +13 -0
- package/dist/multipart.d.ts.map +1 -0
- package/dist/nextjs.d.ts.map +1 -1
- package/dist/nextjs.js +79 -0
- package/dist/routes/fs.d.ts +22 -0
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -325,6 +325,55 @@ Response: `application/x-ndjson` chunked stream — each line is an AI SDK UI me
|
|
|
325
325
|
| POST | `/api/fs/remove` | `{"path":"tmp","recursive":true}` |
|
|
326
326
|
| POST | `/api/fs/move` | `{"from":"a.txt","to":"b.txt"}` |
|
|
327
327
|
| POST | `/api/fs/copy` | `{"from":"a.txt","to":"b.txt"}` |
|
|
328
|
+
| POST | `/api/fs/upload` | `multipart/form-data` — see below |
|
|
329
|
+
|
|
330
|
+
#### `POST /api/fs/upload`
|
|
331
|
+
|
|
332
|
+
Upload one or more files via `multipart/form-data`.
|
|
333
|
+
|
|
334
|
+
Form fields:
|
|
335
|
+
|
|
336
|
+
| Field | Type | Required | Description |
|
|
337
|
+
|-------|------|----------|-------------|
|
|
338
|
+
| `path` | string | No | Target directory (default: `.`) |
|
|
339
|
+
| `volume` | string | No | Volume name for multi-tenant isolation |
|
|
340
|
+
| `create_dirs` | string | No | Create parent dirs (default: `"true"`) |
|
|
341
|
+
| `file` | file | Yes | One or more files to upload |
|
|
342
|
+
|
|
343
|
+
Example:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Upload a single file
|
|
347
|
+
curl -X POST http://localhost:3080/api/fs/upload \
|
|
348
|
+
-F "path=uploads" \
|
|
349
|
+
-F "file=@local-file.txt"
|
|
350
|
+
|
|
351
|
+
# Upload multiple files
|
|
352
|
+
curl -X POST http://localhost:3080/api/fs/upload \
|
|
353
|
+
-F "path=data" \
|
|
354
|
+
-F "file=@report.csv" \
|
|
355
|
+
-F "file=@image.png"
|
|
356
|
+
|
|
357
|
+
# Upload to a specific volume
|
|
358
|
+
curl -X POST http://localhost:3080/api/fs/upload \
|
|
359
|
+
-F "path=docs" \
|
|
360
|
+
-F "volume=vol-001" \
|
|
361
|
+
-F "file=@readme.md"
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Response:
|
|
365
|
+
|
|
366
|
+
```json
|
|
367
|
+
{
|
|
368
|
+
"ok": true,
|
|
369
|
+
"data": {
|
|
370
|
+
"files": [
|
|
371
|
+
{ "fieldname": "file", "filename": "report.csv", "path": "/workspace/data/report.csv", "size": 1234 }
|
|
372
|
+
]
|
|
373
|
+
},
|
|
374
|
+
"error": null
|
|
375
|
+
}
|
|
376
|
+
```
|
|
328
377
|
|
|
329
378
|
All fs endpoints accept optional `volume` for multi-tenant isolation.
|
|
330
379
|
|
package/dist/bundle.mjs
CHANGED
|
@@ -206419,6 +206419,42 @@ var require_lib4 = __commonJS({
|
|
|
206419
206419
|
import * as http3 from "node:http";
|
|
206420
206420
|
import { URL as URL2 } from "node:url";
|
|
206421
206421
|
|
|
206422
|
+
// src/multipart.ts
|
|
206423
|
+
function parseMultipart(contentType, body) {
|
|
206424
|
+
const match2 = contentType.match(/boundary=(?:"([^"]+)"|([^\s;]+))/);
|
|
206425
|
+
if (!match2) throw new Error("missing multipart boundary");
|
|
206426
|
+
const boundary = match2[1] ?? match2[2];
|
|
206427
|
+
const delimiter2 = Buffer.from(`--${boundary}`);
|
|
206428
|
+
const fields = {};
|
|
206429
|
+
const files = [];
|
|
206430
|
+
let start = body.indexOf(delimiter2) + delimiter2.length;
|
|
206431
|
+
while (start < body.length) {
|
|
206432
|
+
const end = body.indexOf(delimiter2, start);
|
|
206433
|
+
if (end === -1) break;
|
|
206434
|
+
const part = body.subarray(start, end);
|
|
206435
|
+
const partStart = part[0] === 13 && part[1] === 10 ? 2 : 0;
|
|
206436
|
+
const headerEnd = part.indexOf("\r\n\r\n", partStart);
|
|
206437
|
+
if (headerEnd === -1) {
|
|
206438
|
+
start = end + delimiter2.length;
|
|
206439
|
+
continue;
|
|
206440
|
+
}
|
|
206441
|
+
const headerStr = part.subarray(partStart, headerEnd).toString("utf-8");
|
|
206442
|
+
let data = part.subarray(headerEnd + 4);
|
|
206443
|
+
if (data.length >= 2 && data[data.length - 2] === 13 && data[data.length - 1] === 10) {
|
|
206444
|
+
data = data.subarray(0, data.length - 2);
|
|
206445
|
+
}
|
|
206446
|
+
const nameMatch = headerStr.match(/name="([^"]+)"/);
|
|
206447
|
+
const filenameMatch = headerStr.match(/filename="([^"]+)"/);
|
|
206448
|
+
if (filenameMatch && nameMatch) {
|
|
206449
|
+
files.push({ filename: filenameMatch[1], data: Buffer.from(data) });
|
|
206450
|
+
} else if (nameMatch) {
|
|
206451
|
+
fields[nameMatch[1]] = data.toString("utf-8");
|
|
206452
|
+
}
|
|
206453
|
+
start = end + delimiter2.length;
|
|
206454
|
+
}
|
|
206455
|
+
return { fields, files };
|
|
206456
|
+
}
|
|
206457
|
+
|
|
206422
206458
|
// src/routes/fs.ts
|
|
206423
206459
|
import * as fs2 from "node:fs/promises";
|
|
206424
206460
|
import * as path2 from "node:path";
|
|
@@ -206596,6 +206632,26 @@ async function fsCopy(state, body) {
|
|
|
206596
206632
|
await fs2.copyFile(from, to);
|
|
206597
206633
|
return ok({ path: to });
|
|
206598
206634
|
}
|
|
206635
|
+
async function fsUpload(state, parts) {
|
|
206636
|
+
const volume = parts.fields.volume;
|
|
206637
|
+
const targetDir = parts.fields.path ?? ".";
|
|
206638
|
+
const createDirs = parts.fields.create_dirs !== "false";
|
|
206639
|
+
const root2 = resolveVolumeRoot(state, volume);
|
|
206640
|
+
const dir = resolveUnderRoot(root2, targetDir);
|
|
206641
|
+
if (createDirs) await ensureDir(dir);
|
|
206642
|
+
const results = [];
|
|
206643
|
+
for (const file of parts.files) {
|
|
206644
|
+
const target = resolveUnderRoot(root2, path2.join(targetDir, file.filename));
|
|
206645
|
+
await fs2.writeFile(target, file.data);
|
|
206646
|
+
results.push({
|
|
206647
|
+
fieldname: "file",
|
|
206648
|
+
filename: file.filename,
|
|
206649
|
+
path: target,
|
|
206650
|
+
size: file.data.length
|
|
206651
|
+
});
|
|
206652
|
+
}
|
|
206653
|
+
return ok({ files: results });
|
|
206654
|
+
}
|
|
206599
206655
|
|
|
206600
206656
|
// src/routes/git.ts
|
|
206601
206657
|
import { execFile } from "node:child_process";
|
|
@@ -262410,6 +262466,10 @@ async function sandagentRun(req, res, env2) {
|
|
|
262410
262466
|
function createDaemon(config) {
|
|
262411
262467
|
const router = new DaemonRouter({ root: config.root });
|
|
262412
262468
|
const env2 = process.env;
|
|
262469
|
+
const state = {
|
|
262470
|
+
root: config.root,
|
|
262471
|
+
volumesRoot: `${config.root}/volumes`
|
|
262472
|
+
};
|
|
262413
262473
|
return http3.createServer(async (req, res) => {
|
|
262414
262474
|
const method = req.method ?? "GET";
|
|
262415
262475
|
const url = new URL2(
|
|
@@ -262421,6 +262481,29 @@ function createDaemon(config) {
|
|
|
262421
262481
|
const body2 = JSON.parse(await readBody(req) || "{}");
|
|
262422
262482
|
return sandagentRun(body2, res, env2);
|
|
262423
262483
|
}
|
|
262484
|
+
if (method === "POST" && pathname === "/api/fs/upload") {
|
|
262485
|
+
try {
|
|
262486
|
+
const ct2 = req.headers["content-type"] ?? "";
|
|
262487
|
+
if (!ct2.includes("multipart/form-data")) {
|
|
262488
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
262489
|
+
res.end(
|
|
262490
|
+
JSON.stringify(fail("content-type must be multipart/form-data"))
|
|
262491
|
+
);
|
|
262492
|
+
return;
|
|
262493
|
+
}
|
|
262494
|
+
const raw = await readBodyRaw(req);
|
|
262495
|
+
const parts = parseMultipart(ct2, raw);
|
|
262496
|
+
const result2 = await fsUpload(state, parts);
|
|
262497
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
262498
|
+
res.end(JSON.stringify(result2));
|
|
262499
|
+
} catch (err) {
|
|
262500
|
+
const status2 = err instanceof AppError ? err.status : 500;
|
|
262501
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
262502
|
+
res.writeHead(status2, { "Content-Type": "application/json" });
|
|
262503
|
+
res.end(JSON.stringify(fail(msg)));
|
|
262504
|
+
}
|
|
262505
|
+
return;
|
|
262506
|
+
}
|
|
262424
262507
|
const params = method === "GET" ? Object.fromEntries(url.searchParams) : JSON.parse(await readBody(req) || "{}");
|
|
262425
262508
|
const result = await router.handle(method, pathname, params);
|
|
262426
262509
|
const status = result?.status ?? 404;
|
|
@@ -262437,6 +262520,14 @@ function readBody(req) {
|
|
|
262437
262520
|
req.on("error", reject);
|
|
262438
262521
|
});
|
|
262439
262522
|
}
|
|
262523
|
+
function readBodyRaw(req) {
|
|
262524
|
+
return new Promise((resolve14, reject) => {
|
|
262525
|
+
const chunks = [];
|
|
262526
|
+
req.on("data", (c) => chunks.push(c));
|
|
262527
|
+
req.on("end", () => resolve14(Buffer.concat(chunks)));
|
|
262528
|
+
req.on("error", reject);
|
|
262529
|
+
});
|
|
262530
|
+
}
|
|
262440
262531
|
|
|
262441
262532
|
// src/cli.ts
|
|
262442
262533
|
var host = process.env.SANDAGENT_DAEMON_HOST ?? "0.0.0.0";
|
package/dist/index.js
CHANGED
|
@@ -206591,6 +206591,26 @@ async function fsCopy(state, body) {
|
|
|
206591
206591
|
await fs2.copyFile(from, to);
|
|
206592
206592
|
return ok({ path: to });
|
|
206593
206593
|
}
|
|
206594
|
+
async function fsUpload(state, parts) {
|
|
206595
|
+
const volume = parts.fields.volume;
|
|
206596
|
+
const targetDir = parts.fields.path ?? ".";
|
|
206597
|
+
const createDirs = parts.fields.create_dirs !== "false";
|
|
206598
|
+
const root = resolveVolumeRoot(state, volume);
|
|
206599
|
+
const dir = resolveUnderRoot(root, targetDir);
|
|
206600
|
+
if (createDirs) await ensureDir(dir);
|
|
206601
|
+
const results = [];
|
|
206602
|
+
for (const file of parts.files) {
|
|
206603
|
+
const target = resolveUnderRoot(root, path2.join(targetDir, file.filename));
|
|
206604
|
+
await fs2.writeFile(target, file.data);
|
|
206605
|
+
results.push({
|
|
206606
|
+
fieldname: "file",
|
|
206607
|
+
filename: file.filename,
|
|
206608
|
+
path: target,
|
|
206609
|
+
size: file.data.length
|
|
206610
|
+
});
|
|
206611
|
+
}
|
|
206612
|
+
return ok({ files: results });
|
|
206613
|
+
}
|
|
206594
206614
|
|
|
206595
206615
|
// src/routes/git.ts
|
|
206596
206616
|
import { execFile } from "node:child_process";
|
|
@@ -206764,6 +206784,42 @@ var DaemonRouter = class {
|
|
|
206764
206784
|
import * as http3 from "node:http";
|
|
206765
206785
|
import { URL as URL2 } from "node:url";
|
|
206766
206786
|
|
|
206787
|
+
// src/multipart.ts
|
|
206788
|
+
function parseMultipart(contentType, body) {
|
|
206789
|
+
const match2 = contentType.match(/boundary=(?:"([^"]+)"|([^\s;]+))/);
|
|
206790
|
+
if (!match2) throw new Error("missing multipart boundary");
|
|
206791
|
+
const boundary = match2[1] ?? match2[2];
|
|
206792
|
+
const delimiter2 = Buffer.from(`--${boundary}`);
|
|
206793
|
+
const fields = {};
|
|
206794
|
+
const files = [];
|
|
206795
|
+
let start = body.indexOf(delimiter2) + delimiter2.length;
|
|
206796
|
+
while (start < body.length) {
|
|
206797
|
+
const end = body.indexOf(delimiter2, start);
|
|
206798
|
+
if (end === -1) break;
|
|
206799
|
+
const part = body.subarray(start, end);
|
|
206800
|
+
const partStart = part[0] === 13 && part[1] === 10 ? 2 : 0;
|
|
206801
|
+
const headerEnd = part.indexOf("\r\n\r\n", partStart);
|
|
206802
|
+
if (headerEnd === -1) {
|
|
206803
|
+
start = end + delimiter2.length;
|
|
206804
|
+
continue;
|
|
206805
|
+
}
|
|
206806
|
+
const headerStr = part.subarray(partStart, headerEnd).toString("utf-8");
|
|
206807
|
+
let data = part.subarray(headerEnd + 4);
|
|
206808
|
+
if (data.length >= 2 && data[data.length - 2] === 13 && data[data.length - 1] === 10) {
|
|
206809
|
+
data = data.subarray(0, data.length - 2);
|
|
206810
|
+
}
|
|
206811
|
+
const nameMatch = headerStr.match(/name="([^"]+)"/);
|
|
206812
|
+
const filenameMatch = headerStr.match(/filename="([^"]+)"/);
|
|
206813
|
+
if (filenameMatch && nameMatch) {
|
|
206814
|
+
files.push({ filename: filenameMatch[1], data: Buffer.from(data) });
|
|
206815
|
+
} else if (nameMatch) {
|
|
206816
|
+
fields[nameMatch[1]] = data.toString("utf-8");
|
|
206817
|
+
}
|
|
206818
|
+
start = end + delimiter2.length;
|
|
206819
|
+
}
|
|
206820
|
+
return { fields, files };
|
|
206821
|
+
}
|
|
206822
|
+
|
|
206767
206823
|
// ../../packages/runner-claude/dist/ai-sdk-stream.js
|
|
206768
206824
|
import { appendFileSync, existsSync, unlinkSync } from "node:fs";
|
|
206769
206825
|
import { join as join3 } from "node:path";
|
|
@@ -262409,6 +262465,10 @@ async function sandagentRun(req, res, env2) {
|
|
|
262409
262465
|
function createDaemon(config) {
|
|
262410
262466
|
const router = new DaemonRouter({ root: config.root });
|
|
262411
262467
|
const env2 = process.env;
|
|
262468
|
+
const state = {
|
|
262469
|
+
root: config.root,
|
|
262470
|
+
volumesRoot: `${config.root}/volumes`
|
|
262471
|
+
};
|
|
262412
262472
|
return http3.createServer(async (req, res) => {
|
|
262413
262473
|
const method = req.method ?? "GET";
|
|
262414
262474
|
const url = new URL2(
|
|
@@ -262420,6 +262480,29 @@ function createDaemon(config) {
|
|
|
262420
262480
|
const body2 = JSON.parse(await readBody(req) || "{}");
|
|
262421
262481
|
return sandagentRun(body2, res, env2);
|
|
262422
262482
|
}
|
|
262483
|
+
if (method === "POST" && pathname === "/api/fs/upload") {
|
|
262484
|
+
try {
|
|
262485
|
+
const ct2 = req.headers["content-type"] ?? "";
|
|
262486
|
+
if (!ct2.includes("multipart/form-data")) {
|
|
262487
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
262488
|
+
res.end(
|
|
262489
|
+
JSON.stringify(fail("content-type must be multipart/form-data"))
|
|
262490
|
+
);
|
|
262491
|
+
return;
|
|
262492
|
+
}
|
|
262493
|
+
const raw = await readBodyRaw(req);
|
|
262494
|
+
const parts = parseMultipart(ct2, raw);
|
|
262495
|
+
const result2 = await fsUpload(state, parts);
|
|
262496
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
262497
|
+
res.end(JSON.stringify(result2));
|
|
262498
|
+
} catch (err) {
|
|
262499
|
+
const status2 = err instanceof AppError ? err.status : 500;
|
|
262500
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
262501
|
+
res.writeHead(status2, { "Content-Type": "application/json" });
|
|
262502
|
+
res.end(JSON.stringify(fail(msg)));
|
|
262503
|
+
}
|
|
262504
|
+
return;
|
|
262505
|
+
}
|
|
262423
262506
|
const params = method === "GET" ? Object.fromEntries(url.searchParams) : JSON.parse(await readBody(req) || "{}");
|
|
262424
262507
|
const result = await router.handle(method, pathname, params);
|
|
262425
262508
|
const status = result?.status ?? 404;
|
|
@@ -262436,6 +262519,14 @@ function readBody(req) {
|
|
|
262436
262519
|
req.on("error", reject);
|
|
262437
262520
|
});
|
|
262438
262521
|
}
|
|
262522
|
+
function readBodyRaw(req) {
|
|
262523
|
+
return new Promise((resolve14, reject) => {
|
|
262524
|
+
const chunks = [];
|
|
262525
|
+
req.on("data", (c) => chunks.push(c));
|
|
262526
|
+
req.on("end", () => resolve14(Buffer.concat(chunks)));
|
|
262527
|
+
req.on("error", reject);
|
|
262528
|
+
});
|
|
262529
|
+
}
|
|
262439
262530
|
export {
|
|
262440
262531
|
DaemonRouter,
|
|
262441
262532
|
createDaemon
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal multipart/form-data parser — zero external dependencies.
|
|
3
|
+
* Parses raw body buffer using the boundary from Content-Type header.
|
|
4
|
+
*/
|
|
5
|
+
export interface ParsedMultipart {
|
|
6
|
+
fields: Record<string, string>;
|
|
7
|
+
files: Array<{
|
|
8
|
+
filename: string;
|
|
9
|
+
data: Buffer;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseMultipart(contentType: string, body: Buffer): ParsedMultipart;
|
|
13
|
+
//# sourceMappingURL=multipart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../src/multipart.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,eAAe,CAiDjB"}
|
package/dist/nextjs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,IASzD,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAkD/C"}
|
package/dist/nextjs.js
CHANGED
|
@@ -206414,6 +206414,42 @@ var require_lib4 = __commonJS({
|
|
|
206414
206414
|
}
|
|
206415
206415
|
});
|
|
206416
206416
|
|
|
206417
|
+
// src/multipart.ts
|
|
206418
|
+
function parseMultipart(contentType, body) {
|
|
206419
|
+
const match2 = contentType.match(/boundary=(?:"([^"]+)"|([^\s;]+))/);
|
|
206420
|
+
if (!match2) throw new Error("missing multipart boundary");
|
|
206421
|
+
const boundary = match2[1] ?? match2[2];
|
|
206422
|
+
const delimiter2 = Buffer.from(`--${boundary}`);
|
|
206423
|
+
const fields = {};
|
|
206424
|
+
const files = [];
|
|
206425
|
+
let start = body.indexOf(delimiter2) + delimiter2.length;
|
|
206426
|
+
while (start < body.length) {
|
|
206427
|
+
const end = body.indexOf(delimiter2, start);
|
|
206428
|
+
if (end === -1) break;
|
|
206429
|
+
const part = body.subarray(start, end);
|
|
206430
|
+
const partStart = part[0] === 13 && part[1] === 10 ? 2 : 0;
|
|
206431
|
+
const headerEnd = part.indexOf("\r\n\r\n", partStart);
|
|
206432
|
+
if (headerEnd === -1) {
|
|
206433
|
+
start = end + delimiter2.length;
|
|
206434
|
+
continue;
|
|
206435
|
+
}
|
|
206436
|
+
const headerStr = part.subarray(partStart, headerEnd).toString("utf-8");
|
|
206437
|
+
let data = part.subarray(headerEnd + 4);
|
|
206438
|
+
if (data.length >= 2 && data[data.length - 2] === 13 && data[data.length - 1] === 10) {
|
|
206439
|
+
data = data.subarray(0, data.length - 2);
|
|
206440
|
+
}
|
|
206441
|
+
const nameMatch = headerStr.match(/name="([^"]+)"/);
|
|
206442
|
+
const filenameMatch = headerStr.match(/filename="([^"]+)"/);
|
|
206443
|
+
if (filenameMatch && nameMatch) {
|
|
206444
|
+
files.push({ filename: filenameMatch[1], data: Buffer.from(data) });
|
|
206445
|
+
} else if (nameMatch) {
|
|
206446
|
+
fields[nameMatch[1]] = data.toString("utf-8");
|
|
206447
|
+
}
|
|
206448
|
+
start = end + delimiter2.length;
|
|
206449
|
+
}
|
|
206450
|
+
return { fields, files };
|
|
206451
|
+
}
|
|
206452
|
+
|
|
206417
206453
|
// src/routes/fs.ts
|
|
206418
206454
|
import * as fs2 from "node:fs/promises";
|
|
206419
206455
|
import * as path2 from "node:path";
|
|
@@ -206591,6 +206627,26 @@ async function fsCopy(state, body) {
|
|
|
206591
206627
|
await fs2.copyFile(from, to);
|
|
206592
206628
|
return ok({ path: to });
|
|
206593
206629
|
}
|
|
206630
|
+
async function fsUpload(state, parts) {
|
|
206631
|
+
const volume = parts.fields.volume;
|
|
206632
|
+
const targetDir = parts.fields.path ?? ".";
|
|
206633
|
+
const createDirs = parts.fields.create_dirs !== "false";
|
|
206634
|
+
const root = resolveVolumeRoot(state, volume);
|
|
206635
|
+
const dir = resolveUnderRoot(root, targetDir);
|
|
206636
|
+
if (createDirs) await ensureDir(dir);
|
|
206637
|
+
const results = [];
|
|
206638
|
+
for (const file of parts.files) {
|
|
206639
|
+
const target = resolveUnderRoot(root, path2.join(targetDir, file.filename));
|
|
206640
|
+
await fs2.writeFile(target, file.data);
|
|
206641
|
+
results.push({
|
|
206642
|
+
fieldname: "file",
|
|
206643
|
+
filename: file.filename,
|
|
206644
|
+
path: target,
|
|
206645
|
+
size: file.data.length
|
|
206646
|
+
});
|
|
206647
|
+
}
|
|
206648
|
+
return ok({ files: results });
|
|
206649
|
+
}
|
|
206594
206650
|
|
|
206595
206651
|
// src/routes/git.ts
|
|
206596
206652
|
import { execFile } from "node:child_process";
|
|
@@ -262415,6 +262471,10 @@ function createNextHandler(opts) {
|
|
|
262415
262471
|
const router = new DaemonRouter({ root: opts.root });
|
|
262416
262472
|
const env2 = process.env;
|
|
262417
262473
|
const prefix = opts.prefix ?? "/api/daemon";
|
|
262474
|
+
const state = {
|
|
262475
|
+
root: opts.root,
|
|
262476
|
+
volumesRoot: `${opts.root}/volumes`
|
|
262477
|
+
};
|
|
262418
262478
|
return async (req) => {
|
|
262419
262479
|
const url = new URL(req.url);
|
|
262420
262480
|
const pathname = url.pathname.startsWith(prefix) ? url.pathname.slice(prefix.length) || "/" : url.pathname;
|
|
@@ -262423,6 +262483,25 @@ function createNextHandler(opts) {
|
|
|
262423
262483
|
const body = await req.json().catch(() => ({}));
|
|
262424
262484
|
return codingRunStream(body, env2);
|
|
262425
262485
|
}
|
|
262486
|
+
if (method === "POST" && pathname === "/api/fs/upload") {
|
|
262487
|
+
try {
|
|
262488
|
+
const ct2 = req.headers.get("content-type") ?? "";
|
|
262489
|
+
if (!ct2.includes("multipart/form-data")) {
|
|
262490
|
+
return Response.json(
|
|
262491
|
+
fail("content-type must be multipart/form-data"),
|
|
262492
|
+
{ status: 400 }
|
|
262493
|
+
);
|
|
262494
|
+
}
|
|
262495
|
+
const raw = Buffer.from(await req.arrayBuffer());
|
|
262496
|
+
const parts = parseMultipart(ct2, raw);
|
|
262497
|
+
const result2 = await fsUpload(state, parts);
|
|
262498
|
+
return Response.json(result2);
|
|
262499
|
+
} catch (err) {
|
|
262500
|
+
const status = err instanceof AppError ? err.status : 500;
|
|
262501
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
262502
|
+
return Response.json(fail(msg), { status });
|
|
262503
|
+
}
|
|
262504
|
+
}
|
|
262426
262505
|
const params = method === "GET" ? Object.fromEntries(url.searchParams) : await req.json().catch(() => ({}));
|
|
262427
262506
|
const result = await router.handle(method, pathname, params);
|
|
262428
262507
|
if (!result) {
|
package/dist/routes/fs.d.ts
CHANGED
|
@@ -79,5 +79,27 @@ export declare function fsMove(state: AppState, body: MoveCopyBody): Promise<imp
|
|
|
79
79
|
export declare function fsCopy(state: AppState, body: MoveCopyBody): Promise<import("../utils.js").ApiEnvelope<{
|
|
80
80
|
path: string;
|
|
81
81
|
}>>;
|
|
82
|
+
export interface UploadedFile {
|
|
83
|
+
fieldname: string;
|
|
84
|
+
filename: string;
|
|
85
|
+
path: string;
|
|
86
|
+
size: number;
|
|
87
|
+
}
|
|
88
|
+
export interface UploadResult {
|
|
89
|
+
files: UploadedFile[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Handle multipart/form-data file upload.
|
|
93
|
+
* Expects form field "path" for target directory and one or more "file" parts.
|
|
94
|
+
*/
|
|
95
|
+
export declare function fsUpload(state: AppState, parts: {
|
|
96
|
+
fields: Record<string, string>;
|
|
97
|
+
files: Array<{
|
|
98
|
+
filename: string;
|
|
99
|
+
data: Buffer;
|
|
100
|
+
}>;
|
|
101
|
+
}): Promise<import("../utils.js").ApiEnvelope<{
|
|
102
|
+
files: UploadedFile[];
|
|
103
|
+
}>>;
|
|
82
104
|
export {};
|
|
83
105
|
//# sourceMappingURL=fs.d.ts.map
|
package/dist/routes/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAU5C,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAID,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;;;MAkBzD;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;IAKzD;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;;IASzD;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;IAQ3D;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;UAMhD,MAAM;UACN,MAAM;YACJ,OAAO;UACT,MAAM;MA4Bf;AAED,wBAAsB,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;;;IAQ7D;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;;IAQ9D;AAED,wBAAsB,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;;IAK7D;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU;;IAU/D;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY;;IAS/D;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY;;IAS/D"}
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAU5C,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,SAAS;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAID,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;;;MAkBzD;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;IAKzD;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;;IASzD;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;;;IAQ3D;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;UAMhD,MAAM;UACN,MAAM;YACJ,OAAO;UACT,MAAM;MA4Bf;AAED,wBAAsB,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;;;IAQ7D;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;;IAQ9D;AAED,wBAAsB,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;;IAK7D;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU;;IAU/D;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY;;IAS/D;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY;;IAS/D;AAID,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE;IACL,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;;IAuBF"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAQlC,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,CA2D9D"}
|